---
title: "Open a plan; stream SSE of the orchestration."
method: POST
path: "/plan"
tags: ["Plan"]
---

# Open a plan; stream SSE of the orchestration.

`POST /plan`

Accepts a user question + agent catalog, starts (or resumes) a
session, and streams Server-Sent Events as the planner runs.

### Session continuation (stateless model — Path A)

The gateway no longer owns durability. Each `/plan` call is its
own ephemeral session: pass prior turns in `history` and the
latest compaction summary (if you have one) in `prior_summary`.
The client is the canonical record; the gateway is pure compute.

`session_id` is now just a correlation tag echoed back on the
first SSE frame — it doesn't index a server-side store. The
durable conversation lives in your application (in the Bindu
reference frontend, that's comms's SQLite events log).

### Compaction-summary sidechannel

When the planner compacts overflowing history, it emits an
`event: compaction-summary` SSE frame mid-stream. Clients
should persist the `summary` field locally and ship it back as
`prior_summary` on the next call so the planner keeps the
compacted context across requests.

### Catalog immutability per request

The `agents` catalog applies to a single `/plan` call. Each
request is independent — there's no first-plan / subsequent-
plan distinction in stateless mode.

### Streaming & abort

Closing the HTTP connection aborts the plan — in-flight A2A calls
receive an `AbortSignal` and the planner loop terminates. Clients
that want a partial result should buffer `text.delta` frames
client-side rather than relying on `final`.

## Request body

- PlanRequest
  - `question` string, required — The user's natural-language question. Non-empty — an empty string is rejected upstream because some LLM providers (Anthropic) reject empty user messages with a 400 mid-stream, surfacing as a vague "Provider returned error". Validating here gives a clean 400 with `invalid_request` instead.
  - `agents` AgentRequest[] — Catalog of A2A peers the planner may call. Empty array = planner runs with no tools (useful for questions the configured planner LLM can answer on its own, e.g., general knowledge).
    - `name` string, required — Display name of the peer. Used to derive the tool id exposed to the planner LLM (`call_<name>_<skillId>`) and to correlate SSE events back to the catalog entry. Operator-chosen and potentially collision-prone — use `trust.pinnedDID` for a cryptographically stable identifier.
    - `endpoint` string, uri, required — Absolute HTTP(S) URL where the peer's A2A endpoint is reachable. The gateway POSTs JSON-RPC envelopes here for `message/send` and `tasks/get`.
    - `auth` union — How the gateway authenticates its outbound calls to this peer. Discriminated on `type`: - `none` — anonymous; peer must accept unauthenticated calls. - `bearer` — static token passed literally in `Authorization`. Caller includes the secret in the request, so only use over TLS. - `bearer_env` — gateway reads the token from the named env var. Keeps secrets out of the wire; rotation = restart. - `did_signed` — gateway signs the request body with its configured Ed25519 identity and attaches an OAuth2 token. By default uses the gateway's own auto-acquired Hydra token; pass `tokenEnvVar` to use a per-peer federated token.
      - PeerAuthNone
        - `type` 'none', required
      - PeerAuthBearer
        - `type` 'bearer', required
        - `token` string, required — Literal bearer token to include in `Authorization: Bearer <token>`.
      - PeerAuthBearerEnv
        - `type` 'bearer_env', required
        - `envVar` string, required — Name of the env var on the gateway process whose value is the bearer token.
      - PeerAuthDidSigned
        - `type` 'did_signed', required
        - `tokenEnvVar` string — Optional. Env var name for a pre-acquired OAuth2 token to pair with the DID signature. Omit to use the gateway's own Hydra auto-acquired token (requires `BINDU_GATEWAY_HYDRA_*` env).
    - `trust` PeerTrust — Per-peer trust policy. Both fields are optional; omitting both means "trust the peer's identity at face value — don't verify."
      - `verifyDID` boolean — When true, the gateway verifies every Ed25519 signature on artifacts returned by this peer. Mismatched signatures fail the task. Requires a resolvable DID on the peer.
      - `pinnedDID` string — DID the peer is expected to present. Used both for correlation (SSE `agent_did`) and, when `verifyDID` is true, to reject responses signed by a different key.
    - `skills` SkillRequest[] — Peer capabilities the planner may invoke. Each becomes one dynamic tool scoped to this request. The gateway does NOT discover skills from the peer's `AgentCard` — the caller declares them, ensuring the planner sees only capabilities the caller vouches for.
      - `id` string, required — The skill id the A2A peer recognizes. Passed back to the peer inside `message/send` so it can route to the right internal handler.
      - `description` string — Human-readable description. The planner LLM relies heavily on this to decide whether to invoke the skill — write 3–4 sentences covering intent, inputs, outputs, and when to use it. Descriptions under 120 chars are auto-padded server-side with agent/skill context so the LLM still gets enough signal.
      - `inputSchema` object — Optional JSON Schema for structured inputs. When present, the planner LLM emits a JSON object matching this shape and the gateway forwards it as the message text (serialized). When omitted, the planner sends a plain-text `input` string.
      - `outputModes` string[] — Advisory list of output MIME-like hints the peer may return (e.g., `text/plain`, `application/json`). Surfaced in the tool description so the planner knows what to expect back.
      - `tags` string[] — Free-form tags — helps the planner disambiguate when multiple peers expose similarly-named skills.
  - `preferences` PlanPreferences — Caps and shaping hints. All keys are **snake_case**; an earlier draft declared them camelCase, which caused docs-compliant clients to silently lose the caps — the schema is now strict on casing and unknown keys pass through via `additionalProperties: true` for forward compatibility.
    - `response_format` string — Advisory hint for the planner's final-message format (`"markdown"`, `"plain"`, `"json"`, etc.). Not enforced by the gateway; the planner may honor or ignore it.
    - `max_hops` integer — Maximum number of A2A hops (recursive peer-to-peer calls) the gateway allows. Phase 2+ enforced; currently informational.
    - `timeout_ms` integer — Overall wall-clock budget for the `/plan` call, in milliseconds. Applies to the entire planner loop including LLM calls, compaction, and every downstream peer call combined. When the budget expires, in-flight peer polls are aborted and a best-effort `tasks/cancel` is dispatched to each peer; the gateway then returns `BinduError(-32040, AbortedByCaller)` with `data.reason = "deadline"`. Default when unset: **1,800,000** ms (30 minutes). Minimum: 1,000 ms. Maximum: 21,600,000 ms (6 hours). Requests above the ceiling are rejected at the API boundary as `invalid_request` — callers with genuine multi-hour workloads set it explicitly.
    - `max_steps` integer — Maximum agentic loop steps. Overrides the planner agent's default (`agent.steps`). A "step" is one LLM call — tool calls inside a step don't count.
  - `session_id` string — Opaque correlation tag the caller chooses. Echoed back on the first SSE `session` frame as `external_session_id`. In the stateless gateway this is NOT a resumption key — the gateway has no persistent session store. Pass prior turns explicitly via `history` (and optionally a `prior_summary`) to give the planner context across calls.
  - `history` HistoryTurn[] — Prior conversation for this session. The client (canonical record) sends the most recent turns on every call so the planner has context. Older turns that didn't fit into the cap are preserved as the `prior_summary` field instead. Order: oldest → newest. Omit (or send `[]`) for a brand- new session. Reference frontend (comms) caps this at 30 turns; bigger payloads work but waste tokens.
    - `role` 'user' | 'assistant', required
    - `parts` object[], required
      - `type` 'text', required
      - `text` string, required
  - `prior_summary` string — Compaction summary the gateway emitted on a prior call, persisted by the client. The planner sees it as a synthetic user turn at the head of history: "[Prior session context, compacted]\n\n...". Omit on first call.

