fix(providers): require an interface that bounds a prompt as content - #320
Merged
Conversation
`@tangle-network/agent-provider-tangle` is the code that VALIDATES a turn, so the agent-interface copy IT resolves decides what a turn may contain — not the copy the consumer installed at its own top level. It declared `^2.6.1`, which still admits the release whose bug it was shipped to escape. agent-interface 2.7.0 (#314) moved every `prompt` field off the 16 KiB metadata bound (`CONTRACT_MAX_STRING_LENGTH`) onto the 1 MiB content bound, and 2.8.0 (#315) made the contract bounds public. Measured 2026-09-11: a consumer whose lockfile already held 2.6.1 for this package, and which then raised its own top-level agent-interface to 2.8.0, ended up with two copies — agent-runtime on 2.8.0, the provider on 2.6.1. Because the provider validates, children kept dying on the 16 KiB bound with a ZodError at `path: ["prompt"]` on a stack whose package.json said the bug was fixed: 14 of 34 children of one run and 8 of 52 of another, every one at iterations 0, surfaced as "retained provider execution requires reconciliation before replacement". Ranges raised, each decided on whether the package's own validation depends on the change: agent-provider-tangle ^2.6.1 -> ^2.8.0 parses every turn with AgentTurnInputSchema and every interactive command with AgentInteractiveSessionPromptCommandSchema; also imports the bounds 2.8.0 made public. agent-provider-cli-bridge ^2.4.0 -> ^2.7.0 prepareCliBridgeRun parses every turn with AgentTurnInputSchema. Same exposure, and 2.7.0 is where the bound it needs arrived. Left alone, each checked rather than assumed: agent-core ^2.0.0 interaction schemas and stream event types only; the interaction-answer bound did not move in 2.7.0. agent-provider-{e2b,daytona} ^2.4.0 type-only imports; validate no turn. agent-provider-computesdk ^2.4.0 parses WorkspaceRequestSchema only. agent-provider-testkit ^2.4.0 builds short conformance prompts and asserts digest replay, not size; a prompt digest is content-addressed and identical across the bound change. agent-trace-contract no agent-interface dependency. `producer-consumer-bounds.test.ts` (#315) cannot see this split: it reads the shipped Sandbox dist and compares it against contract constants it imports, and the workspace pins `overrides["@tangle-network/agent-interface"]: workspace:*`, so in this repo it always links the workspace copy. `resolved-interface-floor.test.ts` checks the other half — behaviour first, then the versions: - `promptFromTurnInput` accepts a prompt one character over the metadata bound; - `AgentInteractiveSessionPromptCommandSchema` reports no issue at `path: ["prompt"]` for an oversized prompt; - the resolved copy exports the contract bounds `producer-consumer-bounds.test.ts` imports by name — on an older copy that file does not fail, it fails to LOAD; - the agent-interface this package resolves is at or above its declared floor; - the declared floor is at or above the release the validation needs, which is what the workspace override would otherwise hide. It imports agent-interface as a namespace on purpose: a named import of a constant an old resolution does not export fails at link time with a SyntaxError naming no package, no version and no reason, and this file exists to diagnose exactly that resolution. Proven against a deliberately old resolution rather than only asserted. The published provider 1.2.0 installed beside agent-interface 2.6.1, with no override, shares the 2.6.1 copy and refuses a 20,000-character prompt with `too_big maximum 16384 path ["prompt"]`; this branch's tarball, in the same tree, makes npm nest 2.9.0 under the provider and the same prompt is accepted unchanged. The new test file, run against an installed 2.6.1, fails 4 of its 5 cases with those messages, and passes 5 of 5 against the published 2.9.0. Tests, serialized (`--fileParallelism=false`): agent-provider-tangle 285 pass in 20 files, up from 280 in 19; cli-bridge 214; testkit 27; agent-core 506; trace-contract 182; computesdk 6; daytona 4; e2b 4. agent-interface 554 of 555 — `certified-context.test.ts` fails 1 of 12 identically on unmodified origin/main in this worktree. `pnpm -r check-types`, `pnpm -r build`, `pnpm check:package-artifacts` (9 packages, 31 exports, 62 export targets) and `pnpm check:control-artifacts` (183 tests) all pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The defect
@tangle-network/agent-provider-tangleis the code that validates a turn, so the@tangle-network/agent-interfacecopy it resolves decides what a turn may contain — not the copythe consumer installed at its own top level. It declared
^2.6.1, which still admits the releasewhose bug it was shipped to escape.
agent-interface 2.7.0 (#314) moved every
promptfield off the 16 KiB metadata bound(
CONTRACT_MAX_STRING_LENGTH) onto the 1 MiB content bound (boundedEventContentStringSchema),because a manager's brief could not fit and every oversized turn died at zero iterations. 2.8.0
(#315) made the contract bounds public.
Measured 2026-09-11 in the Discovery Lab: a consumer whose lockfile had already resolved 2.6.1 for
the provider, and which then raised its own top-level agent-interface to 2.8.0, ended up with two
copies — agent-runtime on 2.8.0, the provider on 2.6.1. Because the provider validates, children
kept dying on the 16 KiB bound with a ZodError at
path: ["prompt"]on a stack whose package.jsonsaid the bug was fixed: 14 of 34 children of one run and 8 of 52 of another, every one at
iterations 0, surfaced as "retained provider execution requires reconciliation before replacement".
Confirmed by reading the pnpm store links: the provider's own
node_modules/@tangle-network/agent-interfacepointed at 2.6.1 on agent-runtime 0.217.1 +agent-provider-tangle 1.1.11 + agent-interface 2.8.0. The published
latest, 1.2.0, still declares^2.6.1.Ranges changed, and the ones deliberately left
Each package was decided on whether its own validation depends on the 2.7.0/2.8.0 change.
agent-provider-tangle^2.6.1→^2.8.0promptFromTurnInputparses every turn withAgentTurnInputSchema,sendPromptparses every interactive command withAgentInteractiveSessionPromptCommandSchema— both are 2.7.0 sites. It also importsCONTRACT_MAX_STRING_LENGTH,CONTRACT_MAX_IDENTIFIER_LENGTHandCONTRACT_MAX_ARRAY_LENGTH, which only became public in 2.8.0, so the floor is the later release.agent-provider-cli-bridge^2.4.0→^2.7.0prepareCliBridgeRunparses every turn withAgentTurnInputSchema. Same exposure, same failure mode. It imports none of the constants 2.8.0 exported, so its floor is where the bound it needs actually changed.agent-core^2.0.0unchangedinteraction-answer-validation.tsstayed onCONTRACT_MAX_STRING_LENGTHin 2.7.0 by design; nothing it validates moved.agent-provider-e2b,agent-provider-daytona^2.4.0unchangedagent-interface/environment-provider. They validate no turn.agent-provider-computesdk^2.4.0unchangedWorkspaceRequestSchemaonly.agent-provider-testkit^2.4.0unchangedagent-trace-contractMaking the split detectable
packages/agent-provider-tangle/src/producer-consumer-bounds.test.ts(#315) cannot see this. Itreads the shipped Sandbox dist and compares it against contract constants it imports, and
pnpm-workspace.yamlpinsoverrides["@tangle-network/agent-interface"]: workspace:*, so insidethis repo it always links the workspace copy and never sees a consumer's older resolution.
packages/agent-provider-tangle/src/resolved-interface-floor.test.tschecks the other half.Behaviour first, then the versions:
promptFromTurnInputaccepts a prompt one character over the metadata bound — the exact callthat killed the children above.
AgentInteractiveSessionPromptCommandSchemareports no issue atpath: ["prompt"]for anoversized prompt. The command is otherwise invalid on purpose; the assertion is only that its
length is not one of the reasons.
producer-consumer-bounds.test.tsimports byname. On an older copy that file does not fail, it fails to load, which is how the seam it
guards silently stops being checked.
found by walking the
node_moduleschain, because agent-interface'sexportsmap has no./package.jsonsubpath and norequirecondition, sorequire.resolvecannot reach it.for the wrong reason in this repo: the override means a range lowered back to
^2.4.0wouldstill resolve something new enough, and only a consumer would find out.
It imports agent-interface as a namespace on purpose: a named import of a constant an old
resolution does not export fails at link time with a
SyntaxErrornaming no package, no version andno reason, and this file exists to diagnose exactly that resolution.
Proof, not assertion
End to end, with the published artifacts and no override.
npm installing the published@tangle-network/agent-provider-tangle@1.2.0beside@tangle-network/agent-interface@2.6.1lets npmdedupe onto the single 2.6.1 copy, because
^2.6.1admits it. Calling the provider's owndist/tangle-prompt.jswith a 20,000-character prompt:The same package built from this branch, in the same tree, forces npm to nest its own copy:
The new check against a deliberately old resolution. A scratch copy of the package with
@tangle-network/agent-interfacepinned to 2.6.1 while the manifest still declares^2.8.0fails4 of 5 cases, each with its own reason, including the production error verbatim:
Against the published 2.9.0 in the same scratch project: 5 of 5 pass. Lowering the declared range
back to
^2.6.1while resolving 2.9.0 fails only case 5, which is the regression this repo'soverride would otherwise hide.
Checks, run locally
Serialized with
--fileParallelism=false.agent-provider-tangle(vitest run src)agent-provider-cli-bridgeagent-provider-testkitagent-coreagent-trace-contractagent-provider-computesdk/daytona/e2bagent-interfacesrc/certified-context.test.tsfails 1 of 12 identically on unmodifiedorigin/mainin this worktree, checked by stashing and rerunningpnpm -r build,pnpm -r check-typespnpm check:package-artifactspnpm check:control-artifactspnpm install --frozen-lockfilepnpm-lock.yamlunchanged (the workspace override recordsworkspace:*, so raising a published range does not move it)Release
Through changesets, not by hand:
.changeset/provider-requires-content-bound-prompt.mdmarks bothproviders
patch.changeset status --since=origin/mainconfirms@tangle-network/agent-provider-tangle1.2.0 → 1.2.1 and@tangle-network/agent-provider-cli-bridge1.0.2 → 1.0.3. The Release workflow versions andpublishes on merge; nothing version-bearing is edited in this PR.
🤖 Generated with Claude Code