SCHEMAS.md was presenting the target v2.0 schema as the current binary contract. This is the source of truth document, so the misdocumentation propagated to every downstream doc (USAGE.md, ERROR_HANDLING.md, CLAUDE.md all inherited the false premise that v1.0 includes timestamp/command/exit_code/etc). Fixed with: 1. CRITICAL header at top: marks entire doc as v2.0 target, not v1.0 reality 2. 'TARGET v2.0 SCHEMA' headers on Common Fields section 3. Comprehensive Appendix: v1.0 actual shape + migration timeline + v1.0 code example 4. Links to FIX_LOCUS_164.md + ERROR_HANDLING.md for v1.0 reality 5. FAQ: clarifies the version mismatch and when v2.0 ships This closes the fourth P0 doc-truthfulness instance (4/4 in family): - #78 USAGE.md: active misdocumentation (fixed #78) - #79 ERROR_HANDLING.md: copy-paste trap (fixed #79) - #165 CLAUDE.md: boundary collapse (fixed #81) - #166 SCHEMAS.md: aspirational source doc (fixed #82) Pattern is now crystallized: SCHEMAS.md was the aspirational source; three downstream docs (USAGE, ERROR_HANDLING, CLAUDE) inherited the false v2.0-as-v1.0 claim. Fix the source (SCHEMAS.md), which eliminates the root cause for all four.
17 KiB
JSON Envelope Schemas — Clawable CLI Contract
⚠️ CRITICAL: This document describes the TARGET v2.0 envelope schema, not the current v1.0 binary behavior. The Rust binary currently emits a flat v1.0 envelope that does NOT include
timestamp,command,exit_code,output_format, orschema_versionfields. SeeFIX_LOCUS_164.mdfor the full migration plan and timeline. Do not build automation against the field shapes below without first testing against the actual binary output. Useclaw <command> --output-format jsonto inspect what your binary version actually emits.
This document locks the target field-level contract for all clawable-surface commands. After the v1.0→v2.0 migration (FIX_LOCUS_164 Phase 2), every command accepting --output-format json will conform to the envelope shapes documented here.
Target audience: Claws planning v2.0 migration, reference implementers, contract validators.
Current v1.0 reality: See ERROR_HANDLING.md Appendix A for the flat envelope shape the binary actually emits today.
Common Fields (All Envelopes) — TARGET v2.0 SCHEMA
This section describes the v2.0 target schema. The current v1.0 binary does NOT emit these fields. See FIX_LOCUS_164.md for the migration timeline.
After v2.0 migration, every command response, success or error, will carry:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "list-sessions",
"exit_code": 0,
"output_format": "json",
"schema_version": "2.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)
Appendix: Current v1.0 vs. Target v2.0 Envelope Shapes
⚠️ IMPORTANT: Binary Reality vs. This Document
This entire SCHEMAS.md document describes the TARGET v2.0 schema. The actual Rust binary currently emits v1.0 (flat) envelopes.
Do not assume the fields documented above are in the binary right now. They are not.
Current v1.0 Envelope (What the Rust Binary Actually Emits)
The Rust binary in rust/ currently emits a flat v1.0 envelope without common metadata wrapper:
v1.0 Success Envelope Example
{
"kind": "list-sessions",
"sessions": [
{"id": "abc123", "created": "2026-04-22T10:00:00Z", "turns": 5}
],
"type": "success"
}
Key differences from v2.0 above:
- NO
timestamp,command,exit_code,output_format,schema_versionfields kindfield contains the verb name (or is entirely absent for success)type: "success"flag at top level- Verb-specific fields (
sessions,turn, etc.) at top level
v1.0 Error Envelope Example
{
"error": "session 'xyz789' not found in .claw/sessions",
"hint": "use 'list-sessions' to see available sessions",
"kind": "session_not_found",
"type": "error"
}
Key differences from v2.0 error above:
errorfield is a STRING, not a nested object- NO
error.operation,error.target,error.retryablestructured fields kindis at top-level, not nested- NO
timestamp,command,exit_code,output_format,schema_version - Extra
type: "error"flag
Migration Timeline (FIX_LOCUS_164)
See FIX_LOCUS_164.md for the full phased migration:
- Phase 1 (Opt-in):
claw <cmd> --output-format json --envelope-version=2.0emits v2.0 shape - Phase 2 (Default): v2.0 becomes default;
--legacy-envelopeflag opts into v1.0 - Phase 3 (Deprecation): v1.0 warnings, then removal
Building Automation Against v1.0 (Current)
For claws building automation today (against the real binary, not this schema):
- Check
typefield first (string: "success" or "error") - For success: verb-specific fields are at top level. Use
jq .kindfor verb ID (if present) - For error: access
error(string),hint(string),kind(string) all at top level - Do not expect:
timestamp,command,exit_code,output_format,schema_version— they don't exist yet - Test your code against
claw <cmd> --output-format jsonoutput to verify assumptions before deploying
Example: Python Consumer Code (v1.0)
Correct pattern for v1.0 (current binary):
import json
import subprocess
result = subprocess.run(
["claw", "list-sessions", "--output-format", "json"],
capture_output=True,
text=True
)
envelope = json.loads(result.stdout)
# v1.0: type is at top level
if envelope.get("type") == "error":
error_msg = envelope.get("error", "unknown error") # error is a STRING
error_kind = envelope.get("kind") # kind is at TOP LEVEL
print(f"Error: {error_kind} — {error_msg}")
else:
# Success path: verb-specific fields at top level
sessions = envelope.get("sessions", [])
for session in sessions:
print(f"Session: {session['id']}")
After v2.0 migration, this code will break. Claws building for v2.0 compatibility should:
- Check
schema_versionfield - Parse differently based on version
- Or wait until Phase 2 default bump is announced, then migrate
Why This Mismatch Exists
SCHEMAS.md was written as the target design for v2.0. The Rust binary is still on v1.0. The migration (FIX_LOCUS_164) will bring the binary in line with this schema, but it hasn't happened yet.
This mismatch is the root cause of doc-truthfulness issues #78, #79, #165. All three docs were documenting the v2.0 target as if it were current reality.
Questions?
- "Is v2.0 implemented?" No. The binary is v1.0. See FIX_LOCUS_164.md for the implementation roadmap.
- "Should I build against v2.0 schema?" No. Build against v1.0 (current). Test your code with
clawto verify. - "When does v2.0 ship?" See FIX_LOCUS_164.md Phase 1 estimate: ~6 dev-days. Not scheduled yet.
- "Can I use v2.0 now?" Only if you explicitly pass
--envelope-version=2.0(which doesn't exist yet in v1.0 binary).