claw-code/SCHEMAS.md
YeonGyu-Kim 97c4b130dc feat: #164 Stage B prep — add cancel_observed field to TurnResult
#164 Stage B requires exposing whether cancellation was observed at the
turn-result level. This commit adds the infrastructure field:

Changes:
- TurnResult.cancel_observed: bool = False (query_engine.py)
- _build_timeout_result() accepts cancel_observed parameter (runtime.py)
- Two timeout paths now pass cancel_event.is_set() to signal observation (runtime.py)
- bootstrap command includes cancel_observed in turn JSON (main.py)
- SCHEMAS.md documents Turn Result Fields with cancel_observed contract

Usage:
  When a turn timeout occurs, cancel_observed=true indicates that the
  engine observed the cancellation event being set. This allows callers
  to distinguish:
    - timeout with no cancel → infrastructure/network stall
    - timeout with cancel observed → cooperative cancellation was triggered

Backward compat:
  - Existing TurnResult construction without cancel_observed defaults to False
  - bootstrap JSON output still validates per SCHEMAS.md (new field is always present)

Test results: 182 passing, 3 skipped, zero regression.

Related: #161 (wall-clock timeout), #164 (cancellation observability protocol)
ROADMAP continues #164 with Stage C (test coverage for cancellation + turn envelope).
2026-04-22 19:44:47 +09:00

9.4 KiB

JSON Envelope Schemas — Clawable CLI Contract

This document locks the field-level contract for all clawable-surface commands. Every command accepting --output-format json must conform to the envelope shapes below.

Target audience: Claws building orchestrators, automation, or monitoring against claw-code's JSON output.


Common Fields (All Envelopes)

Every command response, success or error, carries:

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "list-sessions",
  "exit_code": 0,
  "output_format": "json",
  "schema_version": "1.0"
}
Field Type Required Notes
timestamp ISO 8601 UTC Yes Time command completed
command string Yes argv[1] (e.g. "list-sessions")
exit_code int (0/1/2) Yes 0=success, 1=error/not-found, 2=timeout
output_format string Yes Always "json" (for symmetry with text mode)
schema_version string Yes "1.0" (bump for breaking changes)

Turn Result Fields (Multi-Turn Sessions)

When a command's response includes a turn object (e.g., in bootstrap or turn-loop), it carries:

Field Type Required Notes
prompt string Yes User input for this turn
output string Yes Assistant response
stop_reason enum Yes One of: completed, timeout, cancelled, max_budget_reached, max_turns_reached
cancel_observed bool Yes #164 Stage B: cancellation was signaled and observed (#161/#164)

Error Envelope

When a command fails (exit code 1), responses carry:

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "exec-command",
  "exit_code": 1,
  "error": {
    "kind": "filesystem",
    "operation": "write",
    "target": "/tmp/nonexistent/out.md",
    "retryable": true,
    "message": "No such file or directory",
    "hint": "intermediate directory does not exist; try mkdir -p /tmp/nonexistent"
  }
}
Field Type Required Notes
error.kind enum Yes One of: filesystem, auth, session, parse, runtime, mcp, delivery, usage, policy, unknown
error.operation string Yes Syscall/method that failed (e.g. "write", "open", "resolve_session")
error.target string Yes Resource that failed (path, session-id, server-name, etc.)
error.retryable bool Yes Whether caller can safely retry without intervention
error.message string Yes Platform error message (e.g. errno text)
error.hint string No Optional actionable next step

Not-Found Envelope

When an entity does not exist (exit code 1, but not a failure):

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "load-session",
  "exit_code": 1,
  "name": "does-not-exist",
  "found": false,
  "error": {
    "kind": "session_not_found",
    "message": "session 'does-not-exist' not found in .claw/sessions/",
    "retryable": false
  }
}
Field Type Required Notes
name string Yes Entity name/id that was looked up
found bool Yes Always false for not-found
error.kind enum Yes One of: command_not_found, tool_not_found, session_not_found
error.message string Yes User-visible explanation
error.retryable bool Yes Usually false (entity will not magically appear)

Per-Command Success Schemas

list-sessions

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "list-sessions",
  "exit_code": 0,
  "output_format": "json",
  "schema_version": "1.0",
  "directory": ".claw/sessions",
  "sessions_count": 2,
  "sessions": [
    {
      "session_id": "sess_abc123",
      "created_at": "2026-04-21T15:30:00Z",
      "last_modified": "2026-04-22T09:45:00Z",
      "prompt_count": 5,
      "stopped": false
    }
  ]
}

delete-session

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "delete-session",
  "exit_code": 0,
  "session_id": "sess_abc123",
  "deleted": true,
  "directory": ".claw/sessions"
}

load-session

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "load-session",
  "exit_code": 0,
  "session_id": "sess_abc123",
  "loaded": true,
  "directory": ".claw/sessions",
  "path": ".claw/sessions/sess_abc123.jsonl"
}

