Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ databases, credentials and migrations. The product uses Core exclusively; it has
with a synthetic model does not constitute live model validation. Keep provider
credentials in private test configuration, outside source, logs and task records.
Record unspecified or unverified behavior explicitly; never invent official
semantics. Track partial
semantics. When current documentation adds operations or fields absent from the
fixed baseline, queue a protocol upgrade instead of silently implementing a new
version. Owned-resource live probes can qualify status codes and wire details
left unspecified by the SDK; retain request evidence and distinguish observations
from guaranteed or fully covered behavior. Track partial
coverage in `contracts/agents-api/README.md` until the complete target is verified.
Reconcile current coverage summaries with merged routes and recorded acceptance;
distinguish accepted profiles, partial implementation, missing operations and
Expand Down
2 changes: 1 addition & 1 deletion apps/web/e2e/core-connection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ test("announces loading, authenticated access, and each safe failure state from
{ status: 200, body: { object: "list", data: [null], has_more: false, first_id: null, last_id: null } },
{ status: 401, body: { error: { code: "invalid_api_key", message: "safe fixture failure" } } },
{ status: 401, body: { error: { code: "gateway_auth_required", message: "safe fixture failure" } } },
{ status: 400, body: { error: { code: "invalid_beta_header", message: "safe fixture failure" } } },
{ status: 400, body: { error: { code: "invalid_beta", message: "safe fixture failure" } } },
{ status: 503, body: { error: { code: "unavailable", message: "safe fixture failure" } } },
{ abort: true },
];
Expand Down
22 changes: 12 additions & 10 deletions apps/web/e2e/fixture-core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ function initialState() {
updateStatus: 200,
deleteDelayMs: 0,
deleteStatus: 200,
sendStatus: 204,
sendStatus: 202,
sendResponseLoss: 0,
itemsScenario: 0,
turnsScenario: 0,
Expand Down Expand Up @@ -894,7 +894,7 @@ const server = http.createServer(async (request, response) => {
metadata: body.metadata ?? {},
};
state.vaults.unshift(vault);
return sendJson(response, vault);
return sendJson(response, vault, 201);
}
}

Expand Down Expand Up @@ -924,7 +924,7 @@ const server = http.createServer(async (request, response) => {
};
state.credentials.unshift(credential);
state.credentialTokens.add(credential.id);
return sendJson(response, credential);
return sendJson(response, credential, 201);
}
}

