Skip to content

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", an actor_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_HOME environment 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:

json
{
  "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:

  • v MUST be 1.
  • transport MUST be "unix" or "tcp".
  • If transport == "unix", path MUST be a non-empty filesystem path.
  • If transport == "tcp", host MUST be a connectable host (typically 127.0.0.1) and port MUST 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: 0 meaning “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:

  1. Client sends a normal request line with op="term_attach".
  2. Daemon sends a normal response line.
  3. 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).

  1. Client sends a normal request line with op="events_stream".
  2. Daemon sends a normal response line.
  3. 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):

ts
type EventStreamItem =
  | { t: "event"; event: CCCSEventV1 }
  | { t: "heartbeat"; ts: string }
  | { t: string; [k: string]: unknown } // forward-compatible extension

Rules:

  • Clients MUST ignore unknown t values.
  • heartbeat items 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_list or 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.

  1. Client sends a normal request line with op="presentation_browser_attach".
  2. Daemon sends a normal response line.
  3. If the response is ok=true, the connection remains open and becomes a bidirectional NDJSON stream.

After upgrade:

  • The daemon pushes state and frame items 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_attach operation 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):

ts
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):

ts
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 t values 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

ts
interface DaemonRequestV1 {
  v: 1
  op: string
  args?: Record<string, unknown> // default {}
}

Rules:

  • v MUST be 1.
  • op MUST 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

ts
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:

  • v MUST be 1.
  • If ok == true, error MUST be omitted or null.
  • If ok == false, error MUST be present.
  • Clients MUST NOT expect a stable schema for result beyond what each op specifies.

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.code MUST be a stable, machine-readable token.
  • error.message MUST be human-readable.
  • error.details MUST be a JSON object (may be empty).
  • The set of error.code values is an open set; clients MUST handle unknown codes gracefully.

Common codes used by CCCC v0.4.x include (non-exhaustive):

  • invalid_request, unknown_op
  • missing_group_id, group_not_found
  • missing_actor_id, actor_not_found, actor_not_running, not_pty_actor
  • permission_denied
  • invalid_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 (ledger event.by) and permission checks.
  • If by is 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 treat by as 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:

ts
{ version: string; pid: number; ts: string; ipc_v?: 1; capabilities?: Record<string, unknown> }

Notes:

  • ipc_v is RECOMMENDED for SDK compatibility checks.
  • capabilities is RECOMMENDED as a best-effort feature map (e.g., { "events_stream": true }).

shutdown

Args: none

Result:

ts
{ message: string } // "shutting down"

8.2 Observability (Global)

observability_get

Args: none

Result:

ts
{ observability: Record<string, unknown> }

observability_update

Args:

ts
{ by?: "user"; patch: Record<string, unknown> }

Result:

ts
{ observability: Record<string, unknown> }

branding_get

Args: none

Result:

ts
{
  branding: {
    product_name: string
    logo_icon_asset_path?: string
    favicon_asset_path?: string
    updated_at?: string
  }
}

branding_update

Args:

ts
{ by?: "user"; patch: Record<string, unknown> }

Result:

