#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).
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_versionbefore relying on field presence.
Regression Testing
Each command is covered by:
- Fixture file (golden JSON snapshot under
tests/fixtures/json/<command>.json) - Parametrised test in
test_cli_parity_audit.py::TestJsonOutputContractEndToEnd - 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_versionsignals compatibility for future upgrades
Why both "found" and "error" on not-found?
- Exit code 1 covers both "entity missing" and "operation failed"
found=falsedistinguishes not-found from error without string matchingerror.kindanderror.retryablelet 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
writeops 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-commandreportsfound: bool(inventory signal: "does this exist?")exec-commandreportshandled: 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)