Several agents, one shared record of the work

Two agents polling the same queue will do the same job twice. An agent with no memory of the last run will start again from nothing. This is the coordination layer — an append-only bus, a dependency graph with leases, and a background runtime that is not allowed to act on its own conclusions.

  • Append-only message bus
  • Leases from 30s to 24h
  • 13 operations
  • Writes candidates, never actions

The bus is a log, not a queue

POST /v1/agent-workflows/messages appends. Nothing consumes a message and makes it disappear, so what one agent told another six weeks ago is still there to read. A message names its sender, optionally its recipient — omit to_agent and it is a broadcast — a message type of your choosing, a human-readable body and a structured payload.

Messages are written with authority raw, truth_state: fact and confidence 1.0, because the fact being recorded is that the message was sent, not that its contents are true. Both agent names go into the record's tags, so filtering by agent returns everything that agent said and everything said to it.

The reason this is a log is that debugging multi-agent work is archaeology. When a run goes wrong at three in the morning, the question is always what the agents told each other and in what order — and a queue that deletes on read cannot answer it.

Ready means ready

A task graph is only useful if asking it what to do next gives an answer you can act on without checking anything else.

DONE import-contract DONE scan-repo READY doctor-pass PROCESSING write-adr BLOCKED publish-digest lease → agent-2 · expires 14:32:07Z GET /tasks/ready → [ doctor-pass ]
publish-digest is blocked because one of its prerequisites is still running. write-adr is excluded because agent-2 holds a live lease on it. Only doctor-pass has every dependency satisfied and no claim against it, so it is the only task returned.

Tasks are upserted by key, so a supervisor that re-declares its plan on every run converges on the same graph instead of duplicating it. depends_on holds task keys, not record ids, which means you can declare a dependency on work that does not exist yet and have it resolve when that task appears.

GET /tasks/ready applies two filters at once. Every key in depends_on must belong to a task that is done or completed, and no unexpired lease may be held. A task blocked on either count is simply absent from the response — there is no status field for the caller to interpret, and no way to accidentally start blocked work by ignoring one.

GET /graph returns the whole thing as nodes and precedes edges. Dependencies declared by key are resolved to record ids where the task exists, so the graph is drawable as it stands.

Leases, and why they expire

POST /tasks/{task_id}/lease takes an agent name and a duration — 900 seconds by default, 30 at the shortest, 86,400 at the longest. The task moves to processing and records who holds it and when the claim runs out.

Because the claim carries an expiry rather than a flag, a crashed agent unblocks its own work. Nothing has to notice the crash, no reaper process has to run, and no operator has to clear a stuck row: the lease simply stops being live and the task is ready again the next time anybody asks. Long jobs re-lease to extend.

Tasks also carry a checkpoint object, merged rather than replaced on each write, so an agent that picks up work after an expired lease can resume from where the last one got to instead of starting over.

The Default Mode runtime, and its hard boundary

A background process that reflects on your work and updates your memory is a good idea right up until it is wrong about something. The boundary here is structural rather than a matter of prompting.

It writes candidates

Every output of this runtime is a default_mode_candidate — a record in a holding area, not in memory. Nothing it produces is retrievable as an ordinary memory until a separate, explicit promote call moves it.

It takes no external action

There is no branch in this runtime that sends an email, calls a webhook, writes a file or hits a third-party API. It reads records and writes records. That is the whole surface, and it is the reason it is safe to leave running.

The critic sits in front of promotion

Anything mentioning risk, privacy, legal, medical, financial or failure — and anything with no evidence references at all — is forced to needs_review and truth_state: hypothesis, regardless of how confident the candidate was.

  1. Reflect over a session

    Send a transcript, or a session id to pull one. Each line over twenty characters is scored for salience — action-word density, a risk signal, novelty, repetition, whether the user said “remember” or “important”, and supplied goal-relevance and evidence-strength weights. Anything under 0.35 is dropped on the floor.

  2. Classify what survived

    A line ending in a question mark becomes an open_question. Decided, choose or selected make it a decision; todo, next or blocker make it an open_loop; prefer, likes or style make it a preference; a risk word makes it a risk. Everything else is a reflection.

  3. Run the critic

    Risky or unevidenced candidates drop to needs_review as hypotheses. Synthetic candidates are approved but keep truth_state simulation, so approval never launders them into facts. The decision, the risk flag and whether evidence was present are all written onto the record.

  4. Promote, deliberately

    POST /promote/{candidate_id} creates the real record. It carries authority derived and evidence_refs that begin with the candidate it came from, and the candidate is marked promoted with a pointer to what it became. A candidate that is neither approved nor needs_review is refused with a 400.

Consolidation follows the same path: POST /consolidate summarises the distribution of recent records into a single candidate carrying every record it read as an evidence reference — needs_review, hypothesis, never promoted on its own.

Three endpoints that are checklists, not thinking

We would rather tell you this than have you discover it. /simulate-future, /stakeholder-simulation and /incubate return fixed templates with your input substituted in. They are the same every time.

/simulate-future returns five named paths — best case, most likely, failure path, hidden risk and low effort — each a fixed list of considerations. /stakeholder-simulation returns a fixed set of concerns and message adjustments under the label speculative_general_pattern. /incubate crosses your problem with six fixed reframing seeds. No model is called, and none of these three is generation, imagination or reasoning.

What is genuinely working is the governance around them, and that part is not cosmetic. The two simulation endpoints stamp truth_state: simulation and synthetic: true, which is what keeps their output out of ordinary Librarian answers, where include_synthetic defaults to off. Incubation lands as a needs_review hypothesis. All three are candidates until somebody promotes them.

A fixed prompt list is a legitimately useful thing — it is what a good checklist is — and it costs nothing, needs no key and cannot hallucinate. It is simply not what the word “simulation” usually implies, so the page says which it is.

Thirteen operations

Twelve paths across /v1/agent-workflows and /v1/default-mode.

EndpointWhat it does
POST /v1/agent-workflows/messages Post to the bus: from_agent, an optional to_agent — leave it out to broadcast — a message type, a body, a structured payload and dependency ids.
GET /v1/agent-workflows/messages Read the bus, filtered by project or by agent. An agent filter matches messages at either end of the exchange.
POST /v1/agent-workflows/tasks Create or update a task by key: title, depends_on, status and a checkpoint object for resumable work.
GET /v1/agent-workflows/tasks/ready The only tasks that can be started right now: every dependency done, no live lease held.
POST /v1/agent-workflows/tasks/{task_id}/lease Claim a task for an agent for a bounded number of seconds — 30 minimum, 86,400 maximum, 900 by default.
GET /v1/agent-workflows/graph The whole task graph as nodes and precedes edges, ready to draw.
POST /v1/default-mode/reflect-session Score a transcript line by line for salience, write the survivors as candidates, then run the critic over them.
POST /v1/default-mode/consolidate Summarise the distribution of recent records into one candidate carrying every record it looked at as evidence.
POST /v1/default-mode/simulate-future Stamp a goal against a fixed five-path checklist and store it as a simulation.
POST /v1/default-mode/stakeholder-simulation Store a fixed concerns-and-adjustments checklist for a named stakeholder, labelled speculative.
POST /v1/default-mode/incubate Combine a problem with six fixed reframing seeds and store the result as a hypothesis for review.
POST /v1/default-mode/critic Review candidates: anything risky or unevidenced is forced to needs_review.
POST /v1/default-mode/promote/{candidate_id} Move one reviewed candidate into real memory, carrying its provenance with it.

Give your agents a memory you can audit

Run the whole system on your own hardware under the MIT licence, or ask us about hosted access. Both start from the same place.