Skip to content

feat!: SDK phase 1 stack (#11–#23) into main - #25

Merged
yash-kark merged 38 commits into
mainfrom
feat/p1-sdk-2-remove-events-sink
Oct 28, 2026
Merged

yash-kark merged 38 commits into
mainfrom
feat/p1-sdk-2-remove-events-sink

Conversation

@yash-kark

Copy link
Copy Markdown
Contributor

Brings the phase-1 stack to main. #10 (cas-phase-1 → main) merged before the stacked PRs, so main has only v0.6.0. #11–#23 were merged into intermediate branches, and feat/p1-sdk-2-remove-events-sink holds all of them. Its tree is identical to the top of the stack (feat/p1-sdk-13-sandbox-images).

Contents (already reviewed and merged as)

PR Change
#11 Pin the agent document's egress URL (tests and the plan)
#12 Remove the AgentOS dashboard events sink
#13 Guardrails through the egress proxy (/guardrails/v1/check)
#14 Remove the standalone SRS policy engine
#15 Adopt sandbox tokens reissued by the egress proxy in every client
#16 Keep OTLP exports under the proxy's 1 MiB limit
#17 Session store through the egress proxy
#18 MCP servers addressed by name
#19 Read the llm / mcp sections of the agent document (needs old-CAS #164)
#20 Read the Lyzr key every turn
#21 App server for stage images (Lambda MicroVM)
#22 AgentCore relay envelope on the app server
#23 Sandbox images for AgentCore and Lambda MicroVM, COMPUTER_AGENT_* setting names, per-session agent, SSE keepalive fix, session store keyed by session_id

Notes

🤖 Generated with Claude Code

Yash Karkhanis and others added 30 commits October 26, 2026 12:36
Adds the P1 SDK egress plan (.claude/plans/p1-sdk-egress-routes.md) and a
regression test: with the egress base
http://egress/computer-agent-api/agentos/api (with or without a trailing slash
or /v1) the agent is read from exactly
GET http://egress/computer-agent-api/agentos/api/v1/agents/{id} with x-api-key.
api_base() already yields a single /agentos/api, so no code changes; the module
docstring and USING_THE_SDK.md §3 and §6 name the egress path.

