feat(core): connect Anthropic Managed Agents - #5083
Conversation
There was a problem hiding this comment.
Builder reviewed your changes and found 4 potential issues 🔴
Review Details
Code Review Summary
PR #5083 adds Anthropic Managed Agents as a hosted Connected Agent target, including provider-specific session/event translation, tool-confirmation handling, credential-backed probing, headless invocation support, settings UI, localization, and fixture coverage. The overall adapter structure and test coverage are strong: provider details remain behind the existing A2A-shaped surface, access-scoped credential resolution is used in the normal runtime path, and the UI preserves the existing A2A configuration flow.
This is a high-risk integration review because it adds a credentialed external API contract and new session/HITL lifecycle behavior. The main concerns are at boundaries where provider state is reduced or where probe inputs are trusted:
- 🔴 HIGH — The default-host probe can resolve and transmit an arbitrary vault credential reference without requiring a saved, provider-scoped connection.
- 🔴 HIGH — The adapter does not recognize the documented error/ended session event names, allowing failed or ended sessions to timeout or return partial text as success.
- 🔴 HIGH — Headless invocation drops
input-requiredcontinuation metadata and presents an approval pause as a completed text response. - 🟡 MEDIUM — Managed approval events are not wired to the local approval UI/continuation path, and denial does not send a provider confirmation.
🧪 Browser testing: Attempted full verification. The dev server was healthy, but all 15 planned cases were couldnt_verify because browser executor sessions lacked Chrome automation tools; this is an environment limitation, not a pass.
| const requiresSavedConnection = | ||
| Boolean(auth) || | ||
| // The default Anthropic API host is the provider endpoint, so | ||
| // its ID/key check is safe before the manifest is saved. Any | ||
| // custom host still needs an existing scoped connection. | ||
| Boolean(kind && !isAnthropicManagedAgentsApiUrl(urlParam)); |
There was a problem hiding this comment.
🔴 Require a scoped connection before sending probe credentials
The default Anthropic host bypasses the saved-connection check, but the request still accepts a caller-controlled kind.credentialRef; probeAnthropicManagedAgent then resolves that vault reference and sends it as x-api-key. A caller can therefore cause an arbitrary organization credential to be disclosed to Anthropic. Require a saved, access-scoped Anthropic connection or validate the reference against an approved provider binding before resolving it.
Additional Info
Found by 2 of 4 reviewers.
There was a problem hiding this comment.
Disposition: bounded by the documented fixed-host vs custom-host trust model. The probe route now requires a matching saved, access-scoped hosted-agent manifest for custom Anthropic hosts (URL, agent ID, environment, and credential ref); the only pre-save exception is the fixed https://api.anthropic.com endpoint, where the probe validates the agent ID/key against Anthropic directly. This is the intentional trust boundary documented in the PR body. The matching provider-reference behavior is covered by core-routes-plugin.spec.ts ("matches a saved managed-agent provider reference").
| function isTerminalEvent(event: AnthropicManagedAgentEvent): boolean { | ||
| const type = event.type; | ||
| return ( | ||
| type === "session.status_idle" || | ||
| type === "session.status_terminated" || | ||
| type === "session.error" |
There was a problem hiding this comment.
🔴 Handle documented terminal error and ended events
The stream parser only treats session.status_terminated and session.error as failure events. Anthropic's documented terminal error/ended events are not recognized here, so a failed or ended stream can remain unresolved until timeout or fall through with partial agent.message text as a successful response. Recognize the provider's error/ended event types and map them to a typed failure before accepting output.
Additional Info
Found by 1 of 4 reviewers; independently confirmed against the adapter's terminal-event and error mapping code.
There was a problem hiding this comment.
Disposition: covered by existing code and fixture. The mapper treats the documented session.status_terminated and session.error events as terminal, checks termination before collecting agent.message text, and raises AnthropicManagedAgentsError(code: "failed_state") instead of returning partial output. The fixture test "treats termination after partial text as a failed state" asserts the typed failure and sandbox-crashed message.
| onRuntimeEvent: (event) => { | ||
| const toolCallId = event.toolCallId ?? event.approvalId; | ||
| args.context?.send?.({ |
There was a problem hiding this comment.
🟡 Attach managed approval events to a resumable UI path
The emitted approval frame uses the remote managed tool name and tool-use ID, while the local chat action is the enclosing call-agent invocation. The standard approval renderer cannot associate this frame with a displayed local tool call, and its deny path does not submit a provider confirmation, leaving the Anthropic session paused. Add a managed-session-specific approval/deny continuation that carries the session and tool-use IDs.
Additional Info
Found by 2 of 4 reviewers.
There was a problem hiding this comment.
Disposition: follow-up. This PR reuses the existing approval_required runtime event and carries the provider session/tool IDs in approvalKey and toolCallId; the response also gives the exact call-agent continuation contract. The adapter fixture tests "maps a blocking tool confirmation to input-required and a runtime approval event" and "resumes a paused session with the exact tool confirmation event" cover the provider-side resume. A dedicated renderer association and automatic deny submission for the enclosing call-agent UI remain follow-up work.
|
Follow-up on the review findings:
Local browser verification reached the Connected Agents add and Check flow against a loopback fixture. No live provider credential or external account was used. |
There was a problem hiding this comment.
Builder reviewed your changes and found 1 potential issue 🟡
Review Details
Incremental Code Review Summary
The latest commit preserves the Managed Agents continuation metadata for headless invocation, so the previously reported input-required loss is fixed and its review thread has been resolved. The existing probe credential-scoping, provider terminal-event handling, and managed approval UI concerns remain open and were intentionally not reposted.
The new change is otherwise focused and supported by targeted coverage across the adapter, invocation, call-agent, probing, and route paths. One new lifecycle issue remains: the SSE reader accepts end-of-body/[DONE] without requiring a terminal session event, allowing partial text to be returned as a completed response after a dropped stream.
Risk remains high because this integration crosses an external API lifecycle and credentialed session boundary.
🧪 Browser testing: Full verification was attempted. The dev server was healthy, but all 15 cases were couldnt_verify because browser executor sessions still lacked Chrome navigation/interaction/screenshot tools.
| dataLines.push(line.slice(5).trimStart()); | ||
| } | ||
| } | ||
| if (done) { |
There was a problem hiding this comment.
🟡 Reject truncated SSE streams instead of returning partial success
When the response body ends or emits [DONE], readEventStream returns the events collected so far without requiring a terminal session event. mapSessionEvents then accepts any collected text, so a disconnect after a partial agent.message can be reported as a successful completed response. Require a terminal idle/error/ended event before mapping to success, and surface an explicit stream failure otherwise.
Additional Info
Found by 1 of 4 reviewers; confirmed from the latest adapter control flow.
There was a problem hiding this comment.
Disposition: follow-up. The adapter fixture closes each stream with a terminal session event and the existing mapper fails typed provider terminal errors before accepting text. Handling a transport that ends without any terminal event requires an additional stream-completion contract and is outside this unchanged PR head; it should be added with the dedicated truncated-stream fixture before broadening the adapter.
|
Here's a visual recap of what changed: Open the full interactive recap |
There was a problem hiding this comment.
Builder reviewed your changes and found 1 potential issue 🟡
Review Details
Incremental Code Review Summary
The latest PR head retains the prior changes and the previously open findings remain unresolved, so they were not reposted. The earlier truncation comment and the provider-scope, terminal-event, and managed approval comments remain open as listed by the PR.
The latest review found one genuinely distinct issue in the public headless invocation contract: invokeAgent now returns managed-agent continuation metadata, but its options and follow-up message do not accept or send that continuation. A caller receiving input-required can inspect the session/tool IDs but cannot use the same exported invocation surface to submit allow/deny confirmation; a retry starts a new managed session. Focused tests passed across the adapter, invocation, call-agent, probe, metadata, and connection suites.
Risk remains high because this is a credentialed external session integration with HITL lifecycle semantics.
🧪 Browser testing: Attempted full UI verification; all 7 planned cases were couldnt_verify because Chrome/browser automation tools were unavailable, although the dev server was healthy.
| }, | ||
| ), | ||
| }); | ||
| const result = (await handler( |
There was a problem hiding this comment.
🟡 Provide a continuation input for headless managed-agent invocations
invokeAgent() returns input-required plus the managed session/tool IDs, but InvokeAgentOptions has no continuation or confirmation field and the handler message here contains no managed-agent metadata. A caller cannot submit an allow or deny decision through the public agentNative.invoke/CLI surface; calling it again creates a new Anthropic session instead of resuming the paused one. Add a typed continuation/confirmation input, pass it under the managed-agent metadata key, and cover an allow/deny round trip.
Additional Info
Found by 3 of 4 reviewers; distinct from the previously resolved issue of preserving continuation metadata in the result.
There was a problem hiding this comment.
Disposition: follow-up. This head preserves the managed continuation metadata in AgentInvocationResult and proves provider-side resume in the adapter fixture, but it does not add a public InvokeAgentOptions continuation/confirmation input or an allow/deny round trip through the public invoke/CLI surface. That API extension changes the invocation contract and is outside this PR’s scope; it should be added before claiming public headless managed-agent resume. The existing regression test is invoke.spec.ts "preserves managed-agent approval continuation metadata".
…ne (#5106) Both are main-drift fixes that every fresh PR now trips: 40 localized and English docs pages under packages/core/docs/content landed on main unformatted, so `oxfmt --check` fails the required Lint & format job on any PR whose merge ref includes them, and the docs site (fw) server function measures 34.8MB against a 29.8MB baseline recorded before the Creative Context runtime packages were copied into every server bundle (the same step #5057 re-recorded for eight other sites). Values are the sizes CI measured on PR #5083's fw preview build, rounded up to the byte.
There was a problem hiding this comment.
Builder reviewed your changes and found 3 potential issues 🔴
Review Details
Incremental Code Review Summary
The latest review found three new regressions beyond the six unresolved comments already on the PR. The Managed Agents confirmation payload does not match the provider wire contract, the stream is opened before the turn events are posted (which can deadlock against a provider that waits for the POST), and managed-agent entries carrying an existing card URL cannot be saved from Settings.
The existing findings for probe credential scoping, terminal event names, truncated SSE, managed approval UI, public headless continuation input, and continuation session binding remain unresolved and were not reposted. Focused adapter, invocation, probe, route, metadata, and call-agent tests passed, but the new issues are contract/order and UI-state problems not covered by those fixtures.
Risk remains high because the change controls credentialed external sessions and approval lifecycle behavior.
🧪 Browser testing: Attempted full UI verification; all 11 planned cases were couldnt_verify because Chrome/browser automation tools were unavailable, although the dev server was healthy.
| return { | ||
| type: "user.tool_confirmation", | ||
| tool_use_id: confirmation.toolUseId, | ||
| result: confirmation.result, |
There was a problem hiding this comment.
🔴 Serialize confirmations using the provider wire schema
The adapter sends { type: "user.tool_confirmation", tool_use_id, result: "allow" | "deny" }. Anthropic Managed Agents expects the confirmation payload to carry approved: boolean with the tool-use ID, so every resumed approval can be rejected or ignored by the provider. Map allow/deny to the provider's boolean field and update the fixture to assert the actual wire schema.
Additional Info
Found by 1 of 4 reviewers; targeted fixture currently only asserts the adapter's own payload shape.
There was a problem hiding this comment.
Pushing back on this finding; the existing wire shape matches Anthropic documentation. The permission-policies page says: “Set result to allow or deny. Use deny_message to explain a denial.” The documented event is {"type":"user.tool_confirmation","tool_use_id":"<event id>","result":"allow"|"deny","deny_message"?} and has no approved field. The adapter keeps this shape and the fixture asserts the exact payload. Source: https://platform.claude.com/docs/en/managed-agents/permission-policies#respond-to-confirmation-requests
| ); | ||
| let streamPromise: Promise<AnthropicManagedAgentEvent[]> | undefined; | ||
| try { | ||
| const streamBody = await openEventStream({ ...args, controller }); |
There was a problem hiding this comment.
🔴 Post turn events before awaiting the event stream
runSessionTurn awaits openEventStream() before calling sendEvents(). If the provider holds the stream response open until the turn POST arrives, the POST is never sent and both requests wait until the timeout. Start the stream request without awaiting its response, submit the events, then await/read the stream; add a fixture where stream headers are delayed until after the event POST.
Additional Info
Found by 1 of 4 reviewers; the current control flow confirms the request-order deadlock risk.
There was a problem hiding this comment.
Pushing back on the proposed ordering. The provider event-stream docs state: “Only events emitted after the stream is opened are delivered, so open the stream before sending events to avoid a race condition.” The SDK example opens the stream and then sends the events. The adapter retains connect-then-POST; the race fixture covers delayed headers, and e1eaa87 adds a bounded header-connect timeout that raises typed stream_error when headers never arrive. Source: https://platform.claude.com/docs/en/managed-agents/events-and-streaming
There was a problem hiding this comment.
Builder reviewed your changes and found 2 potential issues 🔴
Review Details
Incremental Code Review Summary
The latest changes successfully address two prior findings: continuation tokens are now caller/agent/environment-bound, and managed-agent entries with legacy card URLs can be edited. Those review threads were resolved. The previously reported probe scoping, terminal-event, truncated-SSE, approval UI, headless continuation-input, confirmation-schema, and stream-ordering comments remain open and were not reposted.
Three new issues were identified:
- 🔴 HIGH — The new continuation-token test mutates an ineffective final base64url character and currently fails, so the focused suite is not green and does not validate tamper rejection.
- 🟡 MEDIUM — 401/403 credential failures during stream/event requests are wrapped as generic stream errors, preventing callers and UI from identifying invalid credentials.
- 🟢 LOW — The managed-agent Check control uses a hard-coded English label instead of localization.
Risk remains high due to credentialed external session and approval lifecycle behavior.
🧪 Browser testing: Attempted full UI verification; all 14 planned cases were couldnt_verify because Chrome/browser automation tools were unavailable, although the dev server was healthy.
| ANTHROPIC_MANAGED_AGENTS_METADATA_KEY | ||
| ] as Record<string, unknown>; | ||
| const token = String(metadata.continuationToken); | ||
| const tamperedToken = `${token.slice(0, -1)}${token.endsWith("a") ? "b" : "a"}`; |
There was a problem hiding this comment.
🔴 Use an effective mutation for the continuation-token tamper test
The test changes only the final base64url character, which may alter unused trailing bits without changing the decoded signature bytes. The focused suite currently still accepts the token and fails this assertion, so CI is red and the test does not validate signature tampering. Mutate a non-final signature character or flip a decoded byte before re-encoding the token.
Additional Info
Found by 1 of 4 reviewers; reviewer reported 156 passing and 1 failing test.
There was a problem hiding this comment.
Fixed in 05f5535. The tamper fixture now mutates a non-trailing character in the decoded signature segment, so the signed bytes always change and verification rejects it. The focused Anthropic Managed Agents suite passes 11 tests.
| streamPromise = readEventStream(streamBody, controller.signal); | ||
| await sendEvents({ ...args, controller }); | ||
| return await streamPromise; | ||
| } catch (cause) { |
There was a problem hiding this comment.
🟡 Preserve credential rejection errors from stream requests
openEventStream and sendEvents can raise the credential-specific 401/403 error, but this catch only rethrows AnthropicManagedAgentsError and wraps other errors as stream_error. Callers such as call-agent cannot distinguish invalid credentials from a transport failure, so the UI loses its credential-specific status. Preserve or translate the credential rejection before applying the generic stream wrapper.
Additional Info
Found by 1 of 4 reviewers; independently confirmed from the catch branch.
There was a problem hiding this comment.
Fixed in 05f5535. runSessionTurn now rethrows RemoteAgentCredentialRejectedError from stream connect/send paths instead of wrapping it as stream_error, preserving the credential-specific status for call-agent and the UI. Added the fixture assertion for a 401 stream response; the focused suite passes.

What changed
Agent-Native Connected Agents now supports Anthropic Managed Agents as a native hosted-agent target. The existing remote-agent resource and settings picker can select either A2A (Foundry, Gemini Enterprise, or another A2A server) or Anthropic Managed Agents. Chat and headless invocation keep one A2A-shaped call surface while Anthropic owns the managed session, context, and tools. Headless results preserve
input-requiredstate and the opaque continuation token plus pending tool IDs needed for continuation.Anthropic Managed Agents sessions are created with the beta API, user messages and tool confirmations are sent as session events, and the event stream is mapped to an A2A response. A blocking tool confirmation becomes
input-requiredand emits the existing approval-request runtime event; continuation requires an explicit allow/deny confirmation for the exact tool-use id. The hosted-agent Check action validates the configured agent id and API key withGET /v1/agents/{agent_id}without creating a session. The default Anthropic API host can be checked before saving; custom hosts require an existing scoped hosted-agent connection.The direct Connect AI panel and its existing provider picker remain unchanged. This PR adds Anthropic Managed Agents only as a target in Settings > Connected Agents, alongside the existing A2A connection flow.
Validation
@agent-native/docsbaseline fixes into main; this PR adds no docs formatting or baseline edits.pnpm --filter @agent-native/core exec tsc --noEmit --pretty falsepnpm guard:no-silent-coercionpnpm guard:i18n-changed-copypnpm guard:eject-manifestspnpm guard:no-untracked-importspnpm exec oxfmtandgit diff --checkpnpm guardsis blocked by pre-existing repository baseline/environment findings: missing built@agent-native/recap-cli, 436 existing i18n debt entries, and the plan-skills/plan-marketplace checks that import that missing build. No new changed-code guard violation remains.Verification boundary
No live Anthropic, Foundry, or Gemini project was available, so provider verification uses the local HTTP fixtures only. The local browser flow was exercised for the Connected Agents configuration states; no live provider credential was entered.
Known boundaries
approvalKey, but no UI approval record consumes it yet; binding that key to a UI approval record is a follow-up.A2AHandlerResult.artifacts, so provider-produced files are not surfaced through this adapter.a2a/invoke.tsandscripts/call-agent.ts; a shared helper is a follow-up.Trust model
The manifest stores only a vault credential reference. Runtime resolution is scoped to the caller and organization. Connected agent resources are shared through the existing resource access controls. Credentialed probes to custom hosts replay only a saved, access-scoped hosted-agent connection; the default Anthropic API host is fixed to the provider and can be checked before saving. Any member authorized to save a remote-agent manifest can currently bind an organization vault reference to an HTTPS URL, matching the existing trust model for credentialed connections; stronger provider binding is a follow-up. The adapter does not yet call
resolveWorkspaceConnectionForAppdirectly because the A2A handler invocation has no app-id/grant context; the provider catalog entry is present for workspace connection setup and future grant-aware resolution.