{"_id":"@alineo-labs/agent","_rev":"2-4eddd9103bb44e94f748f6becb0b1149","name":"@alineo-labs/agent","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@alineo-labs/agent","version":"0.1.0","license":"Apache-2.0","_id":"@alineo-labs/agent@0.1.0","maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"homepage":"https://github.com/DrejT/alineo#readme","bugs":{"url":"https://github.com/DrejT/alineo/issues"},"dist":{"shasum":"08c1b8c9c8f60ae298d7870adb5c5fe5dc41ff24","tarball":"https://registry.npmjs.org/@alineo-labs/agent/-/agent-0.1.0.tgz","fileCount":5,"integrity":"sha512-X+AWLQVnMgRcRrFC089YC08+USrvPLpPBCEuOxXMzbRm5f6exrupHrtpZYov4qIhdbBziiUIaHOg8j50+RjPFQ==","signatures":[{"sig":"MEYCIQDJ9r1IIEzr6R86xn26pxdwqvKZ1cdz/3vAeAAWNoTd3AIhANx9caMnPlTU3fLa62dQbFo8TZW2nhU5j+cQffjKZ2nl","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alineo-labs%2fagent@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":115582},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"85844a1b5aae067c377cbeb8dd692f3e7c29efa3","scripts":{"test":"bun test","build":"tsdown"},"_npmUser":{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"},"repository":{"url":"git+https://github.com/DrejT/alineo.git","type":"git","directory":"packages/agent"},"_npmVersion":"10.9.8","description":"Run [Pi](https://pi.ai) coding agents inside isolated [alineo](https://alineo.tech) sandbox containers. Pi can read and write files, run shell commands, and execute scripts — streamed back through a simple TypeScript API.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"alineo":"0.1.0","@alineo-labs/core":"0.1.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"0.22.3","bun-types":"1.3.14","typescript":"6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/agent_0.1.0_1786774285843_0.21199241520506207","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alineo-labs/agent","version":"0.1.1","license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/DrejT/alineo.git","directory":"packages/agent"},"type":"module","main":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"import":"./dist/index.mjs","types":"./dist/index.d.mts"}},"publishConfig":{"access":"public","provenance":true},"scripts":{"build":"tsdown","test":"bun test"},"dependencies":{"@alineo-labs/core":"0.1.0","alineo":"0.1.0"},"devDependencies":{"bun-types":"1.3.14","tsdown":"0.22.3","typescript":"6.0.3"},"_id":"@alineo-labs/agent@0.1.1","gitHead":"f186ee485cf4ac1834d6f318f2bca970252b3215","description":"Run [Pi](https://pi.ai) coding agents inside isolated [alineo](https://alineo.tech) sandbox containers. Pi can read and write files, run shell commands, and execute scripts — streamed back through a simple TypeScript API.","bugs":{"url":"https://github.com/DrejT/alineo/issues"},"homepage":"https://github.com/DrejT/alineo#readme","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-uSyFeSV3GLvrVZMOME887++dueNG0o5o0w+XtHTSR7eb0ua+rKGo5AUKOdR3xNAlVneSJGLogt44cKPXGT5K0A==","shasum":"96fc5b9992c26564df702597e6eee24ffa6cd02c","tarball":"https://registry.npmjs.org/@alineo-labs/agent/-/agent-0.1.1.tgz","fileCount":5,"unpackedSize":116379,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alineo-labs%2fagent@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEIuGmSu8lMOVwhYrLqKEvOu6i3w/C3QKU5rs9cHFt+3AiAHZvYzsllUINk2gWnhROio0/88/G9QcBPrHancX40q3g=="}]},"_npmUser":{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"},"directories":{},"maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/agent_0.1.1_1786890855647_0.6506528456063747"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-15T06:11:25.702Z","modified":"2026-08-16T14:34:16.157Z","0.1.0":"2026-08-15T06:11:25.987Z","0.1.1":"2026-08-16T14:34:15.776Z"},"bugs":{"url":"https://github.com/DrejT/alineo/issues"},"license":"Apache-2.0","homepage":"https://github.com/DrejT/alineo#readme","repository":{"type":"git","url":"git+https://github.com/DrejT/alineo.git","directory":"packages/agent"},"description":"Run [Pi](https://pi.ai) coding agents inside isolated [alineo](https://alineo.tech) sandbox containers. Pi can read and write files, run shell commands, and execute scripts — streamed back through a simple TypeScript API.","maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"readme":"# @alineo-labs/agent\n\nRun [Pi](https://pi.ai) coding agents inside isolated [alineo](https://alineo.tech) sandbox containers. Pi can read and write files, run shell commands, and execute scripts — streamed back through a simple TypeScript API.\n\n```bash\nbun add @alineo-labs/agent\n```\n\n**[Full documentation →](https://docs.alineo.tech/docs/agent)**\n\n---\n\n## Quickstart\n\nCreate an agent spec (`agents/my-agent.json`):\n\n```json\n{\n  \"$schema\": \"https://registry.alineo.tech/spec/agent.json\",\n  \"name\": \"my-agent\",\n  \"cli\": \"pi\",\n  \"model\": \"gemini-flash-latest\",\n  \"packages\": [\"python3\"],\n  \"env\": { \"GEMINI_API_KEY\": \"${GEMINI_API_KEY}\" },\n  \"resources\": { \"cpu\": \"1000m\", \"memory\": \"2Gi\" }\n}\n```\n\n```ts\nimport { Agent, textOnly } from \"@alineo-labs/agent\";\nimport { SQLiteAdapter } from \"@alineo-labs/sqlite\";\n\nconst adapter = new SQLiteAdapter(\"./.alineo/ledger.db\");\nconst agent = await Agent.load(\"./agents/my-agent.json\", { adapter });\ntry {\n  for await (const chunk of textOnly(agent.prompt(\"Write and run a Python hello world script.\"))) {\n    process.stdout.write(chunk);\n  }\n} finally {\n  await agent.close();\n}\n```\n\n`opts.adapter` is required — `@alineo-labs/agent` has no storage-adapter dependency of its own, so you choose: `new SQLiteAdapter(path)` from `@alineo-labs/sqlite` for local dev, or `new PostgresAdapter(connectionString)` from `@alineo-labs/postgres` for production.\n\n---\n\n## Agent spec\n\nThe spec JSON controls the agent's environment, model, and workspace setup.\n\n| Field        | Type                     | Description                                                                                    |\n| ------------ | ------------------------ | ---------------------------------------------------------------------------------------------- |\n| `name`       | `string`                 | Unique identifier, used as the sandbox session name                                            |\n| `cli`        | `\"pi\"`                   | CLI to run (currently only `\"pi\"`)                                                             |\n| `cliVersion` | `string?`                | Pin to a specific Pi version, e.g. `\"0.80.2\"`. Defaults to latest.                             |\n| `model`      | `string?`                | Model ID passed to Pi via `--model`                                                            |\n| `provider`   | `string?`                | AI provider passed via `--provider`. Omit for direct Google API key.                           |\n| `packages`   | `string[]?`              | APT packages to install before Pi. e.g. `[\"git\", \"python3\"]`                                   |\n| `env`        | `Record<string,string>?` | Env vars in the sandbox. Values may reference host env: `\"${MY_KEY}\"`                          |\n| `resources`  | `object?`                | CPU/memory limits: `{ cpu: \"1000m\", memory: \"2Gi\" }`                                           |\n| `setup`      | `SetupStep[]?`           | Workspace setup steps (see below)                                                              |\n| `spawnDepth` | `number?`                | Nesting-depth budget for `agent.spawn()` — see [Spawning child agents](#spawning-child-agents) |\n| `maxAgents`  | `number?`                | Optional cap on total descendants for this lineage — see below                                 |\n\n### Setup steps\n\n`setup` runs bash commands after Pi CLI install, before the snapshot is taken. Changes to any step automatically invalidate the snapshot cache.\n\n```json\n{\n  \"name\": \"my-agent\",\n  \"cli\": \"pi\",\n  \"setup\": [\n    { \"name\": \"Create workspace\", \"run\": \"mkdir -p /workspace\" },\n    { \"name\": \"Install deps\", \"run\": \"npm install\", \"cwd\": \"/workspace\" },\n    { \"name\": \"Seed data\", \"run\": \"node scripts/seed.js\", \"cwd\": \"/workspace\" }\n  ]\n}\n```\n\nEach step:\n\n| Field  | Type      | Description                                                       |\n| ------ | --------- | ----------------------------------------------------------------- |\n| `name` | `string`  | Human-readable label shown in logs and included in the setup hash |\n| `run`  | `string`  | Bash command to execute                                           |\n| `cwd`  | `string?` | Working directory. Runs as `cd <cwd> && <run>`                    |\n\n---\n\n## Snapshotting\n\nOn first load, `Agent.load()` installs the Pi CLI and any `setup` steps, then checkpoints the sandbox. Subsequent loads restore from that snapshot — skipping the install entirely.\n\n```\nLoad 1 (cold):   sandbox → Pi install → setup steps → checkpoint → bridge   ~50s\nLoad 2 (warm):   snapshot restore → bridge                                   ~5s\n```\n\nThe snapshot is invalidated automatically when `cli`, `cliVersion`, `packages`, or `setup` change.\n\n```ts\n// adapter: an IStorageAdapter — SQLiteAdapter or PostgresAdapter, see Quickstart\nconst agent = await Agent.load(\"./agents/my-agent.json\", { adapter });\nconsole.log(agent.fromSnapshot); // false on first load, true after\n\n// Force a full reinstall:\nconst agent = await Agent.load(\"./agents/my-agent.json\", { adapter, rebuild: true });\n```\n\n---\n\n## Streaming\n\n`agent.prompt()` and `agent.bash()` return an `AgentStream` — an `AsyncIterable<AgentEvent>`:\n\n```ts\ntype AgentEvent =\n  | { type: \"text\"; text: string }\n  | { type: \"tool_start\"; toolCallId: string; toolName: string; args: unknown }\n  | { type: \"tool_update\"; toolCallId: string; toolName: string; partialResult: unknown }\n  | { type: \"tool_end\"; toolCallId: string; toolName: string; result: unknown; isError: boolean }\n  | { type: \"extension_ui\"; method: string; params: unknown; isDialog: boolean; requestId?: string }\n  | {\n      type: \"auto_retry_start\";\n      attempt: number;\n      maxAttempts: number;\n      delayMs: number;\n      errorMessage: string;\n    }\n  | { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n  | { type: \"agent_start\" }\n  | { type: \"agent_end\"; messages: unknown[] }\n  | { type: \"turn_start\"; turnIndex: number; timestamp: number }\n  | { type: \"turn_end\"; turnIndex: number; message: unknown; toolResults: unknown[] }\n  | { type: \"message_start\"; message: unknown }\n  | { type: \"message_update\"; message: unknown; delta: unknown }\n  | { type: \"message_end\"; message: unknown }\n  | { type: \"queue_update\"; steering: string[]; followUp: string[] }\n  | { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n  | {\n      type: \"compaction_end\";\n      reason: string;\n      result: object | null;\n      aborted: boolean;\n      willRetry: boolean;\n    }\n  | { type: \"extension_error\"; extensionPath: string; event: string; error: string };\n```\n\nUse `textOnly()` to filter to just the text chunks (equivalent to the old `PromptStream` behavior):\n\n```ts\nimport { Agent, textOnly } from \"@alineo-labs/agent\";\n\nfor await (const chunk of textOnly(agent.prompt(\"Summarise this repo.\"))) {\n  process.stdout.write(chunk);\n}\n```\n\n### Tool call observability\n\nIterate the raw stream to see every tool Pi uses:\n\n```ts\nfor await (const ev of agent.prompt(\"Run /workspace/script.py with python3.\")) {\n  switch (ev.type) {\n    case \"text\":\n      process.stdout.write(ev.text);\n      break;\n    case \"tool_start\":\n      console.log(`[tool] ${ev.toolName} args=${JSON.stringify(ev.args)}`);\n      break;\n    case \"tool_end\":\n      console.log(`[tool] ${ev.toolName} done  isError=${ev.isError}`);\n      break;\n  }\n}\n```\n\n---\n\n## API reference\n\n### Loading and lifecycle\n\n#### `Agent.load(specPath, opts)`\n\nLoad a spec, spin up a sandbox, install Pi, run setup steps, and return a ready `Agent`. Restores from snapshot on subsequent calls. `opts.adapter` is required (see [Quickstart](#quickstart)).\n\n```ts\nconst agent = await Agent.load(\"./agents/my-agent.json\", { adapter });\nconst agent = await Agent.load(\"./agents/my-agent.json\", { adapter, rebuild: true });\n```\n\n#### `Agent.resume(sandboxId, opts)`\n\nReconnect to an existing sandbox after the host process has exited. Only restarts the bridge — Pi and the workspace are untouched. `opts.adapter` is required.\n\n```ts\n// Original process saved agent.sandboxId somewhere...\nconst agent = await Agent.resume(savedSandboxId, { adapter });\n// Or provide the spec explicitly:\nconst agent = await Agent.resume(savedSandboxId, { adapter, specPath: \"./agents/my-agent.json\" });\n```\n\n#### `Agent.attach(sandboxId, opts)`\n\nConnect to an already-running sandbox **without** touching its Pi bridge — unlike `resume()`, which kills and restarts the bridge process. Use this when you only need `.spawn()`/`.sandbox`, not `.prompt()`/`.bash()` (the returned `Agent` has no bridge, so those throw).\n\nThe main caller is `alineo fork`: it runs as a fresh CLI process started BY the very Pi bash-tool call it's attaching to (a session forking a child from inside its own turn) — going through `resume()` there would kill the bridge currently running the call itself.\n\n```ts\nconst self = await Agent.attach(process.env.ALINEO_SANDBOX_ID!, {\n  adapter,\n  name: \"my-session\",\n});\nconst child = await self.spawn(\"./agents/worker.json\");\n```\n\n#### `agent.close()`\n\nStop the sandbox container and release all resources. Always call in a `finally` block.\n\n---\n\n### Spawning child agents\n\n#### `agent.spawn(childSpecPath, opts?)`\n\nFork **this agent's own live sandbox** — filesystem, installed packages, checked-out state, everything currently on disk — into a brand-new independent sandbox running its own Pi bridge. Unlike `Agent.load()` (always starts from a spec's own snapshot) or `fork()`/`clone()` (Pi's own conversation-branching — same container, same bridge, new session branch), this is sandbox-level forking: the child sees exactly what this agent's sandbox sees right now, including uncommitted work. No install/setup steps run — the child inherits whatever is already installed on this agent's sandbox.\n\n```ts\nconst child = await agent.spawn(\"./agents/worker.json\", { spawnDepth: 2, maxAgents: 5 });\ntry {\n  for await (const chunk of textOnly(child.prompt(\"Handle the auth module\"))) {\n    process.stdout.write(chunk);\n  }\n} finally {\n  await child.close();\n}\n```\n\nRefuses immediately unless this agent's own spawn-depth budget (`spawnDepth` in the spec, or `opts.spawnDepth` to override) is a positive integer — `0` means no budget left, `undefined` means spawning was never enabled. Each spawn force-decrements the budget (`current - 1`) into the child's env, regardless of what the child's own spec says.\n\n`maxAgents` (spec field or `opts.maxAgents`) is a separate, optional ceiling on total descendants for this lineage, independent of nesting depth. Unset means uncapped for this dimension — only `spawnDepth` gates whether spawning is allowed at all. **Not** coordinated across sibling branches spawned in parallel; it's a per-lineage counter.\n\n---\n\n### Streaming\n\n#### `agent.prompt(message, opts?)`\n\nSend a message to Pi and stream the response as `AgentStream`.\n\n```ts\nfor await (const chunk of textOnly(agent.prompt(\"Explain this file.\"))) {\n  process.stdout.write(chunk);\n}\n```\n\n#### `agent.bash(command)`\n\nRun a shell command inside Pi's working context and stream stdout as `AgentStream`.\n\n```ts\nfor await (const chunk of textOnly(agent.bash(\"ls -la /workspace\"))) {\n  process.stdout.write(chunk);\n}\n```\n\n---\n\n### Mid-flight control\n\n#### `agent.steer(message)`\n\nRedirect Pi's current response mid-flight. Pi acknowledges and adjusts.\n\n```ts\nconst stream = textOnly(agent.prompt(\"Write an essay on every sorting algorithm...\"));\nsetTimeout(() => agent.steer(\"Stop — give me 3 bullet points instead.\"), 1500);\nfor await (const chunk of stream) process.stdout.write(chunk);\n```\n\n#### `agent.followUp(message)`\n\nQueue a message for Pi to process after it finishes the current task.\n\n#### `agent.abort()`\n\nInterrupt the current in-progress response immediately.\n\n---\n\n### Session management\n\n#### `agent.newSession()`\n\nStart a fresh Pi conversation, clearing all context. Filesystem and workspace are unchanged.\n\n#### `agent.clone()`\n\nBranch the current Pi session at the current position. Returns `{ cancelled: boolean }`.\n\n#### `agent.fork(entryId)`\n\nBranch from a specific message entry in the conversation history. Returns `{ text, cancelled }`.\n\n#### `agent.switchSession(sessionPath)`\n\nSwitch Pi to a different session file on disk.\n\n#### `agent.getMessages()`\n\nRetrieve the full conversation history for the current session.\n\n```ts\nconst messages = await agent.getMessages();\nconsole.log(messages.length, \"messages\");\n```\n\n---\n\n### Model control\n\n#### `agent.setModel(provider, modelId)`\n\nSwitch Pi to a specific model. Returns the activated `PiModel`.\n\n#### `agent.cycleModel()`\n\nCycle to the next configured model. Returns `{ model, thinkingLevel, isScoped }` or `null` if only one model is configured.\n\n#### `agent.getAvailableModels()`\n\nList all models available to Pi under the current provider configuration.\n\n#### `agent.setThinkingLevel(level)`\n\nSet Pi's reasoning level (`\"low\" | \"medium\" | \"high\"`). Only effective on models that support extended thinking.\n\n#### `agent.cycleThinkingLevel()`\n\nCycle Pi's thinking level. Returns `{ level }` or `null` if the current model doesn't support thinking.\n\n---\n\n### Context management\n\n#### `agent.setAutoCompaction(enabled)`\n\nEnable or disable Pi's automatic context compaction.\n\n#### `agent.compact(customInstructions?)`\n\nManually trigger Pi's context compaction. Returns `{ tokensBefore, estimatedTokensAfter }`.\n\n---\n\n### Reliability\n\n#### `agent.setAutoRetry(enabled)`\n\nEnable or disable Pi's automatic retry on transient errors (429, 500, 502, 503, 504). Auto-retry is **on by default**: 3 attempts with exponential backoff (2 s / 4 s / 8 s). Disable it when you want to handle failures yourself via `auto_retry_start` / `auto_retry_end` events.\n\n```ts\nawait agent.setAutoRetry(false); // take full control\n```\n\n#### `agent.abortRetry()`\n\nAbort an in-progress auto-retry immediately. Pi fails the current operation and emits `auto_retry_end` with `success: false`.\n\n#### `agent.abortBash()`\n\nAbort a currently-executing bash command without cancelling the whole prompt. No-op when no bash is running.\n\n---\n\n### Session inspection\n\n#### `agent.getSessionStats()`\n\nRetrieve token usage, cost, and message counts for the current session. Returns a `SessionStats` object.\n\n```ts\nconst stats = await agent.getSessionStats();\nconsole.log(`${stats.tokens.total} tokens used, $${stats.cost.toFixed(6)} cost`);\n```\n\n#### `agent.getLastAssistantText()`\n\nRetrieve the text of Pi's most recent assistant response without iterating the stream. Returns `null` if Pi hasn't responded yet.\n\n#### `agent.getForkMessages()`\n\nList the fork entry points available in the current session. Each entry has `entryId` (pass to `fork()`) and `text`.\n\n#### `agent.getCommands()`\n\nList Pi's available slash commands, including extensions, prompt templates, and skills. Returns `PiSlashCommand[]`.\n\n```ts\nconst cmds = await agent.getCommands();\nfor (const cmd of cmds) console.log(`/${cmd.name} [${cmd.source}]`);\n```\n\n#### `agent.setSessionName(name)`\n\nSet a display name for the current Pi session.\n\n#### `agent.exportHtml(outputPath?)`\n\nExport a static HTML transcript of the session to the sandbox filesystem. Returns `{ path }` — the container path of the file. Use `agent.sandbox.readFile(path)` to retrieve it.\n\n```ts\nconst { path } = await agent.exportHtml();\nconst html = await agent.sandbox.readFile(path);\n```\n\n---\n\n### Advanced control\n\n#### `agent.setSteeringMode(mode)`\n\nControl how Pi processes queued steering messages: `\"all\"` applies all at once, `\"one-at-a-time\"` applies them sequentially.\n\n#### `agent.setFollowUpMode(mode)`\n\nControl how Pi processes queued follow-up messages: `\"all\"` sends all at once, `\"one-at-a-time\"` sends them sequentially.\n\n---\n\n### Environment and debugging\n\n#### `agent.setEnv(vars)`\n\nSet or update env vars in the running container. Restarts Pi so it picks up the new env.\n\n```ts\nawait agent.setEnv({ DATABASE_URL: \"postgres://...\" });\n```\n\n#### `agent.getLogs()`\n\nRetrieve recent bridge logs (ring-buffered, last 200 entries).\n\n#### `agent.sandbox`\n\nDirect access to the underlying `Sandbox` — run commands, read/write files, or inspect state independently of Pi.\n\n```ts\nawait agent.sandbox.writeFile(\"/workspace/input.txt\", data);\nconst { stdout } = await agent.sandbox.exec(\"wc -l /workspace/input.txt\");\nconst result = await agent.sandbox.readFile(\"/workspace/output.txt\");\n```\n\n---\n\n## Properties\n\n| Property             | Type      | Description                                    |\n| -------------------- | --------- | ---------------------------------------------- |\n| `agent.sandboxId`    | `string`  | OpenSandbox container ID                       |\n| `agent.name`         | `string`  | Agent name from the spec                       |\n| `agent.sandbox`      | `Sandbox` | Underlying alineo `Sandbox` object             |\n| `agent.fromSnapshot` | `boolean` | `true` when restored from snapshot (fast path) |\n\n---\n\n## License\n\nApache 2.0\n","readmeFilename":"README.md"}