## Response `200`

SSE stream of the plan. Each event is one of the types
documented under `SSEEvent` below. The stream closes after
`event: done`.

## Other responses

- `400` — Malformed JSON, missing required fields, schema validation failure, or a catalog that would produce colliding tool ids (two entries whose `<agent>_<skill>` combination normalizes to the same value — silently swallowed before this guard, which let one peer mask another).
- `401` — Missing or invalid bearer token.
- `500` — Internal error before the SSE stream opens — e.g. planner misconfiguration or an exception during request handling. Once streaming starts, errors surface as `event: error` on the stream instead. The `session_failed` shape from the stateful era is gone; the stateless gateway has no session-creation step that can fail at the DB layer.

## Changes

- **2026-05-16** `3c89680038fb` — 3 warning, 5 info
  - added the new `internal_error` enum value to the `error` response property for the response status `400`
  - added the new `internal_error` enum value to the `error` response property for the response status `401`
  - added the new `internal_error` enum value to the `error` response property for the response status `500`
  - added the new optional request property `history`
  - …4 more
- **2026-04-23** `b4f015868674` — 1 breaking, 1 warning
  - the `preferences/timeout_ms` request property's min was increased to `1000.00`
  - the `preferences/timeout_ms` request property's max was set to `21600000.00`

[Change history](https://skmtc.dev/getbindu/apis/bindu-gateway-api/changes/plan/post.md)

---

[API](https://skmtc.dev/getbindu/apis/bindu-gateway-api.md) · [All operations](https://skmtc.dev/getbindu/apis/bindu-gateway-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/getbindu/bindu-gateway-api/revisions/3c89680038fb/schema)
