Skip to content
SHAMPOO

Tool catalogue

107 documented tools.

Derived from the canonical SHAMPOO tool documentation (the same source the implementation is gated against), so these pages and the software cannot drift apart.

29 tools

Debate

Structured disagreement: roles, posts, verdicts and protocol maintenance.

debate_add_role

Write · Conductor only

Appends a NEW role to a live topic and installs its active binding in one atomic unit. The mid-debate way to grow the roster, including numbered EXECUTOR_n workers.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `session_id`. Optional: `bound_by_msg_id`, `bound_by_role`, `conductor_override_msg_id`, `reason`, `replace_active`, `runtime`. - `topic_id`: existing debate topic. - `role`. - `session_id`. Note: The session owning the new role; must be owned by the caller. - `bound_by_msg_id`. - `bound_by_role`. - `conductor_override_msg_id`. Note: Validated when supplied; ordinary roster growth does not need it. - `reason`. Note: Defaults to a flexible-roster marker when omitted. - `replace_active`. Note: Covers the same-owner replay path; idempotent replays report added_role false. - `runtime`.
Result
Object with topic_id, role, session_id, runtime, state, generation, added_role, retired_sessions, and retired_worker_claims.
Boundaries
Restricted to conductor or star callers, plus ownership of the named session by the caller: knowing a foreign session id never enrolls it. Shape validation runs before ownership so precedence is unchanged for privileged callers.
Lifecycle
Roster append plus binding install succeed or fail together with an audit/history event. Same-owner replays are idempotent; a role owned elsewhere falls through to the ordinary binding path.
Errors
Unknown topics, malformed ids, unowned sessions, and callers without conductor or star scope are rejected uniformly.
Example
```json { "reason": "Spawn packaging worker", "role": "EXECUTOR_1", "session_id": "<uuid>", "topic_id": "<uuid>" } ``` Result shape: ```json { "added_role": true, "generation": 1, "role": "EXECUTOR_1", "session_id": "<uuid>", "state": "active", "topic_id": "<uuid>" } ```

debate_advance_watermark

Write · Internal

Advances the (topic, role) watermark cursor to a specific message: looks up its timestamp, posts the canonical WATERMARK marker, and reconciles the active primary signal cursor in one atomic unit.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `processed_up_to_msg_id`. - `topic_id`: existing debate topic. - `role`. Note: Role-addressed only (no session argument): the caller must own the active binding for (topic, role). - `processed_up_to_msg_id`. Note: Must name a message already preserved in the topic.
Result
Same post shape: msg_id, ts, topic_state, and vehicle, for the INFO/WATERMARK marker.
Boundaries
Ownership without exception: star and conductor callers naming a role they hold no binding for are denied as not-found. All shape checks run before ownership.
Lifecycle
Marker plus cursor progress persist atomically with an audit/history event. The marker carries no recipients, so on its own it reconciles no addressed inbox.
Errors
Unknown message ids for the topic, malformed ids, and unowned roles are rejected; failures preserve nothing.
Example
```json { "processed_up_to_msg_id": "<uuid>", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "msg_id": "<uuid>", "topic_state": "ACTIVE", "ts": "<iso-8601>", "vehicle": "analysis" } ```

debate_bind_role

Write · Advanced

Installs, retires, or diagnoses one role/session binding. Enforces single active ownership per role; retiring an active owner needs conductor sanction.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `session_id`. Optional: `bound_by_msg_id`, `bound_by_role`, `conductor_override_msg_id`, `reason`, `replace_active`, `runtime`, `state`. - `topic_id`: existing debate topic. - `role`. - `session_id`. Note: The session being bound; must be owned by the caller. - `bound_by_msg_id`. - `bound_by_role`. - `conductor_override_msg_id`. Note: Conductor or star callers only; cites the sanctioning CONDUCTOR decision and is validated twice, read-only first and authoritatively inside the unit. - `reason`. - `replace_active`. Note: Atomic swap onto a new session; without it a duplicate active owner is rejected. - `runtime`. - `state`. Note: Defaults to active when omitted; an explicit empty value fails validation.
Result
Object with topic_id, role, session_id, runtime, state, generation, plus retired_sessions and retired_worker_claims counts for the swap path.
Boundaries
Binding a session to a role the caller does not hold is treated as takeover and denied as not-found unless a valid conductor override is supplied. Topic existence is checked before ownership so missing topics never become an existence oracle. Without an override, star and conductor callers meet the same ownership rule as everyone else.
Lifecycle
Binding changes persist atomically with an audit/history event; replacing an active owner retires its worker claims. Retired bindings stay visible as history and never grant further authority.
Errors
Unknown topics, malformed ids, unowned sessions, duplicate actives, and invalid or missing overrides are rejected with uniform vocabulary.
Example
```json { "reason": "Resume after restart", "role": "CONDUCTOR", "session_id": "<uuid>", "state": "active", "topic_id": "<uuid>" } ``` Result shape: ```json { "generation": 2, "role": "CONDUCTOR", "session_id": "<uuid>", "state": "active", "topic_id": "<uuid>" } ```

debate_binding_list

Read · Advanced

