Skip to content

Coordination model

The hub composes a handful of independent mechanisms into one coordination plane.

1. Plan

Any agent declares work on the shared blackboard. A declared task has an id, a title, a description, and optional dependencies. The hub refuses dependency cycles, so the set of ready tasks (open, with every dependency finished) is always well-defined.

The blackboard keeps recent progress bounded by total note count, author, and task id. These caps are live hub settings and are also applied when replaying a durable event log, so a restart does not resurrect an unbounded in-memory board view.

The planned differential-privacy blackboard design defines future redacted and noisy projections for multi-organisation board views. It is not implemented yet; the current local board remains exact for the operator.

A declared task is the plan; a claim is the lease on doing it. The two share a task id but stay independent, so the simple claim flow keeps working with no plan entry at all.

2. Claim

An agent leases a task by id. The claim may declare a file scope — a worktree and a set of paths. The hub refuses a claim whose file scope overlaps another agent's live claim; claims with different canonical worktree identities do not contend at the hub.

Git-aware clients retain those fields as human-readable displays and add a versioned path_identity derived locally from the canonical worktree, Git-index spelling, resolved filesystem path, actual case policy, Unicode NFC, and an existing object's device/object key. The hub validates and compares the supplied identity without accessing Git or the filesystem. This makes symlink, hard-link, junction, case, Unicode-equivalent, and Windows short-name aliases contend while preserving distinct case-sensitive paths on filesystems such as Linux/ext4. Malformed or display-misaligned identities are refused before state mutation. Legacy claims remain readable; an identity-aware peer applies its known case policy to a legacy display, while two legacy peers keep literal comparison.

Every lease:

  • expires, so a crashed agent never holds a claim forever;
  • carries an epoch, so a paused or superseded agent cannot act on a dead claim;
  • carries a version for optimistic concurrency, so a stale update is refused.

3. Work

The owner moves the task through a typed lifecycle (claimed → working → input_required → done/failed); the hub rejects an illegal transition. The owner can save a checkpoint — an opaque resume token that survives lease expiry.

When the owner manually releases a claim as the closeout record, the release can carry a receipt with evidence, artifacts, changed files, generated artifacts, approvals, known failures, confidence, and evidence freshness. The hub echoes the receipt and records it as a board assessment note when evidence is present. The receipt's epistemic_status is advisory metadata derived from those fields, not proof that the release is safe to merge: fresh positive caller evidence is unverified, needs_freshness means freshness was not supplied, stale means the evidence is older than one hour, degraded means known failures were declared, and unsupported means no positive evidence was attached. The observed verify-release path may apply supported after executing the declared checks itself. The offline signature verifier reports a successful historical commitment check separately as VALID_LEGACY; it never promotes advisory epistemic_status to a verification verdict or silently treats the historical receipt as an AEF receipt.

The separate core.aef_verification boundary verifies native AEF v0.1 receipt identity, Ed25519 signatures, key policy, freshness, and signed-tree-head inclusion under explicit trust and caller-supplied time. Its conformance fixture covers all ten accepted receipt and inclusion vectors; the source vector token INVALID_EXPIRED is recorded as a source correction to the normative EXPIRED verdict. AefReceiptIndex remains the explicit ephemeral batch boundary; AefDurableReceiptIndex persists accepted (log_id, seq, receipt_id) identities with a FULL-synchronous atomic transaction, so replays and conflicting sequence claims remain detectable across restarts and concurrent verifier processes. Its table may coexist in the hub database but never reads or writes legacy event rows. Native AEF log emission remains separate from the historical event log and its Merkle tree; neither serializer is reinterpreted as the other.

AefReceiptLog is the native-emission boundary for the next migration step. It assigns an independent AEF sequence and prev_receipt chain, signs canonical receipt bytes with the existing hub Ed25519 key type, validates every receipt before a FULL-synchronous append, and can bind the frozen legacy root only in the AEF genesis receipt. A supplied legacy_seq is stored as reconciliation evidence, never as an AEF sequence or Merkle leaf. The native tables may coexist in the hub database without touching legacy event rows.

legacy_event_to_aef is the explicit compatibility mapper for the first runtime evidence families: lease grants and minimized claim denials, digest-only guard denials, sandbox executions, and durable multi-hub partition/heal transitions. It converts the historical float timestamp through the AEF integer-time boundary and carries the legacy sequence only into signed reconciliation evidence. Minimized identifiers remain visibly digest-labelled; the mapper does not reconstruct deleted plaintext. Unsupported kinds remain legacy-only and malformed supported rows fail closed. This mapper still does not make two separate commits atomic; the durable outbox below is the recovery boundary between them.

