Skip to content

v0.72.0

Choose a tag to compare

@github-actions github-actions released this 30 Jun 06:52
· 1288 commits to main since this release

Added

  • Added the Participant Fabric (synapse_channel.participants) — an optional layer, on top
    of the bus and never in core, that drives a provider CLI session as a uniform bus
    participant. A Participant answers a typed TurnRequest with a typed TurnResult
    (answer, disclosed rationale, abstain/error state, provider resume token, metered cost),
    so a multi-hop conversation exchanges structure rather than re-summarised prose. This first
    release covers the headless channel: HeadlessClaudeParticipant runs
    claude -p … --output-format stream-json and parses its event stream, injecting shared
    context through --append-system-prompt so peer text never arrives as the user prompt.
    conduct_exchange runs a two-participant loop — one answers, a second reacts to the first's
    result — and BusExchange publishes each result to a live hub. Every participant output
    that becomes another's input passes through a prompt-injection boundary that fences it as
    data and forbids obeying instructions inside it. A provider failure becomes an error result,
    never a raised exception. The layer adds no new dependency and is not imported by the bus
    core; it drives the external claude binary at runtime. 100% line+branch on the new modules.
  • Added session continuity and multi-round conversations to the Participant Fabric. A
    ContinuitySeat wraps any participant and gives it memory across turns by threading the
    provider session resume token, so a later turn resumes the earlier one; an errored or
    session-less turn never overwrites a good thread. conduct_conversation runs a bounded
    multi-round deliberation that cycles through participants — each round reacting to the
    previous turn's result through the injection boundary, each participant remembering its own
    earlier turns — under a hard round cap and an optional cumulative cost budget that halts the
    run early and records that it did (a bounded run never reads as a completed one).
    BusConversation publishes such a conversation to a live hub. 100% line+branch.
  • Added a second Participant Fabric provider: a headless Codex driver. CodexParticipant
    runs codex exec --json (and codex exec resume <id> for continuity) under a read-only
    sandbox by default, and parses its JSONL event stream into the same typed TurnResult the
    Claude driver produces — so the two compose as uniform peers with no provider-specific code
    in the orchestration. Two contract differences are handled and documented: Codex has no
    system-prompt channel, so the shared context (including any fenced peer contribution) is
    prepended to the prompt under a separator; and Codex reports token usage but no monetary
    cost, so its turns carry cost_usd of 0 and a conversation's cost budget cannot bound them
    (only the round cap can). A ContinuitySeat gives a Codex session memory across turns the
    same way it does a Claude one. 100% line+branch; the headless turn, real --resume
    continuity, and a cross-provider exchange (a Claude turn and a Codex turn in one
    conversation) are each covered by gated real smoke tests.
  • Added the multi-party conversation layer to the Participant Fabric — the part that
    multiplies reasoning rather than relaying it. A conversation is run in one of three modes,
    selected for the session: a Colloquy (a small, deep exchange), a Roundtable (equal
    participants, one broad refinement pass), or a Symposium (a larger gathering whose
    moderator synthesises a final answer). convene runs any mode through one shape: an opening
    fan-out where every participant answers concurrently, then the mode's cross-critique rounds
    where each refines having seen the whole panel's answers as fenced data, then a moderator
    synthesis when the mode uses one. select_mode picks the mode from the panel size and
    whether a moderator is available. Every paid turn is bounded — a capped number of critique
    rounds and an optional cumulative cost budget that halts the convocation between rounds and
    records that it did. A peer's answer reaches another participant only through the injection
    boundary, so the multiplication layer has no injection hole. BusConvocation publishes a
    convocation to a live hub. 100% line+branch.
  • Added a third Participant Fabric provider: a headless Kimi driver. KimiParticipant runs
    kimi --print --output-format stream-json (adding -r <id> for continuity) and parses its
    JSONL message stream into the same typed TurnResult the other drivers produce, so all
    three compose as uniform peers with no provider-specific code in the orchestration. Three
    contract differences are handled and documented: Kimi has no system-prompt channel, so the
    shared context (including any fenced peer contribution) is prepended to the prompt under a
    separator; its print mode auto-approves tool calls, so a reasoning participant runs in
    read-only plan mode by default and cannot modify the workspace; and it reports no monetary
    cost, so its turns carry cost_usd of 0 and a conversation's cost budget cannot bound them
    (only the round cap can). The resume token is read from the provider's stderr, where Kimi
    reports it, and a ContinuitySeat gives a Kimi session memory across turns the same way it
    does the others. 100% line+branch; the headless turn and real session resume are covered by
    gated real smoke tests.
  • Added a fourth Participant Fabric provider: a headless Ollama driver — the one provider that
    runs entirely locally, so it is free, offline, and has no account or terms-of-service gate.
    OllamaParticipant runs ollama run <model> and distils the model's plain-text reply into
    the same typed TurnResult the other drivers produce, so all four compose as uniform peers
    with no provider-specific code in the orchestration. Unlike the others, Ollama's run mode
    emits no JSON event stream, no session token, and no cost, so a local turn carries an empty
    session and cost_usd of 0, and its continuity comes from the conversation's fenced context
    rather than provider-side memory; a thinking-capable model's reasoning is suppressed so it
    cannot pollute the reply. A model name is required, as ollama run always names one. 100%
    line+branch; the local turn is covered by a gated real smoke test.
  • Added a fifth Participant Fabric provider: a headless Grok driver, built for completeness but
    not run here. GrokParticipant builds grok --single <prompt> --output-format streaming-json --permission-mode plan, routing shared context through Grok's --rules system-prompt append
    and resuming a session via --resume. The argv is verified against grok --help (Grok
    0.2.64); the stream schema is not, because the Grok CLI is heavy and unreliable on this
    machine and its output was not captured at source. The parser therefore targets the assumed
    Claude-Code-family streaming-json convention (it delegates to the Claude parser) and is
    flagged as such by GROK_SCHEMA_VERIFIED = False; the real smoke is triple-gated and stays
    skipped until the schema can be verified against a usable Grok. 100% line+branch on both new
    modules under that assumption.
  • Added the bus-mediated turn relay, the foundation for the Participant Fabric's PTY and MCP
    channels. Where a headless participant spawns a fresh process and reads its stdout, a
    long-lived peer instead receives the turn over the bus and answers over the bus; relay_turn
    publishes a turn request to the peer, runs an injected wake hook to nudge it, and awaits the
    reply. Reply correlation is a hybrid: it prefers a typed turn_result matched by topic id
    (what a peer running the forthcoming responder returns) and falls back, after a short grace,
    to wrapping a plain-text reply as a degraded answer, so a peer without the responder still
    participates. A hub that never becomes ready, or a turn with no reply, becomes an error
    result rather than a raised exception. The turn request now has a symmetric wire envelope
    (turn_request_to_payload / turn_request_from_payload) beside the existing turn result.
    No new dependency; 100% line+branch.
  • Added the peer-side turn responder, the other half of the bus-mediated relay. A
    TurnResponder wraps a local participant and connects one bus identity; for each turn
    request addressed to it, it runs the participant and publishes a typed turn_result back to
    the requester, re-stamped with the responder's own identity and channel so the envelope
    records who answered on the bus rather than the inner driver. This is the structured side of
    the relay's hybrid correlation — a peer running the responder returns a full typed result,
    while a peer without one still answers through the relay's degraded free-text fallback. Turns
    are served one at a time, and a payload that is not a turn request, or that carries no usable
    sender, takes no turn; an unready hub ends serving without answering. No new dependency;
    100% line+branch.
  • Added the two bus-mediated participant channels on top of the relay. A PtyParticipant fronts
    a terminal agent reading from a tmux pane: it relays the turn over the bus and supplies the
    relay's wake hook by injecting the fixed, payload-free wake prompt into the pane, so the task
    travels as bus data and only the routing nudge touches the terminal. An McpParticipant fronts
    a peer already listening on the bus through its own waker and the Synapse MCP tools, so it
    relays with no wake at all. Both front exactly one peer — the seat's identity is that peer's bus
    identity, which the relay addresses and matches the reply by, while the relay connects under a
    separate sender identity. A peer running the responder answers with a typed result; a peer
    without one still answers through the degraded free-text fallback. No new dependency;
    100% line+branch.
  • Added a channel selector that chooses how to drive a provider. select_channel reads a small
    capabilities descriptor — whether the peer is reachable over MCP, the name of its headless
    binary, whether a tmux session is configured — and returns the most robust available channel in
    the MCP > HEADLESS > PTY order, with the headless rung counting only when its binary resolves
    on PATH. A provider that exposes no usable channel selects nothing, so a caller reports it as
    undrivable rather than guessing. 100% line+branch.
  • Captured the model token usage the Participant Fabric had been discarding, and added an opt-in
    bridge to the existing usage accounting. A turn outcome now carries the provider-reported input
    and output token counts (read from the Claude result usage block and the Codex turn.completed
    usage), and a turn request and result carry the model the turn is attributed to — the operator's
    declared model on the request, restamped by a driver that knows the model it actually ran. A new
    opt-in helper formats these into the canonical usage accounting note and posts it to the
    progress ledger, so a bus-bound exchange or conversation run with usage emission enabled becomes
    visible in the existing cost/token report; emission is off by default, keeping the no-telemetry
    default. The hub core is unchanged and no dependency is added. 100% line+branch.
  • Added an API channel and a first participant for it: an Ollama REST driver. Instead of spawning
    a CLI, OllamaApiParticipant POSTs to a model server's /api/generate endpoint and reads the
    JSON reply, capturing the API-reported token counts straight into the usage accounting. The
    transport is the Python standard library, so no dependency is added, and the request is made
    through an injectable poster so the path is tested without the network. A new api channel value
    joins the selection order as MCP > API > HEADLESS > PTY — a direct HTTP call is more robust than
    spawning a subprocess — and the channel selector gains an API rung. A model name is required, the
    endpoint is stateless (continuity rides the conversation's fenced context), and a local turn has
    no cost; a transport failure or malformed body becomes an error result. 100% line+branch, with a
    gated real smoke against a running local server.
  • Captured the rate-limit signal the Claude parser had been discarding. A turn outcome and result
    now carry the provider's last reported rate-limit utilisation (or none when unreported), read
    from the rate_limit_event the parser previously ignored, with the latest event winning and a
    malformed one dropped rather than coerced. The signal travels on the turn result so a router can
    read a provider's headroom and deprioritise one close to its limit, instead of the awareness
    being thrown away. 100% line+branch.
  • Added a provider/model router that chooses which model should answer a task. Where the channel
    selector answers how to drive one provider, select_provider answers which to drive: from a task
    profile (required capability tags, expected token sizes) and a set of candidate models, it keeps
    the candidates that are drivable and carry every required capability, then ranks the survivors by
    rate-limit headroom (a candidate at or over its limit is dropped, so the captured rate-limit
    signal steers load away from a throttling provider), then estimated cost (a local unpriced model
    ranks free), then channel robustness. It returns the winning candidate with its channel and the
    cost it was ranked on, or nothing when the task is unroutable. The router is pure and selects but
    never constructs a participant, leaving that to the caller. 100% line+branch.
  • Added session telemetry and an operational advisor. A running SessionMetrics total folds each
    finished turn — its tokens, cost, latency, error and abstention counts, the highest rate-limit
    utilisation seen, and the current context size (the last turn's input tokens, since the
    cumulative figure overcounts a re-sent history). From those metrics and a small set of
    thresholds, assess_session reports advisory operational signals: compact a filling context, log
    on a turn cadence, stop against a budget, ease off a provider near its rate limit, or investigate
    a high error rate. The advice is descriptive evidence, not an action and not a gate — the
    function never logs, compacts, or stops a run; it returns recommendations with reasons for a
    human or a higher layer to act on. The fold is pure (the caller measures latency and passes it
    in) and the assessment is pure over the metrics, so both are deterministic and tested without a
    clock. The token figures are the driven participants' pressure, the honest signal this layer can
    see; the orchestrator's own remaining context is a harness metric it does not observe. 100%
    line+branch.
  • Added the WASM sandbox getting-started guide (docs/wasm-sandbox-getting-started.md):
    an operator walkthrough from a tool's source to a capability-limited run — compile a Rust
    tool to wasm32-unknown-unknown, compute its digest and write a deny-by-default manifest,
    validate the manifest, test (pre-flight) the tool, and run --approve it for an audit
    receipt. Every command and its output were captured from a real end-to-end run; the guide
    uses a digest placeholder (each build differs) rather than a fixed digest. Linked from the
    nav and README, with a doc test that keeps its commands parseable by the live CLI and its
    documented verbs in sync. (KIMI v0.71.0 gap closed.)
  • Added synapse sandbox test — a dry-run pre-flight that loads a .wasm tool and verifies
    it against its manifest without running it: core/wasm_sandbox.py compiles the module
    (validating its structure) and reads its exported functions but never instantiates or
    calls it, so no fuel is spent and a runaway tool still pre-flights instantly. The bounded
    PreflightReport (core/sandbox_receipt.py) records whether the module is well-formed,
    whether the --entrypoint (default run) is an exported function, whether the module
    matches its manifest digest, and what it would be granted, with a single ok verdict the
    CLI maps to exit 0 (ready), 1 (pre-flight ran, tool not ready), or 2 (could not
    pre-flight). A cheap gate before sandbox run --approve. Behind the optional [wasm]
    extra; 100% line+branch on the new code. (KIMI v0.71.0 gap closed.)
  • Added the live Studio command centre /studio/command (Studio Stage B): the operator
    view that reads /studio.json and renders it in the instrument-panel design system. Its
    signature instrument is the Coordination Clock — a radial gauge where every claim is a
    segment around the dial, coloured by lease health (green fresh, amber ageing, red stale),
    conflicts marked on the rim, a slow radar sweep, and the verdict and live claim count at
    the centre — surrounded by the verdict pill, headline counters, and agents/claims/tasks/
    risk panels. The shell is hub-independent (it loads and shows an offline state with no hub,
    then fills in as it polls) and honours prefers-reduced-motion (the sweep stills and a
    claims-table fallback appears). Vanilla HTML + the studio.css tokens + dependency-free
    ES — no build step, no external request. 100% line+branch.
  • Added the Studio snapshot endpoint /studio.json (Studio Stage A): studio_snapshot.py
    projects the read-only dashboard payload into the command-centre shape — a single risk
    verdict (the reserved red/amber/green signal), a row of headline counters, and the
    agents, claims, tasks, conflicts, and risk behind them. It is a pure dict-to-dict reshape
    of the existing /snapshot.json read model, so Studio adds no new hub call; every
    headline count is derived from the list it summarises (so the instrument and its rows
    cannot drift apart), and a partial payload from a degraded hub still projects to a
    renderable snapshot. 100% line+branch.

Changed

  • Extracted the hub's idempotency cache, durable-finding quota, and message-id counter
    into core/hub_ledger_guard.py (HubLedgerGuard): the at-most-once replay guard, the
    per-agent finding quota, and the strictly increasing message id now live in one class
    the hub seeds from a durable-log replay, with _next_msg_id / _remember /
    reserve_finding_slot / _maybe_replay_duplicate left as thin delegating wrappers
    (the handler call surface is unchanged) and _idempotency / _message_seq still
    readable off the hub. No behaviour change; the restart-survival of the at-most-once and
    quota guarantees is identical. Final slice of the bounded hub decomposition, which took
    core/hub.py from 1127 to 1009 lines and left it as the connection and message-routing
    coordination core. 100% line+branch on the new module.
  • Removed four dead HTTP wrapper methods from the hub (_http_ok, _http_unauthorized,
    _request_metrics_token, _metrics_authorised) — superseded by the free functions in
    core/hub_http.py and with no remaining callers — and collapsed the redundant
    _http_endpoint_response indirection into the _process_request websockets hook, which
    now calls http_endpoint_response directly. No behaviour change; the /metrics and
    /health endpoints and their token enforcement are unchanged. Third slice of the bounded
    hub decomposition.
  • Extracted the hub's outbound messaging into core/hub_broadcast.py
    (HubBroadcaster): sending one frame to a socket, fanning a broadcast out to every
    client (mirroring to the relay first), addressing a named agent, and composing a
    presence update now live in one class the hub holds, with _send_json / _broadcast
    / _broadcast_presence / _send_to_agent left as thin delegating wrappers (the
    handler call surface is unchanged). It reads the live socket registry and takes the
    hub's system-message factory and online-agents roster as injected callbacks, so it
    carries no back-reference to the hub. No behaviour change. Second slice of the bounded
    hub decomposition. 100% line+branch on the new module.
  • Extracted the relay-log mirroring out of the hub into core/hub_relay.py
    (RelayMirror): the append, lite encoding, and self-trimming that bound the file
    now live in a single-responsibility class the hub holds, leaving _mirror_to_relay
    a thin delegating wrapper. No behaviour change — the relay log, its trimming, and the
    no-log no-op are identical. First slice of the bounded hub decomposition. 100%
    line+branch on the new module.