Skip to content

REQ-C-015: Commands Declare Input and Output Schema

Tier: Command Contract | Priority: P1

Source: §21 Schema & Help Discoverability

Addresses: Severity: Medium / Token Spend: High / Time: Medium / Context: Medium


Description

Every command MUST declare a complete input schema (all parameters: name, type, required, default, enum values if applicable, description) and output schema (JSON Schema for the data field of the response envelope). The framework MUST auto-generate --schema output from these declarations. Command authors MUST NOT write --schema output manually; it MUST be derived from the declaration. A passthrough command (REQ-C-031) is exempt from the input schema: its arguments belong to the delegated tool, so it declares no flags or positionals, and --schema shows arguments: "passthrough" instead.

Structured flag values. A flag whose value is a JSON object SHOULD declare type: "object" and the value's JSON Schema in FlagEntry.schema rather than type: "string" with the shape described in prose, because an agent cannot see the shape it must build otherwise. A flag declared type: "object" MUST carry schema. The caller passes the value as one argv token of JSON text, as --raw-payload takes its payload (REQ-O-032): --filter '{"status":"open"}'. An array flag whose items are objects puts the item schema in schema, and each item it receives is one such token. For a flag declared type: "object" (or an array flag with schema), the framework MUST parse and validate the value against schema during Phase 1 (REQ-F-015) and exit 2 (ARG_ERROR) when the text is not valid JSON or does not match, before any side effect. schema appears only on object and array flags.

Fixed value sets. A flag or positional that accepts only a fixed set of values lists them in enum_values: every value a caller may pass, and nothing else. On a type: "enum" entry they are strings. An entry whose values are a few integers declares type: "integer" and lists them as integers (enum_values: [0, 1, 2]), so a JSON route (a payload, an MCP call) takes the numbers and shell completion offers the same list; it never declares type: "enum" with numeric strings, nor moves the values into description. enum_values appears only on enum and integer entries, and the framework MUST reject during Phase 1 a value outside the list with exit 2 (ARG_ERROR).

Acceptance Criteria

  • tool <cmd> --schema returns valid JSON containing parameters and output_schema
  • tool --schema returns a manifest of all commands with their parameter and output schemas
  • Adding a parameter to a command automatically appears in --schema without separate documentation effort
  • Positional arguments appear in the command's positionals array in call order, never only in its description
  • The output_schema is a valid JSON Schema object
  • A flag declared type: "object" carries a schema; --filter '{"status":"open"}' passes when the text matches that schema
  • On a flag declared type: "object", --filter 'status=open' (not JSON) and --filter '{"status":3}' (does not match schema) both exit 2 with an ARG_ERROR naming the flag, before any side effect
  • A flag declared type: "integer" with enum_values: [0, 1, 2] shows the list as integers in --schema; --sig-type 1 passes, and --sig-type 3 exits 2 with an ARG_ERROR naming the flag, before any side effect

Schema

Types: manifest-response.md · response-envelope.md

The --schema output for a command is a CommandEntry (from ManifestResponse.commands) extended with an output_schema field:

Field Type Description
parameters Record<string, FlagEntry> Identical to CommandEntry.flags — one entry per declared option
positionals PositionalEntry[] Identical to CommandEntry.positionals — positional arguments in call order
output_schema JSON Schema object Describes the shape of ResponseEnvelope.data on success

Wire Format

$ tool deploy --schema
{
  "parameters": {
    "target":  { "type": "enum",    "required": true,  "enum_values": ["prod", "staging", "dev"], "description": "Target environment" },
    "dry-run": { "type": "boolean", "required": false, "default": false, "description": "Validate without executing" },
    "timeout": { "type": "integer", "required": false, "default": 300,   "description": "Seconds before abort" },
    "sig-type": { "type": "integer", "required": false, "default": 0, "enum_values": [0, 1, 2], "description": "Signature scheme: 0 EOA, 1 proxy, 2 safe" },
    "labels":  { "type": "object",  "required": false, "description": "Labels to attach as JSON text",
                 "schema": { "type": "object", "additionalProperties": { "type": "string" } } }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "deployment_id": { "type": "string" },
      "status":        { "type": "string", "enum": ["pending", "running", "complete", "failed"] },
      "started_at":    { "type": "string", "format": "date-time" }
    },
    "required": ["deployment_id", "status"]
  },
  "exit_codes": {
    "0":  { "name": "SUCCESS",   "description": "Deployment completed",       "retryable": false, "side_effects": "complete" },
    "2":  { "name": "ARG_ERROR", "description": "Invalid target environment", "retryable": false, "side_effects": "none"     },
    "10": { "name": "TIMEOUT",   "description": "Deployment timed out",       "retryable": false, "side_effects": "partial"  }
  }
}

Example

Command authors declare input parameters and output shape at registration time. The framework derives --schema from these declarations automatically:

register command "deploy":
  parameters:
    target:  type=enum(prod, staging, dev), required=true,  description="Target environment"
    dry-run: type=boolean, required=false, default=false,   description="Validate without executing"
    timeout: type=integer, required=false, default=300,     description="Seconds before abort"
    sig-type: type=integer(0, 1, 2), required=false, default=0, description="Signature scheme"
    labels:  type=object,  required=false, schema={type: object, additionalProperties: {type: string}},
             description="Labels to attach as JSON text"
  output_schema:
    type: object
    required: [deployment_id, status]
    properties:
      deployment_id: { type: string }
      status:        { type: string, enum: [pending, running, complete, failed] }
      started_at:    { type: string, format: date-time }

# tool deploy --schema  →  derived automatically; no manual schema writing

Requirement Tier Relationship
REQ-C-001 C Composes: exit_codes appears alongside parameters and output_schema in --schema output
REQ-O-041 O Aggregates: manifest collects parameters and output_schema declarations from all commands
REQ-F-015 F Enforces: declared parameters drive Phase 1 validation before execution
REQ-C-026 C Extends: conditional requires graph is part of the --schema output
REQ-O-032 O Composes: an object flag takes JSON text on argv the same way --raw-payload does
REQ-C-031 C Specializes: a passthrough command declares no input schema