Schema: ResponseEnvelope
File: response-envelope.json
Used by: REQ-F-004 · REQ-C-013 · REQ-C-014 · REQ-C-030 · REQ-O-041 · all commands in JSON output mode
Shape is invariant — ok, data, error, warnings, meta are always present regardless of success, failure, or result count.
ResponseEnvelope
| Field | Type | Description |
|---|---|---|
ok |
boolean | Derived from exit code. true iff exit code is SUCCESS (0). Never set by command logic |
data |
object | array | null | Primary output. null on failure, never absent |
error |
ErrorDetail | null |
null on success |
warnings |
string[] | Non-fatal messages. May be empty, never null |
meta |
ResponseMeta |
Always present |
| --- |
ErrorDetail
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | Stable machine-readable identifier. Agents branch on this, not message. Never changes between versions |
message |
string | yes | Human-readable summary. May be localized. Do not parse — use code |
detail |
string | no | Stack context or raw upstream error |
retryable |
boolean | no | true = the identical unchanged invocation may succeed and no side effects occurred. Mirrors ExitCodeEntry.retryable for the emitted exit code |
retry_after_ms |
integer | no | Milliseconds to wait before retrying. Only when retryable: true and back-off is known |
retry_strategy |
"immediate" | "linear_backoff" | "exponential_backoff" |
no | Back-off strategy to apply. Only when retryable: true |
fix_required |
string | no | Condition the caller must correct before reissuing. Only when retryable: false and the failure is caller-correctable; absent on terminal failures |
fix_command |
string | no | Exact command that resolves fix_required, executable verbatim (no placeholders). Read-only or idempotent, never destructive. Only when a single-command remediation exists |
phase |
"validation" | "execution" | "cleanup" |
no | "validation" guarantees zero side effects |
suggestion |
string | no | Actionable next step for the agent |
redirect |
Redirect |
no | Present only when exit code is REDIRECTED (13) |
| ### error.code values for AUTH_REQUIRED (8) |
The exit code intentionally does not distinguish why auth failed — that detail is in error.code:
| error.code | Meaning | Agent action |
|---|---|---|
TOKEN_EXPIRED |
Valid token, past expiry | Auto-refresh using refresh token, retry immediately |
TOKEN_INVALID |
Malformed or revoked token | Acquire new credentials |
TOKEN_MISSING |
No credentials provided | Acquire credentials |
Redirect
Present in error.redirect when exit code is REDIRECTED (13).
| Field | Type | Required | Description |
|---|---|---|---|
command |
string | yes | Exact replacement invocation. Agent uses this verbatim on retry |
permanent |
boolean | yes | true → memorize, never call the old form again. false → use replacement for this request only |
reason |
string | no | "renamed" | "restructured" | "deprecated" | "typo_corrected" |
ResponseMeta
| Field | Type | Required | Description |
|---|---|---|---|
duration_ms |
integer | yes | Wall-clock ms from entry to last byte |
request_id |
string | no | Correlation ID for logs and traces |
schema_version |
string | no | Envelope schema version e.g. "1.0" |
not_modified |
boolean | no | true on etag cache hit — data will be null |
truncated |
boolean | no | Output was capped. Agent should paginate or narrow the query |
cursor |
string | no | Pass as --cursor to get the next page |
| --- |
Examples
Success
{
"ok": true,
"data": { "id": "deploy-42", "status": "complete" },
"error": null,
"warnings": [],
"meta": { "duration_ms": 340, "request_id": "req_abc123" }
}
ARG_ERROR (2) — fix the input, then reissue; zero side effects
{
"ok": false,
"data": null,
"error": {
"code": "INVALID_ENVIRONMENT",
"message": "Unknown target environment 'prodution'",
"retryable": false,
"fix_required": "Correct the target environment argument",
"phase": "validation",
"suggestion": "Valid environments: prod, staging, dev"
},
"warnings": [],
"meta": { "duration_ms": 5 }
}
AUTH_REQUIRED (8) — token expired, agent can auto-refresh
{
"ok": false,
"data": null,
"error": {
"code": "TOKEN_EXPIRED",
"message": "Access token has expired",
"retryable": false,
"fix_required": "Refresh the access token, then reissue the call",
"fix_command": "tool auth refresh"
},
"warnings": [],
"meta": { "duration_ms": 4 }
}
REDIRECTED (13) — permanent rename, agent must memorize
{
"ok": false,
"data": null,
"error": {
"code": "COMMAND_RENAMED",
"message": "'tool user create' was renamed in v2.0",
"retryable": false,
"fix_required": "Reissue using error.redirect.command verbatim",
"redirect": {
"command": "tool users add --name alice",
"permanent": true,
"reason": "renamed"
}
},
"warnings": [],
"meta": { "duration_ms": 2 }
}
RATE_LIMITED (11) — retry after
{
"ok": false,
"data": null,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "API rate limit reached",
"retryable": true,
"retry_after_ms": 30000
},
"warnings": [],
"meta": { "duration_ms": 6 }
}
Agent interpretation
Rules for agents parsing ResponseEnvelope at runtime, including handling malformed, contradictory, or incomplete responses.
Determining outcome
- Primary signal: exit code. Secondary signal: ok. If they conflict, trust exit code
- ok: true + non-zero exit code — command proxy or wrapper may have swallowed the code; treat as failure, inspect error
- ok: false + exit code 0 — treat as success; ok may be stale in a cached or proxied response
Missing or null fields
- error key absent entirely — malformed response; treat as GENERAL_ERROR; do not retry blindly
- data: null and error: null simultaneously — malformed; log and escalate; do not act on data
- warnings key absent — treat as empty array; do not fail
Deciding whether to act on data
- meta.truncated: true — data is incomplete; do not draw conclusions or trigger downstream actions until all pages are fetched
- meta.not_modified: true — data is intentionally null; use the previously cached response; this is not an error
Retrying
- error.retryable: true without error.retry_after_ms — apply a 1s default back-off before retrying
- error.retryable: false with error.fix_required or error.redirect present — apply the stated fix, then reissue once; with neither, treat as terminal and stop
- error.fix_command present — run it verbatim (it is declared safe: read-only or idempotent), then reissue the original call once; if either step fails, stop and escalate
- error.retryable absent — fall back to the exit code's retryability from exit-code.md
- Never retry more than 3 times on the same error code without a state change
Auth flow
- error.code: "TOKEN_EXPIRED" — refresh token, replace credential, retry the original call once; if the retry also returns TOKEN_EXPIRED, escalate to TOKEN_INVALID handling
- error.code: "TOKEN_INVALID" or "TOKEN_MISSING" — do not retry; acquire new credentials from the appropriate source
Redirect flow
- error.redirect present — execute error.redirect.command verbatim on the next call; do not modify it
- error.redirect.permanent: true — record the mapping (old command → new command) before retrying; apply it to all future calls in this session and persist to memory
- error.redirect.permanent: false — use replacement for this call only; do not update stored knowledge
Warnings
- Non-empty warnings on a successful response — log all warnings; complete the current action; surface warnings to the orchestrator for review
- A warning containing words like "deprecated" or "will be removed" — treat as a soft REDIRECTED; plan to update the call in a future session
Coding agent notes
Type representation
- Generate ResponseEnvelope<T> as a generic type where T is the command's declared output schema — do not use any or object for data
- ok must be a derived/computed field, not a settable field — generate it as a property that reads the exit code, not as a constructor parameter
Construction
- Generate a single respond() / envelope() factory function that the framework calls after the command handler returns — command handlers must never construct the envelope directly
- The factory derives ok from the exit code, sets meta.duration_ms from a start timer, and validates that error is non-null whenever exit code is non-zero
Validation to generate
- A schema validator that runs on every envelope in test mode and asserts all five required fields are present and non-absent
- An assertion that ok == (exit_code == 0) — any divergence is a framework bug
- An assertion that data and error are not both null simultaneously (unless meta.not_modified: true)
Tests to generate
- Success path: ok: true, data matches declared output schema, error: null
- Failure path: ok: false, data: null, error has code and message
- Truncation path: meta.truncated: true present when output exceeds size limit
- Cache hit path: meta.not_modified: true, data: null, error: null, exit code 0
- For REDIRECTED (13): error.redirect is present with both command and permanent
Anti-patterns
- Do not let command handlers set ok directly — it must be derived
- Do not omit data on failure or error on success — both keys must always be present
- Do not generate separate envelope shapes for success and failure — the shape is invariant
Implementation notes
okmust be derived from the process exit code, not set by command logic. Prevents a command exiting non-zero withok: truedatamust always be present as a key — usenullrather than omitting iterrormust always be present as a key — usenullrather than omitting itResponseMetausesadditionalProperties: trueto allow framework extensions without breaking existing parsersErrorDetailusesadditionalProperties: truefor the same reason — requirements extend it with fields such asrefresh_command(REQ-F-063) and network context (REQ-F-037)error.redirectis only meaningful when the exit code isREDIRECTED (13). Parsers should ignore it at other exit codeserror.codeis the stable identifier agents act on.error.messageis for humans and may change between versions