ROST implementation methodCompass authoring guideInstall the ROST implementation SkillROST implementation evidence and groundingAICOS chat guideResponsibility Graph playbookCharter design playbookCharter authoring deep-diveAgent staffing playbookAdd agents to your Responsibility GraphDesign a custom agentAgent builder guideAgent definition import guideHow agents workTool access and vaultAvailable tools guideCLI and MCP installation guideSkill builder guideAgent Skill authoring guideAgent Skill setup guideAgent reference mapCascade guideSignal guideFriction guideSteward queue guideBilling and pricing guideConfirmations and human gates guideGoverned command capability matrixSettings guideMembers and invites guideNotifications guideLocal runner guideStock agents guideTroubleshooting guideAI model data handling guideSecurity model guideSync rhythm playbookMigration and legal notes
Docs

Charter design playbook

How to draft strong Charters that work for human, agent, and hybrid seats.

charter designstaffing

On this page

  • Required Charter sections
  • Responsibility quality bar
  • Agent and hybrid Charters
  • Operate Charters from CLI or MCP
  • When a declared Signal already exists
  • When to stop for confirmation
  • Approval rule

A Charter is a seat's operating contract. It must be specific enough for a human to understand the job and strict enough for an agent to operate safely.

Required Charter sections

  • Purpose: why this seat exists.
  • Responsibilities: the recurring outcomes the seat owns.
  • Autonomous scope: what the seat may do without asking.
  • Approval scope: what requires human confirmation.
  • Must-escalate conditions: what the seat must not decide alone.
  • Measurables: how the seat knows it is on track.
  • Tools and data: which systems the seat may use.
  • Reporting rhythm: what the seat reports into Signal, Friction, Cascade, and Sync.

Responsibility quality bar

Responsibilities should be outcomes, not vague activities. "Keep vendor payments clean and on time" is stronger than "help with accounting." The test is whether a measurable, escalation, or handoff can be attached to the responsibility.

Agent and hybrid Charters

Agent Charters need tighter boundaries than human Charters. They must separate autonomous work from approval-required work, and they must name conditions that always escalate.

Hybrid Charters should split the work: what the human remains accountable for, what the agent performs, and where the handoff happens.

Operate Charters from CLI or MCP

