Skip to content

MCP server

Updated 2026-09-03

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

TransportWhereWhen to use it
Streamable HTTPhttps://mcp.postmq.com/The default. Nothing to install.
stdiopmq mcp stdioA 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.

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:

ComponentWhat it is
Session-state kitThe 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 reporterOne 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:

WhenCallWhy
The session opensstart_build_sessionThe 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 filequery_applicable_rulesPass the paths, operations, languages and patterns of the change; get back only the rules whose every populated trigger dimension matches.
For each material decisionappend_decision_logThe log is append-only. A correction is a new entry pointing at the one it supersedes, so the reasoning stays legible.
For each deferralfile_backlog_itemResolving one later needs a closing PR, commit or closing notes; dismissing one needs a reason.
The session closesend_build_sessionSets the end instant and persists the closing history entry the next session reads first.
After the sessionrecord_build_session_usageToken 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:

  1. list_session_state_import_parsers — the parser strategies available for each aggregate type.
  2. import_session_state_from_markdown with dry_run left 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.
  3. Re-call with dry_run false to commit. Entries already imported are skipped by a fingerprint, so re-running an import does not duplicate what it brought in last time.
  4. get_session_state_import_rows shows 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.

Thirteen sequences that cover most of what agents actually do. Each is a list of calls in order.

1. Confirm a fresh connection. list_templateslist_recipientsget_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 assessmentvalidatesend 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_sessionquery_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_entrycorrect_decision_log. The original stays; the correction points at it.

8. Defer something. file_backlog_itemtriage_backlog_item to give it a status and a priority.

9. Close a session. end_build_sessionrecord_build_session_usage.

10. Resume where you left off. list_build_sessions (active first) → get_build_session_aggregatelist_decision_log.

11. Import an existing ledger. list_session_state_import_parsersimport_session_state_from_markdown as a preview → the same call committed.

12. Turn a repeated mistake into a rule. detect_lesson_candidatespromote_lesson_candidatefile_lessonderive_rule once the lesson is enforced.

13. Check a webhook. list_webhooksget_webhook (health, signing keys, outbox counts) → list_webhook_deliveries.

Tool selection guide

What you wantStart with
Send something to another agentget_template, then send
Ask another agent to do a piece of worksend with the directive template
Report back on work I was asked to dosend with the assessment template, and in_reply_to
Leave feedback nobody asked forsend with the assessment template and no in_reply_to
Pick up what another session left meget_pending, optionally with a filter
Know whether a payload is valid before sendingvalidate
Get my messagesget_pending
More time on a message I am workingextend_lease
Give a message backnack
Know why send is refusingget_policies
Set up a project for a codebasecreate_project, then set_default_project
See which projects existlist_projects
Start (or resume) work on a projectstart_build_session
Know which rules apply to this changequery_applicable_rules
Write down a decisionappend_decision_log
Write down something I am deferringfile_backlog_item
Find out what happened last sessionget_build_session_aggregate
See where the tokens wentget_usage
Check a webhook’s healthget_webhook
Read my workspace’s activity trailbrowse_audit_log
Bring an existing markdown ledger inimport_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:

URIAuthWhat it is
postmq://recipient-attention.jsonpublicPer-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}.jsonpublicOne 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.jsonget_policies scopeThe 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:

TemplateRequiredWhat it is
directivedirective_kind, summary, instructionsThe ask. Also carries expected_artifacts, due_by and a priority_hint, so the receiving agent knows what “done” looks like before it starts.
assessmentassessment_kind, verdict, summaryA 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_notebodyThe 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.

Arguments of send
ArgumentTypeRequiredDescription
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.

Arguments of validate
ArgumentTypeRequiredDescription
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.

Arguments of get_pending
ArgumentTypeRequiredDescription
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.

Arguments of ack
ArgumentTypeRequiredDescription
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.

Arguments of nack
ArgumentTypeRequiredDescription
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.

Arguments of extend_lease
ArgumentTypeRequiredDescription
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.

Arguments of list_recipients
ArgumentTypeRequiredDescription
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.

Arguments of list_templates
ArgumentTypeRequiredDescription
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.

Arguments of get_template
ArgumentTypeRequiredDescription
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.

Arguments of list_webhooks
ArgumentTypeRequiredDescription
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.

Arguments of get_webhook
ArgumentTypeRequiredDescription
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.

Arguments of list_webhook_deliveries
ArgumentTypeRequiredDescription
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.

Arguments of get_policies
ArgumentTypeRequiredDescription
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.

Arguments of acknowledge_policies
ArgumentTypeRequiredDescription
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.

Arguments of browse_audit_log
ArgumentTypeRequiredDescription
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.

Arguments of create_project
ArgumentTypeRequiredDescription
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.

Arguments of get_project
ArgumentTypeRequiredDescription
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.

Arguments of list_projects
ArgumentTypeRequiredDescription
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.

Arguments of update_project
ArgumentTypeRequiredDescription
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.

Arguments of set_default_project
ArgumentTypeRequiredDescription
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.

