Skip to main content

The charter

The charter is your team's written async-communication rules. Ever Async cites it in every nudge, posts it when it is invited to a channel, and returns it from /async charter.

The point is authority. "Your message was unclear" is a machine's opinion. "Our charter says: if it has a link, include the link" is the team holding itself to something it already agreed to. Same nudge, completely different reception.

The defaults​

Ship with these three and change them later — they are the rules the product was designed around:

[charter]
rules = [
"Give context immediately — explain what you need in the first message; link a document for anything complex.",
"Tag the relevant people — untagged messages get missed in busy channels.",
"If it has a link, include the link — the ticket, the PR, the doc, the conversation you are referring to.",
]

Each rule maps to something the classifier can actually detect:

RuleDetected as
Give context immediatelyMissing::Details — naked greeting, contentless help plea
Tag the relevant peopleMissing::Recipients — asks for action, mentions nobody
If it has a link, include the linkMissing::Link { artifact } — a vague reference with no URL

That correspondence is the design constraint on a good charter: write rules a reader could check. "Be considerate" cannot be nudged; "link the ticket" can.

Writing your own​

Replace the array with your team's actual rules. Keep them short — they are quoted inside an ephemeral message, not read as a policy document.

[charter]
rules = [
"Lead with the ask. First line says what you need and by when.",
"Link the artifact — PR, ticket, doc, dashboard. No 'the thing from yesterday'.",
"Name a person. @here is not a recipient.",
"If it needs a call, propose two slots instead of asking for availability.",
]

Rules are rendered as a bullet list wherever they appear:

• Lead with the ask. First line says what you need and by when.
• Link the artifact — PR, ticket, doc, dashboard. No 'the thing from yesterday'.
…

Where the charter shows up​

On join. When Ever Async is invited to a conversation it posts the charter publicly, once. Members learn the rules the bot will cite before it ever nudges anyone — nobody's first contact with the tool should be being corrected by it.

In every nudge. The rules travel with the reason, so the author sees which agreement the message missed.

In the AI prompt. The charter is passed to the AI provider on both classify and suggest_rewrite. This is what makes the AI layer team-specific: it is not judging against a generic idea of good writing, it is judging against your rules. Change the charter and the model's verdicts change with it.

On demand.

/async charter

Replies privately with the charter currently in force.

Config default vs. stored override​

There are two layers, and the stored one wins:

  • [charter] in everasync.toml is the default — what a fresh install, and any tenant that has never saved one, cites.
  • A charter saved at runtime is stored in the database against that tenant and takes precedence from then on, for every conversation in it.

The pipeline reads the stored charter on every nudge, so an edit applies immediately without a restart. If the lookup fails for any reason, it falls back to the configured default and logs a warning — a broken read never silences a nudge.

Editing at runtime

GET /api/v1/charter returns the rules in force and PUT /api/v1/charter replaces them — both are served, and the dashboard uses exactly these.

curl -s http://localhost:8100/api/v1/charter
curl -X PUT http://localhost:8100/api/v1/charter \
-H 'content-type: application/json' \
-d '{"rules":["Lead with the ask.","Link the artifact."]}' # → 204 No Content

Two validations, both 400, and neither disturbs what is already stored:

  • An empty rules array is rejected. The charter is the justification a nudge carries, so saving none would silently turn every nudge into an unexplained correction.
  • A rule longer than 500 characters is rejected. A rule is a sentence a nudge quotes back to an author, not a policy document.

The write needs the operator role (action charter.write) when [auth] mode = "authenticated"; the read needs viewer. Under the default local_trusted mode both pass with no credential.

One charter per tenant​

The charter is tenant-wide, not per-channel. GET/PUT /api/v1/charter act on the tenant of the verified principal — never on a tenant named in the request — and the stored row is keyed (tenant, 'charter'), so one customer's charter can never overwrite another's.

For a self-hosted install that is a distinction without a difference: there is one implicit tenant, and the charter is simply workspace-wide. Two teams inside one self-hosted deployment that genuinely need different rules should run two deployments — per-channel charters would turn every nudge into a question of which rules applied.

Per-channel behaviour is a different thing, and that is what policy is for: on/off, sensitivity, delay and quiet hours are all per conversation.