Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .agents/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# `.agents/` — engineering record of the python-sdk repository

- [Managed Agent input and saved-configuration contract](notes/proposed/feature/2026-11-12-managed-agent-input-contract.md) · [verification](evidence/verify/2026-11-12-managed-agent-input-contract.md).

## Recent changes — 2026-11-10

- [Owner change index](notes/proposed/process/2026-11-10-recent-change-index.md) · [records verification](evidence/verify/2026-11-10-recent-change-index.md).
Expand Down
84 changes: 84 additions & 0 deletions .agents/evidence/verify/2026-11-12-managed-agent-input-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Verification: managed Agent input contract

- Date: 2026-11-12
- Base revision: a9a572d81847fe99216bf1bb454b91810dacc676
- Branch: feat/agents-api-contract-20261112
- Owner issue: https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/162
- Delivery tracker: https://github.com/Alibaba-Agent-Sandbox/awesome-templates/issues/309
- Result: PARTIAL
- Environment: Linux, Python3.11.15; matching local client/server0.4.0a7 source candidates in an isolated virtual environment. No cloud/model credentials loaded by these checks. Local PostgreSQL uses pgserver0.1.4 with psycopg/psycopg-binary3.3.2.

## What was verified

The managed Gateway accepts the optional message discriminator, uses supplied saved Agent
IDs without coercion or truthiness fallback, preserves saved-plus-inline overrides, and
distinguishes429/503 error categories. This is a bounded API correction, not the complete
YAML migration, new adapter support or an OpenAI SDK compatibility claim.

## Commands run

- `python -m pytest client-sdk/tests/test_managed_agents.py -q -k 'typed_user_message or invalid_typed_input or saved_agent_overrides or explicit_agent_id or managed_error_types'`
before production edits: exit1,10 failed/5 passed/52 deselected. Failures reproduce typed
message rejection, empty/non-string ID fallback/coercion and429/503 category behavior.
- The same command after the correction: exit0,15 passed/52 deselected.
- With `or real_http_typed_message` added: exit0,16 passed/52 deselected. The added case uses
a real local HTTP listener, actual quota admission, and a deliberately failing dependency
to prove safe503 serialization; it is not a live cloud service failure test.
- `make test`: initial exit2 in the Server package,874 passed/1 skipped/1 failed. The failing
`test_pip_returns_structure` found no installed packages because this uv-created environment
lacked pip; Make stopped before the Client package. Installed pip26.1.1 and re-ran that
exact case: exit0,1 passed. No product workaround was added.
- Installed optional pgserver0.1.4 and matching psycopg3.3.2, then parameterized typed-input
and saved-override regressions over SQLite and actual temporary PostgreSQL. The focused
four-case selection passed, exit0; fixtures shut down their temporary databases.
- `make test` after environment preparation: exit0; Server875 passed/1 skipped, Client2483
passed/27 skipped. These counts are separate package runs, not cloud/model acceptance.
- `make lint`, `make typecheck`, `make format-check`, `make hygiene`: each exit0 for both packages.
- `make ci`: exit0; Server875 passed/1 skipped, Client2473 passed/27 skipped/10 deselected;
hygiene, lint, format, typecheck, both wheel/sdist builds, twine checks, py.typed markers,
clean client-only HTTP and optional-server combined installation passed. This is one
Python3.11 interpreter, not the remote3.10/3.11/3.12 matrix.
- The full runs skipped the packaging modules because hatchling was absent from the test
environment (isolated builds had their own backend). Installed the build's actual
hatchling1.33.0 and ran each `tests/test_packaging.py` separately: Server5 passed, Client7
passed, both exit0. No full suite was rerun solely to change skip counts.
- `bash scripts/check-repo-health.sh --quiet`: exit0,106 passes/1 existing recommended
release-runbook warning/0 failures, including a final repeat after the result update.
- `make hygiene`, `git diff --check`, and staged `git diff --cached --check`: final exit0.
- `bash .githooks/check-secrets.sh`: exit0 against the ten explicitly staged scoped files;
no sensitive information found. The follow-up instance design is outside this patch.