flush-transcript

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "flush-transcript",
  "exit_code": 0,
  "session_id": "sess_abc123",
  "path": ".claw/sessions/sess_abc123.jsonl",
  "flushed": true,
  "messages_count": 12,
  "input_tokens": 4500,
  "output_tokens": 1200
}

show-command

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "show-command",
  "exit_code": 0,
  "name": "add-dir",
  "found": true,
  "source_hint": "commands/add-dir/add-dir.tsx",
  "responsibility": "creates a new directory in the worktree"
}

show-tool

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "show-tool",
  "exit_code": 0,
  "name": "BashTool",
  "found": true,
  "source_hint": "tools/BashTool/BashTool.tsx"
}

exec-command

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "exec-command",
  "exit_code": 0,
  "name": "add-dir",
  "prompt": "create src/util/",
  "handled": true,
  "message": "created directory",
  "source_hint": "commands/add-dir/add-dir.tsx"
}

exec-tool

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "exec-tool",
  "exit_code": 0,
  "name": "BashTool",
  "payload": "cargo build",
  "handled": true,
  "message": "exit code 0",
  "source_hint": "tools/BashTool/BashTool.tsx"
}

route

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "route",
  "exit_code": 0,
  "prompt": "add a test",
  "limit": 10,
  "match_count": 3,
  "matches": [
    {
      "kind": "command",
      "name": "add-file",
      "score": 0.92,
      "source_hint": "commands/add-file/add-file.tsx"
    }
  ]
}

bootstrap

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "bootstrap",
  "exit_code": 0,
  "prompt": "hello",
  "setup": {
    "python_version": "3.13.12",
    "implementation": "CPython",
    "platform_name": "darwin",
    "test_command": "pytest"
  },
  "routed_matches": [
    {"kind": "command", "name": "init", "score": 0.85, "source_hint": "..."}
  ],
  "turn": {
    "prompt": "hello",
    "output": "...",
    "stop_reason": "completed"
  },
  "persisted_session_path": ".claw/sessions/sess_abc.jsonl"
}

command-graph

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "command-graph",
  "exit_code": 0,
  "builtins_count": 185,
  "plugin_like_count": 20,
  "skill_like_count": 2,
  "total_count": 207,
  "builtins": [
    {"name": "add-dir", "source_hint": "commands/add-dir/add-dir.tsx"}
  ],
  "plugin_like": [],
  "skill_like": []
}

tool-pool

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "tool-pool",
  "exit_code": 0,
  "simple_mode": false,
  "include_mcp": true,
  "tool_count": 184,
  "tools": [
    {"name": "BashTool", "source_hint": "tools/BashTool/BashTool.tsx"}
  ]
}

bootstrap-graph

{
  "timestamp": "2026-04-22T10:10:00Z",
  "command": "bootstrap-graph",
  "exit_code": 0,
  "stages": ["stage 1", "stage 2", "..."],
  "note": "bootstrap-graph is markdown-only in this version"
}

Versioning & Compatibility

  • schema_version = "1.0": Current as of 2026-04-22. Covers all 13 clawable commands.
  • Breaking changes (e.g. renaming a field) bump schema_version to "2.0".
  • Additive changes (e.g. new optional field) stay at "1.0" and are backward compatible.
  • Downstream claws must check schema_version before relying on field presence.

Regression Testing

Each command is covered by:

  1. Fixture file (golden JSON snapshot under tests/fixtures/json/<command>.json)
  2. Parametrised test in test_cli_parity_audit.py::TestJsonOutputContractEndToEnd
  3. Field consistency test (new, tracked as ROADMAP #172)

To update a fixture after a intentional schema change:

claw <command> --output-format json <args> > tests/fixtures/json/<command>.json
# Review the diff, commit
git add tests/fixtures/json/<command>.json

To verify no regressions:

cargo test --release test_json_envelope_field_consistency

Design Notes

Why common fields on every response?

  • Downstream claws can build one error handler that works for all commands
  • Timestamp + command + exit_code give context without scraping argv or timestamps from command output
  • schema_version signals compatibility for future upgrades

Why both "found" and "error" on not-found?

  • Exit code 1 covers both "entity missing" and "operation failed"
  • found=false distinguishes not-found from error without string matching
  • error.kind and error.retryable let automation decide: retry a temporary miss vs escalate a permanent refusal

Why "operation" and "target" in error?

  • Claws can aggregate failures by operation type (e.g. "how many write ops failed?")
  • Claws can implement per-target retry policy (e.g. "skip missing files, retry networking")
  • Pure text errors ("No such file") do not provide enough structure for pattern matching

Why "handled" vs "found"?

  • show-command reports found: bool (inventory signal: "does this exist?")
  • exec-command reports handled: bool (operational signal: "was this work performed?")
  • The names matter: a command can be found but not handled (e.g. too large for context window), or handled silently (no output message)