The durable outbox boundary queues selected legacy event sequences in the same SQLite transaction as their unchanged event rows. drain_aef_outbox consumes those rows in legacy order. If the process stops after native AEF emission but before acknowledgement, restart lookup by the signed/indexed legacy_seq verifies that the existing receipt is the same deterministic projection and settles the cursor without emitting a duplicate. A mismatch, unsupported queued kind, malformed row, or receipt without identity stops the drain fail-closed. The outbox remains opt-in. A hub enables the live route only when the operator supplies --db, a stable --hub-id, and an owner-only Ed25519 --aef-signing-key. Startup reconciles the complete pending backlog before the hub accepts traffic. Live reconciliation runs on a dedicated worker thread with fresh SQLite connections, so it never shares the hub connection across threads; failure leaves the durable cursor pending, logs the condition, and retries at the bounded --aef-drain-interval. Shutdown signals and joins that worker before the journal closes. Omitting the signing-key flag preserves the legacy-only runtime exactly.

Before closeout, python tools/test_ownership_map.py --check can map changed source files to likely owning tests. The map uses AST imports and a conservative test-filename fallback, so it is useful evidence for picking focused tests and receipt changed_file entries without pretending to be an approval system.

For generated artefacts, python tools/generated_dependency_claims.py --claim-args --source <path> maps source paths to generated outputs that can go stale, such as the README capability inventory and docs/_generated/capability_manifest.json. Use those generated paths in the same file-scope claim and receipt. This generated-output dependency map is a coordination aid; freshness still comes from the owning generator's check command.

python tools/semantic_claims.py --selector <kind:value> --claim-args is the semantic claim resolver for local planning. It accepts module, symbol, API, source, test, generated, and migration selectors. Symbol and API selectors emit a canonical synthetic descendant such as src/pkg/worker.py/.synapse-symbol/Worker/run; the other selectors and owning test/generated companions remain whole-file paths. The hub enforces all of them with its existing path ancestry rule, so symbol separation needs no parser or wire change on the server.

For day-to-day claims, synapse git-claim accepts the same selector kinds as first-class flags (--module, --symbol, --api, --source, --test, --generated, and --migration). It resolves them after discovering the local git root, merges the derived paths with explicit --paths, and can write the selector evidence with --semantic-evidence-json.

Function-level inference from actual edits is also client-side. Install synapse-channel[semantic], then use python tools/semantic_diff_claims.py --base main --claim-args or synapse git-claim TASK --diff-base main. The resolver maps zero-context Git hunks on both old and new source sides to the smallest named tree-sitter declaration for Python, JavaScript, TypeScript, Rust, and Go. Any incomplete mapping — including module-level changes, unsupported or invalid syntax, add/delete/rename statuses, or unavailable source content — widens to the whole file. Optional --diff-head, repeatable --diff-path, and --semantic-evidence-json keep the comparison and receipt evidence explicit. No parser is downloaded at runtime; the hub still stores only canonical paths and branch metadata.

The client obtains that Git evidence through a bounded, non-interactive local process: ten-second deadline, eight-MiB stdout and 64-KiB stderr ceilings, isolated terminate/kill teardown, restricted environment, and no pager, external diff, textconv, or repository-configured filesystem monitor. Error text is terminal-safe. A pipe-reader failure or failure before file enumeration denies the operation; unsafe evidence for an already known file widens that file to whole-file scope.

Those symbol paths participate in the complete local enforcement chain. Precise provider edit tools may provisionally use a symbol claim for its source only when the exact worktree/branch has no competing semantic owner; full-file writes and patch tools require a literal file claim. At commit time, git-claim-check --staged compares HEAD with the authoritative index and checks the exact declarations touched. Incomplete evidence widens to the file and parser failure denies. Post-commit/post-merge auto-release repeats the committed semantic projection and releases only the exact proven symbol; it retains ambiguous symbol claims for manual release. Parallel sibling-symbol work should use isolated Git worktrees because the underlying physical file cannot safely host independent pre-edit mutations in one shared checkout.

python tools/import_merge_risk.py --changed <path> --claimed <path> --check is the import graph merge-risk radar for pre-merge and handoff checks. It combines explicit changed paths or --base main --head HEAD branch diffs with claimed paths, package-local Python import edges, CODEOWNERS, and the test ownership map. It is advisory: a non-zero --check result means review the overlap before merge, not that the hub has rejected anything.

synapse event-query ./synapse.db "task <id> timeline" is the temporal event-log query surface for post-hoc reconstruction. It reads the SQLite event store directly and can show a task timeline, task state at a sequence or timestamp, path touches between timestamps, or historical claim conflicts. The query is read-only and does not change hub state. Prototype Datalog-like aliases such as timeline("TASK"). and Cypher-like aliases such as MATCH (task:TASK {id:"TASK"}) RETURN timeline normalize into that same small query model over journal snapshots.

synapse postmortem ./synapse.db TASK-1 turns those same durable events into a replayable postmortem. It reconstructs who claimed the task, release points, assessment evidence that existed in the board progress stream, path-overlap conflicts involving the task, and candidate unanswered messages. The unanswered message section is deliberately conservative: it reports directed chats that mention the task id and have no later matching chat reply in the log; it does not prove intent or off-channel response.

synapse reliability ./synapse.db aggregates evidence-only reliability memory from the event log. It tracks stale claims, declared failed-check evidence, broken handoff candidates, and merge-conflict frequency as audit signals, not scores. The report is suitable for handovers and routing review, but it does not rank agents or assign trust grades.

