@tangle-network/sandbox-cli
v0.22.124
Published
CLI for Tangle Sandbox operations
Readme
@tangle-network/sandbox-cli
CLI for provisioning and operating Tangle sandboxes.
Status
Published to the public npm registry as @tangle-network/sandbox-cli.
- Binary:
tangle - Package path:
products/sandbox/cli - Auth: browser login (default), device-code (
--no-browser), and API key - Test coverage: unit and integration tests run via
pnpm --filter @tangle-network/sandbox-cli test
See Limitations for open gaps.
Install
One-liner (installs the CLI and a private runtime selected from its engines metadata):
curl -fsSL https://sandbox.tangle.tools/install.sh | shRun without installing via npx:
npx @tangle-network/sandbox-cli --help
npx @tangle-network/sandbox-cli sandbox list
# or the shorter official alias
npx tangle-sandbox --helpNever run bare npx tangle (or npx tangle-cli).
Those npm names belong to unrelated third-party packages and would execute someone else's code.
The official names are @tangle-network/sandbox-cli and its forwarding alias tangle-sandbox.
Install globally to expose the short tangle binary on PATH:
npm install -g @tangle-network/sandbox-cli
# or
pnpm add -g @tangle-network/sandbox-cli
tangle --help
tangle sandbox listBuild from this monorepo:
pnpm --filter @tangle-network/sandbox-cli build
node products/sandbox/cli/bin/tangle.js --helpGraph factories
Save a graph in a repository's .tangle/graphs/<name>.yaml or in
~/.tangle/graphs/<name>.yaml. Repository graphs take precedence. The packaged
haiku-review.yaml contains a writer, an independent
critic and a declared completion check.
tangle graph new "Write and independently judge a haiku" --name haiku-review
tangle graph run haiku-review --dry-run
tangle graph run haiku-review
tangle --json graph run haiku-review --cloud
tangle --json graph inspect <run-id>
tangle graph run haiku-review --resume <run-id>Graphs use the selected harness's native subscription login. Set seat in
~/.tangle/config.json to keep a default; --seat overrides it for every node.
A graph with no integration needs requires no Tangle login. Before starting
agents, an interactive run connects missing needs in the browser and resumes
the same run. It uses the existing Tangle login when authentication is needed.
Validation, --json and noninteractive runs report missing providers with the
exact tangle hub connect <provider> command. Ctrl-C cancels login, connection
polling and pending Hub requests.
Local graph gates accept --param gate.<id>=<choice> or ask in the terminal.
Supplied choices are validated before execution and recorded with the run;
resume preserves them and Runtime replays completed decisions. JSON and
noninteractive runs require an explicit answer. The gate. prefix is reserved
for these CLI controls.
Progress goes to stderr; --json returns the result or the shared error contract
on stdout. Local run journals live under ~/.tangle/runs/<run-id>, and local graph history
lives in ~/.tangle/graph-registry. Unknown token and cost totals remain null.
To learn from retained runs, admit related tasks under the same independent unit and keep each unit in one partition. Repeating ingestion updates that run's checkpoint without adding another example. Unavailable runs remain visible with evidence gaps; lifecycle completion never becomes a quality label.
tangle --json graph dataset ./runs <run-id> --domain software --unit repository-a --split train
tangle --json graph dataset ./runs <another-run-id> --domain software --unit repository-b --split dev
tangle --json graph dataset ./runs <heldout-run-id> --domain software --unit repository-c --split test
tangle --json graph improve ./delivery.graph.yaml ./runs --suite ./software-evaluation.json --campaign ./campaign
tangle --json graph activate ./campaign ./delivery.graph.yaml
tangle --json graph observe ./campaign <subsequent-run-id> --unit new-production-taskFor local tasks that depend on repository files, first capture one explicit Git root while its other writers are idle. The capture uses Runtime's maintained archive and limits. It retains HEAD, the index, tracked files and unignored untracked files in private local artifacts. Keep the reference outside the repository:
tangle --json graph workspace capture ./repository --output ./task-workspace.json
tangle --json graph run ./factory.graph.yaml --task-workspace ./task-workspace.json --param task=exampleDataset ingestion retains that exact workspace reference, and learning materializes it into every fresh candidate's private directory. Retries reuse the same allocation and preserve agent edits; a standalone dry-run releases only its own fresh validation copy. Missing or corrupt archive bytes refuse fresh replay; capture never executes repository scripts. Environment variables, installed tools and Git-ignored files are not snapshotted. Tracked files and Git history may contain private data; these artifacts remain local, and cloud dispatch refuses these local-only references. Legacy runs without captures retain a task-workspace-not-snapshotted gap rather than acquiring an unrelated current workspace.
Datasets retain exact trigger payloads and captured MCP host policies. Legacy manifests that omitted trigger context must be reingested before learning. dispatch, improve and repair accept --mcp-policy and private --mcp-secret key=ENVIRONMENT_VARIABLE bindings; secret values and environment variable names are not campaign identities.
Each graph, including the proposer, reserves its complete declared ceiling through Runtime's budget pool and reconciles only its matching retained settlement. Restart verifies every previously hashed settlement and replays its actual spend through the same owner pool, preserving cache and dollar completeness; missing or inconsistent historical receipts hold. An interrupted reservation keeps the exact run ID, input and compiled graph during the owner's resume attempt. An unavailable native attachment or unknown usage cannot authorize replacement work or a new comparison. Eval may close an interrupted proposal as failed even when its native evidence is recovered. Recovered final-test cells with incomplete end-to-end latency hold the operational comparison instead of appearing artificially fast. Supplied benchmark arms use content-addressed source snapshots throughout a campaign.
Activation locks the canonical target across campaigns and rechecks its bytes after evaluator calibration and immediately before replacement. Atomic rename does not provide a filesystem compare-and-swap against unrelated editors. Activation also recompiles each measured case with ordinary execution settings and refuses a seat override or changed default that would not carry into an ordinary run. It freezes measured harness/model/seat/connection selections and MCP policy from retained compiled graph records. Observations verify those frozen host selections without consulting today's defaults, while allowing new task inputs through the same authored source. Missing execution records, historical activation records without this binding, and previously unmeasured selections refuse observation. Observations must start after activation, exclude every campaign execution, and identify a new production task unit with inputs distinct from the learning cohort and earlier observations. Repeating the same observation returns its retained verdict; another run cannot count that unit or task again.
The evaluation suite is JSON with version: 1, domain, the inline-prompt
node to improve, objective, minimumEffect and reps. Its evaluator
declares an absolute executable command, args, files (every script, rubric and data dependency)
and timeoutMs. The command reads one JSON request from stdin and emits exactly
{"quality": 0.0, "notes": "independent outcome evidence"} on stdout. Requests
contain scenario, retained evidence, and the candidate artifact, including
its native run ID, output, measured usage and duration. The evaluator runs outside
the candidate's workspace. Include calibration entries with a request and
independently known quality of zero or one; both classes must pass before
inference begins. A failed evaluator is an unmeasured cell, never a quality zero.
Evaluator commands follow the operator's subscription-only spending policy.
The suite's budget requires total maxTokens, maxIterations, deadlineMs,
maxExpansions, plus proposalTokens, proposalIterations and
proposalDeadlineMs. The graph also declares finite per-run budgets. Runtime's
existing reflective proposer generates detached prompt candidates, and its search
owns train/selection/final-test partitions, paired decisions and the closed
ledger. A single conserved Runtime pool bounds native candidate and proposal
calls; interruption retains the pending ceiling conservatively. Reusing a
finished campaign returns its exact retained result. Subscription-only execution forbids provider billing; an observed billed charge holds further work. Operating costs remain explicitly unknown.
The suite also requires operatingConstraints.maxMeanTokens and
operatingConstraints.maxMeanDurationMs. Only candidates with a fully matched
final-test cohort, known reported tokens, and measured end-to-end cell latency
including queueing can enter Eval's quality/token/latency frontier. Activation
requires those ceilings as well as the independent quality decision. Setting
operatingConstraints.requireKnownOperatingCost to true holds while environment
and subscription allocation costs are unknown; the report does not claim a
dollar frontier. Evaluator bytes are checked before and after every score and
again before the decision is retained.
Activation requires Runtime's independent final-test promotion, unchanged
evaluator dependencies, the exact measured candidate bytes and the original
baseline. A hold leaves the graph unchanged. Subsequent runs are graded into
separate observation records. The selected change is one authored inline
prompt.systemPrompt, otherwise prompt.appendSystemPrompt, compiled separately
for each task. An unchanged candidate preserves the original source bytes.
Full-profile, structure, code and Knowledge candidate generation stay with their
existing owners and are not activated by this command.
The optional suite optimizer defaults to { "kind": "native" }. Set kind to
gepa and choose recipe.kind: "engine", with recipe.run.maxEvaluations, or
recipe.kind: "omni", with recipe.explore runs and recipe.continueWith.
Both use Runtime's official GEPA implementation, the same retained proposal runs,
independent grader and campaign pool. Supply the request, response, candidate,
evidence and model-request bounds declared in the maintained optimizer schema.
Campaign work is serialized by the retained-attempt contract; larger engine/Omni worker counts refuse.
GEPA resume requires an explicit compatible-state policy and trustLocalState;
Omni requires resume: "never". These adapters accept ordered text reflection
requests through an explicit native JSON-content protocol. They refuse tools,
images and unsupported model controls before dispatch. The legacy js-issues replay remains available
without --suite; it matches seats across arms and accepts total replay
--max-tokens, --max-iterations and --deadline-ms ceilings.
Native graph nodes can use Knowledge through Runtime's existing authenticated
coordination MCP. Declare only the tools the node needs in profile.tools:
agent_runtime_coordination_knowledge_search, ..._knowledge_read,
..._knowledge_record, and ..._knowledge_resolve. Declare a native graph input
knowledge: { default: null } to make the binding optional. Only null or an omitted binding disables Knowledge; every other value must pass the existing strict local/Hub binding schema. The local binding
comes from tangle --json graph knowledge bind ./knowledge-store; supply that
JSON as --param knowledge=<JSON>. It pins the curated store's exact hash, reads
that view, and writes proposed changes only to private run stores. A changed
curated hash refuses retrieval rather than silently selecting new knowledge.
Hosted bindings use {"kind":"hub","references":[{"sourceId":"...","generation":"kg_..."}]}.
The workflow owner materializes those exact references for the admitted native
node's sandbox and uses Knowledge's existing live credential, attachment and
connection checks. Each returned lexical retrieval retains a Knowledge receipt,
canonical visibility, source identities, generation, query and trusted Runtime
run/node/profile identity before delivering the result. Hosted vector retrieval
with graph receipts refuses before embedding calls until that spending has an
enforced budget. Hosted knowledge_record retains an isolated, unevaluated
proposal artifact and leaves published generations unchanged.
An independently measured local candidate reference from the maintained Knowledge
improvement API can be applied with graph knowledge promote <binding.json>
<candidate.json> or restored with graph knowledge restore. Knowledge verifies
the exact baseline, candidate and evaluation identities and performs its durable
transaction. The command returns the next exact binding. Hub generation
activation through this file-backed candidate API is explicitly unsupported;
Hub source publication remains with its existing owner. Retrieval and a proposed
write do not establish an improvement.
--cloud saves the same source through Hub Workflows and returns its run ID
immediately. Later runs reuse that named deployment without changing its triggers.
Changed source or bindings require graph apply first. The Workflow API owns
execution, retained evidence and recovery; closing the CLI leaves the cloud run
running. inspect reads that same run and prints the shared workflow viewer URL
outside JSON mode.
Publish without starting a run, then run the exact deployed revision:
tangle graph apply haiku-review --deploy ./deploy.yaml --dry-run --json
tangle graph apply haiku-review --deploy ./deploy.yaml --json
tangle graph run haiku-review --workflow <workflow-id> --json
tangle workflows get <workflow-id>Deployment settings use the existing Hub Workflow graph deployment contract,
separate from the portable graph. --param and --seat override the file's
defaults. The platform validates trigger availability, connections and schedules.
For example:
name: weekly-audit
enabled: true
on:
- schedule:
cron: "0 8 * * 1"
timezone: America/Los_Angeles
- webhook: {}A provider event can instead declare
provider_event: { connection: github, event: issues, actions: [opened], repo: example/project }.
The graph must consume that event's trigger data; declaring a subscription does
not map an event to a static graph parameter. Provider action tools do not imply
that a deployment can receive its events.
An API key's expiry also bounds any schedule it authors. Set schedule.until
within that grant, or use an owner credential whose authority covers the intended
schedule. Dry-run reports schedule_outlives_grant before publication when it does
not; a browser login's short-lived CLI key does not grant indefinite recurrence.
apply matches one workflow by name, or --workflow <id> selects it explicitly.
An unchanged resolved graph and deployment create no revision for manual, webhook
or scheduled jobs. Provider-event apply refreshes the owner's ingress bindings,
including after a reconnect. First publication reserves
one paused, manual graph atomically, then applies the requested revision and its
triggers. A failed first update leaves that reservation paused and retryable. Concurrent
different updates conflict instead of overwriting one another. Use distinct names
for different deployed jobs. Deployment settings contain references and policy,
never credential values.
After configuring a webhook, explicitly mint its bearer with
tangle graph webhook <workflow-id> --rotate. The JSON response reveals the new
bearer once and retires the previous one; keep it in the receiver's secret store.
It is not written into graph source or deployment settings.
For local recurring work, an OS scheduler or webhook receiver owns the trigger and calls the same durable local executor:
tangle graph dispatch weekly-audit --event ./event.json --delivery schedule:2026-11-16T08:00:00-08:00 --jsonThe event file is a JSON object visible as trigger in the graph. Use the same
delivery id for retries and the nominal scheduled occurrence, not the retry time.
The private ~/.tangle/deliveries record binds that delivery to one Runtime run,
source, workspace, event and parameters. Retries resume that run. A completed
result replays without starting another native execution. Changed event or graph
bytes refuse the reused id; concurrent delivery attempts refuse while the owner
holds the delivery lock. A failed terminal run requires inspection and an explicit
new delivery. Local dispatch does not install a daemon or expose a public webhook;
the scheduler/receiver must remain running and manage provider authentication.
--resume restores saved inputs and rejects changed source, parameters, seat
settings or placement. A completed winner returns its retained result without
new native effects. A healthy cloud run attaches by reading its current state;
only the server decides whether interrupted work can recover. Failed or
cancelled terminal runs refuse continuation.
For a cited analysis of retained graph evidence, install the maintained Braid companion
with npm install --global @tangle-network/braid, then use an existing canonical
AgentProfile reference:
tangle graph analyze <run-id> --profile ./profiles/reviewer.json --dry-run
tangle graph analyze <run-id> --profile ./profiles/reviewer.jsonHere --profile selects the analysis agent, distinct from a Tangle account
profile. Analysis uses the CLI-owned subscription-only Bridge. Braid owns its
saved analysis and encrypted state; the result includes its state path. Missing
native capture or usage remains explicitly unavailable.
Supervisor accounting
tangle supervise reports measured tokens and cost for successful and failed runs.
A run without a winner can still incur cost.
Unknown totals display as Unavailable and appear as null in JSON output.
Workflow inputs
Use tangle workflows run <id> --input-file ~/.tangle/params/code-fix.json --wait
to run a stored preset. The file is a flat JSON object of string values, such as
{"repo":"o/r","base":"develop","change":"fix the parser"}. Repeatable
--input key=value flags override matching file keys. Invalid files fail before
authentication or run admission; --json errors include code, cause, and fix.
Authentication
Three flows are supported:
- Browser login (default):
tangle auth loginopens a browser to complete OAuth withgithub,google, ormicrosoftidentity providers. - Device code:
tangle auth login --no-browserfor headless environments. - API key:
tangle auth login --api-key sk-tan-...(or setTANGLE_API_KEY).
Credential lookup precedence:
--api-keyflagTANGLE_API_KEY_FILEmanaged credential fileTANGLE_API_KEYenvironment variable~/.tangle/credentials.json(populated bytangle auth login)
The sidecar publishes refreshed delegated credentials through TANGLE_API_KEY_FILE.
The CLI reads this file for each request, including requests from an existing client.
A configured file that is missing, unreadable, or malformed fails without falling back to an older key.
A verified Sandbox request can install a new source credential; an expired source cannot renew itself unattended.
Common commands:
tangle auth login
tangle auth status
tangle auth logout
tangle auth profilestangle auth login validates the supplied credential against /v1/account/me, which requires a valid token.
The two surfaces
Commands address one of two services, and the two accept different credentials.
| Surface | Command families | Credential |
|---|---|---|
| Sandbox API | sandbox, exec, ssh, fs, process, snapshot, secret, agent, fleet, batch, usage | the session tangle auth login stores, or an sk-tan-* key |
| Platform control plane (id.tangle.tools) | keys, workflows, hub | an sk-tan-* key only |
The platform does not accept the sandbox session: it belongs to a different audience. After a browser login the CLI therefore mints a short-lived platform key from that session on the first platform command, caches it in the same credential store, and replaces it before it expires. Nothing durable is written for a surface you never use, and no long-lived key sits on disk.
An sk-tan-* key is used exactly as supplied on both surfaces, whether it
arrives through --api-key, TANGLE_API_KEY, or tangle auth login --api-key,
so a spawned sandbox and a CI job keep the credential they were given and no
mint replaces it. TANGLE_HUB_CAPABILITY_TOKEN serves the hub commands only.
What the minted credential may do
The minted key lives for minutes, so the platform refuses key creation and revocation through it: a credential that short must not add or destroy authority that outlasts it.
tangle keys listworks aftertangle auth login.tangle keys createandtangle keys revokeneed a key you minted yourself: setTANGLE_API_KEY, runtangle auth login --api-key sk-tan-…, or mint the key in the web console.
Everything else on the control plane — tangle workflows, tangle hub — works
from the login alone.
tangle auth status reports both surfaces. It exits non-zero when either one
REFUSES the stored credential — a behaviour change for a script that gated on
the old sandbox-only check, and the point of it: the platform rejecting the
credential used to be reported as success. An unreachable platform is a
different answer and exits zero, because an outage says nothing about the
credential; read platform.reachable in --json to gate platform work.
--base-url names the sandbox host for BOTH halves: the session is checked
there, and the platform credential is minted there, so the two lines of the
answer always describe one deployment.
Command Surface
Top-level command groups:
authsandboxsecretexecsshagentfleetsnapshotusagebackendbatchprocessfs
Examples:
# auth
tangle auth login --api-key sk_...
# sandbox lifecycle
tangle sandbox create --name my-box --environment node:20 --ssh
tangle sandbox list
tangle sandbox get sbx_123
tangle sandbox stop sbx_123
tangle sandbox resume sbx_123
tangle sandbox delete sbx_123
# execution
tangle exec sbx_123 "npm test"
tangle ssh sbx_123
tangle agent prompt sbx_123 "Summarize this repo"
tangle agent prompt sbx_123 "Fix the failing tests"
tangle fleet create --count 4 --coordinator
# grant hub connections to the agent (see hub-reference.md for the 3 modes).
# the connection is an id or a provider name (resolved via `tangle hub connections`).
tangle agent prompt sbx_123 "triage issues" \
--connection github:github.issues.search,github.issues.create
tangle agent prompt sbx_123 "file the report" --connection github:* --allow-writes
# temporary GPU for an eval; omitted provider picks the cheapest configured cloud
tangle sandbox gpu run sbx_123 \
--accelerator-kind nvidia-rtx-3090 \
--accelerator-memory 24 \
--max-spend-usd 1 \
--max-lifetime 900 \
--idle-timeout 120 \
-- python eval.py
# state and operations
tangle secret list
tangle snapshot list sbx_123
tangle process list sbx_123
tangle fs ls sbx_123 /workspaceHub commands (tangle hub …) and tangle agent --connection auto-mint a
short-lived platform Hub key from your tangle auth login session, with no separate
key needed. See hub-reference.md for the --connection
grant modes, --allow-writes, and permissions revert-writes.
Temporary GPUs
Keep the base sandbox cheap and attach a GPU only while the accelerated command runs.
Omit --provider to let Tangle choose the cheapest configured GPU cloud.
Always set a spend cap and lifetime.
# Attach, run one command, and destroy the lease.
tangle sandbox gpu run sbx_123 \
--accelerator-kind nvidia-rtx-3090 \
--accelerator-memory 24 \
--max-spend-usd 1 \
--max-lifetime 900 \
--idle-timeout 120 \
-- python eval.pyFor multiple GPU commands, use the manual lifecycle:
tangle sandbox gpu attach sbx_123 \
--accelerator-kind nvidia-rtx-3090 \
--accelerator-memory 24 \
--max-spend-usd 1 \
--max-lifetime 900 \
--idle-timeout 120
tangle sandbox gpu list sbx_123
tangle sandbox gpu exec sbx_123 gpu_abc -- python eval.py
tangle sandbox gpu detach sbx_123 gpu_abcCreate-time GPU flags are a shortcut over the same lease lifecycle:
tangle sandbox create --name gpu-eval \
--accelerator-kind nvidia-rtx-3090 \
--accelerator-memory 24 \
--gpu-max-spend-usd 1 \
--gpu-max-lifetime 900 \
--gpu-idle-timeout 120The final detach output includes billed seconds and customer cost.
Use tangle usage to inspect account-level GPU seconds and GPU spend.
Batch Runs
Run each task against one explicitly named backend:
tangle batch run --tasks tasks.json --backend primary=opencodetasks.json is an array of { "id": "...", "message": "..." } objects or an object with a tasks array.
At least one --backend <id=type> is required with --tasks or --task.
Repeat --backend <id=type> to run every task against multiple backends:
tangle batch run --tasks tasks.json \
--backend writer=opencode \
--backend reviewer=claude-code \
--streamBackend IDs must be unique and use 1 to 128 letters, numbers, dots, underscores, or hyphens.
Use --model provider/model only when one backend is selected.
Each result is identified by both taskId and backendId.
JSON output reports totalTasks, totalBackends, totalExecutions, totalSuccess, totalFailure, totalRetries, and successRate.
Use --request <file.json> when each backend needs its own model, profile, server, or lifecycle configuration.
The file uses the same complete request shape as the TypeScript SDK and cannot be combined with inline task or backend flags.
Provisioning Coverage
tangle sandbox create supports environments or container images, resources, temporary GPU leases, lifecycle limits, runtime (--runtime docker|firecracker), driver and backend selection, SSH and web terminal, environment variables, secrets, metadata, git clone, BYOS3 storage, and snapshot restore.
Use the sandbox network and sandbox expose commands after creation.
Run tangle sandbox create --help for the current flag list.
Limitations
snapshot restorecreates a new sandbox from a snapshot; the command signature suggests in-place restore.
Compliance posture is not a flag
No CLI command takes a --compliance / HIPAA / PHI option, by design: a
caller-asserted flag could dodge the platform's observability gates or
claim a protection it never granted. The platform derives the posture from
the credential's verified attribution and pins it server-side. Reads
(sandbox list, sandbox get) surface it as the read-only compliance:
{ hipaa: true } echo field — absent means the platform made no claim, and
hipaa: false never appears.
A graph deployment can bind an owner Sandbox template or snapshot without changing the graph source:
name: crm-audit
mcpPolicy:
allowedMcpHosts: [crm.example.com]
sandbox:
templateId: my-owner-template
secrets: [CRM_TOKEN]secrets names entries already stored in the owner's Sandbox vault. A profile's MCP secret-ref.key uses that same name; the private sidecar materializer resolves its value. Apply and cloud run never read or upload local environment values. Missing or unselected refs fail before native dispatch. Select publicTemplateId with optional publicTemplateVersionId, or both fromSandboxId and fromSnapshot, instead of templateId. Sandbox checks source ownership and capabilities through its normal creation API. A recovered Runtime checkpoint takes precedence over the deployment's initial source.
An isolated check opts into the same Sandbox binding with environment: deployment; its shell uses the restored /home/agent. Built-in universal and agent-container checks keep their existing placement. Check secrets still use the graph repository credential contract. Custom scripts needing other credentials run through the admitted native Sandbox or MCP tool path; isolated checks receive no owner vault grants.