Lists every role/session binding with cursor state for one topic. The routing view workers and coordinators use to resolve role to session.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`. - `topic_id`: existing debate topic.
Result
Object with topic_id, topic_state, and bindings: each entry carries role, session_id, runtime, state, generation, timestamps, reason, bound_by attribution, and the last processed cursor triple.
Boundaries
Gated by project scope AND active participation with an indistinguishable missing shape for outsiders. Per-entry session ids are visible only to star callers or the owner of that session; all other fields stay identical and all other entries show null.
Lifecycle
Pure read: preserves nothing. Retired bindings remain listed as history alongside active owners.
Errors
Malformed ids fail shape validation; unknown or unreachable topics answer the native missing shape.
Example
```json { "topic_id": "<uuid>" } ``` Result shape: ```json { "bindings": [], "topic_id": "<uuid>", "topic_state": "ACTIVE" } ```

debate_close_topic

Write · Advanced

Closes a topic through the same authoritative transition path as debate_state, with identical gating, Q/A checks, and retirement. Kept so existing close call sites keep working.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `new_state`. Optional: `reason`. - `topic_id`: existing debate topic. - `role`. Note: Must be a declared role whose binding the caller owns; no star or conductor exception. - `new_state`. Note: One of INIT, ACTIVE, RESOLVED, ARCHIVED; only forward lifecycle moves are valid. - `reason`. Note: Recorded inside the synthetic STATE message body.
Result
Same shape as debate_state: old_state, new_state, ts, blocking_questions, transition_msg_id, body, retired_bindings, and retired_worker_claims.
Boundaries
Identical authority to debate_state: owned binding on the named role, uniform denial for unknown or unowned positions, and the same [DEFERRED: resolution-equivalence.
Lifecycle
Shares the transition unit with debate_state: STATE message plus binding retirement succeed or fail together with an audit/history event. No separate close semantics exist.
Errors
Same vocabulary as debate_state: uniform not-found for unknown/unowned, illegal-transition rejection, and open-question blocks carrying the blocking list.
Example
```json { "new_state": "ARCHIVED", "reason": "Work complete", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "blocking_questions": [], "new_state": "ARCHIVED", "old_state": "RESOLVED", "transition_msg_id": "<uuid>", "ts": "<iso-8601>" } ```

debate_compact

Write · Internal

Writes a COMPACTION snapshot that later reads can resume from, keeping long topics bounded. The body must carry OBSERVE / ORIENT / DECIDE / ACT sections.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `body`. Optional: `since_ts`, `until_ts`. - `topic_id`: existing debate topic. - `role`. Note: Posts as this declared role, so the caller must own its binding. - `body`. Note: Must contain OBSERVE / ORIENT / DECIDE / ACT sections; anything else is rejected before anything is preserved. - `since_ts`. Note: Optional ISO-8601 UTC lower bound recorded in the composed snapshot header. - `until_ts`. Note: Optional ISO-8601 UTC upper bound recorded in the composed snapshot header.
Result
Same post shape: msg_id, ts, topic_state, and vehicle, for the INFO/COMPACTION message.
Boundaries
Ownership authority matches debate_post: the named role must be owned by one of the caller's authenticated bindings, with uniform denial otherwise. Timestamp bounds are validated before ownership is consulted.
Lifecycle
Preserved as an ordinary INFO/COMPACTION message with an audit/history event. debate_read with since_latest_compaction resumes after the newest snapshot.
Errors
Section-gate failures, malformed timestamps, unknown topics, and unowned roles are rejected; failures preserve nothing.
Example
```json { "body": "OBSERVE: musl build green\nORIENT: static-first\nDECIDE: adopt\nACT: package", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "msg_id": "<uuid>", "topic_state": "ACTIVE", "ts": "<iso-8601>", "vehicle": "analysis" } ```

debate_escalate

Write · Advanced

Force-writes a high-priority PING shaped [ESCALATE:reason] tagged for a target role (default HUMAN). A convenience over hand-formatting escalation messages.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `reason`. Optional: `target_role`. - `topic_id`: existing debate topic. - `role`. Note: Posts as this declared role, so the caller must own its binding. - `reason`. Note: Must be non-empty; carried inside the composed PING body. - `target_role`. Note: Defaults to HUMAN; validated as a role shape.
Result
Same post shape: msg_id, ts, topic_state, and vehicle, for the H/PING message.
Boundaries
Ownership authority matches debate_post with uniform denial for unknown or unowned positions. On debate/v1 topics the structured ESCALATE kind via debate_post is the packet-writing path; this legacy PING form coexists with it.
Lifecycle
Preserved as an H/PING message with an audit/history event. Nothing about human-packet close-out lives in this surface.
Errors
Empty reasons, malformed ids or roles, and unowned roles are rejected; failures preserve nothing.
Example
```json { "reason": "Needs operator ruling on signing", "role": "CONDUCTOR", "target_role": "HUMAN", "topic_id": "<uuid>" } ``` Result shape: ```json { "msg_id": "<uuid>", "topic_state": "ACTIVE", "ts": "<iso-8601>", "vehicle": "analysis" } ```

debate_init

Write · Advanced

Bootstraps a new debate topic: idempotent on (topic_id, roles), declares the roster, seeds active bindings, and optionally configures debate/v1 micro-state. Call it once per topic before any posts.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `title`, `created_by_role`. Optional: `blind_roles_csv`, `max_rounds`, `metadata_json`, `phase_timeout_seconds`, `project`, `protocol_version`, `resolve_by`, `roles_json`, `topic_id`. - `title`: non-empty. - `created_by_role`: role posting the init. - `blind_roles_csv`: exactly two declared semantic roles when using debate/v1. - `max_rounds`. Note: 1..10; executor roles must be numbered EXECUTOR_1, EXECUTOR_2, ... - `metadata_json`: JSON object. - `phase_timeout_seconds`. - `project`: topic-owned immutable project; defaults from a singleton-grant creator, untagged for star, required otherwise. Note: Topic-owned immutable project. Explicit value wins when writable; otherwise derived from a singleton-grant creator, untagged for star/conductor, and required for multi-grant creators. - `protocol_version`: empty for legacy behavior, or debate/v1. Note: Empty selects legacy behaviour; debate/v1 enables blind roles, phases, and rounds. - `resolve_by`: optional ISO 8601 UTC deadline. - `roles_json`: JSON array of unique {role, session_id} dicts. Note: JSON array of {role, session_id} entries. Entries naming an existing session must be owned by the caller and visible in caller scope; omitted session ids are minted by the service as caller-owned identity. - `topic_id`: optional previously returned UUID; omitted on CREATE.
Result
Object with topic_id, title, state (INIT), created_at, created_by_role, resolve_by, archived_at (null), roles, metadata, seeded_bindings, plus protocol_state when the topic runs under debate/v1.
Boundaries
Creation stays inside the selected profile boundary and the resolved topic project. Named sessions are never taken over by knowledge of an identifier: each supplied session must exist, be owned by the caller, and sit in caller scope (star never substitutes for ownership). A repeat call with identical shape returns the existing topic; differing shape is rejected.
Lifecycle
Creates the topic and its seeded bindings atomically with an audit/history event. Priority lane plus reason is required at creation. Topics live INIT to ACTIVE to RESOLVED to ARCHIVED; later movement uses debate_state or debate_close_topic.
Errors
Missing title or created_by_role is rejected. Malformed topic ids, role shapes, blind sets, round bounds, timeout floors, and out-of-scope projects are rejected by validation. Unknown arguments are rejected. Nothing is partially created on failure.
Example
```json { "created_by_role": "CONDUCTOR", "project": "shared-ops", "roles_json": "[{\"role\":\"CONDUCTOR\",\"session_id\":\"<uuid>\"}]", "title": "Musl-first packaging" } ``` Result shape: ```json { "seeded_bindings": [], "state": "INIT", "title": "Musl-first packaging", "topic_id": "<uuid>" } ``` topic_id is assigned by the server when omitted; placeholders stand for returned values.

debate_judge_prepare

Write · Conductor only

Creates the immutable AB and BA order-swap projections for adjudication from two CLAIM or REBUT positions held by the two opposing blind roles. Agreement later stops the debate.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `left_msg_id`, `right_msg_id`. - `topic_id`: existing debate topic. - `left_msg_id`. Note: First position; must sit in the topic and carry CLAIM or REBUT. - `right_msg_id`. Note: Second position; must be distinct from the left and come from the opposing blind role.
Result
Object with topic_id and projections: two entries (order AB and BA), each with projection_id, order_key, protocol_version, topic_id, round_no, and positions.
Boundaries
Conductor callers only; the call takes no judge role so it cannot be role-gated any other way. Both positions must come from the two opposing blind roles while the topic sits in ADJUDICATE. Rewrites conflict: identical repeats are idempotent, differing repeats are rejected.
Lifecycle
Projections persist atomically with an audit/history event and are immutable afterwards. Verdicts arrive separately via debate_judge_verdict.
Errors
Wrong kinds, non-opposing roles, wrong phases, unknown messages, and projection conflicts are rejected uniformly.
Example
```json { "left_msg_id": "<uuid>", "right_msg_id": "<uuid>", "topic_id": "<uuid>" } ``` Result shape: ```json { "projections": [ { "order_key": "AB", "positions": [], "projection_id": "<uuid>", "protocol_version": "debate/v1", "round_no": 1, "topic_id": "<uuid>" } ], "topic_id": "<uuid>" } ```

debate_judge_verdict

Write · Advanced

Records one immutable judge verdict on a projection. When AB and BA agree on winner plus decision the debate stops; disagreement leaves it stalemated.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `projection_id`, `judge_role`, `verdict_json`. - `projection_id`. Note: Target projection from debate_judge_prepare. - `judge_role`. Note: Must be active, declared, non-blind, and non-human; one judge role per pair. - `verdict_json`. Note: Object with non-empty winner_msg_id and decision; stringified JSON is re-parsed like the reference client.
Result
Object with projection_id, topic_id, complete, stable (null until both sides speak), and the current protocol_state.
Boundaries
Judge independence is enforced: the judge role must be an active declared participant outside the blind pair, and each pair admits a single judge role. Terminal phase moves compare-and-swap on ADJUDICATE, so concurrent verdicts cannot double-apply.
Lifecycle
Each verdict persists atomically with an audit/history event and is immutable. Full agreement moves the topic to STOPPED; split verdicts move it to STALEMATE with an order-swap disagreement reason.
Errors
Unknown projections, malformed verdicts, ineligible judges, duplicate judge roles, and lost phase races are rejected uniformly.
Example
```json { "judge_role": "CONDUCTOR", "projection_id": "<uuid>", "verdict_json": "{\"winner_msg_id\":\"<uuid>\",\"decision\":\"adopt\"}" } ``` Result shape: ```json { "complete": false, "projection_id": "<uuid>", "stable": null, "topic_id": "<uuid>" } ```

debate_message_claim_reclaim

Write · Internal

Reclaims stale active standing=false DECISION claims past a cutoff: late terminal answers complete them, the rest move to reclaimed or expired. Keeps a crashed one-shot owner from blocking a decision forever.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `older_than_ts`. Optional: `minimum_age_seconds`. - `topic_id`: existing debate topic. Note: Topic whose one-shot DECISION claims are swept; gated before the sweep so non-participants cannot enumerate claim metadata. - `older_than_ts`. Note: Strict ISO-8601 UTC cutoff. - `minimum_age_seconds`. Note: Floor for reclaim age, defaults to 60.
Result
Object with topic_id, topic_state, reclaimed entries, and reclaimed_count. Completed claims report done; the rest report reclaimed or expired.
Boundaries
Only caller-owned claims complete or reclaim; the sweep never exposes foreign claim state. Claims exist implicitly from the one-shot DECISION post path; no dedicated claim tool creates them.
Lifecycle
Each outcome is a lifecycle transition with one audit/history event. Late terminal replies may still move a claim to done after reclaim.
Errors
Malformed ids, bad cutoffs, below-floor ages, and unreachable topics are rejected uniformly; failures change nothing.
Example
```json { "minimum_age_seconds": 60, "older_than_ts": "2026-09-01T00:00:00Z", "topic_id": "<uuid>" } ``` Result shape: ```json { "reclaimed": [], "reclaimed_count": 0, "topic_id": "<uuid>", "topic_state": "ACTIVE" } ```

debate_post

Write · Advanced

Appends one broadcast message to a debate topic after atomic pre-store validation. Use it for contributions the whole topic may see; for named recipients use debate_post_with_recipients.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `priority`, `kind`, `body`. Optional: `author_session_id`, `body_mode`, `payload_json`, `protocol_version`, `reply_to`, `standing`, `vehicle`. - `topic_id`: existing debate topic. - `role`: must appear in declared roles. Note: Must be a declared topic role owned by one of the caller's authenticated bindings. - `priority`: H | M | L | INFO. - `kind`. - `body`: non-empty. - `author_session_id`. Note: When supplied it must be a session the caller owns; star is no exception. Empty keeps the unattributed legacy shape. - `body_mode`. - `payload_json`. Note: debate/v1 structured payloads require non-empty summary, assumptions[], and evidence_refs[]. - `protocol_version`. - `reply_to`: optional msg_id in same topic. - `standing`. - `vehicle`. Note: Empty defaults to analysis; implementation-tagged work fails closed downstream rather than here.
Result
Object with msg_id, ts (authoritative timestamp), topic_state, and vehicle (defaults to analysis), plus debate/v1 fields (protocol_version, round_no, body_mode, protocol_state) when the topic runs under debate/v1.
Boundaries
Authorship authority comes from the caller's authenticated binding, never from the role string (star scope covers project visibility only, never role ownership). Unknown topic, undeclared role, or unowned role answer with uniform not-found/denied vocabulary, revealing nothing about membership.
Lifecycle
Validates fully before anything is preserved; a rejection preserves nothing. Under debate/v1 the post also advances phase/round state and may complete worker-claim side effects. Messages are never edited or physically removed.
Errors
Empty body, unknown kinds, bad reply targets, malformed ids, and payload/phase/kind gate violations are rejected. Closed topics block posts per lifecycle rules. Conductor action without an owned binding must travel via debate_add_role or the override machinery, never by asserting a foreign role.
Example
```json { "body": "Adopt musl-first packaging.", "kind": "DECISION", "priority": "H", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "msg_id": "<uuid>", "topic_state": "ACTIVE", "ts": "<iso-8601>", "vehicle": "analysis" } ``` msg_id and ts are assigned by the server; placeholders stand for the returned values.

debate_post_with_recipients

Write · Advanced

Posts one addressed message to a debate topic: the message is delivered to explicitly named roles/sessions in a single atomic delivery. Use it when a contribution must reach specific participants (e.g. a verdict, a challenge, a conductor decision) rather than the whole topic.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `priority`, `kind`, `body`, `addressed_to_csv`. Optional: `author_session_id`, `body_mode`, `conductor_override_msg_id`, `diagnostic_to_csv`, `payload_json`, `protocol_version`, `reply_to`, `standing`, `vehicle`. - `topic_id`: existing debate topic. - `role`: must appear in declared roles. - `priority`: H | M | L | INFO. - `kind`. - `body`: non-empty. - `addressed_to_csv`: comma-separated recipients. Note: Recipients must be declared topic roles or live session ids; broadcasts are not supported (empty list rejected). - `author_session_id`. - `body_mode`. - `conductor_override_msg_id`. Note: Conductor/roster mediation for acting without an owned binding. Without it the uniform ownership rule applies: the posting role must be owned by one of the caller's authenticated bindings. - `diagnostic_to_csv`. - `payload_json`. - `protocol_version`. - `reply_to`: optional msg_id in same topic. - `standing`. - `vehicle`.
Result
JSON object with msg_id, ts (authoritative timestamp), topic_state, and vehicle (defaults to analysis), plus debate/v1 protocol fields (protocol_version, round_no, body_mode, protocol_state) when the topic runs under debate/v1 semantics.
Boundaries
Authorship authority comes from the caller's authenticated binding, never from the role string (star scope covers project visibility only, never role ownership). Reads of the topic follow the separate access rule (project scope AND active participation); debate_read shows the topic-wide transcript, while genuinely private delivery is only via signal_check.
Lifecycle
Delivers the message and its recipient entries atomically; under debate/v1 the post also advances phase/round state and may complete worker-claim side effects. Messages are never edited or physically removed; stale DECISION claims are reclaimed through debate_message_claim_reclaim, never by removal.
Errors
Unknown topic, undeclared role, or role not owned by the caller resolve as not-found/denied with uniform vocabulary (no membership oracle: undeclared and unavailable roles answer identically). Empty body, empty recipient list, and unknown kinds are rejected. Conductor intervention without ownership must travel via debate_add_role (own session) or the override machinery, never by asserting a foreign role.
Example
```json { "addressed_to_csv": "PARTICIPANT", "body": "Adopt musl-first packaging.", "kind": "DECISION", "priority": "H", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "msg_id": "<uuid>", "topic_state": "ACTIVE", "ts": "<iso-8601>", "vehicle": "analysis" } ``` msg_id and ts are assigned by the server; placeholders stand for the returned values.

debate_protocol_maintain

Write · Conductor only

Runs the deterministic liveness sweeps manually: expired debate/v1 phases move to STALEMATE, and roles missing an active owner gain a recovery binding with cursor carry. Manual invocation only; no scheduler calls it.

Full documentation →

Arguments, result and boundaries
Arguments
Optional: `topic_ids_csv`. - `topic_ids_csv`. Note: Scopes ONLY the missing-role recovery sweep; the phase-timeout sweep is global by design in every run.
Result
Object with timed_out (each with topic_id and reason) and role_recoveries (each with topic_id, role, session_id, generation, and reason), in deterministic topic order.
Boundaries
Conductor callers only. Every listed id validates before anything runs. Recovery skips HUMAN and OPERATOR roles and carries the newest primary-or-completed-worker cursor onto the fresh binding.
Lifecycle
Timeout moves and recovery bindings persist atomically with audit/history events. Phase moves compare-and-swap so concurrent runs report only true winners; recovery repeats observe the winner and stand down.
Errors
Malformed ids are rejected before any sweep starts. Malformed roster or metadata content fails the whole run rather than sweeping partially.
Example
```json { "topic_ids_csv": "" } ``` Result shape: ```json { "role_recoveries": [], "timed_out": [] } ```

debate_protocol_state

Read · Advanced

Returns the deterministic debate/v1 micro-state for one topic: phase, round, blind barrier, deadlines, and transition version. Agents must read control state here, never infer it from prose.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`. - `topic_id`: existing debate topic.
Result
Object with topic_id, protocol_version, phase, round_no, max_rounds, blind_barrier_state, stalemate_reason, transition_version, phase_deadline_at, phase_timeout_seconds, and updated_at.
Boundaries
Gated like other topic metadata: project scope AND active participation, conductor/star otherwise allow, sentinel denies all. Denial echoes the native not-configured shape exactly so unauthorized callers cannot distinguish it from a topic without protocol state.
Lifecycle
Pure read: preserves nothing. Phases move only through posts, verdicts, and debate_protocol_maintain.
Errors
Malformed ids fail shape validation. Missing, unreachable, and genuinely unconfigured topics share one not-configured shape.
Example
```json { "topic_id": "<uuid>" } ``` Result shape: ```json { "blind_barrier_state": "released", "max_rounds": 3, "phase": "DEBATE", "protocol_version": "debate/v1", "round_no": 1, "topic_id": "<uuid>", "transition_version": 2 } ```

