* refactor(managed-agent): Consolidate server and broker sources Move the combined Java application beside sdk-java and remove the standalone Runtime Broker artifact. Update build, development, packaging and CI references while preserving Java package identities, runtime protocols and schema resources. Co-authored-by: Qwen-Coder <[email protected]> * docs(managed-agent): Preserve historical evidence and verification guidance Restore commit-pinned H3 probe inputs and align the Java verification environment contract with the consolidated server layout. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): Run hosted Broker tests under Bash Keep Maven property arguments intact on Windows and stop before Broker tests when the SDK install fails. Exercise both successful and failed SDK installation in the workflow regression tests. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): Quote the hosted Broker shell value Follow the repository yamllint single-quote rule without changing the parsed workflow configuration. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]>
17 KiB
Managed Agent API Contract (Stage D1)
Status: D1 implemented; D2 implemented in Session query; D3 implemented in Event replay; the lifecycle work implemented as D4 of #12867 in Durable lifecycle; D5 implemented in Turn queries; D6 designed in Actions, with its Hosted Harness part (D6a) implemented Date: 2026-09-27 Issue: #12793, part of #12380
1. Problem
The Managed Agent server exposes public Session routes under
/v1/agents/sessions and a WebShell adapter under /api/agent/web-shell/v1.
Their reviewed contract, the public API contract and
OpenAPI v1.12, lived only in the design repository. In this
repository, ApiModels.java and the interfaces in
packages/web-shell/client/components/managed/java-managed-agent-client.ts
were written by hand, and nothing compared them, or the mapped Spring routes,
with the schema. Each new field (W0b's workspace, the planned Artifacts and
Actions) had to be added twice with no check.
Sections 3 to 5, 7 and 8 of the contract define request correlation, paging,
replay, versions and capabilities, the known implementation gaps, and the rule
that a route moves from partial to implemented only after contract tests
pass. Stage D closes those gaps in three slices; D1 provides the contract and
the checks that D2 and D3 build on.
2. Goals
- Keep the reviewed OpenAPI in this repository as the single source for the Java server, the WebShell TypeScript client and the contract tests.
- Generate the WebShell TypeScript types from it and fail CI when the committed types are stale.
- Fail CI when a Java record, a mapped Spring route, or a real response of a
partialroute drifts from the spec. - Record the current differences as explicit expected failures, so that D2 and D3 remove them one by one and a fixed gap cannot stay listed.
3. Non-goals
- No route becomes
implemented. Shipped lifecycle routes (4.3) and W0d discovery and binding (4.5) are recorded aspartial. - No server behavior changes. D1 only records what D2 and D3 must fix.
- No schema field is removed to make the current server pass.
- Durable admission, Turns, AgentDefinition, Artifacts, workspace execution and context switching, Actions and event retention stay out of scope, as in the issue.
4. Decisions
4.1 Spec location and format
The spec moves into
packages/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json
and becomes the single source; the design repository points here from now on.
It is stored as JSON rather than YAML. The repository's yamllint rules require
single-quoted scalars and block style, and the upstream YAML fails them in 2264
places. JSON follows the precedent of
docs/developers/daemon-rest-api.openapi.json, needs no YAML parser on either
side, and is formatted by Prettier like the rest of the repository. The
conversion keeps key order and is lossless apart from the changes in 4.3
and 4.5.
D1 introduced 1.13.0 for the lifecycle routes (4.3). Integrating W0d adds
the workspace lookup route and advances the version to 1.14.0 (4.5).
The version is allocated by hand and consumers key capability detection on
it, so monotonicity is enforced in CI (#13804). On every pull request the
SDK Java workflow's fast guard lane compares the merge result's document
with the base branch's head copy and fails when a changed document declares
an equal or lower info.version, numeric per segment, naming whether the
number stalled or regressed; a pull request arriving at the base ref's own
bytes owes no bump. After every push to main that changes the document,
and every 30 minutes, a re-check reads each open pull request's changed
paths and document through the API — no pull request code is fetched,
checked out or run — and sets the
API contract version uniqueness (latest main) status: failure when the
declared version no longer advances past main's, success otherwise,
naming the other open pull requests claiming the same version, and error
when a file list or head document could not be read in full. A head that
already carries the status is re-evaluated even after it stops changing the
document, so a stale failure is cleared.
4.2 Generate TypeScript, validate Java
- TypeScript is generated.
openapi-typescriptgenerates the WebShell types. The client's exported names (JavaAgentSession,JavaAgentEvent, …) stay, now as aliases of the generated types.JavaAgentEnvironmentstays handwritten, because the contract leavesenvironmentan open object. - Java records are validated, not generated. They carry Bean Validation and Jackson annotations that a generator would have to reproduce. A contract test compares them with the schemas instead (5.2).
4.3 Shipped routes that v1.12 does not match
Main already serves rename (PATCH /v1/agents/sessions/{sessionId}),
unarchive, archive and DELETE. Section 8 forbids a mapped planned route,
so v1.13 records all four as partial:
archiveandDELETEkeep their reviewed v1.10 targets (202withPublicCommandOperation). The current200responses are known gaps for the lifecycle work.updateSession(rename) andunarchiveSessionhave no reviewed target. They are added with the shape main ships:200withPublicSessionandX-Qwen-Idempotent-Replay.UpdateSessionRequestrequires atitleof 1 to 256 characters. The lifecycle work decides whether they become durable operations likearchive.
4.4 agent_revision before AgentDefinition
PublicSession.agent_revision is required. D2 returns a fixed revision taken
from the server's agent configuration, and D1 only records the missing field.
D8a (v1.29) implements the /v1/agents routes as stored, immutable revisions;
Sessions keep the configured revision until D8b pins a stored one. See
AgentDefinition revisions.
4.5 W0d discovery and empty-session binding
The W0d integration marks public workspace list/get, WebShell workspace query/get,
and Session workspace selection and readback as partial. It adds the previously
missing WebShell workspaces/get operation and regenerates the client types.
Workspace queries have their own request schema: limit defaults to 50 and is
bounded at 100; cursors are bounded at 2048 characters.
Discovery's required workspace_binding / workspaceBinding capability advertises
only discovery, empty-session creation and binding readback. It does not enable
workspace execution or context switching. The existing workspace_context /
workspaceContext capability remains false.
Discovery now returns the target public workspace id and object and lowercase
WebShell states. Bound Sessions still return only workspace identity and cwd, so
traffic coverage records the missing context revision and state for W2. The
local client override remains only for that Session binding.
5. Java contract test
ManagedAgentApiContractTest runs in the ordinary mvn test lane with the
fake Harness of ManagedAgentServerIntegrationTest and its own in-memory H2
database. The two classes load separate Spring contexts, and each context's
dispatcher polls its database, so a shared database would let one test's
dispatcher claim the other's Turns. It uses the test-scoped
com.networknt:json-schema-validator (1.5.9, Jackson 2 like Spring Boot 3.5)
with JSON Schema 2020-12 and format assertions enabled. Schemas are addressed
by JSON Pointer into the spec, so $ref resolves inside the one file.
5.1 Routes
The test reads every route under /v1/agent and /api/agent/web-shell/v1
from Spring's RequestMappingHandlerMapping and compares them with the spec.
The first prefix covers /v1/agents and the /v1/agent-* resources that the
H0a task contract names; D1 read
only /v1/agents. A mapped route that is absent from the spec or marked
planned fails; so does a partial or implemented route that is not mapped.
There are no route gaps after 4.3 and 4.5.
5.2 Records
Every record in ApiModels must map to a schema, or the test reports it. For
each pair, Jackson's own property introspection supplies the JSON names:
- a schema property that is not
plannedand is absent from the record is reported asmissing; - a record property that the schema does not define is reported as
extra.
oneOf and allOf members are merged, so SessionEventRequest is compared
with both the input and the cancellation event.
5.3 Real traffic
One scenario drives every non-planned operation through MockMvc against the
fake Harness: a completed Turn, an input and a cancellation, rename, archive,
a get and a list of the archived Session, unarchive and delete on the public
API; create with and without a first message, query, get, transcript, submit,
cancel and the SSE stream on the WebShell adapter; and cross-tenant 404 and
idempotency 409 errors. Workspace traffic covers all four discovery routes,
a default outside the current page, and bound empty-session creation and readback
on both surfaces, including normalized cwd. For each exchange it checks that:
- the request body the test sends is valid against the request schema;
- the status is the one the spec expects for that call;
- the body is valid against the declared schema for the actual status;
- every header that the response declares is present;
- each SSE frame's
idis the event'ssequence, itseventis the type, and itsdatais valid againstPublicEventorWebShellEvent.
The test also fails if the scenario skips an operation that the spec does not
mark planned, so a route that becomes partial must be exercised.
5.4 Known gaps
A failure is normalized to a stable line: array indices become *, and a line
names the operation, status, location, keyword and property. The three tests
compare their lines with
src/test/resources/openapi/contract-known-gaps.txt. Any line not in the file
fails as new drift, and any listed line that no longer occurs fails as a
resolved gap that must be deleted. The file groups the lines by the slice that
closes them.
6. TypeScript types
packages/web-shell/scripts/generate-managed-agent-api.mjs writes
client/components/managed/generated/managed-agent-api.ts. Before running
openapi-typescript it:
- keeps only
WebShelloperations that are notplanned; - removes
plannedparameters and properties, and drops them fromrequired; - keeps only the components those operations reach.
The contract says planned surface must not reach a production client, so
fields such as WebShellSession.capabilities stay out until they are
implemented. Properties with a default stay optional
(defaultNonNullable: false); only required makes a field required.
managed-agent-api.test.ts regenerates the file in memory and fails when the
committed copy differs, and covers the filter on a small fixture.
The generated types also correct the handwritten interfaces. Three of the
corrections needed code changes: turnId can be absent on an event, and
nextCursor and olderCursor can be null. The provider now handles all
three. The stream request no longer sends limit, which v1.6 removed and the
server ignores. Input blocks keep
type: 'text' through a local override that names the known gap, because the
server still rejects the contract's input_text.
openapi-typescript depends on supports-color 10. With a second, higher
supports-color in the graph, pnpm rewrote the optional supports-color peer
of every debug entry in the lockfile (about 1100 changed lines) without
changing the hoisted tree. A pnpm override pins
openapi-typescript>supports-color to the 7.2.0 the repository already uses;
the generator only reads stdout and stdout.hasBasic to decide whether to
colour its logs, and 7.2.0 exports the same stdout (false without a TTY,
which turns colours off).
7. Gaps recorded by D1
| Slice | Gaps |
|---|---|
| D2 | agent_revision and capabilities missing from every Session response; replay_floor_sequence, snapshot_through_sequence and PublicTurn.input_item_id missing from the records; agent_revision missing from CreateSessionRequest; no X-Request-Id on WebShell commands; WebShellStreamRequest.limit still in the record; input blocks accept only text, not the contract's input_text. |
| D3 | schema_version, projection_version, item_id and content_part_id missing from public and WebShell events, in JSON and SSE; event and transcript pages reject limit above 100 with 400 invalid_limit. |
| Lifecycle work | archive and DELETE return 200 instead of 202 with a command operation; DeletedSession has no schema; an archived Session reads back with status: "archived", which the contract's status enum lacks (v1.10 models archiving with the planned archived_at). |
| Workspace context (W2) | The Session workspace lacks context_revision and state on both surfaces, checked through records and bound-session responses. |
The input type mismatch was not in the issue's list; the request validation in
5.3 found it. The server's input() rejects anything but text, and the
WebShell client sends text, so both must change together in D2.
8. Changing the contract
- Edit the JSON spec.
- Run
npm run generate:managed-agent-apiinpackages/web-shelland fix the client if the types changed. - Run
ManagedAgentApiContractTest. Fix new drift in the server, or, only for a gap a later slice owns, add its line to the known-gaps file. When a fix closes a gap, delete its line. - Move a route to
implementedonly when it has no remaining gaps and its contract tests pass.
9. Validation
mvn testinpackages/managed-agent-serverruns the three contract tests with the recorded gaps.- Mutations each fail the matching test: a new field on a record, a new mapped route, a removed gap line, and a spec change without regenerating the TypeScript types.
npm run typecheckand the managed component tests inpackages/web-shellpass against the generated types.
10. Follow-up
D2 and D3 can start in parallel. Each closes its gaps from section 7 and then
moves its routes to implemented, as the issue defines. The gap file cannot
hold everything those slices must fix:
- Undeclared error responses (D2). Several
partialoperations do not declare error statuses that the server returns:404 session_not_foundand400 invalid_limiton the WebShell transcript and session query,400 invalid_limiton the public event query, and400/404on WebShell submit and cancel. The test does not validate a body whose status the spec does not declare, so D2 adds these responses together withrequest_id. - Schema-valid semantics (D3). A full event page returns
has_more: falseandnext_cursor: null. The response matches the schema, so no gap line can record it, and D3's own tests must cover it. - Workspace-bound Sessions (W2). Creation and readback on both surfaces now
exercise the missing
context_revisionandstatedirectly. W2 supplies those fields and removes the corresponding record and response gaps.