ts
{
  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:

ts
{ group_id?: string; by?: string }

Result:

ts
{
  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, or foreman when group_id is provided.

8.3 Groups and Scopes

attach

Attach a directory scope to a group (or auto-create/select a group for this scope).

Args:

ts
{ path: string; group_id?: string; by?: string }

Result:

ts
{ group_id: string; scope_key: string; title?: string }

groups

List known groups (registry summaries).

Args: none

Result:

ts
{ groups: Array<Record<string, unknown>> } // includes at least group_id/title/created_at/updated_at + running/state

registry_reconcile

Scan registry entries for missing/corrupt groups, and optionally remove missing entries.

Args:

ts
{ remove_missing?: boolean }

Result:

ts
{
  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:

ts
{
  query?: string
  limit?: number          // default 400, max 2000
  include_indexed?: boolean // default true
}

Result:

ts
{
  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
}

Search capability registry records (built-in packs + local curated catalog + cached remote records).

Args:

ts
{
  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:

ts
{
  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:

  1. Built-in capability packs (pack:*) are directly enable-able and can change MCP exposure.
  2. Skills (kind=skill) use the same capability_enable op for activate/deactivate and can auto-apply declared dependencies.
  3. External MCP execution path supports remote_only, package, and command.
  4. package mode supports npm (npx), pypi (uvx/pipx), OCI (docker/podman) and can fall back to command candidates when package metadata is incomplete.
  5. External enable runs preflight first (required env, runtime binary availability, remote URL sanity) and returns reason=preflight_failed:<code> on deterministic blockers.
  6. activation_pending means relist/reconnect is still required; runnable means binding is live enough to try; verified is reserved for post-call proof, not plain enable.

Args:

ts
{
  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:

ts
{
  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:

  1. CCCC_CAPABILITY_MAX_ENABLED_PER_ACTOR (default 20) limits actor/session enabled non-skill capability count.
  2. CCCC_CAPABILITY_MAX_ENABLED_PER_GROUP (default 24) limits group-scope enabled capability count.
  3. CCCC_CAPABILITY_MAX_INSTALLATIONS_TOTAL (default 128) limits total cached external artifacts.
  4. Quota failures return ok=true with state="failed" and deterministic reason code.

capability_block

Block/unblock capabilities at runtime.

Notes:

  1. scope=group: foreman or user can block/unblock.
  2. scope=global: only user can block/unblock.
  3. Blocking revokes enabled bindings and runtime dynamic tool exposure immediately.

Args:

ts
{
  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:

ts
{
  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:

ts
{
  group_id: string
  actor_id?: string
  by?: string
  capability_id?: string // optional; returns capability_usage for this id
}

Result:

ts
{
  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:

  1. Capability catalog is daemon-owned local state seeded from allowlist and runtime discoveries.
  2. Search uses local curated catalog + cached remote results; no periodic capability sync loop.
  3. Source gates:
    • CCCC_CAPABILITY_SOURCE_MCP_REGISTRY_ENABLED (default 1)
    • CCCC_CAPABILITY_SOURCE_ANTHROPIC_SKILLS_ENABLED (default 1)
    • github_skills_curated is allowlist-curated (no periodic source crawler).
    • agent_self_proposed is 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_remote is on-demand SkillsMP remote search (API key mode + proxy fallback).
    • clawhub_remote is on-demand ClawHub remote search (official API).
    • openclaw_skills_remote is on-demand OpenClaw GitHub corpus search.
    • clawskills_remote is on-demand clawskills.co index search.
  4. Dynamic tool exposure is capped by CCCC_CAPABILITY_MAX_DYNAMIC_TOOLS_VISIBLE (default 32).
  5. Catalog snapshot size is capped by CCCC_CAPABILITY_CATALOG_MAX_RECORDS (default 20000); prune is applied during explicit sync operations.
  6. Search may perform remote augmentation (MCP + skill) when local hits are insufficient:
    • CCCC_CAPABILITY_SEARCH_REMOTE_FALLBACK (default 1)
    • CCCC_CAPABILITY_SEARCH_REMOTE_FALLBACK_LIMIT (default 40, max 100)
    • CCCC_CAPABILITY_SOURCE_SKILLSMP_REMOTE_ENABLED (default 1)
    • CCCC_CAPABILITY_SOURCE_CLAWHUB_REMOTE_ENABLED (default 1)
    • CCCC_CAPABILITY_SOURCE_OPENCLAW_SKILLS_REMOTE_ENABLED (default 1)
    • CCCC_CAPABILITY_SOURCE_CLAWSKILLS_REMOTE_ENABLED (default 1)
    • 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 (default https://r.jina.ai/http://skillsmp.com/search)
    • CCCC_CAPABILITY_SKILLSMP_API_BASE (default https://skillsmp.com/api/v1/skills/search)
    • CCCC_CAPABILITY_SKILLSMP_API_KEY (optional; enables direct SkillsMP API)
    • CCCC_CAPABILITY_CLAWHUB_API_BASE (default https://clawhub.ai/api/v1/skills)
    • CCCC_CAPABILITY_CLAWSKILLS_DATA_URL (default https://clawskills.co/skills-data.js)
  7. Allowlist override env/path compatibility (CCCC_CAPABILITY_ALLOWLIST_PATH and CCCC_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).

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:

ts
{
  group_id: string
  by?: string
  actor_id?: string // default: by or "user"
  capability_id: string
  hidden: boolean
  reason?: string
}

Result:

ts
{
  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:

  1. This op does not parse arbitrary web/forum text; caller must provide structured record.
  2. kind=mcp_toolpack requires install_mode + install_spec.
  3. kind=skill requires capsule_text.
  4. dry_run=true validates/probes only (no catalog persistence).
  5. command* and fallback_command* may be provided as top-level shortcuts; daemon copies them into install_spec when missing.
  6. record.source_id is optional; empty or unknown source ids are normalized to manual_import.
  7. record.source_id=agent_self_proposed preserves autonomous skill-proposal provenance. Default policy treats kind=skill capsule records from this source as mounted, while non-skill toolpacks remain indexed unless policy explicitly promotes them.
  8. agent_self_proposed skill capsule text must include required proposal sections: When to use, Avoid when, Procedure, Pitfalls, and Verification; non-dry-run imports missing sections are rejected before catalog persistence so the last valid active record is preserved.
  9. agent_self_proposed skill capability ids must use skill:agent_self_proposed:<stable-slug> to avoid colliding with curated namespaces such as skill:anthropic:* or skill:github:*.
  10. For low-risk, syntax-valid agent_self_proposed capsule skills, direct import is allowed. Use dry_run=true first when enabling immediately, scope/risk is unclear, or probe diagnostics are useful; high-risk candidates should be recorded as qualification_status=blocked with explicit qualification_reasons.
  11. Re-importing the same capability_id updates the catalog record. Agents should use that path for stale, incomplete, wrong, or duplicative agent_self_proposed skills instead of creating near-duplicates or silently deleting records.
  12. Import results use import_action, record_changed, already_active, and active_after_import to 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 supplied updated_at_source still participates in the comparison. import_action is the primary create/update/unchanged signal; record_changed only compares existing records. already_active is pre-import state; active_after_import is the post-import runnable binding.
  13. If readiness_preview.preview_status=active or active_after_import=true, agents must not re-enable the same skill just to refresh its capsule text. Use capability_state.active_capsule_skills[].capsule_text for full post-import verification; capsule_preview is only a compact display summary.

Args:

ts
{
  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:

ts
{
  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:

ts
{ by?: string } // write ops still enforce by=user; read is open

Result:

ts
{
  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:

ts
{
  mode?: "patch" | "replace" // default: patch
  patch?: Record<string, unknown>   // required when mode=patch
  overlay?: Record<string, unknown> // required when mode=replace
}

Result:

ts
{
  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:

ts
{
  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:

ts
{
  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_mismatch
  • allowlist_validation_failed

capability_allowlist_reset

Reset overlay to empty (removes CCCC_HOME/config/capability-allowlist.user.yaml when present).

Args:

ts
{ by?: string } // must be "user"

Result:

ts
{
  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:

ts
{
  group_id: string
  capability_id: string
  reason?: string
  by?: string
  actor_id?: string
}

Result:

ts
{
  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:

ts
{
  group_id: string
  actor_id?: string
  by?: string
  tool_name: string
  arguments?: Record<string, unknown>
}

Result:

ts
{
  tool_name: string
  capability_id: string
  result: Record<string, unknown>
}

group_show

Args:

ts
{ group_id: string }

Result:

ts
{ group: Record<string, unknown> } // group.yaml content, redacted

group_create

Args:

ts
{ title?: string; topic?: string; by?: string }

Result:

ts
{ group_id: string; title?: string; event?: CCCSEventV1 }

group_update

Args:

ts
{ group_id: string; by?: string; patch: { title?: string; topic?: string } }

Result:

ts
{ group_id: string; group: Record<string, unknown>; event: CCCSEventV1 }

group_delete

Args:

ts
{ group_id: string; by?: string }

Result:

ts
{ group_id: string }

group_use

Set the active scope for a group using path (must already be attached).

Args:

ts
{ group_id: string; path: string; by?: string }

Result:

ts
{ group_id: string; active_scope_key: string; event: CCCSEventV1 }

group_detach_scope

Args:

ts
{ group_id: string; scope_key: string; by?: string }

Result:

ts
{ group_id: string; ruleset: { rules: Array<unknown>; snippets: Record<string, string> }; event: CCCSEventV1 }

group_set_state

Args:

ts
{ group_id: string; state: "active" | "idle" | "paused"; by?: string }

Notes:

  • stopped is not a valid group_set_state value in daemon IPC v1.
  • Higher-level surfaces (CLI/MCP) MAY expose stopped as a convenience alias that maps to group_stop.

Result:

ts
{ group_id: string; state: string; event: CCCSEventV1 }

group_settings_update

Update group-scoped messaging/automation/delivery/transcript settings.

Args:

ts
{ 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:

ts
{ 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:

ts
{ group_id: string; assistant_id?: "voice_secretary" }

Result:

ts
{
  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:

ts
{
  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:

ts
{ 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:

ts
{
  group_id: string
  by?: string
  model_id: string
}

Result:

ts
{
  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:

ts
{
  group_id: string
  by?: string
  audio_base64: string
  mime_type?: string
  language?: string
}

Preconditions:

  • voice_secretary is enabled for the group.
  • recognition_backend is assistant_service_local_asr.
  • The selected service_model_id is 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:

ts
{
  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:

ts
{
  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:

ts
{
  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:

ts
{
  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:

ts
{
  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:

ts
{ group_id: string; include_archived?: boolean }

Result:

ts
{
  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:

ts
{ group_id: string; by?: "voice-secretary" | "assistant:voice_secretary" }

Result:

ts
{
  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:

ts
{
  group_id: string
  by?: string
  document_path?: string
  workspace_path?: string
  title?: string
  content?: string
  status?: "active" | "archived"
  create_new?: boolean
}

Result:

ts
{ 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:

ts
{
  group_id: string
  by?: string
  document_path: string
  instruction?: string
  source_text?: string
  trigger?: Record<string, unknown>
}

Result:

ts
{
  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:

ts
{
  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:

ts
{
  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:

ts
{ group_id: string; by?: string; document_path: string }

Result:

ts
{ 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:

ts
{ group_id: string; by?: string; assistant_id: "voice_secretary"; lifecycle: "disabled" | "idle" | "running" | "working" | "waiting" | "failed"; health?: Record<string, unknown> }

Result:

ts
{ group_id: string; assistant: Record<string, unknown>; event: CCCSEventV1 }

group_automation_update

Replace group automation rules + snippets (scheduled system.notify).

Args:

ts
{
  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:

ts
{ group_id: string; ruleset: Record<string, unknown>; version: number; event: CCCSEventV1 }

group_automation_state

Get effective automation state for a caller.

Args:

ts
{ group_id: string; by?: string }

Result:

ts
{
  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:

  • by as a peer receives a filtered view: group rules + own personal rules.

group_automation_manage

Incremental automation management with action list.

Args:

ts
{
  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:

ts
{
  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:

ts
{ group_id: string; by?: string; expected_version?: number }

Result:

ts
{
  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:

ts
{ group_id: string; by?: string }

Result:

ts
{ group_id: string; started: string[]; forced_headless?: string[]; event: CCCSEventV1 }

group_stop

Stop (disable + terminate) all actors in the group.

Args:

ts
{ group_id: string; by?: string }

Result:

ts
{ group_id: string; stopped: string[]; event: CCCSEventV1 }

8.4 Actors

actor_list

Args:

ts
{ group_id: string; include_unread?: boolean }

Result:

ts
{ actors: Array<Record<string, unknown>> } // includes at least id/title/runner/runtime/enabled + role/running

actor_add

Args:

ts
{
  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_private is restricted to by="user" and values are never returned.
  • If env_private is 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_id links the actor to a global Actor Profile and applies profile-controlled runtime fields + profile secrets.
  • When profile_id is used, env_private is rejected (linked actor private env is profile-controlled).

Result:

ts
{ actor: Record<string, unknown>; event: CCCSEventV1 }

actor_update

Args:

ts
{
  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:

ts
{ actor: Record<string, unknown>; event: CCCSEventV1 }

actor_remove

Args:

ts
{ group_id: string; actor_id: string; by?: string }

Result:

ts
{ actor_id: string; event: CCCSEventV1 }

actor_start / actor_stop / actor_restart

Args:

ts
{ group_id: string; actor_id: string; by?: string }

Result:

ts
{ actor: Record<string, unknown>; event: CCCSEventV1 }

Notes:

  • For linked actors (profile_id set), actor_start and actor_restart first 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:

ts
{ group_id: string; actor_id: string; by?: string }

Result:

ts
{ actor: Record<string, unknown>; event: CCCSEventV1; new_session: true }

Notes:

  • Supported for claude and codex actors.
  • 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:

ts
{}

Result:

ts
{
  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.env must 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:

ts
{
  cwd?: string
  auto_enable_tools?: boolean // alias: yes
  force_mcp?: boolean         // alias: force
}

Result:

ts
{
  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 when auto_enable_tools/yes is 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:

ts
{
  cwd?: string
  group_id?: string
  actor_id?: string
}

Result:

ts
{
  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.envprivate_env → injected CCCC_GROUP_ID/CCCC_ACTOR_ID.
  • This operation is restricted to by="user" (agents should not be able to read/inspect secrets metadata).

Args:

ts
{ group_id: string; actor_id: string; by?: string }

Result:

ts
{ 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:

ts
{
  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:

ts
{ 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:

ts
{ by?: string }

Result:

ts
{ profiles: Array<Record<string, unknown>> } // each profile includes usage_count

actor_profile_get

Args:

ts
{ profile_id: string; by?: string }

Result:

ts
{
  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:

ts
{
  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.env is accepted only as a legacy bridge and migrated into profile secrets; stored profile env is kept empty.

Result:

ts
{ profile: Record<string, unknown> }

actor_profile_delete

Args:

ts
{ 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:

ts
{
  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:

ts
{ profile_id: string; by?: string }

Result:

ts
{ profile_id: string; keys: string[]; masked_values: Record<string, string> }

actor_profile_secret_update

Update profile-level secrets (write-only values).

Args:

ts
{
  profile_id: string
  by?: string
  set?: Record<string, string>
  unset?: string[]
  clear?: boolean
}

Result:

ts
{ 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:

ts
{
  profile_id: string
  group_id: string
  actor_id: string
  by?: string
}

Result:

ts
{ 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:

ts
{
  profile_id: string
  source_profile_id: string
  by?: string
}

Result:

ts
{ 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):

ts
{
  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:

ts
{ event: CCCSEventV1 } // kind="chat.message"

reply

Append a chat.message with reply_to and quote_text.

Args:

ts
{
  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:

ts
{ 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:

ts
{
  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:

ts
{
  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_ref in the emitted chat.message.data.refs is 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_id to avoid duplicate task/message pairs.

send_cross_group

Cross-group send implemented as:

  1. Write a source chat.message in the origin group (with dst_group_id / dst_to metadata).
  2. Write a forwarded chat.message in the destination group with src_group_id / src_event_id provenance.

Args:

ts
{ group_id: string; dst_group_id: string; text: string; by?: string; to?: string[]; priority?: "normal" | "attention" }

Result:

ts
{ 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:

ts
{ group_id: string; actor_id: string; event_id: string; by?: string }

Result:

ts
{ 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:

ts
{ group_id: string; actor_id: string; by?: string; limit?: number; kind_filter?: "all" | "chat" | "notify" }

Result:

ts
{ 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:

ts
{ group_id: string; actor_id: string; event_id: string; by?: string }

Result:

ts
{ 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:

ts
{ group_id: string; actor_id: string; by?: string; kind_filter?: "all" | "chat" | "notify" }

Result:

ts
{ cursor: { event_id: string; ts: string; updated_at: string }; event: CCCSEventV1 | null }

8.8 Context and Tasks

context_get

Args:

ts
{ group_id: string }

Result:

ts
{
  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, or task_list include task_type.

context_sync

Args:

ts
{ group_id: string; by?: string; ops: Array<Record<string, unknown>>; dry_run?: boolean }

Operation item shape (normative minimum):

ts
type ContextOpV1 = { op: string } & Record<string, unknown>

Notes:

  • Unknown op names SHOULD be rejected.
  • See docs/standards/CCCC_CONTEXT_OPS_V1.md for the v2 operation list.

Result:

ts
{
  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:

ts
{ group_id: string }

Result:

ts
{
  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:

ts
{
  group_id: string
  mode?: "scan" | "rebuild"   // default "scan"
}

Result:

ts
{
  indexed_files: number
  indexed_chunks: number
  watched_paths: string[]
  last_sync_at: string
}

Args:

ts
{
  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:

ts
{
  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:

ts
{
  group_id: string
  path: string
  offset?: number   // 1-indexed, default 1
  limit?: number    // default 200
}

Result:

ts
{
  path: string
  offset: number
  limit: number
  total_lines: number
  content: string
}

memory_reme_context_check

Args:

ts
{
  group_id: string
  messages: Array<{ role: string; name?: string; content: string }>
  context_window_tokens?: number
  reserve_tokens?: number
  keep_recent_tokens?: number
}

Result:

ts
{
  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:

ts
{
  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:

ts
{ summary: string } | { prompt: Record<string, string> }

memory_reme_daily_flush

Args:

ts
{
  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:

ts
{
  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:

ts
{
  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:

ts
{
  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:

ts
{ group_id: string; task_id?: string }

Result:

ts
{ 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:

ts
{ task_id: string; task_name?: string; task_goal?: string; theme_hint?: string }

Result:

ts
{ source: "predefined" | "llm"; blueprint_id: string; variant?: number }

Notes:

  • Current implementation uses deterministic FNV-1a hashing on task_id to select from predefined blueprints (shield, house, rocket).
  • Returns error code missing_task_id if task_id is empty.

8.9 Headless Runner

headless_status

Args:

ts
{ group_id: string; actor_id: string }

Result:

ts
{ state: Record<string, unknown> } // see src/cccc/contracts/v1/actor.py HeadlessState

headless_set_status

Args:

ts
{ group_id: string; actor_id: string; status: "idle" | "working" | "waiting" | "stopped"; task_id?: string | null }

Result:

ts
{ state: Record<string, unknown> | null }

headless_ack_message

Args:

ts
{ group_id: string; actor_id: string; message_id: string }

Result:

ts
{ message_id: string; acked_at: string }

8.10 System Notifications (Not Chat)

system_notify

Args:

ts
{
  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:

ts
{ event: CCCSEventV1 } // kind="system.notify"

notify_ack

Args:

ts
{ group_id: string; actor_id: string; notify_event_id: string; by?: string }

Result:

ts
{ event: CCCSEventV1 } // kind="system.notify_ack"

8.11 Terminal Diagnostics and PTY Attach

terminal_tail

Args:

ts
{ group_id: string; actor_id: string; by?: string; max_chars?: number; strip_ansi?: boolean; compact?: boolean }

Result:

ts
{ group_id: string; actor_id: string; warning: string; hint: string; text: string }

terminal_history

Args:

ts
{ group_id: string; actor_id: string; by?: string; before?: number; limit_bytes?: number; strip_ansi?: boolean; compact?: boolean }

Result:

ts
{
  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:

ts
{ group_id: string; actor_id: string; by?: string }

Result:

ts
{ group_id: string; actor_id: string; cleared: true }

debug_tail_logs

Tail daemon/web/im-bridge log files (developer mode).

Args:

ts
{ component: "daemon" | "ccccd" | "web" | "im" | "im_bridge"; group_id?: string; by?: string; lines?: number }

Result:

ts
{ component: string; group_id: string; path: string; lines: string[] }

debug_clear_logs

Truncate daemon/web/im-bridge log files (developer mode).

Args:

ts
{ component: "daemon" | "ccccd" | "web" | "im" | "im_bridge"; group_id?: string; by?: string }

Result:

ts
{ component: string; group_id: string; path: string; cleared: true }

term_resize

Args:

ts
{ group_id: string; actor_id: string; cols: number; rows: number }

Result:

ts
{ group_id: string; actor_id: string; cols: number; rows: number }

term_attach (streaming upgrade)

Args:

ts
{ group_id: string; actor_id: string }

Result (handshake):

ts
{ group_id: string; actor_id: string }

After a successful handshake, the connection becomes a raw PTY stream (see §4.4).

Notes:

  • term_resize MUST be sent over a separate daemon connection (the PTY stream is not NDJSON).
  • term_attach returns not_pty_actor when the actor is not effectively running on the PTY runner.

8.12 Ledger Maintenance

ledger_snapshot

Args:

ts
{ group_id: string; by?: string; reason?: string }

Result:

ts
{ snapshot: Record<string, unknown> }

ledger_compact

Args:

ts
{ 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:

ts
{
  group_id: string
  slot: "slot-1" | "slot-2" | "slot-3" | "slot-4"
  by?: string
  viewer_mode?: "auto" | "screencast" | "vnc"
}

Handshake result:

ts
{ 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 state items when runtime/session status changes and frame items 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:

ts
{
  group_id: string
  slot: "slot-1" | "slot-2" | "slot-3" | "slot-4"
  by?: string
}

Handshake result:

ts
{ 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_unavailable when 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:

ts
{
  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:

ts
{ group_id: string }

Streaming mode:

  • The daemon pushes NDJSON EventStreamItem lines (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 kinds is provided, only matching event kinds SHOULD be emitted.
  • If by identifies an actor_id, a daemon MAY apply the same visibility rules as inbox delivery (e.g., only deliver chat.message/system.notify addressed to that actor and exclude the actor’s own chat.message events).
  • Resume (since_event_id / since_ts) is best-effort in v1; clients MUST be able to reconcile using inbox_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:

ts
{ group_id: string; key: string }

Result:

ts
{ chat_id: string; thread_id: number; platform: string }

Errors:

  • missing_keykey is empty.
  • missing_group_idgroup_id is 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:

ts
{ group_id: string }

Result:

ts
{ authorized: Array<Record<string, unknown>> }

Errors:

  • missing_group_idgroup_id is 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:

ts
{ group_id: string }

Result:

ts
{
  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_idgroup_id is empty.
  • group_not_found – group does not exist.

im_reject_pending

Reject a pending one-time bind key.

Args:

ts
{ group_id: string; key: string }

Result:

ts
{ rejected: boolean } // idempotent: false when key is already absent/expired

Errors:

  • missing_keykey is empty.
  • missing_group_idgroup_id is empty.
  • group_not_found – group does not exist.

im_revoke_chat

Revoke authorization for an IM chat.

Args:

ts
{ group_id: string; chat_id: string; thread_id?: number }

Result:

ts
{ revoked: boolean; unsubscribed?: boolean }

Notes:

  • thread_id defaults to 0 if omitted or invalid.

Errors:

  • missing_chat_idchat_id is empty.
  • missing_group_idgroup_id is 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:

ts
{ by?: string }

Result:

ts
{
  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:

ts
{
  by?: string
  provider?: "off" | "manual" | "tailscale"
  mode?: string
  require_access_token?: boolean
  web_host?: string
  web_port?: number
  web_public_url?: string
}

Result:

ts
{ remote_access: Record<string, unknown> }

remote_access_start

Start remote access according to configured provider/mode.

Args:

ts
{ by?: string }

Result:

ts
{ remote_access: Record<string, unknown> }

remote_access_stop

Stop remote access service.

Args:

ts
{ by?: string }

Result:

ts
{ 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, repo space/ sync, artifacts, general ingest/query.
  • lane="memory": finalized daily memory recall only; daemon syncs state/memory/daily/*.md asynchronously.

Normative lane rules:

  • Agent-facing surfaces SHOULD pass lane explicitly for mutating or lane-targeted actions.
  • group_space_status MAY omit lane; it returns both lanes.
  • group_space_bind|query|sources|jobs|sync are lane-targeted.
  • group_space_ingest|artifact are supported only on lane="work".
  • MEMORY.md MUST 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:

ts
{ group_id: string; provider?: "notebooklm" }

Result:

ts
{
  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:

ts
{ group_id: string; provider?: "notebooklm" }

Result:

ts
{
  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:

ts
{
  group_id: string
  provider?: "notebooklm"
}

Result:

ts
{
  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:

ts
{
  group_id: string
  provider?: "notebooklm"
  lane: "work" | "memory"
  action?: "bind" | "unbind"
  remote_space_id?: string
  by?: string
}

Result:

ts
{
  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:

ts
{
  group_id: string
  provider?: "notebooklm"
  lane: "work" | "memory"
  kind?: "context_sync" | "resource_ingest"
  payload?: Record<string, unknown>
  idempotency_key?: string
  by?: string
}

Result:

ts
{
  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:

ts
{
  group_id: string
  provider?: "notebooklm"
  lane: "work" | "memory"
  query: string
  options?: {
    source_ids?: string[] // optional remote source_id filter
  }
}

Validation notes:

  • options only supports source_ids.
  • options.language / options.lang are invalid for group_space_query because NotebookLM query API does not provide a language parameter.
  • Recommended recall order is local memory first, then lane="memory" for deep recall.

Result:

ts
{
  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_ids is provided, source_basis_hint SHOULD 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:

ts
{
  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):

ts
{
  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):

ts
{
  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:

ts
{
  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:

ts
{
  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": repo space/ reconciliation.
  • lane="memory": async daily memory notebook sync manifest / enqueue scan.

Args:

ts
{
  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:

ts
{
  provider?: "notebooklm"
  by?: string // user-only
}

Result:

ts
{
  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:

ts
{
  provider?: "notebooklm"
  by?: string // user-only
  auth_json?: string
  clear?: boolean
}

Notes:

  • clear=true removes stored credentials for this provider.
  • auth_json is write-only and never returned in response payloads.
  • Environment credential (CCCC_NOTEBOOKLM_AUTH_JSON) has higher precedence than stored credentials.

Result:

ts
{
  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:

ts
{
  provider?: "notebooklm"
  by?: string // user-only
}

Result:

ts
{
  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:

ts
{
  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:

ts
{
  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:

  • start may open a browser on the daemon host for Google sign-in when projected is false.
  • start SHOULD expose the sign-in flow through a projected browser surface when projected=true.
  • Provider write readiness remains gated by auth_configured and runtime mode.

space_provider_auth_browser_attach

Attach to the currently active projected provider-auth browser surface over a dedicated bidirectional NDJSON stream.

Args:

ts
{
  provider: "notebooklm"
  by?: string // user-only
  viewer_mode?: "auto" | "screencast" | "vnc"
}

Handshake result:

ts
{ provider: "notebooklm" }

Streaming mode:

  • After a successful handshake, the connection upgrades into the browser-surface stream described in §4.6.
  • The daemon emits state items when runtime/session status changes and frame items 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:

ts
{
  provider: "notebooklm"
  by?: string // user-only
}

Handshake result:

ts
{ 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_unavailable when 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:

ts
{
  group_id?: string
  actor_id?: string
  by?: string
  viewer_mode?: "auto" | "screencast" | "vnc"
}

Handshake result:

ts
{ 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 state items when runtime/session status changes and frame items 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_id or actor_id is supplied, the actor MUST exist and use runtime=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:

ts
{
  group_id?: string
  actor_id?: string
  by?: string
}

Handshake result:

ts
{ 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_unavailable when 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:

ts
{
  group_id: string
  by?: string
}

Result:

ts
{
  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: false means 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_file and pass the returned package_path to preview/import.

group_copy_export_file

Export one group as a zip package stored on the daemon host filesystem.

Args:

ts
{
  group_id: string
  by?: string
}

Result:

ts
{
  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:

ts
{
  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:

ts
{
  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_copy when the payload is not a valid supported CCCC group copy.
  • contains_secrets: false in 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:

ts
{
  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:

ts
{
  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_id conflicts 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:

json
{"v":1,"op":"ping","args":{}}

Response line:

json
{"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

json
{"v":1,"ok":false,"result":{},"error":{"code":"missing_group_id","message":"missing group_id","details":{}}}

Released under the Apache-2.0 License.