The agent trust graph connects those audit signals with positive release receipts as traceable graph evidence, queryable with synapse trust-graph (filter by agent, task, or time window; text, JSON, or Graphviz DOT). It stays advisory: it does not rank agents, authorize execution, or replace review.

synapse cross-repo extends the same read-side evidence across a whole checkout tree: dependency manifests and CODEOWNERS files become edges between repositories, and the live claims of the event log join onto the graph so an agent can see, before starting a cross-cutting change, whether anyone is working in a repository its repository depends on — or one that depends on it. Declared version constraints that are provably disjoint — two repositories pinning the same package to ranges no version satisfies — are flagged as version_conflict edges; a constraint the bounded comparison cannot model never claims a conflict. Advisory and declaration-level, like every other analysis surface.

The planned policy engine builds on those same release receipts and event-log projections. Its first mode is advisory: required tests, strict type checking, owner approval, evidence freshness, generated artifact parity, and no-merge-without-receipt rules are evaluated against local evidence without changing hub state or merging code.

synapse ttl-advice ./synapse.db evaluates adaptive lease TTL inputs from the event log. It uses completed-task duration samples and live-claim load to print an advisory default, but it does not mutate hub configuration and explicit manual TTL choices remain authoritative.

4. Hand off and recover

  • Atomic handoff transfers a held task to another online agent in one step, with no release/re-claim window for a third agent to grab it. Scope, status, and checkpoint move with it. The move honours the same file-scope mutual exclusion as a direct claim: it is refused if the moved scope would collide with a live claim held by an agent other than the recipient, so the hub does not grant overlapping ownership through handoff.
  • An LLM-free supervisor watches the plan and re-offers tasks that stall (no progress while in progress, or blocked with every dependency finished). Its in-progress rule keeps the fixed idle threshold as the operator ceiling and can optionally supplement it with completed-task progress cadence from the same board.
  • A task taken over after its lease lapses resumes from its last checkpoint rather than restarting.

5. Route

Workers advertise capability cards describing their skills and the task classes they can take; the hub aggregates them into a manifest. Cards may also include declarative capability contracts with per-task-class input_schema, output_schema, preconditions, and postconditions. A request can be classified into a task class and routed to the matching backend, reserving heavy models for the genuinely hard requests, while the contract metadata remains reviewable discovery evidence rather than executable trust.

For board work, synapse route-task <task-id> and the MCP synapse_route_task tool provide the same read-only recommendation payload. They join the board task with live capability cards and score structured task-class matches, skill tags, card description overlap, and contract evidence locally. An optional event-store path adds observed evidence from positive release-receipt assessment notes, preserving source task ids and durable event sequences so a human or policy layer can audit why the hint exists. The result is a routing hint only: it does not claim the task, mutate the board, reserve capacity, grade an agent, or certify an agent.

For resource selection, synapse resource-bids <task-id> and the MCP synapse_resource_bids tool rank live resource offers from the same directory against a board task. The output keeps resource id, provider, kind, name, capacity, score, and reason codes so humans or policy layers can inspect why an offer ranked. The result is advisory only: it does not reserve capacity, authorize execution, mutate the board, or certify provider trust.

MCP resource templates provide narrower read-only context retrieval for hosts that support them: synapse://task/{task_id} for one board task, synapse://agent/{agent} for one agent's card and resources, and synapse://resource-kind/{kind} for matching resource offers. These templates read the same hub snapshots as the static resources; they do not stream updates, chain tools, reserve resources, assign work, or change the hub protocol.

For local durable memory, synapse memory-recall <db> <query> and the MCP synapse_memory_recall tool project findings, checkpoints, and handoffs from the SQLite event store into deterministic token matches. Recall hits keep the source sequence, event kind, source field, task id, actor, evidence reference, and matched tokens so downstream use remains auditable. The projection is local and read-only: it does not create external embeddings, call a service, certify truth, or mutate hub state. The live hub also caps durable findings admitted per agent before journalling, preventing one producer from dominating the local memory spine.

Durability and reconnection

With --db, the hub records every authoritative mutation to an append-only SQLite event log (WAL) and rebuilds its state by replaying it on start-up. A reconnecting agent uses an idempotency key and a resume cursor (to fetch exactly the messages it missed). For keyed claims, task updates, releases, handoffs, checkpoints, guard-denial records, resource offers, and task-board declarations, updates, and progress notes, one BEGIN IMMEDIATE transaction commits the authoritative event sequence, canonical request digest, exact response, and operation-evidence intent. An identical retry replays that response after restart; changed-payload key reuse is refused without exposing request values. Board writes with and without a key share the same serialized candidate-publication actor, so an unkeyed write cannot be overwritten by an earlier in-flight keyed commit. Release and handoff progress already included in their transaction is published to the live board before post-commit cancellation can propagate. Unkeyed operations remain at-least-once, and a hub without a journal suppresses duplicates only for the life of that process. Operation rows are not automatically removed or compacted away in this release. The typed ledger client accepts idem_key on declare, update, and progress, and the CLI exposes the same value as --idem-key; callers, rather than the hub, choose and retain that stable retry identity.