## Results

Input/ID/error regressions and required single-interpreter local gates PASS. Existing saved configuration
overrides were retained; missing/foreign nonempty saved IDs already returned404 before this
change. The newly corrected false-like fallback includes the empty string; its lookup now404.
Unknown input types/images/roles and the original text budget still reject atomically.

The remaining26 Client skips require an explicitly configured compatible Node engine command,
the legacy CLI sibling fixture location, or Node CLI/provider source/dependency prerequisites.
They are reported as skips rather than passes. The two packaging module skips in the full
runs were resolved by the separate12-test packaging run above. Remote matrix, cloud/model
E2E and publication are independent incomplete gates, so the overall result remains PARTIAL.

## Cleanup

Local test HTTP listeners close and threads join in finally. The selected regressions create
only temporary SQLite/PostgreSQL records and fixture directories; no cloud/model resources were created.
This is not an account-wide resource inventory.

## Problems found

An initial mutual-exclusion hypothesis was rejected by the current official Create Session
reference before implementation: saved Agent plus inline overrides are supported. The patch
does not introduce that incorrect restriction. Other HTTP error categories were deliberately
not inferred from SDK exception names or a stale local mapping.

One exploratory pytest command combined both packages and hit their same-named
`tests.conftest` modules (exit4). It was corrected to separate package commands, as the
Makefile does; this was a test invocation error, not a product failure.

## Not verified

Real cloud/model E2E, complete YAML migration, official SDK interoperability, remote CI/review,
PR merge and publication have not been completed by this patch. The decision remains proposed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Decision: Preserve the managed Agent input and saved-configuration contract

Status: proposed

## Problem

The managed Gateway rejects a user message containing the optional `type: "message"`
discriminator accepted by the current OpenAI Create Session reference. Explicit false-like
Agent IDs can bypass lookup and fall back to an inline configuration, while other non-string
IDs are coerced to strings. Rate-limit and unavailable-service errors use the same category
as invalid requests. These mismatches affect clients before native execution begins.

## Decision

Accept only the optional literal message discriminator without widening the supported
user/input_text subset. Validate an explicitly supplied Agent ID as a string and perform
the existing owner-aware lookup even for an empty string. Preserve saved Agent plus inline
overrides and verify that they create independent Session snapshots. Categorize the directly
documented HTTP429 and503 cases while retaining existing codes, messages and field paths.

## API Design

`text_input(value)` accepts the existing string and user-message forms plus optional
`type: "message"`. Unknown types, non-user roles, images and oversized text still fail.
`create_session` requires any supplied `agent_id` to be a string: non-string400; unknown,
foreign, deleted or empty ID404. Supplying `agent_id` and supported inline `agent` fields
remains valid; the latter override only the new Session's configuration.
ManagedError reports `rate_limit_error` for429 and `service_unavailable_error` for503.
Other status categories are unchanged; this is not a claim of full OpenAI compatibility.

## Alternatives considered

- Reject `agent_id` together with `agent`: rejected after reading the current official
reference, which explicitly permits per-session overrides. The local API's older rule
is not authority for the managed contract and is outside this change.
- Require nonempty IDs: not inferred from the upstream schema, whose string minimum is0.
Existing lookup gives404 for an empty ID without creating a Session.
- Copy a universal error-category map or expose every local Agent feature: rejected;
the verified authoritative examples and current task only justify this bounded patch.

## Dependencies