Plan phase 1 (R2, D1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The egress proxy serves no /events route, so the sink cannot reach AgentOS
from a sandbox. Removed: AgentOSHttpSink (telemetry/sinks/agentos_http.py), its
lazy export, the drop-in auto-attach from AGENTOS_INGEST_URL / discovery, the
events_url setting and COMPUTERAGENT_EVENTS_URL. build_pipeline takes only
telemetry_url. examples/with_agentos.py is deleted; multi_sink.py and
run_live_demo.py keep their OtelSink; the stale ingest_live_e2e section of
examples/e2e/README.md is dropped.

Plan phase 2 (Q4, D14).

BREAKING CHANGE: AgentOSHttpSink, events_url and COMPUTERAGENT_EVENTS_URL are
removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user's message and every tool call go to POST {guardrails_url}/v1/check
(ops input and tool-input); the agent gateway applies the org's and the
agent's RAI policies and owns fail-open.

- guardrails_url / COMPUTERAGENT_GUARDRAILS_URL replaces srs_url and
  agent_api_url; no org-policy fetch, rai_policy_id ignored.
- No verdict blocks the message or denies the tool.
- Tool calls carry the engine's tool-use id as call_id; none -> deny.
- Delete policy/srs_client.py and policy/org_policy.py.

BREAKING CHANGE: COMPUTERAGENT_GUARDRAILS_URL is required;
COMPUTERAGENT_SRS_URL and COMPUTERAGENT_AGENT_API_URL are no longer read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(policy)!: check guardrails through the egress proxy
SrsPolicyEngine, srs_authorizer_from_env and computeragent.policy.srs are
deleted; nothing fetches {srs}/rai/policies/{id}. The built-in guardrails
gate replaces them; OPA and Cedar engines remain for custom gates.

BREAKING CHANGE: computeragent.policy no longer exports SrsPolicyEngine or
srs_authorizer_from_env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refactor(policy)!: remove the standalone SRS policy engine
…very client

An egress answer carrying X-Cas-Token / X-Cas-Token-Expires-At extends the
token's chain (sbt_ token, later expiry, sent with the newest token); every
later egress request from every client carries the newest token.

- egress_token: process-wide CREDENTIAL, httpx hooks, install(),
  stamping_session(); Settings.api_key() resolves the chain.
- Agent resolve, guardrails client and gitagent's Anthropic/OpenAI clients
  carry the hooks.
- egress_forwarder: a loopback h11 forwarder for the Claude CLI's model and
  MCP calls; the coordinator rewrites their egress URLs per turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(harness): adopt sandbox tokens reissued by the egress proxy in every client
Each OTLP export is encoded and split recursively under a 960 KiB budget;
a single record over it is dropped with a count-only WARN, and the export
fails if any part failed. Span and log processors export 128 at a time.
The exporters use a stamping requests session that sets x-api-key from
api_key() per request, so reissued sandbox tokens apply to telemetry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(telemetry): split OTLP exports to fit the egress proxy's limit
…s proxy with the API key

- COMPUTERAGENT_SESSION_STORE_URL / session_store_url (optional, no query or
  fragment) sets the http store's base URL via with_base_url().
- The http store sends only x-api-key (LYZR_API_KEY per request, as the
  newest sandbox token); the store token, its refresh and expiry_of are gone.
- A descriptor without a kind is reported by its keys, never its values.

BREAKING CHANGE: HttpSessionStore no longer takes token or auto_refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(session-stores)!: route the HTTP session store through the egress proxy with the API key
Each bound MCP server is {mcp_url}/{quote(tool_name)} with no query; the
server-id path and ?tools= query are removed (the gateway resolves both from
its registry). Docs use COMPUTERAGENT_MCP_URL=http://egress/mcp/v1.

BREAKING CHANGE: the MCP URL shape changes to {mcp_url}/{name}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(harness)!: address MCP servers by name through the egress proxy
…t's gateway sections

The agent document (after old-CAS #164) is read from llm.config.model and
mcp.servers; guardrails is not read. The model-gateway fields and refusals,
mcp_mode and DEFAULT_MCP_MODE are removed; every binding is attached to the
engine. For gitagent a model without a provider prefix gets openai:.

BREAKING CHANGE: ResolvedAgent loses gateway_* fields, has_native_gateway and
mcp_mode; legacy document fields are no longer read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

feat(harness)!: read the model and MCP servers from the agent document's gateway sections
The engine's ANTHROPIC_API_KEY / OPENAI_API_KEY / LYZR_API_KEY and the bound
MCP servers' x-api-key are set from Settings.api_key() at every turn (and at
boot); servers the loader or caller supplied are untouched. A missing key
fails the turn before the engine starts. build_gateway_envs holds no key;
key_envs() returns the three variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(harness): pick up the Lyzr key every turn
computeragent.server (extra `server`), run as `python -m computeragent.server`
on 0.0.0.0:8080: Lambda MicroVM lifecycle hooks, health, sessions, messages,
events (SSE with Last-Event-ID replay), end-input, cancel, approvals and the
workspace file routes, following computeragent-enterprise's harness-server.

- One ComputerAgent per session from the image's settings; envs.LYZR_API_KEY
  seeds the key and each message's credential replaces it; turns run one at a
  time per process.
- Events in sse-events.ts shapes: ca_turn_started and ca_redaction_decision
  (observed inline on the telemetry pipeline), sdk_message in the TS SDK's
  JSON shape, ca_permission_request/decision, ca_usage_snapshot before the
  turn's result, one ca_session_ended per session.
- File routes confined to the workspace; deletes remove a file or an empty
  directory only.
- Plan: Q16-Q18 resolved with the plan's recommendations; the turn-end wording
  corrected to the reference harness's behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(server): serve the CAS harness API from the stage image
The app server also answers AgentCore: GET /ping (Healthy / HealthyBusy while
a turn runs) and POST /invocations, relay envelope v1 dispatched in-process to
its own routes. Streamed answers start with the `: ca-relay` head frame; other
answers return as {relay, instance, status, headers, body | bodyBase64}; a
malformed envelope is status 400 AGENTCORE_RELAY_REQUEST_INVALID. The
instance id is minted on the first invocation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(server): serve the AgentCore relay envelope
…es the agent and token per session

The settings' environment variables are renamed so each says what it is:
COMPUTER_AGENT_ID, COMPUTER_AGENT_ENGINE, COMPUTER_AGENT_EGRESS_PROXY_SECRET
(was LYZR_API_KEY) and COMPUTER_AGENT_EGRESS_{API,LLM,MCP,GUARDRAILS,
TELEMETRY,SESSION_STORE}_URL, plus COMPUTER_AGENT_GAP_DIR. Constructor
arguments are unchanged; no old name is read.

Old CAS now passes COMPUTER_AGENT_EGRESS_PROXY_SECRET and COMPUTER_AGENT_ID
in POST /v1/sessions envs: the app server seeds the token from it and builds
the session's agent from the id, falling back to the image's own.
events() bounded each read with asyncio.wait_for(frames.__anext__(), 20 s).
On a timeout, wait_for cancelled the EventLog generator inside its wait for
new events; a cancelled async generator is finished, so the next read raised
StopAsyncIteration and the response ended right after the first keepalive.
On Lambda MicroVM CAS reported STREAM_UPSTREAM_LOST on any 20 s quiet gap.

EventLog.iterate now takes the keepalive and yields None when it expires, so
no cancellation crosses the generator.
The Claude CLI keys transcripts by its own per-process UUID, so a new sandbox
for the same conversation looked under a key nobody wrote and started blank,
and agentos had no stable id to file the store under (old-CAS plan 10).

With ComputerAgent(session_id=..., session_store=...), PinnedSessionStore
puts that id on every append and load; project_key and subpath are kept. The
Claude engine runs the CLI under uuid5(session_id): on the first turn of a
process it loads the store once and starts a new conversation under that UUID,
or resumes the stored one. Every process serving a conversation then uses the
same CLI UUID, so the stored entries' ids always match. The app server already
passes session_id = the CAS sessionId. Without session_id nothing changes.
Both example images install the otel extra and set COMPUTER_AGENT_EGRESS_TELEMETRY_URL=<egress>/otlp.
vrajp-lyzr and others added 8 commits October 28, 2026 10:20
policy_config_url and fetch_policy_binding had no caller since 371c815
(0.4.0, one run mode). AgentOS has dropped the ingest/policy-config/{name}
route they read (computeragent-enterprise a8843c9) because the agent gateway
applies org and agent policies, so a direct caller got a 404 that read as
"no policy". bind_policy stays: PUT /agents/{id}/policy is still served.
CAS now passes the six egress routes per session, beside the token and the
agent id: COMPUTER_AGENT_EGRESS_{API,LLM,MCP,GUARDRAILS,SESSION_STORE,
TELEMETRY}_URL in POST /v1/sessions envs. Each is used only when the image's
own environment does not set it, and an agent factory's arguments win over
both, so one image runs against whatever proxy CAS names while an image or
its code can still pin a route.
Rename the examples to examples/sdk_default_lambda_microvm and
examples/sdk_default_agentcore: they build the images UAT CAS boots when an
agent names no MicroVM image or AgentCore runtime of its own. Their .env now
bakes only the engine; CAS passes the agent id, the sandbox token and the six
egress routes per session, so one image runs against whichever egress proxy
CAS names.
…og the instance

Matches CAS's AgentCore relay (computeragent-enterprise #158).

- PY1: AgentCore never forwards a caller's disconnect, so CAS names every
  envelope with x-ca-relay-stream: <uuid v4> and sends DELETE
  /_relay/streams/<id> when its caller goes away or before it re-attaches.
  The relay strips the header before dispatch, keeps a map id -> open call,
  and answers the DELETE itself: it closes that call (the relayed /events
  stream ends and its subscriber detaches) -> relayed 204; an unknown or
  non-UUID id -> relayed 404. Logs relay.stream.opened / relay.stream.closed.
- PY2: AgentCore cuts any answer at 104,857,600 bytes. A JSON envelope over
  that is replaced by a relayed 502 AGENTCORE_RESPONSE_TOO_LARGE
  (source CAS_RUNTIME, retry false, meta.maxBytes) instead of a cut body.
  Event streams are not capped.
- PY3: the per-microVM instance id is logged once, as relay.instance, when it
  is minted on the first invocation.
feat!: sandbox images for AgentCore and Lambda MicroVM; per-session agent, SSE keepalive fix, session_id-keyed store
@yash-kark
yash-kark merged commit 52f879a into main Oct 28, 2026
2 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants