Changelog
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.
Before Now alineo spawn <spec>alineo start <spec>alineo fork <name> <child-spec>alineo spawn <parent> <child-spec>alineo kill <sandbox-id>alineo stop <sandbox-id>spawnused to mean the opposite of whatAlineo.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.
spawndid not disappear — it changed meaning, so an alias would silently do the wrong thing with the right-looking command.alineo spawn ./spec.jsonnow fails on its arguments instead, which is the outcome you want: its first argument is a parent session name. Usealineo 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 (
sstops a session;kstill works), and every help, error and doc string that quoted a verb.scripts/check-vocabulary.tsnow fails CI on a command name that is not in@alineo-labs/schema'sVERBS, and on any text quoting a command that does not exist — the check that would have caughtalineo 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, andcli: "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
cligets 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 listnow heads that columnHARNESS, andalineo-mcp'sSpecSummary.cliisSpecSummary.harness. The published JSON Schema atregistry.alineo.tech/spec/agent.jsonrequiresharness, so an editor validating an old spec against$schemawill 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.jsonis located by walking up from the working directory (bounded by a.gitdirectory,$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, thenalineo.config.json, thenALINEO_CONFIG_CONTENT(inline JSON), thenALINEO_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()acceptopts.configto 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.jsonis merged under a project config instead of being ignored whenever a project config exists. The SDK previously ignored the global config entirely whilealineo-clialready read it, so on a machine with a global config and no project config the two disagreed aboutadapterPath— and therefore used different agent snapshot caches. They now agree. If that describes your setup, the firstAlineo.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/agentsDirdefaults while a global config set different ones will now follow the global config. Passopts.config, or set the value explicitly in a projectalineo.config.json, to pin it. -
31e97e5:
alineo initnow sets up the OpenSandbox server so a sandbox with anetworkPolicyorcredentialProxycan start.Before, a sandbox with a
networkPolicyfailed withEgress sidecar did not become ready within 30s … Connection refusedalthough the sidecar was healthy. The server runs in a container and, with no[docker].host_ip, probed the sidecar at its own127.0.0.1.initnow writeshost_ip = "host.docker.internal"and starts the container with--add-host host.docker.internal:host-gateway. Aserver.tomlfrom an earlierinitgets the line added, and the OpenSandbox container is recreated to match (its snapshot db is in a bind mount, so nothing is lost).initalso pullsopensandbox/server:latestwhen it creates the container.docker runreuses an image already on disk, however old, and an oldlatestcan mismatch the pinned egress image.The docs now say that
networkPolicyandcredentialProxyare rejected by a server using the gVisor runtime. -
14ecd92:
alineo initnow starts OpenSandbox and alineod with--restart unless-stopped, and sets that policy on containers an earlierinitcreated. Before, a reboot or a Docker daemon restart left both down — OpenSandbox first, so nothing worked untilinitran again, and alineod's crash recovery never got the chance to run because nothing restarted alineod.docker stopstill sticks. -
8baafa7:
ControlClient.listSandboxes()now returns every sandbox, not just the first 20. OpenSandbox paginates withpage/pageSizeand ignores thelimit/offsetthis client was sending, so every listing stopped at the server's default page. The visible symptom:alineo agentscross-checks the ledger against the live list, so with more than 20 sandboxes running it reported the rest as gone.limitandoffsetstill work — they are applied after every page is fetched.
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 toconsole.logprogress lines ([agent] starting sandbox…,[agent] bridge ready 3210ms, …) unconditionally, with no way to silence them. They now go through the logger as componentagent, which emits nothing until logging is on. To see them again, setALINEO_LOG_LEVEL=info(no code change needed) or callinstallLoggerFromEnv({ defaultLevel: "info" })/installLogger()at your app's entrypoint. Output goes to stderr — pretty on a terminal, JSON lines otherwise (ALINEO_LOG_FORMAT=pretty|jsonoverrides);ALINEO_LOG=agent=debug,alineod=infosets per-component levels andALINEO_LOG_FILEwrites to a file instead.Each of those lines now carries the agent's spec
name(and therunIdwhen 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 awarninstead of being swallowed, and anonPermissionhandler that throws is logged instead of vanishing.alineo-cliturns logging on atinfofor its subcommands, soalineo spawn,alineo prompt, … still show progress — now on stderr rather than stdout, which also keeps--jsonoutput on stdout free of progress lines.ALINEO_LOG_LEVEL=warn(orsilent) quiets it. The interactive TUI is unaffected.@alineo-labs/core:ILogger,LogLevel,ConsoleLoggerandnoopLoggermoved to@alineo-labs/logger.@alineo-labs/corekeeps 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.tomlhandling, and the Pi provider API key list were hand-copied betweenalineo-cliandalineo-mcp(justified at the time byalineo-clionly publishing itsbin, not these internals as a library entry point) and had already drifted — thealineo-mcpcopy was missing rationale comments thealineo-clicopy 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-sharedmodule (private, never published — bundled into each consumer's owndistat build time), soalineo initand thealineo_initMCP tool can no longer silently diverge. -
7f2f39d: Fix
alineo init(and thealineo_initMCP tool) leaving alineod unreachable on Windows and Mac.alineod was started with
--network hostso it could reach OpenSandbox at the same127.0.0.1address a barebun run startwould — but Docker Desktop for Windows/Mac only publishes--network hostcontainer ports to the host behind an opt-in "Enable host networking" setting, so alineod would report healthy internally whilealineo_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 owneipis 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_initalso now detects and recreates a container that Docker reports as "running" but isn't actually reachable (the exact failure mode this fixes), and regeneratesserver.toml(restarting whatever's already running) when itseipno longer matches what the current platform needs, so existing installs pick up the fix on their nextinitrather 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 resolvehost.docker.internalback to itself. Useuvx opensandbox-serverinstead for that case (see the rootCLAUDE.md's "Local OpenSandbox setup"). -
7f2f39d: Fix regressions from the Windows/Mac
alineo initnetworking fix:server.tomlis no longer clobbered on everyinit(only itseipline is patched in place, preserving hand-editednetworkPolicy/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.internalautomatically instead of leaving alineod permanently unreachable,readConfig()no longer crashes on a partialdefaultsblock inalineo.config.json, andalineo add'sregistryDependenciesresolution now detects circular dependencies instead of looping forever.
Minor Changes
-
b9c30e7: New
alineo steer <sandbox-id> <message>— redirect a running session without restarting it. Uses the newAlineo.reattach()(see the siblingalineochangeset), so a mid-turn session isn't interrupted the wayalineo prompt(which goes throughAlineo.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 alongsidefork/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. Usealineo killinstead for an immediate stop. -
b9c30e7:
alineo initnow also pulls and startsalineod(ghcr.io/drejt/alineod:latest) alongside OpenSandbox, on--network hostso it can reach OpenSandbox at the same127.0.0.1:8080a barebun run startwould — a bridge-network container can't resolve the sandbox proxy URLs OpenSandbox hands back, which are built from its own configuredeip. 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-runninginitis idempotent: an already-running or stoppedalineo-alineodcontainer is left running or restarted, never recreated.Model-agnostic:
initforwards whichever of Pi's ~30 supported provider API key env vars (ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY,OPENROUTER_API_KEY, ... — seepackages/cli/src/pi-model-keys.ts) are already set in the operator's shell into the container, not justNVIDIA_API_KEY. AnAgentSpec's env map is resolved fromprocess.envinside whichever process callsAlineo.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/Alineomembers they actually use, so callers (and tests) can pass a structural stand-in instead of a cast:PiAdapter'sinstall/configure/startBridgetakePiSandbox,EgressApprovalGate.bind()takesEgressApprovalSandbox, the Fluealineo()factory takesAlineoSandbox,flushOps()takesSandboxLike, andcollectReply()takesPick<Alineo, "prompt">. Every existing call site still type-checks; there is no runtime change.
Minor Changes
-
c9c8f2f: BREAKING: the SDK ledger's event names are namespaced.
LedgerEventmembers 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.checkpointedrun_startedmeant a workflow run here and a swarm run in alineod. It isworkflow.startednow, andrun.startedbelongs to alineod alone.LedgerEvent.Snapshotis deprecated and emitssandbox.checkpoint_created— the same valueCheckpointCreatedhas, because that is what it always recorded: a sandbox checkpoint the workflow engine happened to take.Code using the
LedgerEventenum keeps compiling. Code comparingentry.eventagainst 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 ofalineo_events.event. It matters that it runs before any read:getSandboxDetailsaggregates 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 singleCASEover one scan rather than ~25UPDATEs, becauseeventis unindexed on that table.Sandboxaccepts an@internalsinkthat receives aLedgerEnvelopefor 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
AgentEventstream (tool_start,text, …). Those are the SDK's public streaming API, not what it stores; alineod translates them at its own boundary.
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.
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.
Patch Changes
- 5ff090b:
client.connect()acceptsallowPaused: trueto connect to a Paused sandbox. The handle comes back marked paused —exec()throws untilresume().