Changelog
Patch Changes
-
f987d00: Credential injection: sandboxes can now register credentials that get injected into outbound requests via OpenSandbox's Credential Vault, without the sandbox process ever holding the real value.
- New
@alineo-labs/vaultpackage —OpenSandboxCredentialBroker, the defaultCredentialBrokerimplementation, wired up automatically by@alineo-labs/sandboxunless overridden. @alineo-labs/core: newCredentialBroker/CredentialBinding/CredentialSourceinterfaces (mirrorsIStorageAdapter's shape),sb.credentials.set()/patch()/remove()/listBindings()onSandboxHandle,SandboxHooks.onCredentialInjected, and two newLedgerEvents (CredentialBound/CredentialRevoked, binding metadata only — never the credential value).CredentialSource({ type: "env", varName }vs.{ type: "external" }) letsresume()andsb.fork()resolve env-backed credentials automatically; anything else requires an explicitresolveCredentialcallback, and both now throw rather than silently drop a bound credential if one isn't resolvable.@alineo-labs/opensandbox:NetworkPolicy/NetworkRule/CredentialProxyConfigtypes, andnetworkPolicy/credentialProxyonCreateSandboxOptions.@alineo-labs/sandbox:SandboxOptions.networkPolicy/credentialProxy,SandboxClientOptions.credentialBroker, andResumeOptions.resolveCredential.sb.fork()now also carries over the parent's own bound credentials to the child automatically (previously silently dropped), and takes an optional{ resolveCredential, credentialProxy }.alineo(agent package):AgentSpec.envvalues can now be aCredentialEnvBinding({ credential, host, injection }) instead of a plain string — that key never becomes a container env var at all;Alineo.load()/.resume()/.spawn()register it with the broker instead (including for a spawned child's own newly-declared bindings, previously dropped).alineo-cli:alineo initnow configures[egress](opensandbox/egress:v1.1.7,mode = "dns+nft") in the generated local server config by default — inert for any sandbox that doesn't requestnetworkPolicy, but required beforecredentialProxy: trueworks at all against a freshalineo initserver.
See
plans/credential-injection.mdfor the full design (issue #203). Verified end-to-end against a liveopensandbox/server:latest+opensandbox/egress:v1.1.7: registration, transparent injection, revocation, andfork()credential carrying. Two known limitations, both from the real Credential Vault API rather than this package: only{ type: "header" }credential bindings are supported for now (query/pathinjection has no direct equivalent in the sidecar'sAuthmodel), andOpenSandboxCredentialBroker.patch()requires bothvalueandbindingtogether (the vault never echoes a credential's value back, so a partial update can't preserve the unspecified half). - New
-
84b7862: Internal: enabled Oxlint's type-aware linting repo-wide and fixed every finding it surfaced (681 → 0). Almost entirely non-behavioral (removing unnecessary type assertions, replacing non-null assertions with real invariant checks, fixing tsconfig gaps that were masking latent type errors) — flagged the couple of exceptions below since they do change observable behavior.
alineo-cli's Pi bootstrap extension (pi-extension/alineo.ts) no longer replaces an empty-but-presentstderrstring with a generic fallback message in its install/init failure notifications — only a genuinely missingstderrfalls back now.@alineo-labs/core'sSandboxCoregained a couple of small correctness fixes surfaced along the way:bun:sqlite's deprecatedexec()alias replaced withrun(), and afinally-block cleanup path in a test that could previously mask a real assertion failure with an unrelated error now logs instead of throwing.packages/cli/src/tui/chat.ts'sAgentEventswitch now lists all 14 previously-implicit "ignored" event kinds explicitly instead of a baredefault, so a future new event kind fails exhaustiveness and forces a conscious decision, rather than silently landing in "ignored".
No public API changes. Full
typecheck/test/buildsuite passes for every package.
Minor Changes
-
f987d00: Credential injection: sandboxes can now register credentials that get injected into outbound requests via OpenSandbox's Credential Vault, without the sandbox process ever holding the real value.
- New
@alineo-labs/vaultpackage —OpenSandboxCredentialBroker, the defaultCredentialBrokerimplementation, wired up automatically by@alineo-labs/sandboxunless overridden. @alineo-labs/core: newCredentialBroker/CredentialBinding/CredentialSourceinterfaces (mirrorsIStorageAdapter's shape),sb.credentials.set()/patch()/remove()/listBindings()onSandboxHandle,SandboxHooks.onCredentialInjected, and two newLedgerEvents (CredentialBound/CredentialRevoked, binding metadata only — never the credential value).CredentialSource({ type: "env", varName }vs.{ type: "external" }) letsresume()andsb.fork()resolve env-backed credentials automatically; anything else requires an explicitresolveCredentialcallback, and both now throw rather than silently drop a bound credential if one isn't resolvable.@alineo-labs/opensandbox:NetworkPolicy/NetworkRule/CredentialProxyConfigtypes, andnetworkPolicy/credentialProxyonCreateSandboxOptions.@alineo-labs/sandbox:SandboxOptions.networkPolicy/credentialProxy,SandboxClientOptions.credentialBroker, andResumeOptions.resolveCredential.sb.fork()now also carries over the parent's own bound credentials to the child automatically (previously silently dropped), and takes an optional{ resolveCredential, credentialProxy }.alineo(agent package):AgentSpec.envvalues can now be aCredentialEnvBinding({ credential, host, injection }) instead of a plain string — that key never becomes a container env var at all;Alineo.load()/.resume()/.spawn()register it with the broker instead (including for a spawned child's own newly-declared bindings, previously dropped).alineo-cli:alineo initnow configures[egress](opensandbox/egress:v1.1.7,mode = "dns+nft") in the generated local server config by default — inert for any sandbox that doesn't requestnetworkPolicy, but required beforecredentialProxy: trueworks at all against a freshalineo initserver.
See
plans/credential-injection.mdfor the full design (issue #203). Verified end-to-end against a liveopensandbox/server:latest+opensandbox/egress:v1.1.7: registration, transparent injection, revocation, andfork()credential carrying. Two known limitations, both from the real Credential Vault API rather than this package: only{ type: "header" }credential bindings are supported for now (query/pathinjection has no direct equivalent in the sidecar'sAuthmodel), andOpenSandboxCredentialBroker.patch()requires bothvalueandbindingtogether (the vault never echoes a credential's value back, so a partial update can't preserve the unspecified half). - New
-
223390e: Fill out the
@alineo-labs/memoryprovider-agnostic layer introduced in a prior release:Ledger scoping (
@alineo-labs/core,@alineo-labs/sandbox,@alineo-labs/sqlite,@alineo-labs/postgres) —SandboxOptions.resourceIdthreads a durable resource identity through the ledger's existingsandbox_createdpayload, the same additive mechanismrunIdalready used (no schema migration).resume(),restoreSnapshot(), andsb.fork()inherit it automatically.sb.fork()also now recordsparentSandboxId, letting episodic memory walk a fork's ancestry. Both fields are optional and exposed onSandboxDetails/ListSandboxOptions.Real persistence backends — two new packages,
@alineo-labs/sqlite-memory(file-based, viabun:sqlite, zero infrastructure) and@alineo-labs/postgres-memory(shared, multi-process, with row-level-security team isolation), each implementingIWorkingMemoryProviderand the newIPrunableSemanticMemoryProvidercapability. Both rank semantic recall with an in-JS cosine scan (no vector index); the Postgres package'svectorcolumn is a plain array today, documented with thepgvectorupgrade path.Compaction —
compactSemanticMemory()(andMemory.compactSemanticMemory()) prunes old or excess facts via the new optionalIPrunableSemanticMemoryProvidercapability (listAll/forget, keyed by a stable per-factid), supported by all three semantic providers now shipping (in-memory, SQLite, Postgres).Lifecycle binding —
createMemoryLifecycleHooks(memory, ref)returns a composableSandboxHooksobject that records the most recent checkpoint and active session into working memory, using@alineo-labs/core's existing hooks extension point rather than any change to sandbox lifecycle internals.Episodic memory —
episodicRecall()'s default session resolver now matches onresourceIddirectly (falling back to the old name-matching convention for pre-existing ledger data), and gains abranch: "lineage"option that walksparentSandboxIdancestry.Agent wiring (
alineo) —Alineo.load()/.resume()/.attach()accept an optionalmemory: Memory;.spawn()carries it over to the child automatically.agent.resourceRefdefaults to{ resourceId: agent.name }, matchingepisodicRecall()'s own naming convention.Embeddings (
@alineo-labs/model-providers, unpublished/private) —createNvidiaEmbeddingProvider()wraps NVIDIA NIM's embeddings endpoint, duck-typed toEmbeddingProvider's shape with no dependency on@alineo-labs/memory.
Patch Changes
-
84b7862: Internal: enabled Oxlint's type-aware linting repo-wide and fixed every finding it surfaced (681 → 0). Almost entirely non-behavioral (removing unnecessary type assertions, replacing non-null assertions with real invariant checks, fixing tsconfig gaps that were masking latent type errors) — flagged the couple of exceptions below since they do change observable behavior.
alineo-cli's Pi bootstrap extension (pi-extension/alineo.ts) no longer replaces an empty-but-presentstderrstring with a generic fallback message in its install/init failure notifications — only a genuinely missingstderrfalls back now.@alineo-labs/core'sSandboxCoregained a couple of small correctness fixes surfaced along the way:bun:sqlite's deprecatedexec()alias replaced withrun(), and afinally-block cleanup path in a test that could previously mask a real assertion failure with an unrelated error now logs instead of throwing.packages/cli/src/tui/chat.ts'sAgentEventswitch now lists all 14 previously-implicit "ignored" event kinds explicitly instead of a baredefault, so a future new event kind fails exhaustiveness and forces a conscious decision, rather than silently landing in "ignored".
No public API changes. Full
typecheck/test/buildsuite passes for every package.
Patch Changes
- 964eff4: Remove the unused
typeboxdevDependency — leftover frompi-extension/alineo.ts's typed tools (alineo_spawn/alineo_prompt/alineo_agents/alineo_kill), which were already removed in favor of bash-only CLI usage. No source file in this package imports it. No behavior change.
Minor Changes
-
d628de4: Breaking: this package is now published as
@alineo-labs/sandbox(wasalineo). The sandbox client —import { Alineo } from "alineo"— moves here asSandbox, per the naming inversion tracked in #182: the barealineopackage name now belongs to the sandboxed-agent API (see that package's own changeset entry).-import { Alineo } from "alineo"; -const client = new Alineo({ baseUrl, adapter }); +import { Sandbox } from "@alineo-labs/sandbox"; +const client = new Sandbox({ baseUrl, adapter });Two supporting types are also renamed, to avoid colliding with
@alineo-labs/core's pre-existingSandboxError/SandboxOptions(which mean something different — container-state errors and per-.sandbox()-call options, respectively):-import { AlineoError, type AlineoOptions } from "alineo"; +import { SandboxClientError, type SandboxClientOptions } from "@alineo-labs/sandbox";Every other exported type is unchanged (
ResumeOptions,SandboxOptions,SandboxHooks,SandboxDetails,SandboxStatus, ...).client.sandbox()'s return type is nowSandboxHandle(wasSandbox) — see@alineo-labs/core's changeset entry.
Patch Changes
-
a8ace55: Bump the
execdimagealineo initpins in the generatedserver.tomlfromv1.0.19tov1.0.22. v1.0.19 predates cached bwrap-archive support, so every sandbox logged "bwrap archive not cached for linux/amd64 -- isolation will be unavailable" and the warning's own suggested fix ("upgrade execd image to v1.1.0+") pointed at a tag that was never actually published. Per OpenSandbox's docs,execd>=v1.0.20 has base isolation-session support and=v1.0.21 is recommended for full functionality; v1.0.22 is the latest published patch.
Note: this resolves the stale-image warning and lets execd's own isolation probe run cleanly, but does not by itself enable bwrap isolation end-to-end -- that additionally requires the sandbox-creation request to opt into
bootstrap.execd.isolation, which grants the containerCAP_SYS_ADMINand unconfined apparmor/seccomp. This SDK does not do that by default (and deliberately doesn't turn it on unconditionally, since it would weaken every sandbox's default container security posture just to support a rarely-used feature) -- pause()/resume() and isolation-session-dependent features remain unavailable out of the box pending a real opt-in API for this. -
9eb1081: Fix
alineo spawn/fork/prompt(and every other subcommand) hanging indefinitely instead of exiting after printing their result. The underlying SDK's exec client keeps a connection open on theAgent/Sandboxobject to support further calls on it, which left the CLI process's event loop non-empty forever -- every--prompt --jsoninvocation needed an externaltimeoutwrapper to actually terminate. Callingagent.close()isn't the fix:spawnandforkdeliberately leave their sandbox running (that's the whole point --alineo agents/alineo prompt <id>interact with it afterward), so closing theAgentobject would delete the very sandbox the command just reported. Added an explicitprocess.exit(0)after a subcommand completes instead -- it ends only this CLI invocation, with no effect on the remote sandbox.Verified via a real
alineo spawn ... --prompt ... --jsonrun: previously hung until killed externally; now exits naturally within a second of printing its result (measured: total wall time matched the CLI's own reported work duration almost exactly).
Major Changes
-
2a61e0c: Rename the project from drej to alineo. Breaking change: every published package's name changed.
- SDK:
drej→alineo(import { Drej } from "drej"→import { Alineo } from "alineo").DrejError/DrejOptions→AlineoError/AlineoOptions. - CLI:
drejx→alineo-cli(npm package name), binary commanddrejx→alineo(drejx init→alineo init, etc).~/.config/drejx/→~/.config/alineo/, project-localdrej.config.json→alineo.config.json,.drej/→.alineo/. - Scoped packages:
@drej/*→@alineo-labs/*across all 14 previously-scoped packages. - Environment variables:
DREJ_*/DREJX_*→ALINEO_*(the two-prefix split collapses to one now that the CLI binary and SDK class share the same root name).
This is a code-level rename only — package/CLI/env-var/config-path identifiers. GitHub org/repo, deploy domains, and Cloudflare project names are unchanged in this pass (that infra isn't provisioned under the new name yet).
- SDK:
Minor Changes
-
425d9bc: Add anonymous CLI usage telemetry: which subcommand ran, a small per-command allowlist of boolean flag presence (never values or raw argv), success/failure, timing, and — for
spawn/forkonly — the target spec's ownproviderid. Default-off for this release (no production endpoint deployed yet — flips on once one is), opt-out viaalineo telemetry disable/status/enableor theALINEO_TELEMETRY_DISABLED/DO_NOT_TRACKenv vars either way. Transport is a plain, bounded (500ms timeout)fetch()POST to a new, completely standalone ingest app,apps/telemetry— no OpenTelemetry, no dependency on any other app in this repo, in either direction. Server-side re-validates every event against the same allowlisted shape (never trusts the client), with a body-size cap and a per-anonymous-ID rate limit on its one unauthenticated route. -
f7c04f7: Point CLI telemetry at the deployed ingest endpoint (
https://telemetry.alineo.tech) and flip the default from off to on, now that the endpoint is actually live. Opt out any time withalineo telemetry disable,ALINEO_TELEMETRY_DISABLED=1, orDO_NOT_TRACK=1. Seealineo telemetryin the docs for exactly what's collected. -
637b678: Add
runId— a first-class way to correlate sandboxes belonging to the same logical run, surfaced throughSandboxDetails.runIdand filterable viaclient.sandboxes.list({ runId })/listByName({ runId }).SandboxOptions.runId(optional, defaults to a freshcrypto.randomUUID()if omitted) is recorded on every sandbox-creation path (client.sandbox(),client.resume(),client.restoreSnapshot(),sb.fork(), environment-backed sandboxes) — a resumed, restored, or forked sandbox always inherits its origin'srunIdrather than getting a new one.sb.fork(tag?, runId?)gains an optional explicit override, needed across a process boundary (e.g.alineo fork, which re-Agent.attach()es in a brand-new CLI process with no access to the original in-memory closure) — same reasoningALINEO_SPAWN_DEPTHalready established, generalized to run identity.Agent.load()/Agent.resume()accept an optionalrunId, bakeALINEO_RUN_IDinto the sandbox's env alongsideALINEO_SPAWN_DEPTH/ALINEO_MAX_AGENTS/ALINEO_OBSERVABILITY, and expose it asagent.runId.Agent.spawn()/alineo forkforce-inherit it into every forked child, tamper-resistant like the existing budget fields.alineo spawngains a--run-idflag.runIdalso rides along inSandboxOptions.metadata/CreateSandboxOptions.metadataat every creation path, since the ledger alone can't correlate sandboxes across separate adapter instances (e.g. a forked child writing to its own in-container ledger file) — the OpenSandbox control plane is the one channel every caller shares regardless of adapter, and itsSandboxtype already declares (and, verified against a live server, actually echoes back)metadata.- Both storage adapters (
@alineo-labs/sqlite,@alineo-labs/postgres) extend their aggregation query to surfacerunIdonSandboxDetailsand support it as aListSandboxOptionsfilter — no schema migration needed, read out of the existing JSON payload.
Patch Changes
-
9fb6550: Document the alineo telemetry command in packages/cli/README.md and the docs site. No behavior change.
-
735ecf7:
alineo fork/spawn/promptnow accept--timeout SECONDSto bound how long a--promptwaits for activity before failing with a clear error, instead of hanging forever if the underlying agent process ever goes silent.--jsonoutput also now includestoolCallsalongsidereply, so a turn that made tool calls but produced no final text is distinguishable from one that did nothing. -
e1f6621: Fix
alineo init's Docker container silently losing every cached agent snapshot whenever it's removed and recreated (host reboot with no restart policy,docker system prune, a straydocker rm) — not just restarted. OpenSandbox itself persists snapshot metadata durably in a SQLite db meant to survive the server process restarting, butalineo initnever bind-mounted that db's directory to the host, so it only ever survived alongside the container's own lifecycle.~/.config/alineo/opensandbox-datais now bind-mounted into the container at/data, with[store].pathpinned explicitly in the generatedserver.toml, so the durability guarantee OpenSandbox already provides actually holds (fixes #20).Also:
Agent.load()'s snapshot-restore fallback now logs the real error instead of a bare "snapshot stale, rebuilding..." — useful for any other reason a cached snapshot might fail to restore, not just this one. -
bd95393: Remove
private: truefrom the 10 publishable packages so they can actually be published to npm. No functional or API changes — this is the last step of npm-publish readiness (repository URLs,publishConfig, andbin/repositoryfields were already correct). -
94dfc3d: Remove
pi-extension/alineo.ts's typedalineo_spawn/alineo_prompt/alineo_agents/alineo_killtools. They sat next toalineo fork— deliberately never a typed tool, since forking is a judgment call about task decomposition that belongs in a real shell command — and that asymmetry measurably steered models toward the wrong primitive (issue #21 Bug B: a run picked the typedalineo_spawntool over thealineo forkshell command the guidance text recommended for that exact scenario). All five subcommands are bash-only now, guided by the same prose guidance the extension already injected. The extension itself stays in place, deprecated, for its remainingbefore_agent_startguidance injection andensureAlineoReady()bootstrap. -
acc51e3: Update package.json repository fields to the renamed GitHub repo (DrejT/drej -> DrejT/alineo). No behavior change.
Major Changes
-
2a61e0c: Rename the project from drej to alineo. Breaking change: every published package's name changed.
- SDK:
drej→alineo(import { Drej } from "drej"→import { Alineo } from "alineo").DrejError/DrejOptions→AlineoError/AlineoOptions. - CLI:
drejx→alineo-cli(npm package name), binary commanddrejx→alineo(drejx init→alineo init, etc).~/.config/drejx/→~/.config/alineo/, project-localdrej.config.json→alineo.config.json,.drej/→.alineo/. - Scoped packages:
@drej/*→@alineo-labs/*across all 14 previously-scoped packages. - Environment variables:
DREJ_*/DREJX_*→ALINEO_*(the two-prefix split collapses to one now that the CLI binary and SDK class share the same root name).
This is a code-level rename only — package/CLI/env-var/config-path identifiers. GitHub org/repo, deploy domains, and Cloudflare project names are unchanged in this pass (that infra isn't provisioned under the new name yet).
- SDK:
Minor Changes
-
637b678: Add
runId— a first-class way to correlate sandboxes belonging to the same logical run, surfaced throughSandboxDetails.runIdand filterable viaclient.sandboxes.list({ runId })/listByName({ runId }).SandboxOptions.runId(optional, defaults to a freshcrypto.randomUUID()if omitted) is recorded on every sandbox-creation path (client.sandbox(),client.resume(),client.restoreSnapshot(),sb.fork(), environment-backed sandboxes) — a resumed, restored, or forked sandbox always inherits its origin'srunIdrather than getting a new one.sb.fork(tag?, runId?)gains an optional explicit override, needed across a process boundary (e.g.alineo fork, which re-Agent.attach()es in a brand-new CLI process with no access to the original in-memory closure) — same reasoningALINEO_SPAWN_DEPTHalready established, generalized to run identity.Agent.load()/Agent.resume()accept an optionalrunId, bakeALINEO_RUN_IDinto the sandbox's env alongsideALINEO_SPAWN_DEPTH/ALINEO_MAX_AGENTS/ALINEO_OBSERVABILITY, and expose it asagent.runId.Agent.spawn()/alineo forkforce-inherit it into every forked child, tamper-resistant like the existing budget fields.alineo spawngains a--run-idflag.runIdalso rides along inSandboxOptions.metadata/CreateSandboxOptions.metadataat every creation path, since the ledger alone can't correlate sandboxes across separate adapter instances (e.g. a forked child writing to its own in-container ledger file) — the OpenSandbox control plane is the one channel every caller shares regardless of adapter, and itsSandboxtype already declares (and, verified against a live server, actually echoes back)metadata.- Both storage adapters (
@alineo-labs/sqlite,@alineo-labs/postgres) extend their aggregation query to surfacerunIdonSandboxDetailsand support it as aListSandboxOptionsfilter — no schema migration needed, read out of the existing JSON payload.
Patch Changes
- bd95393: Remove
private: truefrom the 10 publishable packages so they can actually be published to npm. No functional or API changes — this is the last step of npm-publish readiness (repository URLs,publishConfig, andbin/repositoryfields were already correct). - acc51e3: Update package.json repository fields to the renamed GitHub repo (DrejT/drej -> DrejT/alineo). No behavior change.
Minor Changes
-
87a9c39: Approve-on-egress hold for agents:
approval: "hold"on a credential env binding.A
CredentialEnvBindinginAgentSpec.envcan now carryapproval: "hold". The agent's sandbox starts with that host denied at the egress sidecar and the credential not registered in the vault at all (the vault refuses a binding whose host isn't allowed); everything else, including the agent's own model traffic, keeps working. The first outbound request to the held host pauses and calls theonEgressRequesthandler you pass toAlineo.load(), which returns"allow-once"(reversed when the turn ends),"allow-always"(permanent for the agent's life), or"deny". Only on approval does the gate open the egress rule and then register the credential — so the secret literally does not exist inside the sandbox until a human approves. Enforcement is entirely out-of-process at the sidecar — a compromised in-sandbox agent cannot skip it.- New
EgressApprovalGate(exported fromalineo): a small host-side listener for the sidecar's deny webhook that, on approval, callssb.egress.patch()thensb.credentials.set()(and reverses both on anallow-onceat turn end).Alineostarts one automatically forholdbindings and stops it onclose();agent.pendingEgressRequests()lists what is waiting.agent.egressGateis exposed for direct control. Ledger:PermissionRequested/PermissionResolvedwithtool: "network". - The webhook host defaults to the Docker bridge gateway (
172.17.0.1); override viaALINEO_EGRESS_APPROVAL_HOSTfor other topologies. @alineo-labs/sandbox:restoreSnapshot()gains anenvoption (for re-supplyingOPENSANDBOX_EGRESS_*sidecar vars on a restore — the sidecar is not snapshotted).
Deferred: the deny-webhook signal is not yet unified into the Pi tool-permission stream (so network approvals do not appear in
listPendingPermissions()alongside tool permissions or in the chat UI), and there is no automatic re-run of the request that hit the denial — the model retries on its own (the retry window is effectively instant). Both are follow-ups. - New
-
87a9c39: Egress network policy: CIDR/IP targets, and runtime policy changes on a live sandbox.
NetworkRule.targetnow documents (and the SDK validates) IP and CIDR targets —"10.0.0.5","10.0.0.0/8", plus IPv6 — alongside FQDNs and*.wildcards. IP/CIDR rules are enforced at the nftables layer (so they needegress.mode = "dns+nft") and gate raw-IP egress only, not name resolution. A malformednetworkPolicytarget now throwsSandboxClientErrorlocally instead of failing on a server round-trip (isValidEgressTargetis exported from@alineo-labs/opensandbox).sb.egress.patch(rules)/sb.egress.delete(targets)/sb.egress.get()adjust a running sandbox's egress policy through its sidecar — merge in allow/deny rules (an incoming rule replaces any rule with the sametarget) or remove them by target. Changes apply immediately and are recorded to the ledger (EgressRuleAdded/EgressRuleRemoved), soSandbox.resume()re-applies a still-wanted allowance — egress policy is sidecar-local and does not survive a resume on its own. NewEgressClientin@alineo-labs/opensandboxandreconstructEgressRulesin@alineo-labs/core.