{
  "contractVersion": "1.3.0",
  "supportedMajors": [1],
  "description": "The pipeline's client contract: every surface a client reads or writes, the CLI invocation and the HTTP route that carry it, and the schema its JSON follows. Schemas are not copied here; schemasDir points at the one tree the producers validate against. Paths in cli are relative to the pipeline directory (installed: ~/.claude).",
  "schemasDir": "../schemas",
  "types": "types/index.d.ts",
  "errorSchema": "contract-error.schema.json",
  "server": {
    "cli": ["node", "scripts/contract-server.mjs"],
    "bind": "127.0.0.1",
    "auth": "Authorization: Bearer <token>, token in ~/.claude/logs/multi-agent/contract-server/<pid>.json (0600). Not accepted under phone.pathPrefix, which takes a device signature instead.",
    "query": {
      "redact": "1 drops every x-sensitivity local field, as --redact does; forced for a request that carries an Origin",
      "v": "the major to serve; see README.md"
    },
    "confirm": "POST /v1/runs/{id}/kill and POST /v1/gc take two requests: the first returns a preview, confirmToken and expiresAt; the second sends confirmToken back. Tokens are 32 random bytes held in the server's memory, single use, valid 120 seconds, bound to the route and run id."
  },
  "phone": {
    "description": "Routes a phone reaches through a forwarder (a private network or a local relay; see multi-agent-refs/features/phone-api.md). Every request is signed with an enrolled device's Ed25519 key; the bearer token is neither needed nor accepted here, and a signature opens no desktop route. Responses are always the redacted view.",
    "pathPrefix": "/v1/phone/",
    "envelopeSchema": "phone-signed-request.schema.json",
    "registrySchema": "phone-devices.schema.json",
    "enrol": [
      "node",
      "scripts/phone-devices.mjs",
      "add",
      "--name",
      "<name>",
      "--public-key",
      "<base64url>",
      "--scopes",
      "<read,answer,launch>"
    ],
    "signature": {
      "version": "MA-PHONE-SIG-1",
      "algorithm": "Ed25519",
      "headers": {
        "device": "X-MA-Device",
        "timestamp": "X-MA-Timestamp",
        "nonce": "X-MA-Nonce",
        "signature": "X-MA-Signature"
      },
      "payload": ["version", "method", "target", "bodySha256", "deviceId", "timestamp", "nonce"],
      "join": "\n",
      "windowSeconds": 60,
      "futureSkewSeconds": 5
    },
    "scopes": ["read", "answer", "launch"],
    "launch": "POST /v1/phone/launch is refused with launch-disabled until `phone-devices.mjs launch on`, whatever scopes the device holds. Its input is a structured reference only (a Jira key, a GitHub issue URL, repo#N, #N, or a Jira URL on the configured host), and its repo must be a configured or registered one (repo-not-allowed otherwise)",
    "notOffered": ["pause", "resume", "stop", "steer", "shell", "prompt", "run", "merge", "ready"]
  },
  "schemas": [
    "runs-index.schema.json",
    "command-parameters.schema.json",
    "run-questions.schema.json",
    "issues.schema.json",
    "worktrees.schema.json",
    "launch.schema.json",
    "launch-request.schema.json",
    "launch-plan.schema.json",
    "answer-request.schema.json",
    "answer-result.schema.json",
    "phone-signed-request.schema.json",
    "phone-devices.schema.json",
    "run-log.schema.json",
    "resume-request.schema.json",
    "kill-request.schema.json",
    "kill.schema.json",
    "gc-request.schema.json",
    "gc.schema.json",
    "autopilot-status.schema.json",
    "autopilot-off-request.schema.json",
    "autopilot-off.schema.json",
    "repos.schema.json",
    "contract-error.schema.json"
  ],
  "surfaces": [
    {
      "id": "runs",
      "method": "GET",
      "path": "/v1/runs",
      "query": ["group", "redact"],
      "cli": ["node", "scripts/runs-index.mjs", "--json", "[--group <group>]", "[--redact]"],
      "schema": "runs-index.schema.json",
      "version": "1.2.0"
    },
    {
      "id": "run",
      "method": "GET",
      "path": "/v1/runs/{id}",
      "query": ["redact"],
      "cli": ["node", "scripts/runs-index.mjs", "--json", "--task-id", "{id}", "[--redact]"],
      "schema": "runs-index.schema.json",
      "version": "1.2.0"
    },
    {
      "id": "commands",
      "method": "GET",
      "path": "/v1/commands",
      "query": ["redact"],
      "cli": ["node", "scripts/commands.mjs", "--json"],
      "schema": "command-parameters.schema.json",
      "version": "1.0.0"
    },
    {
      "id": "questions",
      "method": "GET",
      "path": "/v1/questions",
      "query": ["redact"],
      "cli": ["node", "scripts/launch-request.mjs", "questions"],
      "schema": "run-questions.schema.json",
      "version": "1.1.0"
    },
    {
      "id": "issues",
      "method": "GET",
      "path": "/v1/issues",
      "query": ["source", "project", "max", "redact"],
      "cli": [
        "node",
        "scripts/issues.mjs",
        "--json",
        "[--source jira|github|all]",
        "[--project <KEY>]...",
        "[--max <n>]"
      ],
      "schema": "issues.schema.json",
      "version": "1.0.0"
    },
    {
      "id": "worktrees",
      "method": "GET",
      "path": "/v1/worktrees",
      "query": ["measure", "redact"],
      "cli": ["node", "scripts/worktrees.mjs", "--json", "[--measure]", "[--redact]"],
      "schema": "worktrees.schema.json",
      "version": "1.0.0"
    },
    {
      "id": "launch-spec",
      "method": "GET",
      "path": "/v1/launch-spec",
      "query": ["redact"],
      "cli": ["node", "scripts/launch-request.mjs", "spec"],
      "schema": "launch.schema.json",
      "version": "1.2.0"
    },
    {
      "id": "repos",
      "method": "GET",
      "path": "/v1/repos",
      "query": ["redact"],
      "cli": ["node", "scripts/launch-request.mjs", "repos", "[--redact]"],
      "schema": "repos.schema.json",
      "version": "1.1.0",
      "note": "The repositories POST /v1/launch?repo= accepts: configured autopilot repos and registered ones."
    },
    {
      "id": "launch",
      "method": "POST",
      "path": "/v1/launch",
      "query": ["repo", "redact"],
      "body": "launch-request.schema.json",
      "cli": [
        "node",
        "scripts/launch-request.mjs",
        "plan",
        "<request-file>",
        "--repo",
        "<repo>",
        "--session-id",
        "<uuid>",
        "[--redact]"
      ],
      "schema": "launch-plan.schema.json",
      "version": "1.1.0",
      "note": "The server writes the request file (0600, named for a session id it mints) and returns the plan; the CLI plans a file the client wrote. Neither starts the host."
    },
    {
      "id": "answer",
      "method": "POST",
      "path": "/v1/runs/{id}/answer",
      "query": [],
      "body": "answer-request.schema.json",
      "cli": [
        "node",
        "scripts/answer-question.mjs",
        "<agent-state.json>",
        "--question",
        "<questionId>",
        "--answer",
        "<optionId>[,<optionId>...]"
      ],
      "schema": "answer-result.schema.json",
      "version": "1.0.0"
    },
    {
      "id": "run-log",
      "method": "GET",
      "path": "/v1/runs/{id}/log",
      "query": ["tail", "redact"],
      "cli": ["node", "scripts/run-log.mjs", "{id}", "[--tail <n>]", "--json"],
      "schema": "run-log.schema.json",
      "version": "1.0.0",
      "note": "tail is 1 to 2000, 200 by default. Lines are scrubbed under redact."
    },
    {
      "id": "resume",
      "method": "POST",
      "path": "/v1/runs/{id}/resume",
      "query": ["redact"],
      "body": "resume-request.schema.json",
      "cli": [
        "node",
        "scripts/launch-request.mjs",
        "resume",
        "--task-id",
        "{id}",
        "--repo",
        "<repo>",
        "--session-id",
        "<uuid>",
        "[--autopilot]",
        "[--redact]"
      ],
      "schema": "launch-plan.schema.json",
      "version": "1.1.0",
      "note": "Returns the plan (mode resume) the client spawns; nothing starts the host. Only a run recorded under the unattended log root resumes here (409 not-resumable otherwise, and for a finished or killed run); a run whose session is alive is 409 run-active. answers answers the question the run is parked on before the plan is returned."
    },
    {
      "id": "kill",
      "method": "POST",
      "path": "/v1/runs/{id}/kill",
      "query": ["redact"],
      "body": "kill-request.schema.json",
      "cli": ["node", "scripts/run-kill.mjs", "preview|apply", "{id}", "--json"],
      "schema": "kill.schema.json",
      "version": "1.0.0",
      "note": "Two steps: without confirmToken the preview and a token (single use, 120 s, bound to this route and run); with it the kill. 409 confirm-expired, 409 confirm-mismatch. Never deletes the remote branch or the logs."
    },
    {
      "id": "gc",
      "method": "POST",
      "path": "/v1/gc",
      "query": ["redact"],
      "body": "gc-request.schema.json",
      "cli": [
        "node",
        "scripts/gc-plan.mjs",
        "preview|apply",
        "[--scope tmp,worktrees,refs,abandoned]",
        "--json"
      ],
      "schema": "gc.schema.json",
      "version": "1.0.0",
      "note": "Two steps, the same token flow as kill. Step 2 removes exactly the items step 1 listed."
    },
    {
      "id": "autopilot",
      "method": "GET",
      "path": "/v1/autopilot",
      "query": ["redact"],
      "cli": ["node", "scripts/autopilot-control.mjs", "status"],
      "schema": "autopilot-status.schema.json",
      "version": "1.0.0"
    },
    {
      "id": "autopilot-off",
      "method": "POST",
      "path": "/v1/autopilot/off",
      "query": [],
      "body": "autopilot-off-request.schema.json",
      "cli": ["node", "scripts/autopilot-control.mjs", "off", "[--now]"],
      "schema": "autopilot-off.schema.json",
      "version": "1.0.0",
      "note": "Turning autopilot on stays a terminal command: it picks repositories with the user."
    },
    {
      "id": "phone-runs",
      "method": "GET",
      "path": "/v1/phone/runs",
      "query": ["group", "redact"],
      "auth": "device-signature",
      "scope": "read",
      "cli": ["node", "scripts/runs-index.mjs", "--json", "[--group <group>]", "--redact"],
      "schema": "runs-index.schema.json",
      "version": "1.2.0",
      "note": "Always redacted; redact=0 is refused."
    },
    {
      "id": "phone-run",
      "method": "GET",
      "path": "/v1/phone/runs/{id}",
      "query": ["redact"],
      "auth": "device-signature",
      "scope": "read",
      "cli": ["node", "scripts/runs-index.mjs", "--json", "--task-id", "{id}", "--redact"],
      "schema": "runs-index.schema.json",
      "version": "1.2.0",
      "note": "Always redacted; redact=0 is refused."
    },
    {
      "id": "phone-answer",
      "method": "POST",
      "path": "/v1/phone/runs/{id}/answer",
      "query": [],
      "auth": "device-signature",
      "scope": "answer",
      "body": "answer-request.schema.json",
      "cli": [
        "node",
        "scripts/answer-question.mjs",
        "<agent-state.json>",
        "--question",
        "<questionId>",
        "--answer",
        "<optionId>[,<optionId>...]"
      ],
      "schema": "answer-result.schema.json",
      "version": "1.0.0",
      "note": "The same refusals as POST /v1/runs/{id}/answer: only an option id the pending question offered is recorded."
    },
    {
      "id": "phone-launch",
      "method": "POST",
      "path": "/v1/phone/launch",
      "query": ["repo", "redact"],
      "auth": "device-signature",
      "scope": "launch",
      "body": "launch-request.schema.json",
      "cli": [
        "node",
        "scripts/launch-request.mjs",
        "plan",
        "<request-file>",
        "--repo",
        "<repo>",
        "--session-id",
        "<uuid>",
        "--channel",
        "phone",
        "--redact"
      ],
      "schema": "launch-plan.schema.json",
      "version": "1.1.0",
      "note": "Off until `phone-devices.mjs launch on`. Writes the request file and returns the redacted plan; nothing starts the host."
    }
  ],
  "fixtures": {
    "runs-running.json": "runs-index.schema.json",
    "runs-awaiting-question.json": "runs-index.schema.json",
    "runs-failed.json": "runs-index.schema.json",
    "runs-pr-opened.json": "runs-index.schema.json",
    "runs-pr-opened-redacted.json": "runs-index.schema.json",
    "runs-empty.json": "runs-index.schema.json",
    "runs-old-schema.json": "runs-index.schema.json",
    "worktrees-empty.json": "worktrees.schema.json",
    "issues-empty.json": "issues.schema.json",
    "launch-plan.json": "launch-plan.schema.json",
    "answer-result.json": "answer-result.schema.json",
    "error-unauthorized.json": "contract-error.schema.json",
    "error-invalid-request.json": "contract-error.schema.json",
    "error-unsigned.json": "contract-error.schema.json"
  }
}