List and read first, then draft, then leave activation to a human.

  • List: rost command charter.list --json '{}' or rost_list_charters (scope: tenant). Read one with charter.get / rost_get_charter.
  • Draft one: rost command charter.draft --json '{"seat_id":"<seat-id>"}' (scope: seat or tenant-admin); draft every eligible seat with charter.draft_all / rost_draft_all_charters. Draft commands are not gated — they produce a draft charter_version_id. charter.draft_all drafts every active or vacant non-reserved seat — a vacant seat can receive a conservative draft Charter before it is staffed, since Charters attach to seats, not people, and drafting is not activation: Charter activation stays a separate, human-gated step (charter.approve) whether or not the seat is staffed. Reserved system paths (e.g. system.ops, Forge role seats) are excluded entirely, before both drafting and skip accounting — they never appear in drafted, skipped, or skipped_seats. It returns the real result: { drafted, skipped, draft_ids, skipped_seats: [{ seat_id, reason }], effect } where reason is seat_not_active (the seat is still a graph-authoring draft, not yet active or vacant) or charter_exists (it already has a draft/active Charter). Read skipped_seats to see why a freshly-built graph drafted fewer Charters than it has seats — among non-reserved seats.
  • Never read a governed command's success as "it changed something." Every converted mutating command carries an effect block — { attempted, applied, skipped, outcome, skips: [{ target_kind, target_id, reason }], skips_omitted }, where outcome is applied, partial, or none, and skips.length + skips_omitted always equals skipped (skips is capped at 50 entries, so a very large sweep itemises the first 50 and counts the rest — the arithmetic still adds up) — so you can branch on it without knowing any one command's field names. Read it where your surface actually delivers it: response.output.effect over HTTP, and effect on the unwrapped command output in CLI and MCP. (The executor also carries a copy on its internal result envelope, but the HTTP adapter does not re-emit it, so response.effect is undefined — do not read it there.) outcome: "none" means the call succeeded and changed nothing; skips tells you why, per target, as stable codes you can branch on rather than prose. Check it and self-correct in the same run instead of reporting a build that did nothing: a second charter.draft_all over a graph whose seats already have a draft or active Charter returns outcome: "none" with one charter_exists skip per seat, and a graph whose seats are still graph-authoring draft seats returns outcome: "none" with one seat_not_active skip per seat. (A freshly built graph of vacant seats is not one of these cases — vacant seats are eligible and do get drafted, per the bullet above.) A converted command that skipped work without naming a reason fails its own output validation, so a silent zero-effect success is not a shape *those* commands can return. This is not yet an API-wide guarantee — most mutating commands are not converted yet, and an unconverted bulk or sweep command still returns a bare success that changed nothing: confirmation.dismiss_stale with no expired confirmations returns { dismissed_count: 0, dismissed_ids: [] } with no effect at all. Treat a missing effect as "this command cannot tell you", never as "it worked" — for those, verify against live state before reporting the step done. Input that violates a schema is reported the same way rather than swallowed, naming the offending field path, the stable issue code, and the violated limit — e.g. a 6-principle Compass against max(5) gives { path: "doc.principles", code: "too_big", limit: 5, expected: "<= 5", received: "array (6 items)" }. Each violation also carries expected (what the schema declares at that spot — a type, format, allowed values, or bound; for a cross-field or business-rule check it is the fixed marker a value satisfying the schema's custom check (see message), telling you to read message for the authored specifics) and received (the KIND of value you sent — string, number, array (6 items), missing — or, for undeclared extra keys, their proven, secret-gated names — never the value itself). The field sits at a different place on each surface: CLI and HTTP callers read it at error.details.violations (alongside the legacy fieldErrors), while MCP tool callers read violations at the top level of the error result, next to code and help — MCP results have no details envelope, so details.violations is undefined there. Paths are real JSON paths into YOUR OWN submitted payload: a caller-supplied key (e.g. inside a capabilities or headers map) is echoed exactly, and an array position reports as xs[3].on, because a path into input you supplied tells you nothing you do not already hold. A segment shows as * only when the server could not prove it walks your payload — a segment no schema declares and your payload does not contain (a refinement can report a path that was never in your input) — or when the segment name is itself credential-shaped and withheld as secret material. So read * as "withheld, unproven", not as "this was your key" — the field names around it are still exact, and the code still tells you which constraint failed. limit is likewise only present when the schema itself declares that bound, so its absence means "not proven", not "no bound". The error envelope's help names the exact next step — rost command schema <id> prints the full input contract, and when the payload came from --input-file, fix the file and re-run it.
  • Edit a draft: charter.update_draft / rost_update_charter_draft with the charter_version_id and the new doc.

In the app, /charters is both a drafting queue and a governance library. It shows active Charters, draft amendments, seats missing Charters, unsigned manifests, and steward-review gaps. /charters/builder is the seat picker for Charter Builder: start there when you need to draft a missing Charter, continue an amendment, or review which seats are still missing operating contracts. Opening a live Charter is a review mode: read the active version, inspect its governance state, and open an amendment draft when the job needs to change. Active Charter content is never silently edited in place. Both charter.draft_all triggers on this page — the "Draft missing Charters" empty-queue card and the "Draft gaps" header CTA — render the run's effect distinctly rather than a bare success: a full run (outcome: "applied") shows an ordinary "Done" state, a run that skipped some seats ("partial") shows "Partial effect" plus how many seats were skipped and why (the app aggregates them into product copy — "17 skipped: seat not active" — while CLI and MCP callers still get the per-target target_id/reason values from the contract), and a run that drafted nothing ("none") shows "No effect" the same way — so a human running this from the UI gets the same self-correction signal the effect contract gives a CLI/MCP/HTTP caller, above.

Daily and weekly measurable rows can carry Signal metadata: unit, numeric target, direction, cadence, and source. When a human approves a Charter, those structured measurables materialize into tenant Signal definitions for that seat. Free-text, monthly, and quarterly measurable prose remains accountability text until a human converts it into a supported structured Signal or creates the Signal separately.

When a declared Signal already exists

Preview first. Approval requires an explicit choice for a declaration that COLLIDES with an active exact-name Signal and is not already linked to one: adopt it if it is already owned by the Charter Seat, move it if another Seat owns it, or deliberately create a second Signal. A declaration nothing matches is created automatically, and one already linked to a Signal takes no choice. Adoption and movement preserve the Signal ID, readings, and compatible goal bindings.

charter.preview_signal_reconciliation is the owner-only dry run: per declaration it reports whether the declaration is already linked, which existing Signals match its name, who owns them, how much reading history and how many goal links they carry, the action that fits, and what that action would cost. charter.reconcile_signals then approves the Charter carrying one explicit decision per COLLIDING, UNLINKED declaration, and takes the edit_revision the preview returned so a draft edited in between is refused rather than reconciled against a stale reading.

Both commands are owner-only. Ordinary charter.approve stays seat-scoped and cannot take ownership of another Seat's Signal: when a declared name collides, it refuses and says an owner must decide. A Signal that already sets the status of a goal owned by another Seat cannot be moved until that goal's status driver is rebound — informative goal indicators survive a move.

When to stop for confirmation

charter.approve, charter.skip, charter.apply_seat_type_recommendation, and charter.sign_manifest are human_required. Over MCP rost_approve_charter returns a pending confirmation, not an activation. Approving a Charter, signing a permission manifest, or expanding authority is a human act recorded against decided_by. An agent prepares the draft and surfaces the approve link; it does not approve. See the confirmations guide.

Approval rule

Drafting can be assisted by agents. Activation is a human decision. When authority, tool permissions, credentials, or go-live status changes, route through confirmation.

Version

2026-07-31.2

Used by

  • human
  • cli
  • mcp
  • in app agent

API

/api/reference/files/charter-design-playbook