feat(provider-tangle): derive retained control from deployment capability discovery - #165
Merged
Merged
Conversation
…lity discovery Composing an environment reads the deployment's capability document once and derives the retained-control claim from it, instead of from the linked Sandbox SDK's method surface. The SDK probe measured the SDK class, which says nothing about what the connected service honors. retainedControl needs dispatch.runControlRef with cancel.canonicalRunCancellation, cancel.digestBound and cancel.idempotent. streaming.detach needs dispatch.runControlRef alone, because detached dispatch carries the caller's exact reference and refuses a receipt that does not echo it. A missing flag is unknown, and unknown is never a claim. Four inputs claim nothing: an SDK older than 0.22.0, a sandbox that is not running, a null document, and a document that leaves a required flag unset. Those environments omit dispatch and their sessions omit cancelRun. A malformed document throws, and create() deletes the sandbox it just made. The client stage keeps its adapter-surface probe as an upper bound, because no deployment exists to ask before a sandbox is created. The adapter surface stays the ceiling: a deployment claim can only narrow it. The peer floor stays at 0.19.6. Capability discovery is feature-detected, so a consumer on an older SDK degrades to no retained control instead of failing to load.
A capability the connected deployment decides cannot be stated before an environment exists, because one provider reaches deployments of different ages. The provider document said a run could detach while the environment it created omitted dispatch, so runAgentEnvironmentProviderConformance failed with "streaming.detach requires dispatch()" for an SDK-backed client whose deployment returns null. AgentEnvironment gains an optional capabilities document. The Tangle provider publishes the narrowed document there, frozen, and derives the session grant from it, so the operations an environment exposes and the document a caller reads are the same fact. The conformance suites bind every environment-scoped check to that document when an environment publishes one, and to the provider document otherwise. The provider document stays the adapter's ceiling. agent-runtime asserts the retained-control block from provider.capabilities() before create, so a fail-closed provider document would refuse retained runs against every deployment, including the ones that back them. Every flag the capability document carries now gates the claims it backs. streaming.detach and streaming.turnIdempotency need dispatch.runControlRef with dispatch.executionIdOnAdmission; streaming.replay needs runs.eventReplay; retainedControl needs those with the three cancel flags and runs.executionScopedStatus. Detach also needs a session handle, because a detached run is reachable only through one. A failed capability read no longer fails create() and no longer deletes a sandbox that a cold provision has just paid for. It claims nothing and reports the failure on the warning channel. The caller's own abort still propagates.
…aborted read The two capability stages had no asserted relation. Every exposure check binds to the document an environment publishes, which leaves the provider document unchecked against it: an environment could claim what the client stage does not carry, or drop a claim the client stage advertises, and no test failed. Two mutations proved the gap, each of them green on the whole suite. The client-stage and sandbox-stage documents are now paired over both answers a deployment can give to an SDK-backed client. The sandbox stage claims nothing beyond the client stage, the two agree on every claim the deployment does not decide, and the exposed operations follow the sandbox-stage document. Equality across the stages stays unasserted: the client stage runs before any sandbox exists, so a deployment that discloses nothing would drag the provider document down and refuse retained runs against every deployment. A read that fails while the caller's abort lands now propagates the abort at the capability boundary and reaches no warning channel. The provider re-checks the signal after composition, so that boundary was unpinned. The README flag table stated part of what four flags gate. Every flag in it gates sessions.continue, retainedControl, and session.cancelRun. The README and the changeset also counted a document that leaves a flag unset among the inputs that claim nothing; such a document drops the claims that flag gates and keeps the rest.
tangletools
approved these changes
Aug 13, 2026
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — b3106e14
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
This approval is provisional. It rests on the audit running. If the audit cannot run — for example the CLI bridge rejects it — this approval is dismissed rather than left standing, so an unrun check never reads as a passing one.
tangletools · auto-approval · reason: drewstone_author · 2026-08-13T21:21:18Z
Merged
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.
Problem
The Tangle provider decided its capabilities by measuring itself — it minted a throwaway
SandboxInstanceand checked whether the npm package it imported hadcancelRun. The code's own comment admitted the limit: deployment truth "needs the sidecar capability endpoint and is a follow-up."So a modern SDK talking to an older sidecar advertised retained control, the runtime accepted it, a real sandbox got created, and only then did it fail (#154).
Change
The provider now asks the deployment. Composing an environment reads
box.capabilities()once (published in@tangle-network/sandbox@0.22.0) and derives the claim from that document:retainedControlrequiresdispatch.runControlRefand all threecancel.*flags; every one of the seven flags is consumed, and forcing any of them true reds the suite.nulldocument, or a failed read all yield no claim, so the runtime refuses before a sandbox is spent.create(). An earlier revision destroyed a freshly created sandbox on a transient 500; that cost 25–40s of cold provisioning and is now impossible.AgentEnvironmentgains an optionalreadonly capabilities?— the document for that environment. One provider reaches deployments of different ages, so a provider-level document can only state what holds before an environment exists. This is the contract fix for the original complaint thatprovider.capabilities()was the only document a consumer could read.Verification
Two adversarial review rounds; both are reflected here.
runAgentEnvironmentProviderConformancepasses against an SDK-backed client whose deployment returnsnull(dispatchundefined ⇔streaming.detachfalse).pnpm -r check-types;check:package-artifacts(9 packages / 26 exports / 52 targets);check:control-artifacts129/129 — the last builds and packs the provider, so the fix is proven through the published artifact.Scope
Retained-control half only. The interaction half stays unclaimed and is blocked upstream on tangle-network/agent-dev-container#5421 (the SDK cannot aim an answer at a specific interaction, and discards the server's resolution verdict) — shipping it would deliver wrong answers to running agents.
Refs #154