Conversation
Replace the first-party delegate_host.mjs wrapper around @uipath/delegate-sdk with the published @uipath/delegate-stdio host (^1.202.1), which pulls in the SDK and interop runtime itself. - Speak the host's protocol: `event` frames, terminal `result` / `error`, `destroyed`; read toolArgs / toolResult / toolStatus, isStepStart deltas, Anthropic-convention `usage`, and `turnUsages` as the authoritative call count. - Export auth into the host's environment under the names it reads (ORG_SLUG -> ORG_LOGICAL_NAME, TENANT_SLUG -> TENANT_NAME) instead of an `auth` init option; DELEGATE_ENV becomes the `env` option. - Send enableSkills explicitly (the host defaults it off). - Rename DELEGATE_SDK_PATH / DELEGATE_SDK_NODE_MODULES to DELEGATE_STDIO_PATH / DELEGATE_STDIO_NODE_MODULES across code, docs, CI. - Keep max_turns client-side: the host's maxSteps does not stop a turn. - Live-test gate now honours DELEGATE_AUTH_TOKEN. Verified live against alpha: all delegate live tests (saved login and env-token paths) and tasks/delegate/fizzbuzz_delegate.yaml pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The SDK's tool_result carries toolStatus "interrupted" for a tool that did not complete. Only "failed" was treated as an error, so an interrupted tool was recorded with result_status "success". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n conflicts
Port three failure signatures from the out-of-tree delegate-sdk adapter, which
drove this same delegate-stdio host against this same backend:
- A Cloudflare WAF block page (a 403 for shell-like text in the request body)
is rewritten to a "content filter" reason, so it is not retried: the same
payload is blocked again.
- An SSE connect timeout is rewritten to a "connection" reason with "timeout"
defanged, so it is retried as AGENT_API_ERROR instead of ending the task as a
non-retryable AGENT_TIMEOUT.
- A session conflict ("A reply is already being generated") drops the
remembered session id, so the retry starts a new conversation instead of
conflicting again.
A rewritten reason omits the host stderr tail, because a "timeout" in the tail
would undo the categorization; the tail is logged instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…en file is set The agent's shell tools inherit the host env, so the gateway S2S client secret there is readable by the code under test. The host refreshes its token from that pair only when no token file is configured; with DELEGATE_AUTH_TOKEN_FILE (or the older AUTH_TOKEN_FILE) set, the file wins and the pair is unused. Remove LLMGW_CLIENT_ID / LLMGW_CLIENT_SECRET / LLMGW_URL from the host env in that case only, mirroring the host's own lookup, so a long run without a token file still refreshes its token. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ute effort through sdk_options BREAKING CHANGE: the Delegate agent now uses the names that the @uipath/delegate-stdio host reads itself, and passes its environment to the host unchanged. DELEGATE_ENV -> DELEGATE_SDK_ENV, DELEGATE_BACKEND_URL -> BACKEND_URL, ORG_SLUG -> ORG_LOGICAL_NAME, TENANT_SLUG -> TENANT_NAME. The DELEGATE_-prefixed auth spellings (DELEGATE_AUTH_TOKEN, ...) are no longer read; set AUTH_TOKEN / TENANT_ID / ORG_ID. The Delegate-only `effort` field is replaced by `sdk_options.effort`, the key Claude Code already uses, so `-D agent.sdk_options.effort=high` now works for delegate tasks and the reports' Effort row shows it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Delegate turn cut at max_turns or by an early stop never gets the host's result frame, so it lost all its token usage and cost, and the max_usd and max_total_tokens gates had nothing to check. The other harnesses keep the usage of the calls under the cap. The delegate-stdio host now writes one `usage` frame per backend round-trip, before the tool results of that round-trip. The adapter adds up the frames. The result's `usage` is the turn total, so it replaces the sum when it arrives. The max_turns call boundary does not change. The adapter warns when finished model calls report no usage: a completed turn with no usage, or a cut turn from a host that does not send the frame. A zero payload in the known buckets no longer warns as a renamed bucket. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Delegate SDK sends no tool_call for a tool name it cannot resolve, only the failed tool_result. That result carries toolName and echoes the args in toolResult.args, so the synthesized row now takes both from it instead of recording "unknown" with no parameters (29 rows in nightly 13599116). The SDK also starts a new tool while earlier results are still pending, and those results arrive later. A new call no longer force-closes the tools that are still open, so the late results match their own rows instead of becoming "unknown" rows. The call counting is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fixes from the review of nightly 13599116: - No crash reason carries the host's stderr tail now; it is logged at WARNING. The tail holds the sandbox path, so the task id decided the category: "Delegate backend error: terminated" was retried on four tasks and ended skill-review-agents-lowcode-guardrail-unknown-validator as a non-retryable AGENT_INVALID_OUTPUT, because "guardrail" matched. - Only an init error that a retry cannot fix (missing or rejected auth, missing org/tenant slugs, an unknown env) raises AgentConfigError. Other init errors and the 60 s init deadline are retryable. - A stdout drain cancelled at teardown no longer logs "stdout drain failed" at ERROR (396 times on the Linux slice). - get_sdk_options() returns the init options sent to the host. The reports use it in place of agent_config, so the pass-through dict hid the Model row on every run that set an effort. - The install search also looks in agents/delegate/, as the docs say. - _force_kill_host drops the process handle itself, so kill() clears it too, also when the reap times out. HARNESS_PARITY.md no longer describes the out-of-tree delegate-sdk agent as a live agent; its untimed tool records are now a historical note. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…LEGATE_* names again The adapter read the delegate-stdio host's own variable names and passed its environment through unchanged. The bare names collide with other tooling (a BACKEND_URL exported for another service routed the host there), and every agent shell command inherited the token. The adapter now reads coder_eval's names again, as main does: DELEGATE_SDK_PATH, DELEGATE_SDK_NODE_MODULES, DELEGATE_ENV, DELEGATE_BACKEND_URL, and DELEGATE_AUTH_TOKEN / DELEGATE_TENANT_ID / DELEGATE_ORG_ID / DELEGATE_ORG_SLUG / DELEGATE_TENANT_SLUG, each with the bare spelling as a fallback. It sends the auth and the slugs as the host's new `auth` init option, DELEGATE_BACKEND_URL as `backendUrl`, and DELEGATE_ENV as `env`. It removes AUTH_TOKEN, TENANT_ID, ORG_ID, ORG_LOGICAL_NAME, TENANT_NAME, BACKEND_URL and DELEGATE_AUTH_TOKEN from the host's environment. - DELEGATE_SDK_PATH must name delegate-stdio's dist/delegate_stdio.mjs. An old value that names delegate-sdk's dist/index.mjs is an error. - A config-class init error names coder_eval's variables beside the host's message, which names the host's. - get_sdk_options() redacts the credentials: `auth` becomes its field names and `backendUrl` its host, because the run records it. BREAKING CHANGE: needs a @uipath/delegate-stdio release that accepts the `auth` init option; 1.202.1 and older ignore it and fail init with "Auth required" unless a saved login exists. Verified live against alpha with a host built from Autopilot's chore/move-delegate-sdk-2 branch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep each decision and its reason; drop the narration and the repeated detail. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… call Since UiPath/Autopilot#6477 the Delegate SDK loads a catalog skill with LoadSkill {"name", "plugin"} instead of reading its SKILL.md, and the delegate-stdio host passes the name through unaliased. skill_triggered reads only `Skill` + `skill` or a `skills/<name>/` path, so it never saw a Delegate skill load. DelegateAgent now maps LoadSkill {name} to Skill {skill}. The rename is keyed by the host's tool name: the host already reports ExecuteSkillApi as `Skill`, and that call's `name` is an API call, not a skill load. _tool_call's parameters are positional-only so a test can pass a `name=` tool arg. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
PILOT-7854
Warning
Breaking changes. The environment variable names stay as on
main. What changes:main)npm install @uipath/delegate-sdknpm install @uipath/delegate-stdio, a release that accepts theauthinit option (1.202.1 and older ignore it, and init fails withAuth requiredunless a saved login exists)DELEGATE_SDK_PATH→@uipath/delegate-sdk/dist/index.mjsDELEGATE_SDK_PATH→@uipath/delegate-stdio/dist/delegate_stdio.mjs; any other file is anAgentConfigErrorDELEGATE_SDK_NODE_MODULES→ root holding@uipath/delegate-sdk@uipath/delegate-stdioagent.effort(Delegate-only field)agent.sdk_options.effort, the key Claude Code already usesAUTH_TOKEN/TENANT_ID/ORG_ID/ …authinit option on stdinUnchanged:
DELEGATE_ENV,DELEGATE_BACKEND_URL, andDELEGATE_AUTH_TOKEN/DELEGATE_TENANT_ID/DELEGATE_ORG_ID/DELEGATE_ORG_SLUG/DELEGATE_TENANT_SLUG, each with its bare spelling as a fallback.Summary
agents/delegate/delegate_host.mjswith the@uipath/delegate-stdiohost; it installs@uipath/delegate-sdkand the interop runtime itselfDelegateAgentto the host's protocol:eventframes, per-callusageframes, terminalresult/error,destroyedauth,backendUrl,env); remove the host's own names (AUTH_TOKEN,TENANT_ID,ORG_ID,ORG_LOGICAL_NAME,TENANT_NAME,BACKEND_URL) andDELEGATE_AUTH_TOKENfrom its env, so agent shells cannot read the token and a strayBACKEND_URLcannot route the hostmax_turnsor by an early stopChanges
Adapter —
src/coder_eval/agents/delegate_agent.py_resolve_host_bundlereplaces_resolve_sdk_entry:DELEGATE_SDK_PATH,DELEGATE_SDK_NODE_MODULES, then the cwd and its ancestors, this agent's ownagents/delegate/, and home_env(namespaced first, bare fallback) +_auth_optionbuild theauthinit option;_HOST_ENV_REMOVEDlists what leaves the host envtoolArgs/toolResult/toolStatus, Anthropic-conventionusage,result.model;num_turns=len(result.turnUsages)isStepStart: falsedeltas extend one text block;enableSkillsis sent explicitly (the host default isfalse)usageframes; theresult'susage(the turn total) replaces the sumtoolStatus: "interrupted"is an errorAgentConfigError(with a hint that names coder_eval's variables); other init errors and the 60 s init deadline are retryableget_sdk_options()returns the init options withauthreduced to its field names andbackendUrlto its host; a cancelled stdout drain no longer logs at ERROR;_force_kill_hostclears the process handleLLMGW_*from the host env when a token file is setDocs, CI, tests
docs/agents/DELEGATE.md,docs/USER_GUIDE.md,docs/agents/HARNESS_PARITY.md,.env.example,.claude/notes/agents.mdpr-checks.ymldelegate-live-tests: installs@uipath/delegate-stdio, sets theDELEGATE_*names from the existingDELEGATE_*secrets1.202.1transcript; an autouse fixture clears the developer's ownDELEGATE_*valuesImplementation Notes
authfield priority over its env var; the SDK reads none of them. It takes the credentials from theTokenAuthProviderthe host builds, and an explicitbackendUrlreplaces theBACKEND_URLdefault its bundle reads at load. Host side: UiPath/Autopilot#6656. A refresh source (token file,LLMGW_*, saved login) still writes its token into the host's ownprocess.env.AUTH_TOKEN.agents/delegate/package.jsonstill says^1.202.1. Raise it to the first release that carries theusageframe and theauthoption (both in UiPath/Autopilot#6656); until then the live CI job installs a host that ignoresauth.usageframe precedes the tool results of call N. Themax_turnsboundary does not move.max_turnsstays client-side: live,maxSteps: 2ran 7 steps.Testing
test_delegate_agent*.py,test_delegate_agent_config.py,test_error_handling.py: 195 passed, 6 skipped. New tests: theauthoption, namespaced-over-bare, removed host names, redactedsdk_options, theDELEGATE_SDK_PATHfile check, the init-error hint, stderr-tail categorization, init retry, drain cancel,kill()handleDELEGATE_STDIO_VERBOSE=1:PONG, 12,250 tokens, $0.00095; the host log showsUsing init-option / env var auth(not the saved login); the token is in neither the host's stderr norsdk_optionslitellm, anode_modulesin a parent of the temp dir) are environment-only🤖 Generated with Claude Code