Cycle #46 follow-up to cycle #45's #251 implementation. Closes #250's implementation urgency by aligning docs with reality. SCHEMAS.md Updates: For each of the 4 session-management verbs, added: 1. Status marker (Implemented or Stub only) 2. Actual binary envelope (shape produced by the #251-fixed binary) 3. Aspirational (future) shape (original SCHEMAS.md content, preserved as target) 4. Gap notes where the two diverge Per-verb status: - list-sessions: Implemented, nested field layout - load-session: Implemented, nested session object with local session_not_found error - delete-session: Stub, emits not_yet_implemented (local error, not auth) - flush-transcript: Stub, emits not_yet_implemented (local error, not auth) ROADMAP.md Updates: - #251 marked CLOSED: Full status with commit ref, test counts. - #250 marked SCOPE-REDUCED: Option A resolved by #251, Option C moot, only Option B (doc alignment) remains as future cleanup. Why this matters: Every code change should close its documentation loop. #251 landed on the branch, but SCHEMAS.md still described aspirational shapes without marking which were implemented. Claws reading SCHEMAS.md would have assumed full conformance and hit surprises. Now the document tells the truth about which verbs work, which are stubs, and why. Related: - #251 implementation on feat/jobdori-251-session-dispatch branch - #250 scope-reduced to Option B (field-name harmonization) - #145/#146 parser fall-through fix precedent
12 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
Status: ✅ Implemented (closed #251 cycle #45, 2026-04-23).
Actual binary envelope (as of #251 fix):
{
"command": "list-sessions",
"sessions": [
{
"id": "session-1775777421902-1",
"path": "/path/to/.claw/sessions/session-1775777421902-1.jsonl",
"updated_at_ms": 1775777421902,
"message_count": 0
}
]
}
Aspirational (future) shape:
{
"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
}
]
}
Gap: Current impl lacks timestamp, exit_code, output_format, schema_version, directory, sessions_count (derivable), and the session object uses id/updated_at_ms/message_count instead of session_id/last_modified/prompt_count. Follow-up #250 Option B to align field names and add common-envelope fields.
delete-session
Status: ⚠️ Stub only (closed #251 dispatch-order fix; full impl deferred).
Actual binary envelope (as of #251 fix):
{
"type": "error",
"command": "delete-session",
"error": "not_yet_implemented",
"kind": "not_yet_implemented"
}
Exit code: 1. No credentials required. The stub ensures the verb does NOT fall through to Prompt/auth (the #251 fix), but the actual delete operation is not yet wired.
Aspirational (future) shape:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "delete-session",
"exit_code": 0,
"session_id": "sess_abc123",
"deleted": true,
"directory": ".claw/sessions"
}
load-session
Status: ✅ Implemented (closed #251 cycle #45, 2026-04-23).
Actual binary envelope (as of #251 fix):
{
"command": "load-session",
"session": {
"id": "session-abc123",
"path": "/path/to/.claw/sessions/session-abc123.jsonl",
"messages": 5
}
}
For nonexistent sessions, emits a local session_not_found error (NOT missing_credentials):
{
"error": "session not found: nonexistent",
"kind": "session_not_found",
"type": "error",
"hint": "Hint: managed sessions live in .claw/sessions/<hash>/ ..."
}
Aspirational (future) shape:
{
"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"
}
Gap: Current impl uses nested session: {...} instead of flat fields, and omits common-envelope fields. Follow-up #250 Option B to align.
flush-transcript
Status: ⚠️ Stub only (closed #251 dispatch-order fix; full impl deferred).
Actual binary envelope (as of #251 fix):
{
"type": "error",
"command": "flush-transcript",
"error": "not_yet_implemented",
"kind": "not_yet_implemented"
}
Exit code: 1. No credentials required. Like delete-session, this stub resolves the #251 dispatch-order bug but the actual flush operation is not yet wired.
Aspirational (future) shape:
{
"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)