CCCC Daemon API/IPC Contract v1
Status: Draft (for CCCC v0.4.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 + attention/ack). - 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”)CCCC_DAEMON_ALLOW_REMOTE: when set truthy, allows binding to a non-loopback host (dangerous, no auth)
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 be treated as local-only unless an implementation explicitly accepts the risk.
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 (implemented by CCCC v0.4.x):
- Each connection processes exactly one request line and produces exactly one response line.
- The daemon then closes the connection.
Clients MUST assume the daemon may close the connection after any successful response and MUST NOT rely on persistent connections.
Forward-compatible extension (not required for v1):
- A daemon MAY accept multiple request lines over a single connection (strictly serial, no pipelining).
- Clients MUST NOT pipeline requests (there is no request id / multiplexing in v1).
4.3 Size Limits
Implementations MUST respect practical line limits to avoid truncation:
- Request line limit (daemon receive): the daemon MAY stop reading after ~2,000,000 bytes without a newline; clients MUST keep request lines comfortably 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.
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 raw PTY stream until closed.
After upgrade, the stream is not NDJSON.
The stream semantics are implementation-defined but, in CCCC today:
- The client receives raw PTY output bytes.
- The client MAY write raw bytes as input.
- The daemon MAY allow only one writer at a time (others become read-only).
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_listor 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 src/cccc/contracts/v1/ipc.py.
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 implementation in src/cccc/kernel/permissions.py). 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 src/cccc/contracts/v1/event.py 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; pid: number; ts: string; ipc_v?: 1; capabilities?: Record<string, unknown> }Notes:
ipc_vis RECOMMENDED for SDK compatibility checks.capabilitiesis RECOMMENDED as a best-effort feature map (e.g.,{ "events_stream": true }).
shutdown
Args: none
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
}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:
cccc.resources/capability-allowlist.default.yaml - user overlay:
CCCC_HOME/config/capability-allowlist.user.yaml - effective policy: deterministic merge (
default <- overlay).
- packaged default:
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"
}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_uninstall
Revoke capability bindings for the target group, remove current-group actor autoload references, and remove runtime cache when no other group/actor bindings remain. For source_id=agent_self_proposed skill records, uninstall also removes the generated local catalog record plus all actor/profile autoload references for that capability id. External registry catalog records are not deleted.
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_installation: boolean
removed_runtime_bindings?: number
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_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 }Result:
{ group: Record<string, unknown> } // group.yaml content, redactedgroup_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 }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 }group_detach_scope
Args:
{ group_id: string; scope_key: string; by?: string }Result:
{ group_id: string; ruleset: { rules: Array<unknown>; snippets: Record<string, 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.
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 v0.4.x include:
- Messaging:
default_send_to - Delivery:
min_interval_seconds,auto_mark_on_delivery - Automation:
nudge_after_seconds,reply_required_nudge_after_seconds,attention_ack_nudge_after_seconds,unread_nudge_after_seconds,nudge_digest_min_interval_seconds,nudge_max_repeats_per_obligation,nudge_escalate_after_repeats,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 a daemon-managed first-party service process; heavy ASR runtimes remain behind an explicit local command adapter.
Args:
{ group_id: string; assistant_id?: "voice_secretary" }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_runtimes_by_id?: Record<string, unknown>
service_models?: Array<Record<string, unknown>>
service_models_by_id?: Record<string, unknown>
}Voice service runtime records may include primary_package, package_versions, installed_version, latest_version, latest_checked_at, latest_check_error, and update_available so local ASR settings can show the installed sherpa-onnx version and whether a newer official PyPI release is available. 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, runner, 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. If the group is already running, the daemon starts or restarts this assistant actor as needed; 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 the first-party Voice Secretary service and uses an installed local ASR model. The returned assistant health may include health.service with status, alive, asr_command_configured, asr_mock_configured, selected_model_id, managed_model, and last_error 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.
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
}
}assistant_voice_transcribe
Transcribe a push-to-talk audio payload through the daemon-managed first-party Voice Secretary service. 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.
Args:
{
group_id: string
by?: string
audio_base64: string
mime_type?: string
language?: string
}Preconditions:
voice_secretaryis enabled for the group.recognition_backendisassistant_service_local_asr.- The selected
service_model_idis installed and exposes a managed command via the manifest. The effective command receives the audio path as the final argument unless it includes{audio_path}/{input_path}/{input}.
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>
}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.
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
}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
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. 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.
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, keeps a short in-memory/session window in group assistant runtime state, 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 both PTY and headless runtimes; 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: headless runtimes receive it as a control turn, and PTY runtimes receive it through the pending delivery queue so lazy preamble delivery is triggered.
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.
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_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.
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 into the same Voice Secretary input stream used for ASR transcript. The daemon emits a targeted voice_secretary_input notify; the runtime actor works from the inline input_envelope and saves the full revised markdown. The daemon does not directly append the instruction to the 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
instruction?: string
source_text?: string
trigger?: Record<string, unknown>
}Result:
{
group_id: string
assistant?: Record<string, unknown>
document: 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_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"
requires_ack?: boolean
}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_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"
requires_ack?: boolean
}
}>
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
}>
supported_vars: string[] // e.g. 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.
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
Start (enable + run) all actors in the group.
Args:
{ group_id: string; by?: string }Result:
{ group_id: string; started: string[]; forced_headless?: string[]; event: CCCSEventV1 }group_stop
Stop (disable + terminate) all actors in the group.
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/runner/runtime/enabled + role/runningactor_add
Args:
{
group_id: string
actor_id?: string
title?: string
runtime?: string
runner?: "pty" | "headless"
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/runner/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).
Result:
{ actor: Record<string, unknown>; event: CCCSEventV1 }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,runner,command,submit - Scope:
default_scope_key - Enable/disable:
enabled - Environment (use with care):
env - Capability startup baseline:
capability_autoload
Result:
{ actor: Record<string, unknown>; event: CCCSEventV1 }actor_remove
Args:
{ group_id: string; actor_id: string; by?: string }Result:
{ actor_id: string; event: CCCSEventV1 }actor_start / actor_stop / actor_restart
Args:
{ group_id: string; actor_id: string; by?: string }Result:
{ actor: Record<string, unknown>; event: CCCSEventV1 }Notes:
- For linked actors (
profile_idset),actor_startandactor_restartfirst resolve profile runtime config and profile secrets. - If the linked profile includes
capability_defaults, daemon applies baseline capability enables through capability control plane before launch.
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
claudeandcodexactors. - Stops the current actor runtime if present, clears CCCC's saved runtime session metadata for that actor, then starts the actor with the same runtime settings.
- 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.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.
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
runner: "pty" | "headless"
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.
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 current private env map into a profile's secrets (server-side copy, values are never returned).
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[] }8.6 Chat Messaging
send
Append a chat.message event to the group ledger and trigger best-effort delivery to running actors.
Args (core):
{
group_id: string
text: string
by?: string
to?: string[] // recipient tokens (empty = broadcast)
priority?: "normal" | "attention"
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
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[]
}Result:
{ event: CCCSEventV1 } // kind="chat.message"reply
Append a chat.message with reply_to and quote_text.
Args:
{
group_id: string
reply_to: string
text: string
by?: string
to?: string[] // defaults to original sender if omitted
priority?: "normal" | "attention"
attachments?: unknown[]
refs?: ReferenceV1[]
}Result:
{ event: CCCSEventV1 } // kind="chat.message"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 }[]
assignee?: string // defaults from one concrete to actor when possible
waiting_on?: "none" | "user" | "actor" | "external"
handoff_to?: string
notes?: string
priority?: "normal" | "attention"
reply_required?: boolean // default true
idempotency_key?: string
refs?: ReferenceV1[]
}Result:
{
task_id: string
task_ref: ReferenceV1 // kind="task_ref"
event?: CCCSEventV1 // present when message_sent=true
event_id?: string
task_created: boolean
message_sent: boolean
partial_failure: boolean
replayed?: boolean
}Notes:
task_refin the emittedchat.message.data.refsis the canonical message-task link.- 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 (withdst_group_id/dst_tometadata). - 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[]; priority?: "normal" | "attention" }Result:
{ src_event: CCCSEventV1; dst_event: CCCSEventV1 }Notes:
- Attachments are not supported in cross-group send in v1.
chat_ack
Append a chat.ack event (attention acknowledgement).
Args:
{ group_id: string; actor_id: string; event_id: string; by?: string }Result:
{ acked: boolean; already: boolean; event: CCCSEventV1 | null }8.7 Inbox (Read Cursor)
inbox_list
Return unread chat.message and/or system.notify events for an actor based on its read cursor.
Args:
{ group_id: string; actor_id: string; by?: string; limit?: number; kind_filter?: "all" | "chat" | "notify" }Result:
{ messages: CCCSEventV1[]; cursor: { event_id: string; ts: string } }inbox_mark_read
Advance the actor read cursor to at least event_id and append a chat.read event.
Args:
{ group_id: string; actor_id: string; event_id: string; by?: string }Result:
{ cursor: { event_id: string; ts: string; updated_at: string }; event: CCCSEventV1 }inbox_mark_all_read
Advance the actor read cursor to the latest currently-unread event (for the chosen kind filter) and append a chat.read event.
Args:
{ group_id: string; actor_id: string; by?: string; kind_filter?: "all" | "chat" | "notify" }Result:
{ cursor: { event_id: string; ts: string; updated_at: string }; event: CCCSEventV1 | null }8.8 Context and Tasks
context_get
Args:
{ group_id: string }Result:
{
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
}>
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.
context_sync
Args:
{ group_id: string; by?: string; ops: Array<Record<string, unknown>>; dry_run?: boolean }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 v2 operation list.
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 }Result:
{ tasks?: Array<Record<string, unknown>>; task?: Record<string, unknown> }Notes:
- Returned task objects include
task_type.
presence_get has been removed. Agent state is returned in context_get.result.agent_states.
blueprint_generate
Generate a blueprint for a task (currently returns a predefined blueprint ID; LLM integration planned).
Args:
{ task_id: string; task_name?: string; task_goal?: string; theme_hint?: string }Result:
{ source: "predefined" | "llm"; blueprint_id: string; variant?: number }Notes:
- Current implementation uses deterministic FNV-1a hashing on
task_idto select from predefined blueprints (shield,house,rocket). - Returns error code
missing_task_idiftask_idis empty.
8.9 Headless Runner
headless_status
Args:
{ group_id: string; actor_id: string }Result:
{ state: Record<string, unknown> } // see src/cccc/contracts/v1/actor.py HeadlessStateheadless_set_status
Args:
{ group_id: string; actor_id: string; status: "idle" | "working" | "waiting" | "stopped"; task_id?: string | null }Result:
{ state: Record<string, unknown> | null }headless_ack_message
Args:
{ group_id: string; actor_id: string; message_id: string }Result:
{ message_id: string; acked_at: string }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
requires_ack?: boolean
context?: Record<string, unknown>
}Result:
{ event: CCCSEventV1 } // kind="system.notify"notify_ack
Args:
{ group_id: string; actor_id: string; notify_event_id: string; by?: string }Result:
{ event: CCCSEventV1 } // kind="system.notify_ack"8.11 Terminal Diagnostics and PTY Attach
terminal_tail
Args:
{ group_id: string; actor_id: string; by?: string; max_chars?: number; strip_ansi?: boolean; compact?: boolean }Result:
{ group_id: string; actor_id: string; warning: string; hint: string; text: string }terminal_history
Args:
{ group_id: string; actor_id: string; by?: string; 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
}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 }Result:
{ group_id: string; actor_id: string; cols: number; rows: number }term_attach (streaming upgrade)
Args:
{ group_id: string; actor_id: string }Result (handshake):
{ group_id: string; actor_id: string }After a successful handshake, the connection becomes a raw PTY 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.
8.12 Ledger Maintenance
ledger_snapshot
Args:
{ group_id: string; by?: string; reason?: string }Result:
{ snapshot: Record<string, unknown> }ledger_compact
Args:
{ group_id: string; by?: string; reason?: string; force?: boolean }Result: implementation-defined compaction report.
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 v0.4.x streams these kinds:
chat.message,chat.ack,system.notify,system.notify_ack
- When
kindsis provided, only matching event kinds SHOULD be emitted. - If
byidentifies anactor_id, a daemon MAY apply the same visibility rules as inbox delivery (e.g., only deliverchat.message/system.notifyaddressed to that actor and exclude the actor’s ownchat.messageevents). - Resume (
since_event_id/since_ts) is best-effort in v1; clients MUST be able to reconcile usinginbox_list. - 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
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; 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
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 }Result:
{ revoked: boolean; unsubscribed?: boolean }Notes:
thread_iddefaults to0if omitted or invalid.
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"
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
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
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> }remote_access_start
Start remote access according to configured provider/mode.
Args:
{ by?: string }Result:
{ remote_access: Record<string, unknown> }remote_access_stop
Stop remote access service.
Args:
{ by?: string }Result:
{ remote_access: Record<string, unknown> }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[]
}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 work-lane ingest job and execute it with bounded retry policy. lane="memory" MUST be rejected.
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: true
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"
}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>
}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
save_to_space?: boolean // generate/download auto-save behavior
output_path?: string // optional local path override
output_format?: "json" | "markdown" | "html" // quiz/flashcards
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".
group_space_jobs
List/retry/cancel Group Space jobs for one lane.
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
Run/read synchronization state for one lane.
lane="work": repospace/reconciliation.lane="memory": async daily memory notebook sync manifest / enqueue scan.
Args:
{
group_id: string
provider?: "notebooklm"
lane: "work" | "memory"
action?: "status" | "run"
force?: boolean
by?: string
}Result (action=status|run) returns the targeted lane state in sync, and sync_result for action=run.
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.
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
}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.- 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 ChatGPT Web Model Browser Surface (Optional)
web_model_browser_attach
Attach to the currently active daemon-owned ChatGPT 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 ChatGPT browser runtime for the same actor.
- When
group_idoractor_idis supplied, the actor MUST exist and useruntime=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 daemon-owned ChatGPT 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 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","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":{}}}