CCCC Daemon API/IPC Contract v1
Status: Draft (for CCCC v0.5.x ecosystem)
This document defines the daemon-facing client contract for CCCC: how a client (CLI/Web/MCP bridge/SDK) discovers the daemon endpoint, frames requests, and calls daemon operations.
It is intentionally narrow:
- CCCS v1 (
docs/standards/CCCS_V1.md) defines the semantic collaboration substrate (event envelope + kinds + delivery/read/reply facts). - This document defines the transport + RPC layer used by CCCC today (newline-delimited JSON over a local socket/TCP).
0. Conformance Language
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.
1. Goals and Non‑Goals
1.1 Goals
Daemon IPC v1 MUST provide:
- A stable request/response envelope with a normative error shape suitable for SDKs.
- A cross-platform local transport (Unix socket where available; TCP fallback).
- A single-writer control plane for group state, actors, messaging, inbox, and context.
1.2 Non‑Goals
Daemon IPC v1 does NOT standardize:
- Remote authentication/authorization or multi-tenant security.
- Any specific workflow engine or prompting strategy.
- A browser-friendly HTTP API surface (this document is socket/TCP oriented).
2. Terminology
- CCCC_HOME: The single global runtime home directory (default
~/.cccc/). - Daemon: The single-writer process that owns group state and appends to ledgers.
- Client: Any process calling daemon operations (CLI/Web/MCP/SDK).
- Group / Actor / Scope / Ledger: As defined in CCCS v1.
- Principal (
by): A string identity such as"user","system", anactor_id, or a service principal.
3. Endpoint Discovery (Normative)
Clients MUST discover the daemon endpoint via a daemon-written descriptor file:
- Path:
${CCCC_HOME}/daemon/ccccd.addr.json
CCCC_HOME resolution:
- If the
CCCC_HOMEenvironment variable is set, clients MUST use it as the base directory. - Otherwise, clients MUST use the default
~/.cccc/.
If the descriptor file is missing or invalid, a client MAY fall back to:
- Unix socket default:
${CCCC_HOME}/daemon/ccccd.sock(only if AF_UNIX is supported)
3.1 ccccd.addr.json Schema
The daemon writes a JSON object with the following fields:
{
"v": 1,
"transport": "unix",
"path": "/home/alice/.cccc/daemon/ccccd.sock",
"host": "",
"port": 0,
"pid": 12345,
"version": "0.4.x",
"ts": "2026-01-13T12:34:56Z"
}Rules:
vMUST be1.transportMUST be"unix"or"tcp".- If
transport == "unix",pathMUST be a non-empty filesystem path. - If
transport == "tcp",hostMUST be a connectable host (typically127.0.0.1) andportMUST be a positive integer. - Clients MUST treat unknown fields as ignorable metadata (but SHOULD preserve them if re-writing).
3.2 Daemon Runtime Files (Non-normative)
CCCC uses these files under ${CCCC_HOME}/daemon/:
ccccd.addr.json: endpoint descriptor (this spec)ccccd.sock: Unix socket path (POSIX default)ccccd.pid: daemon process id (best-effort)ccccd.log: daemon log file (best-effort)
3.3 Endpoint Configuration (Non-normative)
Daemon endpoint selection is controlled by environment variables:
CCCC_DAEMON_TRANSPORT:"unix"or"tcp"(default:"unix"on POSIX,"tcp"on Windows)CCCC_DAEMON_HOST: bind host for TCP (default:127.0.0.1)CCCC_DAEMON_PORT: bind port for TCP (default:0meaning “choose a free port”)
The native daemon rejects every non-loopback TCP host before binding. Daemon IPC has no authentication and cannot be exposed with 0.0.0.0, a LAN address, or a public address. Use the authenticated Web API for remote access.
4. Transport and Framing (Normative)
4.1 Transport
Daemon IPC v1 uses a stream transport:
- Unix domain socket (
transport="unix") where available. - TCP (
transport="tcp") for cross-platform fallback.
Security note: there is no authentication at this layer. TCP bindings MUST remain loopback-only.
4.2 Framing: NDJSON
For all non-streaming operations, requests and responses are framed as:
- One JSON object per line, delimited by a single
\n(newline). - Encoding MUST be UTF‑8.
Baseline behavior:
- A daemon MAY accept multiple request lines on one connection or close the connection after any response.
- When multiple requests are accepted, they are processed strictly serially and produce one response line each.
- Clients MUST NOT pipeline requests (there is no request id / multiplexing in v1).
- Clients MUST tolerate the daemon closing a connection after any response and reconnect through endpoint discovery before the next request. The reference non-streaming client opens a fresh local connection for each call because v1 has no request id with which to make a close-versus-write race safely replayable.
4.3 Size Limits
Implementations MUST respect practical line limits to avoid truncation:
- Request line limit (daemon receive): the native daemon stops reading after 16 MiB without a newline; clients MUST keep request lines below this bound.
- Response line limit (typical clients): the reference client reader MAY cap a response line at ~4,000,000 bytes; daemons SHOULD keep single-response payloads below this bound.
Clients SHOULD treat truncated/invalid JSON as a transport failure. Once any request bytes have been written, clients MUST NOT automatically replay the request after a send, read, or decode failure unless the operation carries a daemon-enforced idempotency key. Retrying a failure that occurred while establishing the connection is safe because no request was sent.
4.4 Streaming Upgrade: term_attach
term_attach is a special operation that upgrades the connection:
- Client sends a normal request line with
op="term_attach". - Daemon sends a normal response line.
- If the response is
ok=true, the connection becomes a terminal stream until closed.
After upgrade, the stream is not NDJSON.
The stream semantics are implementation-defined but, in CCCC today:
- By default, the client receives raw PTY output bytes.
- A client that requests
bootstrap="snapshot_v1"can receive one negotiated ANSI screen snapshot first, followed by raw PTY bytes after the snapshot's raw cursor fence. - The client MAY write raw bytes as input.
- The daemon MAY allow only one writer at a time (others become read-only).
- A daemon MAY close an attachment that falls behind its bounded retained-output window. A reconnecting client SHOULD resume from its last fully consumed byte cursor using
since; the handshake clamps an expired cursor to retained history.
Input is serialized per runtime session, without holding lifecycle synchronization across PTY I/O. An input transaction remains bound to that session: stopping and restarting an Actor MUST NOT route an old payload or submit-key suffix into its replacement. Writer ownership MUST be checked before starting an attachment input transaction. Partial or cancelled writes MUST NOT be reported as fully submitted. The Linux implementation uses nonblocking PTY readiness waits so backpressure remains responsive to cancellation, writer revocation and hangup; bytes already accepted by the PTY cannot be recalled. Other platforms retain their native PTY I/O. The reference daemon closes a terminal attachment if one input batch cannot be written within 30 seconds, releasing its writer ownership. This bounds a stalled input stream even when pending writes prevent observing client EOF. It does not limit idle viewing time or stop the Actor.
Out-of-band control:
- Control operations (e.g.,
term_resize) MUST be performed over a separate concurrent daemon connection.
4.5 Streaming Upgrade: events_stream (Optional)
events_stream is an optional operation that upgrades the connection into a push event stream for reactive clients (Web/IDE/bots).
- Client sends a normal request line with
op="events_stream". - Daemon sends a normal response line.
- If the response is
ok=true, the connection remains open and the daemon pushes NDJSON items indefinitely.
After upgrade, the stream is NDJSON, but it is no longer request/response: the daemon becomes the writer.
Stream item (recommended envelope):
type EventStreamItem =
| { t: "event"; event: CCCSEventV1 }
| { t: "heartbeat"; ts: string }
| { t: string; [k: string]: unknown } // forward-compatible extensionRules:
- Clients MUST ignore unknown
tvalues. heartbeatitems MUST NOT be appended to the group ledger; they are transport-level keepalives.- Streams are best-effort: clients MUST tolerate disconnects, duplicates, and gaps (use
inbox_peekor a ledger read to reconcile).
4.6 Streaming Upgrade: presentation_browser_attach / VNC attach (Optional)
presentation_browser_attach upgrades the connection into a daemon-local browser-surface control stream for a slot-scoped Presentation browser session. The same stream envelope is reused by other projected browser attach operations, including provider-auth and Web Model browser surfaces.
- Client sends a normal request line with
op="presentation_browser_attach". - Daemon sends a normal response line.
- If the response is
ok=true, the connection remains open and becomes a bidirectional NDJSON stream.
After upgrade:
- The daemon pushes
stateandframeitems for the active browser surface session. - The client MAY send browser-control commands such as navigation, click, scroll, key, text, resize, and close.
- Only one active controller MAY be attached at a time for a given slot browser surface session.
- If a matching
*_vnc_attachoperation succeeds, the connection upgrades into a raw RFB/VNC byte stream instead of NDJSON. VNC attach is an optional viewer transport; browser control and delivery semantics remain owned by the daemon runtime.
Recommended daemon-to-client items (CCCC v0.4.x behavior):
type PresentationBrowserStreamItem =
| {
t: "state"
active: boolean
state: "starting" | "ready" | "failed" | "closed" | "idle"
message: string
error?: Record<string, unknown>
strategy?: string
url?: string
width?: number
height?: number
started_at?: string
updated_at?: string
last_frame_seq?: number
last_frame_at?: string
controller_attached?: boolean
}
| {
t: "frame"
seq: number
captured_at: string
mime: "image/jpeg"
data_base64: string
width: number
height: number
url: string
}
| {
t: "error"
code: string
message: string
}Recommended client-to-daemon commands (CCCC v0.4.x behavior):
type PresentationBrowserCommand =
| { t: "ping" }
| { t: "navigate"; url: string }
| { t: "back" }
| { t: "refresh" }
| { t: "click"; x: number; y: number; button?: "left" | "middle" | "right" }
| { t: "scroll"; dx?: number; dy?: number }
| { t: "key"; key: string }
| { t: "text"; text: string }
| { t: "resize"; width: number; height: number }
| { t: "close" | "disconnect" }Rules:
- Clients MUST treat unknown
tvalues as ignorable forward-compatible extensions. - The browser-surface stream is best-effort and ephemeral; clients MUST be able to reconnect and recover via
presentation_browser_info/presentation_browser_open. - The stream is daemon-local runtime state and MUST NOT be treated as persisted Presentation card state.
5. Request/Response Envelope (Normative)
Daemon IPC v1 uses the envelope defined in crates/cccc-contracts/src/ipc.rs.
5.1 Request
interface DaemonRequestV1 {
v: 1
op: string
args?: Record<string, unknown> // default {}
}Rules:
vMUST be1.opMUST be a non-empty string (snake_case in CCCC v0.4.x).- Clients MUST NOT send unknown top-level fields (the daemon is strict at the envelope level).
5.2 Response
interface DaemonResponseV1 {
v: 1
ok: boolean
result: Record<string, unknown> // default {}
error?: DaemonErrorV1 | null
}
interface DaemonErrorV1 {
code: string
message: string
details: Record<string, unknown> // default {}
}Rules:
vMUST be1.- If
ok == true,errorMUST be omitted ornull. - If
ok == false,errorMUST be present. - Clients MUST NOT expect a stable schema for
resultbeyond what eachopspecifies.
6. Error Model (Normative)
The error envelope shape in §5.2 is normative: daemons MUST return errors using this shape for all application-level failures.
6.1 Error Code Conventions
error.codeMUST be a stable, machine-readable token.error.messageMUST be human-readable.error.detailsMUST be a JSON object (may be empty).- The set of
error.codevalues is an open set; clients MUST handle unknown codes gracefully.
Common codes used by CCCC v0.4.x include (non-exhaustive):
invalid_request,unknown_opmissing_group_id,group_not_foundmissing_actor_id,actor_not_found,actor_not_running,not_pty_actorpermission_deniedinvalid_patch,invalid_template,confirmation_required
7. Operation Conventions
7.1 Identity and Permission Parameters
Many operations accept:
group_id: target group identifier (string)actor_id: target actor identifier (string)by: principal string indicating who is acting (default varies by op)
Authorization is enforced by the daemon (see crates/cccc-core/src/permissions.rs and the operation-level checks in crates/cccc-daemon/src/ops/). Daemon IPC v1 has no authentication. The practical trust boundary is OS-level access control to the local socket / localhost port.
Local-trust model (CCCC v0.4.x behavior):
- If an operation accepts
args.by, the daemon treats it as a caller-provided principal hint and uses it for attribution (ledgerevent.by) and permission checks. - If
byis omitted or blank, the daemon uses an operation-specific default (often"user").
Security note:
- In a local-trust deployment, any process that can connect to the daemon can spoof
by. Do not treatbyas a security boundary. - Remote/multi-tenant authentication is out of scope for v1.
7.2 Event Objects
Many operations return or include ledger events. Event envelopes follow the CCCC/CCCS v1 shape (see crates/cccc-contracts/src/event.rs and docs/standards/CCCS_V1.md).
8. Operation Catalog (Normative for v1)
Unless otherwise stated:
- All operations use the request/response envelope in §5.
- All args live under
request.args. - All returned values live under
response.result.
8.1 Core
ping
Args: none
Result:
{
version: string;
implementation: "rust";
pid: number;
ts: string;
ipc_v: 1;
capabilities: Record<string, unknown>;
compatibility?: string;
build?: { source_id: string };
executable?: string | null;
}Notes:
- SDK-compatible daemons MUST return
ipc_v: 1; omitting it is interpreted as IPC version0. - The bundled daemon MUST identify its implementation as
rust. compatibility, when present, is an implementation-specific compatibility identity; clients MUST NOT infer compatibility from the implementation name alone.- SDK-compatible daemons MUST return a
capabilitiesfeature map. The native daemon advertises supportedevents_stream,remote_access, optional browser-attach operations, and optional terminal-attach extensions here. - Each optional browser stream is advertised under its exact operation name (
presentation_browser_attach,presentation_browser_vnc_attach,space_provider_auth_browser_attach,space_provider_auth_browser_vnc_attach,web_model_browser_attach, orweb_model_browser_vnc_attach).truemeans the daemon recognizes that streaming upgrade;falsemeans callers MUST use another product surface or treat the operation as unavailable. - A product implementation MAY serve an equivalent ephemeral browser surface directly through its local Web port. That does not make the daemon IPC upgrade supported: the exact daemon capability MUST remain
falseunless that daemon recognizes and serves the operation itself. term_attachment_status=truemeansterm_attachreturns a positiveattachment_idand the daemon implements writer-ownership status checks for that ID.term_attach_snapshot_v1=truemeans callers may requestbootstrap="snapshot_v1"and receiveinitial_outputmetadata. Clients MUST retain the baseline replay-stream behavior when either extension is false.- Clients SHOULD probe operation support independently; a recognized operation may reject empty probe arguments, but MUST NOT return
unknown_op. - Clients MUST NOT probe an unadvertised browser attach operation merely to discover support: a successful probe upgrades the connection and may acquire the only controller. They SHOULD consult the exact capability first.
- Clients MUST use protocol, compatibility, and capability fields instead of exact product-version equality.
- Optional
build.source_idis a SHA-256 fingerprint of the Rust workspace source, manifests/lockfile and bundled resources (root and crate-localresources/trees), compiled into the running process. It distinguishes same-version source changes, including uncommitted changes, without requiring Git at build/run time. It is diagnostic metadata, not a binary checksum or compatibility gate. Web assets are identified separately by authenticated Webping(web.assets_idandweb.entry_script), using the same asset source as HTTP delivery. Release builds inspect their immutable embedded bundle; source-run debug builds inspect the current disk bundle, including frontend-only rebuilds. Unavailable assets report null identities rather than old build-time values. Unauthenticated health responses remain minimal. cccc doctorincludes its own build and the daemon's reported build. MCPinitializeincludes the server's build under_meta["cccc/build"]; this reports the MCP process actually answering, not the executable currently on disk.executableis the effective daemon executable path for local diagnosis. Web exposes local paths only to administrators requestinginclude_home; ordinary authenticated ping and all health projections omit the executable path.- Ordinary business commands MUST NOT stop, signal, or replace a reachable daemon. Implementation replacement is restricted to explicit daemon lifecycle commands.
shutdown
Args:
{ expected_pid?: number }When expected_pid is present, it MUST be a positive integer matching the receiving daemon's current process ID. A mismatch MUST return daemon_owner_mismatch and MUST NOT begin shutdown. This optional fence lets a lifecycle owner avoid stopping a replacement daemon after an IPC descriptor handoff. Omitting expected_pid preserves the normal administrator shutdown behavior.
Result:
{ message: string } // "shutting down"8.2 Observability (Global)
observability_get
Args: none
Result:
{ observability: Record<string, unknown> }observability_update
Args:
{ by?: "user"; patch: Record<string, unknown> }Result:
{ observability: Record<string, unknown> }branding_get
Args: none
Result:
{
branding: {
product_name: string
logo_icon_asset_path?: string
favicon_asset_path?: string
updated_at?: string
}
}branding_update
Args:
{ by?: "user"; patch: Record<string, unknown> }Result:
{
branding: {
product_name: string
logo_icon_asset_path?: string
favicon_asset_path?: string
updated_at?: string
}
}debug_snapshot
Developer-mode diagnostic snapshot (global + optional group context).
Args:
{ group_id?: string; by?: string }Result:
{
developer_mode: true
observability: Record<string, unknown>
daemon: { pid: number; version: string; ts: string }
group?: { group_id: string; state: string; active_scope_key: string; title: string }
actors?: Array<{ id: string; role: string; runtime: string; runner: string; runner_effective: string; enabled: boolean; running: boolean; unread_count: number }>
delivery?: Record<string, unknown>
}Notes:
- Requires developer mode.
- Permission is
user, orforemanwhengroup_idis provided.
8.3 Groups and Scopes
attach
Attach a directory scope to a group (or auto-create/select a group for this scope).
Args:
{ path: string; group_id?: string; by?: string }Result:
{ group_id: string; scope_key: string; title?: string }groups
List known groups (registry summaries).
Args: none
Result:
{ groups: Array<Record<string, unknown>> } // includes at least group_id/title/created_at/updated_at + running/stateregistry_reconcile
Scan registry entries for missing/corrupt groups, and optionally remove missing entries.
Args:
{ remove_missing?: boolean }Result:
{
dry_run: boolean
scanned_groups: number
missing_group_ids: string[]
corrupt_group_ids: string[]
removed_group_ids: string[]
removed_default_scope_keys: string[]
}capability_overview
Return a global capability library snapshot for Settings/Policy views (no actor scope required).
Args:
{
query?: string
limit?: number // default 400, max 2000
include_indexed?: boolean // default true
}Result:
{
items: Array<{
capability_id: string
kind: "mcp_toolpack" | "skill" | ""
name: string
description_short?: string
source_id?: string
source_uri?: string
source_tier?: string
trust_tier?: string
license?: string
sync_state?: string
policy_level: "indexed" | "mounted" | "enabled" | "pinned"
policy_visible: boolean
blocked_global: boolean
blocked_reason?: string
enable_supported: boolean
qualification_status: "qualified" | "unavailable" | "blocked"
qualification_reasons?: string[] // currently exposed for agent_self_proposed skill management
capsule_text?: string // currently exposed for agent_self_proposed skill management
install_mode?: string
autoload_candidate: boolean
tags?: string[]
tool_count?: number
tool_names?: string[]
cached_install_state?: string
cached_install_error_code?: string
cached_install_error?: string
recent_success?: {
success_count: number
last_success_at?: string
last_group_id?: string
last_actor_id?: string
last_action?: string
}
}>
count: number
query: string
sources: Record<string, {
source_id: string
enabled: boolean
source_level: "indexed" | "mounted" | "enabled" | "pinned"
rationale?: string
sync_state: string
last_synced_at?: string
staleness_seconds: number
record_count: number
error?: string
}>
blocked_capabilities: Array<{
capability_id: string
scope: "global"
reason?: string
by?: string
blocked_at?: string
expires_at?: string
}>
allowlist_revision: string
}capability_search
Search capability registry records (built-in packs + local curated catalog + cached remote records).
Args:
{
group_id: string
actor_id?: string
by?: string
query?: string
kind?: "mcp_toolpack" | "skill" | ""
source_id?: string
trust_tier?: string
qualification_status?: "qualified" | "unavailable" | "blocked" | ""
include_external?: boolean
limit?: number
}Result:
{
group_id: string
actor_id?: string
default_profile: "core"
items: Array<{
capability_id: string
kind: "mcp_toolpack" | "skill"
name: string
description_short: string
source_id: string
source_tier: string
source_uri?: string
trust_tier: string
license?: string
qualification_status: "qualified" | "unavailable" | "blocked"
sync_state?: string
enabled: boolean
enable_supported: boolean
install_mode?: string
policy_level?: "indexed" | "mounted" | "enabled" | "pinned"
enable_hint?: "enable_now" | "blocked" | "unsupported" | "active"
blocked_reason?: string
readiness_preview?: {
preview_status: "blocked" | "enableable" | "active" | "needs_inspect"
next_step: string
already_active?: boolean
}
tags?: string[]
tool_count?: number
tool_names?: string[]
}>
count: number
sources: Record<string, unknown>
applied_filters: {
kind: string
source_id: string
trust_tier: string
qualification_status: string
}
search_diagnostics?: {
remote_augmented: boolean
remote_added: number
remote_error?: string
policy_hidden_count?: number
}
}capability_enable
Enable or disable a capability by scope.
Notes:
- Built-in capability packs (
pack:*) are directly enable-able and can change MCP exposure. - Skills (
kind=skill) use the samecapability_enableop for activate/deactivate and can auto-apply declared dependencies. - External MCP execution path supports
remote_only,package, andcommand. packagemode supports npm (npx), pypi (uvx/pipx), OCI (docker/podman) and can fall back to command candidates when package metadata is incomplete.- External enable runs preflight first (required env, runtime binary availability, remote URL sanity) and returns
reason=preflight_failed:<code>on deterministic blockers. activation_pendingmeans relist/reconnect is still required;runnablemeans binding is live enough to try;verifiedis reserved for post-call proof, not plain enable.
Args:
{
group_id: string
capability_id: string
scope?: "group" | "actor" | "session" // default: session
enabled?: boolean // default: true
cleanup?: boolean // default: false; disable path can also clean runtime cache
reason?: string // optional short audit reason
ttl_seconds?: number // session scope only
by?: string
actor_id?: string
}Result:
{
action_id: string
group_id: string
actor_id: string
capability_id: string
scope: "group" | "actor" | "session"
enabled: boolean
state: "activation_pending" | "runnable" | "blocked" | "disabled"
refresh_required: boolean
refresh_mode?: "relist_or_reconnect"
wait?: "relist_or_reconnect"
reason?: string
error?: string
retryable?: boolean
install_error_code?: string
required_env?: string[]
missing_binaries?: string[]
policy_level?: "indexed" | "mounted" | "enabled" | "pinned"
install_state?: "installed" | "installed_degraded" | "install_failed"
degraded?: boolean
degraded_reason?: string
degraded_call_hint?: string
fallback_from?: "package"
fallback_reason?: string
preflight?: {
ok: boolean
code: string
message: string
required_env?: string[]
missing_binaries?: string[]
}
diagnostics?: Array<{
code: string
message: string
retryable?: boolean
required_env?: string[]
action_hints?: string[]
}>
removed_binding_count?: number
removed_installation?: boolean
cleanup_skipped_reason?: string
skill?: {
capability_id: string
name: string
description_short?: string
capsule?: string
requires_capabilities?: string[]
applied_dependencies?: string[]
skipped_dependencies?: Array<{ capability_id: string; reason: string }>
source_id?: string
source_uri?: string
}
}Quota notes:
CCCC_CAPABILITY_MAX_ENABLED_PER_ACTOR(default20) limits actor/session enabled non-skill capability count.CCCC_CAPABILITY_MAX_ENABLED_PER_GROUP(default24) limits group-scope enabled capability count.CCCC_CAPABILITY_MAX_INSTALLATIONS_TOTAL(default128) limits total cached external artifacts.- Quota failures return
ok=truewithstate="failed"and deterministicreasoncode.
capability_block
Block/unblock capabilities at runtime.
Notes:
scope=group: foreman or user can block/unblock.scope=global: only user can block/unblock.- Blocking revokes enabled bindings and runtime dynamic tool exposure immediately.
Args:
{
group_id: string
capability_id: string
scope?: "group" | "global" // default: group
blocked?: boolean // default: true
ttl_seconds?: number // 0 means no expiry
reason?: string
by?: string
actor_id?: string
}Result:
{
action_id: string
group_id: string
actor_id: string
capability_id: string
scope: "group" | "global"
blocked: boolean
state: "blocked" | "unblocked"
removed_bindings: number
removed_runtime_bindings: number
refresh_required: boolean
refresh_mode?: "relist_or_reconnect"
wait?: "relist_or_reconnect"
block?: {
reason?: string
by?: string
blocked_at?: string
expires_at?: string
}
}capability_state
Read effective capability exposure and visible MCP tool names for caller scope.
Args:
{
group_id: string
actor_id?: string
by?: string
capability_id?: string // optional; returns capability_usage for this id
view?: "mcp_catalog" // reserved for actor-scoped MCP tool discovery
}The mcp_catalog view is read from one atomic Group snapshot without acquiring either outer lifecycle lock (Group or global). Managed runtimes can initialize their actor-scoped CCCC MCP server while actor_start is still materializing the provider, so serializing this internal catalog read behind that same lifecycle lock would deadlock provider startup. A queued global writer can also block a subsequent global read while waiting for startup to release its permit; therefore a global read permit is not a safe substitute. Catalog assembly reuses its captured Group snapshot for role and tool visibility decisions; authorization checks and capability-store synchronization still apply. All other capability_state reads retain normal Group read serialization; capability-store updates keep their own locking and are not relaxed by this view.
Ordinary Actor base tool exposure MUST share one definition with the native MCP fallback catalog. It includes cccc_connect, cccc_message_deliver and cccc_reply_request_cancel. Web Model additions and the Voice Secretary's restricted profile MUST also agree when daemon IPC is temporarily unavailable; fallback discovery does not grant daemon permissions or activate capability packs. User control tools and enabled packs retain their existing scope checks.
Native MCP admission applies a tool's published optional action default before permission, message classification and routing. JSON Schema defaults are annotations; clients are not required to insert them. Explicit actions remain unchanged. cccc_im_bind has no action selector and maps directly to im_bind_chat.
Result:
{
group_id: string
actor_id: string
default_profile: "core"
core_tool_count: number
visible_tool_count: number
visible_tools: string[]
dynamic_tools?: Array<{
name: string
description?: string
inputSchema: Record<string, unknown>
capability_id: string
real_tool_name: string
}>
dynamic_tool_limit: number
dynamic_tool_dropped: number
enabled_capabilities: string[]
active_capsule_skills?: Array<{
capability_id: string
name: string
description_short?: string
capsule_preview?: string
capsule_text?: string
source_id?: string
source_uri?: string
policy_level?: "indexed" | "mounted" | "enabled" | "pinned"
activation_sources?: Array<{
scope: "group" | "actor" | "session"
actor_id?: string
expires_at?: string
ttl_seconds?: number
}>
}>
autoload_skills?: Array<{
capability_id: string
name: string
description_short?: string
capsule_preview?: string
capsule_text?: string
source_id?: string
policy_level?: "indexed" | "mounted" | "enabled" | "pinned"
}>
autoload_capabilities?: string[]
actor_autoload_capabilities?: string[]
profile_autoload_capabilities?: string[]
actor_hidden_capabilities?: string[] // actor-level UI/menu hide preferences, including Web user slash menu; does not disable the capability
hidden_capabilities: Array<{
capability_id: string
reason: string
name?: string
description_short?: string
kind?: "mcp_toolpack" | "skill"
source_id?: string
policy_level?: "indexed" | "mounted" | "enabled" | "pinned" | "blocked"
state?: string
install_error_code?: string
install_error?: string
}>
external_binding_states?: Record<string, {
mode: "mcp" | "skill"
state: string
install_state?: string
artifact_id?: string
last_error?: string
last_error_code?: string
}>
precedence_chain: ["session", "actor", "group"]
session_bindings: Array<{
capability_id: string
expires_at: string
ttl_seconds: number
}>
source_states: Record<string, unknown>
blocked_capabilities?: Array<{
capability_id: string
scope: "group" | "global"
reason?: string
by?: string
blocked_at?: string
expires_at?: string
}>
capability_usage?: {
capability_id: string
used: boolean
group_enabled: boolean
group_actor_count: number
actor_enabled: Array<{ actor_id: string; actor_title?: string; label?: string }>
session_enabled: Array<{ actor_id: string; actor_title?: string; label?: string; expires_at: string; ttl_seconds: number }>
actor_autoload: Array<{ actor_id: string; actor_title?: string; label?: string }>
profile_autoload: Array<{ actor_id: string; actor_title?: string; label?: string; profile_id?: string; profile_name?: string }>
blocked: boolean
blocked_scope?: "group" | "global"
blocked_reason?: string
}
is_foreman: boolean
}Operational notes:
- Capability catalog is daemon-owned local state seeded from allowlist and runtime discoveries.
- Search uses local curated catalog + cached remote results; no periodic capability sync loop.
- Source gates:
CCCC_CAPABILITY_SOURCE_MCP_REGISTRY_ENABLED(default1)CCCC_CAPABILITY_SOURCE_ANTHROPIC_SKILLS_ENABLED(default1)github_skills_curatedis allowlist-curated (no periodic source crawler).agent_self_proposedis for agent-generated procedural skill candidates; default policy keeps MCP toolpacks indexed while allowing capsule skills to be validated and enabled at narrow scope.skillsmp_remoteis on-demand SkillsMP remote search (API key mode + proxy fallback).clawhub_remoteis on-demand ClawHub remote search (official API).openclaw_skills_remoteis on-demand OpenClaw GitHub corpus search.clawskills_remoteis on-demand clawskills.co index search.
- Dynamic tool exposure is capped by
CCCC_CAPABILITY_MAX_DYNAMIC_TOOLS_VISIBLE(default32). - Catalog snapshot size is capped by
CCCC_CAPABILITY_CATALOG_MAX_RECORDS(default20000); prune is applied during explicit sync operations. - Search may perform remote augmentation (MCP + skill) when local hits are insufficient:
CCCC_CAPABILITY_SEARCH_REMOTE_FALLBACK(default1)CCCC_CAPABILITY_SEARCH_REMOTE_FALLBACK_LIMIT(default40, max100)CCCC_CAPABILITY_SOURCE_SKILLSMP_REMOTE_ENABLED(default1)CCCC_CAPABILITY_SOURCE_CLAWHUB_REMOTE_ENABLED(default1)CCCC_CAPABILITY_SOURCE_OPENCLAW_SKILLS_REMOTE_ENABLED(default1)CCCC_CAPABILITY_SOURCE_CLAWSKILLS_REMOTE_ENABLED(default1)CCCC_CAPABILITY_SEARCH_REMOTE_SKILL_LIMIT(default follows remote fallback limit)CCCC_CAPABILITY_SEARCH_REMOTE_SKILLSMP_LIMIT(default follows remote fallback limit)CCCC_CAPABILITY_SEARCH_REMOTE_CLAWHUB_LIMIT(default follows remote fallback limit)CCCC_CAPABILITY_SEARCH_REMOTE_OPENCLAW_LIMIT(default follows remote fallback limit)CCCC_CAPABILITY_SEARCH_REMOTE_CLAWSKILLS_LIMIT(default follows remote fallback limit)CCCC_CAPABILITY_SKILLSMP_PROXY_BASE(defaulthttps://r.jina.ai/http://skillsmp.com/search)CCCC_CAPABILITY_SKILLSMP_API_BASE(defaulthttps://skillsmp.com/api/v1/skills/search)CCCC_CAPABILITY_SKILLSMP_API_KEY(optional; enables direct SkillsMP API)CCCC_CAPABILITY_CLAWHUB_API_BASE(defaulthttps://clawhub.ai/api/v1/skills)CCCC_CAPABILITY_CLAWSKILLS_DATA_URL(defaulthttps://clawskills.co/skills-data.js)
- Allowlist override env/path compatibility (
CCCC_CAPABILITY_ALLOWLIST_PATHandCCCC_HOME/config/capability-allowlist.yaml) is removed. Policy now always uses:- packaged default:
crates/cccc-daemon/resources/capability-allowlist.default.yaml - user overlay:
CCCC_HOME/config/capability-allowlist.user.yaml - effective policy: deterministic merge (
default <- overlay).
- packaged default:
- The authenticated local user control plane (
actor_id="user",by="user") includescccc_groupandcccc_actorin its baseline visible tools. Actor sessions do not inherit this user-only baseline and continue to requirepack:group-runtimeor their existing role/profile defaults.
capability_visibility
Hide or show a capability for one actor's UI/menu surfaces without changing enabled bindings. The Web UI uses actor_id="user" to control whether an enabled capsule skill appears in the / command menu.
Args:
{
group_id: string
by?: string
actor_id?: string // default: by or "user"
capability_id: string
hidden: boolean
reason?: string
}Result:
{
action_id: string
group_id: string
actor_id: string
capability_id: string
hidden: boolean
actor_hidden_capabilities: string[]
state: "hidden" | "visible"
}The Web slash-command adapter MUST keep slash_skill_dispatch.task_text non-empty. For a bare capsule command such as /cccc-self-evolution, it sends the canonical task Run the skill's default workflow.; explicit text after the command is forwarded unchanged. This keeps the existing daemon validation contract compatible across independently restarted Web and daemon processes.
capability_import
Import one normalized capability record prepared by the caller (agent-driven parsing), then optionally enable it.
Notes:
- This op does not parse arbitrary web/forum text; caller must provide structured
record. kind=mcp_toolpackrequiresinstall_mode+install_spec.kind=skillrequirescapsule_text.dry_run=truevalidates/probes only (no catalog persistence).command*andfallback_command*may be provided as top-level shortcuts; daemon copies them intoinstall_specwhen missing.record.source_idis optional; empty or unknown source ids are normalized tomanual_import.record.source_id=agent_self_proposedpreserves autonomous skill-proposal provenance. Default policy treatskind=skillcapsule records from this source as mounted, while non-skill toolpacks remain indexed unless policy explicitly promotes them.agent_self_proposedskill capsule text must include required proposal sections:When to use,Avoid when,Procedure,Pitfalls, andVerification; non-dry-run imports missing sections are rejected before catalog persistence so the last valid active record is preserved.agent_self_proposedskill capability ids must useskill:agent_self_proposed:<stable-slug>to avoid colliding with curated namespaces such asskill:anthropic:*orskill:github:*.- For low-risk, syntax-valid
agent_self_proposedcapsule skills, direct import is allowed. Usedry_run=truefirst when enabling immediately, scope/risk is unclear, or probe diagnostics are useful; high-risk candidates should be recorded asqualification_status=blockedwith explicitqualification_reasons. - Re-importing the same
capability_idupdates the catalog record. Agents should use that path for stale, incomplete, wrong, or duplicativeagent_self_proposedskills instead of creating near-duplicates or silently deleting records. - Import results use
import_action,record_changed,already_active, andactive_after_importto distinguish create/update/no-op and whether the target actor had an effective binding before and after import. Local sync timestamps do not count as semantic changes, while an explicitly suppliedupdated_at_sourcestill participates in the comparison.import_actionis the primary create/update/unchanged signal;record_changedonly compares existing records.already_activeis pre-import state;active_after_importis the post-import runnable binding. - If
readiness_preview.preview_status=activeoractive_after_import=true, agents must not re-enable the same skill just to refresh its capsule text. Usecapability_state.active_capsule_skills[].capsule_textfor full post-import verification;capsule_previewis only a compact display summary.
Args:
{
group_id: string
by?: string
actor_id?: string
record: {
capability_id: string // mcp:* or skill:*
kind: "mcp_toolpack" | "skill"
name?: string
description_short?: string
source_id?: string // optional; unknown/empty -> manual_import; agent_self_proposed preserves skill-proposal provenance
source_uri?: string
source_record_id?: string
source_record_version?: string
updated_at_source?: string
source_tier?: string
trust_tier?: string
qualification_status?: "qualified" | "unavailable" | "blocked"
qualification_reasons?: string[]
tags?: string[]
license?: string
install_mode?: "remote_only" | "package" | "command" // mcp_toolpack only
install_spec?: Record<string, unknown> // mcp_toolpack only
command?: string | string[] // command mode shortcut
command_candidates?: Array<string | string[]> // command mode/fallback candidates
fallback_command?: string | string[] // optional package->command fallback
fallback_command_candidates?: Array<string | string[]> // optional package->command fallback candidates
capsule_text?: string // skill only
requires_capabilities?: string[] // skill only
}
dry_run?: boolean // default false
probe?: boolean // default true
enable_after_import?: boolean // default false
scope?: "group" | "actor" | "session"
ttl_seconds?: number
reason?: string
}Result:
{
action_id: string
group_id: string
actor_id: string
capability_id: string
kind: "mcp_toolpack" | "skill"
dry_run: boolean
imported: boolean
scope: "group" | "actor" | "session"
import_action?: "created" | "updated" | "unchanged"
record_changed?: boolean
already_active?: boolean // target actor had an effective binding before optional enable_after_import
active_after_import?: boolean // target actor has a runnable binding after import/optional enablement
record: Record<string, unknown>
probe: {
state: "runnable" | "failed" | "skipped"
kind?: "mcp_toolpack" | "skill"
reason?: string
tool_count?: number
tool_names?: string[]
install_error_code?: string
install_error?: string
}
diagnostics: Array<{
code: string
message: string
retryable?: boolean
required_env?: string[]
action_hints?: string[]
}>
effective_policy_level: "indexed" | "mounted" | "enabled" | "pinned"
enableable_now: boolean
enable_block_reason?: "policy_level_indexed" | "qualification_blocked" | "capability_unavailable"
readiness_preview?: {
preview_status: "blocked" | "enableable" | "active" | "needs_inspect"
next_step: string
already_active?: boolean
preview_basis?: string[]
required_env?: string[]
missing_env?: string[]
cached_install_state?: string
install_error_code?: string
enable_block_reason?: "policy_level_indexed" | "qualification_blocked" | "capability_unavailable" | "missing_required_env"
policy_source?: "external_capability_safety_mode"
policy_mode?: "conservative"
}
enable_after_import: boolean
enable_result?: Record<string, unknown> // same shape family as capability_enable
refresh_required: boolean
state: "blocked" | "enableable" | "needs_inspect" | "activation_pending" | "runnable" | "verified"
reason?: string
}capability_allowlist_get
Read allowlist default/overlay/effective snapshots and revision hash.
Args:
{ by?: string } // write ops still enforce by=user; read is openResult:
{
default: Record<string, unknown>
overlay: Record<string, unknown>
effective: Record<string, unknown>
revision: string
default_source: string
overlay_source: string
overlay_error: string
policy_source: string
policy_error: string
external_capability_safety_mode: "normal" | "conservative"
}capability_allowlist_validate
Dry-run allowlist overlay validation (no persistence).
Args:
{
mode?: "patch" | "replace" // default: patch
patch?: Record<string, unknown> // required when mode=patch
overlay?: Record<string, unknown> // required when mode=replace
}Result:
{
valid: boolean
reason: string
default: Record<string, unknown>
overlay: Record<string, unknown>
effective: Record<string, unknown>
revision: string
external_capability_safety_mode: "normal" | "conservative"
}capability_allowlist_update
Persist allowlist overlay with optimistic concurrency.
Args:
{
by?: string // must be "user"
mode?: "patch" | "replace" // default: patch
expected_revision?: string
patch?: Record<string, unknown> // required when mode=patch
overlay?: Record<string, unknown> // required when mode=replace
}Result:
{
updated: true
revision: string
default: Record<string, unknown>
overlay: Record<string, unknown>
effective: Record<string, unknown>
policy_source: string
policy_error: string
external_capability_safety_mode: "normal" | "conservative"
}Errors:
allowlist_revision_mismatchallowlist_validation_failed
capability_allowlist_reset
Reset overlay to empty (removes CCCC_HOME/config/capability-allowlist.user.yaml when present).
Args:
{ by?: string } // must be "user"Result:
{
reset: true
removed_overlay_file: boolean
revision: string
default: Record<string, unknown>
overlay: Record<string, unknown>
effective: Record<string, unknown>
default_source: string
overlay_source: string
overlay_error: string
policy_source: string
policy_error: string
external_capability_safety_mode: "normal" | "conservative"
}capability_install_target
Install and enable either an existing capability id or one or more SKILL.md records from a local path, a direct HTTP(S) URL, or a GitHub repository. GitHub repositories import a root SKILL.md and files matching skills/*/SKILL.md (up to 64 records). Imported records retain their source, qualification, capsule, and installation metadata.
The native daemon commits catalog, binding, and actor slash-visibility state before appending one capability.changed event to the target Group ledger for the complete install batch. Semantically unchanged reinstalls do not append a duplicate event. Failed or rolled-back installs do not append one either. Event publication is a recoverable notification boundary: if the state commit succeeds but the ledger append fails, the operation remains successful and reports event_publish_error; Web clients catch up the authoritative slash-command capability view when their global event stream opens or reconnects.
Args:
{
group_id: string
target: string
actor_id?: string
by?: string
scope?: "actor" | "group" | "session"
ttl_seconds?: number
}Result:
{
action_id: string
group_id: string
actor_id: string
target: string
target_kind: "capability_id" | "local_path" | "url" | "github"
scope: "actor" | "group" | "session"
installed_capability_ids: string[]
enabled_capability_ids: string[]
use_ready_capability_ids: string[]
requires_setup: boolean
refresh_required: boolean // true only when the effective runtime/slash catalog changed
state: "ready" | "needs_setup"
event_publish_error?: string
}capability_uninstall
Revoke capability bindings for the target group, mark the capability removed from that group's catalog view, remove current-group actor autoload references, and remove runtime cache when no other group/actor bindings remain. The catalog record, block policy, other groups, and profile defaults are preserved. Use capability_source_delete for an explicit global deletion of records owned by a removable import source.
Args:
{
group_id: string
capability_id: string
reason?: string
by?: string
actor_id?: string
}Result:
{
action_id: string
group_id: string
actor_id: string
capability_id: string
state: "ready"
removed_record: boolean
removed_bindings: number
removed_blocked?: number
removed_group_marker: boolean
removed_installation: boolean
removed_runtime_bindings?: number
removed_recent_success?: boolean
removed_actor_autoload: number
removed_profile_autoload: number
cleanup_skipped_reason?: "cleanup_skipped_capability_still_bound"
refresh_required: boolean
refresh_mode?: "relist_or_reconnect"
wait?: "relist_or_reconnect"
}capability_source_delete
Explicitly delete every catalog record owned by a removable import source and clean its bindings, runtime state, actor autoload references, and profile defaults across all groups. Built-in and curated sources are protected. Only the user or a group foreman may perform this global operation.
Args:
{
group_id: string
source_id: "manual_import" | "agent_self_proposed" | "github_import" | "url_import" | "local_import"
reason?: string
by?: string
actor_id?: string
}Result:
{
group_id: string
actor_id: string
source_id: string
removed_records: number
removed_capability_ids: string[]
removed_runtime_bindings: number
removed_installations: number
removed_actor_autoload: number
removed_profile_autoload: number
}capability_tool_call
Invoke an enabled dynamic external capability tool by synthetic tool name.
Args:
{
group_id: string
actor_id?: string
by?: string
tool_name: string
arguments?: Record<string, unknown>
}Result:
{
tool_name: string
capability_id: string
result: Record<string, unknown>
}group_show
Args:
{ group_id: string; detail?: "summary" | "full" }Result:
{ group: Record<string, unknown> } // group.yaml content, redactedgroup_preamble_get
Read the effective group startup preamble. A non-empty group override replaces the built-in preamble body on the next preamble delivery; the fixed CCCC identity and protocol frame remains in place.
Args:
{ group_id: string }Result:
{
group_id: string
source: "builtin" | "home"
filename: "CCCC_PREAMBLE.md"
overridden: boolean
content: string
}group_preamble_set
Create or replace the non-empty group preamble override. The UTF-8 encoded content must not exceed 512 KiB. Existing sessions that have already received their preamble are not reinjected; start a fresh session when the new guidance must apply immediately. group_reset creates a new group id and does not carry this override forward, so provisioners must set the desired preamble on the replacement group before starting its actors. This operation manages prompt content only; consumers requiring a distinct standby turn must observe the actor return to waiting or idle before sending the authoritative mission.
Args:
{ group_id: string; content: string; by?: string }Result: the group_preamble_get result plus changed: boolean. When changed is false, the stored override is not rewritten.
group_preamble_reset
Delete the group override and restore the built-in preamble body. The explicit confirmation avoids accidental removal.
Args:
{ group_id: string; confirm: "preamble"; by?: string }Result: the group_preamble_get result plus changed: boolean.
group_help_get
Read the effective group collaboration reference. The built-in ## Canonical Message Delivery section is always authoritative and is composed with the group's CCCC_HELP.md as an additive overlay; an overlay section with the same heading is ignored. When actor_id is supplied, the daemon MUST apply the document's ## @role:, ## @actor:, and ## @voice_secretary visibility rules before returning markdown. Runtime-only MCP addenda are outside this operation and MAY be appended by the MCP adapter. user and the foreman may request any actor's effective help; a peer may request only its own actor view and MUST NOT use this operation to read another actor's scoped note.
Args:
{ group_id: string; actor_id?: string; by?: string }Result:
{
group_id: string
actor_id: string | null
source: "builtin" | "home"
source_path: string
filename: "CCCC_HELP.md"
overridden: boolean
markdown: string
}actor_notes_get
Read actor-scoped notes from ## @actor: <actor_id> blocks in the canonical group CCCC_HELP.md. user and the foreman may read any actor or omit target_actor_id to list all notes. A peer MUST provide its own actor id and MUST NOT read another actor's note.
Args:
{ group_id: string; target_actor_id?: string; by?: string }Result when a target is supplied:
{
target_actor_id: string
content: string
source: "builtin" | "home"
path: string
}Result when listing:
{
actor_notes: Array<{ actor_id: string; content: string }>
source: "builtin" | "home"
path: string
}actor_notes_set
Create or replace one existing actor's scoped note in the canonical group help document. Only user or the foreman may mutate actor notes. The daemon MUST preserve common, role, other-actor, Voice Secretary, and unknown tagged blocks, write atomically, and MUST NOT create a Context or actor-record copy.
Args:
{ group_id: string; target_actor_id: string; content: string; by?: string }Result: the targeted actor_notes_get result plus changed: boolean.
actor_notes_clear
Remove one existing actor's scoped note without changing other help content. Permission and preservation rules are identical to actor_notes_set.
Args:
{ group_id: string; target_actor_id: string; by?: string }Result: the targeted actor_notes_get result plus changed: boolean.
group_create
Args:
{ title?: string; topic?: string; by?: string }Result:
{ group_id: string; title?: string; event?: CCCSEventV1 }group_update
Args:
{ group_id: string; by?: string; patch: { title?: string; topic?: string } }Result:
{ group_id: string; group: Record<string, unknown>; event: CCCSEventV1 }group_delete
Args:
{ group_id: string; by?: string }Result:
{ group_id: string }Notes:
- Successful deletion MUST retire all Direct records and disposable catalogs owned by the deleted Group, preserve retired IDs, and release their relation quota. Reset uses the same deletion cleanup; it MUST NOT transfer those grants to the replacement Group.
- Successful deletion MUST retire every Web Model conversation binding and pending pairing belonging to the deleted Group, without revoking the shared connector or other Groups’ authority. A failure that leaves the group registered and available MUST preserve its pre-delete connector authority.
- Successful deletion MUST retire every local external-space binding, queued job, and referenced job payload owned by the deleted group. It MUST NOT delete the user's remote notebook or other provider space. A failure that leaves the group registered and available MUST restore the pre-delete local binding and queue state.
group_use
Set the active scope for a group using path (must already be attached).
Args:
{ group_id: string; path: string; by?: string }Result:
{ group_id: string; active_scope_key: string; event: CCCSEventV1 }Workspace Web clients must treat group.set_active_scope, group.attach, and group.detach_scope as invalidating their active workspace view and reconcile the current Group document, rather than applying historical event scope fields. The Web workspace path/list/read/write/content requests bind scope_key and scope_url to that document; JSON file reads return both values and saves echo the opened identity. Missing identity is rejected with HTTP 400, and a changed key or attached URL with HTTP 409 (workspace_scope_changed), before resolving the relative path. The checked Group snapshot owns the entire filesystem operation; a subsequent scope switch cannot retarget an in-flight write. The digest detects content changes within that workspace and does not establish workspace identity.
GET /api/v1/groups/{group_id}/workspace/path resolves an existing path under the same scope and access checks, returning {scope_key, scope_url, path, is_dir}. The returned path is canonical and workspace-relative; an empty path names the root. The endpoint reads metadata only. Web opens files in the viewer and reveals folders in the tree without replacing the current file or draft. Missing paths, wrong path types, and escaped paths remain distinct errors; only a boundary violation is outside_scope. This lookup creates no daemon work or ledger event.
Workspace listings mark symbolic links with is_symlink: true. An inaccessible entry carries unavailable: missing, outside_scope, unreadable, or unsupported (not a regular file or directory). External link targets and their metadata are not disclosed. This is a listing-time observation; reads and downloads still validate the scope and path independently. Web keeps path-copy actions available but disables opening, downloading, attaching and pinning an unavailable entry. Refreshing the directory reevaluates its availability.
GET /api/v1/groups/{group_id}/workspace/content reads original file bytes with path, scope_key, and scope_url, under the same Group/exhibit and Connect-frame authorization as workspace text reads. download=true forces an attachment. It supports HEAD and single byte ranges (206/416), without the JSON text limit or whole-file buffering. Requests containing If-Range receive the current full representation because this mutable-file endpoint exposes no strong validator. Every subsequent range request rechecks scope and access; an already admitted response retains its opened file. Responses use no-store and nosniff; inline raw responses are limited to images, audio, video and PDF. SVG remains sandboxed; PDF uses its exact MIME without CSP sandbox, allowing the native browser PDF viewer. Content-Disposition supplies the original safe filename for inline viewing and explicit downloads. Other raw content is downloaded, never rendered as active same-origin HTML. Text-sized Markdown, tables and static HTML are rendered by Web from the existing bounded text read; HTML remains scriptless and scoped resource URLs retain authorization and workspace identity. Media support does not create a daemon operation, ledger event, cloud copy or transcoder.
Workspace mime_type is a filename-derived hint, not proof of media content. JSON reads detect binary content independently; above the 1 MiB text limit they sample at most 8 KiB, accepting a UTF-8 character split at the sample boundary, and retain truncated: true without inlining content or a save digest. Web uses this text/binary result before choosing an audio or video player, so TypeScript .ts/.mts and text playlists remain text while binary transport streams retain media preview. Oversized text keeps the existing size-limit notice and download. SVG and PDF retain their dedicated previews. A rename updates the MIME hint while retaining the opened content classification and any unsaved draft.
Workspace management uses the same Group, exhibit, scope and Connect-frame checks. Paths must be exactly representable as UTF-8. Listings with non-UTF-8 entry names and resolutions to non-UTF-8 canonical paths fail explicitly; lossy conversion must not publish another entry's identity. Removing a UTF-8-named link remains an entry operation and does not require a readable target. POST /api/v1/groups/{group_id}/workspace/entries accepts scope_key, scope_url and one typed operation: {operation: "create", path, directory: boolean}, {operation: "move", path, destination}, or {operation: "delete", path}. Paths are workspace-relative. Creation and move never overwrite an existing entry; collisions return HTTP 409 (workspace_entry_exists). Moves require an existing parent and native exclusive-rename support; there is no copy/delete or overwrite fallback. Successful responses return the normalized entry path, plus destination and its path-derived mime_type for a move. Web applies the returned MIME to the moved file in both the visible editor and cached drafts without reloading unsaved contents; moving a folder preserves its descendants' file types. Entry operations resolve the parent under the scope, retaining the final symlink itself: moving or removing a link does not move or remove its target. Workspace-root and Git-metadata mutations are rejected. Directory removal is recursive and permanent, without following contained links. A failed recursive deletion may have removed some entries; Web retains drafts and refreshes the tree rather than claiming rollback. Text saves and these operations share process-local serialization; this does not lock out external programs.
POST /api/v1/groups/{group_id}/workspace/upload takes scope_key, scope_url, path and bytes in its query and raw file bytes in its body. The declared and received lengths must match and must not exceed 100 MiB. An owned temporary file in the destination directory is published without replacement only after receipt, flush, and a fresh scope/token/Connect-frame check. Failed or canceled requests clean up their temporary file relative to the originally opened parent directory, even if another request or external program has moved that directory. Publication checks the staged file's identity as well as its destination; a recreated old path cannot redirect the upload or its cleanup. Multi-file/folder uploads are sequential browser batches, limited to 1,000 entries and 100 MiB total; completed entries remain when later entries fail or the user stops. Existing directories are not implicitly merged. Web keeps uploads separate from composer attachments. Tree moves use the same entry operation as the menu, and successful moves transfer affected drafts to the new paths. An internal drag carries a one-use random token issued by the current Files panel; its entry identity remains in that panel's memory. Drop/end, scope changes and unmount retire it. Self-reported origin or workspace metadata from a foreign page does not authorize a move.
GET /api/v1/groups/{group_id}/workspace/changes takes the scope identity and returns {repository, branch, entries, limited}. Entries contain workspace-relative path, index/worktree status characters, optional in-scope previous_path, and untracked, conflicted, directory flags. Native Git status is bounded to the active workspace, including when it is a repository subdirectory. A non-repository is distinct from a failed or timed-out query. At most 2,000 entries are returned; limited explicitly marks a shortened list. Queries are user-driven, without background scans or daemon work.
GET /api/v1/groups/{group_id}/workspace/diff additionally takes path and side: "worktree" | "staged", returning {patch, limited}. Only a currently listed change can be selected. Worktree compares saved bytes with the index; staged compares the index with HEAD, including an unborn branch. Untracked/conflicted entries open in Files. Git uses literal pathspecs, disables external diff/text conversion and rename expansion, and never changes the index. A renamed file can appear as an addition/deletion in its patch to avoid pulling an out-of-scope source into the view. Output is limited to 1 MiB/10,000 lines per diff, with an explicit limit result rather than a partial patch. Binary or metadata-only changes remain visible as Git text. No stage, unstage, discard, commit, branch-switch, pull or push operation is exposed.
group_detach_scope
Args:
{ group_id: string; scope_key: string; by?: string }Result:
{ group_id: string; event: CCCSEventV1 }group_set_state
Args:
{ group_id: string; state: "active" | "idle" | "paused"; by?: string }Notes:
stoppedis not a validgroup_set_statevalue in daemon IPC v1.- Higher-level surfaces (CLI/MCP) MAY expose
stoppedas a convenience alias that maps togroup_stop. - While a group remains
paused, the daemon MUST NOT submit queuedchat.messageorsystem.notifywork to actor runtimes. A user-authored Send or Request Reply is an explicit use action: it MUST first resume the group toactive, enable its addressed actors, and then deliver through the normal runtime path. Mail does not resume the group. Canonical unread work remains in the ledger and MAY be surfaced through one bounded recovery notice after the group returns toactiveoridle.
Result:
{ group_id: string; state: string; event: CCCSEventV1 }group_settings_update
Update group-scoped messaging/automation/delivery/transcript settings.
Args:
{ group_id: string; by?: string; patch: Record<string, unknown> }Patch keys used by CCCC include:
- Messaging:
default_send_to - Delivery:
mail_notice_after_seconds(default 1800, zero disables),reply_notice_after_seconds(default 900, zero disables) - Automation:
actor_idle_timeout_seconds,keepalive_delay_seconds,keepalive_max_per_actor,silence_timeout_seconds,help_nudge_interval_seconds,help_nudge_min_messages - Terminal transcript:
terminal_transcript_visibility,terminal_transcript_notify_tail,terminal_transcript_notify_lines
Result:
{ group_id: string; settings: Record<string, unknown>; event: CCCSEventV1 }assistant_state
Read the group-scoped state for first-party built-in assistants. Voice Secretary service-local ASR runs in-process through the Rust sherpa-onnx binding. The native runtime is linked into the CCCC binary; model weights remain explicit, checksummed downloads under CCCC_HOME/cache/voice-models.
Voice Secretary configuration (enabled and config) remains in group.yaml:assistants.voice_secretary. Durable workflow records live in groups/<group_id>/state/assistants.json: lifecycle, durable health, sessions, prompt drafts/requests, and ask requests. Process observations such as PID, port, live service/socket state, and actor handles MUST NOT be persisted there. Implementations MUST preserve the reserved rust_state object when updating the common records. A legacy Rust workflow embedded in group.yaml:assistants is imported canonical-first; after the canonical file commits, only assistant configuration remains in group.yaml.
Args:
{
group_id: string
assistant_id?: "voice_secretary"
view?: "voice_session" | string
session_id?: string
document_path?: string
suppress_retry_notify?: boolean
}view="voice_session" is the canonical session projection used by the Web meeting view. With session_id, it returns that document-capture session. Without session_id, document_path first resolves the durable cross-session transcript at $CCCC_HOME/voice-secretary/<group_id>/documents/<document_id>/transcript.jsonl; when no document transcript exists, it falls back to the latest matching session in state/assistants.json. Prompt-refinement, composer, and instruction semantic inputs are never projected as meeting transcript. The document transcript projection has source="document_transcript" and may aggregate rows from several recording sessions.
Specialized result for view="voice_session":
{
group_id: string
session: {
session_id: string
capture_mode: "document"
document_path?: string
status?: string
segments: Array<Record<string, unknown>>
transcript?: string
diarization?: Record<string, unknown>
source?: "document_transcript"
} | Record<string, never>
}Result:
{
group_id: string
assistants?: Array<Record<string, unknown>>
assistants_by_id?: Record<string, unknown>
assistant?: Record<string, unknown>
proposals?: Array<Record<string, unknown>>
proposals_by_id?: Record<string, unknown>
documents?: Array<Record<string, unknown>>
documents_by_path?: Record<string, unknown>
active_document_path?: string
capture_target_document_path?: string
documents_by_id?: Record<string, unknown> // daemon sidecar/internal compatibility only
active_document_id?: string // daemon sidecar/internal compatibility only
capture_target_document_id?: string // daemon sidecar/internal compatibility only
new_input_available?: boolean
service_runtime?: Record<string, unknown>
service_models?: Array<Record<string, unknown>>
service_models_by_id?: Record<string, unknown>
}service_runtime is read-only engine metadata with the stable runtime ID sherpa_onnx_streaming, readiness, and the linked sherpa-onnx version. The engine ships inside the CCCC executable and has no independent install/remove lifecycle; only voice models are downloaded or removed. Voice model records may include installed_manifest_sha256, update_available, last_update_error, and artifact source fields (url, sha256, archive) so model updates remain explicit and inspectable.
assistant_settings_update
Update group-scoped built-in assistant settings.
When voice_secretary.enabled=true, the daemon also materializes a hidden internal actor with internal_kind="voice_secretary" and actor_id="voice-secretary". That actor is a distinct assistant identity, not the foreman and not a normal peer. Its startup runtime config (Runtime-derived execution surface, command, env/secrets, scope, submit behavior) is copied from the current stable foreman actor so the user does not configure a second runtime profile. The foreman's enabled/running state does not affect assistant config inheritance. If no foreman actor exists, enabling Voice Secretary fails. An explicit enabled=true request also enables the internal actor and, if the group is already running, starts it as needed. Saving configuration alone MUST NOT start a stopped actor. Startup confirmation and health.actor.running MUST use the runtime owner's live state; a managed session does not return a traditional PTY launch status, and a retained error record does not prove it is running. Structured external runtimes retain their enabled/Group-running semantics. A failed start rolls back assistant settings, actor configuration, and private env. Disabling Voice Secretary stops/removes the actor and its private env.
Args:
{
group_id: string
by?: string
assistant_id: "voice_secretary"
patch: {
enabled?: boolean
config?: {
capture_mode?: "browser" | "service"
recognition_backend?: "mock" | "assistant_service_local_asr" | "browser_asr" | "external_provider_asr"
recognition_language?: "auto" | string
retention_ttl_seconds?: number
auto_document_enabled?: boolean
document_default_dir?: string
auto_document_quiet_ms?: number
auto_document_min_chars?: number
auto_document_max_window_seconds?: number
service_model_id?: string
tts_enabled?: boolean
}
}
}browser_asr means browser-managed speech recognition and does not guarantee browser-device-local model execution. assistant_service_local_asr means ASR runs on the daemon host through native Rust and uses an installed local ASR model. The returned assistant health may include health.service with status, alive, ready, selected_model_id, model, runtime, and streaming_backend so Web can show whether service-local ASR is actually usable. service_model_id is optional and selects a daemon-managed local ASR model for on-demand install/use. recognition_language="auto" means the browser/client chooses the best language hint; otherwise callers should pass a BCP-47-like tag such as zh-CN, en-US, or ja-JP. auto_document_enabled=true is the default path: stable transcript segments are compacted into the Voice Secretary input stream, then the voice-secretary runtime actor pulls unread input and edits the working markdown document directly in the repository. auto_document_quiet_ms is the client silence window before flushing speech into that semantic lane; auto_document_min_chars and auto_document_max_window_seconds are daemon-side guardrails that keep long continuous speech from waiting forever for a pause. The runtime actor should treat transcript as source material for evidence-bounded reconstruction: it may use transcript, group context, existing documents, common knowledge, and verified lightweight research to produce a coherent artifact, but must not fabricate facts and should compactly mark low-confidence entities, numbers, quotations, or dates. The document loop should be incremental and non-lossy: each unread input batch should be organized into the best current document structure while preserving useful concrete details, and idle review should refine/reorganize/enrich rather than replace detail-rich material with a short executive summary. The daemon does not track per-job completion: it stores an input cursor, nudges the actor when unread input exists, and sends idle-review nudges only on recording stop or after enough new transcript input plus the group cooldown (default: stop flush immediately, otherwise 8 new transcript input flushes and at least 5 minutes since the previous idle review). If the group has an active workspace scope, document_default_dir (default docs/voice-secretary) is resolved under that workspace; otherwise the daemon falls back to CCCC_HOME. Raw transcript/source/input sidecars stay in CCCC_HOME. external_provider_asr must remain explicit opt-in.
The semantic input authority is $CCCC_HOME/voice-secretary/<group_id>/input_events.jsonl; its daemon-owned read/delivery cursor and retry timing live in the sibling input_state.json. Implementations MUST NOT maintain an engine-private sequence or cursor. The former Rust inputs.jsonl and groups/<group_id>/state/assistants.json:rust_state.input_* shape is a one-way migration source: canonical input/state commit first, then the legacy log and cursor fields are retired. If independently written streams must be merged, migration may conservatively replay an already-read item but MUST NOT advance across or skip an unread item.
Result:
{ group_id: string; assistant: Record<string, unknown>; event: CCCSEventV1 }assistant_voice_model_install
Download and verify a daemon-managed local Voice Secretary ASR model into CCCC-owned cache storage. Built-in releases include a default model manifest; tests and local development may add a local overlay at CCCC_HOME/config/voice-models.json. Each artifact entry must include a fixed URL and sha256. Reinstalling/updating a model downloads into staging storage and replaces the active model only after all artifacts verify successfully.
Args:
{
group_id: string
by?: string
model_id: string
}Result:
{
group_id: string
assistant: Record<string, unknown>
model: {
model_id: string
status: "not_installed" | "downloading" | "ready" | "failed" | "unknown"
install_dir?: string
installed_at?: string
updated_at?: string
error?: Record<string, unknown>
update_available?: boolean
installed_manifest_sha256?: string
}
}HTTP Voice Secretary transcription
Transcribe a push-to-talk audio payload through the daemon-managed first-party Voice Secretary runtime. This endpoint only returns transcript text and service health; it does not create a chat message, proposal, or working document by itself. Call assistant_voice_transcript_append after transcription so the daemon can append stable transcript source material and update the current working document.
Request:
POST /api/v1/groups/{group_id}/assistants/voice_secretary/transcriptions
?language={language}&by={actor_id}
Content-Type: audio/pcm | audio/wav | application/octet-stream
<streamed binary audio body>Preconditions:
voice_secretaryis enabled for the group.recognition_backendisassistant_service_local_asr.- The selected offline
service_model_idis installed and its manifest exposes a supported sherpa-onnx model configuration. HTTP transcription accepts mono PCM16 or WAV up to 100 MiB. The HTTP body and WebSocket PCM16 frames are streamed to auto-deleted temporary files; browser service capture sends binary PCM16 WebSocket frames.
Result:
{
group_id: string
assistant: Record<string, unknown>
transcript: string
mime_type: string
language?: string
bytes?: number
backend: "assistant_service_local_asr"
service: Record<string, unknown>
asr?: Record<string, unknown>
}WebSocket Voice Secretary transcription
The service-local ASR browser transport keeps one recording lease and one microphone capture active while raw PCM is rolled into bounded server-side files:
GET /api/v1/groups/{group_id}/assistants/voice_secretary/transcriptions/ws
?owner_id={owner_id}&lease_id={lease_id}After upgrade, the client sends a JSON start command, then 16 kHz mono PCM16 binary frames, and finally a JSON stop command. The server returns a ready event whose recording_segment_duration_ms is currently 1500000. Whenever a full segment has been flushed and data-synced, the server emits:
{
type: "recording_segment_saved"
ok: true
seq: number
segment_index: number
start_ms: number
end_ms: number
duration_ms: number
bytes: number
}Segment rollover MUST NOT stop live recognition or require a new microphone capture. Rust stores 48,000,000 PCM bytes per segment (25 minutes) and caps one WebSocket session at 800 MiB (about 7 hours 17 minutes). On stop or an unexpected disconnect, persistent recordings longer than 30 seconds MUST defer final transcription when speaker analysis is available. Short persistent recordings MAY run immediate final ASR. Final ASR paths that cannot defer MUST process segment files sequentially and reuse one offline recognizer per recording segment across inference ranges no longer than 30 seconds. When final ASR is deferred because the recording is long or the native inference worker is occupied, WebSocket stop MUST complete promptly with final_asr_status.status set to deferred_to_speaker_analysis, retain the durable live transcript, and queue speaker analysis; temporary worker occupancy MUST NOT permanently skip speaker analysis or retain the recording lease. A final ASR path that cannot defer but finds the native inference worker occupied MUST bound its wait well below the recording lease TTL and complete stop with an asr_busy final_asr_text error if the worker stays occupied, so a queued stop never outlives its lease. HTTP upload transcription MAY retain a fail-fast busy response. The final_asr_text event keeps the combined text in timeline order and includes a segments array with each inference range's status and its owning recording_segment_index. If at least one range succeeds and another fails, the event keeps ok=true so the available text is retained, and MUST also report partial=true plus failed_segment_count; clients MUST surface that incompleteness rather than presenting the text as a complete transcript. Speaker analysis is likewise sequential so native diarization holds at most one segment waveform at a time. Multi-segment speaker results MUST NOT imply cross-segment identity matching; Rust marks them with speaker_identity_scope="recording_segment".
assistant_voice_recording_lease
Acquire, refresh, release, or inspect the daemon-owned Voice Secretary recording lease. Web clients may keep a local browser lock for fast UX debouncing, but the daemon lease is the final cross-tab / cross-browser / cross-device guard that prevents two Voice Secretary recording streams from running at the same time. The lease is TTL-based so a crashed tab or disconnected browser eventually expires without manual cleanup.
The service-local ASR WebSocket requires the active owner_id and lease_id as query parameters and revalidates them while audio is streaming. Opening the transcription WebSocket directly cannot bypass the daemon lease. Lease mutations match group_id, owner_id, and lease_id; public status and conflict payloads redact lease_id. The stable browser owner identifies the lease holder, while every recording uses a fresh session_id.
Args:
{
group_id: string
by?: string
action: "acquire" | "heartbeat" | "release" | "status"
owner_id?: string // required for acquire/heartbeat/release
lease_id?: string // returned by acquire; required to refresh/release that acquisition
ttl_seconds?: number // default 30; bounded by the daemon
capture_mode?: string
recognition_backend?: string
dispatch_target?: string
}Result:
{
group_id: string
action: string
acquired: boolean
released: boolean
lost: boolean
lease_id?: string // only returned to the acquiring/refreshing owner
lease?: {
owner_id: string
group_id: string
group_title?: string
capture_mode?: string
recognition_backend?: string
dispatch_target?: string
by?: string
created_at?: string
updated_at?: string
expires_at?: string
}
}If another live lease exists, acquire / heartbeat returns assistant_voice_recording_busy with details.active_lease. Every successful acquire creates a fresh lease_id, including when the owner_id matches the active lease, so cleanup from an older connection cannot release its replacement. heartbeat only refreshes the matching active owner_id + lease_id; it never creates a new lease. Stale heartbeat / release requests return lost or released=false without modifying a newer lease. An omitted heartbeat metadata field preserves the value from the active lease. The transcription WebSocket binds its start frame to the lease's capture_mode, recognition_backend, and dispatch_target; changing capture scope requires a new lease.
The daemon MUST serialize lease mutations and every read that may expire and clear the lease through $CCCC_HOME/state/voice_secretary_recording_lease.json.lock. A process-local lock alone is insufficient because daemon and Web processes may use the same home. acquire and heartbeat MAY operate while Voice Secretary is disabled only when the effective dispatch_target is composer; an omitted heartbeat target inherits the active lease target. This direct-dictation path MUST NOT create Voice Secretary input, session, document, or diarization state.
assistant_voice_transcript_append
Append a stable transcript segment for Voice Secretary. Web/browser ASR and service-local ASR converge here. The daemon writes stable segments to $CCCC_HOME/voice-secretary/<group_id>/<session_id>/transcripts/segments.jsonl, updates the bounded shared session projection in groups/<group_id>/state/assistants.json, appends final document-capture rows to $CCCC_HOME/voice-secretary/<group_id>/documents/<document_id>/transcript.jsonl, and by default appends a semantic input event for the current Voice Secretary markdown working document. The working document is a user-facing repo artifact; raw transcript/source/revision sidecars remain in CCCC_HOME. When new input is available, the daemon emits a targeted system.notify to voice-secretary with context.kind="voice_secretary_input" and a daemon-owned input_envelope. The envelope is the canonical work item delivered to every actor runtime; assistant_voice_document_input_read / cccc_voice_secretary_document(action="read_new_input") remains a legacy, recovery, and debugging entrypoint. Input append is durable before runtime actor wake-up; if wake-up fails, the input remains readable and the API reports the best-effort wake error separately. If wake-up succeeds after the notify was created while the actor was stopped, the daemon re-dispatches that same notify through the actor's normal runtime input so lazy startup instructions are included.
The group operation validates or creates the Markdown target before committing transcript/session/input state. Retrying the same session_id and segment_id is idempotent for the stable session, document transcript, and semantic input records. Document paths must be repository-relative .md paths and must not traverse symbolic links.
Idempotency is checked against the complete semantic input log, not the bounded session display window. If the input log was committed but its ledger input or notify event was interrupted, retrying the same segment reuses the canonical input record and completes only the missing delivery work.
Transcript producers MAY attach the following revision metadata:
transcript_stage:liveorfinal. When omitted, the daemon infersfinalonly fromtrigger.recognition_backend=assistant_service_local_asr_final; other legacy segments are treated aslive.supersedes_segment_ids: segment IDs in the same session that the new record replaces in the current projection.supersede_stage: "live": daemon shorthand for a stablefinalsegment (is_final=true) that resolves all current live segment IDs in the session intosupersedes_segment_idsbefore committing and keeps race-late live checkpoints in that session raw-only.source_model_id: the producer model identity retained with the raw record.revision_only: true: valid only for a stablefinalsegment (is_final=true). The daemon retains this marker with the raw segment and updates transcript projection. Under the input-state lock it MUST reuse an existing ASR input for that session; when none exists and automatic document input is enabled, it MUST create and deliver the final segment as the session's first semantic input.
Revision records are append-only. The daemon MUST retain superseded live rows in session and document transcript sidecars, while the bounded session transcript projection and Web meeting view omit them. A normal document-mode WebSocket stop and a successfully finalized disconnect MUST persist the final SenseVoice result as a revision-only record superseding the session's live checkpoints. A partial final result MUST NOT supersede live checkpoints. A document-mode final_asr_text reports transcript_persistence as persisted, skipped_partial, or failed, with transcript_persisted carrying the boolean commit result. Callers MUST retain or retry their fallback text until persistence is confirmed. If final ASR fails, the existing live projection remains valid.
The public document identity for Voice Secretary APIs is document_path, a repository-relative markdown path. document_id may exist in daemon sidecar state as an implementation detail, but runtime actors and Web clients should route by document_path.
Repository markdown is the document-content authority. The canonical document registry and active selection live at $CCCC_HOME/voice-secretary/<group_id>/documents/index.json; implementations serialize mutations with the sibling index.json.lock. The former Rust groups/<group_id>/state/assistants.json:rust_state.documents/active_document_* shape is a one-way migration source. Canonical index entries win path conflicts, unique legacy entries are retained, and the legacy fields are removed only after the canonical index commits. Implementations MUST NOT keep an engine-private active-document selection.
assistant_index, assistant_voice_document_list, and assistant_voice_document_select reconcile repository Markdown edits into the daemon document index before returning. Reconciliation also discovers previously unindexed .md files under the effective document_default_dir, as runtime actors may create working documents directly in the repository; an archived or deleted indexed path MUST NOT be rediscovered as active. Reconciliation updates content, hash, character count, and revision only when file content changed. Missing files do not clear indexed content, and path/symbolic-link validation is applied before reading. The emitted assistant.voice.document reconciliation event is an auxiliary signal; index persistence and ledger append are not one atomic transaction.
Args:
{
group_id: string
by?: string
session_id: string
segment_id?: string
text?: string
language?: string
document_path?: string
is_final?: boolean
flush?: boolean
trigger?: {
trigger_kind?: "push_to_talk_stop" | "service_transcript" | "meeting_window"
mode?: "dictation" | "meeting"
capture_mode?: "browser" | "service" | string
recognition_backend?: string
client_session_id?: string
input_device_label?: string
language?: string
}
}Result:
{
group_id: string
assistant: Record<string, unknown>
session_id: string
segment?: Record<string, unknown>
segment_path?: string
document?: Record<string, unknown>
document_updated: boolean
input_event?: Record<string, unknown>
input_event_created: boolean
input_notify_emitted: boolean
input_notify_error?: string
actor_woken?: boolean
actor_wake_error?: string
actor_notify_delivered?: boolean
actor_notify_delivery_error?: string
}assistant_voice_session_update
Persist a Web-owned completion projection (currently speaker diarization) into the canonical session authority. With completion_event, the daemon then appends the matching assistant.voice.session event under the same Group write permit. Web MUST NOT append the completion directly to the ledger. This is an internal daemon boundary used by browser capture; callers do not replace transcript segments through this operation.
Voice-session mutation is limited to the user, the assistant:voice_secretary principal, or a foreman allowed to update group settings. A session_id used for filesystem-backed state MUST be canonicalized to one safe path component or rejected before any state or filesystem mutation; caller-controlled absolute paths and . / .. components MUST never be joined into the Voice Secretary storage root.
Args:
{
group_id: string
session_id: string
by?: "assistant:voice_secretary" | string
completion_event?: "diarization_ready" | "diarization_failed"
patch: {
status?: string
document_path?: string
audio_duration_ms?: number
diarization_ready?: boolean
diarization_artifact_path?: string
diarization?: Record<string, unknown>
diarization_error?: Record<string, unknown>
error?: Record<string, unknown> | null
latest_partial?: string
}
}Result:
{ group_id: string; session: Record<string, unknown>; completion_event_id?: string }When completion_event is supplied, patch.status MUST be closed and patch.diarization_ready MUST match the outcome. Invalid combinations MUST fail before state mutation. Omitting it preserves projection-only updates. An append failure MUST be reported even when session state was already saved; repeating the same completion is safe and emits at most one event per Group/session/outcome. A successful completion response MUST include its completion_event_id; a projection-only response from an older daemon is not completion confirmation. The Web completion caller retries transient I/O or unknown transport outcomes at most four times, then reports failure. This does not promise an atomic transaction across the state file and ledger, nor recovery after the Web host exits.
assistant_voice_session_transcript_clear
Clear the selected session display transcript and the matching durable document transcript. Document Markdown content is not deleted.
Args:
{ group_id: string; session_id?: string; document_path?: string; by?: string }Result:
{ group_id: string; session_id: string; cleared: boolean }assistant_voice_document_list
List active Voice Secretary working documents for the group. Archived documents are excluded unless include_archived=true.
Args:
{ group_id: string; include_archived?: boolean }Result:
{
group_id: string
documents: Array<Record<string, unknown>>
documents_by_id: Record<string, unknown>
documents_by_path: Record<string, unknown>
active_document_id?: string
capture_target_document_id?: string
active_document_path?: string
capture_target_document_path?: string
}assistant_voice_document_input_read
Read all unread Voice Secretary input events since the actor's last successful read. Reading advances the daemon-managed cursor immediately; the actor does not see or manage cursor/sequence values. This intentionally avoids a separate job-completion protocol. If the actor crashes after reading, the raw input log remains in CCCC_HOME for debugging/replay, but the normal live cursor has moved.
Args:
{ group_id: string; by?: "voice-secretary" | "assistant:voice_secretary" }Result:
{
group_id: string
item_count: number
document_count: number
input_text: string
input_batches: Array<{
document_path: string
filename?: string
title?: string
item_count: number
kinds?: string[]
intent_hints?: string[]
languages?: string[]
sources?: string[]
}>
documents: Array<Record<string, unknown>>
has_new_input: boolean
}assistant_voice_document_save
Save or create a Voice Secretary working markdown document. This is the daemon path used by Web when the user edits the document surface. The voice-secretary actor should normally edit repository-backed markdown directly at document_path; the MCP document tool intentionally has no save action. When content is omitted for an unindexed path, an implementation MUST NOT rewrite an existing repository file: it MAY read the file into the document index or reject the request. An empty file MAY be created only when the target does not already exist.
Args:
{
group_id: string
by?: string
document_path?: string
workspace_path?: string
title?: string
content?: string
status?: "active" | "archived"
create_new?: boolean
}Result:
{ group_id: string; document: Record<string, unknown>; event: CCCSEventV1 }assistant_voice_document_instruction
Append a user instruction for one active working document into the same Voice Secretary input stream used for ASR transcript. The daemon emits a targeted voice_secretary_input notify and the runtime actor works from the inline input_envelope. The daemon does not directly append the instruction to a document. Cross-peer handoff is intentionally handled only by assistant_voice_request, and only when the Voice Secretary decides the work belongs to foreman or one concrete peer.
Args:
{
group_id: string
by?: string
document_path: string
request_id?: string
input_append_id?: string
instruction?: string
source_text?: string
trigger?: Record<string, unknown>
}Result:
{
group_id: string
assistant?: Record<string, unknown>
document: Record<string, unknown>
request_id: string
input_append_id?: string
ask_request?: Record<string, unknown>
input_event?: Record<string, unknown>
input_event_created?: boolean
input_notify_emitted?: boolean
input_notify_error?: string
actor_woken?: boolean
actor_wake_error?: string
actor_notify_delivered?: boolean
actor_notify_delivery_error?: string
event?: CCCSEventV1
}request_id identifies the logical Ask request. input_append_id identifies one durable append attempt. A caller retrying an accepted append MUST reuse both values. The daemon MUST then return the existing input with input_event_created=false and MUST NOT append a second semantic input, request event, or notification. When either value is omitted, the daemon may generate it and no retry guarantee exists until the caller retains the returned values.
assistant_voice_input_append
Append a general Voice Secretary Ask or create/update a composer refinement request. The daemon persists the request before emitting one targeted voice_secretary_input notification. This operation creates work for Voice Secretary; it does not create a prompt draft.
Args:
{
group_id: string
by?: string
kind: "voice_instruction" | "prompt_refine"
request_id?: string
input_append_id?: string
instruction?: string
text?: string
source_text?: string
voice_transcript?: string
composer_text?: string
operation?: "append_to_composer_end" | "replace_with_refined_prompt" | string
composer_context?: Record<string, unknown>
composer_snapshot_hash?: string
}For voice_instruction, at least one of instruction/text or source_text must be non-empty. For prompt_refine, at least one of voice_transcript or composer_text must be non-empty. request_id groups a composer refinement and may be reused for intentional follow-up input. Each distinct follow-up MUST use a new input_append_id; an exact retry MUST reuse the prior one and follows the same no-duplicate rule as assistant_voice_document_instruction.
Result:
{
group_id: string
assistant?: Record<string, unknown>
request_id: string
input_append_id?: string
prompt_request?: Record<string, unknown>
ask_request?: Record<string, unknown>
input_event?: Record<string, unknown>
input_event_created: boolean
input_notify_emitted: boolean
input_notify_error?: string
actor_woken?: boolean
actor_wake_error?: string
actor_notify_delivered?: boolean
actor_notify_delivery_error?: string
event?: CCCSEventV1
}assistant_voice_instruction_feedback
Report progress or the terminal result for an existing Voice Secretary Ask. Only the voice-secretary actor/principal may submit feedback.
Args:
{
group_id: string
by?: "voice-secretary" | "assistant:voice_secretary"
request_id: string
status: "working" | "done" | "needs_user" | "failed"
reply_text?: string
result_text?: string
message?: string
document_path?: string
artifact_paths?: string[]
source_summary?: string
checked_at?: string
source_urls?: string[]
}Result:
{
group_id: string
assistant: Record<string, unknown>
ask_request: Record<string, unknown>
event: CCCSEventV1
}assistant_voice_ask_requests_clear
Hide Ask history from the current projection. keep_active=true preserves pending and working requests in the visible result. Clearing is a display operation, not cancellation: the daemon MUST retain enough bounded state to accept later feedback for a cleared in-flight request. User-visible feedback may make that request visible again.
Args:
{ group_id: string; keep_active?: boolean; by?: string }Result:
{
group_id: string
assistant: Record<string, unknown>
ask_requests: Array<Record<string, unknown>>
latest_ask_request?: Record<string, unknown>
cleared_count: number
removed_count: number
kept_count: number
}assistant_voice_prompt_draft_submit
Submit the Voice Secretary result for an existing prompt refinement request. Only voice-secretary / assistant:voice_secretary may call this operation. The daemon inherits a missing operation and composer snapshot hash from the request, stores the result as pending, and emits assistant.voice.prompt_draft. no_op=true stores no_change with empty draft text. Submission MUST NOT append another semantic input or emit another voice_secretary_input notification.
Args:
{
group_id: string
by?: "voice-secretary" | "assistant:voice_secretary"
request_id: string
draft_text?: string
no_op?: boolean
summary?: string
operation?: string
composer_snapshot_hash?: string
}draft_text is required unless no_op=true.
assistant_voice_prompt_draft_ack
Mark a submitted draft as applied, dismissed, or stale. Acknowledgement removes it from the active prompt_draft projection while retaining bounded request history.
Args:
{
group_id: string
request_id: string
status: "applied" | "dismissed" | "stale"
}assistant_voice_request
Send a structured Voice Secretary action request to @foreman or one concrete actor without exposing normal chat.message send tools to the voice-secretary runtime actor. The daemon records an assistant.voice.request event and delivers a targeted system.notify with context.kind="voice_secretary_action_request". This is the default path for spoken "please do X / ask Y to do X" content; ordinary memo/document updates MUST stay in the Voice Secretary document surface.
Args:
{
group_id: string
by?: "voice-secretary" | "assistant:voice_secretary"
target?: "@foreman" | string // one concrete actor id; no @all/user broadcast
request_text: string // concise actionable handoff, not raw transcript
summary?: string
document_path?: string
artifact_paths?: string[] // repo-relative produced docs/artifacts for user-visible links
source_event_id?: string
priority?: "low" | "normal" | "high" | "urgent"
}Result:
{
group_id: string
assistant: Record<string, unknown>
request: Record<string, unknown>
notify_event: CCCSEventV1
event: CCCSEventV1
}assistant_voice_document_archive
Archive a Voice Secretary working document. The markdown file is left in place; the assistant index hides it from the active document list, and later transcript ingress without an explicit document_path creates or selects another active document instead of appending to the archived one.
Args:
{ group_id: string; by?: string; document_path: string }Result:
{ group_id: string; document: Record<string, unknown>; event: CCCSEventV1 }assistant_voice_document_library
Read the group's stored Voice Secretary library, including archived documents. Deleted entries are excluded. Folders organize documents without changing their Markdown paths. This reads the index; workspace discovery and external-content reconciliation remain the responsibility of assistant_voice_document_list.
Args:
{ group_id: string }Result:
{
folders: Array<{ folder_id: string; name: string }>
root_order: string[] // mixed folder:<folder_id> / document:<document_path> keys; initially []
documents: Array<Record<string, unknown>> // includes content and optional folder_id
}assistant_voice_document_library_update
Update library metadata or restore an archived document. Writers must be the user, a Foreman, or assistant:voice_secretary; omitted by means user. Operations serialize with other writes in the same Group and persist in the existing document index. These metadata updates do not append ledger events.
Args:
{
group_id: string
by?: string
action: "create_folder" | "rename_folder" | "remove_folder" | "reorder_root" | "rename" | "move" | "restore"
name?: string // create/rename: trimmed, 1–80 characters
folder_id?: string // folder rename/removal or move destination; empty move means root
document_path?: string // rename/move/restore: registered, non-deleted document
root_order?: string[] // reorder_root: required array of mixed root item keys
}Folder names must be unique within the Group. Removing a folder returns its documents to the root without deleting files. Renaming a document changes its display title only. Restore accepts archived documents and preserves their folder assignment; deleted documents cannot be restored. Result has the same shape as assistant_voice_document_library.
reorder_root replaces the saved display order. It requires an array of strings, removes duplicate keys (keeping the first), and drops keys for unknown folders or unregistered/deleted documents. An empty array clears the order. Ordering does not move documents or change their paths or archive status; clients apply it to visible root items. The Web tree places items absent from the saved order before ordered items, using its default order within that group.
assistant_voice_document_delete
Permanently remove a registered Voice Secretary Markdown file and mark its index entry deleted. Uses the same writer permission as library updates. Rejects traversal and symlink paths, and returns voice_recording_active while the Group holds a recording lease. Historical ledger events and transcript logs remain.
Args:
{ group_id: string; by?: string; document_path: string }Result:
{ group_id: string; document: Record<string, unknown>; event: CCCSEventV1 }The event is assistant.voice.document with action deleted. The file is moved to a temporary transaction location before the index and event are saved; either write failure attempts to restore the file and previous index. Failed rollback reports the retained recovery path. Successful writes remove that temporary copy; cleanup failures log a warning and retain it. Deleted paths reject subsequent saves, transcript appends and archive requests and are not rediscovered as active documents. The Web consumer clears quoted references and capture targets only after a successful response.
assistant_status_update
Update lifecycle/health for a built-in assistant service. The assistant principal (assistant:<assistant_id>) may update its own status; users/foremen may also update it for control-plane repair.
Args:
{ group_id: string; by?: string; assistant_id: "voice_secretary"; lifecycle: "disabled" | "idle" | "running" | "working" | "waiting" | "failed"; health?: Record<string, unknown> }Result:
{ group_id: string; assistant: Record<string, unknown>; event: CCCSEventV1 }group_automation_update
Replace group automation rules + snippets (scheduled system.notify).
Args:
{
group_id: string
by?: string
expected_version?: number
ruleset: {
rules: Array<{
id: string
enabled?: boolean
scope?: "group" | "personal"
owner_actor_id?: string | null
to?: string[]
trigger:
| { kind: "interval"; every_seconds: number }
| { kind: "cron"; cron: string; timezone?: string }
| { kind: "at"; at: string } // RFC3339
action?:
| {
kind?: "notify"
title?: string
snippet_ref?: string | null
message?: string
priority?: "low" | "normal" | "high" | "urgent"
}
| { kind: "group_state"; state?: "active" | "idle" | "paused" | "stopped" }
| { kind: "actor_control"; operation?: "start" | "stop" | "restart"; targets?: string[] }
}>
snippets: Record<string, string>
}
}Result:
{ group_id: string; ruleset: Record<string, unknown>; version: number; event: CCCSEventV1 }group_automation_state
Get effective automation state for a caller.
Args:
{ group_id: string; by?: string }Result:
{
group_id: string
ruleset: {
rules: Array<Record<string, unknown>>
snippets: Record<string, string>
}
status: Record<string, {
last_fired_at: string
last_error_at: string
last_error: string
next_fire_at: string
completed: boolean
completed_at: string
}>
supported_vars: string[] // exactly: interval_minutes, group_title, actor_names, scheduled_at
version: number
server_now: string
config_path: string
}Notes:
byas a peer receives a filtered view: group rules + own personal rules.- Rule IDs are non-empty and unique within a ruleset. Unknown fields and invalid trigger/action combinations are rejected instead of being persisted for one engine to ignore later.
group_stateandactor_controlactions require anattrigger. Actor callers may manage onlynotifyrules; a peer may mutate only its own personal notification rule targeting itself.- The first tick of a newly enabled interval rule establishes its clock and does not fire immediately. A paused or stopped group runs no automation. An idle group runs user rules but suppresses the built-in
standuprule. - Resume never catches up missed work: interval and cron clocks are rebased, missed one-time rules are completed without execution, and future one-time rules remain eligible.
- A notification firing completes only after at least one
system.notifyhas been appended durably for an enabled matching recipient. Recipient delivery does not require a currently running actor process. A successfully completed one-time rule is disabled. group.yaml:automationandstate/automation.jsonare the shared config and runtime authorities. The single-daemon process lock owns scheduling; engine handoff consumes these files and does not introduce a second scheduler lease or retry journal.
group_automation_manage
Incremental automation management with action list.
Args:
{
group_id: string
by?: string
expected_version?: number
actions: Array<
| { type: "create_rule"; rule: Record<string, unknown> }
| { type: "update_rule"; rule: Record<string, unknown> }
| { type: "set_rule_enabled"; rule_id: string; enabled: boolean }
| { type: "delete_rule"; rule_id: string }
| { type: "replace_all_rules"; ruleset: { rules: Array<Record<string, unknown>>; snippets: Record<string, string> } }
>
}Result:
{
group_id: string
ruleset: Record<string, unknown>
status: Record<string, Record<string, string>>
supported_vars: string[]
version: number
server_now: string
applied_actions: Array<Record<string, unknown>>
changed: boolean
event?: CCCSEventV1 | null
}group_automation_reset_baseline
Reset automation ruleset to built-in baseline defaults.
Args:
{ group_id: string; by?: string; expected_version?: number }Result:
{
group_id: string
ruleset: { rules: Array<Record<string, unknown>>; snippets: Record<string, string> }
status: Record<string, Record<string, unknown>>
supported_vars: string[]
version: number
server_now: string
config_path: string
event: CCCSEventV1
}group_start
Resume the Group and run every actor whose desired enabled state is true. Actors explicitly disabled through actor_stop remain disabled.
Args:
{ group_id: string; by?: string }Result:
{ group_id: string; started: string[]; event: CCCSEventV1 }group_stop
Stop the Group's actor runtimes without changing any actor's desired enabled state. This is a group-level suspension; use actor_stop to disable one actor.
Args:
{ group_id: string; by?: string }Result:
{ group_id: string; stopped: string[]; event: CCCSEventV1 }8.4 Actors
actor_list
Args:
{ group_id: string; include_unread?: boolean }Result:
{ actors: Array<Record<string, unknown>> } // includes at least id/title/runtime/enabled + role/runningactor_add
Args:
{
group_id: string
actor_id?: string
title?: string
runtime?: string
runtime_mode?: "default" | "acp" // optional headless ACP for Antigravity, Copilot, Devin and Cursor
command?: string[]
env?: Record<string, string>
capability_autoload?: string[] // actor startup autoload capability ids
capability_hidden?: string[] // actor-level skill menu hide preferences; does not disable capabilities
env_private?: Record<string, string> // write-only secrets (stored under CCCC_HOME/state; never persisted into ledger)
profile_id?: string // optional Actor Profile link (runtime/command/submit/env + secrets)
default_scope_key?: string
submit?: "enter" | "newline" | "none"
by?: string
}Notes:
env_privateis restricted toby="user"and values are never returned.- If
env_privateis provided (even empty), it is treated as authoritative for this create: it clears any existing private keys for that actor_id, then sets the provided keys. profile_idlinks the actor to a global Actor Profile and applies profile-controlled runtime fields + profile secrets.- When
profile_idis used,env_privateis rejected (linked actor private env is profile-controlled). - The appended
actor.addevent starts that actor id's current generation. The daemon MUST initialize the new generation's read boundary at that append position, so events from before the add are not delivered as unread. Removing and later re-adding the same actor id starts a new generation at the lateractor.addposition. - Actor records also expose an opaque
generationUUID for delayed remote recipient binding. Add assigns a fresh value, including when a supplied record came from an earlier Actor; update cannot overwrite it. Restart and ordinary edits preserve it. Existing records without this field use their originalcreated_atidentity (legacy:<created_at>) until they are recreated; browser ownership, pairing and tool routing MUST use that same identity and pairing MUST NOT assign a new generation after the browser opens. This does not replace the ledger-based inbox boundary above. - Multiple ChatGPT (
web_model) and Grok Bot (grok_web_model) Actors MAY coexist, including linked Profiles and Group imports/resets. Runtime configuration does not grant connector authority. Conversation bindings MUST identify the exact Group, Actor and generation; copied/imported Actors MUST be unpaired. - A new actor generation MUST NOT inherit Web Model delivery preferences or persisted runner/turn status left by an earlier generation with the same actor id.
- For a Web Model actor, successful add MUST establish the current generation's missing browser target as canonical empty state. A legacy actor-scoped browser shadow MUST NOT populate the new generation merely because it uses the same actor id.
- Adding an enabled actor to an
activeoridlegroup MAY start it immediately and transition the group's runtime to running. Adding one to apausedorstoppedgroup MUST only persist the actor and MUST NOT change the group lifecycle state. - When immediate startup is attempted, startup capability baselines follow the same rules as
actor_startbelow.
Result:
{
actor: Record<string, unknown>
event: CCCSEventV1
running?: boolean
start_event?: CCCSEventV1
start_error?: string
}actor_update
Args:
{
group_id: string
actor_id: string
by?: string
patch: Record<string, unknown>
profile_id?: string // attach/replace profile link
profile_action?: "convert_to_custom" // snapshot profile config + secrets, then unlink
}Patch keys used by CCCC v0.4.x include:
- Identity/UI:
title - Runtime:
runtime,runtime_mode,command,submit - Scope:
default_scope_key - Enable/disable:
enabled - Environment (use with care):
env - Capability startup baseline:
capability_autoload
A linked Profile owns runtime, runtime_mode, command, submit and its explicit environment. Actor-local title, notes, Web Model conversation bindings and capability_autoload remain editable; the Actor's capability baseline is additive to Profile defaults. A linked Actor MUST NOT merge dormant custom environment values over the Profile. Converting to custom snapshots the effective Profile configuration and secrets, replacing dormant custom secrets rather than reviving them. Private values remain outside events. Web editors MUST distinguish persisted Actor/Profile snapshots from unsaved private-env drafts. A successful Profile conversion MUST NOT discard pending secret edits; those edits are cleared only after their private-env write succeeds or the user leaves the editing session. Clients editing a command MUST preserve argument boundaries (including quotes, spaces and empty arguments) and SHOULD omit unchanged runtime fields.
Result:
{ actor: Record<string, unknown>; event: CCCSEventV1 }actor_remove
Args:
{ group_id: string; actor_id: string; by?: string }Notes:
- Removing an actor ends that actor id's current generation. Actor-generation-scoped browser target, bootstrap, delivery receipt, delivery preference, and persisted runner/turn state MUST be retired before the operation reports success. A shared provider login profile MAY remain.
- Every Web Model binding and pending pairing belonging to the removed Actor generation MUST be retired before success. Shared connector credentials and other Actors remain intact. Re-adding the same Actor ID MUST NOT restore an earlier generation’s authority.
Result:
{ actor_id: string; event: CCCSEventV1 }actor_start / actor_stop / actor_restart
Daemon-owned runtime resources MUST be retired after owner exit, including abrupt exit. On Unix, a daemon MAY use an EOF watchdog and a durable owned-process-group ledger to recover groups missed by graceful shutdown. Recovery MUST start only after acquiring the exclusive daemon-home lock. Watch lists MUST be isolated per daemon instance; cleanup MUST check process identity before signalling, and MUST leave unverified groups untouched. Ownership observations MUST record actual observation times, not future heartbeat allowances, and relinquishing a child MUST remove it from both active and durable cleanup lists. This cleanup MUST NOT invalidate durable provider conversation receipts or signal another daemon instance's runtime groups. Recovery MUST accept existing alive_until records by subtracting their original 20-second allowance before using the observation as ownership evidence. Unresolved groups after TERM, unavailable identity snapshots, and failed escalation MUST remain recorded for later recovery. An unreadable cleanup ledger MUST fail startup before replacing that ledger or the previous watchdog lists.
Kilo uses the same managed-session ownership as OpenCode: a private authenticated loopback ACP backend and a writable native TUI attached to the exact session. Actor and Voice Analyst share this adapter. Kilo configuration, MCP, model/variant synchronization, input readiness, cancellation and result settlement MUST follow the same boundaries; Kilo storage identity MUST include KILO_DB and its effective home/config roots. Starting or resuming an empty session MUST NOT submit a prompt. Only matching Kilo managed receipts may resume; legacy terminal receipts are not adopted.
On Windows, Kilo's official npm kilo.cmd entrypoint MUST be supported for both global and project-local installations. Actor and Analyst MUST use the same resolved launch prefix for the ACP backend and native TUI. The installed npm JavaScript launcher retains ownership of platform/binary selection and resource setup; CCCC launches it with Node without shell reinterpretation of arguments, preserving the configured environment and owned process-tree containment.
Args:
{ group_id: string; actor_id: string; by?: string }Result:
{ actor: Record<string, unknown>; event: CCCSEventV1 }Notes:
Antigravity, Copilot, Devin and Cursor
runtime_modedefaults todefault(native TUI);acpselects official pure headless ACP with derivedrunner=headlessandruntime_state_source=managed_session. Other Runtime/mode combinations MUST be rejected. An update repeating the same Runtime with no runtime_mode MUST preserve the existing mode; changing Runtime without an explicit mode resets it to default. Mode is Profile-controlled and settings changes apply on explicit restart or next start; listing MUST report the actual running surface asrunner_effective/effective_runtime_mode. Antigravity ACP MUST use its separate CCCC-owned provider home. Copilot, Devin and Cursor ACP use native CLI login/configuration; CCCC MUST NOT rewrite their global MCP configuration. Every ACP process/session MUST receive its own explicit CCCC Group/Actor (or global Analyst) identity and keep its resume receipt separate from the native TUI receipt. Copilot injects local MCP through process-scoped--additional-mcp-config; Devin and Cursor use session MCP injection. New and resumed sessions MUST reapply explicitly configured model and permission policy. Failed load MUST NOT silently start a fresh session. Copilot, Devin and Cursor do not durably store empty sessions: CCCC MUST distinguish never-attempted empty sessions from attempted prompts. The attempt state MUST be durable before sending the first prompt, including an unconfirmed delivery. Never-attempted empty sessions MAY start fresh on restart; attempted sessions MUST load the same identity or expose an error requiring explicit New session/reset. Global Voice persistence usesmaterializedfor the same boundary; status polling cannot materialize a session.Antigravity ACP Actors have no native TUI. The daemon retains ordinary ledger delivery in FIFO order while a prompt is active, admits it on live same-session provider activity or the prompt response, and projects normalized progress/completion through the existing headless journal. Replay or foreign-session events MUST NOT admit work. Permission requests are interactive unless the effective command selects unrestricted policy; user controls MUST bind the exact session generation and pending request, and grant at most one operation. Cancel/stop MUST retire pending permissions. Provider end_turn means a completed protocol turn, not proof that the task succeeded. An ACP prompt whose admission cannot be confirmed MUST settle its existing runtime.delivery claim as ambiguous and leave the automatic retry lane, including after session or daemon replacement. This MUST NOT be reported as accepted or downgraded to a retryable failure during worker shutdown. An admitted failed turn MUST project headless.turn.failed with its normalized provider error, so existing journal/SSE consumers can distinguish incomplete work.
For linked actors (
profile_idset),actor_startandactor_restartfirst resolve profile runtime config and profile secrets.Saving Runtime configuration does not itself replace a running session.
actor_startremains idempotent while a registered session is running. A surviving attached terminal MUST NOT make a disconnected managed registration count as running; Start MUST retry its cleanup and report any failure before launching a replacement. Stop/restart MUST retire registered ownership by Group/Actor identity independently of the saved Runtime; restart MUST NOT start a second backend after a reported cleanup failure. Lifecycle status MUST follow registered sessions until explicit restart applies the saved configuration.Message delivery and its transport record MUST also follow the registered running surface. Saving ACP mode on a live native PTY MUST preserve native delivery until explicit restart or the next start; a live managed session continues managed delivery after saving native mode. A registered Claude workspace-trust prompt MUST remain pending recovery rather than becoming a native task-input surface, including after a saved Runtime edit.
A managed Actor's native terminal is an attachment, not its provider lifetime. Its exit MUST NOT mark a healthy provider stopped, even before terminal-exit reconciliation. Reconciliation MUST follow the registered owner rather than saved next-launch Runtime settings and MUST NOT detach a replacement terminal. Explicit
actor_start, writableterm_attachand message delivery MAY reopen the terminal against the same healthy managed session; status reads and passive viewer attachments MUST NOT start it. A stopped or disconnected provider MUST NOT be restarted by terminal attachment. Confirmed provider process absence alone MUST NOT invalidate a durable Claude conversation receipt; existing identity and transcript validation still govern resume.A daemon-launched actor whose executable is directly identified as
codexMUST use one daemon-owned Codex app-server thread and MUST attach Codex's writable native TUI to that exact thread. Unsupported subcommands, wrappers, or prompt tails fail explicitly instead of silently selecting another transport. The app-server and TUI MUST receive the same executable, supported Codex global arguments, profile/model/provider configuration, and private environment. CCCC-owned listener, MCP identity, approval, and sandbox settings remain host-controlled. For both Actors and Voice Analyst, execution-policy overrides MUST be applied to the app-server; the remote TUI MUST attach without approval, sandbox, or shell-environment policy overrides. Stop/start MUST validate and resume the same version-2 managed receipt only when Runtime, workspace, command, model, and effective Codex storage identity still match. Legacy Codex receipts MUST NOT be resumed.A daemon-launched
claudeactor MUST use one CCCC-owned Claude Agent View background session and MUST startclaude attachagainst that exact session. The resolved executable and each observed live worker MUST independently report Claude Code 2.1.259 or newer; their versions need not match. Agent View can retain older workers after upgrading the supervisor and migrate an idle session to a newer worker, so a supported version change alone MUST NOT invalidate the same managed session. CCCC MUST continue validating exact session identity, the protocol-v1 control response shape, and the credential-file boundary; unsupported or unverifiable versions, invalid protocol responses, and credential-boundary violations MUST fail closed. CCCC observes turn ownership and terminal settlement from the append-only provider transcript. A single retryable control-query failure MUST NOT invalidate a still-live session; sustained inability to verify liveness or confirmed job absence MUST disconnect it. CCCC owns background/session/attach, name, MCP identity, autonomy, and resume arguments. Runtime Profile environment values MUST be merged into one stable, owner-scoped, CCCC-protected settings file because Agent View deliberately strips arbitrary process environment from persisted jobs and stores that file path in its durable respawn metadata; raw values MUST NOT appear in the job record, terminal command, receipt, or logs. An ordinary process stop MUST retain this file while the durable session receipt remains resumable. The copy MUST be atomically replaced when that owner's effective settings change and removed when the managed session identity, Actor, or Group is retired. Stop MUST report success only after the Agent View job is confirmed absent. Start MUST validate and resume the same version-2 managed receipt only when Runtime, workspace, command, and the complete effective Claude launch identity, including content of file-backed settings and prompt inputs, still match. A live idle matching session MAY be re-adopted; an active, ambiguous, copied, or identity-mismatched session MUST fail or start fresh according to the existing receipt boundary and MUST NOT be guessed. Legacy Claude Hook and print-mode receipts MUST NOT be resumed.A daemon-launched
grokactor MUST use one CCCC-owned managed session. CCCC starts a dedicated private Grok leader, connects its ACP observer, and attaches the native writable Grok TUI to the same provider session. Actor startup, Voice Analyst startup, and CLI setup MUST share a verified nativeccccMCP registration so ACP session creation, resume, and native TUI reload use the same configuration. The shared command MUST resolve the launching CCCC executable dynamically and inherit Actor/instance/profile/origin identity from its process, not persist that identity in global settings. CCCC MUST preserve unrelated MCP entries and native Claude/Cursor imports, reject malformed configuration and conflicting project overrides without replacing them, and serialize its user-level updates across instances. Readiness MUST check the effective executable, arguments, enabled state, and inherited CCCC identity after native configuration overrides, and MUST reject a native policy denial. A valid base table or discovery entry alone is insufficient. Conflicting version/project overrides and policy documents MUST NOT be rewritten to force readiness. This native registration also takes precedence for standalone Grok sessions. Structured lifecycle events remain the working/completion authority. Stop/start MUST validate and load the same version-2 managed receipt when its Runtime, workspace, command, model, and effective provider-home identity still match. Legacy raw-terminal Grok receipts MUST NOT be resumed.An explicit Grok
--trustruntime argument grants folder trust to the native TUI for the selected workspace. CCCC MUST forward it only to the TUI, not theagentsubcommand, and MUST NOT add it by default. Without an explicit or previously saved trust decision, native folder confirmation can block business input; PTY delivery acceptance is not provider admission.The Grok ACP observer MUST associate live
_meta.promptIdactivity with its local turn and consume matching durableturn_completedupdates, including_x.ai/session/update, for both controlled and native turns. Asend_nowcancellation MUST settle the old turn before the next native input is associated; a delayed prompt RPC response or duplicate terminal MUST NOT settle the new turn or consume its sources. A turn ending before prompt-bearing activity MAY use the persisted session-scoped event sequence following its user record as its completion boundary, never wall-clock timing. Replay records MUST NOT admit controlled or native input. Uncorrelatedprompt_completenotifications remain non-authoritative. OpenCode and Kilo use their ordered backend event stream as described below.Grok ACP initialization MUST advertise
clientCapabilities._meta["x.ai/userMessageEcho"]=true, and new/load session requests MUST bind_meta.clientUserMessageEcho=truebecause the leader shares initialization across clients. Current Grok persists user input even when its live echo is disabled; persistence alone does not admit the controlled prompt. Both new and resumed managed sessions need this opt-in so correlated progress can flow before the prompt completion RPC, without increasing or bypassing the bounded admission buffer.A daemon-launched
opencodeactor MUST use one CCCC-owned managed session. CCCC startsopencode acpwith a generation-scoped authenticated loopback backend, observes the ACP session over stdio, attachesopencode attachto that exact session, and injects the actor-scoped CCCC MCP server at session creation. The resolved executable MUST report OpenCode 1.18.14 or newer. ACP remains the control and permission port. The observer MUST use the authenticated workdir-scoped/eventendpoint, whose listener is registered before the HTTP response; the lazily subscribed/global/eventendpoint cannot guarantee delivery of the first input. Text parts markedmetadata["kilocode.lifecycle"]="transient"are temporary Kilo UI progress and MUST NOT contribute to streamed or completed Actor/Analyst answer text; later deltas for these parts MUST also be excluded. Thesyntheticflag alone MUST NOT exclude ordinary answer text. This backend event stream MUST order user admission, assistant output, and terminal session status for both controlled and native turns; a prompt RPC response or a duplicate ACP output update MUST NOT finish or contribute text to another turn. Persisted native input is not evidence that the current turn consumed it: correlation MUST follow the assistant message's parent user identity, retaining queued inputs across the preceding turn's idle status. A lost or malformed non-replayable stream invalidates the session. A model selection made in the native TUI becomes authoritative for later CCCC-managed prompts when the user submits the next TUI message; CCCC MUST mirror that message's exact provider/model and variant into the same ACP session. An explicit runtime-command--modelremains the launch-time override. Stop/start MUST validate and load the same version-2 managed receipt when its Runtime, workspace, command, model, and effective OpenCode storage identity still match. Legacy raw-terminal OpenCode state MUST NOT be resumed.Completion of a temporary Actor restore worker or retirement of an IPC request worker MUST NOT terminate an otherwise healthy managed provider session. Providers that bind their lifetime to the spawning OS thread MUST be launched by daemon-lifetime workers. Explicit stop, failed-start rollback, and daemon shutdown retain ownership of process cleanup.
If managed Claude startup reports an untrusted workspace, the Actor terminal MAY present the configured Claude command for the operator to approve trust. CCCC MUST NOT write the trust decision. Configuration observation MUST begin before opening that prompt; approval recorded before the watcher starts MUST remain observable. Task delivery stays pending until the managed session is attached. Recovery MUST acquire the runtime-start permit before the Actor start guard and revalidate cancellation and the same live terminal before attachment. Actor and Group stop MUST cancel pending recovery, including prompts without a managed session, and clean up a cancelled launch instead of attaching it.
Claude trust recovery MUST continue waiting only for a verified workspace-trust refusal. Other launch errors MUST end that watcher and retire its own temporary trust terminal; an unrelated provider configuration rewrite MUST NOT trigger another recovery launch. The trust terminal is not a managed task-delivery surface.
A failed Claude resume MUST retain the original provider session ID, record a bounded safe diagnostic and failure count, and fence that outcome to the exact resume attempt. Provider stderr and credential values MUST NOT be persisted in this record. Known temporary startup/control failures MAY remain retryable. Copied session IDs, unavailable durable history after the bounded discovery wait, invalid identity/protocol/credential evidence and other unverifiable resume failures MUST pause automatic startup with
runtime_session_status="resume_failed",runtime_session_resume_eligible=falseandruntime_session_last_resume_error. Paused startup returnsclaude_resume_failed; it MUST NOT silently start a fresh session. Undelivered messages remain pending and their handoff claims become retryableruntime.delivery=failed, leaving the automatic worker retry lane. An authorized explicitactor_startoractor_restartMAY retry the same saved conversation and redispatch those messages. Only explicitactor_new_session(or configured resume disablement / changed launch identity) selects a fresh conversation; provider history remains intact. Successful resume clears the failure diagnostic and count. Process absence alone is not a resume failure.Managed runtime startup MAY synchronously enumerate the injected actor-scoped CCCC MCP tools before its provider session becomes ready. That catalog discovery MUST use
capability_statewithview="mcp_catalog"so it cannot wait on the same Group lifecycle lock held byactor_start; ordinary capability reads remain serialized normally.For Codex, Claude, Grok, OpenCode, and Kilo Actors, CCCC MUST hand an incoming Actor delivery to the writable native TUI as soon as that terminal is ready. CCCC MUST NOT inspect provider busy state to choose
steerversusqueue, and MUST NOT hold the delivery until the current turn settles. The receiving Runtime owns that policy according to its own configuration.runtime.delivery=acceptedmeans the canonical input and submit sequence were written successfully to the Runtime terminal; it does not claim that the provider completed or semantically accepted the work. Structured protocols remain authoritative for session identity, lifecycle, progress, completion, cancellation, and Voice Analyst delegation.In assistant mode, Realtime Voice owns the intent decision to create a Voice Analyst delegation; it does not own provider scheduling. For a Runtime with a native terminal or verified steering surface, once
delegation.createdexists, CCCC MUST immediately hand the exact correlated input to the managed Runtime and MUST NOT hide it in a server-side wait-for-idle queue. An active Runtime with a verified exact-turn steer operation MAY receive the input through that operation; otherwise CCCC MUST write the exact payload and submit sequence to the same verified native terminal session, after which the Runtime owns the steer-versus-queue decision. CCCC MUST register correlation before the write, project whichever authoritative turn consumes it, and report success only after the Runtime control operation or complete terminal submit sequence was accepted. A missing, closed, or rejecting Runtime input path MUST return an explicit delivery error; busy state alone MUST NOT drop, delay, merge, or reject the delegation.The Web Voice start contract MAY carry optional immutable
application_contextwith a validated 1–128-byte ASCII identifier and nonempty UTF-8 instructions up to 24 KiB (24,576 bytes) in both assistant and persona modes. This is a local input size limit, not a provider token budget. CCCC MUST reject oversized instructions without truncating them and MUST preserve accepted text exactly; provider limits still apply. In assistant mode, CCCC MUST include it in Realtime startup instructions and each Voice delegation delivered to the Analyst, without changing the provider delegation ID or at-most-once admission. Start idempotence MUST include the entire validated context, including mode; changed context MUST NOT reuse an active call. Context MUST NOT be interpreted as authentication, a selected Group, or a tool grant, and MUST NOT implicitly reset the persistent Analyst. Embedded assistant expression (language, length, tone, and user-facing attribution labels) MUST follow the host instructions without appending global expression preferences. Assistant calls, including embedded calls, retain provider automatic acknowledgement filler and client delegation. A brief receipt acknowledges hearing the request without waiting for Analyst completion; it MUST NOT claim execution succeeded. Host expression policy governs wording and avoids repeated receipt narration. Omission preserves global Voice behavior. Arbitrary context text MUST NOT appear in startup diagnostics.Antigravity ACP Voice Analyst shares the Actor adapter, has no terminal, and MAY hold up to 32 inputs in an explicit FIFO because official ACP does not accept concurrent session/prompt calls. Its empty persisted command MUST resolve to the Actor default
agy --dangerously-skip-permissions, setting provider YOLO mode and automatically selecting only a one-time allow option for any remaining permission request. This default MUST be reapplied after both new and resumed sessions; explicit custom commands and linked Profile commands MUST retain their configured permission policy. Web MUST display the same default without materializing it into saved settings. Admission MUST distinguish host queue acceptance (Queued, eventanalyst_queued) from provider turn acceptance (Started). Each input keeps its correlation and completion association; no merge, eviction, auto-replay after an uncertain outcome, or terminal fallback is permitted. Cancellation MUST remain available before the first provider admission receipt, correlate with that exact pending input in the protocol command loop, and cancel both current and queued inputs. Confirmed cancellation retains the warm session; failure to settle uses the existing bounded invalidation/stop path. A terminal outcome without a provider receipt MUST use a distinct host-generated turn_id per input, shared by both Web consumers for idempotent notification persistence. Stop/reset retires the queue. Actor stop/restart and daemon shutdown MUST also interrupt pending ACP admission waits without automatically replaying the unconfirmed batch. Settings replacement and rollback MUST resume only a genuinely resumable session; execution surface alone does not imply materialization. Disconnect or admission failure MUST expose an unconfirmed/failed outcome. Read-only status reportsstructured,queued_inputs,permissions, boundedprogressandlast_error; it MUST NOT start work. Terminal failure MUST retain a bounded diagnostic message (at most 4096 UTF-8 bytes, no raw RPC envelope), including pre-admission rejection, in the warm Analyst snapshot outside a call. The structured console MUST label partial output as incomplete. The next task or successful/cancelled terminal outcome clears this error. The generation-bound interactive control endpoint accepts manual input, a one-time permission answer or an explicit Cursor user-interaction response. Copilot, Devin and Cursor ACP use this same FIFO, cancellation and unconfirmed-delivery boundary, without a native terminal. Existing native Runtime admission remains unchanged. The structured Web console MUST publish same-generation authoritative read/control snapshots to its call controller. Cancellation availability MUST cover executing, queued and pending-admission manual inputs, including the interval after a previous turn completes but before the next provider receipt. A terminal event for the previous turn MUST NOT hide cancellation for remaining work.Pure ACP Analyst manual investigations use the existing generation-bound control endpoint with
action="input", a stableinput_id, completetextand optional nullablecall_generation. Omitted/null call identity is standalone; CCCC MUST NOT infer it from an active microphone session. An explicit identity MUST match the attached assistant call and that exact Analyst generation, otherwise admission fails without changing the draft or starting work. Retries preserve both input ID and original call scope; reusing an ID with changed content/scope MUST fail. Admission remains the existing FIFO and at-most-once lifecycle, distinct from provider acceptance. The Web snapshot includesmanual_tasks(id,text, nullablecall_generation,status,result,error) and nullablemanual_task_ididentifying its current/last manual output; these are read-only, generation-local console projections, not a persistent chat or another scheduler. Keep all unsettled tasks and final results awaiting the original call's output; retain at most eight settled disposable rows, with a 64-record total bound that rejects new input rather than discarding unsettled or awaiting-output work. Eviction MUST NOT erase lifecycle deduplication or permit replay. An explicit call-bound investigation receives the call's immutable host context. Only its settled outcome returns throughsession.context.append, with a bounded task label, authoritative final and non-execution instructions; it MUST NOT invent a provider delegation identity. Failed/cancelled work MUST NOT return partial output as a successful answer. Completed rows and failures remain visible outside a call. Call retirement prevents queued/late outcomes from entering a replacement call, while the warm Analyst may finish independent work. Independent native-TUI tasks remain Terminal-originated and are not newly opted into automatic voice projection. Persona calls neither admit call-bound investigations nor consume their results. Context submission does not prove speech; no automatic retry of uncertain task execution or context delivery is added.The browser MUST publish the same-generation call attachment state from the Voice control socket
readymessage before completing startup. The HTTP start response describes a not-yet-attached call; retaining that snapshot after socket attachment MUST NOT misclassify manual investigations as standalone. Socket readiness does not itself prove provider context acceptance or speech.Embedded Voice
application_context.modeMUST beassistant(default) orpersona, inside the same strict, bounded context object. Omitted and explicitassistantmodes are equivalent; unknown modes/fields MUST be rejected. Mode is immutable and participates in start-request identity. Persona MUST use only host instructions as the CCCC-supplied Realtime instructions, without assistant role/routing or user expression preferences. Persona MUST NOT resolve, launch, reuse, reset or subscribe to a Voice Analyst, execute provider delegations, consume or reserve notification sources/results, prepare notification output, or apply output receipts. Delegation input MUST be ignored before content parsing; diagnostics MUST NOT contain its payload. Existing Analyst work and notification state remain independent and available for later assistant calls. Call authorization/revocation, generation fencing, microphone lease, heartbeat and cleanup remain enforced. The call-start cue MUST defer opening behavior to host instructions. Persona requests omit quicksilver client-delegation configuration; upstream suppression is experimental and MUST NOT be claimed solely from omission or a successful SDP response. A provider rejection MUST NOT silently fall back to assistant mode.readiness.supported_modesadvertises supported call modes; persona requires Realtime credentials, not Analyst availability. Call payloads includemodeand nullableanalyst_generation; the persona start payload hasanalyst: null, while the active endpoint MAY still report the independently managed global Analyst. These fields confer no additional caller permissions.Codex Voice startup failures MUST distinguish configuration, Analyst startup, recording ownership and Realtime connection failures. The Web start response MUST retain a safe error category and diagnostic details (
stage, total attemptelapsed_ms, andhttp_status/os_errorwhen available). Packaged startup MUST emit these safe fields to stderr even without a tracing subscriber. Credentials, private paths, SDP, provider response bodies and arbitrary error chains MUST NOT appear in these diagnostics. Diagnostic classification MUST NOT add automatic provider retries, extend timeouts or discard a successfully started Analyst after Realtime failure.calls/activereadiness MUST distinguish a missing Antigravity ACP distribution from a missing native Runtime executable.analyst_runtime_setup_required=trueidentifies a resolved Antigravity ACP configuration whose managed distribution is not installed;analyst_runtime_availableremains false until installation. Web MUST give the explicitcccc setup --runtime antigravity --runtime-mode acp --logininstallation/separate-login instruction rather than nativeagy/PATH restart advice. This read-only discovery MUST NOT install, authenticate or launch an Analyst; installed status does not prove valid provider credentials.The read-only Web catalog
GET /api/v1/runtimesMAY addmode_availability: {default: boolean, acp: boolean}to the Antigravity, Copilot, Devin and Cursor entries. Existingavailableand the top-levelavailablelist retain native-executable semantics.defaultdetects nativeagy;acpuses the same installed-distribution check as Voice readiness. For Copilot, Devin and Cursor both modes detect the native CLI executable; detection is not proof of login or provider task readiness. Detection MUST NOT install, authenticate, start sessions or write provider state. Web MUST distinguish loading/failed/unknown detection from a negative result and provide an explicit read-only recheck; Profile editing remains independent of host installation.Voice Analyst Runtime settings MUST NOT change while a Realtime Voice call is active. After the call stops, active or queued Analyst work MUST block an ordinary settings update rather than being discarded implicitly. An interactive administrator MAY explicitly confirm discarding that work as part of the same settings transaction; CCCC MUST then stop the old managed session before applying the replacement and MUST report whether unfinished work was discarded. Candidate-launch failure MUST restore the prior settings and Runtime, but MUST NOT claim that explicitly discarded work was recovered.
Claude transcript entries MUST use the provider
promptIdas the durable provider-turn identity and MUST NOT infer identity from the Agent View summary headline. A human prompt observed before control acceptance is an external turn. Because the authenticatedreplyresponse does not expose thatpromptId, CCCC MAY return a stable local turn receipt as soon as the control request is accepted, but Voice ownership, progress, and results become authoritative only when the next transcript user record exactly matches the one pending controlled prompt and supplies its provider identifier. A competing prompt plus successful control acceptance is ambiguous and MUST invalidate the managed session rather than replaying the delivery. A controlled request that never starts, or settles without exposing the matching transcript, MUST fail within bounded post-acceptance or post-settlement intervals; active provider work MUST NOT expire solely because its turn is long.turn_duration, the provider interruption marker, and an explicit failure record are terminal authority. The state file and selected transcript file identity MUST be revalidated while following the session. An active transcript MAY relocate inside the configured Claude project store only after the old path disappears, a unique same-session file is found, and its entire consumed byte prefix matches the observer’s retained SHA-256 digest. The reader MUST preserve its byte offset and partial record without replaying history. A missing or incomplete relocation destination MUST settle within a bounded 10-second grace period; sustained loss, consumed-history mismatch, ambiguous candidates, same-path replacement, truncation of the active file, or malformed tail records MUST invalidate the session. Relocation alone MUST NOT stop or recreate the provider session.A managed-runtime native-terminal delivery MUST wait for the TUI's advertised input mode before writing any payload; a PTY handle alone does not prove input readiness. Readiness waiting MUST be bounded and cancellable and MUST NOT inject a probe prompt, fall through on timeout, or wait for provider work to finish. Actor deliveries remain unaccepted on readiness failure; Voice native-input deliveries report an explicit error without writing the payload.
A Claude receipt MAY name an empty session that has never created a transcript. Before respawning it, CCCC MUST inspect the existing durable job metadata: only positive empty-input evidence with no transcript path, consumed transcript bytes, output, or token usage permits transcript-free recovery. An absent/null or numeric zero output-token counter does not indicate usage; nonzero or malformed counters MUST retain strict transcript validation. Unknown or materialized history MUST retain strict transcript validation, including a transcript written before Agent View publishes its path. This exception MUST preserve the exact provider session ID and MUST NOT replay old input or create a replacement conversation silently.
Daemon shutdown MAY stop managed Actors concurrently, but each ordinary stop MUST still confirm provider termination and retain retryable ownership on failure. A launcher forced exit MAY send best-effort Claude control stop requests for Actor sessions registered in that same process, within a single bounded deadline and without acquiring their graceful-stop locks. This does not guarantee stop confirmation or cover a detached daemon's registry or the separate Voice Analyst. An unavailable or unverifiable Agent View control endpoint MUST NOT by itself authorize signaling processes found by PID or command-line matching.
Observer failure MUST NOT be treated as proof of provider process exit. Actor and Analyst teardown MUST use confirmed provider stop, and a failed stop MUST retain retryable ownership rather than mark the job stopped. Normal managed-client shutdown MUST explicitly terminate event readers even when the session still retains the event sender; observers MUST distinguish expected closure from a failure and MUST NOT emit duplicate stop events.
Unexpected managed Codex disconnects MUST retain structural first-cause diagnostics before teardown: transport category, numeric close/OS code when available, elapsed time, and observed owned-process state/exit status. Voice control-stream closure MUST distinguish browser closure, authorization loss, server shutdown and Analyst lifecycle failure, correlating call and Analyst generations. Packaged launches MUST expose these bounded records without requiring a tracing subscriber. Diagnostic records MUST NOT contain peer close text, protocol payloads, credentials, conversation text or arbitrary error chains. A generic control failure MUST NOT assert Analyst availability. These diagnostics MUST NOT change reconnect, replay or teardown policy.
Managed protocol output received before a protocol-originated request is admitted MUST be buffered within fixed byte and event-count bounds. This rule applies to Voice Analyst and internal control requests; Actor message delivery uses the native-TUI rule above. Once the provider authoritatively accepts a protocol request, CCCC MUST publish buffered lifecycle updates in order. A bounded post-response drain MAY be enabled only as an explicit provider-specific normalization policy.
Voice result projection MUST reconcile the authoritative final with the exact already-projected prefix. A different final MUST NOT be discarded merely because progress was streamed. Result accumulation is bounded to 32 KiB; overflow without a bounded authoritative final MUST settle as
result_too_large, not as successful truncated output. The Voice port MUST report that limitation without terminating the warm Analyst or the audio call. Context sends, provider context receipts, and completed speech turns MUST remain distinct observations; receipt absence MUST NOT trigger blind replay, and receipt presence MUST NOT be treated as proof that every fact was spoken.Managed-runtime preview restoration and live activity MUST remain distinct for every supported Runtime. The Web
/api/v1/groups/{group_id}/headless/stream(andcodex/streamalias), with defaultreplay=true, first emits oneheadless.snapshotSSE frame containing{events:[...]}from the bounded retained journal. Subsequentheadlessframes carry only increments after that same captured file boundary;replay=falseomits the initial snapshot. The standalone snapshot GET remains a read-only inspection API. An unfinished journal line at attachment MUST be retained until complete, not skipped between snapshot and tail. Consumers MUST apply each stable journal event ID at most once before delta accumulation or lifecycle effects. If received IDs survive a disconnect or Group switch, consumers MUST preserve their pending projections as well; the Web consumer flushes buffered text and activities to their original Group before cleanup. Restored history MUST NOT trigger fresh progress bubbles, change message-read state, or start Runtime work. Dock bubbles show bounded recent live progress per Actor; full transcripts remain available in the inspector/TUI. Reconnecting, projecting the same completion, or evicting a UI cache MUST NOT revive stale progress as a new notification.Context delivery is separate from Runtime input admission and Provider speech scheduling. The browser MUST deliver checked context in order without waiting for user or assistant speech to finish; the Provider owns speaking and yielding to interruptions. Adjacent unsent fragments with the same context/delegation envelope MAY be joined without truncation, subject to the final append-size limit below. Missing speech transitions or context receipts MUST NOT block later context, trigger blind replay, or discard positively unsent results. A completed speech turn does not prove that all source facts were spoken. This delivery policy MUST NOT delay Analyst inputs or intercept provider delegations.
The browser Voice control socket MAY report
provider_errorwith anerrorobject containing bounded providercode,type,event_id, andparamidentifiers. This is transport diagnostics only: it MUST NOT start, cancel, or replay Analyst work. The Web port MUST validate these identifiers and correlate its diagnostic with the active call generation; it MUST NOT log arbitrary browser payloads, provider error messages, or credentials. The browser MUST distinguish a provider error from an Analyst failure and retain the provider code in its visible error when available. Error reporting alone MUST NOT change provider recovery policy.Actor start, restart, new-session, and daemon restoration MUST NOT submit a model turn solely to initialize an Actor or materialize a provider session. They create or resume the daemon-owned control session and attach its native terminal while the model remains idle. Only real input—a pending CCCC delivery or human terminal input—may start model work. The CCCC startup prompt MUST be deferred to and combined with the first successfully accepted CCCC delivery, MUST NOT be sent as a standalone turn, and MUST remain pending if that delivery is not accepted. Recovery of an actual pending Send is a valid work trigger; lifecycle operations alone are not.
A newly created Codex thread MUST be durably resumable before CCCC records a usable receipt or attaches its native TUI.
thread/startreturning an ID and planned rollout path is insufficient. CCCC MUST materialize the empty thread through native metadata operations and verify full-history readability for that exact thread, without submitting a model turn or adding synthetic conversation items. Resumed conversations MUST retain their existing names and history.A provider process exit MUST record
actor.stopwithby="system"anddata.reason="process_exit", but MUST NOT disable the actor or stop the Group. A user-authored Send or Request Reply explicitly targeting an actor ID, or a Send targeting@foreman, is also a wake action: it MUST enable that actor, move a paused or stopped Group toactive, and start delivery through the normal runtime path whether the prior stop was automatic or user initiated. Broadcast selectors (@all,@peers, including the materialized default) MUST NOT enable disabled actors; they MAY resume the Group and wake already-enabled recipients. An actor explicitly named alongside a broadcast retains its explicit wake behavior. A broadcast with no enabled recipients MUST NOT resume the Group. This wake policy MUST NOT narrow the message's logical audience or remove it from history. Mail and Actor-authored messages MUST NOT enable disabled actors; Mail and previously queued work MUST NOT independently wake a runtime while a Group remainspaused.If the linked profile includes
capability_defaults, daemon applies baseline capability enables through capability control plane before launch.Daemon also applies role defaults and the actor's
capability_autoloadbefore launch. These are durable desired capability bindings, so they remain applied when the subsequent runtime launch fails.A daemon-launched runtime process MUST resolve an explicit existing attached scope from the actor default or group active scope. It MUST return
missing_project_root,scope_not_attached, orinvalid_project_rootas applicable and MUST NOT fall back to the daemon working directory. An explicitly external structured executor may omit a local process only when its product capability and documentation say so.Unmanaged native PTY Actors, including Antigravity in its default TUI mode, retain the shared best-effort input-mode wait and automatic submission path. Startup context MUST accompany the first task in one submission; starting an idle Actor MUST NOT submit a model prompt. Antigravity delivery does not depend on terminal display text or a per-process Web confirmation. Native login/trust setup must be completed before delivery; paste mode alone does not prove that setup is complete. PTY handoff evidence is not a provider receipt. Custom terminal programs retain their separate preamble submission contract.
Antigravity TUI Actor startup and native CLI setup MUST use native
agy mcp addand verify the effective CCCC MCP configuration before launch. A conflicting project.agents/mcp_config.jsonentry or malformed configuration MUST fail without being overwritten. Global setup MUST preserve unrelated servers and serialize updates across instances. Itscccc mcpentry MUST resolve via the owning Actor launcher on PATH and inherit instance/Actor identity rather than pinning it in shared configuration. The complete startup prompt remains attached to the first task; a bounded, cancellable settling interval before the first payload addresses the observed early paste-mode initialization window. Later deliveries MAY include a conditional bootstrap reminder. Neither pacing nor that reminder changes PTY handoff evidence into a provider receipt.Antigravity TUI preparation also disables the native
showFeedbackSurveyuser preference, because its rating overlay can consume terminal input. Other preferences and user-owned symlinks MUST be preserved; invalid settings MUST fail explicitly without being replaced. This user-wide change also applies to standalone AGY sessions and MUST be documented as such. It does not add a UI-text readiness detector, a survey-dismissal keystroke, or automatic replay of accepted input.The
kimiruntime targets the current Kimi Code native TUI. Actor startup and CLI setup MUST share MCP configuration atKIMI_CODE_HOME/mcp.json(defaulting to the user's.kimi-code/mcp.json), preserve unrelated servers, and reject malformed documents without overwriting them. A project.kimi-code/mcp.jsonentry forcccctakes precedence; a conflicting project entry MUST fail setup without modification, unless it is the user-level configuration file itself. CCCC MUST NOT infer the active client from legacy directories or invoke the removedkimi mcp addcommand. MCP setup MUST NOT write provider trust records. Native input-mode signals do not establish first-use initialization readiness; users must finish startup dialogs before PTY delivery. Kimi's explicit session arguments are preserved, but CCCC does not claim managed session-ID capture or automatic resume for this runtime.A
deepseekactor has no native terminal surface and MUST use CCCC's structured ACP surface. The daemon MUST install and resolve CCCC's pinned ACP composition fromCCCC_HOME/runtimes/deepseek/<release>and MUST NOT modify the user'sDSH_HOME, home-level npm project, or attached project.The managed DeepSeek root manifest and lockfile MUST declare exactly
dsh-acp,dsh-mcp-client,dsh-acp-demo, anddsh-llm-deepseekas direct dependencies. Every installed@deepseek-ai/dsh*package MUST remain on the release declared bycrates/cccc-contracts/src/deepseek.rs; checking only direct package manifests is insufficient.Each DeepSeek actor MUST set
CCCC_DEEPSEEK_SESSION_ROOTtogroups/<group_id>/state/deepseek/<actor_id>/sessionsunder the activeCCCC_HOME. A provider turn MUST reach a successful terminal response within the shared bounded timeout before its source cursor advances; timeout cancellation MUST be durably projected as a failed turn, or the unconfirmed supervisor MUST be stopped. Output and failed-terminal idempotency keys MUST include the provider-attempt identity so a retry cannot be hidden by partial output from an earlier failed attempt; the successful terminal remains idempotent by source event. Crash recovery MUST query that durable per-source completion marker directly (or through its persistent index) and MUST NOT stop recognizing completed turns merely because the append-only headless event log crossed a size or line-count threshold. A permanent credential or context-window failure MUST persist a manual-restart gate before automatic delivery can run again. The gate MUST be bound to both the actor creation identity and the failed provider launch generation, MUST survive daemon restart, and MUST be cleared only after a lifecycle start/restart operation successfully initializes a replacement provider process; daemon restore and message-triggered auto-wake MUST NOT clear it. A late failure from a replaced generation MUST NOT close the replacement actor's gate.The managed
dsh-llm-deepseekprofile MUST setmaxTokensto the sharedDEEPSEEK_MAX_OUTPUT_TOKENScontract value (currently 65,536), preserving input/tool headroom instead of inheriting the upstream 256k output reservation. Credential absence and provider context-window overflow are permanent for the current runtime session: both MUST be normalized to stable, secret-free failed-turn errors and MUST stop automatic retries until a lifecycle start/restart successfully initializes the actor again.
Voice notification state (trusted local user)
Voice notification preferences and derived delivery observations are private instance state under CCCC_HOME/state/codex_voice/notifications.json, atomically replaced under one exclusive lock. The Group ledger remains the source of message bodies. These operations MUST NOT write mail.read, complete reply obligations, wake Actors, start a microphone, or imply user approval of Actor text. Web access MUST require the same administrator principal as the other global Voice routes. Long-lived calls, notification handoffs and Analyst terminal sockets MUST revalidate a remote administrator token. Revocation or administrator downgrade MUST stop input/output; terminal connections MUST also check while idle (one-second polling for both viewer and control attachments). Existing trusted-local identity is unchanged.
voice_preferences_get: readVoicePreferencesonly. This andvoice_notifications_getMUST be classified as read-only dispatcher operations so panel polling does not queue a global writer ahead of MCP catalog discovery during Actor startup.voice_preferences_set:{preferences: {revision, groups: Record<group_id, "off"|"to_user"|"all_chat">, suppress_viewed: boolean, verbosity: "concise"|"standard"|"detailed", style: "natural"|"direct"|"patient"}}. The supplied revision MUST match; successful updates increment it. Newly enabled message categories begin at the ledger boundary at save time, not historical chat. Expression changes apply to the next call and MUST NOT restart the Analyst.- Source-message handoff MUST include canonical Group/event and sender IDs, a readable Group name, and the canonical sender-title snapshot (current Actor title or ID when unavailable). Names and message contents remain data, never instructions. Analyst notification prompts MUST use the active call's expression preference. Both Analyst and Realtime instructions MUST require each new Actor notification to identify its Group and sender without a follow-up question. Detailed applies to notifications as well as user answers and preserves material findings, numbers/units, conditions, evidence/uncertainty and next steps; it does not require reading raw tool traces. Output preflight MUST supply source identities independently of the Analyst's free-text summary, using only eligible sources for background output. A partly suppressed summary remains generic; adding attribution MUST NOT reintroduce its excluded source names or contents. An explicit user answer retains its existing suppression exception and original source attribution.
voice_messages_viewed:{messages: Array<{group_id, event_id}>}(at most 128). Exact formal-chat references only; idempotent, private, and independent of Actor unread state. The Web observer MUST require an unobscured, foreground, fully visible expanded message with at least 1.5 seconds of stable viewport exposure, not GET/SSE receipt or virtual-list mounting.voice_notifications_get: read at most 128 recent source references pluspending_countandunconfirmed_count, plussuppressed_countfor skipped results among the visible references. Each reference includes its associated result'soutput_status(processing,ready,unconfirmed,submitted,suppressed) and optionalsuppression_reason(viewed,policy,source_unavailable). Submitted is a browser submission observation, never playback completion or confirmation that the user heard it. The GET MUST NOT scan, mark, consume or start work. Full message text, origin tokens and stored Analyst result text MUST NOT appear in this UI projection.
A notification consumer failure MUST retain durable references and publish {type:"notification_status", paused:true} to the connected call owner over the existing Voice control WebSocket. This state is independent of Analyst availability and audio state. The consumer stops for that call; a new call starts a fresh consumer. It MUST NOT silently reset corrupt state.
Before submitting source-bearing output on the browser data channel, the host MUST recheck current policy and exact viewed references. /api/v1/codex_voice/calls/{generation}/notification-output accepts {result_id} only for the active connected call and its reserved result. A fully suppressed background result returns {message:null}. That decision and its reason MUST be stored atomically with the final policy/viewed check and remain idempotent even after later preference changes. A partly suppressed unstructured summary MUST NOT be sliced or spoken as if its sources were separable; an explicit user answer remains deliverable. The browser reports notification_output_submitted only after data-channel submission and reports notification_output_not_submitted for positively unsent result IDs at orderly stop. Queue or receipt-capacity overflow MUST retain the rejected result ID for this report before initiating teardown. Reports MUST precede the stop frame and fit both the 1,024-ID observation limit and the 128 KiB WebSocket frame limit; the browser batches at most 64 managed result IDs per report. An ID already submitted to the provider MUST NOT be included merely because its receipt is missing. Disconnection, missing receipts, or timeout alone MUST NOT release a reserved result for automatic replay. The private Realtime context-append protocol limits each append to 500 tokens. The browser MUST apply a conservative 500-UTF-8-byte text budget at the final data-channel boundary, after local coalescing and notification preflight have produced the actual text. Session and delegation context appends MUST preserve the complete text and Unicode code points across ordered fragments. One output's fragments MUST be sent consecutively, without waiting for speech between fragments. Receipt capacity MUST be checked for all fragments before sending the first one. Report a result submitted only after every fragment was written; if some fragments were written and a later write fails, retain the result as unknown and MUST NOT report it positively unsent or replay the complete result. This limit does not require shortening the Analyst summary or changing the selected response detail. Context delivery MUST NOT depend on user/assistant turn.created / turn.done, a prior speech response, or context receipts. These events do not authorize or release a delivery slot; the Provider owns speech timing and interruption. The browser MUST drain queued context in order after coalescing and policy checks, even if a previous turn never ends or a received context produces no response. A delegated result MUST NOT wait for the turn that needs that result to end. The browser's conversation transcript MUST allow input/output transcript fragments to precede turn.created. A later provider turn ID, including one first received in turn.done, MUST bind the same role's unbound draft in place; the authoritative final transcript replaces that entry. An event for an already-known turn MUST update that turn without consuming another draft. Distinct provider turns MUST remain distinct even when their text is identical. Transcript reconciliation is display state only and MUST NOT trigger delegation or context delivery. The browser MUST bound transport waits: while connecting or while the data-channel buffer exceeds 256 KiB, queued output waits at most 15 seconds for usable capacity. New output MUST NOT extend that deadline. Capacity recovery resumes delivery and rechecks notification policy. Failure MUST be explicit and stop the call through the existing orderly-stop path, retaining positively unsent results; already submitted or partially submitted results remain uncertain and are never replayed on that basis. Closing the channel MUST remove its callbacks and cancel all pending waits. Notification-output preflight MUST have a finite request deadline, including when the underlying request ignores cancellation or never settles. Late responses MUST NOT submit obsolete output. Only transient preflight failures may be retried, with a bounded attempt count and backoff; each attempt MUST recheck policy. Suppression of the same call/result reservation MUST return {message:null} on subsequent checks, including when the original response was lost. Persistent or terminal preflight failure MUST stop the call explicitly and retain positively unsent results through the existing orderly-stop path. Browser delivery diagnostics MAY report queue counts, transport wait reasons, buffered bytes, receipt/speech observation counts and preflight failures. They MUST NOT log message bodies or credentials, change source-processing state, or turn any speech observation into per-source heard confirmation. Each retained result MUST have a durable completion sequence, allocated exactly once from the instance's monotonic notification sequence under the same state lock. Output snapshots MUST sort results by this sequence, not opaque Runtime turn IDs, Analyst generations, or source arrival. Duplicate completion observations MUST preserve the original sequence and result; sequence exhaustion MUST fail atomically without marking sources processed. This order survives call and Analyst restarts and does not alter Runtime input admission or imply speech completion.
The shared local client carries a host-owned Analyst origin on message-write operations. The daemon MUST validate that origin and persist an exact source-event/expected-recipient association before appending its canonical message. Expected recipients MUST snapshot the same inbox routing semantics as delivery, including @foreman, @all, @peers and internal-actor exclusions. Later role changes MUST NOT retarget this persisted reply association. Internal origin metadata MUST NOT enter public chat data. Ordinary Send, tracked Send, and nested code-mode calls use this same boundary. Failed appends may leave an inert source intent, but only an existing canonical ledger event can activate it.
An active call consumes ledger increments in bounded pages and atomically registers candidate references before advancing cursors. Lost broadcasts MUST be recoverable from these cursors. The first acknowledgement does not terminate reply association. Only the expected Actor's exact reply association is a request result; subscription matching is independent and deduplicated by the canonical group/event pair. Attachment-only messages remain eligible; their contents MUST NOT be read automatically. Source copies of cross-group messages MUST NOT duplicate the destination.
Before Runtime handoff, persist the source references and receiving Analyst generation. On an unproven post-handoff failure, retain unknown delivery rather than automatically executing again. An accepted but unfinished source belonging to a replaced Analyst generation MUST also remain visible as unconfirmed, rather than implying that the replacement is still working on it. All inputs consumed by one Runtime turn MUST survive in its completion association set. Active user answers MUST NOT be silenced by a concurrent non-speaking background update. Only the explicitly reported Antigravity ACP FIFO described above may wait for Runtime completion; no hidden Runtime-busy wait queue or oldest-item eviction is permitted. Viewing or narrowing subscriptions suppresses unsubmitted background speech, not actual Group work or explicitly requested answers.
The private version-1 state has a bounded 10,000-reference working set, 16 MiB encoded size, and a 256-event scan page per Group. Reads are size-bounded and incompatible state MUST NOT be silently reset. At most 128 completed notification references are retained for recent source links. Capacity exhaustion MUST stop scanning before losing references or advancing past unregistered events. Unknown handoffs MUST NOT be evicted to free capacity. Completed notification attempts may be compacted after their cursor has passed; Group deletion retires its derived references, never another Group with the same name. Source associations remain until their Group is retired; they are not synthetic permanently-running tasks. Voice-off MUST NOT cause notification model turns. Cancelling an unhanded background candidate because it was viewed or excluded by preferences MUST retire its viewed reference in the same transaction: scan has already passed that source. Viewed references ahead of the scan cursor, explicit request context, and handed-off observations MUST remain available for their existing consumption and output checks.
actor_new_session
Args:
{ group_id: string; actor_id: string; by?: string }Result:
{ actor: Record<string, unknown>; event: CCCSEventV1; new_session: true }Notes:
- Supported for Antigravity,
claude,codex, Grok, OpenCode, and Kilo actors. - Antigravity in its default TUI mode uses its ordinary process lifecycle: stop the existing process if present, then start the actor with the same runtime settings and a fresh deferred bootstrap. CCCC does not issue a native
/clearcommand or claim automatic Antigravity session resume. - Other supported runtimes stop the current actor process if present, clear CCCC's saved runtime session metadata for that actor, then start the actor with the same runtime settings. Receipt selection, snapshot and rollback MUST resolve the current linked Runtime Profile before selecting ACP versus native metadata; the other surface's receipt remains intact.
- Does not delete provider-side conversation/session history.
runtime_hermes_status
Return Hermes runtime setup diagnostics for the selected user Hermes profile.
Args:
{}Result:
{
runtime: "hermes"
setup_ready: boolean
auth_ready: boolean
launch_ready: boolean
hermes_cli: { available: boolean; path?: string; version?: string }
hermes_home: string
profile: { name: "default"; dir: string; exists: boolean; config_path: string; config_exists: boolean }
mcp: Record<string, unknown>
auth: Record<string, unknown>
phase0_gates: Array<Record<string, unknown>>
issues: string[]
}Notes:
- CCCC does not create or select a separate Hermes profile.
HERMES_HOME, when supplied by the user, is treated as ordinary runtime environment. Explicit Actor/Profile environment takes precedence over the host default.mcp.envmust persist${CCCC_HOME},${CCCC_GROUP_ID}, and${CCCC_ACTOR_ID}placeholders so each actor process resolves its own CCCC identity.
runtime_hermes_prepare
Configure the cccc MCP server in the selected Hermes profile through Hermes' official MCP setup flow.
Args:
{
cwd?: string
auto_enable_tools?: boolean // alias: yes
force_mcp?: boolean // alias: force
}Result:
{
ok: boolean
commands_run?: Array<Record<string, unknown>>
status: Record<string, unknown>
error?: { code: string; message: string }
}Notes:
- Setup MAY invoke
hermes mcp add cccc ...and answer Hermes' discovery prompt only whenauto_enable_tools/yesis true. - Discovery uses concrete CCCC env values, then CCCC normalizes saved Hermes MCP env back to actor-time placeholders.
- Hermes and other runtime MCP configuration/check helpers are finite commands: input, both output streams, and exit share a deadline. Their owned process group / Windows Job is released on completion or timeout. Captured output is bounded to 2,000,000 bytes per stream; oversized output is an explicit command error, never partial setup evidence.
runtime_hermes_mcp_test
Run Hermes' MCP test command for the configured cccc server with probe CCCC actor env.
Args:
{
cwd?: string
group_id?: string
actor_id?: string
}Result:
{
ok: boolean
argv: string[]
result?: { returncode: number; stdout: string; stderr: string }
error?: { code: string; message: string }
}actor_env_private_keys
List configured private env keys for an actor (keys only; never returns values).
Notes:
- Private env is runtime-only and MUST NOT be persisted into the append-only group ledger.
- Intended for secrets like API keys/tokens that may vary per actor.
- Effective env at process start is:
daemon_env(inherited) →actor.env→private_env→ injectedCCCC_GROUP_ID/CCCC_ACTOR_ID. - This operation is restricted to
by="user"(agents should not be able to read/inspect secrets metadata).
Args:
{ group_id: string; actor_id: string; by?: string }Result:
{ group_id: string; actor_id: string; keys: string[] }actor_env_private_update
Update an actor's private env map (set/unset/clear). Values are never returned.
Args:
{
group_id: string
actor_id: string
by?: string
set?: Record<string, string> // set/overwrite keys
unset?: string[] // remove keys
clear?: boolean // remove all keys (wins)
}Result:
{ group_id: string; actor_id: string; keys: string[] }8.5 Actor Profiles (Global)
Actor Profiles are global reusable runtime profiles stored under CCCC_HOME/state/actor_profiles/. They are not group-local settings.
actor_profile_list
Args:
{ by?: string }Result:
{ profiles: Array<Record<string, unknown>> } // each profile includes usage_countactor_profile_get
Args:
{ profile_id: string; by?: string }Result:
{
profile: Record<string, unknown>
usage: Array<{
group_id: string
group_title?: string
actor_id: string
actor_title?: string
}>
}actor_profile_upsert
Create/update a profile with optimistic concurrency.
Args:
{
by?: string
profile: {
id?: string
name: string
runtime: string
runtime_mode?: "default" | "acp"
command?: string[] | string
submit?: "enter" | "newline" | "none"
env?: Record<string, string> // deprecated legacy input; values are migrated into profile secrets
capability_defaults?: {
autoload_capabilities?: string[]
default_scope?: "actor" | "session" // default actor
session_ttl_seconds?: number // clamped to 60..86400
} | null
}
expected_revision?: number
}Notes:
- Runtime variables are unified as profile secrets (
actor_profile_secret_*). profile.envis accepted only as a legacy bridge and migrated into profile secrets; stored profileenvis kept empty.commandaccepts the historical shell-command string for compatibility, but successful writes normalize it to astring[]. Readers must continue to accept an existing string until that profile is saved again.
Result:
{ profile: Record<string, unknown> }actor_profile_delete
Args:
{ profile_id: string; by?: string; force_detach?: boolean }Notes:
- Default behavior rejects delete when the profile is still used by linked actors (
profile_in_use). - With
force_detach: true, linked actors are converted to custom first, then the profile is deleted.
Result:
{
deleted: true
profile_id: string
detached_count: number
detached: Array<{ group_id: string; actor_id: string }>
}actor_profile_secret_keys
List profile secret keys (masked previews only).
Args:
{ profile_id: string; by?: string }Result:
{ profile_id: string; keys: string[]; masked_values: Record<string, string> }actor_profile_secret_update
Update profile-level secrets (write-only values).
Args:
{
profile_id: string
by?: string
set?: Record<string, string>
unset?: string[]
clear?: boolean
}Result:
{ profile_id: string; keys: string[] }actor_profile_secret_copy_from_actor
Copy an actor's effective explicit environment into a profile's secrets (server-side copy, values are never returned). Linked Actors use their referenced Profile environment, requiring read access to that source Profile; custom Actors use their private environment. Web draft secret edits are applied after copying, before reporting the Profile save as complete.
Args:
{
profile_id: string
group_id: string
actor_id: string
by?: string
}Result:
{ profile_id: string; group_id: string; actor_id: string; keys: string[] }actor_profile_secret_copy_from_profile
Copy one profile's current secret map into another profile (server-side copy, values are never returned).
Args:
{
profile_id: string
source_profile_id: string
by?: string
}Result:
{ profile_id: string; source_profile_id: string; keys: string[] }actor_profile_copy_voice_analyst_secrets
Copy the Voice Analyst custom private environment into a Runtime Profile without returning values. This operation is administrator-only and is used when the Voice settings surface saves its Custom configuration as a reusable Profile.
Args:
{
profile_id: string
profile_scope?: "global" | "user"
profile_owner?: string
by?: string
}Result:
{ profile_id: string; keys: string[] }8.6 Chat Messaging
send
Append a chat.message event. message_mode is required and is the only chat-delivery selector.
Args (core):
{
group_id: string
text: string
by?: string
to?: string[] // empty/omitted materializes group default_send_to
message_mode: "send" | "request_reply" | "mail"
path?: string // optional filesystem path to attribute scope_key
attachments?: unknown[] // attachment refs (implementation-defined)
refs?: ReferenceV1[] // structured message refs, e.g. presentation_ref/task_ref
insight?: string // optional provisional sender perspective; max 1200 characters
require_peer_insight?: boolean // profile gate; default false
src_group_id?: string // relay provenance (both required if either is set)
src_event_id?: string
dst_group_id?: string // optional "send record" metadata (source messages)
dst_to?: string[]
dst_message_mode?: "send" | "request_reply" | "mail"
}Result:
{
event: CCCSEventV1 // kind="chat.message"
message_mode: "send" | "request_reply" | "mail"
}send and request_reply preflight the concrete runtime audience, append the message, and then attempt prompt delivery. mail appends only; it MUST NOT wake, steer, queue, open a browser, or write to a runtime input. request_reply MUST resolve to explicit concrete recipients and rejects broadcast selectors. In the Rust implementation, omitted recipients and @foreman resolve to the current enabled Foreman actor ID before this validation. New daemon callers MUST send message_mode; missing or old priority, reply_required, or requires_ack fields fail validation. After aliases and selectors are normalized, one message MUST address either the human user or one or more agents, never both. mail is valid only for agent recipients. Mixed audiences fail with mixed_recipient_kinds; Mail addressed to the user fails with mail_requires_actor_recipient. Validation occurs before the message, blobs, delivery claims, or other side effects are written. The immediate response confirms ledger acceptance and echoes the canonical mode; it is not transport evidence. Per-recipient delivery truth is reported only by daemon-authored runtime.delivery events and the corresponding status queries.
Every daemon-rendered chat.message handed to an actor runtime MUST expose the current ledger identity before the message body:
[cccc] <sender> → <recipients> [event_id=<current_event_id>]: <body>
[cccc] <sender> → <recipients> (reply:<short_parent_id>) [event_id=<current_event_id>]: <body>
[cccc] <sender> → <recipients> [event_id=<current_event_id> reply_required]: <body>event_id is the target an actor passes to cccc_message_reply when answering the delivered message. A reply MAY expose its parent as the existing short (reply:<id>) correlation hint; it MUST NOT repeat the full parent reply_to in the runtime envelope. The ledger remains authoritative for the full parent identity and canonical message_mode. Ordinary send and delivered mail messages need no mode label because the label does not change the receiver's next action. A request_reply message MUST instead add the actionable reply_required marker in the same metadata block. Implementations MUST use the same envelope in native-terminal, structured, and Web Model delivery. The metadata MUST remain in the existing first header line so adding it does not turn a one-line message into multiple runtime input lines. It does not add or mutate ledger fields, and it is not required for system.notify. Every delivered batch containing one or more chat.message events MUST append one concise instruction telling the actor to call cccc_message_reply with its event_id argument set to the target message's current event_id. The instruction MUST reserve cccc_message_send for a new message and MUST appear only once per batch.
Native MCP adapters MUST preserve a failed daemon operation's error.code, error.message, and non-empty error.details in the tool result's structuredContent.error; flattening the daemon error into text is not conforming. MCP results MUST preserve the operation's factual receipt and error fields. They MAY add the passive mail_pending summary, but MUST NOT append instructions to take over, reframe, or review unrelated work. Actor collaboration guidance belongs in the configured runtime instructions, not operation receipts. A peer-insight validation error MUST retain delivery_state="not_sent" and new_side_effects=false, with a concise explanation of the missing field.
The MCP file surface separates read permission from message delivery: cccc_file(action="read"|"info"|"blob_path", rel_path=...) is read-only and MUST reject action="send" without writing blobs, messages or delivery work. cccc_file_send(path=..., ...) sends through the existing send_files or connect_send_files operation and retains its audience, sender, peer-insight, path-scope and idempotency rules. Tool annotations describe the complete exposed action set; a mixed read/write tool MUST NOT be labelled read-only.
message_upload_preflight
Validate a Web-owned staged upload before its temporary files are committed to the group blob store. This operation is side-effect free and exists so both Web implementations use the selected daemon's canonical send and reply rules rather than reimplementing message policy in the HTTP port.
Args:
{
operation: "send" | "reply" | "send_cross_group"
group_id: string
dst_group_id?: string // required when operation="send_cross_group"
text?: string
by?: string
to?: string[]
message_mode: "send" | "request_reply" | "mail"
reply_to?: string // required when operation="reply"
path?: string
client_id?: string
refs?: ReferenceV1[]
insight?: string
require_peer_insight?: boolean
has_attachments: boolean // temporary upload parts exist; no blob refs yet
}Result:
{ ready: true }
// or, for send/reply when client_id already identifies an accepted message:
{
ready: false
duplicate: true
result: { event: CCCSEventV1; message_mode: "send" | "request_reply" | "mail" }
}The operation MUST perform the deterministic validation used by the eventual send, reply, or send_cross_group, including mode, audience, target, scope, Insight, and content, without waking actors, changing group state, writing the ledger, storing blobs, or starting delivery. send and reply additionally perform successful-idempotency lookup and MUST return a duplicate result before an upload is committed. Local cross-group uploads are unsupported and rejected. Connect reply uploads use the canonical reply authorization. The HTTP port MUST discard its staged files on rejection or duplicate replay. The eventual operation MUST validate again at the commit boundary; preflight is not a reservation.
send_files
Store one or more files from the group's active scope in the group blob store, then append one chat.message carrying those files as attachments. This is the daemon-owned upload boundary for SDK clients; callers MUST NOT write directly to state/blobs/ or manufacture attachment records.
Args:
{
group_id: string
paths: string[] // absolute, or relative to the active scope root
text?: string // defaults to a compact file notice
by?: string
to?: string[]
message_mode: "send" | "request_reply" | "mail"
insight?: string
client_id?: string
}Every resolved path MUST be a regular file beneath the group's active scope. All paths are validated and read before any message is appended. The resulting event uses the selected message_mode recipient, permission, and delivery rules. If client_id already identifies an accepted message, the daemon MUST return that message before reading or storing new source content. After source paths have been validated and read, deterministic normal-send validation (including message mode, recipients, and required peer insight) MUST succeed before any new blob is stored. A request rejected by that preflight MUST NOT add a blob or ledger event.
Result:
{
event: CCCSEventV1 // kind="chat.message", data.attachments contains stored blobs
message_mode: "send" | "request_reply" | "mail"
}reply
Append a chat.message with reply_to and quote_text. Connect replies derive the qualified destination and participant from the original local event, as specified in CCCC_CONNECT_V1.md. Historical manual Bridge routes are rejected; their provenance sender is never a local recipient token.
Args:
{
group_id: string
reply_to: string
text: string
by?: string
to?: string[] // local: original sender; Connect: original remote participant
message_mode?: "send" | "mail" // default: "send"
attachments?: unknown[]
refs?: ReferenceV1[]
insight?: string
require_peer_insight?: boolean // profile gate; default false
}Replies default to message_mode="send". Callers MAY choose message_mode="mail" when fulfilling the original reply obligation does not justify immediately prompting the recipient. Both modes fulfill the original reply obligation; message_mode="request_reply" is invalid for a reply and a reply cannot create another generic reply obligation. Mail replies are valid only when every reply recipient is an agent. A reply to the human user MUST use Send. Replies also reject a recipient list that mixes the human user with agents.
Result:
{
event: CCCSEventV1 // kind="chat.message"
message_mode: "send" | "mail"
}tracked_send
Create a durable task and send one linked visible delegation message. This is an explicit composite write; the daemon MUST NOT infer it from arbitrary chat text.
Args:
{
group_id: string
title: string
text: string
by?: string
to?: string[]
outcome?: string
checklist?: { text: string; status?: "pending" | "in_progress" | "done" | string }[]
task_priority?: string // task-domain priority; does not affect message delivery
assignee?: string // defaults from one concrete to actor when possible
waiting_on?: "none" | "user" | "actor" | "external"
handoff_to?: string
notes?: string
idempotency_key?: string
refs?: ReferenceV1[]
insight?: string
require_peer_insight?: boolean // profile gate; default false
}Result:
{
task_id: string
task_ref: ReferenceV1 // kind="task_ref"
event?: CCCSEventV1 // present when message_sent=true
event_id?: string
message_mode?: "send" // present when message_sent=true
task_created: boolean
message_sent: boolean
partial_failure: boolean
replayed?: boolean
}Notes:
task_refin the emittedchat.message.data.refsis the canonical message-task link.- The linked visible message always uses
message_mode="send"; task lifecycle is the only completion authority for tracked work. priority,message_priority, andreply_requiredare rejected; callers usetask_priorityonly for the task-domain field.- If task creation fails, no message is sent.
- If message delivery fails after task creation, the response MUST report
partial_failure=true. - Successful retries SHOULD use
idempotency_key/client_request_idto avoid duplicate task/message pairs.
send_cross_group
Cross-group send implemented as:
- Write a source
chat.messagein the origin group as a local Send touser, with the actual remotedst_group_id,dst_to, anddst_message_modemetadata. - Write a forwarded
chat.messagein the destination group withsrc_group_id/src_event_idprovenance.
Args:
{ group_id: string; dst_group_id: string; text: string; by?: string; to?: string[]; message_mode: "send" | "request_reply" | "mail"; insight?: string; require_peer_insight?: boolean }Result:
{ src_event: CCCSEventV1; dst_event: CCCSEventV1 }Notes:
- Local
user/systemprincipals and registered source actors, including peers, may send cross-group messages; unknown source actors are rejected. The foreman-only group administration permission does not apply to message delivery. - Local cross-group forwarding rejects attachments. Connect sends use their qualified instance/Group destination and bounded attachment contract.
Agent Insight Profile marker
require_peer_insight is an internal request-profile marker, not a global message-validity rule. It defaults to false. When true, the daemon resolves the operation's real audience and rejects a new peer-facing message whose normalized insight is empty. User-only sends remain valid without Insight.
The check MUST occur after routing and successful-idempotency lookup, but before this request creates a new message, task, actor wake, or remote outbox entry. The recommended error code is peer_insight_required, with details.delivery_state="not_sent" and details.new_side_effects=false. Invalid Insight type or length SHOULD use invalid_insight instead. Existing accepted idempotent operations MUST replay their original result without being reinterpreted by a newer profile requirement.
Connect peers MUST satisfy the current account and peer protocol version gates before messages are exchanged. There is no fallback to manual Bridge.
reply_request_cancel
Cancel every still-open recipient obligation for an existing message_mode="request_reply" message.
Args:
{ group_id: string; source_event_id: string; by?: string }Result:
{ event: CCCSEventV1 } // kind="chat.reply_request.cancelled"Only the source sender or user may cancel. The operation is idempotent for an already-cancelled source event.
message_deliver
Explicitly attempt prompt delivery for one existing message without appending a second chat.message. This promotes Mail or retries a blocked/failed Send.
Args:
{
group_id: string
source_event_id: string
actor_ids: string[]
by?: string
force_ambiguous?: boolean // default false; explicit warning/confirmation required
}Only the source sender or user may request delivery. Recipients MUST be explicit concrete recipients of the source event. Existing accepted evidence is never retried. Existing claimed evidence reports delivery_in_progress. Existing ambiguous evidence is rejected unless force_ambiguous=true. Disabled recipients return delivery_blocked without creating a delivery claim. A request that reserves new claims while the Group is paused or stopped explicitly resumes the Group before handoff. A conflicting, already accepted, or otherwise no-op request MUST NOT change lifecycle state. A successful request records all claims before returning:
{
event: CCCSEventV1
actor_ids: string[]
delivery_state: "claimed"
}Terminal per-recipient states remain authoritative in runtime.delivery and the normal message-status projection; they may settle before or after this operation returns.
Delivery evidence, Mail notice, and reply notice
For each concrete recipient handoff, the daemon appends runtime.delivery using the CCCS contract. claimed precedes external I/O; accepted, failed, or ambiguous records the outcome. A transport queue accepting a payload is an accepted handoff; it is not a claim that the model understood it. Automatic retry is forbidden after accepted or ambiguous. Concurrent claimants treat claimed as in-progress. Daemon startup settles claims stranded by the prior daemon process to ambiguous before attempting runtime recovery. Recovery MUST interpret a same-generation legacy chat.read.event_id as an inclusive ledger watermark for that actor, not as a per-event receipt. It MUST use the furthest valid referenced ledger position and exclude each legacy system.notify at or before that position. A later notification MUST also be excluded when data.event_id, data.related_event_id, or data.context.event_id references an event at or before the watermark. Targets outside the current actor generation or after their chat.read record are invalid compatibility boundaries and MUST be ignored. This rule prevents already-consumed pre-runtime.delivery nudges from being replayed after an upgrade.
Mail is eligible for an active notice for one recipient only while all of these hold: the source event belongs to the recipient's current actor generation, is unread, has message_mode="mail", has no reply from that recipient, and has no accepted or ambiguous manual-delivery record. Reply and manual delivery suppress only the active notice; neither advances the Mail cursor nor removes the message from inbox_peek / inbox_read. Concrete-recipient Mail batches retain the earliest eligible deadline. New Mail does not reset it. Broadcast-like Mail remains visible in the Inbox but does not start an active runtime notice timer.
After mail_notice_after_seconds, an active/idle enabled actor may receive one content-free system.notify(kind="mail_notice") for the concrete batch. The notice states only that Mail is waiting and directs the actor to inbox_read; it does not copy message bodies, repeat, escalate, or create another Inbox obligation. Bootstrap, the next explicit Push, and low-frequency coordination responses MAY carry a passive mail_pending count without writing a notice.
A request_reply obligation starts its timer only after an accepted runtime delivery. If no matching reply_to message or cancellation has closed it by reply_notice_after_seconds, an active/idle enabled actor may receive one content-free system.notify(kind="reply_notice"). It never repeats or escalates. Failed, blocked, or ambiguous delivery does not nudge the recipient.
Paused, stopped, or disabled actors are never woken for either notice. Pending Mail is surfaced at the next start/resume bootstrap and begins a fresh notice window. Explicit start/resume may recover only blocked Push work from the current actor generation that has no accepted/ambiguous delivery evidence; Mail is never automatically promoted. No implementation may depend on a universal runtime "idle" detector.
8.7 Inbox (Mail Cursor)
inbox_peek
Return unread chat.message events whose message_mode="mail" without changing the Mail cursor. This is for UI polling, bootstrap previews, and diagnostics; agent tooling SHOULD use inbox_read. Send, Send + Reply, and system.notify events are never members of this projection.
The current actor generation begins at the latest actor.add for that actor id in ledger append order. Inbox membership and Mail read status MUST exclude events before that boundary, including after an actor is removed and re-added with the same id. When a cursor contains a resolvable event_id, cursor advancement and unread membership MUST use ledger append order; timestamp is informational only.
Passive mail_pending summaries MUST preserve these same cursor, recipient and Actor-generation boundaries without materializing a persistent full-ledger index merely to count unread Mail. A reverse scan MAY stop at the cursor or generation boundary; segment discovery and reads MUST share the ledger reader boundary so concurrent rotation cannot hide events. These reads MUST NOT advance the cursor.
Args:
{ group_id: string; actor_id: string; by?: string; limit?: number }Result:
{ messages: CCCSEventV1[]; cursor: { event_id: string; ts: string } }inbox_read
Atomically return and consume the next unread Mail prefix for an actor.
Args:
{ group_id: string; actor_id: string; by?: string; limit?: number }Result:
{
messages: CCCSEventV1[]
cursor: { event_id: string; ts: string; updated_at: string }
event: CCCSEventV1 | null
}Selection, mail.read append, Mail cursor persistence, and returned messages MUST be one consuming operation under the canonical ledger/cursor transaction boundary. Ordering is the append order of the Mail projection: non-Mail events between two Mail events are intentionally skipped and do not acquire read state. If the event or cursor write fails, the operation returns no message bodies. An empty Inbox returns messages=[] and event=null without moving the cursor.
The ledger mail.read event is the authoritative commit record; the cursor file is a rebuildable projection. Implementations MUST persist state/read_cursors.pending.json before appending mail.read, append the event before advancing the cursor projection, and clear the marker after both writes commit. The shared recovery marker is:
{
"schema": 1,
"group_id": "...",
"actor_id": "peer1",
"expected": {"event_id": "...", "ts": "..."},
"target": {"event_id": "...", "ts": "...", "updated_at": "..."}
}After interruption, a matching mail.read fact completes the target cursor; without that fact the marker is discarded and the old cursor remains. Recovery MUST never move an already-later cursor backward. This makes a process exit between the two durable writes recoverable without replaying or silently skipping Mail.
The canonical cursor file is state/read_cursors.json with {"schema":1,"cursors":{...}}. Documents without this Mail-specific schema are not delivery boundaries and MUST be ignored; in particular, a cursor from the former all-message read model cannot suppress Mail.
message_history
Return an actor-visible, non-consuming history of chat.message events. This is the explicit path for inspecting past Send or Send + Reply traffic; it does not change the Mail cursor.
Args:
{
group_id: string
actor_id: string
by?: string
mode?: "all" | "send" | "request_reply" | "mail"
query?: string
before_event_id?: string
limit?: number
}Result:
{ messages: CCCSEventV1[]; has_more: boolean }Results are newest-first, limited to the actor's current generation, and MUST contain only messages sent by or addressed to that actor. The operation is read-only and MUST NOT append mail.read or mutate any delivery state.
8.8 Context and Tasks
context_get
Args:
{ group_id: string; detail?: "overview" | "summary" | "full" }Result:
{
version: string
tasks_version: string
coordination: {
brief: {
objective: string
current_focus: string
constraints: string[]
project_brief: string
project_brief_stale: boolean
updated_by: string
updated_at: string
}
tasks?: Array<Record<string, unknown>>
recent_decisions?: Array<{ at: string; by: string; summary: string; task_id?: string | null }>
recent_handoffs?: Array<{ at: string; by: string; summary: string; task_id?: string | null }>
}
agent_states: Array<{
id: string
hot: {
active_task_id?: string | null
focus?: string | null
blockers?: string[]
next_action?: string | null
}
warm: {
what_changed?: string | null
open_loops?: string[]
commitments?: string[]
environment_summary?: string | null
user_model?: string | null
persona_notes?: string | null
}
updated_at?: string | null
}>
actors_runtime?: Array<Record<string, unknown>>
tasks_summary?: {
total: number
done: number
active: number
planned: number
archived: number
root_count?: number
}
attention?: {
blocked?: number | Array<Record<string, unknown>>
waiting_user?: number | Array<Record<string, unknown>>
pending_handoffs?: number | Array<Record<string, unknown>>
}
board?: {
planned?: Array<Record<string, unknown>>
active?: Array<Record<string, unknown>>
done?: Array<Record<string, unknown>>
archived?: Array<Record<string, unknown>>
}
meta?: Record<string, unknown>
}Notes:
- Task objects returned in
coordination.tasks,board, ortask_listincludetask_type. - Daemon IPC defaults
detailtofull; the Web HTTP route defaults it tosummaryfor routine refreshes. detail="overview"does not read task files and omitscoordination.tasks,tasks_summary,attention, andboard. It retains the coordination brief, recent decisions/handoffs, agent states,version,tasks_version, and metadata. Web startup and the context modal use this projection before loading task pages separately.detail="summary"omitsboard, recent coordination notes, and live runtime probing. Itsattentionfields are counts, but each task incoordination.tasksMUST retain every task-editor field, includingoutcome,notes, andchecklist, so a summary refresh cannot erase a client's editable draft.detail="full"returns the complete task objects and board projections.- MCP convenience tools MUST preserve their focused response contracts instead of exposing this complete snapshot:
cccc_coordination(action="get")returns onlyversion,coordination,attention,board, andtasks_summary, whilecccc_agent_state(action="get")returns onlyversionplus the selectedagent_state(oragent_stateswhen no actor is selected).include_warm=falsekeeps onlyid,hot, andupdated_at; archived tasks remain hidden from coordination unlessinclude_archived=true.
context_sync
Args:
{ group_id: string; by?: string; ops: Array<Record<string, unknown>>; dry_run?: boolean; if_version?: string }Operation item shape (normative minimum):
type ContextOpV1 = { op: string } & Record<string, unknown>Notes:
- Unknown op names SHOULD be rejected.
- See
docs/standards/CCCC_CONTEXT_OPS_V1.mdfor the v3 operation list and permission/storage failure semantics. if_versionis compared with the current locked snapshot; a mismatch returnsversion_conflict.- Invalid or unauthorized batches do not persist. Unreadable canonical state and persistence failures return
io_error; after such a failure, reload before retrying because per-file atomic writes do not imply multi-file rollback.
Result:
{
success: true
dry_run: boolean
changes: Array<Record<string, unknown>>
version: string
space_sync?: {
queued: boolean
reason?: "not_bound" | "binding_inactive" | "missing_remote_space_id" | "provider_disabled" | "enqueue_failed"
deduped?: boolean
job_id?: string
provider?: "notebooklm"
kind?: "context_sync"
idempotency_key?: string
error?: string
}
}memory_reme_layout_get
Args:
{ group_id: string }Result:
{
group_label: string
memory_root: string
memory_file: string
daily_dir: string
today_daily_file: string
backend: { name: "local"; vector_enabled: false; fts_enabled: true }
}memory_reme_index_sync
Args:
{
group_id: string
mode?: "scan" | "rebuild" // default "scan"
}Result:
{
indexed_files: number
indexed_chunks: number
watched_paths: string[]
last_sync_at: string
}memory_reme_search
Args:
{
group_id: string
query: string
max_results?: number // 1..50, default 5
min_score?: number // 0..1, default 0.1
sources?: string[] // default ["memory"]
vector_weight?: number // 0..1 (optional)
candidate_multiplier?: number // 1..20 (optional)
}Result:
{
hits: Array<{
path: string
start_line: number
end_line: number
score: number
snippet: string
source: string
raw_metric?: number
metadata: Record<string, unknown>
}>
count: number
took_ms: number
}memory_reme_get
Args:
{
group_id: string
path: string
offset?: number // 1-indexed, default 1
limit?: number // default 200
}Result:
{
path: string
offset: number
limit: number
total_lines: number
content: string
}memory_reme_context_check
Args:
{
group_id: string
messages: Array<{ role: string; name?: string; content: string }>
context_window_tokens?: number
reserve_tokens?: number
keep_recent_tokens?: number
}Result:
{
needs_compaction: boolean
token_count: number
threshold: number
messages_to_summarize: Array<Record<string, unknown>>
turn_prefix_messages: Array<Record<string, unknown>>
left_messages: Array<Record<string, unknown>>
is_split_turn: boolean
cut_index: number
}memory_reme_compact
Args:
{
group_id: string
messages_to_summarize: Array<{ role: string; name?: string; content: string }>
turn_prefix_messages?: Array<{ role: string; name?: string; content: string }>
previous_summary?: string
language?: string
return_prompt?: boolean
}Result:
{ summary: string } | { prompt: Record<string, string> }memory_reme_daily_flush
Args:
{
group_id: string
messages: Array<{ role: string; name?: string; content: string }>
date?: string // YYYY-MM-DD
version?: string // default "default"
language?: string // default "en"
return_prompt?: boolean
signal_pack?: Record<string, unknown>
signal_pack_token_budget?: number // default 320
dedup_intent?: "new" | "update" | "supersede" | "silent" // default "new"
dedup_query?: string
}Result:
{
status: "written" | "silent"
reason?: "empty_summary" | "precheck_silent" | "persistence_idempotency_key" | "persistence_content_hash"
target_file: string
content_hash: string
bytes_written: number
signal_pack?: {
schema: string
token_budget: number
token_estimate: number
truncated: boolean
}
dedup?: {
intent: "new" | "update" | "supersede" | "silent"
query: string
candidate_count: number
top_score: number
precheck_decision: "new" | "update" | "supersede" | "silent"
final_decision: "new" | "update" | "supersede" | "silent"
final_reason: "accepted" | "empty_summary" | "precheck_silent" | "persistence_idempotency_key" | "persistence_content_hash"
decision: "new" | "update" | "supersede" | "silent" // alias of final_decision
hits: Array<{ path: string; start_line: number; score: number }>
error?: string
}
}memory_reme_write
Args:
{
group_id: string
target: "memory" | "daily"
content: string
date?: string // required when target="daily"
mode?: "append" | "replace" // default "append"
idempotency_key?: string
actor_id?: string
source_refs?: string[]
tags?: string[]
supersedes?: string[]
dedup_intent?: "new" | "update" | "supersede" | "silent" // default "new"
dedup_query?: string
}Result:
{
file_path: string
line_count: number
content_hash: string
status: "written" | "silent"
reason?: "precheck_silent" | "persistence_idempotency_key" | "persistence_content_hash"
dedup?: {
intent: "new" | "update" | "supersede" | "silent"
query: string
candidate_count: number
top_score: number
precheck_decision: "new" | "update" | "supersede" | "silent"
final_decision: "new" | "update" | "supersede" | "silent"
final_reason: "accepted" | "precheck_silent" | "persistence_idempotency_key" | "persistence_content_hash"
decision: "new" | "update" | "supersede" | "silent" // alias of final_decision
hits: Array<{ path: string; start_line: number; score: number }>
error?: string
}
}task_list
Args:
{
group_id: string
task_id?: string
task_ids?: string // comma-separated exact ids, at most 100
status?: "planned" | "active" | "done" | "archived"
statuses?: string // comma-separated statuses for an atomic multi-column page
query?: string
assignee?: string // use "__unassigned__" for tasks without an assignee
attention?: "blocked" | "waiting_user" | "handoff" | "unassigned"
offset?: number
limit?: number // 1..100
include_index?: boolean
}Result:
// Exact lookup when task_id is present:
{
task: Record<string, unknown> & { children: Array<Record<string, unknown>> }
tasks_version: string
delete_info: { allowed: boolean; total: number; reason: string }
}
// Batch exact lookup when task_ids is present:
{
tasks: Array<Record<string, unknown>> // requested order; missing ids omitted
tasks_version: string
}
// Paged listing when limit is present:
{
tasks: Array<Record<string, unknown>>
count: number
total_count: number
offset: number
limit: number
has_more: boolean
tasks_version: string
facets: {
status_counts: Record<string, number>
blocked: number
waiting_user: number
pending_handoffs: number
unassigned: number
assignees: string[]
}
}
// Atomic multi-column listing when statuses is present:
{
pages: Partial<Record<"planned" | "active" | "done" | "archived", {
tasks: Array<Record<string, unknown>>
count: number
total_count: number
offset: number
limit: number
has_more: boolean
}>>
tasks_version: string
facets: {
status_counts: Record<string, number>
blocked: number
waiting_user: number
pending_handoffs: number
unassigned: number
assignees: string[]
}
task_index?: Array<{
id: string
title: string
status: string
assignee?: string | null
parent_id?: string | null
}>
}
// Compatibility listing when task_id, task_ids, statuses, and limit are absent:
{ tasks: Array<Record<string, unknown>> }Notes:
- Returned task objects include
task_type. task_idtakes precedence over other arguments, followed bytask_ids.statusandstatusesMUST NOT be combined. Astatusesrequest reads one task snapshot and returns every requested column at the sametasks_version.include_index=trueadds an unfiltered, non-archived, lightweight task index. It is intended for relationship selectors; it is not a substitute for exact task detail.- Filters are applied before pagination. Planned tasks sort newest-created first; other columns sort most-recently-updated first, with numeric task id as a stable tie-breaker.
offsetrequireslimit.tasks_versionis the task-specific revision, not the broader context revision. Clients MUST discard a continuation response and restart all loaded pages when it differs from the initial page revision.
presence_get has been removed. Agent state is returned in context_get.result.agent_states.
8.9 Structured Runtime State
headless_status
Args:
{ group_id: string; actor_id: string }Result:
{ state: Record<string, unknown> }headless_control_state / headless_control
Read returns { state: null | { generation, runtime, runtime_mode, runner, running, working, permissions } } for the actual managed session. The Web port exposes GET/POST /api/v1/groups/{group_id}/actors/{actor_id}/headless/control. Read does not create a session. Write requires by="user", exact generation and { action:"permission", request_id, allow:boolean }, { action:"interaction", request_id, response } or { action:"cancel" }. Permission metadata contains the pending operation for user review, never private environment/configuration. Cursor blocking cursor/ask_question and cursor/create_plan requests MUST remain explicit user decisions even under tool YOLO. Metadata uses kind=question with questions or kind=plan with overview / plan; responses contain the documented Cursor outcome (answered/skipped/cancelled or accepted/rejected/cancelled). Answers MUST match all pending question IDs and allowed option IDs/cardinality. CCCC MUST bind pending interactions to the active session generation and turn, retire them on cancellation/completion/disconnect, and reject arbitrary RPC replies. Voice structured controls use the same interaction action and validator. request_id MUST be an opaque CCCC-generated token unique to each pending interaction and bound to its exact turn; clients MUST echo it unchanged. Provider JSON-RPC ids remain private to the adapter and MUST NOT serve as these tokens, because providers may reuse them across turns. Stale tokens, generations and settled requests fail without modifying another session. Cancellation stops the current turn; ordinary queued Group messages remain owned by the ledger.
headless_set_status
Args:
{ group_id: string; actor_id: string; status: "idle" | "working" | "waiting" | "stopped"; task_id?: string | null }Result:
{ state: Record<string, unknown> | null }8.10 System Notifications (Not Chat)
system_notify
Args:
{
group_id: string
by?: string
kind?: string
priority?: "low" | "normal" | "high" | "urgent"
title?: string
message?: string
target_actor_id?: string | null
im_visibility?: "internal" | "public" // default: "internal"
context?: Record<string, unknown>
}system.notify is internal by default. An IM bridge may forward it only when im_visibility="public"; actor-targeted notifications are never eligible for external IM delivery. Producers must opt in explicitly instead of relying on to, actor_id, or target_actor_id inference.
Result:
{ event: CCCSEventV1 } // kind="system.notify"There is no generic notification acknowledgement operation. Domain workflows must expose domain lifecycle operations; chat reply obligations use request_reply and reply_request_cancel.
8.11 Terminal Diagnostics and Attach
terminal_tail
Args:
{ group_id: string; actor_id: string; by?: string; max_chars?: number; strip_ansi?: boolean; compact?: boolean }max_chars limits the final returned Unicode text. Implementations MUST render the complete retained PTY backlog before applying this limit; truncating the raw ANSI/VT byte stream first can start replay inside an escape sequence or incremental screen update and produce corrupt snapshots.
Result:
{ group_id: string; actor_id: string; warning: string; hint: string; text: string; end_cursor: number }end_cursor is the exclusive raw PTY byte cursor captured with the backlog used to produce text. A terminal client MAY display the rendered snapshot and then attach its live stream with since=end_cursor; the stream must replay output produced after the snapshot so the transition is gap-free.
terminal_snapshot
Return a bounded rendered screen snapshot and the exact raw cursor boundary used to render it. This operation is intended for diagnostics; interactive clients use terminal_replay so they can rebuild scrollback from the original ANSI stream.
Args:
{ group_id: string; actor_id: string; by?: string; limit_bytes?: number }Result:
{ data: string; start_cursor: number; end_cursor: number }The implementation MUST apply the same group transcript visibility policy as terminal_tail and terminal_history.
terminal_replay
Return one bounded page of raw ANSI output from the active PTY session's in-memory ring. This operation MUST NOT read the durable archive or a completed session. The first request atomically captures a UTF-8-complete replay_end_cursor. Callers pass that value back as end_cursor on every following page, so output produced during replay cannot extend the initial replay loop.
Args:
{ group_id: string; actor_id: string; by?: string; after?: number; end_cursor?: number; limit_bytes?: number }Result:
{
replay_end_cursor: number
history: {
data: string
start_cursor: number
end_cursor: number
has_more: boolean
cursor_expired: boolean
}
}The default page limit is 512 KiB. history.has_more is relative to the fixed replay_end_cursor, not the moving live tail. The implementation MUST apply the same group transcript visibility policy as terminal_tail and must leave an incomplete UTF-8 suffix for a later live page.
terminal_history
Args:
{ group_id: string; actor_id: string; by?: string; before?: number; render_before?: number; limit_bytes?: number; strip_ansi?: boolean; compact?: boolean }Result:
{
group_id: string
actor_id: string
warning: string
hint: string
text: string
start_cursor: number
end_cursor: number
has_more: boolean
cursor_expired: boolean
}For backward paging of rendered text, pin the first response's end_cursor as render_before on subsequent requests. before and limit_bytes select the next older page's start, while the returned range extends through render_before. The server renders that contiguous range once; clients replace their previous rendering rather than concatenate independently rendered pages. The cumulative range is limited to 50 MB and excludes output written after the pinned end. If retention or terminal clear overtakes the pinned range, return an empty page with cursor_expired=true, has_more=false, and both cursors at the pinned end, not invalid_args. Clients retain already displayed text when the older cursor does not advance and show the existing expired-history warning. Omitting render_before preserves the existing per-page contract. With strip_ansi=true, history text preserves inferred pre-redraw frames in chronological order (blank-line separated), including overwritten and erased screens. Identical consecutive frames are collapsed; these are inferred terminal states, not timestamped captures. Rendered frames are bounded to 50 MB; exceeding that display budget inserts an explicit omission marker before the retained newest frames. Scrolled-off history lines are retained separately from the 4,096-row screen, so expanding a cumulative range MUST NOT silently discard previously returned newer lines. This scrollback uses the same display budget and omission marker. This does not modify raw history.data, cursor semantics, terminal_tail, or live snapshots.
terminal_since
Args:
{ group_id: string; actor_id: string; by?: string; after: number; limit_bytes?: number }Result:
{
history: {
data: string
start_cursor: number
end_cursor: number
has_more: boolean
cursor_expired: boolean
}
}The cursors count raw PTY bytes. Because data is transported as UTF-8 JSON text, an implementation MUST NOT advance end_cursor through an incomplete UTF-8 code point. It MAY return up to three bytes beyond limit_bytes to finish a code point. If the retained stream currently ends inside a code point, it returns the complete prefix and leaves the incomplete suffix for a later call.
terminal_clear
Args:
{ group_id: string; actor_id: string; by?: string }Result:
{ group_id: string; actor_id: string; cleared: true }debug_tail_logs
Tail daemon/web/im-bridge log files (developer mode).
Args:
{ component: "daemon" | "ccccd" | "web" | "im" | "im_bridge"; group_id?: string; by?: string; lines?: number }Result:
{ component: string; group_id: string; path: string; lines: string[] }debug_clear_logs
Truncate daemon/web/im-bridge log files (developer mode).
Args:
{ component: "daemon" | "ccccd" | "web" | "im" | "im_bridge"; group_id?: string; by?: string }Result:
{ component: string; group_id: string; path: string; cleared: true }term_resize
Args:
{ group_id: string; actor_id: string; cols: number; rows: number; attachment_id?: number }cols MUST be in 10..=65535 and rows MUST be in 2..=65535; invalid or missing dimensions return invalid_size without resizing the PTY. When term_attachment_status=true, WebSocket attachment bridges MUST include the positive attachment_id returned by term_attach. The daemon atomically verifies that the attachment is still the current writer before resizing; a stale or viewer attachment returns terminal_not_writer. Legacy clients and daemons that advertise the capability as false omit the field.
Result:
{ group_id: string; actor_id: string; cols: number; rows: number }term_attachment_status
Optional extension, advertised by ping.capabilities.term_attachment_status. Clients MUST NOT assume this operation is available when that capability is false.
Args:
{ group_id: string; actor_id: string; attachment_id: number }Result:
{ terminal_writable: boolean }term_attach (streaming upgrade)
Args:
{
group_id: string
actor_id: string
since?: number
mode?: "control" | "viewer"
takeover?: boolean
bootstrap?: "snapshot_v1"
cols?: number
rows?: number
}Result (handshake):
{
group_id: string
actor_id: string
attachment_id?: number
terminal_mode: "control" | "viewer"
terminal_writable: boolean
writer_replaced: boolean
replay_cursor: number
replay_end_cursor: number
initial_output?: {
kind: "replay" | "snapshot"
bytes: number
cursor: number
cols?: number
rows?: number
}
}After a successful handshake, the connection becomes a terminal stream (see §4.4).
Notes:
term_resizeMUST be sent over a separate daemon connection (the PTY stream is not NDJSON).term_attachreturnsnot_pty_actorwhen the actor is not effectively running on the PTY runner.- Control mode MAY recreate a missing native terminal for an already-running managed Actor without replacing its provider. Viewer mode only attaches to an existing terminal; it MUST NOT launch one. Neither mode starts a stopped provider.
attachment_idandinitial_outputare optional extensions. Callers MUST consultping.capabilities.term_attachment_statusandping.capabilities.term_attach_snapshot_v1before depending on them. The baseline handshake always provides replay cursors and streams retained output fromreplay_cursorthroughreplay_end_cursorbefore live PTY bytes.- A successful
term_attachowns a dedicated connection and MUST NOT be returned to an NDJSON request pool. Rust daemon and Web implementations use this raw stream directly; terminal output is not transported through polling RPCs. replay_cursorandreplay_end_cursorMUST come from the same backlog snapshot that is queued for this attachment; sampling either cursor before the actual attach is not sufficient.- For
initial_output.kind="replay", bytes in[replay_cursor, replay_end_cursor)are retained history. Clients MUST NOT send terminal-generated query replies while rendering that historical range; only live output afterreplay_end_cursormay generate PTY input. - For
initial_output.kind="snapshot",replay_cursor,replay_end_cursor, andinitial_output.cursorMUST be equal. Exactlyinitial_output.bytesbytes at the start of the upgraded daemon stream encode the ANSI snapshot and do not consume raw cursor space. All bytes after that payload are raw PTY output beginning atinitial_output.cursor. colsandrowsare optional snapshot-size hints (10..=4096and2..=4096). The Rust daemon applies them only to a control attach withtakeover=true; viewer and non-takeover attaches do not resize the shared PTY. Writer registration, this initial resize, and initial-output capture MUST be serialized as one runtime operation so concurrent takeovers cannot return a snapshot at another controller's dimensions.- The WebSocket bridge maps a negotiated snapshot to opcode
7in one binary frame. The browser MUST resize its local xterm parser to the advertised snapshotcols/rowswhen present, reset xterm, parse that frame, and only then commit/ackinitial_output.cursor. It MAY refit the local viewport after parsing, but a viewer MUST NOT resize the shared PTY. Opcode1remains raw replay/live output, and its payload length advances the raw cursor. - A WebSocket bridge MAY negotiate
output_flow=ack_v1. The attach frame then advertisesoutput_flow_control.protocol="ack_v1"and a boundedwindow_bytes. After xterm has parsed an output frame, the browser sends opcode5with{cursor}. The bridge MUST bound unacknowledged output and MUST continue accepting input while replay is waiting for acknowledgements. Clients and bridges that do not negotiate this extension retain the legacy stream behavior. - The WebSocket bridge sends opcode
6with{terminal_writable}whenever takeover or disconnect changes the attachment's writer ownership. Clients MUST update their writable state from this frame instead of retaining the handshake value for the lifetime of the connection.
8.12 Ledger Maintenance
ledger_snapshot
Args:
{ group_id: string; by?: string; reason?: string }Result:
{ snapshot: Record<string, unknown> }Snapshot metadata counts and hashes the canonical Event array in ledger append order, including sealed plain or gzip segments. Snapshot and compaction MUST reject malformed JSON and records that cannot be decoded as Event objects before publishing snapshot metadata or rotating the active file. They MUST NOT silently omit unreadable records from a successful integrity report. Maintenance scans do not require a full-history query index. Stored records MUST contain their nonempty id and ts; maintenance MUST NOT invent those fields using construction defaults. Deterministic defaults for omitted optional fields and the snapshot hash format remain unchanged for canonical records.
ledger_compact
Args:
{ group_id: string; by?: string; reason?: string; force?: boolean }Result: implementation-defined compaction report.
For implementations that share a CCCC home, sealed ledger segment bytes remain part of the append-only source of truth even when a crash occurs before their manifest entry is published. Before rotating another active ledger, compaction MUST reconcile every unambiguous canonical segment present on disk and allocate a sequence greater than every physical or manifested segment sequence. It MUST NOT reuse an unpublished physical sequence. If distinct canonical segment files already claim the same sequence, compaction MUST fail before another rotation rather than silently selecting, overwriting, or hiding either file. Compaction MUST hold the same canonical ledger writer lock used by appenders from its source snapshot through active-ledger replacement and manifest publication. It MUST NOT rotate or report success while an earlier writer still owns that lock; snapshot and segment metadata MUST include every write committed before the lock is released to compaction.
8.13 Presentation State
The canonical group Presentation snapshot is groups/<group_id>/state/presentation.json. Ports MUST use the daemon operations below for snapshot mutations; a browser-surface session is ephemeral and is not part of this durable state.
presentation_get
Args:
{ group_id: string }Result:
{
group_id: string
presentation: {
v: 1
updated_at: string
highlight_slot_id: "" | "slot-1" | "slot-2" | "slot-3" | "slot-4"
slots: Array<{
slot_id: "slot-1" | "slot-2" | "slot-3" | "slot-4"
index: 1 | 2 | 3 | 4
card?: Record<string, unknown>
}>
}
}presentation_publish
Args:
{
group_id: string
by?: string
slot?: "auto" | "slot-1" | "slot-2" | "slot-3" | "slot-4"
card_type?: "markdown" | "table" | "image" | "pdf" | "file" | "web_preview"
title?: string
summary?: string
source_label?: string
source_ref?: string
content?: string
table?: Record<string, unknown> | Array<Record<string, unknown>>
path?: string
url?: string
blob_rel_path?: string
}by MUST identify user, system, or an actor in the group. Workspace paths MUST resolve below the active scope. A stored remote url MUST be an absolute HTTP(S) URL with a host; local or generated content uses path, content, or blob_rel_path instead. auto selects the first empty slot, then the oldest published slot.
Result:
{
group_id: string
slot_id: string
card: Record<string, unknown>
presentation: Record<string, unknown>
replaced: boolean
event: CCCSEventV1
event_id: string // compatibility alias of event.id
}The event kind is presentation.publish; its data is {slot_id,title,card_type,source_label,source_ref,summary}.
presentation_clear
Args:
{
group_id: string
by?: string
slot?: "slot-1" | "slot-2" | "slot-3" | "slot-4"
all?: boolean
}by has the same validation as presentation_publish. all=true clears all slots regardless of slot; an omitted/empty slot also means all slots.
Result:
{
group_id: string
slot_id: string // populated only when exactly one occupied slot was cleared
cleared_slots: string[]
presentation: Record<string, unknown>
event: CCCSEventV1
event_id: string // compatibility alias of event.id
}The event kind is presentation.clear; its data is {slot_id,cleared_all,cleared_slots}. For both mutation operations, the snapshot update and ledger event form one acknowledged transition: if event append fails, the prior snapshot MUST be restored before failure is returned.
8.14 Presentation Browser Surface (Optional)
presentation_browser_attach
Attach to the currently active slot browser-surface session over a dedicated bidirectional NDJSON stream.
Args:
{
group_id: string
slot: "slot-1" | "slot-2" | "slot-3" | "slot-4"
by?: string
viewer_mode?: "auto" | "screencast" | "vnc"
}Handshake result:
{ group_id: string; slot_id: string }Streaming mode:
- After a successful handshake, the connection upgrades into the browser-surface stream described in §4.6.
- The daemon emits
stateitems when runtime/session status changes andframeitems for captured browser frames. - The client MAY send browser-control commands (
navigate,back,refresh,click,scroll,key,text,resize,close,disconnect). - At most one active controller MAY be attached at a time; a second attach attempt SHOULD fail with a busy-style error.
- If no active browser-surface session exists for the slot, attach SHOULD fail with
browser_surface_not_found. - If the underlying browser runtime is no longer active, attach SHOULD fail with
browser_surface_not_active.
presentation_browser_vnc_attach
Attach to the currently active slot browser-surface session over a raw RFB/VNC stream.
Args:
{
group_id: string
slot: "slot-1" | "slot-2" | "slot-3" | "slot-4"
by?: string
}Handshake result:
{ group_id: string; slot_id: string }Streaming mode:
- After a successful handshake, the connection upgrades into a raw VNC/RFB byte stream.
- The operation SHOULD fail with
browser_vnc_unavailablewhen the browser surface is not backed by a local VNC projection.
8.15 Event Streaming (Optional)
events_stream
Subscribe to new ledger events for a group.
Args:
{
group_id: string
by?: string
since_event_id?: string | null // resume strictly after this event (preferred)
since_ts?: string | null // best-effort resume using timestamps
kinds?: string[] | null // optional kind allowlist (exact match)
}Handshake result:
{ group_id: string }Streaming mode:
- The daemon pushes NDJSON
EventStreamItemlines (see §4.5). - A daemon may initially emit only a subset of event kinds. CCCC streams these kinds:
chat.message,mail.read,chat.reply_request.cancelled,runtime.delivery,system.notify
- When
kindsis provided, only matching event kinds SHOULD be emitted. - If
byidentifies anactor_id, a daemon MAY apply the same recipient-routing visibility rules used by messaging (e.g., only emitchat.message/system.notifyaddressed to that actor and exclude the actor’s ownchat.messageevents). This stream filter is independent of the Mail-only Inbox projection. - Resume (
since_event_id/since_ts) is best-effort in v1; clients MUST be able to reconcile usinginbox_peek. - Ledger compaction preserves event IDs. An established follower MUST retain unseen events across rotation and refill, and MUST NOT advance its cursor when a busy writer defers polling. A missing previously observed cursor is an explicit recovery error, not permission to skip events.
- The stream ends when the client closes the connection or the daemon exits.
- To protect daemon responsiveness, a daemon MAY drop slow subscribers (clients SHOULD reconnect and reconcile).
8.16 IM Authentication
The durable group-local IM authority is group.yaml:im for provider configuration and the sibling state files im_pending_keys.json, im_authorized_chats.json, and im_subscribers.json for delivery targets. Implementations MUST serialize reads that can cause a write and every read-modify-write across those classes with groups/<group_id>/state/im_state.lock; a long-running worker MUST refresh authorization and subscription truth after acquiring that lock rather than continue from a process-private startup snapshot. Binding and revocation MUST update their coupled authorization/subscription records under one such transaction.
The former Rust group.yaml:im_bridge durable fields (config, enabled, authorized, pending, and subscribers) are a one-way migration source. Canonical classes win when present, and imported fields MUST be retired after canonical commit. An explicit IM unset MUST clear the canonical target files and consume those legacy durable fields so a later native load cannot restore configuration or delivery authority. Non-durable runtime diagnostics in im_bridge MAY remain.
Across IM authentication and subscriber state, thread_id is a platform-owned opaque identifier. Implementations MUST preserve it as either a legacy JSON number or a non-empty JSON string and MUST NOT coerce string identifiers (for example, Slack timestamps such as 1710000000.100) through an integer or floating-point representation. Omitted, null, empty, integer 0, and string "0" mean no thread; unsupported JSON value types MAY normalize to numeric 0.
im_bind_chat
Bind a pending one-time key to authorize an IM chat. On success the chat is also auto-subscribed for outbound message delivery.
Args:
{ group_id: string; key: string }Result:
{ chat_id: string; thread_id: number | string; platform: string }Errors:
missing_key–keyis empty.missing_group_id–group_idis empty.group_not_found– group does not exist.invalid_key– key not found or expired.
im_list_authorized
List all authorized IM chats for a group.
Args:
{ group_id: string }Result:
{ authorized: Array<Record<string, unknown>> }Errors:
missing_group_id–group_idis empty.group_not_found– group does not exist.
im_list_pending
List pending one-time bind requests for a group (expired keys are omitted).
Args:
{ group_id: string }Result:
{
pending: Array<{
key: string
chat_id: string
thread_id: number | string
platform: string
created_at: number
expires_at: number
expires_in_seconds: number
}>
}Errors:
missing_group_id–group_idis empty.group_not_found– group does not exist.
im_reject_pending
Reject a pending one-time bind key.
Args:
{ group_id: string; key: string }Result:
{ rejected: boolean } // idempotent: false when key is already absent/expiredErrors:
missing_key–keyis empty.missing_group_id–group_idis empty.group_not_found– group does not exist.
im_revoke_chat
Revoke authorization for an IM chat.
Args:
{ group_id: string; chat_id: string; thread_id?: number | string }Result:
{ revoked: boolean; unsubscribed?: boolean }Notes:
thread_iddefaults to0when omitted or represented by an unsupported JSON value type.
Errors:
missing_chat_id–chat_idis empty.missing_group_id–group_idis empty.group_not_found– group does not exist.
8.17 Remote Access (Contract-Gated)
These operations are optional extensions for productized remote-access control. Deployments without this feature MAY return unknown_op.
remote_access_state
Read global remote-access state.
Args:
{ by?: string }Result:
{
remote_access: {
provider: "off" | "manual" | "tailscale" | "reach"
mode: string
require_access_token: boolean
enabled: boolean
status: "stopped" | "running" | "not_installed" | "not_authenticated" | "misconfigured" | "error"
endpoint?: string | null
updated_at?: string | null
diagnostics?: {
access_token_present?: boolean
access_token_source?: "store" | "none" | string
access_token_count?: number
admin_access_token_present?: boolean
admin_access_token_count?: number
remote_listener_auth_required?: boolean
remote_listener_auth_requirement_satisfied?: boolean
allow_unauthenticated_listener_override?: boolean
web_host?: string
web_host_source?: "settings" | "env" | "default" | string
web_port?: number
web_port_source?: "settings" | "env" | "default" | string
web_public_url?: string | null
web_public_url_source?: "settings" | "env" | "none" | string
web_bind_loopback?: boolean
web_bind_reachable?: boolean
mode_supported?: boolean
tailscale_installed?: boolean | null
tailscale_backend_state?: string | null
[k: string]: unknown
}
config?: {
web_host?: string
web_port?: number
web_public_url?: string | null
access_token_configured?: boolean
access_token_count?: number
admin_access_token_configured?: boolean
admin_access_token_count?: number
access_token_source?: "store" | "none" | string
[k: string]: unknown
}
next_steps?: string[]
}
}remote_access_configure
Update global remote-access configuration.
Args:
{
by?: string
provider?: "off" | "manual" | "tailscale"
mode?: string
require_access_token?: boolean
web_host?: string
web_port?: number
web_public_url?: string
}Result:
{ remote_access: Record<string, unknown> }A non-local Web binding or a configured public URL requires at least one administrator Access Token before it can be started or applied. Group-scoped tokens do not satisfy this recovery/control-plane requirement. Localhost-only configuration remains available without a token. Implementations MAY expose CCCC_WEB_ALLOW_UNAUTHENTICATED=1 as an explicit unsafe listener override for deployments that already enforce a trusted network boundary.
remote_access_start
Start remote access according to configured provider/mode.
Tailscale start/stop commands have a 30-second deadline and bounded captured output. Missing executables report remote_access_not_installed; command failure or timeout reports remote_access_start_failed / remote_access_stop_failed. An error does not mark the desired enabled state as successfully changed. A timeout does not roll back external network changes; inspect Tailscale before retrying. Other providers retain their own lifecycle semantics.
Args:
{ by?: string }Result:
{ remote_access: Record<string, unknown> }Errors:
remote_access_admin_token_required– remote exposure has no administrator Access Token and the explicit unsafe listener override is not enabled.
remote_access_stop
Stop remote access service.
Args:
{ by?: string }Result:
{ remote_access: Record<string, unknown> }provider=reach is not set through remote_access_configure. It is owned by the membership reach verbs. Settings may persist reach after a successful membership_reach_on. While Reach is enabled or its tracked helper is still running, remote_access_configure MUST reject every configuration mutation; callers must complete membership_reach_off before changing provider, binding, or public URL.
8.17.1 Membership reach
The device-authenticated Connect directory, read-only connect_status and connect_catalog, user-only connect_group_status / connect_group_select operations, durable connect_send / connect_send_files acceptance, ordinary reply / reply_request_cancel and upload-preflight integration, connect_delivery / connect_cancellation status projections, failure notification recovery, and signed internal connect_peer_receive catalog/message/receipt/cancel port are specified in CCCC_CONNECT_V1.md. Connect is an in-progress extension; it does not give device credentials Web administrator authority. connect_group_status distinguishes not_linked, syncing, ready and unavailable, with sharing-check time and errors. Only an unexpired confirmed empty grant means no connections; GET does not perform synchronization.
Optional extension for third-party deployments. The bundled native implementation implements the complete operation set below. Deployments without membership MAY return unknown_op.
Stable error classes:
membership_not_logged_in– no local device binding exists for an operation that requires onemembership_gate– missing Admin Token, unauthenticated-listener override, or another remote provider is already onmembership_disabledmembership_networkmembership_subprocessmembership_unsupported_versionmembership_unavailable– account plane origin is not configuredmembership_not_in_reach
membership_status
{ by?: string }{
membership: {
logged_in: boolean
device_id?: string | null
account_label?: string | null
hostname?: string | null
web_url?: string | null
online: boolean
cut: boolean
disabled: boolean
in_reach: boolean
reach_enabled: boolean
reach_status: "off" | "connecting" | "online" | "offline" | "unknown"
checked_at?: string
reach_supported: boolean
account_reachable?: boolean | null
account_origin?: string | null
last_error?: string | null
warning?: string
pending?: {
user_code: string
verification_uri: string
verification_uri_complete?: string | null
interval: number
expires_at: string
} | null
}
}membership_status is user-only. Implementations MUST reject non-user callers before assembling it. hostname is the reserved, tokenless device origin; its presence does not prove that DNS or a tunnel has been provisioned. web_url is the tokenless Web sign-in address, assembled locally and null while logged out. It MUST NOT contain a bearer credential. The Web port can separately issue a short-lived, one-time Web login grant for the current authorized administrator. Website account sign-in does not authenticate a browser to the local CCCC Web. Private Web Model connector URLs remain part of the administrator-only instance connector API and MUST NOT be selected or exposed through global membership status.
account_label is optional display-only identity (currently the verified account email). The authenticated device status and Connect directory refresh synchronize it to the issuer-bound local membership state. New device grants clear the prior label; cut/unlinked devices never expose it. Connect refresh MUST clear it on confirmed device or credential rejection, independently of membership_status. Transient transport failures retain it, and updates remain bound to the device and issuer that initiated the request. Older issuers may omit it. It does not identify the browser's Web Access Token principal or grant any Web rights.
reach_enabled is the saved Reach intent; in_reach only identifies the selected provider. Neither proves connectivity. reach_status is an ephemeral projection: off when stopped or unlinked/cut, connecting immediately after an accepted start, offline when enabled but the helper is absent or the account reports a disconnected/unprovisioned tunnel, and unknown when the running helper has no current connection confirmation. online=true and reach_status=online require enabled Reach, a live tracked helper, and current account-side connection evidence. A successful process start or an unavailable status service MUST NOT produce online=true. Mutating operations do not claim unperformed connection checks. checked_at is the UTC time of a completed status refresh, including a failed account check; it is not a cached last-success timestamp.
The account device API adds connection: not_started | online | offline | unknown alongside its existing fields. Native clients preserve unknown, including unrecognized future states; when the field is absent on an older issuer, they use the existing online boolean. This is an additive v1 change.
Tunnel connection evidence is not proof of origin application availability or browser authorization. Clients label it accordingly and offer the Web sign-in flow for an actual access check. After an explicit start, clients MAY perform bounded status confirmation while the panel is visible; they MUST stop on a deadline and MUST NOT reissue Reach start or change configuration as a retry. Disconnected Reach still owns its provider/binding settings until explicitly stopped; configuration controls must not infer ownership from online.
account_reachable is ephemeral evidence from the current status refresh; it is omitted when no linked-device probe applies, true after a valid account-plane response, and false after a transient account-plane failure. It MUST NOT be persisted as a second freshness state machine.
reach_supported reports whether this CCCC build provides a pinned managed Reach helper for the current platform. It is independent of account linkage and helper installation: an unsupported platform can still link and manage its CCCC Account, but MUST NOT present Reach as startable.
Status refresh may observe an account-side Cut or learn that the bearer for an already-linked device is definitively absent. Both are terminal revocations: the daemon MUST stop the tracked helper, persist the disabled state, and clear Reach-owned enabled / web_public_url state before returning. A timeout, DNS failure, 5xx response, or malformed response is transient and MUST preserve the binding and helper state. Daemons therefore MUST serialize membership_status with membership mutations rather than treating it as a side-effect-free read. Membership and remote-access operations share exclusive ownership, but their network waits MUST NOT hold the global Group read/write permit. Global mutations and Reach restore commits also acquire this ownership before their global permit; restore still fetches outside both permits and verifies its captured intent before committing. Ordinary Group reads and writes remain available while the account service is slow.
membership_login / membership_login_poll / membership_logout
{ by?: string }membership_login starts RFC 8628 device-code login against CCCC_ACCOUNT_ORIGIN and returns membership.pending (verification_uri, optional verification_uri_complete, user_code, interval). While an unexpired, still-pollable grant exists, another membership_login MUST replay it rather than issue a second device code or retarget it to a different origin. Both verification URLs MUST be absolute HTTP(S) URLs on the configured account origin; clients reject an off-origin authorization URL before storing or opening it. The selected account origin is persisted with the pending login and resulting device grant. Polling and every later authenticated device or Reach request MUST use that issuer-bound origin; a changed daemon environment or per-request override MUST NOT retarget an existing bearer token. The CLI or Web client opens verification_uri_complete when present and otherwise presents verification_uri plus user_code. The advertised interval is a minimum and MUST NOT be capped downward. On slow_down, subsequent polling waits MUST increase by at least five seconds. authorization_pending, slow_down, transport failures, and 5xx responses preserve the pending grant. access_denied and expired_token are terminal for that grant: the daemon MUST clear the matching pending state before returning the error so the next membership_login can issue a fresh code. Once a grant has been committed, an exact or late membership_login_poll MUST replay the logged-in status instead of failing because the pending code was consumed.
After a successful user-requested login grant (or its replay), the local daemon initializes one administrator Access Token if none exists, under the existing Token store lock. Existing administrator and scoped Tokens are preserved. CLI login and Web login use the same initialization. Status reads and background reconnection/directory refresh never create Tokens. The Web port binds a login cookie only for an already verified direct-loopback local administrator; account membership alone does not grant an anonymous remote browser administrator access. Explicit local Web reach on can also initialize a previously linked installation before the daemon's normal remote-access gate. Tokens never enter account requests or public URLs. Initialization failure is returned and can be retried without reissuing the already committed account grant.
membership_logout first stops any tracked Reach helper and persists disabled Reach intent, then retires the issuer-bound account device through the account plane, and only then clears local membership secrets and retired Reach URLs. Network or account errors preserve the local credential so the user can retry, but MUST NOT re-enable local Reach through background recovery. An unrelated remote-access provider is preserved. An already absent or disabled remote device is treated as retired. The result includes a warning that the next login is a new device and hostname.
Requests send CCCC-Membership-Version: 1. An account plane that no longer supports the client returns membership_unsupported_version. CCCC_ACCOUNT_ORIGIN MUST be an HTTP(S) origin without user information, a non-root path, query, or fragment. It MUST use HTTPS, except that loopback HTTP is allowed for local development. Clients MUST NOT follow account-plane redirects because authenticated requests carry a device bearer token.
For authenticated device endpoints, absence of an Authorization bearer remains 401 unauthorized. When a bearer is present but no active device exists for it (including after device or account deletion), the account plane MUST return 403 device_disabled. Clients talking to an older or third-party issuer MAY also receive 401 or 404; if the request used a locally stored device bearer, those responses are the same terminal revocation, not evidence of a transient network failure. Relinking first retires the invalid local binding and then starts a fresh device-code authorization.
membership_reach_install
{ upgrade?: boolean, by?: string }Installs the pinned cloudflared binary under CCCC_HOME after verifying its platform, version, and SHA-256 digest. With upgrade=true, an existing unpinned or mismatched managed binary is replaced. Installation does not enable remote access or start a tunnel.
membership_reach_on / membership_reach_off
{ by?: string }reach on requires an administrator Access Token, a logged-in device that is not disabled, and an account origin. It MUST refuse if CCCC_WEB_ALLOW_UNAUTHENTICATED is set or if tailscale is already enabled. An enabled manual public URL remains active while Reach is prepared and is replaced only after the Reach helper starts successfully; any pre-commit failure MUST preserve the manual provider and URL. It installs the pinned cloudflared if missing, and refuses a version/hash mismatch unless membership_reach_install (cccc reach install) was used. The account-plane request includes the port of the currently live, identity-verified Web listener as origin_port (1–65535), not merely the desired setting or environment default. The runtime descriptor MUST contain an unguessable Web-instance identifier and an owner-only proof key. Reach MUST send a fresh random challenge, and the loopback /api/v1/ready response MUST return the recorded identifier plus an HMAC-SHA256 proof bound to that challenge before Reach may start. The verifier MUST NOT send the expected identifier or proof key to the listener; a live PID, accepting TCP port, or reflected request value alone is not proof that the listener belongs to CCCC. The account plane MUST route the named tunnel to 127.0.0.1:<origin_port> and MUST NOT accept an arbitrary origin host. A returned Reach hostname MUST normalize to one HTTPS origin without user information, a non-root path, query, or fragment before it can be stored or used to assemble public URLs and origin-bound Web login links. On success it sets remote_access.provider=reach and writes web_public_url.
The tunnel token MUST NOT appear in process arguments; supported helpers use a permission-restricted token file. Before signaling a persisted helper PID, an implementation MUST verify the live executable against the exact managed executable recorded when the helper started (or use an in-process child handle it still owns); process names and argument substrings are insufficient. A mismatch preserves tracking and returns an error instead of killing an unrelated process. reach off keeps provider=reach, but reports success only after the tracked helper has exited and its tracking files are retired. A persisted enabled flag alone is not proof that Reach is online: status requires enabled Reach, a live tracked helper, and confirmation of a connected named tunnel at the account plane. If any authenticated device-status or Reach-issuance response reports the device disabled or definitively missing, the helper is stopped, Reach-owned public state is cleared, and status is cut before the operation returns.
The daemon MUST reconcile saved provider=reach, enabled=true intent after restart without a CLI action, open browser, or status poll. Only a linked, non-disabled device with an administrator Access Token may restore. A running, identity-checked tracked helper is left running; cloudflared owns its transport reconnection. When the helper has exited, restoration waits for the signed live Web binding, verifies the already-installed pinned helper, and requests fresh credentials from the bound account issuer using that Web port. Automatic restoration MUST NOT download or upgrade helpers. Missing/mismatched helpers remain offline with an actionable error and require explicit installation.
Restoration is single-flight. Account requests have a fifteen-second deadline and run outside dispatcher permits; failed attempts back off from five seconds to at most sixty seconds. Local reconciliation checks run every five seconds when idle, without cloud calls for a running helper or disabled Reach. Background work MUST NOT queue a global writer behind Actor startup. Before applying a result it MUST serialize with membership/settings mutations and recheck the saved intent, issuer/device binding, administrator prerequisite, live Web port, and shutdown state. A result superseded by off/logout/relink/configuration changes is discarded; it MUST NOT resurrect access or overwrite the newer state. Definitive device rejection clears the matching Reach intent as above; transient failures preserve it for retry. Status polling observes this work and MUST NOT trigger restoration. Helper startup alone is not online evidence.
Membership state lives in CCCC_HOME/secrets/membership.json. Every read-modify-write mutation MUST hold CCCC_HOME/secrets/membership.json.lock and preserve the full v1 shape, including issuer-bound account_origin, device_token, tunnel_token, and pending_login.
8.17.2 CCCC Connect and manual Bridge retirement
CCCC_CONNECT_V1.md defines current same-account discovery, qualified messaging, delivery receipts, browser authorization, and revocation. Manual pairing/session operations, remote_send, remote_delivery_status, send_cross_group_remote_record, and remote arbitrary-tool endpoints are removed. They MUST NOT be used as fallback routes for Connect or local cross-group sends.
Before serving requests, the daemon retires old pairing, registration, credential, and delivery state under the Home lock. It preserves the stable instance private key and original Group ledgers. Pending operations are never replayed or converted into account authority. Confirmed outcomes remain confirmed; unattempted queued work ends failed; possibly transmitted work ends unconfirmed. Final chat.cross_group_receipt events preserve source scope and operation IDs, carry group_bridge_retired=true, and precede removal of source state. Cleanup is idempotent across interruption and ledger rotation. Deleted Groups are not recreated. Unreadable or unprojectable records remain for inspection and retry at next startup, with a warning; they neither revive old workers nor prevent ordinary local startup. A deduplicated internal system.notify informs the user in each affected Group. Secrets and original payloads MUST NOT appear in it.
ledger_statuses exposes retired_bridge=true for affected historical messages; queries with with_obligation_status decorate those messages with _retired_bridge=true. Consumers preserve that marker across raw replay and hide the reply action. reply and its upload preflight independently reject retired provenance or archived retirement receipts with group_bridge_retired, before writes, even when a local Group happens to share the old destination ID. Historical sender names and reference text remain readable. Ordinary local cross-group send and cancellation are preserved.
8.18 Group Space (Provider-Backed Shared Memory, dual-lane NotebookLM)
These operations provide a thin control-plane for optional external memory providers. Provider failures MUST NOT block core collaboration flows (chat/context/actors).
NotebookLM is modeled as two fixed daemon-owned lanes:
lane="work": project/shared external knowledge, repospace/sync, artifacts, general ingest/query.lane="memory": finalized daily memory recall only; daemon syncsstate/memory/daily/*.mdasynchronously.
Normative lane rules:
- Agent-facing surfaces SHOULD pass
laneexplicitly for mutating or lane-targeted actions. group_space_statusMAY omitlane; it returns both lanes.group_space_bind|query|sources|jobs|syncare lane-targeted.group_space_ingest|artifactare supported only onlane="work".MEMORY.mdMUST remain local-only and MUST NOT be uploaded to NotebookLM.
group_space_status
Read provider mode, both lane bindings, queue summaries, work-lane repo space/ sync state, and memory-lane daily sync summary.
Args:
{ group_id: string; provider?: "notebooklm" }Result:
{
group_id: string
provider: {
provider: "notebooklm"
enabled: boolean
mode: "disabled" | "active" | "degraded"
real_adapter_enabled?: boolean
stub_adapter_enabled?: boolean
auth_configured?: boolean
write_ready?: boolean
readiness_reason?: string
last_health_at?: string | null
last_error?: string | null
}
bindings: {
work: {
group_id: string
provider: "notebooklm"
lane: "work"
remote_space_id: string
bound_by: string
bound_at: string
status: "bound" | "unbound" | "error"
}
memory: {
group_id: string
provider: "notebooklm"
lane: "memory"
remote_space_id: string
bound_by: string
bound_at: string
status: "bound" | "unbound" | "error"
}
}
queue_summary: {
work: { pending: number; running: number; failed: number }
memory: { pending: number; running: number; failed: number }
}
sync?: {
available?: boolean
reason?: string
space_root?: string
remote_space_id?: string
last_run_at?: string
converged?: boolean
unsynced_count?: number
last_error?: string
}
memory_sync?: {
lane: "memory"
manifest_path: string
last_scan_at?: string | null
last_success_at?: string | null
pending_files: number
running_files: number
failed_files: number
blocked_files: number
eligible_daily_files: number
synced_daily_files: number
empty_daily_skipped: number
last_eligible_daily_date?: string | null
last_synced_daily_date?: string | null
}
}group_space_spaces
List available remote notebooks/spaces for provider selection UI.
Args:
{ group_id: string; provider?: "notebooklm" }Result:
{
group_id: string
provider: "notebooklm"
provider_state: Record<string, unknown>
bindings: Record<"work" | "memory", Record<string, unknown>>
spaces: Array<{
remote_space_id: string
title?: string
created_at?: string
is_owner?: boolean
}>
}group_space_capabilities
Return Group Space capability matrix for current group/provider.
Args:
{
group_id: string
provider?: "notebooklm"
}Result:
{
group_id: string
provider: "notebooklm"
local_scope_attached: boolean
space_root: string
local_file_policy: {
allowed_extensions: string[]
max_file_size_bytes: number
unsupported_error_code: string
oversize_error_code: string
}
ingest: {
kinds: Array<"context_sync" | "resource_ingest" | "memory_daily_sync">
resource_ingest: {
source_types: string[]
required_fields: Record<string, string[]>
optional_fields: Record<string, string[]>
aliases: Record<string, string>
examples: Record<string, Record<string, unknown>>
}
}
query: {
options: {
source_ids: string
}
unsupported_options: Record<string, string>
examples: Record<string, Record<string, unknown>>
}
artifacts: {
actions: string[]
kinds: string[]
options: Record<string, string>
aliases: Record<string, string>
examples: Record<string, Record<string, unknown>>
}
notes: string[]
capabilities: string[]
unavailable_capabilities: string[]
}Capability matrices are implementation-specific runtime truth. Callers MUST NOT assume an ingest source type or asynchronous behavior that is absent from the returned matrix. An implementation MUST fail an unavailable operation with capability_unavailable; it MUST NOT silently coerce a file, URL, YouTube, or Drive source into pasted text.
group_space_bind
Bind/unbind a group lane to a provider remote notebook. When action=bind and remote_space_id is empty, daemon may auto-create an appropriate notebook and bind it.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
action?: "bind" | "unbind"
remote_space_id?: string
by?: string
}Result:
{
group_id: string
lane: "work" | "memory"
provider: Record<string, unknown>
bindings: Record<"work" | "memory", Record<string, unknown>>
queue_summary: {
work: { pending: number; running: number; failed: number }
memory: { pending: number; running: number; failed: number }
}
sync?: Record<string, unknown> // work-lane repo sync view
memory_sync?: Record<string, unknown> // memory-lane manifest summary
sync_result?: Record<string, unknown>
}group_space_ingest
Create (or dedupe) a durable work-lane ingest job and execute one provider attempt. lane="memory" MUST be rejected. The job MUST be persisted before the provider mutation, then settled to succeeded or failed. A process exit or a provider response whose remote commit cannot be determined MUST leave the job running to represent an uncertain outcome. Reissuing the same idempotency key MUST return that job instead of creating another source. 0.4.36 has no background ingest retry worker: retry after a terminal failure is an explicit group_space_jobs action=retry operation.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
kind?: "context_sync" | "resource_ingest"
payload?: Record<string, unknown>
idempotency_key?: string
by?: string
}Result:
{
group_id: string
lane: "work"
job_id: string
accepted: boolean
completed: boolean
deduped: boolean
job: Record<string, unknown>
ingest_result?: Record<string, unknown>
source_id?: string
source_ids?: string[]
queue_summary: { pending: number; running: number; failed: number }
provider_mode: "disabled" | "active" | "degraded"
}accepted=true, completed=false means durable work remains in progress. Terminal succeeded, failed, or canceled jobs report accepted=false, completed=true.
group_space_query
Query provider-backed knowledge for one lane. If provider is degraded, result MAY return ok=true with degraded=true and an empty answer.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
query: string
options?: {
source_ids?: string[] // optional remote source_id filter
}
}Validation notes:
optionsonly supportssource_ids.options.language/options.langare invalid forgroup_space_querybecause NotebookLM query API does not provide a language parameter.- Recommended recall order is local memory first, then
lane="memory"for deep recall.
Result:
{
group_id: string
provider: "notebooklm"
lane: "work" | "memory"
provider_mode: "disabled" | "active" | "degraded"
degraded: boolean
answer: string
references: unknown[]
reference_count: number
binding_status: "bound" | "unbound" | "error"
source_basis_hint: "requested_sources_hit" | "requested_sources_mixed" | "requested_sources_only" | "referenced_sources_present" | "context_sync_only" | "materialized_sources_present" | "mixed" | "memory_manifest_only" | "unknown"
requested_source_ids?: string[]
referenced_source_ids?: string[]
references_match_requested?: boolean
latest_context_sync_at?: string
remote_sources?: number
materialized_sources?: number
memory_last_success_at?: string
memory_pending_files?: number
memory_failed_files?: number
error?: { code: string; message: string } | null
}Notes:
- The extra query fields above are lightweight diagnostics/provenance hints, not retrieval guarantees.
- When
options.source_idsis provided,source_basis_hintSHOULD prefer explicit source scope / actual cited sources over inferred local sync state. - Work-lane answers may come from synced coordination/context even when repo materialized sources are sparse.
- Memory-lane diagnostics describe sync-manifest health only; they do not promise a semantic hit for every query.
group_space_sources
List/refresh/rename/delete provider sources in the currently bound lane notebook.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
action?: "list" | "refresh" | "rename" | "delete"
source_id?: string // required for refresh/rename/delete
new_title?: string // required for rename
by?: string
}Result (action=list):
{
group_id: string
provider: "notebooklm"
lane: "work" | "memory"
provider_mode: "disabled" | "active" | "degraded"
binding: Record<string, unknown>
action: "list"
sources: Record<string, unknown>[]
list_result: Record<string, unknown>
}Result (action=refresh | rename | delete):
{
group_id: string
provider: "notebooklm"
lane: "work" | "memory"
provider_mode: "disabled" | "active" | "degraded"
binding: Record<string, unknown>
action: "refresh" | "rename" | "delete"
source_id: string
refresh_result?: Record<string, unknown>
rename_result?: Record<string, unknown>
delete_result?: Record<string, unknown>
}action=refresh MUST invoke the provider refresh mutation. Re-listing the source without refreshing it is not a successful refresh result. A successful NotebookLM refresh reports refresh_result.refreshed=true.
group_space_artifact
List/generate/download provider artifacts (NotebookLM studio outputs) on lane="work". lane="memory" MUST be rejected. For action=generate, daemon can optionally wait for completion and auto-save the artifact into local repo/space/artifacts/....
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
action?: "list" | "generate" | "download"
kind?: "audio" | "video" | "report" | "study_guide" | "quiz" | "flashcards" | "infographic" | "slide_deck" | "data_table" | "mind_map"
options?: Record<string, unknown> // for action=generate
wait?: boolean // action=generate only; default false
save_to_space?: boolean // generate/download local-save behavior; default false
output_path?: string // optional local path override
output_format?: "json" | "markdown" | "html" | "pdf" | "pptx" | "csv"
artifact_id?: string // optional explicit download target
timeout_seconds?: number // generate+wait only
initial_interval?: number // generate+wait only
max_interval?: number // generate+wait only
by?: string
}Result (action=list|generate|download) mirrors the lane-targeted binding and includes lane: "work".
Generation defaults to wait=false and save_to_space=false, so a normal request does not hold a group mutation lane while polling a remote provider and does not perform an implicit local write. When wait=false, an implementation without a background artifact worker MAY return after remote generation starts with saved_to_space=false; the caller can later list or download the artifact. It MUST NOT claim that a local file was saved. wait=true plus save_to_space=true performs the wait and local save before returning or reports a provider timeout/failure.
When save_to_space=true, the implementation MUST validate the local destination and kind/format download capability before creating the remote artifact. Unsupported kind/format combinations fail with capability_unavailable without provider-side generation. Authenticated media downloads MUST require HTTPS and validate an explicit provider host allowlist for both the initial URL and every redirect hop.
Common provider error semantics:
space_provider_not_configured: required provider credentials or binding configuration is absent; non-transient.space_provider_auth_invalid: credentials are malformed, expired, or rejected; non-transient until re-authentication.space_provider_not_found: requested remote resource is absent; non-transient and does not degrade the whole provider.space_provider_compat_mismatch: provider response schema cannot be decoded; non-transient and degrades the provider until compatibility is restored.space_provider_timeout: request or provider-side wait timed out; transient and does not by itself degrade the provider.space_provider_rate_limited: provider refused work because of quota/rate limits; transient and does not by itself degrade the provider.space_provider_outcome_unresolved: the provider may have committed the mutation, but CCCC cannot prove its result; the durable job remainsrunningand MUST NOT be retried until a user inspects/reconciles or cancels it.space_provider_upstream_error: another provider transport/RPC failure occurred; retryability depends on the operation and provider response.
group_space_jobs
List/retry/cancel Group Space jobs for one lane.
retry accepts only terminal failed/canceled jobs (and legacy pending jobs). It MUST reject an uncertain running job. Before a retry performs any provider mutation, the job's saved remote_space_id MUST equal the lane's current binding; otherwise it fails with binding_changed and leaves the job unchanged. This prevents an old job from writing into a notebook the Group has since left.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
action?: "list" | "retry" | "cancel"
job_id?: string
state?: "pending" | "running" | "succeeded" | "failed" | "canceled"
limit?: number
by?: string
}group_space_sync
Read legacy Python 0.4.35 synchronization state for one lane during a native upgrade. This compatibility operation is not advertised by the 0.4.36 CLI, Web API, or MCP surface. Automatic repo/memory mirroring is retired; callers use explicit group_space_ingest and source operations instead.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
action?: "status"
by?: string
}Result returns the targeted lane state in sync. Implementations that list sync.work or sync.memory in unavailable_capabilities MUST still expose canonical read-only status after the upgrade. A legacy client that sends action=run MUST receive capability_unavailable before any provider-side mutation.
group_space_provider_credential_status
Read provider credential status (masked metadata only, no secret values).
Args:
{
provider?: "notebooklm"
by?: string // user-only
}Result:
{
provider: "notebooklm"
credential: {
provider: "notebooklm"
key: string
configured: boolean
source: "none" | "store" | "env"
env_configured: boolean
store_configured: boolean
updated_at?: string | null
masked_value?: string | null
}
}group_space_provider_credential_update
Update or clear provider credentials in the daemon secret store.
Args:
{
provider?: "notebooklm"
by?: string // user-only
auth_json?: string
clear?: boolean
}Notes:
clear=trueremoves stored credentials for this provider.auth_jsonis write-only and never returned in response payloads.- Environment credential (
CCCC_NOTEBOOKLM_AUTH_JSON) has higher precedence than stored credentials. - Updating or clearing the effective stored credential invalidates its prior verified-ready state; callers must run a successful health check before the provider is reported
write_ready=true.
Result:
{
provider: "notebooklm"
credential: {
provider: "notebooklm"
key: string
configured: boolean
source: "none" | "store" | "env"
env_configured: boolean
store_configured: boolean
updated_at?: string | null
masked_value?: string | null
}
}group_space_provider_health_check
Run provider health check and update provider state (active/degraded/disabled) accordingly.
Args:
{
provider?: "notebooklm"
by?: string // user-only
auth_json?: string // optional candidate storage-state JSON
}When auth_json is present it is treated as write-only credential material: it is never returned, persisted, or used to update the current provider state. This candidate-validation form lets browser-auth controllers verify a captured session before committing it.
Result:
{
provider: "notebooklm"
healthy: boolean
health?: Record<string, unknown>
error?: { code: string; message: string }
provider_state: Record<string, unknown>
credential: {
provider: "notebooklm"
key: string
configured: boolean
source: "none" | "store" | "env"
env_configured: boolean
store_configured: boolean
updated_at?: string | null
masked_value?: string | null
}
}group_space_provider_auth
Control provider auth flow (status/start/cancel/disconnect) for backend-managed NotebookLM sign-in.
Args:
{
provider?: "notebooklm"
action?: "status" | "start" | "cancel" | "disconnect"
timeout_seconds?: number
projected?: boolean // when true, expose sign-in through the projected browser surface instead of a daemon-host browser window
by?: string // user-only
}Result:
{
provider: "notebooklm"
provider_state: Record<string, unknown>
credential: {
provider: "notebooklm"
key: string
configured: boolean
source: "none" | "store" | "env"
env_configured: boolean
store_configured: boolean
updated_at?: string | null
masked_value?: string | null
}
auth: {
provider: "notebooklm"
state: "idle" | "running" | "succeeded" | "failed" | "canceled"
phase?: string
delivery?: "local_browser" | "projected_browser" | ""
session_id?: string
started_at?: string
updated_at?: string
finished_at?: string
message?: string
error?: { code: string; message: string } | Record<string, unknown>
projected_browser?: {
active: boolean
state: string
message?: string
error?: { code?: string; message?: string } | Record<string, unknown>
strategy?: string
url?: string
width?: number
height?: number
started_at?: string
updated_at?: string
last_frame_seq?: number
last_frame_at?: string
controller_attached?: boolean
}
}
}Notes:
startmay open a browser on the daemon host for Google sign-in whenprojectedis false.startSHOULD expose the sign-in flow through a projected browser surface whenprojected=true.- When the daemon advertises both provider-auth browser attach capabilities as false and a product Web process owns the browser lifecycle, daemon-level
start/cancel/disconnectMUST returncapability_unavailable;statusremains a valid durable credential/provider-state projection. - Provider write readiness remains gated by
auth_configuredand runtime mode.
space_provider_auth_browser_attach
Attach to the currently active projected provider-auth browser surface over a dedicated bidirectional NDJSON stream.
Args:
{
provider: "notebooklm"
by?: string // user-only
viewer_mode?: "auto" | "screencast" | "vnc"
}Handshake result:
{ provider: "notebooklm" }Streaming mode:
- After a successful handshake, the connection upgrades into the browser-surface stream described in §4.6.
- The daemon emits
stateitems when runtime/session status changes andframeitems for captured browser frames. - The client MAY send browser-control commands (
navigate,back,refresh,click,scroll,key,text,resize,close,disconnect). - At most one active controller MAY be attached at a time; a second attach attempt SHOULD fail with a busy-style error.
- If no active projected auth browser exists, attach SHOULD fail with
browser_surface_not_found. - If the underlying browser runtime is no longer active, attach SHOULD fail with
browser_surface_not_active.
space_provider_auth_browser_vnc_attach
Attach to the currently active projected provider-auth browser surface over a raw RFB/VNC stream.
Args:
{
provider: "notebooklm"
by?: string // user-only
}Handshake result:
{ provider: "notebooklm" }Streaming mode:
- After a successful handshake, the connection upgrades into a raw VNC/RFB byte stream.
- The operation SHOULD fail with
browser_vnc_unavailablewhen the browser surface is not backed by a local VNC projection.
8.19 Web Model Browser Surface (Optional)
web_model_delivery_preferences_get
Read the durable browser-delivery preference for one Web Model actor.
Args:
{ group_id: string; actor_id: string }Result:
{
group_id: string
actor_id: string
preference: {
mode: "standard" | "image_compat"
updated_at: string
updated_by: string
}
}Missing or invalid stored state MUST resolve to standard without mutating the group. The preference is scoped to (group_id, actor_id) and MUST survive browser-target changes and daemon restarts.
web_model_delivery_preferences_update
Update the durable browser-delivery preference for one Web Model actor.
Args:
{
group_id: string
actor_id: string
mode: "standard" | "image_compat"
by: "user"
}Result has the same shape as web_model_delivery_preferences_get. The operation is user-only and MUST reject other modes. A runtime turn snapshots the effective mode in turn.delivery.web_model_mode; a preference change therefore applies to the next accepted delivery, not a delivery already in flight.
image_compat is an experimental ChatGPT transport workaround. The browser adapter MUST attach exactly one CCCC-owned blank PNG before invoking Send and MUST NOT use the OS clipboard. An attachment failure before a submit action MUST settle the delivery attempt as failed; it MUST NOT affect the Mail cursor. The mode does not select or change the ChatGPT model.
runtime_wait_next_turn
Accept one pending structured-runtime delivery turn. This operation is mutating: it claims work, records pull delivery as accepted, and sets the actor's active turn. It MUST NOT advance the Mail cursor.
Args:
{
group_id: string
actor_id: string
by: string // MUST equal actor_id
limit?: number // 1..20, default 20
transport?: "web_model_pull" | "web_model_browser" // internal browser owner only
}kind_filter is not supported. Runtime delivery selects only pending direct delivery work; ordinary message_mode="mail" messages remain in Inbox until an explicit promotion or a mailbox notice creates direct work.
Result is one of:
{ status: "stopped"; turn: null }
{ status: "turn_in_progress"; turn: null; active_turn_id: string; event_ids: string[] }
{ status: "idle"; turn: null; suggested_retry_after_ms: number }
{
status: "work_available"
turn: {
turn_id: string
group_id: string
actor_id: string
event_ids: string[] // exact canonical delivery batch
latest_event_id: string
latest_ts: string
messages: Event[]
coalesced_text: string
system_prompt: string
delivery: {
mode: "runtime_delivery"
transport: "web_model_pull" | "web_model_browser"
max_events: number
web_model_mode: "standard" | "image_compat"
}
}
}For web_model_pull, every returned source event MUST already have a durable runtime.delivery state of accepted. For web_model_browser, this operation only establishes the browser claim; web_model_browser_delivery_record settles the claim after the submit boundary. A second wait while the actor owns an active turn MUST return turn_in_progress and MUST NOT replace that turn. coalesced_text MUST preserve the complete daemon-rendered batch without a secondary character-count truncation. Browser-originated oversized text is converted to a .txt attachment before daemon delivery; the canonical messages array remains the source of truth for every structured turn.
The browser adapter MUST wait for a signed-in conversation composer before claiming new work. A guest composer or provider security-verification page MUST NOT count as ready. While waiting for sign-in or verification, background delivery MUST NOT navigate to the saved conversation; existing ambiguous submissions still follow their normal reconciliation contract. A browser adapter MUST NOT overwrite an unrelated non-empty composer draft. Legacy draft recovery MAY replace only the exact expected old draft, revalidated on the same target page immediately before replacement, with no user messages, submission echo, or response generation. This MUST NOT grant general overwrite permission or replay an already-submitted message. After an unverified submission, it MUST fence later deliveries even for an existing conversation, until direct evidence verifies that submission or the user explicitly resolves it. User acknowledgement MUST NOT replay the unverified turn or fabricate an accepted runtime handoff. Ordinary occupied composers detected before claiming work MUST leave pending daemon work unclaimed. The adapter also MUST wait while the provider is visibly generating a response.
Immediate submission checks and later recovery MUST use the same receipt: the current batch, source-event and Actor marker in a user message. Message-node counts can change when history is loaded or virtualized and MUST NOT acknowledge a send. Assistant quotes, an emptied composer, generation indicators and a successful click dispatch are not sufficient receipts. Recovery MUST inspect the currently owned bound page rather than promote stored weak observations. Before clicking Send, the adapter MUST recheck the staged draft and the button's stable, unobstructed viewport position. Background-window dispatch MUST NOT rely on a rendering callback or activate another Actor's window.
Only one worker and one submission may own an Actor's delivery at a time; rejected acquisition MUST NOT release the current owner's registration. While a submission remains unverified, the adapter MAY observe the same owned, bound conversation for the exact batch, source-event and Actor marker in a user message. That later receipt (including a manual send) MUST append the accepted handoff before releasing subsequent work. An empty composer, an unrelated message, an assistant echo or a different conversation MUST NOT resolve the uncertainty. Reconciliation MUST NOT click Send, navigate, or modify a draft.
runtime_complete_turn
Close the actor's exact active structured-runtime turn after processing.
Args:
{
group_id: string
actor_id: string
by: string // MUST equal actor_id
turn_id: string
event_ids: string[] // MUST exactly equal the active turn event_ids
delivery_id?: string // default `runtime:<turn_id>`; part of the replay fingerprint
status?: "done" | "partial" | "failed" | "cancelled" // default done
summary?: string
}latest_event_id is not supported. Every supplied event MUST already have a terminal handoff fact (runtime.delivery=accepted|ambiguous) for this actor. Completion records runtime progress and releases the active turn; every status, including done, MUST leave the Mail cursor unchanged. An actor consumes Inbox contents only through inbox_read / cccc_inbox_read. The daemon MUST persist a deterministic runtime.turn.completed receipt before acknowledging completion. An exact retry with the same actor, turn, event IDs, status, and delivery ID MUST replay that receipt even after active-turn state was cleared. Reusing the turn identity with a different fingerprint MUST fail with completion_conflict.
Common result fields:
{
status: "done" | "partial" | "failed" | "cancelled"
turn_id: string
delivery_id: string
completion_event: CCCSEventV1 // kind="runtime.turn.completed"
processed_event_ids: string[]
followup_delivery_scheduled: boolean
summary: string
}web_model_runtime_recover_turn
Rebuild a previously handed-off Web Model turn without changing runtime state, delivery state, or the actor Mail cursor. This is used only to reconcile a persisted browser-delivery attempt after process interruption.
Args:
{ group_id: string; actor_id: string; event_ids: string[] }Result:
{
status: "recovered"
turn: {
turn_id: string
group_id: string
actor_id: string
event_ids: string[] // canonical ledger order
latest_event_id: string
latest_ts: string
messages: Event[]
coalesced_text: string
system_prompt: string
delivery: {
mode: "recovery_no_delivery_mutation"
web_model_mode: "standard" | "image_compat"
}
}
}Every event MUST exist, be addressed to the actor, have a supported turn kind, and already have a terminal handoff fact (runtime.delivery=accepted|ambiguous). The operation MUST NOT change delivery state, read state, active runtime state, or completion state.
web_model_browser_delivery_record (internal)
Append a browser-delivery observation for a claimed Web Model turn. The browser owner uses this operation to expose the canonical message status in the Web surface and to settle the runtime handoff; it does not complete the turn or advance the actor cursor.
Args:
{
group_id: string
actor_id: string
by: string // MUST equal actor_id
turn_id: string
event_ids: string[] // 1..20 addressed chat.message/system.notify IDs
delivery_id: string
browser_delivery: {
state: "submitting" | "submitted" | "bound" | "pending" | "ambiguous" | "failed"
detail?: string
provider?: string
target_url?: string
bound_conversation_url?: string
pending_conversation_url?: string
auto_bind_new_chat?: boolean
resolved_pending_new_chat?: boolean
}
}Result:
{ event: CCCSEventV1 }Each call appends an ordinary web_model.browser_delivery.<state> event. submitted and bound MUST settle every referenced runtime claim as runtime.delivery=accepted; ambiguous and failed settle it with the matching terminal outcome. submitting and pending remain observations only. The browser owner MUST record the terminal handoff before runtime_complete_turn so completion cannot outrun delivery evidence. If the record call itself fails, a verified submission remains completion-pending and reconciliation retries this operation; it MUST NOT resubmit the browser prompt.
web_model_browser_attach
Attach to the currently active managed Web Model browser surface over a dedicated bidirectional NDJSON stream.
Args:
{
group_id?: string
actor_id?: string
by?: string
viewer_mode?: "auto" | "screencast" | "vnc"
}Handshake result:
{ group_id: string; actor_id: string }Streaming mode:
- After a successful handshake, the connection upgrades into the browser-surface stream described in §4.6.
- The daemon emits
stateitems when runtime/session status changes andframeitems for captured browser frames. - The client MAY send browser-control commands (
navigate,back,refresh,click,scroll,key,text,resize,close,disconnect). - The daemon owns the browser runtime; Web clients are surface proxies and MUST NOT create a separate browser runtime for the same Actor.
- When
group_idoractor_idis supplied, the Actor MUST exist and useruntime=web_model|grok_web_model. - If no active Web Model browser surface exists, attach SHOULD fail with
browser_surface_not_found. - If the underlying browser runtime is no longer active, attach SHOULD fail with
browser_surface_not_active.
web_model_browser_vnc_attach
Attach to the currently active managed Web Model browser surface over a raw RFB/VNC stream.
Args:
{
group_id?: string
actor_id?: string
by?: string
}Handshake result:
{ group_id: string; actor_id: string }Streaming mode:
- After a successful handshake, the connection upgrades into a raw VNC/RFB byte stream.
- The operation SHOULD fail with
browser_vnc_unavailablewhen the browser surface is not backed by a local VNC projection.
8.20 Copy Groups
Copy Groups operations export/import durable CCCC group state as a zip package. Copy packages contain CCCC group state only; workspace repository files are not included.
group_copy_export
Export one group as a base64-encoded zip package.
Args:
{
group_id: string
by?: string
}Result:
{
package_b64: string
filename: string
manifest: {
kind: "cccc.group_copy"
version: number
source_group_id: string
source_title?: string
exported_at: string
cccc_version?: string
source_platform?: string
export_mode: "group_state_only"
workspace_included: false
contains_secrets: false
content_digest?: string
content?: Record<string, unknown>
}
}Notes:
- Export MUST exclude live runtime state, browser profiles, credentials, connector secrets, lock files, and rebuildable caches.
- Export and import MUST exclude persisted IM reply credentials, including
state/im_weixin_context_tokens.json; import MUST apply exclusions even when an older package declarescontains_secrets: false. - Export MUST scrub actor environment secrets from packaged
group.yaml. contains_secrets: falsemeans CCCC-managed live credentials and auth sessions are excluded. The package can still contain user-provided sensitive content such as ledger history, memory, blobs, and attachments.- This compatibility operation is intended for small packages. Large packages SHOULD use
group_copy_export_fileand pass the returnedpackage_pathto preview/import.
group_copy_export_file
Export one group as a zip package stored on the daemon host filesystem.
Args:
{
group_id: string
by?: string
}Result:
{
package_path: string
package_size_bytes: number
filename: string
manifest: {
kind: "cccc.group_copy"
version: number
source_group_id: string
source_title?: string
exported_at: string
cccc_version?: string
source_platform?: string
export_mode: "group_state_only"
workspace_included: false
contains_secrets: false
content_digest?: string
content?: Record<string, unknown>
}
}Notes:
- The package path is a temporary daemon-local file path intended for local download flows.
- This operation uses the large package limit. Secret-scrubbing requirements match
group_copy_export.
group_copy_preview_import
Validate a copy package and return an import preview without writing group state.
Args:
{
package_b64?: string
package_path?: string
by?: string
}Exactly one of package_b64 or package_path is required. package_b64 is a small-package compatibility path; large local flows SHOULD use package_path.
Result:
{
preview: {
manifest: Record<string, unknown>
source_group_id: string
source_title: string
actor_count: number
actors: Array<Record<string, unknown>>
source_workspace_root: string
workspace_root_exists: boolean
group_id_conflict: boolean
target_default_scope_conflict?: boolean
requires_reconnect?: Record<string, boolean>
workspace_included: false
contains_secrets: false
runtime_reset?: Record<string, unknown>
}
}Errors:
invalid_group_copywhen the payload is not a valid supported CCCC group copy.contains_secrets: falsein the preview has the same meaning as export: system credentials are excluded, but user content in ledger history, memory, blobs, and attachments can still be sensitive.
group_copy_import
Import a group copy into the current CCCC_HOME.
Args:
{
package_b64?: string
package_path?: string
workspace_root?: string
title?: string
by?: string
}Exactly one of package_b64 or package_path is required. package_b64 is a small-package compatibility path; large local flows SHOULD use package_path.
Result:
{
group_id: string
source_group_id: string
group_id_conflict: boolean
workspace_root: string
active_scope_key: string
}Notes:
- Import MUST stage and validate copy package contents before moving them into
groups/<group_id>. - If the source
group_idconflicts in the target home, import MUST allocate a new group id. - Imported groups MUST start stopped:
running=false,state="idle". workspace_root, when supplied, remaps the active workspace root during import.- Import MUST reject unsupported copy package schema versions, workspace-including copy packages, secret-containing copy packages, path traversal, symlinks, duplicate entries, and unsafe package paths.
9. Appendix: Example Lines
9.1 Ping
Request line:
{"v":1,"op":"ping","args":{}}Response line:
{"v":1,"ok":true,"result":{"version":"0.4.x","implementation":"rust","pid":12345,"ts":"2026-01-13T12:34:56Z","ipc_v":1,"capabilities":{"events_stream":true,"remote_access":true}},"error":null}9.2 Error
{"v":1,"ok":false,"result":{},"error":{"code":"missing_group_id","message":"missing group_id","details":{}}}External ASR Web boundary
External provider credentials are Web-owned configuration, not daemon IPC or Group settings. Administrator-only Web routes are:
GET /api/v1/voice/asr/providers: redacted provider configuration and credential-presence flags.PUT /api/v1/voice/asr/providers/{provider}: update Bailian/Volcengine configuration; omitted/blank secrets preserve existing values;clear_credentials=trueexplicitly clears credentials.POST /api/v1/voice/asr/providers/{provider}/probe: test the saved connection without audio.
Group assistant settings select recognition_backend=external_provider_asr and external_asr_provider=bailian|volcengine; they never carry provider secrets. The existing leased /transcriptions/ws route accepts this backend and preserves ready, partial, final, final_asr_text, error, and closed semantics. External document checkpoints use external_provider_asr_streaming and explicit auto_document_max_window_seconds scheduling (10–300 seconds; absent means 300). An explicit null disables periodic submission until stop/recovery. Stable sentences MUST be buffered and flushed in audio-time order; periodic ticks MUST flush due text even when the provider sends no further sentences. Stop/recovery MUST flush pending text regardless of this setting. Provider completion MUST NOT bypass checkpoint recovery: retries retain segment IDs, and a connected browser MUST receive final_asr_text with persistence status even after the final packet's checkpoint failed. These checkpoints have transcript_stage=live; complete final revisions use external_provider_asr_final, transcript_stage=final, and supersede_stage=live. Late live input MUST NOT create another semantic input once that session has a final cloud revision. Provider errors are sanitized; recovered_text on an error contains only recognized non-document speech for recovery into the originating composer, not an automatic Agent request.
A complete final revision MUST NOT be submitted while any nonempty cloud checkpoint lacks acknowledgement. Final revision persistence is not proof of checkpoint or semantic-input delivery. In this case final_asr_text MUST report transcript_persistence=failed, transcript_persisted=false, and transcript_pending_segments, in audio-time order. Each pending record contains the original daemon segment_id, text, start_ms, and end_ms; an uncertain write acknowledgement MUST retain its original idempotency key too.
The browser retries these records sequentially through the existing transcript append operation using the recording's Group, session, document, language, and model. Each retry is stable live input (is_final=true, flush=true, transcript_stage=live, backend external_provider_asr_streaming). Only after all acknowledgements may a complete final result be retried as final-asr. Pending checkpoints take precedence over partial=true: partial results MUST also recover their pending records, but MUST NOT submit a superseding final revision. A failed browser checkpoint retry stops the sequence and retains the remaining IDs and text in its error details. The Web UI reports failure and returns this unconfirmed text to the original Group's composer for user recovery; it MUST NOT dispatch it automatically, mark it committed, or retry indefinitely.
Standalone Direct Group administration
The operations below are user-only (by must be user, default user). They do not require membership and do not grant Web administration to the remote peer. New Direct grants and approvals require an existing local group_id. Administrator status/revoke/remove also accept an original missing Group ID, permitting cleanup of records left by an interrupted deletion; another Group ID cannot remove them.
| Operation | Additional arguments | Result |
|---|---|---|
connect_direct_status | group_id | Listener configuration, local addresses: [{interface, bind, address}] suggestions, fresh runtime diagnostics, redacted relations for this Group |
connect_direct_configure | listener: {bind, address} | null, display_name?, expected_listener? | {configured:true}; actual listener state is checked separately |
connect_direct_invite | group_id, expected_listener?: {bind, address} | {invitation}; explicit secret-bearing response, never polled |
connect_direct_join | group_id, invitation | {id}; persists the request, not approval |
connect_direct_approve | group_id, id | {updated:true}; receiver approves the exact pending pair |
connect_direct_revoke | group_id, id | {updated:true}; authorization is revoked before return |
connect_direct_remove | group_id, id | {removed:true}; only revoked or expired unapproved records |
connect_direct_status is read-only; configuration uses its own store lock without a global dispatcher permit, while invitation/join/approval/revocation/removal use Group write permits and the shared local store lock. Peer network waits do not hold these permits. Status never returns the invitation or secret digest. Local addresses are read from operational interfaces on the daemon machine, excluding loopback, wildcard, multicast and link-local addresses; suggestions do not promise reachability or change saved settings. Enumeration failure yields an empty list, allowing manual configuration. Each suggestion carries a matching IPv4/IPv6 wildcard bind and the configured local port (default 8847). No discovery packet is sent. When supplied, expected_listener compares the full saved listener under the store lock: configure accepts null to mean previously disabled; invite requires an object. A mismatch rejects the mutation instead of overwriting another setup or issuing an invitation for a different address. Omitted expectations preserve explicit CLI use. The Web create action can sequence configuration, bounded readiness reads and one invitation mutation. No peer wait holds a dispatcher permit; closing the page or a failed/uncertain step must not schedule a delayed invitation or retry a POST. Relation expires_at reports the invitation deadline; it does not expire an already active grant. For a joining pending record, expired:true means the local deadline passed, not that approval was refused. state:expired is the receiver's terminal refusal; unconfirmed pending records remain reconcilable and require cancellation before removal. Local deletion/reset retires the old Group's Direct records and catalogs, retaining their IDs as retirement markers. initiated distinguishes the joining side from the receiving administrator who must approve. A saved pending request alone does not prove peer contact. connect_catalog includes approved Direct pairs in external_groups without an account; it never grants instance-wide discovery. connect_status.group_connections includes active Direct relations. connect_group_status.direct_routes identifies account link IDs whose exact pair has an explicit Direct route preference; this is a display projection, not a replacement grant or confirmation of delivery. The Connect standard defines identity, transport, routing and durable delivery semantics.
Web realtime transport (not daemon IPC)
GET /api/v1/events/ws upgrades an authenticated browser connection. The optional connect_frame query parameter carries the existing frame capability. On one socket the client may maintain one subscription for each global, ledger, and headless channel. These use the same event sources as their SSE counterparts.
- Subscribe:
{"type":"subscribe","channel":"ledger","id":2,"group_id":"g_example","cursor":"last-event-id"}. - Headless subscribe additionally accepts
replay(defaulttrue). - Unsubscribe:
{"type":"unsubscribe","channel":"ledger","id":2}. - Ready:
{"type":"ready","channel":"ledger","id":2}. - Event:
{"type":"event","channel":"ledger","id":2,"message":{"event":"ledger","id":"event-id","data":{}}}. - Producer termination or rejection:
{"type":"closed","channel":"ledger","id":2,"code":"permission_denied"};codemay be absent for EOF. - Connection-level rejection:
{"type":"fatal","code":"auth_required"}. - Heartbeat:
{"type":"heartbeat"}; WebSocket Ping/Pong also verifies peer liveness.
Subscription IDs must identify the logical subscription, change when a Group is replaced, and be echoed on all its packets. Subscribing again replaces that channel's producer; unsubscribe only affects the matching ID. Scope and live authority checks apply to subscription messages because the socket URL itself contains no Group ID.
Web Model instance connector and conversation binding
The private Web port owns one shared ChatGPT browser/profile with a persistent independent window/Page target per Actor. Shared login affects every Actor; global settings own login and connector credentials, Actor settings own conversation pairing. No active-tab switching or model-declared Actor identity is allowed. A manually closed Actor window stays paused for that Actor generation only; Actor/Group removal and generation replacement MUST retire its closed-window marker even after its browser session is gone. Old Actor-specific connector stores require explicit reconfiguration, preserve user history and unresolved delivery evidence, and MUST NOT authorize tools or automatic redelivery.
Shared login and Actor windows MUST become viewable and closable after navigation starts, without waiting for the provider's DOMContentLoaded or load event. Window availability is distinct from delivery readiness: the existing target, composer, draft and running-state checks still apply before claiming or submitting work. The global shared-browser close endpoint MUST close only its provider's owned login Page; it MUST preserve Actor windows, bindings and authenticated profile, without requiring Actors to stop. Web shutdown remains responsible for closing the shared process and display resources.
These IPC operations use the daemon global write permit and require by=user; none is an Actor MCP catalog operation:
| Operation | Required input / effect |
|---|---|
web_model_connector_configure | Optional provider (chatgpt_web, default, or grok_web); create or rotate only that provider’s instance credential and return its secret once. Rotation retains bindings and routing keys. |
web_model_connector_revoke | connector_id; disable all tool routes and pending pairings. |
web_model_pairing_begin | connector_id, group_id, actor_id; optional automatic=true requires an enabled, unbound Web Model Actor in a running, non-paused Group; otherwise requires a stopped Actor. Issue one 10-minute code, persisting only its hash and automatic intent. |
web_model_pairing_accept | connector_id, code, session_key; private Web port forwards the authenticated host session hash. Return a unique, idempotent receipt for this candidate/session; receipt alone grants no business authority. |
web_model_pairing_confirm | connector_id, group_id, actor_id, pairing_id, url; current generation, accepted unexpired pairing and verified stable ChatGPT URL. Recheck the persisted automatic lifecycle condition (or stopped Actor for manual replacement). Bind atomically; reject session/URL collisions. |
web_model_pairing_cancel | connector_id, group_id, actor_id, pairing_id; cancel only the named incomplete candidate without changing an existing binding; retain the cancellation to prevent automatic retries. |
web_model_pairing_fail | connector_id, group_id, actor_id, pairing_id, error_code; retire the named incomplete attempt, reject later acceptance/confirmation, preserve existing bindings and display the failure. A late failure MUST NOT alter a replacement or committed attempt. |
web_model_grok_bind | connector_id, group_id, actor_id, url; user-only, stopped Grok Actor and matching Grok connector. Canonical HTTPS grok.com/bot/<UUID> URL, unique within the connector. Saving the same current-generation URL is idempotent. A changed URL requires no unresolved delivery. |
web_model_binding_remove | connector_id, group_id, actor_id; stopped Actor with no unresolved delivery; remove only its binding/pairing. |
Normal Actor startup authorizes one initial automatic setup exchange for an enabled, unbound Actor in a running Group. The Web supervisor prepares its owned window; no business delivery is claimed until a binding is verified. An existing current-generation attempt, including expired, failed, cancelled or orphaned attempts, MUST prevent another automatic send. Unrelated Actor setup MUST NOT prune these attempt fences.
The private Web POST /api/v1/web-model/pairing exposes connect (explicit retry or stopped-Actor replacement), cancel, and remove. It does not activate the Actor. A bounded Web-owned task sends a one-time code to the captured Page without navigation or overwriting text/attachments. Before web_model_pairing_confirm, it MUST verify the returned receipt in an assistant reply after the exact setup message on that same Page, and a stable conversation URL (unchanged for an existing conversation). MCP receipt alone MUST NOT trigger confirmation. The receipt is generated after acceptance and is absent from the setup prompt and status API. On automatic success, release the pairing control permit and resume the existing business delivery worker.
Pairing submission deduplication MUST compare the complete setup message, including its unique code. A shared explanatory prefix from an earlier attempt is not evidence that the current setup message has been sent.
An initial handshake started on the new-chat page MUST wait through ChatGPT's provisional /c/WEB:<client-id> URL (including its encoded-colon form) on that same Page. A provisional URL MUST NOT be bound, even with a visible receipt; confirmation still requires the stable conversation URL and receipt proof. This transition allowance MUST NOT apply to a handshake started in an existing conversation. Browser inspection failure is an interruption, not evidence that the user changed conversations.
Pairing/navigation/delivery share the Actor control permit. Cancellation invalidates the exact candidate even while sending. Actor stop/restart/new-session and Group pause/stop invalidate incomplete automatic pairings before the lifecycle transition, so a rapid restart cannot accept a late receipt. Failure, cancellation, expiry and Web shutdown MUST NOT automatically resend a prompt or resume a handshake. Status GETs are observational; an unfinished attempt without its owning Web task is reported as interrupted. Earlier bindings and uncertain delivery evidence remain until an explicitly authorized replacement is verified or removed. An unpaired Actor's setup_url is only a persisted browser startup destination; it grants no tool or delivery authority.
Unresolved delivery forbids changing conversations or removing the binding, including persisted completion ambiguous and completion_conflict states after the daemon has stopped working. Re-pairing the same saved stable URL MAY restore access while retaining all pending evidence. The Web UI MUST keep this manual recovery reachable for a stopped unpaired Actor with a saved conversation, even without an earlier pairing attempt. The browser port observes the owned Page URL and serializes pairing/navigation against delivery; URL opening alone never changes authority. Both preview navigation and saved-conversation alignment MUST preserve unsent text and attachment-only drafts.
The ChatGPT remote MCP entrance advertises one fixed tool schema. Connector-level cccc_pair and read-only cccc_connector_status work before Actor activation; they are not nested tools in CCCC's cccc_code_exec, and do not constrain the host's own connector dispatch mechanism. Pairing MUST be declared state-changing: it records a pending link but does not itself read workspace files, execute tasks or grant business-tool access. Setup instructions MUST respect host approval requirements and report rejected calls; browser receipt verification activates the Actor route before subsequent calls can use its configured permissions. All business calls require a current enabled Web Model Actor binding. Each call uses host params._meta["openai/session"], scoped by optional openai/subject and openai/organization; values must be nonempty strings, at most 1,024 bytes, without control characters. Persist only salted hashes, never raw host identifiers. Missing metadata or invalid/stale bindings MUST fail closed; tool arguments, HTTP session IDs, titles and default Actor selection MUST NOT substitute for host metadata.
Code-mode nested calls MUST recheck captured Actor generation and binding revision before executing. Request context fixes the caller (by) and Group; cccc_actor.actor_id remains the explicit management target and MUST NOT be replaced with the caller. Other identity-bearing tools continue to use the bound Actor. Command stdin/output and code cells MUST remain scoped to their original Actor/binding; re-pairing MUST NOT transfer access to retained work. Credential rotation invalidates the old credential without reassigning conversations; revocation and Actor/Group removal invalidate affected routes. These checks cannot undo an already executed command.
Grok Bot routing and provider isolation
grok_web_model uses a headless Actor and the same browser queue, exact user-message receipts, review/recovery controls and draft/attachment protection as web_model. It has no CLI command and no ChatGPT pairing exchange. The private Web port owns a separate shared Grok profile, with one persistent Page/window per Actor. Provider settings MUST NOT rotate another provider's connector or close another provider's browser. The version-3 connector store preserves existing version-2 ChatGPT credentials, bindings and correlation salt.
Opening a Grok Actor window requires its current binding's canonical Bot URL. The Web port MUST reject an unconfigured Actor with grok_bot_url_required before creating a browser surface; it MUST NOT fall back to the Grok homepage or a new conversation. The shared login window is separate and MAY open the provider homepage. An Actor viewer MUST complete the owned-window open operation before attaching, rather than treating a registered surface during initialization as a completed open.
The Bot URL belongs to the Actor, including when its runtime comes from a linked Profile. A private Web binding save MUST align an already-open, idle Actor page with the saved Bot URL before reporting success. It MUST verify the page's provider and Actor generation before reuse. A stale page is retired only after checking for drafts and in-progress responses, then replaced through the current provider's shared profile; the new window follows the navigation-start availability rule above. Navigation failure leaves the Actor stopped and the same URL can be retried. This alignment belongs to the save operation, not status GETs or viewer opening.
Grok Bot URL saving is routing configuration authorized in the private CCCC UI. It proves no host-provided Bot identity. Grok business-tool requests MUST include actor_token, an opaque credential selecting an existing Group/Actor/generation/binding revision within the authenticated Grok connector. Possession grants that Actor's configured authority; intentional sharing lends that same authority. Missing, invalid, revoked, stopped or stale routes MUST be rejected. Model-supplied Group/Actor names, progress tokens, transport sessions and connector names MUST NOT substitute for this credential. cccc_connector_status MAY report connector connectivity without a token but MUST NOT select a default Actor. Grok MUST NOT advertise cccc_pair.
The Web MCP boundary removes actor_token before dispatch. Nested code-mode calls inherit and revalidate the captured binding, preserving explicit management targets. Browser delivery reconstructs a stable credential using the private provider signing key and binding identity; it adds the credential only to the rendered task prompt. Canonical ledger events, runtime turns, public settings, binding snapshots, tool output and activity diagnostics MUST NOT expose it. Stop/start and unchanged URL saves retain the binding; unbind, URL replacement, provider/runtime changes and Actor/Group removal retire affected authority. Copy/import/reset MUST NOT copy it.
Grok delivery uses standard mode only. image_compat is ChatGPT-specific and MUST be rejected for Grok. An enabled Submit control alone is not evidence that Grok is idle: Bot working indicators fence later delivery. Only a batch marker inside a Grok user-message element can settle an uncertain handoff; assistant echoes do not count. Host tool approval or safety rejection is reported rather than bypassed or retried blindly.