@kybernesis/create 0.4.0 → 0.5.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/dist/doctor.js CHANGED
@@ -90,24 +90,39 @@ export async function doctor() {
90
90
  add("warn", "KYBERNESIS_ISSUER not set", "agent is not control-plane governed");
91
91
  }
92
92
  // ── slack ──────────────────────────────────────────────────────────────
93
- if (env.SLACK_CONNECTOR_UID)
94
- add("pass", `Slack connector uid: ${env.SLACK_CONNECTOR_UID}`, "verify trigger path /eve/v1/slack (vercel connect list)");
95
- else
96
- add("warn", "SLACK_CONNECTOR_UID not set", "vercel connect create slack --triggers");
93
+ // Only relevant when the agent actually has a Slack channel — a client on
94
+ // iMessage or Telegram should never be told to create a Slack connector.
95
+ const hasSlackChannel = existsSync(join(cwd, "agent/channels/slack.ts"));
96
+ if (hasSlackChannel) {
97
+ if (env.SLACK_CONNECTOR_UID)
98
+ add("pass", `Slack connector uid: ${env.SLACK_CONNECTOR_UID}`, "verify trigger path /eve/v1/slack (vercel connect list)");
99
+ else if (env.SLACK_BOT_TOKEN)
100
+ add("pass", "Slack via portable credentials (SLACK_BOT_TOKEN)");
101
+ else
102
+ add("warn", "Slack channel present but no credentials", "SLACK_CONNECTOR_UID (Vercel) or SLACK_BOT_TOKEN (portable)");
103
+ }
97
104
  // ── engineer layer (optional — checked only when installed) ────────────
98
105
  const hasEngineer = Boolean(deps["@kybernesis/engineer"]) || existsSync(join(cwd, "agent/extensions/engineer.ts"));
