Changelog

CLIv0.1.4· August 30, 2026

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/vault package — OpenSandboxCredentialBroker, the default CredentialBroker implementation, wired up automatically by @alineo-labs/sandbox unless overridden.
    • @alineo-labs/core: new CredentialBroker/CredentialBinding/CredentialSource interfaces (mirrors IStorageAdapter's shape), sb.credentials.set()/patch()/remove()/listBindings() on SandboxHandle, SandboxHooks.onCredentialInjected, and two new LedgerEvents (CredentialBound/CredentialRevoked, binding metadata only — never the credential value). CredentialSource ({ type: "env", varName } vs. { type: "external" }) lets resume() and sb.fork() resolve env-backed credentials automatically; anything else requires an explicit resolveCredential callback, and both now throw rather than silently drop a bound credential if one isn't resolvable.
    • @alineo-labs/opensandbox: NetworkPolicy/NetworkRule/CredentialProxyConfig types, and networkPolicy/credentialProxy on CreateSandboxOptions.
    • @alineo-labs/sandbox: SandboxOptions.networkPolicy/credentialProxy, SandboxClientOptions.credentialBroker, and ResumeOptions.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.env values can now be a CredentialEnvBinding ({ 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 init now 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 request networkPolicy, but required before credentialProxy: true works at all against a fresh alineo init server.

    See plans/credential-injection.md for the full design (issue #203). Verified end-to-end against a live opensandbox/server:latest + opensandbox/egress:v1.1.7: registration, transparent injection, revocation, and fork() 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/path injection has no direct equivalent in the sidecar's Auth model), and OpenSandboxCredentialBroker.patch() requires both value and binding together (the vault never echoes a credential's value back, so a partial update can't preserve the unspecified half).

  • 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-present stderr string with a generic fallback message in its install/init failure notifications — only a genuinely missing stderr falls back now.
    • @alineo-labs/core's SandboxCore gained a couple of small correctness fixes surfaced along the way: bun:sqlite's deprecated exec() alias replaced with run(), and a finally-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's AgentEvent switch now lists all 14 previously-implicit "ignored" event kinds explicitly instead of a bare default, 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/build suite passes for every package.

SDKv0.3.0· August 30, 2026

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/vault package — OpenSandboxCredentialBroker, the default CredentialBroker implementation, wired up automatically by @alineo-labs/sandbox unless overridden.
    • @alineo-labs/core: new CredentialBroker/CredentialBinding/CredentialSource interfaces (mirrors IStorageAdapter's shape), sb.credentials.set()/patch()/remove()/listBindings() on SandboxHandle, SandboxHooks.onCredentialInjected, and two new LedgerEvents (CredentialBound/CredentialRevoked, binding metadata only — never the credential value). CredentialSource ({ type: "env", varName } vs. { type: "external" }) lets resume() and sb.fork() resolve env-backed credentials automatically; anything else requires an explicit resolveCredential callback, and both now throw rather than silently drop a bound credential if one isn't resolvable.
    • @alineo-labs/opensandbox: NetworkPolicy/NetworkRule/CredentialProxyConfig types, and networkPolicy/credentialProxy on CreateSandboxOptions.
    • @alineo-labs/sandbox: SandboxOptions.networkPolicy/credentialProxy, SandboxClientOptions.credentialBroker, and ResumeOptions.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.env values can now be a CredentialEnvBinding ({ 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 init now 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 request networkPolicy, but required before credentialProxy: true works at all against a fresh alineo init server.

    See plans/credential-injection.md for the full design (issue #203). Verified end-to-end against a live opensandbox/server:latest + opensandbox/egress:v1.1.7: registration, transparent injection, revocation, and fork() 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/path injection has no direct equivalent in the sidecar's Auth model), and OpenSandboxCredentialBroker.patch() requires both value and binding together (the vault never echoes a credential's value back, so a partial update can't preserve the unspecified half).

  • 223390e: Fill out the @alineo-labs/memory provider-agnostic layer introduced in a prior release:

    Ledger scoping (@alineo-labs/core, @alineo-labs/sandbox, @alineo-labs/sqlite, @alineo-labs/postgres)SandboxOptions.resourceId threads a durable resource identity through the ledger's existing sandbox_created payload, the same additive mechanism runId already used (no schema migration). resume(), restoreSnapshot(), and sb.fork() inherit it automatically. sb.fork() also now records parentSandboxId, letting episodic memory walk a fork's ancestry. Both fields are optional and exposed on SandboxDetails/ListSandboxOptions.

    Real persistence backends — two new packages, @alineo-labs/sqlite-memory (file-based, via bun:sqlite, zero infrastructure) and @alineo-labs/postgres-memory (shared, multi-process, with row-level-security team isolation), each implementing IWorkingMemoryProvider and the new IPrunableSemanticMemoryProvider capability. Both rank semantic recall with an in-JS cosine scan (no vector index); the Postgres package's vector column is a plain array today, documented with the pgvector upgrade path.

    CompactioncompactSemanticMemory() (and Memory.compactSemanticMemory()) prunes old or excess facts via the new optional IPrunableSemanticMemoryProvider capability (listAll/forget, keyed by a stable per-fact id), supported by all three semantic providers now shipping (in-memory, SQLite, Postgres).

    Lifecycle bindingcreateMemoryLifecycleHooks(memory, ref) returns a composable SandboxHooks object 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 memoryepisodicRecall()'s default session resolver now matches on resourceId directly (falling back to the old name-matching convention for pre-existing ledger data), and gains a branch: "lineage" option that walks parentSandboxId ancestry.

    Agent wiring (alineo)Alineo.load()/.resume()/.attach() accept an optional memory: Memory; .spawn() carries it over to the child automatically. agent.resourceRef defaults to { resourceId: agent.name }, matching episodicRecall()'s own naming convention.

    Embeddings (@alineo-labs/model-providers, unpublished/private)createNvidiaEmbeddingProvider() wraps NVIDIA NIM's embeddings endpoint, duck-typed to EmbeddingProvider'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-present stderr string with a generic fallback message in its install/init failure notifications — only a genuinely missing stderr falls back now.
    • @alineo-labs/core's SandboxCore gained a couple of small correctness fixes surfaced along the way: bun:sqlite's deprecated exec() alias replaced with run(), and a finally-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's AgentEvent switch now lists all 14 previously-implicit "ignored" event kinds explicitly instead of a bare default, 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/build suite passes for every package.

CLIv0.1.3· August 22, 2026

Patch Changes

  • 964eff4: Remove the unused typebox devDependency — leftover from pi-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.
SDKv0.2.0· August 22, 2026

Minor Changes

  • d628de4: Breaking: this package is now published as @alineo-labs/sandbox (was alineo). The sandbox client — import { Alineo } from "alineo" — moves here as Sandbox, per the naming inversion tracked in #182: the bare alineo package 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-existing SandboxError/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 now SandboxHandle (was Sandbox) — see @alineo-labs/core's changeset entry.

CLIv0.1.1· August 16, 2026

Patch Changes

  • a8ace55: Bump the execd image alineo init pins in the generated server.toml from v1.0.19 to v1.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 container CAP_SYS_ADMIN and 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 the Agent/Sandbox object to support further calls on it, which left the CLI process's event loop non-empty forever -- every --prompt --json invocation needed an external timeout wrapper to actually terminate. Calling agent.close() isn't the fix: spawn and fork deliberately leave their sandbox running (that's the whole point -- alineo agents/ alineo prompt <id> interact with it afterward), so closing the Agent object would delete the very sandbox the command just reported. Added an explicit process.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 ... --json run: 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).

CLIv0.1.0· August 15, 2026

Major Changes

  • 2a61e0c: Rename the project from drej to alineo. Breaking change: every published package's name changed.

    • SDK: drejalineo (import { Drej } from "drej"import { Alineo } from "alineo"). DrejError/DrejOptionsAlineoError/AlineoOptions.
    • CLI: drejxalineo-cli (npm package name), binary command drejxalineo (drejx initalineo init, etc). ~/.config/drejx/~/.config/alineo/, project-local drej.config.jsonalineo.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).

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/fork only — the target spec's own provider id. Default-off for this release (no production endpoint deployed yet — flips on once one is), opt-out via alineo telemetry disable/status/enable or the ALINEO_TELEMETRY_DISABLED/DO_NOT_TRACK env 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 with alineo telemetry disable, ALINEO_TELEMETRY_DISABLED=1, or DO_NOT_TRACK=1. See alineo telemetry in the docs for exactly what's collected.

  • 637b678: Add runId — a first-class way to correlate sandboxes belonging to the same logical run, surfaced through SandboxDetails.runId and filterable via client.sandboxes.list({ runId })/listByName({ runId }).

    • SandboxOptions.runId (optional, defaults to a fresh crypto.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's runId rather 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 reasoning ALINEO_SPAWN_DEPTH already established, generalized to run identity.
    • Agent.load()/Agent.resume() accept an optional runId, bake ALINEO_RUN_ID into the sandbox's env alongside ALINEO_SPAWN_DEPTH/ALINEO_MAX_AGENTS/ALINEO_OBSERVABILITY, and expose it as agent.runId. Agent.spawn()/alineo fork force-inherit it into every forked child, tamper-resistant like the existing budget fields. alineo spawn gains a --run-id flag.
    • runId also rides along in SandboxOptions.metadata/CreateSandboxOptions.metadata at 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 its Sandbox type 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 surface runId on SandboxDetails and support it as a ListSandboxOptions filter — 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/prompt now accept --timeout SECONDS to bound how long a --prompt waits for activity before failing with a clear error, instead of hanging forever if the underlying agent process ever goes silent. --json output also now includes toolCalls alongside reply, 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 stray docker rm) — not just restarted. OpenSandbox itself persists snapshot metadata durably in a SQLite db meant to survive the server process restarting, but alineo init never bind-mounted that db's directory to the host, so it only ever survived alongside the container's own lifecycle. ~/.config/alineo/opensandbox-data is now bind-mounted into the container at /data, with [store].path pinned explicitly in the generated server.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: true from 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, and bin/repository fields were already correct).

  • 94dfc3d: Remove pi-extension/alineo.ts's typed alineo_spawn/alineo_prompt/alineo_agents/alineo_kill tools. They sat next to alineo 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 typed alineo_spawn tool over the alineo fork shell 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 remaining before_agent_start guidance injection and ensureAlineoReady() bootstrap.

  • acc51e3: Update package.json repository fields to the renamed GitHub repo (DrejT/drej -> DrejT/alineo). No behavior change.

SDKv0.1.0· August 15, 2026

Major Changes

  • 2a61e0c: Rename the project from drej to alineo. Breaking change: every published package's name changed.

    • SDK: drejalineo (import { Drej } from "drej"import { Alineo } from "alineo"). DrejError/DrejOptionsAlineoError/AlineoOptions.
    • CLI: drejxalineo-cli (npm package name), binary command drejxalineo (drejx initalineo init, etc). ~/.config/drejx/~/.config/alineo/, project-local drej.config.jsonalineo.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).

Minor Changes

  • 637b678: Add runId — a first-class way to correlate sandboxes belonging to the same logical run, surfaced through SandboxDetails.runId and filterable via client.sandboxes.list({ runId })/listByName({ runId }).

    • SandboxOptions.runId (optional, defaults to a fresh crypto.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's runId rather 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 reasoning ALINEO_SPAWN_DEPTH already established, generalized to run identity.
    • Agent.load()/Agent.resume() accept an optional runId, bake ALINEO_RUN_ID into the sandbox's env alongside ALINEO_SPAWN_DEPTH/ALINEO_MAX_AGENTS/ALINEO_OBSERVABILITY, and expose it as agent.runId. Agent.spawn()/alineo fork force-inherit it into every forked child, tamper-resistant like the existing budget fields. alineo spawn gains a --run-id flag.
    • runId also rides along in SandboxOptions.metadata/CreateSandboxOptions.metadata at 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 its Sandbox type 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 surface runId on SandboxDetails and support it as a ListSandboxOptions filter — no schema migration needed, read out of the existing JSON payload.

Patch Changes

  • bd95393: Remove private: true from 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, and bin/repository fields were already correct).
  • acc51e3: Update package.json repository fields to the renamed GitHub repo (DrejT/drej -> DrejT/alineo). No behavior change.
SDKv0.4.0

Minor Changes

  • 87a9c39: Approve-on-egress hold for agents: approval: "hold" on a credential env binding.

    A CredentialEnvBinding in AgentSpec.env can now carry approval: "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 the onEgressRequest handler you pass to Alineo.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 from alineo): a small host-side listener for the sidecar's deny webhook that, on approval, calls sb.egress.patch() then sb.credentials.set() (and reverses both on an allow-once at turn end). Alineo starts one automatically for hold bindings and stops it on close(); agent.pendingEgressRequests() lists what is waiting. agent.egressGate is exposed for direct control. Ledger: PermissionRequested / PermissionResolved with tool: "network".
    • The webhook host defaults to the Docker bridge gateway (172.17.0.1); override via ALINEO_EGRESS_APPROVAL_HOST for other topologies.
    • @alineo-labs/sandbox: restoreSnapshot() gains an env option (for re-supplying OPENSANDBOX_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.

  • 87a9c39: Egress network policy: CIDR/IP targets, and runtime policy changes on a live sandbox.

    • NetworkRule.target now 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 need egress.mode = "dns+nft") and gate raw-IP egress only, not name resolution. A malformed networkPolicy target now throws SandboxClientError locally instead of failing on a server round-trip (isValidEgressTarget is 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 same target) or remove them by target. Changes apply immediately and are recorded to the ledger (EgressRuleAdded / EgressRuleRemoved), so Sandbox.resume() re-applies a still-wanted allowance — egress policy is sidecar-local and does not survive a resume on its own. New EgressClient in @alineo-labs/opensandbox and reconstructEgressRules in @alineo-labs/core.