WRITING_DOCS.md
Rama develop · réplica de lectura del repositorio original.
# Writing Parlant Docs
This guide collects the rules we apply to everything under `docs/`. It exists
because the same problems keep coming back in review: prose that reads like
marketing, examples that can't actually run, terms used before they're
introduced, and claims that no longer match the code.
Read it before writing or editing a doc. The checklist at the end is the
short version.
## Who you are writing for
Two audiences read these docs, and both matter:
- **Integrators** building on Parlant. They know the product as "Parlant".
They do not know our module names, class names, or internal architecture.
- **AI agents** writing Parlant code for those integrators. They copy our
snippets and imitate our patterns.
The second audience raises the stakes on examples. A half-designed snippet
does not just confuse one reader; it gets reproduced at scale.
## Voice: Parlant is the actor
In user-facing docs, Parlant does things. Internal component names are
architecture, not vocabulary.
```
Don't: The matcher routes every usable rule by cost tier.
Compaction is the compass engine's answer to long sessions.
Do: Parlant routes every usable rule by cost tier.
Compaction is how Parlant handles long sessions.
```
Class and function names stay as they are inside code blocks and when you
name a real API (`p.CompassOptimizationPolicy`, `CompassHooks`). The rule is
about prose.
Internal docs (`docs/internal/`) may name components freely — that is their
subject.
**Never frame behavior against a version the reader never used.** "There is
no longer a `condition` field" tells a new reader nothing except that they
missed something. State what is true now.
**Document only what ships here.** `docs/*.md` describes this repository. If a
capability is not in this tree, it does not belong in these docs.
## Register: plain, human, and confident
Write in plain English, roughly CEFR B2: short sentences, common words, one
idea per sentence. Many readers are not native English speakers, and dense
idiomatic prose costs them comprehension.
Explain like a person explaining something simply — not like a tech marketer.
The failure mode to avoid is the clipped fragment used as a slogan:
```
Don't: Same process, same PID, brand-new server.
Do: The process and PID stay the same; the server inside it starts
completely fresh.
Don't: Usage is tokens; cost needs prices.
Do: Usage records count tokens. To turn tokens into cost, Parlant needs
prices.
Don't: **Same database, no drift.** Your store reads the same user database
that issues the tokens.
Do: **Your tokens and your customers come from the same place, so they
can't disagree.** The backend that issues login tokens and the store
that answers "does this customer exist?" both read the same user
database. Any customer who can log in is a customer your store can
find.
```
Note what changed in that last one: the heading became a full statement, and
the body explains *why* instead of asserting a benefit.
**This is not a ban on confidence.** Strong positive claims in full sentences
are wanted:
- "Reviews are one of the most powerful features of Parlant."
- "Parlant is a unique harness, because it's built to offer flexibility for
engineers."
- "Parlant stores **no token state whatsoever**."
Keep the strength and the personality. Cut the fragments and the hype words
(`seamless`, `blazing`, `effortless`, `just works`, `magic`, and `simply`
used as filler).
Other register habits worth breaking:
- **Em-dash asides that carry the core meaning.** If the point is inside the
dashes, it deserves its own sentence.
- **Jargon where a plain word exists**: "seams" → "connection points",
"knobs" → "settings", "sugar" → "shortcuts", "by construction" → just give
the reason, "the stalest" → "the least relevant".
- **Invented verbs and metaphors**: "an evicted rule is *ledgered*", "the set
would *flap*", "resurrect every evicted rule". Say what happens.
- **Rhetorical questions nobody asked.** Do not open a section with "So what
happens when effort rises mid-turn?" if the reader had no reason to wonder
yet. Lead with the fact.
## Structure
The default shape of a feature doc is
**motivation → mental model → tutorial → reference**.
Readers arrive wanting to know what a thing is for before they can absorb how
it works.
**Cover when to use it, and the trade-offs, in the intro.** A feature doc
that only explains mechanics leaves the reader unsure whether they need the
feature at all. Prompt customization opens by saying it is for the rare case
where a built-in prompt says something you need said differently, then names
what you gain (precise control through a stable API) and what you take on
(you stop receiving upstream improvements to that section).
**General case first, special cases after.** The rate-limits section used to
open on indexing, which is one specific application. It now starts with what
a rate limit is and how it's configured, and indexing appears later as the
notable case.
**Don't dive too deep too early.** An overview that jumps into "the most
common customization is replacing the responder's general instructions" is
already three levels down. The overview establishes the concept; specifics
come next.
**Lead with the recommended path and rank the options.** `docs/customers.md`
lists four ways to provision customers, and says outright that plugging in
your own `CustomerStore` is the recommended one and why, before covering the
other three.
**Put the load-bearing distinction where the reader needs it.**
"Attachment means the inbound side only; the outbound side is just data a
tool returns" started life as a closing footnote, after a whole outbound
section a reader could spend confused. It belongs in the overview.
## Introduce concepts before using them
Read your doc as someone who arrived from a search engine. Every term you use
must already be defined, or defined right there.
Real misses we fixed:
- `docs/customers.md` used group ids like `grp_vip` in HTTP examples, but only
explained where groups come from near the end. Now the intro says groups are
defined in code with `server.create_group`, and that ids like `grp_vip` come
from those definitions.
- The same doc used "provisioning" as a section title without ever defining
it. It now opens: "Provisioning means making sure the customer record
exists before it is needed."
- An auth passage used `JWT`, `sub`, and "minted" cold. It now reads "a JWT —
the signed login token your backend creates for them" and "the customer id
in the token (its `sub` claim)".
- A section used "observation" as though the reader knew it. It now carries a
one-line definition plus a link to Policy Design.
Coining a term is fine; using a coined term as though it were standard is not.
"Ambient accessors" was our own invention, never defined anywhere, and it
appeared in eight places across the docs. We replaced it with the thing
itself: "the `.current` accessors", `p.Session.current`.
## Code examples
This is where the bar is highest, because agents copy these.
**Simulate the turn before you publish.** Ask: when this policy matches or
this tool runs, what exactly happens next? If the agent would have to
improvise, or the conversation dead-ends, the example is broken — no matter
how good it reads.
```python
# Don't: an instruction written for a human employee handbook. The agent has
# no approval channel and no concrete move, so the turn dead-ends.
await agent.create_policy(
content="When the customer requests a refund above $500, require manager approval.",
)
# Do: name the agent's actual in-conversation move.
await agent.create_policy(
content=(
"When the customer requests a refund above $500, don't process it. "
"Tell them it needs a manager's review and they'll hear back within "
"one business day."
),
labels=["manager-review"], # the managers' queue: sessions awaiting review
)
```
**A tool's `data` is what the model reads.** Return what the model should act
on or say — never an internal state dump you added to demonstrate an API.
```python
# Don't: the label list is internal bookkeeping; the model gains nothing.
return p.ToolResult(data={"labels": sorted(session.labels)})
# Do: tell the model what happened, so it can tell the customer.
return p.ToolResult(
data=(
"Transfer requested. Let the customer know a human agent "
"will pick the conversation up shortly."
),
control={"mode": "manual"},
)
```
**Close every loop.** If something waits on a human, show the mechanism (a
label as the queue, metadata as the reason, `mode="manual"` to park the
session) and how it resolves, even when the resolution happens out of band.
**Every field must earn its place.** A reviewer should not be able to ask
"why is that there?" and get no answer. We deleted a `note=` from an
observation example because the note only restated the condition. If a field
is in the example, the prose should be able to justify it.
**Prefer one coherent scenario per doc** over scattered toy cases.
`docs/sessions.md` uses human handoff throughout: the tool requests the
transfer, the labels form the operator queue, metadata records why, and the
HTTP section shows the same handoff from the remote side. Each example
reinforces the last.
**Don't inherit examples from a doc's previous version unquestioned.**
That is how filler survives rewrites.
**Handle the unhappy path the way real code should.** A tool that raises a
bare exception teaches the wrong pattern:
```python
# Don't: the model just sees an error and has to improvise.
raise Exception("No customer message yet")
# Do: tell the model what to do next.
return p.ToolResult(
data="No photo found on the customer's message. Ask them to attach a photo of the problem."
)
```
**Examples must run against the shipped API.** Check the signature before you
publish. Two real failures: an example called
`client.sessions.create_event(attachments=[...])` when no shipped client had
that parameter (it would `TypeError` for every reader), and several examples
declared a `context: p.ToolContext` first parameter that had become optional,
teaching an obsolete idiom.
## Accuracy: verify against the source
Every factual claim in a doc is a claim about code. Check it. Reviews of our
own docs turned up:
- Pins described as *not* feeding the classifier, when they train it like any
other signal *and* act as an override.
- `allow_greeting` documented as unused, when it starts a turn on session
creation.
- "Caption is the only field the agent model can read", when the model also
sees the filename and an image/file marker.
- A `tool_id` example showing `generate_invoice` when tool events carry the
qualified `built-in:generate_invoice`, so any frontend matching the doc
would match nothing.
- An undocumented permission (`create_customer_event_with_attachments`) whose
absence returns 403.
When a doc states a threshold, a default, or an ordering, cite it from the
code — not from memory or from the previous version of the doc. When you
describe a rename or a removal, grep for stragglers instead of assuming.
## Cross-references and filenames
- **File names are kebab-case**: `adaptive-effort.md`, not
`adaptive_effort.md`.
- **Link with titled markdown links**, not bare backticked paths:
`[Sessions](sessions.md)`, not `` `docs/sessions.md` ``. Use paths relative
to the linking file (`../sessions.md` from `docs/internal/`).
- **Anchors must resolve.** A link to `#managing-custom-signals` has to match
a real heading.
- **Point at the right doc.** A claim about tool-result metadata should link
to the tools doc, not to an internal doc that happens to mention it.
## Markdown mechanics
**Close every emphasis span on the line it opens.** Markdown does not render
`**bold` on one line and `text**` on the next. This bites constantly when you
reflow paragraphs:
```
Don't:
Note the consequence: **the classifier is trained on your signals versus
your anti-signals.**
Do:
Note the consequence: **the classifier is trained on your signals versus your anti-signals.**
```
The same applies to `*italics*`, `__underline__`, and `` `inline code` ``.
When a span makes the line too long, move the whole span to the next line
rather than splitting it.
Also: wide tables and long code lines need a container that scrolls, so keep
table cells short and put the explanation in the surrounding prose.
## Before you publish
- [ ] Every claim checked against the code, not remembered.
- [ ] Every code example simulated: what happens on the turn it runs?
- [ ] Every field in every example justified by the prose.
- [ ] Every term defined before it is used.
- [ ] Intro covers what the feature is for, when to use it, and the trade-offs.
- [ ] General case before special cases; recommended option first.
- [ ] Parlant is the actor; no internal component names in prose.
- [ ] Plain English, full sentences, no marketing fragments and no hype words.
- [ ] Confident claims kept — the pass should not flatten the doc's voice.
- [ ] All emphasis and code spans close on their own line.
- [ ] Links use titled markdown links, resolve, and point at the right doc.
- [ ] Nothing documented that does not ship in this tree.