Skip to content

CCCC Context Ops Contract v3 ​

Status: Active (Context v3 in the native CCCC workspace)

This document defines the operation list and payload shapes for daemon IPC:

  • context_sync (see docs/standards/CCCC_DAEMON_IPC_V1.md)

The goal is to make context_sync usable from SDKs and MCP/Web ports without reading daemon source code.

0. Conformance Language ​

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.

1. Overview ​

context_sync applies a batch of small operations to a group's context v3 storage:

  • coordination = shared control plane (brief, recent notes)
  • tasks = shared dispatch truth
  • agent_states = per-actor short-term working memory
  • meta = restricted projection-support metadata

Read-only projections such as board and attention are daemon-computed and are not directly writable via context ops.

All operations are applied in order. Each operation is authorized against the in-memory result of the preceding operations under the Context storage lock. If any operation is invalid or unauthorized, the daemon rejects the entire batch before writing payload files. Task IDs used for authorization MUST match the IDs used by the corresponding operation, including accepted whitespace.

Canonical Context files that are unreadable, malformed, or contain inconsistent identity metadata MUST cause an explicit error; they MUST NOT be interpreted as empty state and overwritten by a later update. Missing files still support new Groups and the documented legacy migration path.

Context uses atomic replacement of individual files, not a multi-file database transaction. A storage failure can leave part of a validated batch written. The revision is reserved before payload writes so such a failure invalidates prior if_version tokens. After an I/O error, callers MUST reload the current state before deciding what to retry; a newer revision alone is not a success receipt. Dry runs and rejected batches do not reserve a revision.

context_sync only mutates the Context v3 stores named by its operations. It MUST NOT implicitly write the separate durable memory store. Callers that want to retain a decision or outcome beyond the bounded Context projections MUST use the explicit memory_reme_write daemon operation after a successful context_sync receipt.

2. Operation Item Shape ​

Each item in args.ops MUST be a JSON object with:

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

Rules:

  • op MUST be one of the strings listed in §3.
  • Unknown op values MUST be rejected.
  • Unknown fields MAY be ignored unless an op explicitly forbids them.

3. Operation List ​

3.1 Coordination ​

coordination.brief.update ​

ts
{
  op: "coordination.brief.update"
  objective?: string
  current_focus?: string
  constraints?: string[]
  project_brief?: string
  project_brief_stale?: boolean
}

Permission: foreman or user.

Notes:

  • Partial patch: only provided fields are updated.
  • Daemon sets updated_by and updated_at automatically.

coordination.note.add ​

ts
{
  op: "coordination.note.add"
  kind: "decision" | "handoff"
  summary: string
  task_id?: string | null
}

Permission: any actor.

Notes:

  • Used for compact recent decisions / handoffs.
  • Daemon keeps only the newest bounded slice in context.
  • This operation does not implicitly copy the note into daily or stable memory.

3.2 Tasks ​

Task statuses:

ts
type TaskStatus = "planned" | "active" | "done" | "archived"

Checklist statuses:

ts
type ChecklistStatus = "pending" | "in_progress" | "done"

Waiting values:

ts
type TaskWaitingOn = "none" | "user" | "actor" | "external"

Task type values:

ts
type TaskType =
  | "free"
  | "standard"
  | "optimization"

task.create ​

ts
{
  op: "task.create"
  title: string
  outcome?: string
  status?: TaskStatus
  parent_id?: string | null
  assignee?: string | null
  priority?: string
  blocked_by?: string[]
  waiting_on?: TaskWaitingOn
  handoff_to?: string | null
  task_type?: TaskType
  notes?: string
  checklist?: Array<{ id?: string; text: string; status?: ChecklistStatus }>
}

Permission: any actor.

Rules:

  • title is required.
  • If parent_id is provided, the parent task MUST exist.
  • Peer actors MUST NOT create a task assigned to another peer.
  • If task_type is provided, it MUST be one of the allowed task-type ids above.
  • If task_type is omitted, the daemon MUST persist standard for a root task and free for a subtask.
  • task_type stores the durable work kind. It MUST NOT be treated as lifecycle state or parent/child structure.
  • task_type does not auto-seed notes or checklist; those remain ordinary editable task content.

task.update ​

ts
{
  op: "task.update"
  task_id: string
  title?: string
  outcome?: string
  parent_id?: string | null
  assignee?: string | null
  priority?: string
  blocked_by?: string[]
  waiting_on?: TaskWaitingOn
  handoff_to?: string | null
  task_type?: TaskType
  notes?: string
  checklist?: Array<{ id?: string; text: string; status?: ChecklistStatus }>
}

