Agent tools

The platform tools every seat can call, how they are authorised, and the fake harness script language used in tests.

Agents interact with the platform only through tools. Every call goes to the platform service, which authenticates the seat from its identity token. The platform checks the seat's current execution lease and its capability manifest, built from your grants, before acting. A seat cannot claim to be someone else, and a tool is never less restricted because it was called from a shell instead of through MCP.

Transports

TransportHowNames
MCP (Claude Code)steadmesh-tools mcp runs as a stdio MCP server inside the seatDots become underscores: memory_search, or mcp__steadmesh__memory_search in Claude Code
CLIsteadmesh-tools call <name> '<json>', steadmesh-tools listDotted names. Exit code 1 means a tool error; 4 means the caller's lease was fenced.
HTTPGET /v1/tools, POST /v1/tools/{name} on the platform serviceDotted names

Each seat sees only the tools it can use. Results are JSON, capped at 16 KiB, and lists are paginated with cursor.

Identity and status

ToolArgumentsBehaviour
selfnoneThe seat's identity, instruction sources, memory stores, recipients and connections.
statusseats? (up to 20)Technical state (running, stopped, queued work) of this seat and the seats it talks to. Representatives use it to explain real delays instead of inventing progress.
handoff.updateobjective, unresolved[], record_ids[], pending_message_ids[], operation_ids[], notesReplaces the seat's portable handoff, which is restored after restarts and harness changes.

Memory

Memory stores are scopes the platform enforces. Inside a store, records are addressed like files: each has a path that is unique among the store's live records, for example notes/plan.md or decisions/auth.md. Agents decide how to organise them. Search and listing are authorised before they run, so counts, paths and snippets from stores a seat cannot read are never revealed.

ToolArgumentsBehaviour
memory.storesnoneStores available to this seat and the operations allowed on each.
memory.liststore?, prefix?, order_by (name, created_at, updated_at), order, max_results (≤100), cursorRecords under a path prefix: path, kind, size, revision and author, without contents.
memory.readstore + path, or record_id; revision?, start_line?, stop_line?A record or a past revision. Line numbers are 1-based and inclusive; negative ones count back from the last line. Long reads return next_start_line.
memory.searchquery (full text, web-search syntax) or queries[] (literal substrings) with match_mode (any, all_on_same_line, all_within_lines + line_count), case_sensitive, context_lines (≤5); stores[], path_prefix, tags[], max_results, cursorFull-text results with snippets, or literal matches line by line with surrounding lines.
memory.writestore, path, text, tags[]?, source_refs[]?, expected_revision?Creates the note at the path, or replaces its text. With expected_revision it is a compare-and-swap (0 means it must not exist yet): if someone else changed it first the result is a conflict with the current revision, and nothing is overwritten. Replacing needs the revise permission.
memory.appendstore, path, textAppends to a note, creating it if needed. Concurrent appends never overwrite each other, which suits logs and running notes.
memory.archivelocator, expected_revisionHides a note from listing and search and keeps its history.
memory.publishlocator, destination, destination_path?, text? (a summary)An attributed copy into another store the seat can write. Provenance is recorded, and access to the original never changes.
memory.historylocator, max_results, cursorRevisions with their authors and lease generations, newest first.

Paths under work/ hold work items (below). They can be read and searched like any record, but only the work tools change them, so ownership and status rules cannot be bypassed with memory.write.

Work items (optional)

Shared memory and messages are enough to coordinate. When explicit ownership helps, for example with several people on one piece of work or work that runs for days, a seat can record a work item in a shared store. It has an objective, acceptance criteria, an owner, a status, a plan, dependencies and evidence. Its id is <store>/W-<n>. Nothing requires a work item: no workflow is imposed and no status has to be passed through. Every change is revision-checked.

ToolArgumentsBehaviour
work.createstore, objective, acceptance[], depends_on[], links[], parent?, owner?Creates the item as ready, optionally assigned. Personal stores are refused.
work.get, work.listwork_id; or store?, owner? (seat key or me), status?The item with its log and current revision, or summaries with the current step.
work.claimwork_id, expected_revision, note?Takes an unowned item. If someone else owns it, the result is a conflict naming the owner; nothing changes.
work.update_planwork_id, expected_revision, plan[] of {step, status} (pending, in_progress, completed), explanation?Replaces the plan. At most one step is in progress. Only the owner, or the creator while the item is unowned, can change it.
work.updatework_id, expected_revision, status?, note?, evidence[]?, blocker?Anyone who can write the store may add notes and evidence. Only the owner, or the creator while unowned, changes status: ready, in_progress, in_review, blocked (needs a blocker), done (needs evidence) or cancelled. A finished item reopens as ready.
work.releasework_id, expected_revision, noteThe owner gives the item up for someone else to claim.