No new dependency, adapter, schema language, cloud resource or package release.
Authoritative references: [Create Session](https://developers.openai.com/api/reference/resources/beta/subresources/agents/subresources/sessions/methods/create)
and [Error codes](https://developers.openai.com/api/docs/guides/error-codes).
Current managed runtime and template work are independent of the broader YAML migration;
these corrections do not establish migration completion or real model acceptance.
Owner issue: [python-sdk162](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/162).
Delivery tracker: [templates309](https://github.com/Alibaba-Agent-Sandbox/awesome-templates/issues/309).

## Test Strategy

First run new consumer regressions against unchanged production code to demonstrate the
typed-input, ID-lookup/type and category failures. Exercise all three input entry points,
atomic invalid-input rejection, saved overrides, owner/missing-ID behavior and a real local
HTTP listener's accepted request, quota rejection and safe dependency-error conversion.
Run both package suites, lint, typecheck and repository health/hygiene. Preserve the required
cloud/model E2E gate as pending until the encompassing delivery runs it.

## Acceptance criteria

- Typed and untyped supported messages are accepted without allowing new input kinds.
- Invalid input and invalid Agent IDs leave no accepted Session or Submission.
- Saved Agent overrides do not mutate the source Agent or previous Sessions.
- 429/503 response categories are correct and private exception text remains hidden.
- Required local checks and current-head remote gates are reported accurately.
- Real cloud/model validation and publication remain explicit incomplete gates.

## Evidence

[Verification](../../../evidence/verify/2026-11-12-managed-agent-input-contract.md).
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

## [Unreleased]

### Fixed

- Accept the optional `type: "message"` discriminator for managed user-text input,
validate explicitly supplied saved Agent IDs without coercion or inline fallback,
and distinguish rate-limit and unavailable-service error categories. Saved Agent
overrides remain scoped to the new Session. / 托管用户文本输入支持可选的
`type: "message"`;显式 Agent ID 按字符串校验,不再强转或退回内联配置;
区分限流与服务不可用错误类别,已保存 Agent 的覆盖仍只作用于新 Session。

### Documentation

- Synchronize CLI-dependent SDK documentation, HTTP MCP template deployment and source-acquisition error guidance. / 同步 SDK 文档中的 CLI 用法、HTTP MCP 模板部署与源码获取错误指引。
Expand Down
8 changes: 6 additions & 2 deletions client-sdk/src/agent_sandbox/managed_agents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@ def text_input(value: Any) -> str:
for message in value:
if not isinstance(message, dict):
raise ManagedError(400, "unsupported_input", "Expected user text", "input")
reject_unknown(message, {"role", "content"}, "input.")
if message.get("role") != "user" or not isinstance(message.get("content"), list):
reject_unknown(message, {"type", "role", "content"}, "input.")
if (
message.get("type", "message") != "message"
or message.get("role") != "user"
or not isinstance(message.get("content"), list)
):
raise ManagedError(400, "unsupported_input", "Expected user text", "input")
for part in message["content"]:
if (
Expand Down
4 changes: 3 additions & 1 deletion client-sdk/src/agent_sandbox/managed_agents/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,8 @@ def create_session(
)
if "stream" in body and not isinstance(body["stream"], bool):
raise ManagedError(400, "invalid_stream", "stream must be boolean", "stream")
if "agent_id" in body and not isinstance(body["agent_id"], str):
raise ManagedError(400, "invalid_agent_id", "agent_id must be a string", "agent_id")
with self.store.transaction() as tx:
old = self._idem(tx, owner, "create-session", key, body)
if old:
Expand All @@ -442,7 +444,7 @@ def create_session(
self.check_admission(tx, self.config.revision)
if len(tx.list("session", owner)) >= self.config.sessions["maxLogicalSessionsPerOwner"]:
raise ManagedError(429, "session_limit", "The logical Session budget is exhausted")
saved = tx.get(str(body["agent_id"]), owner) if body.get("agent_id") else None
saved = tx.get(body["agent_id"], owner) if "agent_id" in body else None
if saved and saved["_kind"] != "agent":
raise ManagedError(404, "resource_not_found", "Agent was not found")
agent = self._agent(body.get("agent", {}), public(saved) if saved else None)
Expand Down
5 changes: 4 additions & 1 deletion client-sdk/src/agent_sandbox/models/managed_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ def body(self) -> dict[str, Any]:
"""Return the documented error envelope."""
return {
"error": {
"type": "invalid_request_error",
"type": {
429: "rate_limit_error",
503: "service_unavailable_error",
}.get(self.status, "invalid_request_error"),
"code": self.code,
"message": self.message,
"param": self.param,
Expand Down
Loading
Loading