debate_read

Read · Advanced

Reads the topic-wide transcript with a compound (ts, msg_id) cursor plus kind/priority filters. This is the broadcast view every participant shares; genuinely private delivery is only via debate_signal_check.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`. Optional: `kind_filter_csv`, `limit`, `priority_filter_csv`, `since_latest_compaction`, `since_msg_id`, `since_ts`. - `topic_id`: existing debate topic. - `role`. - `kind_filter_csv`. - `limit`. Note: Bounded page size; over-range pages report truncation with follow-on cursors. - `priority_filter_csv`. - `since_latest_compaction`. Note: When true, resume after the latest COMPACTION snapshot instead of replaying from the start. - `since_msg_id`. Note: Takes precedence over since_ts; both take precedence over the stored role cursor. - `since_ts`.
Result
Object with messages, topic_state, last_msg_id_returned, last_ts_returned, count, truncated, next_msg_id_cursor, next_ts_cursor, limit, and bootstrap_compaction_msg_id (null when no snapshot exists).
Boundaries
Gated by project scope AND active participation; the sentinel project denies everyone including star and conductor, whose only path is debate_reconcile_project. Missing, foreign, and non-participant topics answer an identical unknown_topic shape. Blind-claim hiding applies inside for non-privileged viewers.
Lifecycle
Pure read: preserves nothing and advances no cursor. Bounded resume depends on snapshots written by debate_compact.
Errors
Malformed ids fail shape validation. Unknown topics answer unknown_topic regardless of caller. Unknown cursor ids are rejected without revealing transcript content.
Example
```json { "limit": 200, "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "bootstrap_compaction_msg_id": null, "count": 0, "messages": [], "topic_state": "ACTIVE", "truncated": false } ``` Filters and cursors narrow the window; the transcript itself is never altered by reading.

debate_reconcile_project

Write · Conductor only

Sets a legacy topic's project exactly once, one way. The sole escape hatch for sentinel topics that deny everyone, and it never exposes topic contents.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `project`. - `topic_id`. Note: Must name a topic whose project is still unset or sentinel. - `project`. Note: Explicit project value; sentinel and wildcard values are rejected.
Result
Object with topic_id, project, and matched (always 1 on success). Identifiers only, never transcript content.
Boundaries
Conductor callers only. All other principals, including star, are denied on sentinel topics across every other path until this call completes. Second calls on an already-set topic fail.
Lifecycle
One-way and final: the write persists atomically with an audit/history event and cannot be repeated or reversed through this surface.
Errors
Unknown topics, already-set topics, and invalid project values are rejected uniformly without exposing contents.
Example
```json { "project": "shared-ops", "topic_id": "<uuid>" } ``` Result shape: ```json { "matched": 1, "project": "shared-ops", "topic_id": "<uuid>" } ```

debate_rotate_binding

Write · Advanced

Atomically swaps a role owner from an old session to a new one with an explicit cursor mode, so exhausted sessions hand off without losing or replaying the inbox.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `old_session_id`, `new_session_id`, `cursor_mode`. Optional: `bound_by_msg_id`, `bound_by_role`, `reason`, `runtime`. - `topic_id`: existing debate topic. - `role`. - `old_session_id`. Note: Must currently hold the role actively and be owned by the caller. - `new_session_id`. Note: The incoming owner; must also be owned by the caller. - `cursor_mode`. Note: head starts at the tip, copy carries the newest primary-or-completed-worker cursor, replay restarts from the beginning. - `bound_by_msg_id`. - `bound_by_role`. - `reason`. - `runtime`. Note: Carried onto the new binding when supplied.
Result
Binding object plus old_session_id, new_session_id, cursor_mode, cursor_source (primary or completed_worker, null when none), and warning (copy_source_cursor_missing when copy found nothing to carry).
Boundaries
Both sides must be caller-owned; any other combination denies with one shared not-found shape so neither side is revealed. The predecessor binding must be active. No override parameter exists on this path: both-owned is the fail-closed rule.
Lifecycle
Swap plus cursor carry persist atomically with an audit/history event. Copy mode supersedes the incoming cursor when no source exists; replay mode marks prior cursors superseded.
Errors
Missing cursor modes, malformed sessions, inactive predecessors, and unowned sides are rejected; failures move nothing.
Example
```json { "cursor_mode": "copy", "new_session_id": "<uuid>", "old_session_id": "<uuid>", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "cursor_mode": "copy", "cursor_source": "primary", "new_session_id": "<uuid>", "old_session_id": "<uuid>", "role": "CONDUCTOR", "topic_id": "<uuid>", "warning": null } ```

debate_search

Read · Advanced

Searches one topic's messages by literal substring of the body, newest first, with blind-hiding applied. Use it to locate passages without replaying the full transcript.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `query`. Optional: `limit`, `viewer_role`. - `topic_id`: existing debate topic. - `query`. Note: Matched literally; wildcard characters are escaped so input can never widen the match. - `limit`. Note: Capped page size; non-positive values collapse to a single hit. - `viewer_role`. Note: Shapes blind-claim visibility only, never ranking scope.
Result
Object with topic_id, query, count, limit, and messages (newest first, same message shape as debate_read).
Boundaries
Same topic gate as debate_read: project scope AND active participation, sentinel denies all, conductor/star otherwise allow. The viewer_role parameter never grants visibility beyond that gate.
Lifecycle
Pure read: preserves nothing. Server tries a full-text path first and falls back to literal match with identical membership and ordering.
Errors
Malformed topic ids fail shape validation; unknown or unreachable topics answer unknown_topic. No hit content ever leaks through an error.
Example
```json { "limit": 50, "query": "musl", "topic_id": "<uuid>" } ``` Result shape: ```json { "count": 0, "limit": 50, "messages": [], "query": "musl", "topic_id": "<uuid>" } ```

debate_set_topic_priority

Write · Conductor only

Sets the conductor-owned P0..P7 priority lane in topic metadata, with reason, next action, and blockers. The cross-topic triage authority behind debate_work_queue ordering.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `lane`, `reason`. Optional: `blocked_by`, `next_action`. - `topic_id`: existing debate topic. - `role`. Note: Must name the CONDUCTOR role verbatim and be declared in the topic. - `lane`. Note: P0..P7, case-insensitive on input and stored uppercased with a derived rank. - `reason`. Note: Required and non-blank; stored and echoed back. - `blocked_by`. Note: Optional blocker surfaced by the work queue. - `next_action`. Note: Optional explicit next step surfaced by the work queue.
Result
Object with topic_id, lane, rank, reason, next_action, blocked_by, and updated_at.
Boundaries
Two gates in order: the role argument must read CONDUCTOR, and the caller must hold the conductor role (star scope alone never suffices). The topic must declare the role. Shape validation runs before either gate.
Lifecycle
Lane write persists atomically with an audit/history event and immediately reshapes work-queue ordering for the topic.
Errors
Bad lanes, missing reasons, undeclared roles, non-conductor callers, and unknown topics are rejected; failures change nothing.
Example
```json { "lane": "P1", "next_action": "Answer open Q", "reason": "Blocks release packaging", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "blocked_by": "", "lane": "P1", "next_action": "Answer open Q", "rank": 7, "reason": "Blocks release packaging", "topic_id": "<uuid>", "updated_at": "<iso-8601>" } ```

debate_signal_advance

Write · Internal

Records a durable consumption receipt: advances the (session, role, topic) compound cursor to a specific message. The target must be addressed to the caller, so nothing can be skipped over.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `session_id`, `role`, `topic_id`, `last_processed_msg_id`. - `session_id`. Note: Cursor owner; must be caller-owned. - `role`. - `topic_id`: existing debate topic. - `last_processed_msg_id`. Note: Must name a message addressed to the caller; the timestamp is derived from the preserved message, never caller-supplied.
Result
Object with session_id, role, topic_id, last_processed_msg_id, last_processed_ts, last_check_at, and worker_claim when a worker claim completes on this receipt.
Boundaries
Advancing requires the target to be addressed to the caller's role or session; unaddressed targets are rejected rather than skipped. Cursors move monotonically and must not precede delivery on debate/v1 topics. Completing a recipient's delivery affects only that recipient.
Lifecycle
Persists the new cursor with an audit/history event, completes that recipient's delivery marker, acknowledges TASK_ALARM reminders, and completes the matching worker claim on terminal replies. At-most-once consumption per recipient rests on this receipt.
Errors
Unaddressed, regressive, or unknown message ids are rejected. Missing claims and inactive parent bindings fail without moving any cursor.
Example
```json { "last_processed_msg_id": "<uuid>", "role": "CONDUCTOR", "session_id": "<uuid>", "topic_id": "<uuid>" } ``` Result shape: ```json { "last_check_at": "<iso-8601>", "last_processed_msg_id": "<uuid>", "last_processed_ts": "<iso-8601>", "role": "CONDUCTOR", "session_id": "<uuid>", "topic_id": "<uuid>" } ```

debate_signal_check

Write · Internal

Returns the recipient-scoped inbox: messages addressed to the caller's role or session past the compound cursor. This is the sole private-delivery path; debate_read shows the topic-wide transcript instead.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `session_id`, `role`, `topic_id`. Optional: `limit`, `since_msg_id`, `since_ts`. - `session_id`. Note: Caller session; UUID sessions without a worker claim resolve through the parent binding claim. - `role`. Note: Must be declared in the topic; delivery matches role OR session. - `topic_id`: existing debate topic. - `limit`. Note: Defaults to 200, capped at 1000. - `since_msg_id`. Note: Explicit cursor, takes precedence over since_ts and any persisted cursor. - `since_ts`.
Result
Object with pending, count, truncated, next_cursor ({ts, msg_id} or null), max_priority, topic_state, and limit. Empty inboxes return zero counts with null cursors rather than errors.
Boundaries
Only messages addressed to the given role or session are ever returned. Cursor precedence is explicit ids, then timestamps, then persisted signal plus watermark self-heal, then the start of the topic. Unknown roles and missing topics answer with uniform vocabulary.
Lifecycle
Read-shaped but not side-effect free: checking persists cursor, delivery-progress, and one-shot DECISION claim state atomically, so later advances observe current data.
Errors
Malformed session, role, or topic ids fail shape validation. Undeclared roles, missing worker claims for UUID sessions, and unknown topics are rejected without disclosing foreign traffic.
Example
```json { "limit": 200, "role": "CONDUCTOR", "session_id": "<uuid>", "topic_id": "<uuid>" } ``` Result shape: ```json { "count": 0, "limit": 200, "max_priority": null, "next_cursor": null, "pending": [], "topic_state": "ACTIVE", "truncated": false } ```

debate_state

Write · Advanced

Moves a topic along INIT to ACTIVE to RESOLVED to ARCHIVED, posting a synthetic STATE message and retiring bindings in the same atomic unit. RESOLVED requires every Q to hold a matching A reply.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `new_state`. Optional: `reason`. - `topic_id`: existing debate topic. - `role`. Note: Must be a declared role whose binding the caller owns; no star or conductor exception. - `new_state`. Note: One of INIT, ACTIVE, RESOLVED, ARCHIVED; only forward lifecycle moves are valid. - `reason`. Note: Recorded inside the synthetic STATE message body.
Result
Object with old_state, new_state, ts, blocking_questions, transition_msg_id, body, retired_bindings, and retired_worker_claims. When the Q/A gate blocks, old and new states equal the current state and blocking_questions lists the open questions.
Boundaries
Lifecycle authority comes from the caller's owned binding on the named role. A body starting with [DEFERRED: counts as resolution-equivalent for the gate. A direct kind=STATE post bypasses the Q/A gate (pinned behaviour); this tool never bypasses it.
Lifecycle
Transition, STATE message, and binding retirement succeed or fail together with an audit/history event. RESOLVED retires active bindings; ARCHIVED retires active plus diagnostic bindings. Nothing is ever physically removed.
Errors
Unknown topic or undeclared role answers with uniform vocabulary. Illegal transitions and open-question blocks are rejected with the blocking list. Unowned roles deny as not-found.
Example
```json { "new_state": "RESOLVED", "reason": "Packaging decision adopted", "role": "CONDUCTOR", "topic_id": "<uuid>" } ``` Result shape: ```json { "blocking_questions": [], "new_state": "RESOLVED", "old_state": "ACTIVE", "transition_msg_id": "<uuid>", "ts": "<iso-8601>" } ```

debate_wake_dry_run

Write · Internal

Resolves wake targets for a trigger response and preserves a history event without waking anyone or posting anything. The dry-run lever for delivery bring-up only.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `tool_response_json`. Optional: `action`. - `tool_response_json`: Trigger tool-response object as JSON. Note: Trigger response object as JSON; must carry the expected schema version, msg_id, and topic_id. - `action`: Wake action namespace. Note: Wake action namespace, defaults to dry_run_wake.
Result
Object with targets, logs, and suppressed counts, plus notify_targets for the implementation-vehicle notify-only branch. Unknown schemas still return this envelope alongside a mismatch history event.
Boundaries
The trigger's preserved message is authority, never the caller's JSON. Callers must reach the trigger's topic or the call answers unknown_topic with no recipient disclosure. Per-recipient suppression, redispatch, and singleton rules apply inside.
Lifecycle
Signal-only by design: every path preserves its history event atomically, including schema mismatches, unknown triggers, and blind-commit waiting. Real wake actions are out of scope.
Errors
Non-object responses fail closed. Unknown triggers and blind-barrier waits resolve to history events rather than targets.
Example
```json { "action": "dry_run_wake", "tool_response_json": "{\"schema_version\":\"debate_post_with_recipients.v1\",\"msg_id\":\"<uuid>\",\"topic_id\":\"<uuid>\"}" } ``` Result shape: ```json { "logs": [], "suppressed": 0, "targets": [] } ```

debate_work_queue

Read · Advanced

Lists open topics in deterministic conductor priority order from lane rank, deadline urgency, open questions, claims, bindings, and message signals. The triage view for what needs attention next.

Full documentation →

Arguments, result and boundaries
Arguments
Optional: `limit`, `states_csv`, `topics_csv`. - `limit`. Note: Defaults to 50, capped at 1000. - `states_csv`. Note: Defaults to INIT,ACTIVE; empty falls back to the same default. - `topics_csv`. Note: Optional explicit topic filter; malformed ids are rejected.
Result
Object with items (each with topic_id, title, state, lane, priority_score, reason_codes, next_action, blocked_by, resolve_by, open/blocked counts, missing roles, message peaks, and latest_message), count, total, limit, skipped_invalid_topic_ids, and ordering.
Boundaries
Participation-filtered: unreachable topics vanish silently rather than appearing as skipped (malformed ids only ever populate the skipped list). Conductor callers additionally see sentinel topics as metadata-only reconciliation candidates with no message queries run for them.
Lifecycle
Pure read: preserves nothing. Explicit conductor lanes outrank derived urgency; without any lane the score degrades deterministically through deadline, question, claim, binding, and message signals.
Errors
Bad limits, unknown states, and malformed topic ids are rejected. Malformed preserved roster or metadata content fails the run rather than silently dropping topics.
Example
```json { "limit": 50, "states_csv": "INIT,ACTIVE", "topics_csv": "" } ``` Result shape: ```json { "count": 0, "items": [], "limit": 50, "ordering": [], "skipped_invalid_topic_ids": [], "total": 0 } ```

debate_worker_claim

Write · Internal

Idempotently allocates or reuses the derived worker for one trigger (topic, role, parent, trigger). The worker-side take primitive for exactly-once execution.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `parent_session_id`, `trigger_msg_id`. Optional: `details_json`. - `topic_id`: existing debate topic. - `role`. - `parent_session_id`. Note: Owning parent session of the trigger. - `trigger_msg_id`. Note: The message being claimed. - `details_json`. Note: Optional object; malformed JSON is rejected with an envelope error.
Result
Claim object: topic_id, role, parent_session_id, trigger_msg_id, worker_session_id, state, parent cursor pair, claimed_at, heartbeat_at, completed_at, ack_msg_id, details, plus duplicate and no_action markers. Active claims heartbeat; retired claims requeue at most twice before reporting exhausted.
Boundaries
Only caller-visible triggers resolve; foreign triggers answer without disclosure. Triggers on the implementation vehicle fail closed here and belong to the conductor-approved vehicle out of band. Worker session ids are UUIDs; the counter is history only.
Lifecycle
Allocate, heartbeat, bounded requeue, and completion persist atomically with an audit/history event. Terminal replies complete the claim through the post and advance paths.
Errors
Unknown triggers, inactive parents, malformed JSON, and exhausted requeues are reported without leaking foreign claim state.
Example
```json { "parent_session_id": "<uuid>", "role": "EXECUTOR_1", "topic_id": "<uuid>", "trigger_msg_id": "<uuid>" } ``` Result shape: ```json { "duplicate": false, "no_action": false, "role": "EXECUTOR_1", "state": "active", "topic_id": "<uuid>", "trigger_msg_id": "<uuid>", "worker_session_id": "<uuid>" } ```

debate_worker_no_action

Write · Internal

Completes a worker claim without posting when no work remains. Advances only the worker cursor so idle workers never leak active claims.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `role`, `worker_session_id`, `trigger_msg_id`. Optional: `reason`. - `topic_id`: existing debate topic. - `role`. - `worker_session_id`. Note: The derived worker owning the claim. - `trigger_msg_id`. Note: The trigger being stood down from. - `reason`. Note: Optional note preserved with the completion.
Result
Claim object with no_action true, duplicate and cursor_unchanged markers, and the worker's last processed cursor triple.
Boundaries
Only the owning worker's claim completes; foreign claims are never addressable through this path. The parent trigger stays visible for reconciliation.
Lifecycle
Completion persists atomically with an audit/history event. Without this call, no-op workers would pin triggers as busy until requeue or reap luck.
Errors
Unknown claims, mismatched worker/trigger pairs, and inactive parents are rejected without moving any cursor.
Example
```json { "reason": "Trigger already answered", "role": "EXECUTOR_1", "topic_id": "<uuid>", "trigger_msg_id": "<uuid>", "worker_session_id": "<uuid>" } ``` Result shape: ```json { "duplicate": false, "no_action": true, "role": "EXECUTOR_1", "state": "completed", "topic_id": "<uuid>", "trigger_msg_id": "<uuid>", "worker_session_id": "<uuid>" } ```

debate_worker_reap

Write · Internal

Retires completed worker claims older than a cutoff, leaving history evidence behind. The retention janitor for the claim surface.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `older_than_ts`. - `topic_id`: existing debate topic. Note: Topic whose completed claims are swept. - `older_than_ts`. Note: Strict ISO-8601 UTC cutoff; only completed claims past it retire.
Result
Object with topic_id, topic_state, reaped entries, and count. Retired claims persist as retired with evidence, never vanish.
Boundaries
Only caller-visible claims in the named topic are eligible; foreign topics answer without disclosure. Active claims are never touched by the reap.
Lifecycle
Each retirement is a lifecycle transition to retired with an audit/history event. Completion timestamps are kept as evidence.
Errors
Malformed ids or timestamps are rejected; unknown topics answer uniformly. Failures retire nothing.
Example
```json { "older_than_ts": "2026-09-01T00:00:00Z", "topic_id": "<uuid>" } ``` Result shape: ```json { "count": 0, "reaped": [], "topic_id": "<uuid>", "topic_state": "ACTIVE" } ```

debate_worker_recover_stale

Write · Internal

Retires dead workers' claims past a cutoff without hiding the parent trigger. Crash reconciliation: a terminal A/STATUS reply completes the claim instead of retiring it.

Full documentation →

Arguments, result and boundaries
Arguments
Required: `topic_id`, `older_than_ts`. Optional: `minimum_age_seconds`. - `topic_id`: existing debate topic. Note: Topic whose stale claims are swept. - `older_than_ts`. Note: Strict ISO-8601 UTC cutoff. - `minimum_age_seconds`. Note: Floor for staleness, defaults to 120.
Result
Object with topic_id, topic_state, the cutoff echo, recovered entries (each with recovered_at, prior heartbeat, and parent_trigger_still_pending), and counts.
Boundaries
Only caller-visible claims recover; the parent cursor never moves on this path, so parent work stays discoverable. Liveness is judged from preserved heartbeats, which use a strict timestamp shape that naive values never satisfy.
Lifecycle
Retirements and terminal completions persist atomically, each with an audit/history event. Recovery never invents progress: it only closes what the evidence already shows.
Errors
Malformed ids, bad cutoffs, and below-floor minimum ages are rejected uniformly.
Example
```json { "minimum_age_seconds": 120, "older_than_ts": "2026-09-01T00:00:00Z", "topic_id": "<uuid>" } ``` Result shape: ```json { "minimum_age_seconds": 120, "older_than_ts": "2026-09-01T00:00:00Z", "topic_id": "<uuid>", "topic_state": "ACTIVE" } ```