Changelog

CLIv0.4.0· October 8, 2026

Minor Changes

  • 157f974: BREAKING: the three session-lifecycle verbs are renamed so that one word means one thing across the CLI, the SDK and the MCP tools.

    BeforeNow
    alineo spawn <spec>alineo start <spec>
    alineo fork <name> <child-spec>alineo spawn <parent> <child-spec>
    alineo kill <sandbox-id>alineo stop <sandbox-id>

    spawn used to mean the opposite of what Alineo.spawn() means: the CLI created a root agent, the SDK a child. Anyone who learned one and moved to the other was actively misled. It now means "child" on both.

    There are deliberately no aliases. spawn did not disappear — it changed meaning, so an alias would silently do the wrong thing with the right-looking command. alineo spawn ./spec.json now fails on its arguments instead, which is the outcome you want: its first argument is a parent session name. Use alineo start ./spec.json.

    Also updated so a running agent is told the truth: the Pi extension's injected guidance, the alineod operator-steer message, the TUI (s stops a session; k still works), and every help, error and doc string that quoted a verb. scripts/check-vocabulary.ts now fails CI on a command name that is not in @alineo-labs/schema's VERBS, and on any text quoting a command that does not exist — the check that would have caught alineo ps, a command documented in the source for months that never existed.

Patch Changes

  • a5bd004: BREAKING: AgentSpec.cli → AgentSpec.harness, cliVersion → harnessVersion.

    -{ "name": "my-agent", "cli": "pi", "cliVersion": "1.2.3", "model": "…" }
    +{ "name": "my-agent", "harness": "pi", "harnessVersion": "1.2.3", "model": "…" }
    

    cli: "pi" said the agent-loop driver is a CLI. True of Pi, accidental in general — Claude Code, Codex and opencode are the next drivers, and cli: "claude-code" would read as a category error the day one ships. Renaming now costs one field; renaming after three drivers ship costs a migration and a docs rewrite.

    There is no alias, but a spec that still uses cli gets told so by name rather than "must have a 'harness' field":

    ✖ Agent spec must have a 'harness' field. Supported values: pi
    
    This spec uses 'cli' and 'cliVersion', renamed to 'harness' and 'harnessVersion':
    the field names the agent-loop driver, which is not always a CLI.
    

    Your cached snapshots survive. computeSetupHash() deliberately keeps the old key names in the object it hashes — it is a cache key nobody reads, and changing the spelling would have invalidated every existing snapshot, turning a field rename into a ~90s rebuild of every agent.

    alineo list now heads that column HARNESS, and alineo-mcp's SpecSummary.cli is SpecSummary.harness. The published JSON Schema at registry.alineo.tech/spec/agent.json requires harness, so an editor validating an old spec against $schema will flag it.

    Unchanged: telemetry's own cliVersion, which is the version of the alineo CLI itself — a different field that happens to share a name.

  • de786a7: Config is now found, merged and validated in one place.

    alineo.config.json is located by walking up from the working directory (bounded by a .git directory, $HOME, or the filesystem root) instead of being read only from the exact working directory. Running a script from a subdirectory previously fell back to built-in defaults in silence, which looked identical to having no config at all.

    Sources are merged lowest-first — ~/.config/alineo/config.json, then alineo.config.json, then ALINEO_CONFIG_CONTENT (inline JSON), then ALINEO_SERVER_URL / ALINEO_API_KEY / ALINEO_USE_SERVER_PROXY, then explicit options — validated once, then frozen. A malformed file or a bad value now fails with the offending key and file named, rather than being dropped.

    Alineo.load(), .resume(), .reattach() and .attach() accept opts.config to skip file and environment discovery entirely, for embedded callers and tests that must not depend on the working directory. The config is also read once per working directory now, not on every call.

    Three behaviour changes worth noting.

    A project config found by walking up will now apply where defaults were previously used.

    A global ~/.config/alineo/config.json is merged under a project config instead of being ignored whenever a project config exists. The SDK previously ignored the global config entirely while alineo-cli already read it, so on a machine with a global config and no project config the two disagreed about adapterPath — and therefore used different agent snapshot caches. They now agree. If that describes your setup, the first Alineo.load() of each spec after upgrading rebuilds its snapshot at the new location instead of reusing the old one; nothing is lost, but expect one slow run per spec.

    A spec that relied on the built-in adapterPath/agentsDir defaults while a global config set different ones will now follow the global config. Pass opts.config, or set the value explicitly in a project alineo.config.json, to pin it.

  • 31e97e5: alineo init now sets up the OpenSandbox server so a sandbox with a networkPolicy or credentialProxy can start.

    Before, a sandbox with a networkPolicy failed with Egress sidecar did not become ready within 30s … Connection refused although the sidecar was healthy. The server runs in a container and, with no [docker].host_ip, probed the sidecar at its own 127.0.0.1. init now writes host_ip = "host.docker.internal" and starts the container with --add-host host.docker.internal:host-gateway. A server.toml from an earlier init gets the line added, and the OpenSandbox container is recreated to match (its snapshot db is in a bind mount, so nothing is lost).

    init also pulls opensandbox/server:latest when it creates the container. docker run reuses an image already on disk, however old, and an old latest can mismatch the pinned egress image.

    The docs now say that networkPolicy and credentialProxy are rejected by a server using the gVisor runtime.

  • 14ecd92: alineo init now starts OpenSandbox and alineod with --restart unless-stopped, and sets that policy on containers an earlier init created. Before, a reboot or a Docker daemon restart left both down — OpenSandbox first, so nothing worked until init ran again, and alineod's crash recovery never got the chance to run because nothing restarted alineod. docker stop still sticks.

  • 8baafa7: ControlClient.listSandboxes() now returns every sandbox, not just the first 20. OpenSandbox paginates with page/pageSize and ignores the limit/offset this client was sending, so every listing stopped at the server's default page. The visible symptom: alineo agents cross-checks the ledger against the live list, so with more than 20 sandboxes running it reported the rest as gone. limit and offset still work — they are applied after every page is fetched.

