MCP server
PostMQ speaks standard MCP, so any MCP-capable client can connect. This page is the reference — how to authenticate, which transport to use, what to do first, and every tool with its arguments.
What the MCP server gives you
One MCP server exposing 87 tools across 11 domains — messaging, webhooks, policies and audit, projects, build sessions, markdown import, the decision log, the backlog, lessons, rules and usage.
It is standard MCP, so any MCP-capable client can connect. The count and every tool on this page come from the
manifest the server itself is held to, so this reference cannot drift from what your client will actually see in
tools/list.
If your client speaks MCP natively — Claude Code, Claude Desktop, an IDE extension — skip to connecting a client; the client handles the protocol for you.
Authentication
Every call carries a credential belonging to an AI account:
Authorization: Bearer pmq_…
A client can obtain that credential itself. Connect with none and the server refuses with 401 and a
WWW-Authenticate challenge naming its protected-resource metadata; a client that speaks the MCP authorization
spec follows it, registers itself, and opens a browser for a person to approve once. That is the path to prefer —
there is no secret for anyone to paste, and the token it receives is short-lived with a rotating refresh token
behind it. Authentication has the whole chain, hop by hop.
Approval needs a browser. An environment with no browser — CI, a container — uses a credential issued by a person instead, as described under Connecting a client.
It is the same credential the REST API takes, and it is scoped. A tool refuses with
POSTMQ_SCOPE_INSUFFICIENT when the credential does not carry the scope that tool needs — send needs send,
get_pending needs get_pending, the session-state writes need write_session_state, and so on. Reads of
session-state records need only an authenticated caller.
Scopes are chosen at issue and cannot be widened afterwards; you rotate to change them. Authentication has the full catalogue and the two tiers.
Operator actions are not part of this surface. There are no operator tools on the MCP server at all, so a credential an agent holds can never reach one.
Transports
| Transport | Where | When to use it |
|---|---|---|
| Streamable HTTP | https://mcp.postmq.com/ | The default. Nothing to install. |
| stdio | pmq mcp stdio | A self-hosted or co-located deployment, where you would rather the credential stayed in the process environment than in a client config file. |
stdio changes the wire, not where the records live — it still talks to the hosted database. Over stdio the
credential is process-ambient: pmq mcp stdio reads POSTMQ_CREDENTIAL, which is the key every paste-ready block
sets.
Connecting a client
If the client implements MCP authorization, give it the URL and nothing else — it will ask for its own credential and prompt you to approve:
{ "mcpServers": { "postmq": { "type": "http", "url": "https://mcp.postmq.com/" } } }
Otherwise issue the credential in the mcp_config form, and the response carries the client configuration next to
the once-disclosed plaintext, field for field — so connecting is a copy, not a transcription.
For Claude Code, that is a .mcp.json entry at the repository root:
{
"mcpServers": {
"postmq": {
"type": "http",
"url": "https://mcp.postmq.com/",
"headers": { "Authorization": "Bearer pmq_…" }
}
}
}
Or the stdio form, which sets the credential in the environment instead:
{
"mcpServers": {
"postmq": {
"type": "stdio",
"command": "pmq",
"args": ["mcp", "stdio"],
"env": { "POSTMQ_CREDENTIAL": "pmq_…" }
}
}
}
Restart the client once after adding the server. Clients fetch each server’s tool catalogue when they start, so one that was already running answers “tool not found” for a tool it has never seen. One restart refetches the catalogue and every tool is callable.
Recommended next steps after connecting
Connecting the server is step one, and on its own it changes nothing about how your project gets built. If an agent is setting PostMQ up for you, it should offer the rest rather than wait to be asked — and it will, because the server tells it to.
The server ships its own installer. On initialize PostMQ returns an instructions string, which MCP clients
surface to the model as a system message. It points at postmq://kit/manifest.json — a public resource listing
what PostMQ can install into your project, what each component does, the files it writes and where they go. So an
agent that has just connected the server can offer you the kit and install what you accept, on any MCP client, with
nothing to download.
It asks first. The instructions tell the agent to offer, wait, and install only what you accept — never to write into your project uninvited. If an agent installs something you did not agree to, that is the agent, not this server.
Two components are in the kit today:
| Component | What it is |
|---|---|
| Session-state kit | The start-session and end-session protocol below, as skills, plus a SessionStart hook that prints your drift signals. Claude Code, Codex, ZCode and Antigravity — on Antigravity the same drift script ships and the start-session skill runs it, because that install wires nothing. |
| Usage reporter | One file that sums the session’s tokens and records them against the build session. Claude Code, Codex, ZCode and Antigravity — on SessionEnd for the first two, on Stop for ZCode, which has no end-of-session event, and on Antigravity not on an event at all — the end-session skill runs it, so a session you never end records nothing. Worth nothing without the session-state kit it reports against. |
Restart your client after installing either — skills and hooks are read at start-up.
Confirm the connection tells you something. list_templates and list_recipients are the cheapest possible
proof that the credential works, the workspace is the one you meant, and the recipient you are about to address
actually exists.
Check whether a policy is gating you. If your workspace has policies, send refuses with
POSTMQ_POLICY_NOT_ACKNOWLEDGED until the agent has acknowledged the current version. get_policies tells you
where you stand and acknowledge_policies clears it. Doing this at setup means the first real send is not the
thing that discovers it.
The session protocol
PostMQ stores build sessions, a decision log, a backlog, lessons and rules — the record of how a project got built, rather than only what it ended up as. The server holds those records and does not enforce how you use them; the protocol below is a recipe, the one we run on PostMQ itself.
At a session boundary:
| When | Call | Why |
|---|---|---|
| The session opens | start_build_session | The same project, computer, branch and actor gets the same active session back rather than a second one — resume is the default, not a special case. |
| Before touching a file | query_applicable_rules | Pass the paths, operations, languages and patterns of the change; get back only the rules whose every populated trigger dimension matches. |
| For each material decision | append_decision_log | The log is append-only. A correction is a new entry pointing at the one it supersedes, so the reasoning stays legible. |
| For each deferral | file_backlog_item | Resolving one later needs a closing PR, commit or closing notes; dismissing one needs a reason. |
| The session closes | end_build_session | Sets the end instant and persists the closing history entry the next session reads first. |
| After the session | record_build_session_usage | Token counts against the session. Idempotent per transcript, so re-reporting overwrites and never double-counts. |
For Claude Code specifically, the integration page describes the two skills and the
two hooks we run — the session-start reminder and the SessionEnd usage reporter, which sums the transcript’s
tokens and reports them against the session. The numbers are self-reported: they are as good as the client that
reports them.
Bringing an existing project in
If the project already has a markdown decision log, backlog or session history, import it rather than starting empty:
list_session_state_import_parsers— the parser strategies available for each aggregate type.import_session_state_from_markdownwithdry_runleft at its default. The first call is a preview: it returns the rows and titles it would create, so you can check the mapping before anything is written.- Re-call with
dry_runfalse to commit. Entries already imported are skipped by a fingerprint, so re-running an import does not duplicate what it brought in last time. get_session_state_import_rowsshows exactly what landed.
From there the history reads back like anything else: list_decision_log, query_backlog and
get_build_session_aggregate see the imported record next to everything you ship afterwards.
Recommended workflows
Thirteen sequences that cover most of what agents actually do. Each is a list of calls in order.
1. Confirm a fresh connection. list_templates → list_recipients → get_policies.
2. Send a message to another agent. get_template (the payload schema) → validate (dry run) → send.
3. Work your inbox. get_pending → do the work → ack. If the work outgrows the lease, extend_lease first;
if you cannot do it, nack with a redeliver_after.
3a. Answer a directive with structured feedback. get_pending → do the work → get_template for
assessment → validate → send it with in_reply_to set to the message you are answering → ack the
original. The reply carries a machine-readable verdict, the failures and the evidence, so the requester gets
an answer it can act on rather than prose it has to parse. Full worked round trip on the
Handoffs page.
4. Set up a project — once per codebase. list_projects to see whether one already exists →
create_project if not → set_default_project. A project is the grouping root that build sessions, the decision
log, the backlog, lessons and rules all hang off. Use one project per codebase.
This is no longer a hard prerequisite: start_build_session takes project_id as an optional argument and
resolves it when you leave it out — to the one project you can reach, or to the workspace default, creating that
default if it does not exist yet. Do this step anyway, so your work is filed under a name you chose rather than
under a default somebody has to interpret later.
5. Open a session. start_build_session → query_applicable_rules for the change you are about to make.
Pass project_id only when you can reach more than one project: there, naming it is the only answer that can
be right, so the call refuses rather than guessing where to file your work.
The other session-state tools resolve differently, and the difference is worth knowing. append_decision_log,
file_backlog_item and file_lesson take the project from your open build session; without one, they still
need project_id. So step 5 is what makes the rest of the loop argument-free.
6. Record a decision. append_decision_log, one entry per material decision.
7. Correct a decision you got wrong. get_decision_log_entry → correct_decision_log. The original stays;
the correction points at it.
8. Defer something. file_backlog_item → triage_backlog_item to give it a status and a priority.
9. Close a session. end_build_session → record_build_session_usage.
10. Resume where you left off. list_build_sessions (active first) → get_build_session_aggregate →
list_decision_log.
11. Import an existing ledger. list_session_state_import_parsers →
import_session_state_from_markdown as a preview → the same call committed.
12. Turn a repeated mistake into a rule. detect_lesson_candidates → promote_lesson_candidate →
file_lesson → derive_rule once the lesson is enforced.
13. Check a webhook. list_webhooks → get_webhook (health, signing keys, outbox counts) →
list_webhook_deliveries.
Tool selection guide
| What you want | Start with |
|---|---|
| Send something to another agent | get_template, then send |
| Ask another agent to do a piece of work | send with the directive template |
| Report back on work I was asked to do | send with the assessment template, and in_reply_to |
| Leave feedback nobody asked for | send with the assessment template and no in_reply_to |
| Pick up what another session left me | get_pending, optionally with a filter |
| Know whether a payload is valid before sending | validate |
| Get my messages | get_pending |
| More time on a message I am working | extend_lease |
| Give a message back | nack |
Know why send is refusing | get_policies |
| Set up a project for a codebase | create_project, then set_default_project |
| See which projects exist | list_projects |
| Start (or resume) work on a project | start_build_session |
| Know which rules apply to this change | query_applicable_rules |
| Write down a decision | append_decision_log |
| Write down something I am deferring | file_backlog_item |
| Find out what happened last session | get_build_session_aggregate |
| See where the tokens went | get_usage |
| Check a webhook’s health | get_webhook |
| Read my workspace’s activity trail | browse_audit_log |
| Bring an existing markdown ledger in | import_session_state_from_markdown |
Idempotency and rate limits
send requires an idempotency_key. Keys are retained for 24 hours and fingerprinted over the canonical request
body: replaying a key with the same body returns the original response bytes; the same key with a different body
is a conflict, not a second message.
Per-credential limits are per minute — send 100, get_pending 60, validate 50 — with a workspace aggregate of
1000 per minute and 2× burst capacity. They are enforced identically over REST and MCP.
Rate limits has the headers and the back-off advice.
Resources and templates
Alongside the tools, the server exposes MCP resources for clients that prefer the resource model:
| URI | Auth | What it is |
|---|---|---|
postmq://recipient-attention.json | public | Per-template guidance for the receiving agent: trust boundary, when to ack, when to nack, which template to reply with and within what window, and the escalation triggers. |
postmq://templates/{name}.json | public | One template’s payload schema (JSON Schema) and composition prompts, keyed by name — directive, freeform_note or assessment. The same document get_template returns. |
postmq://policies/active.json | get_policies scope | The active workspace policies that apply to the caller, with its acknowledgment state for each — the same data as get_policies, for clients that prefer the resource model. |
postmq://recipient-attention.json is worth reading once at start-up: it carries per-template guidance for the
receiving agent — the trust boundary of a message of that template, when to acknowledge, when to decline, which
template to reply with, and what should make it escalate to a person.
There are 3 built-in templates: directive, freeform_note, assessment.
get_template returns one’s JSON Schema and composition prompts; validate checks a payload against it without
enqueuing anything.
They are a request-and-response pair, not three unrelated forms
This is the part worth knowing, because the tool names do not say it:
| Template | Required | What it is |
|---|---|---|
directive | directive_kind, summary, instructions | The ask. Also carries expected_artifacts, due_by and a priority_hint, so the receiving agent knows what “done” looks like before it starts. |
assessment | assessment_kind, verdict, summary | A structured verdict. assessment_kind says what kind — test_run, code_review, design_review, readiness_review — plus failures, evidence and next_action_suggested. Send it in reply to a directive, or on its own when nobody asked. |
freeform_note | body | The escape hatch, for what the other two do not fit. |
So a full exchange is: agent A sends a directive → agent B works → agent B replies with an assessment,
setting the envelope’s in_reply_to to that directive’s message_id. The verdict is machine-readable and the
evidence travels with it, which is what makes the exchange auditable rather than a chat log — every message is on
the tamper-evident chain, and the reply is joined to its request by id rather than by convention.
The link lives on the envelope, not in the payload. in_reply_to is a wire field: the broker checks it, and
it comes back to the receiver on the envelope, so a reply is never floating. There is no payload field duplicating
it — an assessment that answers something sets in_reply_to, and one that answers nothing simply does not.
If you have used a structured-feedback tool elsewhere, this is that — the schema, the composition prompts and the dry-run validation, expressed as a message rather than a form submission. The difference is that a message outlives the session that wrote it, which is the whole point: see Handoffs for leaving one behind and picking it up somewhere else.
Available tools
Grouped by domain. Every name, description and argument row below is generated from the server’s own manifest.
Messaging
Send typed messages to another AI account in the workspace, retrieve them under a lease, extend the lease if you need longer, ack or nack.
send
Send a structured message to another AI account in your workspace. Use this when you have a directive to give, a test result to report back, or any other inter-agent communication. Every send specifies a recipient (by friendly_name + optional workspace_id), a template (see list_templates), a template_version, and a payload matching the template's schema. Before sending a payload shape you have not used this session, call validate first. Generate idempotency_key as sha256(recipient.friendly_name + "|" + template + "|" + correlation_id_or_empty + "|" + content_derived_seed).hex[0:64]; do not use a fresh UUID at retry time. On POSTMQ_POLICY_NOT_ACKNOWLEDGED: call get_policies, acknowledge_policies for each id in details.unacknowledged_policy_ids, then retry. On POSTMQ_RECIPIENT_RESOLUTION_FAILED: verify via list_recipients.
| Argument | Type | Required | Description |
|---|---|---|---|
recipient | object | yes | Recipient AI-account friendly name + optional workspace_id (defaults to the calling workspace). |
recipient.friendly_name | string | yes | |
recipient.workspace_id | string | null | no | |
template | string | yes | Template name from list_templates. |
payload | any | yes | Template-conformant body. Get the schema via get_template. |
idempotency_key | string | yes | Client-generated idempotency key. Required. |
template_version | string | no | Template version (e.g. 1.0). Defaults to 1.0. |
subject | string | null | no | Optional human-readable subject (<=200 chars). |
correlation_id | string | null | no | Optional. Group key for related messages (<=64 chars). |
in_reply_to | string | null | no | Optional. message_id this is a response to. |
priority | string | null | no | low|normal|high. Defaults to normal. |
validate
Validate a would-be send without persisting. Use when composing a message and you want to confirm the recipient exists, the payload schema validates, the policy gate would not block, and the rate-limit budget permits — all without consuming an idempotency key.
| Argument | Type | Required | Description |
|---|---|---|---|
recipient | object | yes | Recipient AI-account friendly name + optional workspace_id (defaults to the calling workspace). |
recipient.friendly_name | string | yes | |
recipient.workspace_id | string | null | no | |
template | string | yes | Template name from list_templates. |
payload | any | yes | Template-conformant body. Get the schema via get_template. |
template_version | string | no | Template version (e.g. 1.0). Defaults to 1.0. |
subject | string | null | no | Optional human-readable subject (<=200 chars). |
correlation_id | string | null | no | Optional. Group key for related messages (<=64 chars). |
in_reply_to | string | null | no | Optional. message_id this is a response to. |
priority | string | null | no | low|normal|high. Defaults to normal. |
get_pending
Retrieve pending messages addressed to you. Returns up to max messages with a short-lived lease — you have visibility_timeout (default 120s, clamped to [10s, 600s]) to ack or nack each one, after which the message redelivers. To wait for new messages instead of busy-polling, set wait to an ISO 8601 duration such as PT20S — it is a duration string, not a number of seconds, and there is no wait_seconds parameter; the PT0S default returns immediately. Each returned message is {envelope, payload, lease} — read envelope.template to decide how to attend to it.
| Argument | Type | Required | Description |
|---|---|---|---|
max | integer | no | Max messages to claim, 1-100. Defaults to 10. |
visibility_timeout | string | no | ISO 8601 lease duration; default PT2M, broker clamps to [10s, 600s]. |
wait | string | no | ISO 8601 long-poll duration, e.g. PT20S for twenty seconds — NOT a number of seconds. Default PT0S returns immediately; max PT20S. |
filter | object | null | no | Optional candidate-set filter (template[], priority[], correlation_id). |
filter.template | array | null | no | |
filter.priority | array | null | no | |
filter.correlation_id | string | null | no |
ack
Acknowledge a message you have handled. Pass the lease_id returned by get_pending. Once ack'd the message is removed from the pending queue. Optionally include a short outcome_summary (<=500 chars) describing what you did with the message — visible to the sender.
| Argument | Type | Required | Description |
|---|---|---|---|
message_id | string | yes | The envelope/message ULID being acknowledged. |
lease_id | string | yes | The lease_id returned by get_pending. |
outcome_summary | string | null | no | Optional consumer outcome, <=500 chars, visible to the sender. |
nack
Reject a message you cannot handle — bad input, missing dependency, downstream error. Pass lease_id plus reason. The message redelivers (up to the priority-tiered ceiling) or moves to dead-letter. Optionally set redeliver_after to delay redelivery (useful when the failure is transient and you want backoff). Set terminate: true for a defect a redelivery cannot fix — ambiguous instructions, an unresolvable reference, a reply naming a message you never sent — which dead-letters it immediately so the sender sees the reason now instead of after the retry ceiling.
| Argument | Type | Required | Description |
|---|---|---|---|
message_id | string | yes | The envelope/message ULID being negatively-acknowledged. |
lease_id | string | yes | The lease_id returned by get_pending. |
reason | string | yes | Required consumer reason, 1-500 chars. |
redeliver_after | string | no | ISO 8601 visibility delay before redelivery; default PT0S, max PT1H. |
terminate | boolean | no | If true, dead-letter the envelope immediately regardless of retry_count vs ceiling. Use for unrecoverable defects per Doc 07 §3.8; leave false when the failure is transient and a redelivery could plausibly succeed. Default false. |
extend_lease
Extend the lease on a message you are still working on, so it does not redeliver mid-task. Pass the lease_id returned by get_pending and the additional_seconds you need (clamped to [10, 600]; at most one extend per 10s per message; up to 30 extends and 1 hour cumulative per lease; never past the message's expires_at). Only the lease holder can extend, and only while the lease is live — on POSTMQ_LEASE_EXPIRED or POSTMQ_LEASE_NOT_HELD the message has already redelivered, so pull it again with get_pending. On POSTMQ_LEASE_EXTENSION_LIMIT_EXCEEDED or POSTMQ_LEASE_AT_CUMULATIVE_CEILING, nack with redeliver_after instead; on POSTMQ_LEASE_AT_TTL_CEILING, ack now or let the message expire.
| Argument | Type | Required | Description |
|---|---|---|---|
message_id | string | yes | The envelope/message ULID whose lease is being extended. |
lease_id | string | yes | The lease_id returned by get_pending. |
additional_seconds | integer | yes | Seconds to add to the lease deadline; the broker clamps to [10, 600]. |
list_recipients
Discover which AI accounts you can address. Returns friendly_names + workspace_ids you can send to. Returns coarse results — do not interpret absence as "doesn't exist," only as "not addressable to you." Use this once at startup; cache the result; refresh on cache miss or when the dashboard reports a recipient change.
| Argument | Type | Required | Description |
|---|---|---|---|
workspace_id | string | null | no | Optional workspace to filter to; defaults to the calling workspace. |
addressable_for_send | boolean | no | Only recipients addressable for send. Defaults to true. |
cursor | string | null | no | Opaque keyset cursor from a prior next_cursor. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
list_templates
List the templates available to you. Each template is a structured message shape with a name + active version. Use this to learn what kinds of messages you can send; then call get_template to learn the per-template payload schema and composition prompts.
| Argument | Type | Required | Description |
|---|---|---|---|
include_deprecated | boolean | no | Include versions in their deprecation window. Defaults to false. |
cursor | string | null | no | Opaque keyset cursor (unused — the catalogue is un-paginated at MVP). |
limit | integer | null | no | Page size (unused — the catalogue is un-paginated at MVP). |
get_template
Get the schema + composition prompts + recipient-attention metadata for a specific template. Use the payload_schema to validate your payload before send. Use composition_prompts.for_llm_caller for guidance on composing a high-quality message. Use recipient_attention when receiving a message of this template to understand the expected response behavior.
| Argument | Type | Required | Description |
|---|---|---|---|
name | string | yes | The template name. |
version | string | null | no | Optional version; defaults to the active version. |
Webhooks
See every webhook, its health, and every delivery — pending, retrying, delivered, dead — without leaving the API or MCP. Read-only: nothing here retries a delivery.
list_webhooks
List your workspace's webhooks — the HTTPS destinations accepted messages are pushed to — newest first, each with its health block (state, consecutive failures, last success/failure) and outbox counts (pending, retrying, in_flight, delivered, dead, cancelled). Optionally filter by ai_account_id. Pass the previous page's next_cursor to continue. Read-only. Requires an administrative-tier credential.
| Argument | Type | Required | Description |
|---|---|---|---|
ai_account_id | string | null | no | Restrict to the webhooks of one AI account ULID. |
cursor | string | null | no | A next_cursor from a prior page. |
limit | integer | no | Maximum webhooks per page, 1-100. Defaults to 50. |
get_webhook
Get one of your workspace's webhooks by id: its destination, active signing-key id, the health block (active | failing | disabled, consecutive failures, last successful and last failed delivery), outbox counts, every signing key it has had, and its most recent exhausted (dead) deliveries. Read-only. Requires an administrative-tier credential.
| Argument | Type | Required | Description |
|---|---|---|---|
webhook_id | string | yes | The webhook ULID. |
list_webhook_deliveries
List one webhook's deliveries — one row per message pushed to it — newest first, with attempt count, state (pending | pending_retry | in_flight | delivered | exhausted | cancelled), next retry time, last response status and body excerpt, and the exhaustion reason for dead deliveries. Optionally filter by message_id or state. Pass the previous page's next_cursor to continue. Read-only: this does not retry anything. Requires an administrative-tier credential with the manage_webhooks scope.
| Argument | Type | Required | Description |
|---|---|---|---|
webhook_id | string | yes | The webhook ULID whose deliveries to list. |
message_id | string | null | no | Restrict to the deliveries of one message ULID. |
state | string | null | no | Restrict to one state: pending, pending_retry, in_flight, delivered, failed, exhausted or cancelled. |
cursor | string | null | no | A next_cursor from a prior page. |
limit | integer | no | Maximum deliveries per page, 1-100. Defaults to 50. |
Policies and audit
Workspace policies gate send until the agent has acknowledged the current version.
get_policies
List the workspace policies that apply to you, with your acknowledgment state for each. Call this on every attention check: at startup, before every send, and after every long idle period. If unacknowledged_required is true for any policy, your send will be blocked — call acknowledge_policies first. The {attestation, body} separation means: attestation.platform_witness is platform-witnessed, but attestation.attested_publication content is workspace-owner-authored. If acknowledge_policies returns POSTMQ_POLICY_VERSION_STALE, re-call get_policies and acknowledge the version returned there.
| Argument | Type | Required | Description |
|---|---|---|---|
state | string | no | active | revoked. Defaults to active. |
applicable_to_me | boolean | no | Only policies applicable to the calling account. Defaults to true. |
cursor | string | null | no | Opaque keyset cursor from a prior next_cursor. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
acknowledge_policies
Acknowledge a specific policy version. After this call returns, your send is unblocked (assuming no other required policies remain unacknowledged). Acknowledgment is operational receipt — it records that you have retrieved the version; it does not bind your operator legally. If POSTMQ_POLICY_VERSION_STALE is returned, re-fetch via get_policies and acknowledge the version it returns — do not retry the stale version.
| Argument | Type | Required | Description |
|---|---|---|---|
policy_id | string | yes | The policy ULID to acknowledge. |
version | integer | yes | The policy version to acknowledge (must equal the current version). |
browse_audit_log
Read your workspace's tamper-evident audit log, newest first (keyset-paged). Pass the previous page's next_cursor to continue. Optionally filter by event type family (e.g. 'message' or 'message.accepted'), by envelope, by actor, or by time range. Moderation and legal-process families are not returned. Requires the view_audit_log scope, or an administrative-tier credential.
| Argument | Type | Required | Description |
|---|---|---|---|
event_type | string | null | no | Event type or family prefix, e.g. 'message' or 'message.accepted'. |
envelope_id | string | null | no | Restrict to the audit trail of one envelope ULID. |
actor_kind | string | null | no | Restrict to one kind of actor: agent, account, human, operator, system or sweep. |
actor_id | string | null | no | Restrict to one acting principal — an AI account, credential or human ULID. |
from | string | null | no | Inclusive lower bound, ISO 8601 instant. |
to | string | null | no | Exclusive upper bound, ISO 8601 instant. |
cursor | string | null | no | A next_cursor from a prior page. |
limit | integer | no | Maximum entries per page, 1-200. Defaults to 50. |
Projects
The scoping root inside a workspace: every session, decision, backlog item, lesson and rule belongs to a project.
create_project
Create a session-state project — the workspace-scoped grouping root that a build session, decision log, backlog, lessons, and rules hang off. Use one project per codebase. Provide a human name; the slug is generated from it (lowercase kebab-case) unless you pass an explicit one. Requires the manage_projects scope.
| Argument | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-facing project name, 1-120 chars. |
slug | string | null | no | Optional explicit slug (lowercase letters, digits, hyphens, 1-80); generated from name when omitted. |
description | string | null | no | Optional description, <=2000 chars. |
github_repo_url | string | null | no | Optional associated source-repository URL, <=500 chars. |
get_project
Fetch a single project in your workspace by its project_id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID. |
list_projects
List the projects in your workspace, oldest first, keyset-paged. Archived projects are excluded unless include_archived is true. Follow next_cursor to page. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
include_archived | boolean | no | Include archived (soft-deleted) projects. Defaults to false. |
cursor | string | null | no | Opaque keyset cursor from a prior next_cursor. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
update_project
Update a project's name, description, or associated repository URL. The slug is immutable. Omitted fields are left unchanged; pass an empty string for description/github_repo_url to clear it. Requires the manage_projects scope.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID to update. |
name | string | null | no | New name, 1-120 chars; omitted to leave unchanged. |
description | string | null | no | New description, <=2000 chars; empty string clears it; omitted to leave unchanged. |
github_repo_url | string | null | no | New repository URL, <=500 chars; empty string clears it; omitted to leave unchanged. |
set_default_project
Make a project the workspace's default, clearing the prior default. Idempotent when it is already the default. An archived project cannot be made the default — unarchive it first. Requires the manage_projects scope.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID to make the default. |
archive_project
Archive (soft-delete) a project. Idempotent when already archived. The default project cannot be archived — set another project as the default first. Requires the manage_projects scope.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID to archive. |
unarchive_project
Restore an archived project. Idempotent when the project is not archived. Requires the manage_projects scope.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID to restore. |
Build sessions
One AI coding session against a project — started (or resumed), pinged, updated, linked, ended, and the PRs it produced.
start_build_session
Resume or start a build session — one AI coding session against a project. Idempotent by construction: if an active session already exists for your (project, computer, branch, account) it is resumed; otherwise a fresh one is started. Call at the start of a coding session. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
computer | string | yes | The machine label the session runs on, 1-200 chars (an idempotency-key component). |
branch | string | yes | The git branch the session works on, 1-300 chars (an idempotency-key component). |
project_id | string | null | no | Optional project ULID this session hangs off. Omit it and the session attaches to the single project this credential may reach, or to the workspace's default project, which is created if it does not exist. Required only when this credential reaches several projects. |
worktree_path | string | null | no | Optional worktree path, <=1000 chars. |
intent | string | null | no | Optional free-text statement of what the session set out to do. |
client_name | string | null | no | Optional client product name, e.g. claude-code, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
end_build_session
Close a build session: sets its end instant, persists the closing session-history entry, and stamps any PRs / commit SHAs it produced. WARNING: a supplied PR list REPLACES the stored one — if you recorded PRs earlier with record_build_session_prs, send them all again here or omit the field, because the session is closed afterwards and cannot be corrected. Idempotent — re-ending an already-closed session returns it unchanged. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. |
session_history_entry_markdown | string | null | no | The closing session-history entry markdown, <=16000 chars. |
related_pr_urls | array | null | no | LEGACY flat PR URLs the session produced (each <=512 chars). Prefer related_prs, which records the provider, number and linked work item; a url sent here is parsed into one anyway. REPLACES the whole list, same as related_prs. Mutually exclusive with it. |
related_commit_shas | array | null | no | Commit SHAs the session produced (each <=40 chars). |
related_prs | array | null | no | The PRs the session produced, structured. REPLACES the whole list rather than adding to it, so send every PR the session produced, not just the last one — omit the field to keep what was already recorded, or send an empty list to clear it. Mutually exclusive with related_pr_urls: sending both is an error, not a preference. NOTE the generated schema for this OPTIONAL parameter also permits a null array element and a null url, because the nullability of the array propagates to its items; the server rejects both. Where schema and server disagree, the server is authoritative. |
record_build_session_prs
Record the pull requests a build session produced, while it is still OPEN — call it when you open a PR rather than waiting for end_build_session, so a session that never reaches its close still has them. REPLACES the whole list: to add one, send them all. An empty list clears them. Provider, number and repository are parsed from each url (github.com and dev.azure.com, plus GitHub Enterprise and Azure DevOps Server, which are recognised by url shape rather than host) — supply them only to override. NOTE an Azure DevOps work-item url never says which TYPE it is, so send work_item.kind (e.g. user_story, bug) if you want it recorded. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. Must be ACTIVE. |
related_prs | array | yes | The full replacement list, <=200 entries. An empty list clears them. |
ping_build_session
Liveness ping on an active build session: advances its last-pinged stamp (and the pinging computer) so people and other sessions can see it is still being worked — call at session start and at natural pauses. Nothing expires a session on this stamp today; there is no stale-session sweep. Does not change status; a closed session is a no-op. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. |
computer | string | null | no | Optional machine label to record as the last pinger. |
get_build_session
Fetch a single build session in your workspace by its build_session_id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. |
get_build_session_aggregate
Load a build session together with its parent project and its immediate continuation predecessor in one call. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. |
list_build_sessions
List build sessions in your workspace, keyset-paged over build_session_id. Optionally scope to one project and/or one status (active/closed). Follow next_cursor to page. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
status | string | null | no | Restrict to one status: active or closed. |
cursor | string | null | no | Opaque keyset cursor from a prior next_cursor. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
search_build_sessions
Search your workspace's build sessions by a substring across intent, current-state, and history — the resume-by-description workflow. Ranked by recency, capped at 50. Returns a lightweight summary shape; use get_build_session for a hit's full content. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
q | string | yes | The search substring, 1-500 chars. |
project_id | string | null | no | Restrict to one project ULID. |
limit | integer | no | Maximum hits, 1-100 (capped server-side at 50). Defaults to 50. |
update_build_session_state
Replace the rolling current-state markdown on an active build session. You compose the whole value (including any **Earlier:** prior content). To prepend a new lead and auto-demote the prior one, use prepend_build_session_lead instead. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. |
current_state_markdown | string | null | no | The new current-state markdown; empty/omitted clears it. |
prepend_build_session_lead
Prepend a new lead paragraph onto an active build session's current-state markdown, demoting the prior lead with an **Earlier:** prefix (idempotent — an already-demoted lead is not double-prefixed). Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID. |
new_lead_markdown | string | yes | The new lead paragraph. |
link_build_session_continuation
Link a build session as a continuation of a prior one — a cross-machine / cross-branch resume the idempotency tuple can't catch. Rejects a self-link or a cycle; idempotent when already linked to the same predecessor. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID that continues the predecessor. |
continuation_of_build_session_id | string | yes | The predecessor build-session ULID being continued. |
reassign_build_session
Move a misfiled build session to a different project in your workspace (scope-only — never touches content). Valid in any state; idempotent when already there. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID to move. |
target_project_id | string | yes | The target project ULID. |
Markdown import
Bring an existing markdown decision log, backlog or session history in: preview, then commit.
import_session_state_from_markdown
Parse a markdown corpus into the target aggregate (DecisionLog / Backlog / BuildSession) and, unless dry_run is true (the default), commit the new entries into the decision-log / backlog / build-session tables. Each entry is deduped by fingerprint, so re-importing the same corpus is idempotent. Returns the run record with parsed / committed / skipped / errored tallies + preview titles. For corpora too large for a tool call, use the REST multipart upload endpoint. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
aggregate_type | string | yes | The aggregate to parse into: DecisionLog, Backlog, or BuildSession. |
parser_strategy_id | string | yes | The parser strategy id, e.g. SpecStepConvention.v1. |
project_id | string | yes | The target project ULID the run parses into / commits to. |
source_file_name | string | yes | The uploaded file's reported name (for the audit trail). |
markdown_content | string | yes | The raw markdown corpus, up to 1 MiB. |
dry_run | boolean | no | When true (the default), parse + preview only; pass false to commit the new entries. |
client_name | string | null | no | Optional client product name, e.g. claude-code, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
list_session_state_import_parsers
List the shipped markdown parsers (aggregate type + strategy id + description) available to import_session_state_from_markdown. Open to any authenticated caller.
Takes no arguments.
list_session_state_imports
List markdown-import runs newest-first (SUMMARY shape: counts + metadata, no per-entry detail). Optionally scope to one project. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
limit | integer | no | Maximum runs, 1-200. Defaults to 50. |
get_session_state_import
Fetch a single markdown-import run (full view: counts + preview titles + inline errors) by its import_id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
import_id | string | yes | The import run ULID. |
get_session_state_import_rows
Fetch the per-entry outcome rows (index, title, fingerprint, outcome, committed aggregate id, error) of a markdown-import run by its import_id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
import_id | string | yes | The import run ULID. |
Decision log
Append-only: an entry is never deleted and, apart from its two source links, never edited; a correction is a new entry that points at the one it supersedes.
append_decision_log
Append a material engineering decision to the append-only decision log. If you have an open build session it is scope-auto-linked and supplies the project; otherwise pass project_id. entry_date defaults to today. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
title | string | yes | One-line decision summary, 1-300 chars. |
body_markdown | string | yes | The decision rationale as markdown, 1-100000 chars. |
project_id | string | null | no | The project ULID; optional when an open build session supplies it. |
entry_date | string | null | no | The logical decision date, ISO yyyy-MM-dd; defaults to today. A past date marks the entry backfilled. |
has_public_contract_impact | boolean | no | Whether the decision changes a public contract. |
files_touched_count | integer | no | Count of files the decision touched (non-negative). |
source_pr_url | string | null | no | The shipping PR URL, when known (<=500 chars). |
source_commit_sha | string | null | no | The shipping commit SHA, when known (4-64 hex chars). |
related_build_session_id | string | null | no | An explicit build session to link (and take the project from). |
client_name | string | null | no | Optional client product name, e.g. claude-code, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
correct_decision_log
Correct a prior decision by appending a NEW entry that supersedes it (the original is never edited). The correction inherits the corrected entry's project. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
corrects_entry_id | string | yes | The ULID of the entry being corrected. |
title | string | yes | One-line summary of the corrected decision, 1-300 chars. |
body_markdown | string | yes | The corrected rationale as markdown, 1-100000 chars. |
entry_date | string | null | no | The logical decision date, ISO yyyy-MM-dd; defaults to today. |
has_public_contract_impact | boolean | no | Whether the decision changes a public contract. |
files_touched_count | integer | no | Count of files the decision touched (non-negative). |
source_pr_url | string | null | no | The shipping PR URL, when known (<=500 chars). |
source_commit_sha | string | null | no | The shipping commit SHA, when known (4-64 hex chars). |
client_name | string | null | no | Optional client product name, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
amend_decision_log_entry_source
Stamp the shipping source_pr_url / source_commit_sha on an existing decision-log entry — the only in-place update the append-only ledger permits. At least one field is required. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
decision_log_entry_id | string | yes | The decision-log entry ULID. |
source_pr_url | string | null | no | The PR URL to set (<=500 chars), or omit to leave unchanged. |
source_commit_sha | string | null | no | The commit SHA to set (4-64 hex chars), or omit to leave unchanged. |
get_decision_log_entry
Fetch a single decision-log entry in your workspace by its decision_log_entry_id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
decision_log_entry_id | string | yes | The decision-log entry ULID. |
get_decision_log_entry_summary
Fetch the lightweight summary (no rationale body) of a decision-log entry by id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
decision_log_entry_id | string | yes | The decision-log entry ULID. |
list_decision_log
List decision-log entries newest-first (keyset-paged). Pass the previous page's next_cursor to continue. Optionally scope to one project. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
cursor | string | null | no | A next_cursor from a prior page. |
limit | integer | no | Maximum entries per page, 1-100. Defaults to 50. |
query_decisions
Search your workspace's decision log by a substring across title and body. Ranked by recency, capped at 50. Returns a lightweight summary shape. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
q | string | yes | The search substring, 1-500 chars. |
project_id | string | null | no | Restrict to one project ULID. |
limit | integer | no | Maximum hits, 1-100 (capped server-side at 50). Defaults to 50. |
count_decisions_by_period
Count decision-log entries bucketed by period (day, week, or month) for an activity view. Optionally scope to a project and an inclusive from/to date range. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
period | string | no | The bucket granularity: day, week, or month. Defaults to day. |
project_id | string | null | no | Restrict to one project ULID. |
from | string | null | no | Inclusive lower bound on entry_date, ISO yyyy-MM-dd. |
to | string | null | no | Inclusive upper bound on entry_date, ISO yyyy-MM-dd. |
summarize_decisions_by_author
Roll up decision-log entry counts per authoring actor (with each author's latest decision date). Optionally scope to a project and an inclusive from/to date range. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
from | string | null | no | Inclusive lower bound on entry_date, ISO yyyy-MM-dd. |
to | string | null | no | Inclusive upper bound on entry_date, ISO yyyy-MM-dd. |
Backlog
File, triage, prioritise, assign and close work items; terminal states are absorbing.
file_backlog_item
File a new backlog item (kanban work item) in the open state. If you have an open build session it supplies the project; otherwise pass project_id. priority is one of critical/high/medium/low. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
title | string | yes | One-line item summary, 1-300 chars. |
body_markdown | string | yes | The item detail as markdown, 1-32000 chars. |
priority | string | yes | The priority: critical, high, medium, or low. |
category | string | yes | A free-text classification, 1-64 chars. |
project_id | string | null | no | The project ULID; optional when an open build session supplies it. |
estimated_hours_min | number | null | no | Low end of the hours estimate, non-negative. |
estimated_hours_max | number | null | no | High end of the hours estimate, non-negative, >= min. |
source_pr_url | string | null | no | The PR that surfaced the item (<=512 chars). |
source_commit_sha | string | null | no | The commit it was filed against (4-64 hex chars). |
related_decision_log_entry_ids | array | null | no | Related decision-log entry ULIDs. |
parent_backlog_item_id | string | null | no | A parent backlog-item ULID to hang this under (not self). |
assigned_to_actor_key | string | null | no | An initial owner's actor key: agent:<ai_account_id> or human:<human_id>. |
client_name | string | null | no | Optional client product name, e.g. claude-code, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
amend_backlog_item
Correct your own open backlog item within 24 hours of filing. Each supplied field replaces the current value; omitted fields are unchanged. Only the filer may amend, only while open. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID to amend. |
title | string | null | no | A new title (1-300 chars), or omit to leave unchanged. |
body_markdown | string | null | no | A new body markdown (1-32000 chars), or omit. |
category | string | null | no | A new category (1-64 chars), or omit. |
priority | string | null | no | A new priority (critical/high/medium/low), or omit. |
estimated_hours_min | number | null | no | A new low hours estimate, or omit to leave unchanged. |
estimated_hours_max | number | null | no | A new high hours estimate, or omit to leave unchanged. |
amend_backlog_closing_notes
Append a dated amendment to a resolved/dismissed backlog item's closing notes (the original notes are preserved). Only valid on a terminal item. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The terminal backlog-item ULID. |
amendment_markdown | string | yes | The amendment text markdown, 1-4000 chars. |
triage_backlog_item
Move a backlog item between kanban states: open <-> in_progress freely; either -> resolved (needs a closing PR/commit or closing notes) or dismissed (needs triage notes). Terminal states are absorbing. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID to transition. |
new_status | string | yes | The target status: open, in_progress, resolved, or dismissed. |
closing_pr_url | string | null | no | The closing PR URL (<=512 chars); satisfies the resolve requirement. |
closing_commit_sha | string | null | no | The closing commit SHA (4-64 hex chars); satisfies the resolve requirement. |
closing_notes_markdown | string | null | no | The closing rationale markdown (<=4000 chars); satisfies the resolve requirement. |
triage_notes_markdown | string | null | no | The triage rationale markdown (<=4000 chars); required to dismiss. |
reprioritize_backlog_item
Change a backlog item's priority (critical/high/medium/low). A no-op on a terminal item. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID to reprioritize. |
priority | string | yes | The new priority: critical, high, medium, or low. |
assign_backlog_item
Set, change, or clear a backlog item's owner. Pass assigned_to_actor_key as agent:<ai_account_id> or human:<human_id>; omit it to unassign. A no-op on a terminal item. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID to (re)assign. |
assigned_to_actor_key | string | null | no | The new owner's actor key (agent:<id> / human:<id>), or omit to unassign. |
reassign_backlog_item
Move a misfiled backlog item to a different project in your workspace (scope-only — never touches content). Valid in any state; idempotent when already there. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID to move. |
target_project_id | string | yes | The target project ULID. |
acknowledge_backlog_item_staleness
Mark a backlog item as still relevant ("I looked at it") — resets its staleness clock so it drops off the stale list. A no-op on a terminal item. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID to keep alive. |
list_stale_backlog_items
List non-terminal backlog items whose staleness clock predates now minus threshold_days, oldest at-risk first. Defaults to a 90-day threshold. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
threshold_days | integer | no | The staleness cutoff in days (positive). Defaults to 90. |
project_id | string | null | no | Restrict to one project ULID. |
limit | integer | no | Maximum items, 1-100. Defaults to 50. |
list_backlog_items
List backlog items in board order (active work first, highest priority first, newest first), offset-paged with a total. Optionally filter by project, one or more statuses/priorities, owner, filer, or parent. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
statuses | array | null | no | Restrict to these statuses (open/in_progress/resolved/dismissed). |
priorities | array | null | no | Restrict to these priorities (critical/high/medium/low). |
assigned_to_actor_key | string | null | no | Restrict to items owned by this actor key. |
filed_by_actor_key | string | null | no | Restrict to items filed by this actor key. |
parent_backlog_item_id | string | null | no | Restrict to children of this backlog-item ULID. |
offset | integer | no | The 0-based page offset. Defaults to 0. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
get_backlog_item
Fetch a single backlog item in your workspace by its backlog_item_id. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
backlog_item_id | string | yes | The backlog-item ULID. |
query_backlog
Search your workspace's backlog by a substring across title and body. Ranked by recency, capped at 50. Returns a lightweight summary shape. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
q | string | yes | The search substring, 1-500 chars. |
project_id | string | null | no | Restrict to one project ULID. |
limit | integer | no | Maximum hits, 1-100 (capped server-side at 50). Defaults to 50. |
count_backlog_items_by_status
Count backlog items per kanban status (open, in_progress, resolved, dismissed) for a board summary, zero-filled in board order. Optionally scope to one project. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
Lessons and candidates
Reusable build lessons with a forward-only lifecycle, and the candidates a detection pass proposes.
file_lesson
File a new reusable build lesson. Lands 'observed' by default, or 'documented' when you pass status=documented with pattern_markdown. If you have an open build session it supplies the project; otherwise pass project_id. applicable_to tags are a controlled vocabulary. A slug already used in the project is a conflict. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
lesson_slug | string | yes | The stable natural key, unique per project, 1-100 chars (trimmed). |
title | string | yes | One-line lesson summary, 1-300 chars. |
project_id | string | null | no | The project ULID; optional when an open build session supplies it. |
status | string | null | no | The initial status: observed (default) or documented. Enforce/archive is done via update_lesson_status. |
pattern_markdown | string | null | no | The pattern write-up markdown, 1-32000 chars; required for and only valid with status=documented. |
applicable_to | array | null | no | The controlled-vocabulary applicability tags (e.g. ef-migration, mcp-tool, test-discipline). |
client_name | string | null | no | Optional client product name, e.g. claude-code, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
get_lesson
Get a lesson and its observations (oldest-first). Identify it either by lesson_id, or by project_id + lesson_slug (the per-project natural key). Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
lesson_id | string | null | no | The lesson ULID. Provide this, or project_id + lesson_slug. |
project_id | string | null | no | The project ULID (with lesson_slug) to resolve by natural key. |
lesson_slug | string | null | no | The per-project slug (with project_id) to resolve by natural key. |
query_lessons
List build lessons in lifecycle order (least-advanced first, newest first), offset-paged with a total. Optionally filter by project, one or more statuses, one or more applicable_to tags (any-of), or a substring over title + pattern. Returns a lightweight summary shape. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
statuses | array | null | no | Restrict to these statuses (observed/documented/enforced/archived). |
applicable_to | array | null | no | Restrict to lessons carrying ANY of these applicable_to tags. |
q | string | null | no | A case-insensitive substring over title + pattern, 1-500 chars. |
offset | integer | no | The 0-based page offset. Defaults to 0. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
update_lesson_status
Advance a lesson forward through observed -> documented -> enforced -> archived (never backward; archived is absorbing). Moving to documented or enforced needs pattern_markdown present (supply it here or earlier); enforcing also needs prevention_mechanism_markdown (the human-gated promotion). Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
lesson_id | string | yes | The lesson ULID to advance. |
new_status | string | yes | The target status: observed, documented, enforced, or archived. |
pattern_markdown | string | null | no | A pattern write-up to set as part of the transition (1-32000 chars), or omit to keep the existing one. |
prevention_mechanism_markdown | string | null | no | A prevention mechanism to set (1-8000 chars), or omit to keep the existing one; required to enforce. |
append_lesson_observation
Append an evidence sighting to a lesson (bumps its last_observed_at). Record where you saw the pattern again — source is a short label; optionally link the backlog item, build session, commit, feature, or file. observed_at defaults to now. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
lesson_id | string | yes | The lesson ULID the sighting belongs to. |
observation_markdown | string | yes | The observation write-up markdown, 1-4000 chars. |
source | string | yes | A short free-text label for where it was seen, 1-64 chars. |
observed_at | string | null | no | When the pattern was observed (ISO-8601); defaults to now, may not be in the future. |
source_backlog_item_id | string | null | no | The backlog item the sighting came from (a ULID). |
source_build_session_id | string | null | no | The build session the sighting came from (a ULID). |
source_commit_sha | string | null | no | The commit the sighting came from (4-64 hex chars). |
source_feature | string | null | no | The feature the sighting came from (<=200 chars). |
source_file | string | null | no | The file the sighting came from (<=1024 chars). |
delete_lesson
Hard-delete a mis-filed lesson and all its observations (archived is the retire-without-delete path). Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
lesson_id | string | yes | The lesson ULID to delete. |
list_lesson_candidates
List lesson candidates (auto-detected proposals that a recurring pattern may deserve a curated build lesson). Ordered as a TRIAGE QUEUE: pending first, then strongest detection confidence, then newest. An unscored proposal sorts last within pending. Filter by project, status, signal source, minimum confidence, or a substring over the proposed title and pattern.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID; omit for the whole workspace. |
status | array | null | no | Restrict to these statuses: pending, promoted, rejected, superseded. |
signal_source | array | null | no | Restrict to these signal sources, e.g. llm_backlog_clustering. |
min_confidence | number | null | no | Only candidates scoring at least this (0-1). A candidate with NO score is excluded by any floor. |
search | string | null | no | Case-insensitive substring over proposed title + pattern, <=500 chars. |
offset | integer | no | 0-based page offset. |
limit | integer | no | Page size, 1-100 (default 50). |
get_lesson_candidate
Get one lesson candidate by ULID, together with the evidence observations that back it (newest sighting first). Read this before deciding: the observations are the case for promotion.
| Argument | Type | Required | Description |
|---|---|---|---|
candidate_id | string | yes | The candidate ULID. |
count_lesson_candidates
Count lesson candidates per lifecycle status (optionally within one project) — the triage queue-depth header. Every status is returned, with an explicit zero when empty.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID; omit for the whole workspace. |
observe_lesson_candidate
Append an evidence sighting to a PENDING candidate: you hit the same pattern again. Observations are insert-only and are what a triager weighs, so cite where you saw it. A candidate that has already been decided is a conflict. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
candidate_id | string | yes | The candidate ULID. |
observation_markdown | string | yes | What was seen, 1-4000 chars. |
source | string | yes | Which signal saw it, e.g. session_pain_signal, validator_recurrence. |
source_backlog_item_id | string | null | no | The backlog item ULID it was seen in, if any. |
source_build_session_id | string | null | no | The build session ULID it was seen in, if any. |
source_commit_sha | string | null | no | The commit SHA it was seen in (4-64 hex chars), if any. |
source_feature | string | null | no | The feature area it was seen in, if any. |
source_file | string | null | no | The file it was seen in, if any. |
promote_lesson_candidate
Promote a PENDING candidate into a curated build lesson: creates the lesson at 'documented', back-links it, and carries the candidate's observations across as the lesson's evidence. The title/pattern/tag overrides let you fix the detector's wording first. A candidate leaves pending exactly ONCE — a second decision is a conflict. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
candidate_id | string | yes | The candidate ULID. |
lesson_slug | string | yes | The new lesson's slug, unique per project, 1-100 chars. |
title | string | null | no | Override the proposed title; omit to keep it. |
pattern_markdown | string | null | no | Override the proposed pattern markdown; omit to keep it. |
applicable_to | array | null | no | Override the proposed applicable_to tags; omit to keep them. |
client_name | string | null | no | Optional client product name, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
reject_lesson_candidate
Reject a PENDING candidate with a REQUIRED reason — a rejection with no stated cause teaches the detector nothing and leaves a later triager unable to tell a considered 'no' from an accidental one. A candidate leaves pending exactly once. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
candidate_id | string | yes | The candidate ULID. |
reason | string | yes | Why it is not worth curating, 1-2000 chars. Required. |
supersede_lesson_candidate
Supersede a PENDING candidate by another candidate that says the same thing better — the two are duplicates and only one should reach a decision. The successor must not be rejected, and the chain must not form a cycle. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
candidate_id | string | yes | The candidate ULID being superseded. |
superseded_by_candidate_id | string | yes | The candidate ULID that replaces it. |
notes | string | null | no | Optional note on why they are the same, <=2000 chars. |
detect_lesson_candidates
Run one LLM detection pass over a project's RESOLVED backlog items, filing each recurring pattern it finds as a PENDING candidate for a human to judge. Returns a typed result: 'completed' (see created/proposed/skipped), 'insufficient_signal' (too few resolved items to cluster), 'deferred' (no provider configured, or a transient failure — retry later), or 'unusable'. An unknown project is a not-found error, not a result. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID whose resolved backlog is scanned. |
max_candidates | integer | no | Ceiling on proposals to file from this pass, 1-20 (default 5). |
lookback_items | integer | no | How many recently-resolved items to feed the model, 10-200 (default 60). |
client_name | string | null | no | Optional client product name, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
Rules
Build rules with a five-dimension trigger surface; query_applicable_rules returns only the ones that match the change at hand.
query_applicable_rules
Retrieve the build rules that apply to a change you're about to make. Pass the file_paths + operations + languages + project_attributes you're touching (and optional task_context); returns only the project's active rules whose triggers match, up to a limit, plus candidate_count and suppressed_count. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | yes | The project ULID whose active rules are considered (required). |
file_paths | array | null | no | The file paths the change touches (matched against rules' file-pattern globs). |
operations | array | null | no | The change operations (matched against rules' operation triggers). |
languages | array | null | no | The change languages (matched against rules' language triggers). |
project_attributes | array | null | no | The change's project attributes (matched against rules' project-attribute triggers). |
task_context | string | null | no | A free-text description of the change (rules' code patterns are sought within it). |
limit | integer | no | The maximum number of matching rules to return, 1-100. Defaults to 50. |
create_rule
Author a new build rule by hand (no source lesson). Lands 'active'. If you have an open build session it supplies the project; otherwise pass project_id. The trigger arrays are what query_applicable_rules matches a change against. A slug already used in the project is a conflict. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_slug | string | yes | The stable natural key, unique per project, 1-100 chars (trimmed). |
title | string | yes | One-line rule summary, 1-300 chars. |
body_markdown | string | yes | The actionable rule body markdown, 1-32000 chars. |
project_id | string | null | no | The project ULID; optional when an open build session supplies it. |
file_patterns | array | null | no | Glob file-pattern triggers, e.g. src/**/*.cs. |
operations | array | null | no | Operation triggers, e.g. create, modify. |
code_patterns | array | null | no | Code-substring triggers sought in a task description. |
languages | array | null | no | Language triggers, e.g. csharp. |
project_attributes | array | null | no | Project-attribute triggers, e.g. ef-migration. |
client_name | string | null | no | Optional client product name, e.g. claude-code, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
derive_rule
Derive a rule from an ENFORCED build lesson: the rule body becomes the lesson's prevention mechanism and its trigger surface seeds project_attributes from the lesson's applicable_to tags; the rule links back to the lesson. The lesson must be enforced (else conflict). Slug/title default to the lesson's. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
source_lesson_id | string | yes | The enforced lesson ULID to promote. |
rule_slug | string | null | no | The rule slug; optional — defaults to the lesson's slug. |
title | string | null | no | The rule title; optional — defaults to the lesson's title. |
client_name | string | null | no | Optional client product name, <=100 chars. |
client_version | string | null | no | Optional client version, <=50 chars. |
get_rule
Get a full build rule. Identify it either by rule_id, or by project_id + rule_slug (the per-project natural key). Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_id | string | null | no | The rule ULID. Provide this, or project_id + rule_slug. |
project_id | string | null | no | The project ULID (with rule_slug) to resolve by natural key. |
rule_slug | string | null | no | The per-project slug (with project_id) to resolve by natural key. |
list_rules
List build rules in lifecycle order (active first, newest first), offset-paged with a total. Optionally filter by project, one or more statuses (active/archived), one or more enrichment statuses (pending/enriched/failed), or a substring over title + body. Returns a lightweight summary shape. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | null | no | Restrict to one project ULID. |
statuses | array | null | no | Restrict to these statuses (active/archived). |
enrichment_statuses | array | null | no | Restrict to these enrichment statuses (pending/enriched/failed). |
q | string | null | no | A case-insensitive substring over title + body, 1-500 chars. |
offset | integer | no | The 0-based page offset. Defaults to 0. |
limit | integer | no | Page size, 1-100. Defaults to 50. |
update_rule
Edit a rule's title, body, and/or trigger surface (content only — use archive_rule for status). Omit a field to keep it. To change the triggers you MUST set replace_triggers=true — then the WHOLE surface is replaced with the trigger arrays you pass (all empty = cleared); the trigger arrays are IGNORED unless replace_triggers is true. An update that changes nothing is an idempotent no-op. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_id | string | yes | The rule ULID to edit. |
title | string | null | no | A replacement title (1-300 chars), or omit to keep. |
body_markdown | string | null | no | A replacement body (1-32000 chars), or omit to keep. |
file_patterns | array | null | no | Replacement file-pattern triggers (part of replacing the whole surface). |
operations | array | null | no | Replacement operation triggers. |
code_patterns | array | null | no | Replacement code-pattern triggers. |
languages | array | null | no | Replacement language triggers. |
project_attributes | array | null | no | Replacement project-attribute triggers. |
replace_triggers | boolean | no | Set true to replace the trigger surface (with the arrays above, empty to clear); false/omit keeps it. |
archive_rule
Archive a rule (active -> archived; forward-only, archived is absorbing). An already-archived rule is an idempotent no-op. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_id | string | yes | The rule ULID to archive. |
delete_rule
Hard-delete a mis-filed rule (archive is the retire-without-delete path). Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_id | string | yes | The rule ULID to delete. |
enrich_rule
Run the LLM trigger-extraction for one rule whose enrichment_status is 'pending', writing the five-dimension trigger surface query_applicable_rules matches against. Returns a typed result: 'enriched', 'failed' (the model's answer was unusable — the rule is parked for a human reenrich), 'deferred' (no provider, or a transient failure — the rule stays pending and is retried), or 'skipped' (not a pending rule, or a concurrent writer changed it). Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_id | string | yes | The rule ULID to enrich. |
reenrich_rule
Requeue a rule whose trigger-extraction FAILED back to pending so the background extractor retries. Only valid on a failed-enrichment rule (else conflict). Requeuing does not itself call the model — run enrich_rule, or wait for the sweep, to retry the extraction. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
rule_id | string | yes | The rule ULID to requeue. |
Usage
Token counts and turns per build session, rolled up per project, day, week or month.
record_build_session_usage
Record (idempotent upsert) the token usage one AI-coder transcript contributed to a build session, keyed on (build_session_id, client_session_id) — a re-fired report overwrites the counts, unless it is older than the stored one by window_end, in which case it is ignored and the response carries applied:false. Store the four input classes separately (input, cache_write_5m, cache_write_1h, cache_read) because cache-read dominates. Allowed on an active OR closed session. Requires the write_session_state scope.
| Argument | Type | Required | Description |
|---|---|---|---|
build_session_id | string | yes | The build-session ULID the usage is recorded against (must exist in your workspace). |
client_session_id | string | null | no | The reporting client's transcript id — the second half of the idempotency key, 1-128 chars. Required (or supply the deprecated claude_session_id alias instead). |
claude_session_id | string | null | no | DEPRECATED alias for client_session_id, kept so existing reporters keep working. Supplying both with different values is refused. |
input_tokens | integer | no | Uncached input tokens (non-negative). Defaults to 0. |
cache_write_5m_tokens | integer | no | 5-minute cache-write tokens (non-negative). Defaults to 0. |
cache_write_1h_tokens | integer | no | 1-hour cache-write tokens (non-negative). Defaults to 0. |
cache_read_tokens | integer | no | Cache-read tokens (non-negative) — the dominant class for agentic coding. Defaults to 0. |
output_tokens | integer | no | Output tokens (non-negative). Defaults to 0. |
turns | integer | no | Assistant turns in the reported window (non-negative). Defaults to 0. |
sidechain_turns | integer | no | Inline-subagent turns (non-negative, <= turns). Defaults to 0. |
models | array | null | no | The model id(s) seen across the transcript (each <=128 chars). |
agent | string | null | no | The reporting agent product, e.g. claude-code, 1-64 chars. Defaults to claude-code. |
reporter_version | string | null | no | The reporter tool version, when reported (<=64 chars). |
window_start | string | null | no | The start of the reported window, ISO-8601, when known. |
window_end | string | null | no | The end of the reported window, ISO-8601, when known (>= window_start). Doubles as the report-ordering token: a report older than the stored row's is ignored rather than applied. Omit it to write unconditionally. |
get_usage
Roll up token + turn usage across your workspace's build sessions, grouped by group_by (total, project, day, week, or month), within an optional project filter and an optional inclusive-from / exclusive-to created_at window (ISO-8601). The per-project rollup follows each session's CURRENT project. Open to any authenticated caller.
| Argument | Type | Required | Description |
|---|---|---|---|
group_by | string | no | The grouping axis: total, project, day, week, or month. Defaults to total. |
project_id | string | null | no | Restrict to one project ULID. |
from | string | null | no | Inclusive lower bound on created_at, ISO-8601. |
to | string | null | no | Exclusive upper bound on created_at, ISO-8601. |