Expand Down Expand Up @@ -1023,7 +1023,7 @@ const server = http.createServer(async (request, response) => {
return sendError(response, 409, "Fixture idempotency key was reused with a different Session request.");
}
if (body.stream === true) {
response.writeHead(200, {
response.writeHead(201, {
"content-type": "text/event-stream; charset=utf-8",
"cache-control": "no-cache, no-transform",
connection: "keep-alive",
Expand All @@ -1032,7 +1032,7 @@ const server = http.createServer(async (request, response) => {
setTimeout(() => response.end(), state.controls.sessionCreateStreamCloseDelayMs);
return;
}
return sendJson(response, receipt.session, 200);
return sendJson(response, receipt.session, 201);
}

const control = consumeControl("sessionCreate", 201);
Expand Down Expand Up @@ -1097,7 +1097,7 @@ const server = http.createServer(async (request, response) => {
}
if (body.stream === true) {
const createdSnapshot = structuredClone(created);
response.writeHead(200, {
response.writeHead(201, {
"content-type": "text/event-stream; charset=utf-8",
"cache-control": "no-cache, no-transform",
connection: "keep-alive",
Expand Down Expand Up @@ -1358,7 +1358,7 @@ const server = http.createServer(async (request, response) => {
updated_at: created,
};
state.environmentTemplates.push(template);
return sendJson(response, template);
return sendJson(response, template, 201);
}
return sendError(response, 405, "This API method is not supported.", "unsupported_operation");
}
Expand Down Expand Up @@ -1557,21 +1557,23 @@ const server = http.createServer(async (request, response) => {
object: "list",
data,
has_more: start + data.length < sessionTurns.length,
first_id: data[0]?.id ?? null,
last_id: data.at(-1)?.id ?? null,
});
}

const eventsMatch = url.pathname.match(/^\/v1\/agents\/sessions\/([^/]+)\/events$/);
if (request.method === "POST" && eventsMatch) {
const status = state.controls.sendStatus;
const responseLoss = state.controls.sendResponseLoss;
state.controls.sendStatus = 204;
state.controls.sendStatus = 202;
state.controls.sendResponseLoss = 0;
if (responseLoss) {
response.destroy();
return;
}
if (status !== 204) return sendError(response, status, "Fixture send failed.");
response.writeHead(204);
if (status !== 202) return sendError(response, status, "Fixture send failed.");
response.writeHead(202);
response.end();
return;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/core-probe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ describe("Core connection probe", () => {
});

it.each([
[jsonResponse({ error: { code: "invalid_beta_header" } }, 400), 400],
[jsonResponse({ error: { code: "invalid_beta" } }, 400), 400],
[jsonResponse({ error: { code: "not_found" } }, 404), 404],
[jsonResponse({ error: { code: "method_not_allowed" } }, 405), 405],
[jsonResponse(canonicalPage(), 201), 201],
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/core-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export async function probeCore(options: CoreProbeOptions): Promise<CoreProbeRes
return { kind: "unauthorized", executionReadiness: "unknown", httpStatus: response.status };
}
if (
(response.status === 400 && errorCode === "invalid_beta_header") ||
(response.status === 400 && errorCode === "invalid_beta") ||
response.status === 404 ||
response.status === 405
) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/pending-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function failPendingSend(
): FailedPendingSend {
return {
...pending,
code: error instanceof AgentCoreError ? error.code : undefined,
code: error instanceof AgentCoreError ? error.code ?? undefined : undefined,
message,
uncertain: isUncertainSendFailure(error),
};
Expand Down
13 changes: 10 additions & 3 deletions contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,16 @@ including further deployment qualification; this inventory describes merged beha

## Public semantics

The [September 22 wire comparison](official-semantics-alignment.md) records the
bounded official-service observations, aligned responses and remaining differences.
It supplements the fixed SDK baseline; current documentation does not silently
upgrade the protocol.

- Credentials use `POST /vaults/{vault_id}/credentials` and
`GET /vaults/{vault_id}/credentials/{credential_id}`. The static profile accepts
`static_bearer` with required string token and HTTPS destination, plus a
required name trimmed to 1–256 UTF-8 bytes. Tokens remain opaque, including empty
strings; exact hosted token validation is unverified. The local URL profile
required name trimmed to 1–256 UTF-8 bytes. Tokens remain opaque and nonempty; explicitly empty tokens are rejected before
mutation, following the sampled official create/update behavior. The local URL profile
excludes userinfo/fragments and preserves queries without normalization or network
contact. Public metadata contains identity, owning Vault, name, timestamps and
auth type/destination; it never returns tokens or ciphertext and can be read
Expand Down Expand Up @@ -563,7 +568,9 @@ historical native transport evidence.
`POST /v1/agents/sessions/{session_id}/events` accepts `agent.session.input.message`
with ordered user `input_text` content and [qualified image content](message-input.md), `agent.session.input.cancel` and
`agent.session.input.tool_result`. Successful atomic
admission returns 204, as consumed by the official `events.create` method. A retry
admission returns 202 with no body, as observed from the official service.
An empty event array is an authenticated no-op: it creates no Turn or Item and
does not reserve an execution retry key. A retry
key identifies the entire ordered request; conflict does not partially admit it.
Messages start queued work or steer the active Turn. Individual input messages
remain distinct Items even when their text shares one native prompt.
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/execution-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ and Environment Plugin MCP have separate inventories and qualification.
| Operation | Implemented behavior and real qualification | Unsupported or unverified boundary |
| --- | --- | --- |
| Initial message input, `sessions.create` | String input and ordered user-message arrays share atomic admission. Codex/Claude text and inline image execution: M1/M2; ordinary MiniMax text: M1/P1. [Input contract](message-input.md), [initial parser](../../services/agents-api/internal/api/session_initial_input.go). | Empty-message behavior and local size-limit parity need upstream evidence. Images are only qualified for inline PNG/JPEG on `none` and Core-managed Docker `openai_hosted`; MiniMax, self-hosted images and remote URLs remain gaps. |
| Prepared and active messages, `sessions.events.create` | Ordered `input_text`/`input_image` arrays retain original content and distinct public user Items. HTTP 204 confirms persistence; native receipts establish application. Initial/prepared/active Docker PNG/JPEG: M2; active PNG on `none`: M1. [Shared admission](../../services/agents-api/internal/api/inputs.go). | Codex flattens native messages with blank-line separators. Claude can fold or queue native turns; it does not promise Codex's same-native-turn behavior. Public durability does not prove native consumption. |
| Prepared and active messages, `sessions.events.create` | Ordered `input_text`/`input_image` arrays retain original content and distinct public user Items. HTTP 202 confirms persistence; native receipts establish application. Initial/prepared/active Docker PNG/JPEG: M2; active PNG on `none`: M1. [Shared admission](../../services/agents-api/internal/api/inputs.go). | Codex flattens native messages with blank-line separators. Claude can fold or queue native turns; it does not promise Codex's same-native-turn behavior. Public durability does not prove native consumption. |
| Structured output, `agent.text.format` | Save/inherit/freeze `{type:json_schema,schema:...}`. Claude SDK object-root, single Agent, medium verbosity, ordinary functions returning text: S1 (`none`) and S2 (Docker, including prepared/active input and Files/Artifacts). Native final text is retained unchanged. [Contract](structured-output.md), [profile](../../services/agents-api/internal/engine/claude.go). | Codex/MiniMax, other schema roots, schema numbers changed by binary64, self-hosted, Skills/Plugins, MCP, Subagent and discovery combinations reject execution. No output repair, coercion or extra model loop. Arbitrary schema dialects are unverified. |
| Function configuration, saved/inline Agents | Required name/description/schema; `defer_loading` defaults false. Saved references resolve into an immutable Session snapshot. Codex/Claude real calls: F1/F2/M2/S1/S2. [Parser](../../services/agents-api/internal/api/function_configuration.go), [saved tools](../../services/agents-api/internal/api/saved_tools.go). | MiniMax public functions reject. Claude requires object-root schemas. Local unique/nonblank name and 64-definition bounds are compatibility gaps. Saving configuration alone does not qualify execution. |
| Function-result admission, `events.create` | Required `turn_id`, `call_id`, `success`; optional nullable `error` and `output`. Output is string or ordered text/image content. Scoped atomic batches retain field presence, original content and retry identity. Same result retries are accepted; changed results conflict, including after terminal state. F1/F2/M2 plus [controlled SDK/raw checks](../../services/agents-api/tests/official_function_inputs.py). [Parser](../../services/agents-api/internal/api/function_inputs.go), [Store](../../services/agents-api/internal/store/function_results.go). | Admission is separate from application and public Item publication. Invalid or unqualified content cannot consume a pending call. Exact hosted errors, defaults and publication timing remain unverified. |
Expand Down
77 changes: 77 additions & 0 deletions contracts/agents-api/official-semantics-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Official wire semantics: September 22, 2026

This batch compares owned-resource requests to the official Agents API with Core
main `692e32daafb19521e0919915c7685eef78b813fa`. The protocol remains Python SDK
3.13.0, upstream `d7c41efee1b0802b79f3f88a678ef2052b06e9ce`, `agents=v1`.
The current [Session reference](https://developers.openai.com/api/reference/python/resources/beta/subresources/agents/subresources/sessions)
and [overview](https://developers.openai.com/api/docs/guides/agents-api/overview)
were consulted alongside the pinned source. Current documentation is not a
replacement baseline. A successful SDK parse alone is not conformance evidence.

## Selected common behavior

| Operation | Official observation and Core behavior in this batch |
| --- | --- |
| Create Agent, Vault, Credential, EnvironmentTemplate or Session | HTTP 201. Session JSON and live SSE creation use the same status. Non-creation successes retain their operation-specific status. |
| Submit Session events | HTTP 202 with an empty response body. An empty array is an authenticated no-op; null is invalid. No-op requests do not create a Turn, Item or execution retry reservation. |
| Session, Turn and Item lists | `object: list`, `data`, `has_more`, `first_id` and `last_id`. Empty pages contain null first/last IDs. |
| Empty Agent/Template update | Advance `updated_at` through the existing atomic update, preserving IDs, content, ownership and frozen Session snapshots. Timestamp precision is seconds; immediate updates may have the same serialized timestamp. |
| Static bearer create/replacement | Reject an explicitly empty token before mutation. Preserve valid opaque token bytes without trimming. |
| OAuth grant create/replacement | Reject an explicitly empty access token and a replacement without mutable grant fields. Preserve previously qualified refresh/expiry/null handling. |
| Input message discriminator | Omission remains valid; a supplied `type` must be `message`. Explicit null or empty strings reject through the shared initial/event decoder, matching the pinned literal type. |
| Missing beta resource | HTTP 404 with `type` and `code` equal to `not_found_error`. Missing and foreign resources remain indistinguishable. |
| Missing required Beta header | HTTP 400 with `type` and `code` equal to `invalid_beta`, after authentication. |
| Missing non-beta File or Skill | HTTP 404 with `type: invalid_request_error`, `code: null`. Exact message, File `param` and additional detail payload remain outside this batch. |

The resource comparison made 40 raw requests over six newly owned resources
(one Agent, two Vaults, two Credentials and one Template), including actual
rejected replacements and post-delete reads. All six resources were deleted.
Session probes used real `gpt-6-astra` executions and compared event admission,
query envelopes and accumulated usage; their owned Sessions and Agent were also
deleted. Separate missing File/Skill probes used randomly generated IDs.
Private request/status/body evidence and cleanup results are retained under
`~/.parsar/remediation/20260922/official-semantics-alignment/`; no API keys or
credential values belong in the repository or task board.

## Explicit remaining differences

- Current documentation supports Session Agent configuration updates; the fixed
`SessionUpdateParams` exposes only metadata. New fields and newer Environment
status/configuration shapes are a queued baseline upgrade, as approved by the user.
- Official `none` creation rejected omitted, null and empty initial input. Core
still permits idle `none` Sessions. Changing this requires a coordinated client
and acceptance-flow migration and is separately queued.
- Two otherwise identical official creates with the same `Idempotency-Key`
returned 201 and distinct Session IDs. Core retains its durable creation retry
guarantee. This is a local behavior, not evidence of official idempotency parity.
- An empty Session update body, generic validation codes/field `param`, malformed
queries, page limits and overlapping mutation behavior need separate qualification.
The error mapping above must not be extrapolated to every status or resource.
- Template references with inline installation overrides, optional Skill version
semantics and the other active board entries remain outstanding.
- Native model defaults, tool combinations and unavailable usage counters retain
their documented multi-harness differences. Core does not reconstruct model
output, guess counters or introduce a second tool loop to manufacture equality.

These observations establish a bounded comparison, not complete official protocol
compatibility. Core regression and real-model validation are recorded with the
implementation acceptance before merge.

## Core acceptance

The deployed server/runtime at `7c80d604` passed seven real-PostgreSQL resource and
safety groups, including unchanged credential-row hashes after rejected writes
and restart. Codex and Claude Code used Kimi K3; MiniMax Code used MiniMax M2.7.
Each completed three real Turns covering JSON creation, live SSE creation and
event continuation, with history paging, empty no-op requests and tenant isolation.
Four earlier attempts were interrupted by failed test-network relays and retained
as unsuccessful evidence. After end-to-end TLS checks, the controlled rerun passed.

The server `make check` gate passed with Web checks run separately: type checks,
production build, 287 client tests, 573 Web tests and 74 fixture browser cases
(the corrected Beta-error fixture was rerun separately). Full standalone fixed
Python SDK and Go-client acceptance passed. A fresh Astra high full-diff review
found no blockers and independently ran API/contract tests. Rebase onto main
`c96ea82` preserved every batch patch; the combined tree passed API/execution and
three PostgreSQL scheduling regressions. Test resources were scoped to this batch.
E2B, OAuth provider refresh and new native capability combinations were not requalified.
Loading
Loading