Permission: assignee / handoff target / foreman / user.

Rules:

  • Partial patch.
  • If parent_id changes, the daemon MUST reject cycles.
  • task.update does not change lifecycle status.
  • If task_type is provided, it MUST be one of the allowed task-type ids above.

task.move ​

ts
{
  op: "task.move"
  task_id: string
  status: TaskStatus
}

Permission: assignee / handoff target / foreman / user.

Notes:

  • This is the canonical lifecycle transition op.
  • task.move only accepts task_id and status. Fields such as outcome, notes, checklist, or task_type MUST go through task.update.
  • Moving to archived records archived_from.
  • This operation changes only task lifecycle state. It does not implicitly rewrite an assignee's agent_state or append daily/stable memory.

task.restore ​

ts
{ op: "task.restore"; task_id: string }

Permission: assignee / handoff target / foreman / user.

Rules:

  • The task MUST currently be archived.

task.delete ​

ts
{ op: "task.delete"; task_id: string }

Permission: assignee / handoff target / foreman / user.

Rules:

  • This operation is for removing work that never entered execution, not for erasing task history.
  • The target task and every descendant MUST be planned, or archived directly from planned.
  • If the target or any descendant has execution history, the entire operation MUST be rejected.
  • A successful delete removes the complete target subtree atomically.
  • Task deletion does not implicitly rewrite actor-owned agent_state; an actor or the local user recovery authority may reconcile stale working-state hints explicitly.

3.3 Agent State ​

Agent states are keyed by actor_id.

Agent state is actor-owned short-term working context. Task and coordination operations MUST NOT implicitly update or clear it. Normal writes are explicit self-writes through the operations below; the local user principal remains the recovery authority described in §4.

agent_state.update ​

ts
{
  op: "agent_state.update"
  actor_id: string
  active_task_id?: string | null
  focus?: string
  blockers?: string[]
  next_action?: string
  what_changed?: string
  open_loops?: string[]
  commitments?: string[]
  environment_summary?: string
  user_model?: string
  persona_notes?: string
}

Permission: self / user.

Notes:

  • Partial patch.
  • Daemon stores data into hot (active_task_id, focus, blockers, next_action) and warm (the rest).
  • open_loops is the current memo for unfinished work, unresolved facts, risks, assumptions, and follow-ups.
  • commitments records promises made to users or other actors.
  • agent_state is actor-owned working state. Foreman should guide peers via tasks, coordination, or help/role-notes, not by directly rewriting a peer's agent_state.
  • resume_hint is not part of this contract; clients must use open_loops for current memo entries.
  • Legacy aliases agent_id, environment, user_profile, and notes are tolerated by daemon but SHOULD NOT be used by new clients.

agent_state.clear ​

ts
{ op: "agent_state.clear"; actor_id: string }

Permission: self / user.

Notes:

  • Clears both hot and warm, then refreshes updated_at.

3.4 Restricted Meta ​

meta.merge ​

ts
{
  op: "meta.merge"
  data: {
    project_status?: string | null
  }
}

Permission: foreman or user.

Rules:

  • Only project_status is allowed.
  • Resulting meta payload MUST stay within daemon size limits.

4. Optimistic Concurrency (CAS) ​

context_sync accepts an optional if_version field:

ts
{ group_id: string; ops: [...]; if_version?: string }

Rules:

  • If if_version is provided and does not match the current context version hash, the entire batch MUST be rejected with error code version_conflict.
  • If if_version is omitted, no version check is performed.

5. Dry Run ​

If context_sync.args.dry_run == true, the daemon MUST NOT persist changes and SHOULD still return a computed changes list.

6. Result Notes ​

context_sync returns changes: Array<{ index, op, detail }> where detail is intended for logs/UI. SDKs SHOULD NOT parse detail as a stable machine contract.

When Group Space is enabled and a curated context change is detected, the daemon MAY also return:

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

Curated trigger allowlist:

  • coordination.*
  • task.*

agent_state.* updates do not trigger Group Space export.

7. Permission Model ​

RoleAllowed ops
userAll ops
foremanAll ops except changing another actor's agent_state
peercoordination.note.add, task.create, task.update (own assigned / handed-off), task.move (own assigned / handed-off), task.restore (own assigned / handed-off), task.delete (own assigned / handed-off), agent_state.update (self), agent_state.clear (self)

Permission checks use context_sync.args.by.

Released under the Apache-2.0 License.