99
106
  if (hasEngineer) {
100
107
  add("pass", `@kybernesis/engineer ${deps["@kybernesis/engineer"] ?? "(extension file present)"}`);
101
- if (existsSync(join(cwd, "agent/sandbox/sandbox.ts")))
102
- add("pass", "workshop sandbox file present");
103
- else
104
- add("fail", "agent/sandbox/sandbox.ts missing", "eve add @kybernesis/engineer --overwrite writes it");
105
- if (env.BLOB_READ_WRITE_TOKEN)
106
- add("pass", "file delivery configured (BLOB_READ_WRITE_TOKEN)");
107
- else
108
- add("warn", "BLOB_READ_WRITE_TOKEN not set — deliver tool will fail", "vercel blob create-store <name>-deliverables --access public --yes");
108
+ // The workshop may sit on the root OR on the engineer subagent (the
109
+ // scoped pattern). Either is valid; neither is not.
110
+ const rootSandbox = existsSync(join(cwd, "agent/sandbox/sandbox.ts"));
111
+ const builderSandbox = existsSync(join(cwd, "agent/subagents/builder/sandbox/sandbox.ts"));
112
+ if (rootSandbox || builderSandbox) {
113
+ add("pass", `workshop sandbox present (${builderSandbox ? "engineer subagent" : "root agent"})`);
114
+ }
115
+ else {
116
+ add("fail", "no workshop sandbox", "the engineer layer needs one — on the root or on agent/subagents/builder/");
117
+ }
109
118
  const vercelConn = join(cwd, "agent/connections/vercel.ts");
110
- if (existsSync(vercelConn)) {
119
+ const selfHostedAgent = Boolean(deps["@kybernesis/exe"]) ||
120
+ (existsSync(join(cwd, "agent/subagents/builder/sandbox/sandbox.ts")) &&
121
+ readFileSync(join(cwd, "agent/subagents/builder/sandbox/sandbox.ts"), "utf8").includes("docker("));
122
+ if (selfHostedAgent && !existsSync(vercelConn)) {
123
+ add("pass", "no Vercel MCP connection (self-hosted)", "public deploys need the CLIENT's own Vercel token — Vercel Connect does not work off-Vercel");
124
+ }
125
+ else if (existsSync(vercelConn)) {
111
126
  const src = readFileSync(vercelConn, "utf8");
112
127
  const uid = /connect\(\s*"([^"]+)"/.exec(src)?.[1];
113
128
  if (uid && uid.includes("/"))
@@ -118,10 +133,12 @@ export async function doctor() {
118
133
  else {
119
134
  add("warn", "agent/connections/vercel.ts missing — no preview deploys/link-back", "eve add connection/vercel, then vercel connect create + attach");
120
135
  }
121
- if (env.VERCEL_OIDC_TOKEN || env.VERCEL_TOKEN)
122
- add("pass", "Vercel credentials for local hosted sandboxes");
123
- else
124
- add("warn", "no VERCEL_OIDC_TOKEN — local sandbox/eval runs cannot reach Vercel Sandbox", "vercel link && vercel env pull");
136
+ if (!selfHostedAgent) {
137
+ if (env.VERCEL_OIDC_TOKEN || env.VERCEL_TOKEN)
138
+ add("pass", "Vercel credentials for local hosted sandboxes");
139
+ else
140
+ add("warn", "no VERCEL_OIDC_TOKEN — local sandbox/eval runs cannot reach Vercel Sandbox", "vercel link && vercel env pull");
141
+ }
125
142
  }
126
143
  // ── dispatch edges (agent-to-agent — checked only when present) ────────
127
144
  const subagentsDir = join(cwd, "agent/subagents");
@@ -168,6 +185,69 @@ export async function doctor() {
168
185
  add("warn", "@kybernesis/dispatch installed but no edges or dispatch channel found", "see the connect-agents skill");
169
186
  }
170
187
  }
188
+ // ── self-hosted agents (host !== Vercel) ───────────────────────────────
189
+ // Every check here cost a real debugging session on the first exe.dev
190
+ // deployment. None of them are theoretical.
191
+ const selfHosted = Boolean(deps["@kybernesis/exe"]) ||
192
+ existsSync(join(cwd, "agent/sandbox/sandbox.ts")) &&
193
+ readFileSync(join(cwd, "agent/sandbox/sandbox.ts"), "utf8").includes("docker(");
194
+ if (selfHosted) {
195
+ // Vercel Connect needs Vercel OIDC — it CANNOT work off-Vercel, for Slack,
196
+ // the Vercel MCP connection, or anything else. Every such connection has to
197
+ // become a static credential the client issues.
198
+ const connectUsers = [];
199
+ for (const dir of ["agent/channels", "agent/connections"]) {
200
+ const full = join(cwd, dir);
201
+ if (!existsSync(full))
202
+ continue;
203
+ for (const f of readdirSync(full)) {
204
+ const file = join(full, f);
205
+ if (!f.endsWith(".ts"))
206
+ continue;
207
+ if (readFileSync(file, "utf8").includes("@vercel/connect"))
208
+ connectUsers.push(`${dir}/${f}`);
209
+ }
210
+ }
211
+ if (connectUsers.length) {
212
+ add("fail", `Vercel Connect used off-Vercel: ${connectUsers.join(", ")}`, "Connect authenticates via Vercel OIDC, which does not exist on this host — the agent will fail to boot. Switch to portable/static credentials");
213
+ }
214
+ else {
215
+ add("pass", "no Vercel Connect dependencies (correct for a self-hosted agent)");
216
+ }
217
+ // eve start does not read .env.local the way eve dev does.
218
+ add("warn", "self-hosted: export .env.local into the server process", "eve start does NOT read it; use the supervision script from @kybernesis/exe (scripts/eve-server.sh)");
219
+ // Prewarm runs in the eve CLI, not the built server.
220
+ add("warn", "self-hosted: start via `npx eve start`, not `node .output/server/index.mjs`", "sandbox templates are prewarmed by the CLI; starting the server directly skips prewarm and every sandbox tool fails with SandboxTemplateNotProvisionedError");
221
+ }
222
+ // ── engineer subagent (build capability scoped to a subagent) ──────────
223
+ const builderDir = join(cwd, "agent/subagents/builder");
224
+ if (existsSync(builderDir)) {
225
+ // Subagents own their sandbox — they do NOT inherit the root's. Without one
226
+ // the builder gets a bare template and every screenshot fails with
227
+ // "Cannot find module 'playwright'" while the root's template is fine.
228
+ if (existsSync(join(builderDir, "sandbox/sandbox.ts"))) {
229
+ add("pass", "engineer subagent has its own workshop sandbox");
230
+ }
231
+ else {
232
+ add("fail", "engineer subagent has NO sandbox of its own", "subagents do not inherit the root sandbox — add agent/subagents/builder/sandbox/sandbox.ts or the vision loop cannot run");
233
+ }
234
+ if (existsSync(join(builderDir, "extensions/engineer.ts"))) {
235
+ add("pass", "engineer mounted locally on the subagent (root keeps no shell)");
236
+ }
237
+ else {
238
+ add("warn", "engineer extension not mounted on the subagent", "agent/subagents/builder/extensions/engineer.ts");
239
+ }
240
+ // Delivery: either storage works, or the agent cannot hand over artifacts.
241
+ if (env.BLOB_READ_WRITE_TOKEN) {
242
+ add("pass", "file delivery via Vercel Blob");
243
+ }
244
+ else if (env.DELIVER_DIR && env.DELIVER_BASE_URL) {
245
+ add("pass", `file delivery via host directory (${env.DELIVER_DIR})`);
246
+ }
247
+ else {
248
+ add("warn", "file delivery not configured — the agent cannot hand over artifacts", "set BLOB_READ_WRITE_TOKEN (the CLIENT's blob store) or DELIVER_DIR + DELIVER_BASE_URL");
249
+ }
250
+ }
171
251
  // ── eve discovery + local port ─────────────────────────────────────────
172
252
  const info = capture("npx", ["eve", "info"], cwd);
173
253
  if (info === null)
package/dist/init.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, yellow, } from "./util.js";
4
- import { CHANNEL_KINDS, channelPlan, envExample, evalFileTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
4
+ import { CHANNEL_KINDS, channelPlan, engineerPlan, envExample, evalFileTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
5
5
  import { suiteDir } from "./skills.js";
6
6
  /**
7
7
  * The always-installed core. Everything else — channels, subagents, engineer,
@@ -9,11 +9,11 @@ import { suiteDir } from "./skills.js";
9
9
  * AND undoes real setup work (an Arcana workspace + scoped key per subagent).
10
10
  */
11
11
  const CORE_ITEMS = ["enterprise", "arcana", "evals"];
12
- const ENGINEER_OFFICIAL_ITEMS = [
13
- "extension/agent-browser",
14
- "extension/github-tools",
15
- "connection/vercel",
16
- ];
12
+ // Official eve-registry limbs installed with the engineer subagent.
13
+ // connection/vercel is Vercel-Connect-backed, so it is VERCEL-HOST ONLY: on a
14
+ // self-hosted agent it cannot get an OIDC token and the agent fails to boot.
15
+ const ENGINEER_ITEMS_ALL = ["extension/agent-browser", "extension/github-tools"];
16
+ const ENGINEER_ITEMS_VERCEL = ["connection/vercel"];
17
17
  const DEFAULT_MODEL = "anthropic/claude-sonnet-5";
18
18
  export async function init(rawName, options = {}) {
19
19
  const engineer = options.engineer === true;
@@ -70,10 +70,12 @@ export async function init(rawName, options = {}) {
70
70
  for (const item of plan.registryItems) {
71
71
  run("npx", ["eve", "add", item, "--overwrite"], { cwd: dir, allowFail: true });
72
72
  }
73
- if (engineer) {
74
- console.log(bold("\n2c Engineer layer: workshop sandbox + vision dev loop …"));
75
- run("npx", ["eve", "add", "@kybernesis/engineer", "--overwrite"], { cwd: dir });
76
- for (const item of ENGINEER_OFFICIAL_ITEMS) {
73
+ const engPlan = engineer ? engineerPlan(host, DEFAULT_MODEL) : null;
74
+ if (engPlan) {
75
+ console.log(bold("\n2c Engineer subagent: workshop sandbox + vision dev loop …"));
76
+ run("npm", ["install", ...engPlan.deps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
77
+ const engItems = [...ENGINEER_ITEMS_ALL, ...(host === "vercel" ? ENGINEER_ITEMS_VERCEL : [])];
78
+ for (const item of engItems) {
77
79
  const ok = run("npx", ["eve", "add", item, "--overwrite"], { cwd: dir, allowFail: true });
78
80
  if (!ok)
79
81
  console.log(yellow(` ! ${item} did not install cleanly — re-run: npx eve add ${item}`));
@@ -121,6 +123,13 @@ export async function init(rawName, options = {}) {
121
123
  }
122
124
  }
123
125
  }
126
+ if (engPlan) {
127
+ for (const file of engPlan.files) {
128
+ const full = join(dir, file.path);
129
+ mkdirSync(join(full, ".."), { recursive: true });
130
+ writeFileSync(full, file.content);
131
+ }
132
+ }
124
133
  console.log(bold("\n5/6 Env template + hermetic eval script …"));
125
134
  writeFileSync(join(dir, ".env.example"), envExample(name, depts, issuer, plan.env));
126
135
  const pkgPath = join(dir, "package.json");
@@ -139,13 +148,14 @@ export async function init(rawName, options = {}) {
139
148
  "self-testing (evals)",
140
149
  channel === "none" ? null : `${channel} channel`,
141
150
  host === "exe" ? "exe.dev host" : null,
142
- engineer ? "engineer (workshop + vision loop)" : null,
151
+ engineer ? "engineer subagent (workshop + vision loop)" : null,
143
152
  depts.length ? `${depts.length} dept subagent(s)` : null,
144
153
  ].filter(Boolean);
145
154
  const steps = [
146
155
  `Arcana: create workspaces (${name}-company, ${name}-eval${depts.map((d) => `, ${name}-${d}`).join("")}) + scoped kb_ keys; fill .env.local from .env.example`,
147
156
  ...hostSteps(host, name),
148
157
  ...plan.steps,
158
+ ...(engPlan?.steps ?? []),
149
159
  `Control plane: register agent "${name}" at ${issuer}/agents + grant the pilot cohort`,
150
160
  `npm run eval → green → deploy → live smoke + the revoke demo`,
151
161
  ];
@@ -28,3 +28,12 @@ export declare function channelPlan(kind: ChannelKind, name: string, host: HostK
28
28
  export type HostKind = "vercel" | "exe";
29
29
  export declare function hostAgentTs(host: HostKind, model: string): string;
30
30
  export declare function hostSteps(host: HostKind, name: string): string[];
31
+ export interface EngineerPlan {
32
+ files: Array<{
33
+ path: string;
34
+ content: string;
35
+ }>;
36
+ deps: string[];
37
+ steps: string[];
38
+ }
39
+ export declare function engineerPlan(host: HostKind, model: string): EngineerPlan;
package/dist/templates.js CHANGED
@@ -334,3 +334,176 @@ export function hostSteps(host, name) {
334
334
  `npx eve deploy`,
335
335
  ];
336
336
  }
337
+ /** The workshop sandbox, per host. Same recipe; different backend. */
338
+ function workshopSandbox(host) {
339
+ if (host === "exe") {
340
+ return `import { defineSandbox } from "eve/sandbox";
341
+ import { docker } from "eve/sandbox/docker";
342
+
343
+ /**
344
+ * The engineer workshop, self-hosted: pnpm + Playwright + Chromium baked into
345
+ * the TEMPLATE so warm sessions run render→screenshot→vision in seconds.
346
+ *
347
+ * Docker rather than vercel(): the hosted backend needs Vercel OIDC, which does
348
+ * not exist off-Vercel.
349
+ *
350
+ * HOST PREREQUISITE: some images ship Docker disabled (exe.dev's exeuntu runs
351
+ * \`systemctl disable docker.service\`). Run \`sudo systemctl enable --now docker\`
352
+ * or every build fails with SandboxTemplateNotProvisionedError.
353
+ *
354
+ * NOTE: Docker sessions do not enforce a domain allowlist the way the hosted
355
+ * backend does. Egress control is the HOST's responsibility here — a deliberate
356
+ * difference from the Vercel deployment, not an oversight.
357
+ */
358
+ export default defineSandbox({
359
+ backend: docker(),
360
+ revalidationKey: () => "kybernesis-workshop-v5-docker",
361
+ async bootstrap({ use }) {
362
+ const sandbox = await use();
363
+ await sandbox.run({ command: "apt-get update" });
364
+ await sandbox.run({ command: "npm install -g pnpm" });
365
+ await sandbox.run({
366
+ command:
367
+ "mkdir -p /workspace/.shot && cd /workspace/.shot && echo '{\\"name\\":\\"kyb-shot\\",\\"private\\":true}' > package.json && npm install playwright",
368
+ });
369
+ await sandbox.run({
370
+ command: "cd /workspace/.shot && npx playwright install --with-deps chromium",
371
+ });
372
+ },
373
+ });
374
+ `;
375
+ }
376
+ return `import { defineSandbox } from "eve/sandbox";
377
+ import { vercel } from "eve/sandbox/vercel";
378
+
379
+ /**
380
+ * The engineer workshop: a warm, safe cloud dev machine.
381
+ *
382
+ * TEMPLATE bootstrap (once, inherited by every session): pnpm + Playwright +
383
+ * Chromium. Prewarm runs at deploy time, so a broken bootstrap fails the build
384
+ * loudly and warm sessions run the full render→screenshot→vision loop in
385
+ * seconds. Backend PINNED to Vercel Sandbox — hosted sandboxes even from local
386
+ * dev (run \`vercel link\` + \`vercel env pull\` first), so evals exercise the
387
+ * exact production backend. No Docker anywhere.
388
+ *
389
+ * All sessions run under a domain ALLOWLIST: an agent that installs arbitrary
390
+ * npm packages must not have open egress. A blocked domain fails loudly;
391
+ * treat every addition as a security decision.
392
+ */
393
+ export default defineSandbox({
394
+ backend: vercel({
395
+ resources: { vcpus: 4 },
396
+ networkPolicy: {
397
+ allow: [
398
+ "registry.npmjs.org",
399
+ "*.npmjs.org",
400
+ "github.com",
401
+ "api.github.com",
402
+ "codeload.github.com",
403
+ "*.githubusercontent.com",
404
+ "cdn.playwright.dev",
405
+ "playwright.azureedge.net",
406
+ "playwright.download.prss.microsoft.com",
407
+ "storage.googleapis.com",
408
+ "archive.ubuntu.com",
409
+ "security.ubuntu.com",
410
+ "ports.ubuntu.com",
411
+ "*.ubuntu.com",
412
+ "deb.debian.org",
413
+ "security.debian.org",
414
+ "*.debian.org",
415
+ "ai-gateway.vercel.sh",
416
+ "vercel.com",
417
+ "*.vercel.app",
418
+ "fonts.googleapis.com",
419
+ "fonts.gstatic.com",
420
+ ],
421
+ },
422
+ }),
423
+ revalidationKey: () => "kybernesis-workshop-v5",
424
+ async bootstrap({ use }) {
425
+ const sandbox = await use();
426
+ // The egress proxy carries HTTPS only; apt defaults to http:// mirrors, so
427
+ // every index fetch silently fails. Rewrite to https first.
428
+ await sandbox.run({
429
+ command:
430
+ "find /etc/apt -type f \\\\( -name '*.list' -o -name '*.sources' \\\\) -exec sed -i 's|http://|https://|g' {} + && apt-get update",
431
+ });
432
+ await sandbox.run({ command: "npm install -g pnpm" });
433
+ await sandbox.run({
434
+ command:
435
+ "mkdir -p /workspace/.shot && cd /workspace/.shot && echo '{\\"name\\":\\"kyb-shot\\",\\"private\\":true}' > package.json && npm install playwright",
436
+ });
437
+ await sandbox.run({
438
+ command: "cd /workspace/.shot && npx playwright install --with-deps chromium",
439
+ });
440
+ },
441
+ });
442
+ `;
443
+ }
444
+ export function engineerPlan(host, model) {
445
+ const onExe = host === "exe";
446
+ const files = [
447
+ {
448
+ path: "agent/subagents/builder/agent.ts",
449
+ content: onExe
450
+ ? `import { defineAgent } from "eve";
451
+ import { createOpenAI } from "@ai-sdk/openai";
452
+ import { exeModel } from "@kybernesis/exe";
453
+
454
+ // The specialist the root agent delegates BUILDING to. \`description\` is what
455
+ // the root routes on — keep it about building, not answering.
456
+ export default defineAgent({
457
+ description:
458
+ "Builds and runs software: scaffolds projects, writes code, installs dependencies, runs builds and dev servers, and visually verifies rendered pages. Use when the user asks for something to be BUILT, prototyped, deployed, or fixed in code — not for questions, planning, or scheduling.",
459
+ model: exeModel({ model: process.env.EXE_MODEL ?? ${JSON.stringify(model)}, createOpenAI }),
460
+ modelContextWindowTokens: 200_000,
461
+ });
462
+ `
463
+ : `import { defineAgent } from "eve";
464
+
465
+ // The specialist the root agent delegates BUILDING to. \`description\` is what
466
+ // the root routes on — keep it about building, not answering.
467
+ export default defineAgent({
468
+ description:
469
+ "Builds and runs software: scaffolds projects, writes code, installs dependencies, runs builds and dev servers, and visually verifies rendered pages. Use when the user asks for something to be BUILT, prototyped, deployed, or fixed in code — not for questions, planning, or scheduling.",
470
+ model: ${JSON.stringify(model)},
471
+ });
472
+ `,
473
+ },
474
+ {
475
+ path: "agent/subagents/builder/extensions/engineer.ts",
476
+ content: `// Engineer layer mounted LOCALLY on this subagent (eve >=0.30): screenshot,
477
+ // deliver, and the trade-school skills belong to \`builder\` alone. The root
478
+ // agent never gets shell or a browser.
479
+ export { default } from "@kybernesis/engineer";
480
+ `,
481
+ },
482
+ {
483
+ path: "agent/subagents/builder/sandbox/sandbox.ts",
484
+ content: workshopSandbox(host),
485
+ },
486
+ ];
487
+ if (onExe) {
488
+ files.push({
489
+ path: "agent/subagents/builder/tools/preview.ts",
490
+ content: `export { previewTool as default } from "@kybernesis/exe/preview";
491
+ `,
492
+ });
493
+ }
494
+ return {
495
+ files,
496
+ deps: onExe ? ["@kybernesis/engineer", "@kybernesis/exe"] : ["@kybernesis/engineer"],
497
+ steps: onExe
498
+ ? [
499
+ "Enable Docker on the host (some images ship it disabled): sudo systemctl enable --now docker",
500
+ "Preview server (so the agent can show you what it built):\n mkdir -p ~/preview && setsid python3 -m http.server 3456 --directory ~/preview &\n then open https://<vm>.exe.xyz:3456/<file> (account-gated, not public)",
501
+ "File delivery needs object storage: set BLOB_READ_WRITE_TOKEN (Vercel Blob) or DELIVER_DIR + DELIVER_BASE_URL to serve from this host",
502
+ "Public deploys need the client's own Vercel token — Vercel Connect does NOT work off-Vercel",
503
+ ]
504
+ : [
505
+ "File delivery: vercel blob create-store <name>-deliverables --access public --yes",
506
+ "Preview deploys: eve add connection/vercel, then vercel connect create mcp.vercel.com --name vercel && vercel connect attach mcp.vercel.com/vercel --yes",
507
+ ],
508
+ };
509
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -0,0 +1,96 @@
1
+ ---
2
+ description: Use when deploying an eve agent OFF Vercel — on exe.dev, a VPS, or any client infrastructure — or when a client wants to use their own ChatGPT/LLM subscription. Covers what breaks, what replaces it, and the credential checklist.
3
+ ---
4
+
5
+ # Self-hosted agents (client infrastructure, not Vercel)
6
+
7
+ The Vercel path is the default and the proven one. Reach for this when the
8
+ client **won't or can't use Vercel**, or wants their agent's inference billed to
9
+ a subscription they already pay for.
10
+
11
+ **The governing rule: everything must come from the CLIENT's accounts.** If a
12
+ step only works because you happen to hold a credential, that step is a bug in
13
+ the deployment, not a shortcut. It will fail on the real engagement.
14
+
15
+ ## Scaffold
16
+
17
+ ```bash
18
+ kyb init <name> --host=exe --channel=<imessage|slack|telegram|none> --engineer
19
+ ```
20
+
21
+ `--host=exe` swaps the bindings; everything else is the same product. Run
22
+ `kyb doctor` after — it knows the self-hosted failure modes below.
23
+
24
+ ## What Vercel gives you that a client host does not
25
+
26
+ | Capability | On Vercel | Self-hosted replacement |
27
+ | --- | --- | --- |
28
+ | Model access | AI Gateway | exe.dev LLM integration (`exeModel`) — managed, BYO key, or a **ChatGPT subscription** |
29
+ | Slack/Photon/Linear credentials | Vercel Connect | **Portable/static credentials the client issues** |
30
+ | Sandbox | `vercel()` hosted | `docker()` on the host |
31
+ | File delivery | Vercel Blob | Blob **or** `DELIVER_DIR` + `DELIVER_BASE_URL` |
32
+ | Public URLs | deployments | a deploy target, or an account-gated preview |
33
+ | Secrets | Vercel env | host env + the platform's own secret injection |
34
+
35
+ **Vercel Connect does not work off-Vercel — at all.** It authenticates via
36
+ Vercel OIDC, which does not exist on another host. That applies to Slack, the
37
+ Vercel MCP connection, Linear, everything. Each becomes a static credential
38
+ someone must issue and rotate. `kyb doctor` fails loudly if a `@vercel/connect`
39
+ import survives into a self-hosted agent.
40
+
41
+ ## The failure modes, each of which cost a real session
42
+
43
+ - **Docker ships disabled on some images.** exe.dev's exeuntu runs
44
+ `systemctl disable docker.service`, so `docker --version` works while nothing
45
+ can run. Every sandbox call fails with `SandboxTemplateNotProvisionedError`.
46
+ Fix: `sudo systemctl enable --now docker`.
47
+ - **Subagents own their sandbox — they do NOT inherit the root's.** An engineer
48
+ subagent without its own `sandbox/sandbox.ts` gets a bare template, and the
49
+ screenshot tool fails with `Cannot find module 'playwright'` while the root's
50
+ template is fine.
51
+ - **`eve start` does not read `.env.local`** the way `eve dev` does. Export it
52
+ into the process (`scripts/eve-server.sh` in `@kybernesis/exe` does this).
53
+ - **Prewarm lives in the eve CLI, not the built server.** Starting
54
+ `node .output/server/index.mjs` directly gives you clean logs but skips
55
+ template prewarm entirely. Start with `npx eve start`.
56
+ - **`localDev()` never authenticates under `eve start`** — it is a property of
57
+ the deployment, not the request. A self-hosted agent needs a real
58
+ authenticator from day one.
59
+ - **`pkill -f <pattern>` over SSH kills your own session** when the pattern
60
+ appears in the SSH command line — and can take the agent with it. Use a
61
+ pidfile (`scripts/eve-server.sh`).
62
+ - **Never diagnose "nothing is happening" from a log file.** Count runs on disk:
63
+ `.eve/.workflow-data/runs/`. A log can look frozen at boot while the agent
64
+ serves happily.
65
+
66
+ ## Showing the client what the agent built
67
+
68
+ - **Vercel Blob refuses to serve HTML inline** — it forces a download. Use it
69
+ for documents and exports, never to show a web page.
70
+ - **exe.dev forwards ports 3000–9999** to `https://<vm>.exe.xyz:<port>/`, but a
71
+ VM has exactly **one public port** and the agent's webhook already owns it.
72
+ Alternate ports are account-gated: fine for the client reviewing work, not for
73
+ the public.
74
+ - **Anything genuinely public needs a deploy target** — the client's own Vercel
75
+ token, or their hosting. Treat "public" as a deploy step, not a toggle.
76
+ - A sandbox is a container: its ports are not reachable from the host, so a dev
77
+ server inside it cannot be previewed directly. Copy the artifact out (the
78
+ `preview` tool in `@kybernesis/exe`) or deploy it.
79
+
80
+ ## Credential checklist — collect ALL of these from the client
81
+
82
+ Nothing here can be borrowed from another agent or another account.
83
+
84
+ 1. **Host** — VM/server, plus the platform token if the agent provisions anything
85
+ 2. **Model source** — their LLM API key, gateway allocation, or connected
86
+ subscription (exe: `integrations setup chatgpt`, then `integrations edit llm`)
87
+ 3. **Channel app** — their Slack app (bot + app token) / Photon project / bot token
88
+ 4. **Arcana** — workspaces + scoped `kb_` keys (one per brain, plus `-eval`)
89
+ 5. **Storage for deliverables** — their blob store, or a served host directory
90
+ 6. **Deploy target** — their Vercel token or hosting, if the agent ships sites
91
+ 7. **Control plane** — agent registered and the pilot cohort granted
92
+
93
+ ## Before calling it done
94
+
95
+ `kyb doctor` green (or every warning consciously accepted), the eval suite green
96
+ against the client's `-eval` workspace, and a live turn on the real surface.