CPG-Radio
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@CPG-Radio poll thread 'handoff' for messages since my last ack"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
CPG-Radio
A fork of the CPG server that adds non-blocking peer messaging and a durable task-lease layer on top of the Cognitive Planning Graph.
The base server is memory with no bus: it models a completed fan-out run (WORKFLOW → WORKER → STRUCTURED_RESULT) but gives no agent a way to tell another anything while work is in flight. AgentRadio-style message buses are the converse — a bus with no memory, whose threads evaporate at the end of a run.
CPG-Radio adds the bus, and every message is also a typed, provenance-linked, FTS + embedding-searchable graph node. Passive awareness that stays queryable after the run and across sessions.
Section references below (DESIGN §x, SPEC §y) point at internal design documents that are not published; they are kept as provenance markers on the code comments that cite them.
What's new
Messaging (DESIGN §6.1)
Tool | What it does |
| Open a THREAD node — a named channel for one topic or handoff. Validates any |
| Post a MESSAGE to a thread as the calling agent. Creates the node, |
| Read messages newer than a cursor, oldest first, excluding your own. Read-only — it does not advance any cursor. |
| The only writer of the caller's cursor. Idempotent and forward-only; a replayed or stale ack cannot re-deliver processed messages. |
| Block up to 30 s for a message mentioning you (or broadcasting to |
Session identity (§6.2)
Tool | What it does |
| Bind this connection to an existing active session and declare your agent id. Returns a |
Task leases (§6.3)
Tool | What it does |
| Atomically take the lease on a TASK. Succeeds when it is unowned, expired, or already yours. Losing the race returns |
| Extend your lease. |
| Give the lease up with a terminal status (or |
| Stamp |
| Close your own WORKER as |
Tips
Tool | What it does |
| Store a short project TIP ( |
| List tips by usefulness and recency, or search them through the existing hybrid FTS + embedding path with a usefulness boost. Superseded tips are hidden by default. |
| Give one idempotent usefulness vote per agent. |
join_session and plan_session surface up to five top project tips on arrival.
Pass the returned binding_token on later identity-bearing calls. A connection
that has carried more than one agent identity rejects tokenless identity-bearing
calls, preventing a sibling join from redirecting messages, acknowledgements,
leases, worker completion, or result authorship.
Ordinary cpg_query calls exclude TIP nodes unless node_types explicitly
includes TIP, keeping operational advice separate from normal graph recall.
Swarm coordination plane
Area | Tools and guarantees |
Authenticated campaigns |
|
Information barriers | Campaign threads enforce |
Synchronization |
|
Resource coordination |
|
Reliable delivery |
|
Atomic lifecycle |
|
Independent work |
|
Governance and health | Campaign command tips are quarantined until |
Staleness |
|
Legacy session threads remain open. Migrate a crew by creating a campaign,
issuing each stable agent a membership token through a trusted channel, having
each call join_campaign, then creating campaign or participant threads.
Planning or joining another session clears the connection's old campaign grant.
Message and TIP text are always data; no body invokes a tool or changes control
state, regardless of its authority label.
Reaper (§6.4) — a background task, not a tool. Every 60 s it returns expired
task leases to planned (bumping the generation and telling the linked thread),
CAS-expires overdue barriers and shared/exclusive resource leases with dedicated
audit events, fails WORKERs with no heartbeat for an hour, and marks a WORKFLOW
whose workers have all terminated as blocked. Expired coordination records are
retained, and resource fencing generations remain monotonic when reclaimed. It
never auto-completed anything: completion is a claim about results, and only an
agent may make it. A coordinator can explicitly reconcile blocked to completed
after it has collected the terminal results.
Everything else from the base server — cpg_think, record_fact,
record_belief, plan_session, update_task, replan, start_workflow,
update_workflow, register_worker, record_structured_result,
import_fable_trace, record_execution, reflect, resolve_contradiction,
and the three cpg://session/* resources — keeps its existing contract.
cpg_query, get_nodes, and record_structured_result now receive caller
context so access and authorship can be enforced.
The agent protocol
A crew is not a new entity: it is one session_id that several agents joined.
Because nodes stay session-scoped, cpg_query's existing
session_scope: current | project | all gives crew-scoped and cross-crew recall
for free.
join_session → claim_task → work → poll / ack → release_taskjoin_session(session_id, agent_id)— orplan_session(..., agent_id=..., archive_previous=False)if you are the one opening the session. Identity is bound here. Keep the returnedbinding_tokenand pass it to later identity-bearing tools. No later tool takes anagent_idargument.claim_task(task_id)— take the lease before touching the work. Keep the returnedlease_generation; every later mutation needs it.claimed: falsejust means someone else got there first — pick another task.Work. Call
renew_leaseif you will exceed the lease (default 900 s, clamped to[60, 7200]), andworker_heartbeatif you registered a worker.poll_messages/ack_messagesbetween work steps. Poll is read-only, so ack only what you have actually processed; a crash between the two re-delivers rather than loses. Post your own findings withsend_message, mentioning the agents who need them.release_task(task_id, lease_generation, status)—done,done_pending_verification,failed,blocked, orplannedto hand it back.
If you crash instead, the reaper does step 5 for you within a minute.
Parameter names are singular by design: after_seq, max_results,
lease_seconds, lease_generation. There are no aliases.
Running it
CPG-Radio runs on port 8773 with its own DB and project id, deliberately distinct from the live CPG server on 8766:
scripts/run-cpg-radio-httpThat script pins CPG_PORT=8773, CPG_PROJECT_ID=cpg-radio, and
CPG_DB_PATH=~/.cpg-radio/radio.db. Health check:
curl -s http://127.0.0.1:8773/ # {"service": "cpg-radio", ...}MCP requests go to http://127.0.0.1:8773/mcp.
A development checkout refuses to start on the reserved legacy endpoint.
Port 8766, or a db_path under ~/.cpg/ or ~/.local/state/codex-mcp/, is
rejected at startup with LiveDeploymentError — the guard exists so a working
copy can never attach to an already-installed server's database. An installed
deployment that genuinely owns that endpoint may set
CPG_ALLOW_LEGACY_ENDPOINT=1; the spawn gate then requires the complete
canonical identity (loopback bind, the canonical project id, SQLite, and the
canonical DB path) and refuses mixtures of development and canonical settings.
Installed launchers should also set CPG_SPAWN_TOKEN_PATH,
CPG_WORKER_LOG_DIR, and CPG_SUBAGENT_MCP_CONFIG to their own state paths.
Configuration
Variable | Meaning | Default |
|
| inferred |
| PostgreSQL connection string | — |
| SQLite path |
|
| project / session namespace |
|
|
|
|
| HTTP bind |
|
| HTTP MCP path |
|
| reaper tick; |
|
| heartbeat age that fails a WORKER |
|
Schema version is 3. create_schema upgrades a v2 DB in place (the new
tables are IF NOT EXISTS and the new payload fields are all optional, so there
is no data migration) and refuses to start against a DB recording a newer
version than the binary.
Development
.venv/bin/python -m pytest -q
# Optional: only against an isolated disposable PostgreSQL database
CPG_TEST_POSTGRES_URL=postgresql://... \
.venv/bin/python -m pytest tests/integration/test_coordination_postgres.py -qEnd-to-end smoke test — in-process, no network, no server, throwaway DB:
.venv/bin/python scripts/smoke-radio.pyIt walks the full protocol (two agents join one session, one claims a task and posts a finding mentioning the other, the other polls and acks, the lease expires, the reaper hands the task back) and prints PASS/FAIL per step, exiting nonzero on the first failure.
Notes
Single process owns the DB. The reaper assumes it. Multi-process deployment still needs leader election, TLS/service identity, and Postgres notification ownership. Coordination objects are backend-neutral; this does not pretend a multi-host control plane is safe yet.
wait_for_mentionis poll-backed at 500 ms on both backends. There is noLISTEN/NOTIFY:get_db()opens and closes a connection per call, so aLISTENwould never become active before the wait begins. Cursor polling is the primary path regardless;wait_for_mentionis latency sugar.Message bodies are capped at 16 KiB and rejected, never truncated. Larger findings go through
record_structured_resultwith the message carrying arefspointer.kind="status"andkind="system"messages skip embedding, so a chatty bus does not run the model on every ping.finding,question,answer,claim, andhandoffare embedded — those are the ones worth recalling later.kind="system"is server-only and rejected fromsend_message.Embedding search degrades gracefully when the model or vector backend is unavailable; SQLite uses
sqlite-vec, PostgreSQL usespgvector.
Available Tools
59 toolsack_deliveryC
Record an idempotent required-delivery acknowledgement.
Inputs: message_id (required), binding_token.
ack_messagesA
Advance the calling agent's cursor to ack_seq. Forward-only: a lower value is ignored, so a replayed ack cannot re-deliver processed messages.
Inputs: ack_seq (required), binding_token.
ack_threadB
Advance a per-thread subscription cursor without hiding other threads.
Inputs: ack_seq (required), thread_id (required), binding_token.
arrive_barrierB
Atomically arrive at a barrier using its observed version.
Inputs: barrier_id (required), expected_version (required), binding_token.
cancel_workerB
Cancel a spawned worker. Idempotent. Authorised for the root spawn_token, or a child_token belonging to one of the target's ancestors.
Inputs: worker_id (required), reason, child_token, spawn_token, +1 more.
claim_resourceC
Claim a shared or exclusive named resource lease.
Inputs: campaign_id (required), resource_key (required), mode, binding_token, +2 more.
claim_taskA
Atomically take the lease on a TASK. Succeeds only if the task is unowned, its lease expired, or the caller already owns it. Pass the lease_generation you observed; a stale one loses the race.
Inputs: task_id (required), binding_token, lease_seconds, lease_generation.
close_threadA
Close a thread under opener or campaign-coordinator authority.
Inputs: thread_id (required), binding_token.
close_watchB
Close the caller's own watch registration (idempotent).
Inputs: watch_id (required), binding_token.
commit_resultA
Commit a SHA-256 digest before independent workers reveal results.
Inputs: commitment (required), campaign_id (required), workflow_id (required), binding_token, +1 more.
complete_assignmentA
Atomically record a result, release the task, close the worker, and post a handoff.
Inputs: summary (required), task_id (required), thread_id (required), idempotency_key (required), +6 more.
complete_workerA
Close the calling agent's own WORKER node with a terminal status. Call this when your delegated work is finished — otherwise the reaper stamps the worker 'failed' once it has been silent for the stale window. Identity-bound: you may only complete a worker you own. Never overwrites an existing terminal status, and refuses a live spawn-managed worker.
Inputs: note, status, worker_id, binding_token.
cpg_queryA
Hybrid FTS + embedding retrieval over the CPG graph. Use for structured lookups (node_type/domain filters) or open-ended recall (natural language questions). A complete 32-hex node id is resolved directly. Returns ranked node dicts.
Inputs: question (required), domain, node_types, max_results, +3 more.
cpg_thinkA
Ephemeral reasoning scratchpad. Optionally promote to an OBSERVATION node when the thought captures directly perceived data (tool output, file read, etc). Use promote_to='observation' with obs_type to persist. Predictions and hypotheses go through record_belief, not here.
Inputs: thought (required), obs_type, promote_to, source_tool.
create_barrierC
Create a quorum barrier for an explicit campaign phase.
Inputs: name (required), campaign_id (required), phase, quorum, +3 more.
create_campaignC
Create a durable coordination campaign and owner membership capability.
Inputs: name (required), description, binding_token, default_sharing_policy.
create_threadB
Open a THREAD node: a named channel other agents in this session can post to. Use one thread per topic or handoff, not one per message.
Inputs: topic (required), purpose, task_ids, visibility, +6 more.
drop_tipA
Drop a short practical TIP for other agents. TIPs are project-scoped, deduplicated by normalized text, and remain separate from FACT/BELIEF governance.
Inputs: text (required), kind, tags, binding_token, +2 more. Returns: tip_id, deduped
get_nodesA
Fetch whole nodes by id — the by-id counterpart to cpg_query's ranked search. Returns each node's full payload (e.g. a STRUCTURED_RESULT's complete data), not a truncated match. Use when you hold specific ids — such as the result ids a crew posted to a thread — and need the recorded detail, not a summary. Scoped to your session by default; session_scope='project' reads across the project.
Inputs: node_ids (required), binding_token, session_scope.
import_fable_traceA
Read-only importer for Claude/Fable JSONL traces or workflow result JSON. Use when analyzing prior Fable sessions or preserving an external multi-agent workflow in CPG. It creates a WORKFLOW, WORKER nodes, trace events, and when present aggregate coverage/surviving/refuted STRUCTURED_RESULT nodes. It never executes commands from the trace. By default it filters JSONL imports to claude-fable rows when present and skips Opus/Sonnet rows.
Inputs: trace_path (required), max_events, workflow_name, include_non_fable.
invite_memberC
Issue a revocable, role-scoped campaign membership capability.
Inputs: agent_id (required), campaign_id (required), role, expires_at, +2 more.
join_campaignC
Authenticate this connection into a campaign using its membership token.
Inputs: campaign_id (required), membership_token (required), binding_token.
join_sessionA
Bind this connection to an existing active session and declare the calling agent's id. Does not archive anything. Required before any other radio tool when the agent did not itself call plan_session.
Inputs: agent_id (required), session_id (required), child_token. Returns: tips, bound, agent_id, worker_id, +5 more
list_spawnedA
Read-only table of spawned workers in this session: provider, spawn_state, depth, parentage, and uptime. No token required.
Inputs: workflow_id, spawn_states, binding_token.
moderate_tipC
Approve or reject a quarantined campaign tip with evidence.
Inputs: tip_id (required), review_state (required), binding_token, evidence_refs.
plan_sessionB
Initialise a planning session. Creates a GOAL node, registers the full task graph, resolves dependency indices, creates IMPACT predictions, and converts constraints to confirmed FACT nodes. Call once per session before executing tasks.
Inputs: goal (required), tasks (required), risks, budget, +8 more. Returns: tips, task_ids, session_id, binding_token, +3 more
poll_messagesA
Read messages in this session newer than a sequence number. Read-only: it does not advance the caller's cursor — call ack_messages for that.
Inputs: after_seq, thread_id, max_results, binding_token, +1 more. Returns: has_more, messages, next_cursor
poll_threadC
Poll a thread from its independent subscription cursor.
Inputs: thread_id (required), max_results, binding_token.
post_progressB
Atomically renew a task lease, heartbeat the worker, and post progress.
Inputs: body (required), task_id (required), thread_id (required), idempotency_key (required), +3 more.
protocol_healthC
Report coordination health without returning restricted message bodies.
Inputs: campaign_id, binding_token.
publish_signalC
Create or compare-and-swap a typed, versioned coordination signal.
Inputs: name (required), campaign_id (required), value, audience, +5 more.
record_beliefA
Record an uncertain BELIEF (inference, hypothesis, assumption). Must cite supporting FACT or OBSERVATION ids. When belief_kind='assumption', also creates a linked ASSUMPTION control-plane node.
Inputs: content (required), confidence (required), domain, risk_id, +4 more.
record_executionB
Episodic log of a significant tool call. Creates an EXECUTION node + auto-observation, processes actual_impacts (lazy ARTIFACT creation + staleness), and stores objective_signals for reflect().
Inputs: success (required), task_id (required), tool_name (required), input_summary (required), +9 more.
record_factA
Record a grounded FACT. Must cite source_observation_id (from a prior cpg_think promote) or a raw source string. Facts start as 'provisional' and are upgraded by evidence. Use supersedes_id to chain replacements.
Inputs: domain (required), content (required), source, symbol_id, +4 more.
record_structured_resultB
Store schema-bound worker output in a WORKFLOW. Use for findings, verifier verdicts, coverage notes, and final reports. For a verifier verdict, set result_kind='verdict', status to confirmed/plausible/refuted, and verifies_result_id to the finding/result being checked. The response returns an aggregate count by result kind and status.
Inputs: data (required), result_kind (required), workflow_id (required), title, +10 more.
reflectC
Core pedagogical loop. Provide deviation_type and optionally objective_signals; server computes ReflectiveSignals and a dual-signal SCORE. Closes the loop on patterns, beliefs, and v1.4 control-plane nodes.
Inputs: task_id (required), actual_outcome (required), deviation_type (required), expected_outcome (required), +10 more.
register_watchC
Record which job (worker id, label, or done-file) this agent is waiting on.
Inputs: note, done_file, job_label, binding_token, +1 more.
register_workerB
Register one delegated worker/agent inside a WORKFLOW. Use one worker per finder/verifier/synthesizer/executor. Include the model, agent_id, prompt, dimension, and role whenever known so later agents can audit who did what.
Inputs: label (required), workflow_id (required), role, model, +7 more.
release_resourceB
Release the caller from a generation-checked resource lease.
Inputs: lease_id (required), generation (required), binding_token.
release_taskA
Give up the caller's lease on a TASK. lease_generation is required so a reaped-and-reassigned task cannot be released out from under its new owner.
Inputs: task_id (required), lease_generation (required), note, status, +1 more.
renew_leaseB
Extend the caller's lease on a TASK it already owns. Fails if the lease was reaped or reassigned.
Inputs: task_id (required), lease_generation (required), binding_token, lease_seconds.
renew_resourceC
Renew a resource lease held by the caller.
Inputs: lease_id (required), generation (required), binding_token, lease_seconds.
replanB
Formal replanning event. Cancels specified tasks (preserved in history), optionally adds new tasks at an incremented plan_version. Does not delete any nodes or edges.
Inputs: reason (required), new_tasks, drop_task_ids.
resolve_contradictionB
Resolve a queued FACT conflict from the contradictions table. Applies one of: a_supersedes_b, b_supersedes_a, both_provisional, dismissed.
Inputs: resolution (required), contradiction_id (required), notes.
resolve_messageB
Mark a question or request resolved by a message in the same thread.
Inputs: message_id (required), resolution_message_id (required), binding_token.
reveal_resultC
Reveal data matching a prior commitment and record the result.
Inputs: data (required), nonce (required), commitment_id (required), title, +1 more.
revoke_memberC
Revoke a campaign membership capability.
Inputs: membership_id (required), binding_token.
send_messageA
Post a MESSAGE to a THREAD as the calling agent. Body is capped at 16 KiB; larger findings go through record_structured_result with the message carrying a refs pointer. kind='system' is server-only.
Inputs: body (required), thread_id (required), kind, refs, +12 more.
spawn_workersA
Fan out to configured local providers. Creates WORKER nodes in spawn_state='requested' and returns immediately — a background supervisor performs the calls and records each answer as a STRUCTURED_RESULT. Requires exactly one of spawn_token or child_token. Caps and unknown providers come back in 'refused', never as an error.
Inputs: workers (required), workflow_id (required), wait, child_token, +2 more.
stale_reportA
List stale running workers, leases whose claimant is gone, a gpu.lock that disagrees with the lease, and watchers of finished jobs. Read-only.
Inputs: scope, gpu_lock_dir, gpu_task_ids, binding_token, +1 more.
start_workflowA
Create a first-class fan-out/fan-in WORKFLOW run. Use this when a task will delegate to multiple agents, run phases such as find→verify→synthesize, or needs schema-bound aggregation. After calling it, register each worker with register_worker and store each worker output or verifier verdict with record_structured_result. Do not use for ordinary single-thread task lists; use plan_session/update_task for those.
Inputs: name (required), phases, metadata, task_ids, +2 more.
subscribe_threadB
Create a durable per-thread subscription cursor.
Inputs: thread_id (required), binding_token.
tipsB
Read ranked practical TIPs. With query, reuses hybrid FTS+embedding retrieval and boosts useful tips; otherwise ranks by votes then recency.
Inputs: kind, tags, query, scope, +3 more. Returns: tips
tip_usefulA
Mark a TIP useful as the calling agent. One vote per agent per TIP; repeated calls are idempotent.
Inputs: tip_id (required), binding_token. Returns: useful_count, already_voted
update_taskA
Update a TASK node lifecycle status. Valid transitions: planned→in_progress, in_progress→done/failed/blocked/cancelled/done_pending_verification/verified.
Inputs: status (required), task_id (required), notes.
update_workflowA
Update a WORKFLOW status and optional summary. Use when all workers finish, a workflow blocks, or final aggregation is complete. Terminal states are completed, failed, and cancelled.
Inputs: status (required), workflow_id (required), summary, binding_token.
wait_for_mentionA
Block until a message mentioning the calling agent (or broadcasting to '*') arrives, or the timeout elapses. Returns immediately if one is already pending.
Inputs: after_seq, thread_id, timeout_s, max_results, +2 more. Returns: has_more, messages, timed_out, next_cursor
wait_for_signalC
Read a signal only when its version advanced; expired signals are absent.
Inputs: name (required), campaign_id (required), after_version, binding_token.
worker_heartbeatA
Stamp heartbeat_at on the calling agent's WORKER node so the reaper does not treat it as stale.
Inputs: worker_id, binding_token.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 59 tools
Several tool families—poll_messages/poll_thread, ack_messages/ack_thread/ack_delivery, record_structured_result/reveal_result/complete_assignment, renew_lease/renew_resource, complete_worker/complete_assignment—have nuanced but near-overlapping purposes, so an agent can easily select the wrong one. The descriptions are detailed, but at 59 tools the set lacks clear semantic separation.
Most tools use snake_case verb_noun, but there are many inconsistent outliers: cpg_think, cpg_query, replan, reflect, tips, protocol_health, stale_report, post_progress, complete_assignment. Verbs are not normalized across categories (create/start/spawn, poll/subscribe, ack/acknowledge, close/complete/cancel), so the pattern is only loosely predictable.
59 tools is an extreme size for an MCP server; even though the platform covers many subdomains, it is far beyond a well-scoped tool set and will overwhelm agents with selection overhead. It would benefit from consolidated compound tools or splitting into focused servers.
The server covers the core lifecycle well: planning, task leasing, workflow fan-out/fan-in, worker heartbeats/completion, threaded messaging with cursors/acks, campaign and barrier coordination, facts/beliefs/tips, and result commitments. Minor gaps like explicit campaign dissolution or direct fact/belief deletion are workaroundable through supersede/cancel/query tools.
Maintenance
Related MCP Connectors
Durable agent-to-agent handoffs and shared scratchpad for multi-agent workflows.
Multi-agent coordination for AI agents: shared spaces, tasks, search, mailbox and persistent state.
Durable background job execution, async task scheduling, and state persistence for AI agents.
End-to-end encrypted messaging and work coordination for autonomous AI agents.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables provider-neutral durable coordination for coding agents sharing Git work, including peer discovery, durable messaging, job progress tracking, and FIFO integration authority with fenced leases.MIT
- AlicenseNot gradedqualityCmaintenanceEnables multiple coding agents running side by side to coordinate through a shared mailbox, claim list, and append-only log, so they can see peers, avoid duplicate work, and share findings or dead ends.MIT
- AlicenseNot gradedqualityBmaintenanceLocal OS for your AI Agents fleets. ——————- Enables AI agents to coordinate through a durable local board with shared state, ticket lifecycle, evidence-based approvals, and journal-woken handoffs.2Apache 2.0
- AlicenseNot gradedqualityBmaintenanceProvides AI agents with shared communication and persistent state over HTTP and MCP, letting an agent save its progress, search and reuse other agents' prior work, coordinate tasks in shared spaces, exchange direct messages, and hand its role to a successor when a session ends. State is kept on PostgreSQL with signed, append-only records that anyone can verify.Business Source 1.1