Durable teammate
Enables two complementary approaches so a teammate can receive and act on messages even after it has gone idle or exited. Both share the same core handler (src/store/durableTeammate.ts). Default OFF — nothing changes for existing users unless they opt in.
Record correction (k7hma, 2026-09-04). The archived task for this capability (T489) still carries its original
note: DEFER (L5), written before the work landed and never updated — read alone it says durable teammates were deferred. They were BUILT:cyberine teammate <team>dispatches option A (src/cyberine.ts→src/commands/teammate.ts→runStandbyTeammate), andcyberine daemonruns option B's scanner whenCYBERINE_WAKE_ON_MESSAGE=1(startWakeOnMessageTicker,src/supervisor/daemon.ts). The archive is frozen by project rule (.claude/rules/frozen-archive.md) — never edited, never authority for a live decision — so the correction lives here, where "what is true now" is supposed to be read.
Who can be addressed, and for how long
Precise boundary, because "send a message to a teammate" hides three different lifetimes:
- A live process (chat session, running run, standby teammate) is addressable by handle, name,
role or team for as long as it heartbeats. Stale means
now - lastBeat > ttlMs(60s default, one missed 30s heartbeat). Past that, and after a graceful exit tombstones its record, it is gone from the roster:/team send <handle>and the planner'smessage_sessionboth fail rather than queue, and the handle is free for the next claimant. There is no registry of past handles. - A finished run has no live process at all, so nothing reads its inbox — UNLESS option B's daemon scanner is on, which turns a message to a terminal run's inbox into a NEW background run seeded with that text. That is a respawn, not a delivery: the original run does not resume.
- A standby teammate is durable only because it is a long-lived process; kill it and it is as unaddressable as any other exited session until restarted.
Regression guard for the first bullet: npm run selftest:session-handle asserts a tombstoned
session stops resolving and its handle becomes claimable again.
Option A — standby subscriber (cyberine teammate)
A long-lived process that registers presence in a team, subscribes to the team broadcast channel and its own mailbox, and dispatches a background run for every inbound note/steer message. Heartbeats presence every 30s; tombstones on SIGTERM/SIGINT.
# Start a standby teammate in team "eng" (runs until SIGTERM)
cyberine teammate eng
# With optional labels (visible in roster / cyberine agents)
cyberine teammate eng --role worker --name coder-1 --capabilities coding,review
# Discover live teammates in that team
cyberine agents --once # fleet view shows presence recordsThe teammate's presence is queryable via resolveAddr({ team: 'eng' }) and roster({ team: 'eng' }) for capability-based routing.
Option B — wake-on-message respawn (daemon scanner)
Add CYBERINE_WAKE_ON_MESSAGE=1 to .env (or export it) before starting cyberine daemon. The daemon then polls terminal run inboxes every 10s and dispatches a new background run for each unseen note/steer message it finds.
# Enable the scanner in the daemon
CYBERINE_WAKE_ON_MESSAGE=1 cyberine daemon
# Tune the scan interval (default 10s)
CYBERINE_WAKE_ON_MESSAGE=1 CYBERINE_WAKE_SCAN_MS=5000 cyberine daemon
# Send a message to a finished run's inbox (triggers respawn on next scan)
# From cyberine MCP tools or busTools:
# sendBusMessage({ kind: 'run', id: '<terminalRunId>' }, 'retry the failing test suite')Dispatched runs carry parentRunId lineage to the original terminal run so cyberine agents shows the chain.
Extend recipes (the vendor-independence payoff)
Three independent ways, none touch the core:
Cross-run comms over HTTP (hub-and-spoke message bus)
The default file bus backend is filesystem IPC: it reaches every process on ONE machine and nothing beyond it. CYBERINEBUSBACKEND=http swaps in a backend that carries the same publish / subscribe...