CLIv0.3.0· September 20, 2026

Minor Changes

  • 7f0d5c2: New @alineo-labs/logger: a dependency-free logger that is silent by default, with one switch to turn it on and child loggers for context (getLogger("agent").child({ runId, agentId })).

    Behavior change — alineo (the agent SDK) no longer prints to stdout. Alineo.load() / .resume() / .reattach() / .spawn() used to console.log progress lines ([agent] starting sandbox…, [agent] bridge ready 3210ms, …) unconditionally, with no way to silence them. They now go through the logger as component agent, which emits nothing until logging is on. To see them again, set ALINEO_LOG_LEVEL=info (no code change needed) or call installLoggerFromEnv({ defaultLevel: "info" }) / installLogger() at your app's entrypoint. Output goes to stderr — pretty on a terminal, JSON lines otherwise (ALINEO_LOG_FORMAT=pretty|json overrides); ALINEO_LOG=agent=debug,alineod=info sets per-component levels and ALINEO_LOG_FILE writes to a file instead.

    Each of those lines now carries the agent's spec name (and the runId when the caller supplied one, as alineod does), so output from several agents loading at once is attributable. Best-effort cleanups that fail — most importantly closing a sandbox after a failed load, which can leak it — now log a warn instead of being swallowed, and an onPermission handler that throws is logged instead of vanishing.

    alineo-cli turns logging on at info for its subcommands, so alineo spawn, alineo prompt, … still show progress — now on stderr rather than stdout, which also keeps --json output on stdout free of progress lines. ALINEO_LOG_LEVEL=warn (or silent) quiets it. The interactive TUI is unaffected.

    @alineo-labs/core: ILogger, LogLevel, ConsoleLogger and noopLogger moved to @alineo-labs/logger. @alineo-labs/core keeps re-exporting them unchanged, so existing imports still work; prefer importing from @alineo-labs/logger.

Patch Changes

  • 7f2f39d: No behavior change: alineo init's Docker orchestration, alineo.config.json/server.toml handling, and the Pi provider API key list were hand-copied between alineo-cli and alineo-mcp (justified at the time by alineo-cli only publishing its bin, not these internals as a library entry point) and had already drifted — the alineo-mcp copy was missing rationale comments the alineo-cli copy had, with no shared import or test to signal when one needed the same fix as the other. Both are now generated from one internal @alineo-labs/cli-shared module (private, never published — bundled into each consumer's own dist at build time), so alineo init and the alineo_init MCP tool can no longer silently diverge.

  • 7f2f39d: Fix alineo init (and the alineo_init MCP tool) leaving alineod unreachable on Windows and Mac.

    alineod was started with --network host so it could reach OpenSandbox at the same 127.0.0.1 address a bare bun run start would — but Docker Desktop for Windows/Mac only publishes --network host container ports to the host behind an opt-in "Enable host networking" setting, so alineod would report healthy internally while alineo_init's own health check (and everything else) timed out reaching it.

    On Windows/Mac, alineod now runs on the default bridge network with an explicit port mapping and reaches OpenSandbox via host.docker.internal, which Docker Desktop resolves without any special configuration; OpenSandbox's own eip is set to match so alineod can also follow the sandbox proxy URLs it hands back. Linux is unaffected — it keeps --network host, which works there natively. alineo_init also now detects and recreates a container that Docker reports as "running" but isn't actually reachable (the exact failure mode this fixes), and regenerates server.toml (restarting whatever's already running) when its eip no longer matches what the current platform needs, so existing installs pick up the fix on their next init rather than staying stuck.

    Trade-off on Windows/Mac: a host-based client talking to the same alineo init-managed OpenSandbox directly (not through alineod) can no longer follow its sandbox proxy URLs, since the host can't reliably resolve host.docker.internal back to itself. Use uvx opensandbox-server instead for that case (see the root CLAUDE.md's "Local OpenSandbox setup").

  • 7f2f39d: Fix regressions from the Windows/Mac alineo init networking fix: server.toml is no longer clobbered on every init (only its eip line is patched in place, preserving hand-edited networkPolicy/credentialProxy/egress settings), the alineod reachability probe no longer false-negatives a healthy-but-slow-to-answer container into a destructive recreate, a wrong host-networking platform guess now falls back to bridge networking + host.docker.internal automatically instead of leaving alineod permanently unreachable, readConfig() no longer crashes on a partial defaults block in alineo.config.json, and alineo add's registryDependencies resolution now detects circular dependencies instead of looping forever.

