bulkhead-runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +625 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +43 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +24 -0
- package/dist/config/index.js.map +1 -0
- package/dist/credentials/index.d.ts +4 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +3 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/proxy.d.ts +3 -0
- package/dist/credentials/proxy.d.ts.map +1 -0
- package/dist/credentials/proxy.js +11 -0
- package/dist/credentials/proxy.js.map +1 -0
- package/dist/credentials/store.d.ts +7 -0
- package/dist/credentials/store.d.ts.map +1 -0
- package/dist/credentials/store.js +91 -0
- package/dist/credentials/store.js.map +1 -0
- package/dist/credentials/types.d.ts +14 -0
- package/dist/credentials/types.d.ts.map +1 -0
- package/dist/credentials/types.js +2 -0
- package/dist/credentials/types.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/runner.d.ts +7 -0
- package/dist/hooks/runner.d.ts.map +1 -0
- package/dist/hooks/runner.js +20 -0
- package/dist/hooks/runner.js.map +1 -0
- package/dist/hooks/types.d.ts +39 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +2 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/infra/env.d.ts +2 -0
- package/dist/infra/env.d.ts.map +1 -0
- package/dist/infra/env.js +6 -0
- package/dist/infra/env.js.map +1 -0
- package/dist/infra/warning-filter.d.ts +8 -0
- package/dist/infra/warning-filter.d.ts.map +1 -0
- package/dist/infra/warning-filter.js +66 -0
- package/dist/infra/warning-filter.js.map +1 -0
- package/dist/logging/subsystem.d.ts +15 -0
- package/dist/logging/subsystem.d.ts.map +1 -0
- package/dist/logging/subsystem.js +57 -0
- package/dist/logging/subsystem.js.map +1 -0
- package/dist/memory/embeddings-debug.d.ts +2 -0
- package/dist/memory/embeddings-debug.d.ts.map +1 -0
- package/dist/memory/embeddings-debug.js +12 -0
- package/dist/memory/embeddings-debug.js.map +1 -0
- package/dist/memory/embeddings.d.ts +16 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +180 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/fs-utils.d.ts +12 -0
- package/dist/memory/fs-utils.d.ts.map +1 -0
- package/dist/memory/fs-utils.js +24 -0
- package/dist/memory/fs-utils.js.map +1 -0
- package/dist/memory/hybrid.d.ts +46 -0
- package/dist/memory/hybrid.d.ts.map +1 -0
- package/dist/memory/hybrid.js +81 -0
- package/dist/memory/hybrid.js.map +1 -0
- package/dist/memory/index.d.ts +12 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +12 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/internal.d.ts +39 -0
- package/dist/memory/internal.d.ts.map +1 -0
- package/dist/memory/internal.js +292 -0
- package/dist/memory/internal.js.map +1 -0
- package/dist/memory/manager-search.d.ts +61 -0
- package/dist/memory/manager-search.d.ts.map +1 -0
- package/dist/memory/manager-search.js +102 -0
- package/dist/memory/manager-search.js.map +1 -0
- package/dist/memory/mmr.d.ts +63 -0
- package/dist/memory/mmr.d.ts.map +1 -0
- package/dist/memory/mmr.js +165 -0
- package/dist/memory/mmr.js.map +1 -0
- package/dist/memory/query-expansion.d.ts +42 -0
- package/dist/memory/query-expansion.d.ts.map +1 -0
- package/dist/memory/query-expansion.js +776 -0
- package/dist/memory/query-expansion.js.map +1 -0
- package/dist/memory/simple-manager.d.ts +23 -0
- package/dist/memory/simple-manager.d.ts.map +1 -0
- package/dist/memory/simple-manager.js +193 -0
- package/dist/memory/simple-manager.js.map +1 -0
- package/dist/memory/sqlite.d.ts +2 -0
- package/dist/memory/sqlite.d.ts.map +1 -0
- package/dist/memory/sqlite.js +16 -0
- package/dist/memory/sqlite.js.map +1 -0
- package/dist/memory/temporal-decay.d.ts +26 -0
- package/dist/memory/temporal-decay.d.ts.map +1 -0
- package/dist/memory/temporal-decay.js +120 -0
- package/dist/memory/temporal-decay.js.map +1 -0
- package/dist/memory/types.d.ts +95 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +2 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/package.json +36 -0
- package/dist/platform/index.d.ts +3 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +2 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/platform.d.ts +3 -0
- package/dist/platform/platform.d.ts.map +1 -0
- package/dist/platform/platform.js +68 -0
- package/dist/platform/platform.js.map +1 -0
- package/dist/platform/types.d.ts +16 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/types.js +2 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/runtime/agent.d.ts +28 -0
- package/dist/runtime/agent.d.ts.map +1 -0
- package/dist/runtime/agent.js +102 -0
- package/dist/runtime/agent.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/sandbox/cgroup.d.ts +17 -0
- package/dist/sandbox/cgroup.d.ts.map +1 -0
- package/dist/sandbox/cgroup.js +69 -0
- package/dist/sandbox/cgroup.js.map +1 -0
- package/dist/sandbox/index.d.ts +11 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +9 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/ipc.d.ts +23 -0
- package/dist/sandbox/ipc.d.ts.map +1 -0
- package/dist/sandbox/ipc.js +138 -0
- package/dist/sandbox/ipc.js.map +1 -0
- package/dist/sandbox/manager.d.ts +5 -0
- package/dist/sandbox/manager.d.ts.map +1 -0
- package/dist/sandbox/manager.js +245 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/namespace.d.ts +12 -0
- package/dist/sandbox/namespace.d.ts.map +1 -0
- package/dist/sandbox/namespace.js +120 -0
- package/dist/sandbox/namespace.js.map +1 -0
- package/dist/sandbox/proxy-tools.d.ts +14 -0
- package/dist/sandbox/proxy-tools.d.ts.map +1 -0
- package/dist/sandbox/proxy-tools.js +63 -0
- package/dist/sandbox/proxy-tools.js.map +1 -0
- package/dist/sandbox/rootfs.d.ts +15 -0
- package/dist/sandbox/rootfs.d.ts.map +1 -0
- package/dist/sandbox/rootfs.js +163 -0
- package/dist/sandbox/rootfs.js.map +1 -0
- package/dist/sandbox/seccomp.d.ts +13 -0
- package/dist/sandbox/seccomp.d.ts.map +1 -0
- package/dist/sandbox/seccomp.js +120 -0
- package/dist/sandbox/seccomp.js.map +1 -0
- package/dist/sandbox/types.d.ts +68 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +8 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/worker.d.ts +12 -0
- package/dist/sandbox/worker.d.ts.map +1 -0
- package/dist/sandbox/worker.js +84 -0
- package/dist/sandbox/worker.js.map +1 -0
- package/dist/sessions/index.d.ts +2 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/store.d.ts +17 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +48 -0
- package/dist/sessions/store.js.map +1 -0
- package/dist/skills/enablement.d.ts +10 -0
- package/dist/skills/enablement.d.ts.map +1 -0
- package/dist/skills/enablement.js +52 -0
- package/dist/skills/enablement.js.map +1 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/loader.d.ts +8 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +8 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/registry.d.ts +19 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +106 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/utils/boolean.d.ts +6 -0
- package/dist/utils/boolean.d.ts.map +1 -0
- package/dist/utils/boolean.js +28 -0
- package/dist/utils/boolean.js.map +1 -0
- package/dist/utils/run-with-concurrency.d.ts +12 -0
- package/dist/utils/run-with-concurrency.d.ts.map +1 -0
- package/dist/utils/run-with-concurrency.js +40 -0
- package/dist/utils/run-with-concurrency.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +38 -0
- package/dist/utils.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/runner.d.ts +19 -0
- package/dist/workspace/runner.d.ts.map +1 -0
- package/dist/workspace/runner.js +158 -0
- package/dist/workspace/runner.js.map +1 -0
- package/dist/workspace/types.d.ts +36 -0
- package/dist/workspace/types.d.ts.map +1 -0
- package/dist/workspace/types.js +2 -0
- package/dist/workspace/types.js.map +1 -0
- package/dist/workspace/workspace.d.ts +12 -0
- package/dist/workspace/workspace.d.ts.map +1 -0
- package/dist/workspace/workspace.js +76 -0
- package/dist/workspace/workspace.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Peter Steinberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<br />
|
|
3
|
+
<img src="https://img.shields.io/badge/%E2%96%88%E2%96%88%E2%96%88_BULKHEAD-RUNTIME_%E2%96%88%E2%96%88%E2%96%88-000?style=for-the-badge&labelColor=0d1117&color=00ff41" alt="Bulkhead Runtime" />
|
|
4
|
+
<br /><br />
|
|
5
|
+
<strong>Watertight isolation for multi-tenant AI agents.</strong>
|
|
6
|
+
<br /><br />
|
|
7
|
+
<a href="https://www.npmjs.com/package/bulkhead-runtime"><img src="https://img.shields.io/npm/v/bulkhead-runtime?style=flat-square&color=00ff41&labelColor=0d1117" alt="npm" /></a>
|
|
8
|
+
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square&labelColor=0d1117" alt="MIT" />
|
|
9
|
+
<img src="https://img.shields.io/badge/node-%3E%3D22.12-brightgreen?style=flat-square&labelColor=0d1117&logo=node.js&logoColor=white" alt="node" />
|
|
10
|
+
<img src="https://img.shields.io/badge/runtime_deps-3-00ff41?style=flat-square&labelColor=0d1117" alt="deps" />
|
|
11
|
+
<img src="https://img.shields.io/badge/tests-88_passing-00ff41?style=flat-square&labelColor=0d1117" alt="tests" />
|
|
12
|
+
<img src="https://img.shields.io/badge/sandbox-5_isolation_layers-ff6b6b?style=flat-square&labelColor=0d1117" alt="isolation" />
|
|
13
|
+
<img src="https://img.shields.io/badge/crypto-AES--256--GCM-blueviolet?style=flat-square&labelColor=0d1117" alt="crypto" />
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<br />
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
Run 1,000 AI agents on a single Linux box.<br />
|
|
20
|
+
Each in its own OS namespace. Each with private memory, encrypted credentials, and an isolated filesystem.<br />
|
|
21
|
+
<b>No Docker. No cloud. One <code>npm install</code>.</b>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<br />
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install bulkhead-runtime
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createPlatform } from "bulkhead-runtime";
|
|
36
|
+
|
|
37
|
+
const platform = createPlatform({
|
|
38
|
+
stateDir: "/var/bulkhead-runtime",
|
|
39
|
+
credentialPassphrase: process.env.CREDENTIAL_KEY,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Create isolated workspaces — one per user, team, or tenant
|
|
43
|
+
const workspace = await platform.createWorkspace("user-42", {
|
|
44
|
+
provider: "anthropic",
|
|
45
|
+
model: "claude-sonnet-4-20250514",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await workspace.run({
|
|
49
|
+
message: "Refactor the auth module to use JWT",
|
|
50
|
+
sessionId: "project-alpha",
|
|
51
|
+
});
|
|
52
|
+
// Runs in a Linux namespace sandbox.
|
|
53
|
+
// Private memory, encrypted credentials, isolated filesystem.
|
|
54
|
+
// No other workspace can see this agent's data. Ever.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> **Requires:** Linux + Node.js 22.12+
|
|
58
|
+
>
|
|
59
|
+
> **macOS / Windows dev:**
|
|
60
|
+
> ```bash
|
|
61
|
+
> git clone https://github.com/tonga54/bulkhead-runtime.git && cd bulkhead-runtime
|
|
62
|
+
> docker compose run dev bash
|
|
63
|
+
> pnpm test # 88 tests, all green
|
|
64
|
+
> ```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Use Cases
|
|
69
|
+
|
|
70
|
+
<table>
|
|
71
|
+
<tr>
|
|
72
|
+
<td width="50%">
|
|
73
|
+
|
|
74
|
+
**One agent per customer in your SaaS**
|
|
75
|
+
|
|
76
|
+
Your platform gives each customer an AI agent. Each agent accesses that customer's repos, APIs, and databases — with their own credentials. Customer A's agent can never see Customer B's tokens, data, or conversation history.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
app.post("/api/agent", async (req, res) => {
|
|
80
|
+
const ws = await platform.getWorkspace(req.org.id);
|
|
81
|
+
const result = await ws.run({
|
|
82
|
+
message: req.body.message,
|
|
83
|
+
sessionId: req.body.threadId,
|
|
84
|
+
});
|
|
85
|
+
res.json({ response: result.response });
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
</td>
|
|
90
|
+
<td width="50%">
|
|
91
|
+
|
|
92
|
+
**Per-team agents inside your company**
|
|
93
|
+
|
|
94
|
+
Engineering, ops, and data each get their own agent. Each team's agent connects to their own tools — different GitHub orgs, different databases, different cloud accounts. No credential leaks between teams.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const eng = await platform.createWorkspace("engineering");
|
|
98
|
+
const ops = await platform.createWorkspace("ops");
|
|
99
|
+
const data = await platform.createWorkspace("data-team");
|
|
100
|
+
|
|
101
|
+
eng.skills.enable("github-pr");
|
|
102
|
+
ops.skills.enable("pagerduty");
|
|
103
|
+
data.skills.enable("bigquery");
|
|
104
|
+
|
|
105
|
+
await eng.credentials.store("github", { token: "ghp_eng..." });
|
|
106
|
+
await ops.credentials.store("pagerduty", { token: "pd_..." });
|
|
107
|
+
await data.credentials.store("gcp", { key: "..." });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
</td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td width="50%">
|
|
114
|
+
|
|
115
|
+
**Client-isolated agents in consulting / agencies**
|
|
116
|
+
|
|
117
|
+
Each client project gets its own workspace. The agent knows that client's stack, their conventions, their infra. When you offboard a client, `deleteWorkspace()` wipes everything — memory, credentials, sessions.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const acme = await platform.createWorkspace("client-acme");
|
|
121
|
+
await acme.credentials.store("aws", { key: "...", secret: "..." });
|
|
122
|
+
await acme.credentials.store("jira", { token: "..." });
|
|
123
|
+
|
|
124
|
+
await acme.run({
|
|
125
|
+
message: "Check the staging deploy and open a Jira ticket if it failed",
|
|
126
|
+
sessionId: "daily-ops",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Client offboarded — clean removal
|
|
130
|
+
await platform.deleteWorkspace("client-acme");
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
</td>
|
|
134
|
+
<td width="50%">
|
|
135
|
+
|
|
136
|
+
**Ephemeral agents for CI / PR review / task runners**
|
|
137
|
+
|
|
138
|
+
Spin up a workspace per job, per PR, or per deploy. The agent runs, does its thing, and the workspace is destroyed. No state leaks between runs.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const jobId = `deploy-${Date.now()}`;
|
|
142
|
+
const ws = await platform.createWorkspace(jobId);
|
|
143
|
+
|
|
144
|
+
await ws.credentials.store("k8s", { kubeconfig: "..." });
|
|
145
|
+
ws.skills.enable("kubectl");
|
|
146
|
+
|
|
147
|
+
const result = await ws.run({
|
|
148
|
+
message: "Roll out v2.3.1 to staging, run smoke tests, report status",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await platform.deleteWorkspace(jobId);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
</td>
|
|
155
|
+
</tr>
|
|
156
|
+
</table>
|
|
157
|
+
|
|
158
|
+
**The common thread:** you have multiple tenants (users, teams, clients, jobs) and each one needs an AI agent with its own secrets, tools, and memory — on the same server, without any cross-contamination.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## How It Works
|
|
163
|
+
|
|
164
|
+
```mermaid
|
|
165
|
+
graph TB
|
|
166
|
+
subgraph HOST["HOST PROCESS"]
|
|
167
|
+
subgraph WA["Workspace A"]
|
|
168
|
+
WA_mem["memory.db"]
|
|
169
|
+
WA_cred["creds.enc"]
|
|
170
|
+
WA_sess["sessions/"]
|
|
171
|
+
WA_skill["skills[]"]
|
|
172
|
+
end
|
|
173
|
+
subgraph WB["Workspace B"]
|
|
174
|
+
WB_mem["memory.db"]
|
|
175
|
+
WB_cred["creds.enc"]
|
|
176
|
+
WB_sess["sessions/"]
|
|
177
|
+
WB_skill["skills[]"]
|
|
178
|
+
end
|
|
179
|
+
subgraph WN["Workspace N"]
|
|
180
|
+
WN_mem["memory.db"]
|
|
181
|
+
WN_cred["creds.enc"]
|
|
182
|
+
WN_sess["sessions/"]
|
|
183
|
+
WN_skill["skills[]"]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
subgraph SA["Sandbox A — user ns · mount ns · pid ns · net ns · cgroup v2"]
|
|
187
|
+
AgentA["Agent A"]
|
|
188
|
+
end
|
|
189
|
+
subgraph SB["Sandbox B — user ns · mount ns · pid ns · net ns · cgroup v2"]
|
|
190
|
+
AgentB["Agent B"]
|
|
191
|
+
end
|
|
192
|
+
subgraph SN["Sandbox N — user ns · mount ns · pid ns · net ns · cgroup v2"]
|
|
193
|
+
AgentN["Agent N"]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
WA -->|"JSON-RPC IPC"| SA
|
|
198
|
+
WB -->|"JSON-RPC IPC"| SB
|
|
199
|
+
WN -->|"JSON-RPC IPC"| SN
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> Agent A cannot see Agent B's files, memory, credentials, or processes. Not by policy. **By kernel enforcement.**
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Why Bulkhead Over Alternatives
|
|
207
|
+
|
|
208
|
+
| | Docker per user | E2B / Cloud | **Bulkhead Runtime** |
|
|
209
|
+
|:---|:---:|:---:|:---:|
|
|
210
|
+
| **Isolation mechanism** | Container per user | Cloud VM per session | **Linux namespaces** |
|
|
211
|
+
| **Credential security** | DIY | Not built-in | **AES-256-GCM, never exposed to agent** |
|
|
212
|
+
| **Persistent memory** | DIY | DIY | **SQLite + vector embeddings per tenant** |
|
|
213
|
+
| **Skills with secret injection** | DIY | DIY | **Credentials injected server-side** |
|
|
214
|
+
| **Per-workspace skill config** | DIY | DIY | **Enable/disable per tenant** |
|
|
215
|
+
| **Infrastructure** | Docker daemon | Cloud API + billing | **Single npm package** |
|
|
216
|
+
| **Cold start** | ~2s | ~5-10s | **~50ms** |
|
|
217
|
+
| **Embeddable in your app** | No | No | **Yes — it's a library** |
|
|
218
|
+
| **License** | — | Proprietary | **MIT** |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Single-User Mode
|
|
223
|
+
|
|
224
|
+
The fastest path for prototyping or single-agent use. No platform needed.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { createRuntime } from "bulkhead-runtime";
|
|
228
|
+
|
|
229
|
+
const runtime = await createRuntime({
|
|
230
|
+
provider: "anthropic",
|
|
231
|
+
model: "claude-sonnet-4-20250514",
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const result = await runtime.run({
|
|
235
|
+
message: "Find all TODO comments and create a summary",
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The agent runs inside a Linux namespace sandbox with full coding tools (read, write, edit, bash, grep, find, ls) and autonomous memory (`memory_store`, `memory_search`).
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Multi-Tenant Isolation
|
|
244
|
+
|
|
245
|
+
This is the core of Bulkhead Runtime. Each user gets a **workspace** — a fully isolated environment with its own memory, encrypted credentials, enabled skills, and session history. Workspaces are physically separated at the OS level.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { createPlatform } from "bulkhead-runtime";
|
|
249
|
+
|
|
250
|
+
const platform = createPlatform({
|
|
251
|
+
stateDir: "/var/bulkhead-runtime",
|
|
252
|
+
credentialPassphrase: process.env.CREDENTIAL_KEY,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Each workspace is a universe unto itself
|
|
256
|
+
const alice = await platform.createWorkspace("alice", {
|
|
257
|
+
provider: "anthropic",
|
|
258
|
+
model: "claude-sonnet-4-20250514",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const bob = await platform.createWorkspace("bob", {
|
|
262
|
+
provider: "google",
|
|
263
|
+
model: "gemini-2.5-flash",
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### What "isolated" actually means
|
|
268
|
+
|
|
269
|
+
When `workspace.run()` executes, Bulkhead spawns a **child process** with 5 layers of kernel isolation. The agent **never runs in your application's process**.
|
|
270
|
+
|
|
271
|
+
```mermaid
|
|
272
|
+
sequenceDiagram
|
|
273
|
+
participant App as Your App
|
|
274
|
+
participant Host as Host Process
|
|
275
|
+
participant Sandbox as Sandbox (isolated)
|
|
276
|
+
|
|
277
|
+
App->>Host: workspace.run({ message })
|
|
278
|
+
Host->>Host: Load session history
|
|
279
|
+
Host->>Host: Resolve enabled skills
|
|
280
|
+
Host->>Host: Detect sandbox capabilities
|
|
281
|
+
Host->>Sandbox: Spawn child process (unshare)
|
|
282
|
+
activate Sandbox
|
|
283
|
+
Note over Sandbox: Enter user namespace<br/>pivot_root → new filesystem<br/>PID namespace (own procs)<br/>Network ns (loopback only)<br/>cgroup v2 (mem/cpu/pid cap)
|
|
284
|
+
Sandbox->>Host: IPC: memory.search
|
|
285
|
+
Host-->>Sandbox: search results
|
|
286
|
+
Sandbox->>Host: IPC: memory.store
|
|
287
|
+
Host-->>Sandbox: store confirmation
|
|
288
|
+
Sandbox->>Host: IPC: skill.execute
|
|
289
|
+
Host->>Host: Decrypt credentials (AES-256-GCM)
|
|
290
|
+
Host-->>Sandbox: skill output (no credentials)
|
|
291
|
+
Sandbox-->>Host: Agent response
|
|
292
|
+
deactivate Sandbox
|
|
293
|
+
Host->>Host: Kill child process
|
|
294
|
+
Host->>Host: Fire lifecycle hooks
|
|
295
|
+
Host->>Host: Update session store
|
|
296
|
+
Host-->>App: { response, session }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The agent gets coding tools (bash, file read/write/edit) because the mount namespace restricts its entire filesystem view. **It literally cannot see anything outside its sandbox.**
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Credential Security
|
|
304
|
+
|
|
305
|
+
Credentials are **AES-256-GCM encrypted** at rest. PBKDF2 key derivation with 100k iterations (SHA-512). The agent **never** sees raw secrets — not through tools, not through IPC, not through environment variables.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Store encrypted credentials for Alice
|
|
309
|
+
await alice.credentials.store("github", { token: "ghp_alice_secret" });
|
|
310
|
+
await alice.credentials.store("openai", { apiKey: "sk-..." });
|
|
311
|
+
|
|
312
|
+
// When the agent uses a skill that needs credentials:
|
|
313
|
+
// 1. Host decrypts credentials server-side
|
|
314
|
+
// 2. Injects them as env vars into the skill process
|
|
315
|
+
// 3. Agent receives the skill's output — never the credential itself
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```mermaid
|
|
319
|
+
sequenceDiagram
|
|
320
|
+
participant Agent as Agent (sandboxed)
|
|
321
|
+
participant Host as Host Process
|
|
322
|
+
participant Skill as Skill Script
|
|
323
|
+
|
|
324
|
+
Agent->>Host: skill_execute({ skillId: "github-issues" })
|
|
325
|
+
Host->>Host: Resolve skill directory
|
|
326
|
+
Host->>Host: Decrypt credentials (AES-256-GCM)
|
|
327
|
+
Host->>Skill: Spawn script with credentials as env vars
|
|
328
|
+
activate Skill
|
|
329
|
+
Skill->>Skill: process.env.token → use API
|
|
330
|
+
Skill-->>Host: stdout → JSON result
|
|
331
|
+
deactivate Skill
|
|
332
|
+
Host-->>Agent: Result string (no credentials)
|
|
333
|
+
Note over Agent: ✗ Never sees the token<br/>✗ Cannot read credential file<br/>✗ Cannot intercept env vars
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
System environment variables (`PATH`, `HOME`, `NODE_ENV`) are protected from credential key collision. Skill IDs are validated against prototype pollution.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Per-Workspace Skills
|
|
341
|
+
|
|
342
|
+
Skills are registered globally and **enabled per workspace**. Each workspace gets exactly the capabilities it needs — nothing more.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// Register skills globally
|
|
346
|
+
// skills/
|
|
347
|
+
// github-issues/
|
|
348
|
+
// SKILL.md ← LLM reads this to understand the skill
|
|
349
|
+
// execute.js ← Runs with credentials injected as env vars
|
|
350
|
+
// db-migration/
|
|
351
|
+
// SKILL.md
|
|
352
|
+
// execute.sh
|
|
353
|
+
|
|
354
|
+
// Enable different skills per workspace
|
|
355
|
+
const frontend = await platform.createWorkspace("team-frontend");
|
|
356
|
+
const backend = await platform.createWorkspace("team-backend");
|
|
357
|
+
|
|
358
|
+
frontend.skills.enable("github-issues");
|
|
359
|
+
backend.skills.enable("github-issues");
|
|
360
|
+
backend.skills.enable("db-migration"); // only backend gets this
|
|
361
|
+
|
|
362
|
+
// Store different credentials per workspace
|
|
363
|
+
await frontend.credentials.store("github", { token: "ghp_frontend_token" });
|
|
364
|
+
await backend.credentials.store("github", { token: "ghp_backend_token" });
|
|
365
|
+
await backend.credentials.store("database", { url: "postgres://prod:5432/app" });
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// skills/github-issues/execute.js
|
|
370
|
+
const params = JSON.parse(await readStdin());
|
|
371
|
+
const token = process.env.token; // Injected from encrypted store — never over IPC
|
|
372
|
+
const res = await fetch(`https://api.github.com/repos/${params.repo}/issues`, {
|
|
373
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
374
|
+
});
|
|
375
|
+
console.log(JSON.stringify(await res.json()));
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Skills run with a minimal env (`PATH`, `HOME`, `NODE_ENV` + credentials), 30s timeout, and 10 MB stdout/stderr cap.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Memory Isolation
|
|
383
|
+
|
|
384
|
+
Each workspace has its own SQLite database. Memories never cross workspace boundaries — not by access control, **by physical separation**.
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
await alice.memory.store("Project uses React and TypeScript");
|
|
388
|
+
await bob.memory.store("Project uses Vue and Python");
|
|
389
|
+
|
|
390
|
+
const search = await alice.memory.search("framework");
|
|
391
|
+
// → "React and TypeScript"
|
|
392
|
+
// Bob's data doesn't exist in Alice's universe.
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Autonomous Agent Memory
|
|
396
|
+
|
|
397
|
+
The agent decides what to remember. Memory persists across sessions, across restarts.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Session 1
|
|
401
|
+
await runtime.run({
|
|
402
|
+
message: "My name is Juan, I work in fintech, I prefer TypeScript",
|
|
403
|
+
sessionId: "onboarding",
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Session 2 — different session, memory persists
|
|
407
|
+
await runtime.run({
|
|
408
|
+
message: "Set up a new project for me",
|
|
409
|
+
sessionId: "new-project",
|
|
410
|
+
});
|
|
411
|
+
// Agent searches memory → finds preferences → scaffolds TypeScript project
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Search engine under the hood:**
|
|
415
|
+
|
|
416
|
+
| Stage | Algorithm |
|
|
417
|
+
|:---|:---|
|
|
418
|
+
| **Vector search** | Cosine similarity against stored embeddings |
|
|
419
|
+
| **Keyword search** | SQLite FTS5 with BM25 ranking |
|
|
420
|
+
| **Fusion** | Weighted merge of vector + keyword scores |
|
|
421
|
+
| **Temporal decay** | Exponential time-based score attenuation |
|
|
422
|
+
| **Diversity** | MMR (Maximal Marginal Relevance) re-ranking |
|
|
423
|
+
| **Query expansion** | 7-language keyword extraction (EN/ES/PT/ZH/JA/KO/AR) |
|
|
424
|
+
|
|
425
|
+
Works without any embedding API key — falls back to FTS5 keyword search.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Session Continuity
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
await workspace.run({
|
|
433
|
+
message: "Create a REST API for user management",
|
|
434
|
+
sessionId: "api-project",
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Later — agent sees the full conversation history
|
|
438
|
+
await workspace.run({
|
|
439
|
+
message: "Add input validation to those endpoints",
|
|
440
|
+
sessionId: "api-project",
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Sessions are per-workspace, stored as JSONL transcripts with async locking.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Subagent Orchestration
|
|
449
|
+
|
|
450
|
+
A tool can spawn a child agent. The parent blocks until the child finishes.
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const result = await runtime.run({
|
|
454
|
+
message: "Review this PR for security and performance",
|
|
455
|
+
tools: [{
|
|
456
|
+
name: "specialist",
|
|
457
|
+
description: "Delegate a subtask to a specialist agent",
|
|
458
|
+
parameters: { task: { type: "string" }, role: { type: "string" } },
|
|
459
|
+
async execute(_id, params) {
|
|
460
|
+
const r = await runtime.run({
|
|
461
|
+
message: params.task,
|
|
462
|
+
systemPrompt: `You are a ${params.role} expert.`,
|
|
463
|
+
});
|
|
464
|
+
return { resultForAssistant: r.response };
|
|
465
|
+
},
|
|
466
|
+
}],
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Lifecycle Hooks
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
workspace.hooks.register("before_tool_call", async ({ toolName, input }) => {
|
|
476
|
+
await auditLog.write({ tool: toolName, input, timestamp: Date.now() });
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
workspace.hooks.register("after_agent_end", async ({ sessionId, result }) => {
|
|
480
|
+
await billing.recordUsage(workspace.id, sessionId);
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
6 hook points: `session_start` · `session_end` · `before_agent_start` · `after_agent_end` · `before_tool_call` · `after_tool_call`
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Security Architecture
|
|
489
|
+
|
|
490
|
+
### 5 Layers of Sandbox Isolation
|
|
491
|
+
|
|
492
|
+
All layers are **fail-closed** — if any layer can't be applied, the sandbox refuses to start.
|
|
493
|
+
|
|
494
|
+
```mermaid
|
|
495
|
+
block-beta
|
|
496
|
+
columns 1
|
|
497
|
+
block:L1["Layer 1 — User Namespace · unprivileged creation via unshare(2)"]
|
|
498
|
+
columns 1
|
|
499
|
+
block:L2["Layer 2 — Mount Namespace · pivot_root → new root · old root unmounted"]
|
|
500
|
+
columns 1
|
|
501
|
+
block:L3["Layer 3 — PID Namespace · agent only sees its own processes"]
|
|
502
|
+
columns 1
|
|
503
|
+
block:L4["Layer 4 — Network Namespace · loopback only · no external access"]
|
|
504
|
+
columns 1
|
|
505
|
+
L5["Layer 5 — cgroups v2 · memory.max · pids.max · cpu.weight"]
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
AGENT["Agent process — can only see its own isolated world"]
|
|
511
|
+
|
|
512
|
+
L1 --> AGENT
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Defense in Depth
|
|
516
|
+
|
|
517
|
+
| Defense | Mechanism |
|
|
518
|
+
|:---|:---|
|
|
519
|
+
| **Env allowlist** | Only `PATH`, `HOME`, `NODE_ENV` + the single API key the agent needs. Everything else dropped. |
|
|
520
|
+
| **Credential proxy** | Secrets decrypted server-side, injected into skill execution. Never sent over IPC. |
|
|
521
|
+
| **Path traversal blocklist** | `/proc`, `/sys`, `/home/`, `/etc/shadow`, `/run/docker.sock`, and more are blocked from bind mounts. |
|
|
522
|
+
| **Symlink rejection** | `additionalBinds` sources must not be symlinks (prevents TOCTOU attacks). |
|
|
523
|
+
| **IPC rate limiting** | 200 calls/sec per method. Prevents resource exhaustion from rogue agents. |
|
|
524
|
+
| **IPC buffer limit** | 50 MB max. Peer stops on overflow to prevent memory exhaustion. |
|
|
525
|
+
| **Prototype pollution guard** | `__proto__`, `constructor`, `prototype` rejected as skill/credential IDs. |
|
|
526
|
+
| **Stdout interception** | IPC uses a dedicated fd. All other stdout is redirected to stderr. |
|
|
527
|
+
| **Sensitive path validation** | `workspaceDir`, `projectDir`, `nodeExecutable`, `additionalBinds` all validated. |
|
|
528
|
+
| **Atomic writes** | Config, credentials, sessions, skill state — all use tmp+rename pattern. |
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Provider Support
|
|
533
|
+
|
|
534
|
+
### LLM Providers
|
|
535
|
+
|
|
536
|
+
Any provider supported by [pi-ai](https://github.com/nicepkg/pi-ai):
|
|
537
|
+
|
|
538
|
+
| Provider | Example Model |
|
|
539
|
+
|:---|:---|
|
|
540
|
+
| **Anthropic** | `claude-sonnet-4-20250514` |
|
|
541
|
+
| **Google** | `gemini-2.5-flash` |
|
|
542
|
+
| **OpenAI** | `gpt-4o` |
|
|
543
|
+
| **Groq** | `llama-3.3-70b-versatile` |
|
|
544
|
+
| **Cerebras** | `llama-3.3-70b` |
|
|
545
|
+
| **Mistral** | `mistral-large-latest` |
|
|
546
|
+
| **xAI** | `grok-3` |
|
|
547
|
+
|
|
548
|
+
### Embedding Providers
|
|
549
|
+
|
|
550
|
+
Optional — keyword search works without any API key.
|
|
551
|
+
|
|
552
|
+
| Provider | Default Model | Local |
|
|
553
|
+
|:---|:---|:---:|
|
|
554
|
+
| **OpenAI** | `text-embedding-3-small` | |
|
|
555
|
+
| **Gemini** | `gemini-embedding-001` | |
|
|
556
|
+
| **Voyage** | `voyage-3-lite` | |
|
|
557
|
+
| **Mistral** | `mistral-embed` | |
|
|
558
|
+
| **Ollama** | `nomic-embed-text` | **Yes** |
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## Architecture
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
src/
|
|
566
|
+
├── platform/ createPlatform() — workspace CRUD, skill registry
|
|
567
|
+
├── workspace/ createWorkspace() — scoped memory, sessions, runner
|
|
568
|
+
│ └── runner.ts Out-of-process agent execution via sandbox + IPC
|
|
569
|
+
├── sandbox/ Linux namespace sandbox — zero external deps
|
|
570
|
+
│ ├── namespace.ts unshare(2) + pivot_root
|
|
571
|
+
│ ├── cgroup.ts cgroups v2 resource limits (fail-closed)
|
|
572
|
+
│ ├── rootfs.ts Minimal rootfs with bind mounts
|
|
573
|
+
│ ├── ipc.ts Bidirectional JSON-RPC 2.0 over stdio
|
|
574
|
+
│ ├── seccomp.ts BPF syscall filter profiles (prepared)
|
|
575
|
+
│ ├── proxy-tools.ts memory/skill tools proxied to host via IPC
|
|
576
|
+
│ └── worker.ts Agent entry point inside sandbox
|
|
577
|
+
├── credentials/ AES-256-GCM encrypted store + credential proxy
|
|
578
|
+
├── skills/ Global registry + per-workspace enablement
|
|
579
|
+
├── runtime/ createRuntime() — single-user mode
|
|
580
|
+
├── memory/ Hybrid search engine
|
|
581
|
+
│ ├── hybrid.ts Vector + FTS5 fusion scoring
|
|
582
|
+
│ ├── mmr.ts Maximal Marginal Relevance re-ranking
|
|
583
|
+
│ ├── temporal-decay.ts Exponential time-based scoring
|
|
584
|
+
│ └── query-expansion.ts 7-language keyword expansion
|
|
585
|
+
├── hooks/ 6 lifecycle hook points
|
|
586
|
+
├── sessions/ File-based store with async locking
|
|
587
|
+
└── config/ Configuration loading
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**59 source files** · **3 runtime deps** · Sandbox, crypto, and IPC use **zero external deps** — all Node.js built-ins.
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## State Directory
|
|
595
|
+
|
|
596
|
+
```
|
|
597
|
+
<stateDir>/
|
|
598
|
+
├── skills/ # Global skill catalog
|
|
599
|
+
│ └── <skill-id>/
|
|
600
|
+
│ ├── SKILL.md # LLM-readable skill description
|
|
601
|
+
│ └── execute.js # Runs with injected credentials
|
|
602
|
+
└── workspaces/
|
|
603
|
+
└── <userId>/
|
|
604
|
+
├── config.json # Workspace config (no secrets)
|
|
605
|
+
├── credentials.enc.json # AES-256-GCM encrypted credentials
|
|
606
|
+
├── enabled-skills.json # Which skills this workspace can use
|
|
607
|
+
├── sessions.json # Session index
|
|
608
|
+
├── sessions/ # JSONL transcripts
|
|
609
|
+
└── memory/
|
|
610
|
+
└── memory.db # SQLite (vectors + FTS5)
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
Every file is workspace-scoped. No shared state between tenants.
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## Requirements
|
|
618
|
+
|
|
619
|
+
- **Linux** — bare metal, VM, or container with `--privileged`
|
|
620
|
+
- **Node.js 22.12+** — uses `node:sqlite` built-in
|
|
621
|
+
- **macOS / Windows** — `docker compose run dev` for development
|
|
622
|
+
|
|
623
|
+
## License
|
|
624
|
+
|
|
625
|
+
[MIT](LICENSE)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRuntime } from "./runtime/index.js";
|
|
3
|
+
async function main() {
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
function getFlag(flag) {
|
|
6
|
+
const i = args.indexOf(flag);
|
|
7
|
+
if (i === -1 || i + 1 >= args.length)
|
|
8
|
+
return undefined;
|
|
9
|
+
return args[i + 1];
|
|
10
|
+
}
|
|
11
|
+
const message = getFlag("--message") ?? getFlag("-m");
|
|
12
|
+
const sessionId = getFlag("--session");
|
|
13
|
+
const model = getFlag("--model");
|
|
14
|
+
const provider = getFlag("--provider");
|
|
15
|
+
const apiKey = getFlag("--api-key");
|
|
16
|
+
const workspaceDir = getFlag("--workspace");
|
|
17
|
+
const systemPrompt = getFlag("--system-prompt");
|
|
18
|
+
if (!message) {
|
|
19
|
+
console.error("Usage: openclaw --message <message> [--session <id>] [--model <model>] [--provider <provider>]");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const runtime = await createRuntime({
|
|
23
|
+
model,
|
|
24
|
+
provider,
|
|
25
|
+
apiKey,
|
|
26
|
+
workspaceDir,
|
|
27
|
+
});
|
|
28
|
+
const result = await runtime.run({
|
|
29
|
+
message,
|
|
30
|
+
sessionId,
|
|
31
|
+
model,
|
|
32
|
+
provider,
|
|
33
|
+
apiKey,
|
|
34
|
+
workspaceDir,
|
|
35
|
+
systemPrompt,
|
|
36
|
+
});
|
|
37
|
+
console.log(result.response);
|
|
38
|
+
}
|
|
39
|
+
main().catch((err) => {
|
|
40
|
+
console.error(err);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=cli.js.map
|