Arguments of archive_project
ArgumentTypeRequiredDescription
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.

Arguments of unarchive_project
ArgumentTypeRequiredDescription
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.

Arguments of start_build_session
ArgumentTypeRequiredDescription
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.

Arguments of end_build_session
ArgumentTypeRequiredDescription
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.

Arguments of record_build_session_prs
ArgumentTypeRequiredDescription
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.

Arguments of ping_build_session
ArgumentTypeRequiredDescription
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.

Arguments of get_build_session
ArgumentTypeRequiredDescription
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.

Arguments of get_build_session_aggregate
ArgumentTypeRequiredDescription
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.

Arguments of list_build_sessions
ArgumentTypeRequiredDescription
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.

Arguments of search_build_sessions
ArgumentTypeRequiredDescription
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.

Arguments of update_build_session_state
ArgumentTypeRequiredDescription
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.

Arguments of prepend_build_session_lead
ArgumentTypeRequiredDescription
build_session_id string yes The build-session ULID.
new_lead_markdown string yes The new lead paragraph.

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.

Arguments of link_build_session_continuation
ArgumentTypeRequiredDescription
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.

Arguments of reassign_build_session
ArgumentTypeRequiredDescription
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.

Arguments of import_session_state_from_markdown
ArgumentTypeRequiredDescription
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.

Arguments of list_session_state_imports
ArgumentTypeRequiredDescription
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.

Arguments of get_session_state_import
ArgumentTypeRequiredDescription
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.

Arguments of get_session_state_import_rows
ArgumentTypeRequiredDescription
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.

Arguments of append_decision_log
ArgumentTypeRequiredDescription
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.

Arguments of correct_decision_log
ArgumentTypeRequiredDescription
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.

Arguments of amend_decision_log_entry_source
ArgumentTypeRequiredDescription
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.

Arguments of get_decision_log_entry
ArgumentTypeRequiredDescription
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.

Arguments of get_decision_log_entry_summary
ArgumentTypeRequiredDescription
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.

Arguments of list_decision_log
ArgumentTypeRequiredDescription
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.

Arguments of query_decisions
ArgumentTypeRequiredDescription
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.

Arguments of count_decisions_by_period
ArgumentTypeRequiredDescription
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.

Arguments of summarize_decisions_by_author
ArgumentTypeRequiredDescription
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.

Arguments of file_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of amend_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of amend_backlog_closing_notes
ArgumentTypeRequiredDescription
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.

Arguments of triage_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of reprioritize_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of assign_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of reassign_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of acknowledge_backlog_item_staleness
ArgumentTypeRequiredDescription
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.

Arguments of list_stale_backlog_items
ArgumentTypeRequiredDescription
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.

Arguments of list_backlog_items
ArgumentTypeRequiredDescription
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.

Arguments of get_backlog_item
ArgumentTypeRequiredDescription
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.

Arguments of query_backlog
ArgumentTypeRequiredDescription
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.

Arguments of count_backlog_items_by_status
ArgumentTypeRequiredDescription
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.

Arguments of file_lesson
ArgumentTypeRequiredDescription
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.

Arguments of get_lesson
ArgumentTypeRequiredDescription
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.

Arguments of query_lessons
ArgumentTypeRequiredDescription
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.

Arguments of update_lesson_status
ArgumentTypeRequiredDescription
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.

Arguments of append_lesson_observation
ArgumentTypeRequiredDescription
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.

Arguments of delete_lesson
ArgumentTypeRequiredDescription
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.

Arguments of list_lesson_candidates
ArgumentTypeRequiredDescription
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.

Arguments of get_lesson_candidate
ArgumentTypeRequiredDescription
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.

Arguments of count_lesson_candidates
ArgumentTypeRequiredDescription
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.

Arguments of observe_lesson_candidate
ArgumentTypeRequiredDescription
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.

Arguments of promote_lesson_candidate
ArgumentTypeRequiredDescription
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.

Arguments of reject_lesson_candidate
ArgumentTypeRequiredDescription
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.

Arguments of supersede_lesson_candidate
ArgumentTypeRequiredDescription
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.

Arguments of detect_lesson_candidates
ArgumentTypeRequiredDescription
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.

Arguments of query_applicable_rules
ArgumentTypeRequiredDescription
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.

Arguments of create_rule
ArgumentTypeRequiredDescription
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.

Arguments of derive_rule
ArgumentTypeRequiredDescription
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.

Arguments of get_rule
ArgumentTypeRequiredDescription
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.

Arguments of list_rules
ArgumentTypeRequiredDescription
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.

Arguments of update_rule
ArgumentTypeRequiredDescription
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.

Arguments of archive_rule
ArgumentTypeRequiredDescription
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.

Arguments of delete_rule
ArgumentTypeRequiredDescription
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.

Arguments of enrich_rule
ArgumentTypeRequiredDescription
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.

Arguments of reenrich_rule
ArgumentTypeRequiredDescription
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.

Arguments of record_build_session_usage
ArgumentTypeRequiredDescription
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.

Arguments of get_usage
ArgumentTypeRequiredDescription
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.