CLIv0.2.0· September 13, 2026

Minor Changes

  • b9c30e7: New alineo steer <sandbox-id> <message> — redirect a running session without restarting it. Uses the new Alineo.reattach() (see the sibling alineo changeset), so a mid-turn session isn't interrupted the way alineo prompt (which goes through Alineo.resume()) would. Meant to be run by one session to redirect another it doesn't own the control plane for — a parent redirecting a child it forked being the common case.

    Pi extension guidance (pi-extension/alineo.ts) updated to mention it alongside fork/ prompt/kill, including the timing caveat: steer lands after the target's current tool call finishes, before its next decision — it does not interrupt work already in progress. Use alineo kill instead for an immediate stop.

  • b9c30e7: alineo init now also pulls and starts alineod (ghcr.io/drejt/alineod:latest) alongside OpenSandbox, on --network host so it can reach OpenSandbox at the same 127.0.0.1:8080 a bare bun run start would — a bridge-network container can't resolve the sandbox proxy URLs OpenSandbox hands back, which are built from its own configured eip. No model API key is required to start; agents whose spec references a missing one will fail at spawn time, not at daemon startup. Re-running init is idempotent: an already-running or stopped alineo-alineod container is left running or restarted, never recreated.

    Model-agnostic: init forwards whichever of Pi's ~30 supported provider API key env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, ... — see packages/cli/src/pi-model-keys.ts) are already set in the operator's shell into the container, not just NVIDIA_API_KEY. An AgentSpec's env map is resolved from process.env inside whichever process calls Alineo.load()/.spawn(), so any Pi-supported provider works as long as its key is set locally. Amazon Bedrock and Google Vertex aren't covered yet — both need a mounted credentials file or IAM role chain, not just an env var.

Patch Changes

  • 68a30c2: Narrow a few parameter types to the SandboxHandle / Alineo members they actually use, so callers (and tests) can pass a structural stand-in instead of a cast: PiAdapter's install/configure/startBridge take PiSandbox, EgressApprovalGate.bind() takes EgressApprovalSandbox, the Flue alineo() factory takes AlineoSandbox, flushOps() takes SandboxLike, and collectReply() takes Pick<Alineo, "prompt">. Every existing call site still type-checks; there is no runtime change.
SDKv0.5.0· September 13, 2026

Minor Changes

  • c9c8f2f: BREAKING: the SDK ledger's event names are namespaced. LedgerEvent members are unchanged; their values are not:

    sandbox_created    → sandbox.created        exec_start    → exec.started
    checkpoint_created → sandbox.checkpoint_created   exec_event    → exec.output
    credential_bound   → credential.bound       exec_complete → exec.completed
    run_started        → workflow.started       checkpoint    → step.checkpointed
    

    run_started meant a workflow run here and a swarm run in alineod. It is workflow.started now, and run.started belongs to alineod alone.

    LedgerEvent.Snapshot is deprecated and emits sandbox.checkpoint_created — the same value CheckpointCreated has, because that is what it always recorded: a sandbox checkpoint the workflow engine happened to take.

    Code using the LedgerEvent enum keeps compiling. Code comparing entry.event against a raw string does not, and neither does a query filtering on one.

    Existing databases migrate on connect() — both the sqlite and postgres adapters run a one-time, idempotent rename of alineo_events.event. It matters that it runs before any read: getSandboxDetails aggregates on literal names, so an unmigrated row does not error, it silently stops existing. One-way: an older SDK reading a migrated database would find sessions it cannot see.

    Both adapters share one statement from @alineo-labs/ledger — a single CASE over one scan rather than ~25 UPDATEs, because event is unindexed on that table.

    Sandbox accepts an @internal sink that receives a LedgerEnvelope for every event, beside the ledger write. Synchronous, isolated, and it runs before the ledger queue, so a slow or broken exporter can neither delay nor fail the operation it is exporting.

    Unchanged: the harness AgentEvent stream (tool_start, text, …). Those are the SDK's public streaming API, not what it stores; alineod translates them at its own boundary.

SDKv0.4.0· September 5, 2026

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.
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.

    Compaction — compactSemanticMemory() (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 binding — createMemoryLifecycleHooks(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 memory — episodicRecall()'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: drej → alineo (import { Drej } from "drej" → import { Alineo } from "alineo"). DrejError/DrejOptions → AlineoError/AlineoOptions.
    • CLI: drejx → alineo-cli (npm package name), binary command drejx → alineo (drejx init → alineo init, etc). ~/.config/drejx/ → ~/.config/alineo/, project-local drej.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).

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

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.2

Patch Changes

  • 5ff090b: client.connect() accepts allowPaused: true to connect to a Paused sandbox. The handle comes back marked paused — exec() throws until resume().