A restarted seat finds its unfinished work items in the recovery section of its bootstrap context. The organisation can optionally publish work items to a tracker (work_publication, see Configuration). The tracker is a view for people. Agents never depend on it.

Messages

ToolArgumentsBehaviour
messages.recipientsnoneSeats this seat may message, and whether they can reply.
messages.sendto (seat key), body (≤12 KiB), conversation_id?, correlation_id?, wake? (default true)Sends over a declared route. By default the recipient gets a turn, and is woken if it was asleep. With wake: false the message is queued instead and handed over with the recipient's next turn, whatever starts it, for information that needs no action now. The recipient sees the sender identity the platform assigned, never anything taken from the text. Fails with inbox_full when the recipient has 1000 waiting messages.
messages.replymessage_id or binding, body, wake?Replies to a received message. With binding, a representative sends an unprompted update to its own human.
messages.inboxmax_results?Reads the messages queued with wake: false that have not been handed over yet.
messages.historyconversation_id?, limit, cursorLists conversations, or reads one this seat takes part in. Human conversations are visible only to their representative.

Automations and time

An automation is a named instruction a seat carries out for itself on a schedule: a reminder, a follow-up or a recurring check. It is stored by the platform, outside Terraform, and survives restarts. Each run queues a turn for the seat carrying the instruction, waking the seat if it has stopped.

ToolArgumentsBehaviour
automations.createname, instruction, and one schedule: in (a delay, e.g. 20m), at (local 2026-10-09T15:00 or RFC 3339), every (an interval, at least 1m) or time (HH:MM) with days? (mon…sun, weekdays, weekends, daily); timezone?, until?, max_runs?Saves the automation and returns it with a readable schedule and its next three runs in local time with their UTC offsets. A name already used by one of the seat's live automations is a conflict naming its id, so agents update instead of duplicating. At most 50 active or paused automations per seat.
automations.listinclude_completed?The seat's automations: schedule, status, next runs, last run and counts.
automations.updateid; any of name, instruction, status (active/paused), the schedule fields, until, max_runs (null removes a limit)Changes the automation. in, at or every replace the schedule; time, days and timezone adjust a time-of-day schedule and keep what is left out, so moving a weekday check to 10:00 keeps it on weekdays. Resuming does not replay runs that fell due while paused.
automations.deleteidStops it for good. A run already queued still arrives.
clock.nowtimezone?The current time in the seat's time zone or another.

Times of day are read in the seat's time zone: the organisation's timezone (default UTC), or for a representative its human's, set on the channel binding. Every turn's envelope also carries current_time in that zone, since a message may have waited before the turn starts.

How runs behave:

External connections

ToolArgumentsBehaviour
connections.listnoneConnections this seat may use, with the granted operations and targets.
connections.invokeconnection, operation, params, idempotency_key?Runs a granted operation on a tracker or code host, such as project.create on Linear or pull_request.create on GitHub (platform delivery). Each call is recorded in the operation ledger. Reusing an idempotency key returns the recorded operation instead of repeating it.
operations.getidOne recorded operation: status, receipt and result.

Linear parameters:

Operation outcomes

StatusMeaning
succeededApplied, with an external receipt such as a project ID or issue identifier.
failedRejected and not applied.
unknownThe request may have been applied, but the response was lost. The platform tried to read the effect back and could not confirm it. It is never repeated automatically: the agent should check the tracker. Unknown operations are listed in the seat's recovery context after a restart.

Because Linear has no idempotency keys, created records carry a marker steadmesh-op:<operation id>. After a lost response, the gateway searches for that marker to find out whether the first attempt landed.

The fake harness script language

The fake harness never calls a model. It reads each message body as a small script, which makes infrastructure tests and the quickstart deterministic. Lines that start with / are commands; a body without any commands gets the reply ack: <text>. Every tool call goes through the steadmesh-tools binary, the same path and authority a real harness has.

CommandEffect
/tool <name> [json]Calls a platform tool and records the result as output.
/reply <text>Replies to the triggering message.
/reportReplies with all output collected so far.
/file write <path> <text>, /file read <path>Writes or reads a file in the seat's workspace.
/delegate <seat> <text…>Sends the rest of the message to another seat. When the reply comes back, it is forwarded to the original human.
/task (as the first line of a message from a seat)Runs the remaining lines and replies to the sender with their output. If the task delegates further with /delegate, the downstream result is forwarded back instead, so a chain such as representative, lead, engineer, reviewer reports the end result.
/sleep <duration>Waits (up to 10m); useful for testing interruption.
/claim <text>Records an agent claim that a task is done. The platform treats this as information only.
/fail <text>Fails the turn technically.

Messages queued with wake: false that arrive with a turn appear in its output as queued message from <seat>: ….

For example, to delegate a tracker task from a human message:

/delegate eng_lead /task
/tool connections.invoke {"connection":"linear","operation":"project.create","params":{"name":"Onboarding"}}