@kybernesis/create 0.4.0 → 0.5.1
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 +116 -17
- package/dist/init.js +21 -11
- package/dist/templates.d.ts +9 -0
- package/dist/templates.js +173 -0
- package/package.json +1 -1
- package/skills/fde-engagement/references/playbook.md +275 -1
- package/skills/self-hosting/SKILL.md +146 -0
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
add("pass",
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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 (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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,88 @@ 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
|
+
// The exe VM sandbox backend needs a credential that cannot be scoped.
|
|
222
|
+
// Surface the blast radius here, where it is still cheap to change course.
|
|
223
|
+
const sandboxFile = join(cwd, "agent/sandbox/sandbox.ts");
|
|
224
|
+
const usesExeSandbox = existsSync(sandboxFile) && readFileSync(sandboxFile, "utf8").includes("exeSandbox(");
|
|
225
|
+
if (usesExeSandbox) {
|
|
226
|
+
const src = readFileSync(sandboxFile, "utf8");
|
|
227
|
+
if (src.includes("allowSharedAccount: true")) {
|
|
228
|
+
add("warn", "exeSandbox runs with allowSharedAccount: true", "the sandbox SSH key grants shell to EVERY VM on the exe.dev account — only keep this if the client has explicitly accepted that blast radius; otherwise give the agent its own account");
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
add("pass", "exeSandbox enforces a dedicated exe.dev account");
|
|
232
|
+
}
|
|
233
|
+
if (!process.env.EXE_SANDBOX_SSH_KEY && !process.env.EXE_SANDBOX_SSH_KEY_PATH) {
|
|
234
|
+
add("fail", "exeSandbox has no SSH key (EXE_SANDBOX_SSH_KEY / EXE_SANDBOX_SSH_KEY_PATH)", "it must be a FULL-PERMISSION account key: a key registered through an API token inherits that token's command scope and cannot open a shell at all");
|
|
235
|
+
}
|
|
236
|
+
if (!process.env.EXE_API_TOKEN) {
|
|
237
|
+
add("fail", "exeSandbox has no EXE_API_TOKEN for VM lifecycle", "mint a narrow one: ssh exe.dev \"ssh-key generate-api-key --label=<agent>-sandbox --cmds='ls,new,rm,cp' --exp=7d\"");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// ── engineer subagent (build capability scoped to a subagent) ──────────
|
|
242
|
+
const builderDir = join(cwd, "agent/subagents/builder");
|
|
243
|
+
if (existsSync(builderDir)) {
|
|
244
|
+
// Subagents own their sandbox — they do NOT inherit the root's. Without one
|
|
245
|
+
// the builder gets a bare template and every screenshot fails with
|
|
246
|
+
// "Cannot find module 'playwright'" while the root's template is fine.
|
|
247
|
+
if (existsSync(join(builderDir, "sandbox/sandbox.ts"))) {
|
|
248
|
+
add("pass", "engineer subagent has its own workshop sandbox");
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
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");
|
|
252
|
+
}
|
|
253
|
+
if (existsSync(join(builderDir, "extensions/engineer.ts"))) {
|
|
254
|
+
add("pass", "engineer mounted locally on the subagent (root keeps no shell)");
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
add("warn", "engineer extension not mounted on the subagent", "agent/subagents/builder/extensions/engineer.ts");
|
|
258
|
+
}
|
|
259
|
+
// Delivery: either storage works, or the agent cannot hand over artifacts.
|
|
260
|
+
if (env.BLOB_READ_WRITE_TOKEN) {
|
|
261
|
+
add("pass", "file delivery via Vercel Blob");
|
|
262
|
+
}
|
|
263
|
+
else if (env.DELIVER_DIR && env.DELIVER_BASE_URL) {
|
|
264
|
+
add("pass", `file delivery via host directory (${env.DELIVER_DIR})`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
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");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
171
270
|
// ── eve discovery + local port ─────────────────────────────────────────
|
|
172
271
|
const info = capture("npx", ["eve", "info"], cwd);
|
|
173
272
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
];
|
package/dist/templates.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -40,6 +40,10 @@ npm create @kybernesis acme-atlas -- --engineer
|
|
|
40
40
|
# Prompts: display name · department subagents · control-plane issuer.
|
|
41
41
|
# (Non-interactive/CI: defaults apply when stdin is not a terminal.)
|
|
42
42
|
|
|
43
|
+
# …or, when the client will NOT use Vercel — their own VM, or their own
|
|
44
|
+
# ChatGPT/LLM subscription paying for inference. See section 11:
|
|
45
|
+
npm create @kybernesis acme-atlas -- --host=exe --engineer
|
|
46
|
+
|
|
43
47
|
# 3. (Optional, for repeated use) put `kyb` on the PATH for the whole engagement:
|
|
44
48
|
npm install -g @kybernesis/create
|
|
45
49
|
```
|
|
@@ -1714,9 +1718,279 @@ the control-plane repo's seed scripts.)
|
|
|
1714
1718
|
it is far faster than booting the dev server. `npx eve logs` reads the last `eve dev`
|
|
1715
1719
|
diagnostic log if you need stderr, tool failures, and rebuild lines.
|
|
1716
1720
|
|
|
1721
|
+
### Self-hosted hosts (exe.dev / client infrastructure)
|
|
1722
|
+
|
|
1723
|
+
**I deployed a change and the agent still does the old thing.**
|
|
1724
|
+
The restart did not take. Check that the running process started *after* the build
|
|
1725
|
+
(`scripts/eve-server.sh` asserts this and fails loudly). A silently-failed restart is the
|
|
1726
|
+
single most expensive failure mode off Vercel, because every test you run afterwards
|
|
1727
|
+
measures the previous build. If the process is current, start a **fresh conversation** —
|
|
1728
|
+
a long-lived channel session caches the compiled agent.
|
|
1729
|
+
|
|
1730
|
+
**Every sandbox tool fails with `SandboxTemplateNotProvisionedError`.**
|
|
1731
|
+
Either Docker is installed but disabled (`sudo systemctl enable --now docker` — exeuntu
|
|
1732
|
+
ships it disabled), or the server was started as `node .output/server/index.mjs`, which
|
|
1733
|
+
skips the CLI's template prewarm. Start with `npx eve start`.
|
|
1734
|
+
|
|
1735
|
+
**The subagent's screenshots fail with `Cannot find module 'playwright'` but the root
|
|
1736
|
+
agent is fine.**
|
|
1737
|
+
Subagents own their sandbox; they do not inherit the root's. Give the subagent its own
|
|
1738
|
+
`sandbox/sandbox.ts`.
|
|
1739
|
+
|
|
1740
|
+
**My env vars are missing under `eve start`.**
|
|
1741
|
+
`eve start` does not read `.env.local` the way `eve dev` does. Use
|
|
1742
|
+
`scripts/eve-server.sh`, which exports it into the process.
|
|
1743
|
+
|
|
1744
|
+
**I killed the agent and my own SSH session at the same time.**
|
|
1745
|
+
`pkill -f <pattern>` matches the SSH command line that contains the pattern. Keep kill
|
|
1746
|
+
patterns inside a script on the host; never pass them on an `ssh` command line.
|
|
1747
|
+
|
|
1748
|
+
**A sandbox VM boots and shows `running`, but every command answers "command not found".**
|
|
1749
|
+
SSH is landing on exe.dev's lobby REPL rather than the VM. exe.dev reuses VM names, so a
|
|
1750
|
+
name deleted and recreated within a few minutes can keep resolving to the lobby, and a
|
|
1751
|
+
stale `known_hosts` entry does the same thing. Use a fresh name, and never reuse the
|
|
1752
|
+
caller's `known_hosts` for sandbox VMs.
|
|
1753
|
+
|
|
1754
|
+
**A command works when I SSH in by hand but not from the agent.**
|
|
1755
|
+
A non-interactive SSH command does not source the login profile, so toolchains installed
|
|
1756
|
+
onto the profile PATH (nvm, pyenv, cargo) are invisible. Run through `bash -lc` — but not
|
|
1757
|
+
for file I/O, whose stdout must stay byte-clean.
|
|
1758
|
+
|
|
1759
|
+
**exe.dev refuses to run commands with my API token.**
|
|
1760
|
+
It cannot. A key registered through an API token inherits that token's command scope, and
|
|
1761
|
+
shell exec is not a scoped command. Running commands needs a full-permission account key;
|
|
1762
|
+
see 11.6 for the isolation this demands.
|
|
1763
|
+
|
|
1764
|
+
### Third-party APIs and connections
|
|
1765
|
+
|
|
1766
|
+
**The agent reports that a service is down or that a resource "isn't shared" with it.**
|
|
1767
|
+
Verify before you believe it, and never change the client's permissions on an agent's
|
|
1768
|
+
say-so. An agent's error message is a hypothesis, not evidence — one real case reported an
|
|
1769
|
+
outage, then a permissions problem, while the data was reachable the entire time and the
|
|
1770
|
+
actual cause was a version header. Read the request and response yourself.
|
|
1771
|
+
|
|
1772
|
+
**My manual `curl` works but the agent's identical call fails.**
|
|
1773
|
+
It is not identical — and the difference between the two requests *is* the bug. Diff them
|
|
1774
|
+
at the first contradiction rather than proving again that the token works. A version
|
|
1775
|
+
header, a content type, or a base URL differing by one path segment will do it.
|
|
1776
|
+
|
|
1777
|
+
**Calls derived from a large OpenAPI spec fail in ways that make no sense.**
|
|
1778
|
+
Specs with ambiguous ID schemes (Notion's `database_id` vs `data_source_id` for the same
|
|
1779
|
+
object) make the model pick wrong, and the API's errors describe a different problem.
|
|
1780
|
+
Write a small purpose-built tool with the endpoint and IDs pinned; keep the generic
|
|
1781
|
+
connection for the long tail.
|
|
1782
|
+
|
|
1717
1783
|
---
|
|
1718
1784
|
|
|
1719
|
-
|
|
1785
|
+
|
|
1786
|
+
## 11. Self-hosted deployments (when the client will not use Vercel)
|
|
1787
|
+
|
|
1788
|
+
Vercel is the default path and the proven one. Take this track when the client
|
|
1789
|
+
**cannot or will not** put the agent on Vercel — procurement, data residency, an
|
|
1790
|
+
existing VM estate — or when they want inference billed to a subscription they
|
|
1791
|
+
already pay for rather than to a gateway.
|
|
1792
|
+
|
|
1793
|
+
Everything else in this playbook still applies. This section covers only what
|
|
1794
|
+
changes.
|
|
1795
|
+
|
|
1796
|
+
**The governing rule: every credential comes from the CLIENT's accounts.** If a
|
|
1797
|
+
step works only because you happen to hold a token, that step is a bug in the
|
|
1798
|
+
deployment, not a shortcut — it will fail on the real engagement, in front of
|
|
1799
|
+
the client. Assume you have no Vercel connection, no blob store, and no API
|
|
1800
|
+
keys of your own.
|
|
1801
|
+
|
|
1802
|
+
### 11.1 Scaffold
|
|
1803
|
+
|
|
1804
|
+
```bash
|
|
1805
|
+
kyb init <name> --host=exe --channel=<imessage|slack|telegram|none> --engineer
|
|
1806
|
+
cd <name> && kyb doctor
|
|
1807
|
+
```
|
|
1808
|
+
|
|
1809
|
+
`--host=exe` swaps the host bindings; everything else is the same product —
|
|
1810
|
+
same memory, same evals, same control-plane wiring, same engineer layer.
|
|
1811
|
+
`kyb doctor` knows every failure mode below and fails loudly on each.
|
|
1812
|
+
|
|
1813
|
+
### 11.2 What Vercel gives you that a client host does not
|
|
1814
|
+
|
|
1815
|
+
| Capability | On Vercel | Self-hosted replacement |
|
|
1816
|
+
| --- | --- | --- |
|
|
1817
|
+
| Model access | AI Gateway | exe.dev LLM integration (`exeModel`) — managed, BYO key, or a **ChatGPT subscription** |
|
|
1818
|
+
| Slack / Photon / Linear credentials | Vercel Connect | **static credentials the client issues** |
|
|
1819
|
+
| Sandbox | `vercel()` | `docker()` on the host, or `exeSandbox()` per-task VMs |
|
|
1820
|
+
| File delivery | Vercel Blob | Blob **or** `DELIVER_DIR` + `DELIVER_BASE_URL` |
|
|
1821
|
+
| Public URLs | deployments | a deploy target, or an account-gated preview |
|
|
1822
|
+
| Secrets | Vercel env | host env + the platform's own secret injection |
|
|
1823
|
+
|
|
1824
|
+
**Vercel Connect does not work off-Vercel — at all.** It authenticates via
|
|
1825
|
+
Vercel OIDC, which does not exist on another host. That applies to Slack, the
|
|
1826
|
+
Vercel MCP connection, Linear, everything. Each becomes a static credential
|
|
1827
|
+
someone must issue and rotate, and `kyb doctor` fails if a `@vercel/connect`
|
|
1828
|
+
import survives into a self-hosted agent.
|
|
1829
|
+
|
|
1830
|
+
### 11.3 Host setup (exe.dev)
|
|
1831
|
+
|
|
1832
|
+
```bash
|
|
1833
|
+
ssh exe.dev new --name <agent>
|
|
1834
|
+
ssh exe.dev share port <agent> 8000 && ssh exe.dev share set-public <agent>
|
|
1835
|
+
```
|
|
1836
|
+
|
|
1837
|
+
Make the host public **before** registering any webhook — webhooks need
|
|
1838
|
+
anonymous access, and a provider that fails verification at registration time
|
|
1839
|
+
usually will not tell you why.
|
|
1840
|
+
|
|
1841
|
+
Then, on the VM: Node 24, `npm ci`, `npx eve build`, and start through
|
|
1842
|
+
`scripts/eve-server.sh` from `@kybernesis/exe`.
|
|
1843
|
+
|
|
1844
|
+
Three things that will cost you an afternoon if you skip them:
|
|
1845
|
+
|
|
1846
|
+
- **`eve start` does not read `.env.local`** the way `eve dev` does. The
|
|
1847
|
+
supervision script exports it into the process for you.
|
|
1848
|
+
- **Start via `npx eve start`, not `node .output/server/index.mjs`.** Sandbox
|
|
1849
|
+
template prewarm lives in the CLI, not the built server. Starting the server
|
|
1850
|
+
directly gives you cleaner logs and no prewarm, so every sandbox tool then
|
|
1851
|
+
fails with `SandboxTemplateNotProvisionedError`.
|
|
1852
|
+
- **Docker ships disabled on some images**, exeuntu among them. `docker
|
|
1853
|
+
--version` answers happily while nothing can actually run. Fix with `sudo
|
|
1854
|
+
systemctl enable --now docker`.
|
|
1855
|
+
|
|
1856
|
+
### 11.4 Model: billing inference to the client's subscription
|
|
1857
|
+
|
|
1858
|
+
exe.dev brokers the model, so no provider key sits on the host:
|
|
1859
|
+
|
|
1860
|
+
```bash
|
|
1861
|
+
ssh exe.dev integrations setup chatgpt --name work # once, device-code flow
|
|
1862
|
+
ssh exe.dev integrations edit llm --openai=chatgpt --openai-account=work
|
|
1863
|
+
```
|
|
1864
|
+
|
|
1865
|
+
Then `exeModel()` from `@kybernesis/exe` points the agent at it. A Codex-backed
|
|
1866
|
+
subscription requires `store: false` on every request; `exeModel` forces it.
|
|
1867
|
+
|
|
1868
|
+
This is the single biggest commercial difference in the self-hosted track: a
|
|
1869
|
+
client with an existing ChatGPT or Claude subscription pays no incremental
|
|
1870
|
+
inference cost for the pilot. Say the number out loud in the discovery
|
|
1871
|
+
conversation — it changes the shape of the deal.
|
|
1872
|
+
|
|
1873
|
+
### 11.5 Third-party APIs: broker the credential, pin the version
|
|
1874
|
+
|
|
1875
|
+
Do not put a client's API token on the agent host. Put it in an exe.dev
|
|
1876
|
+
http-proxy integration, which injects headers server-side:
|
|
1877
|
+
|
|
1878
|
+
```bash
|
|
1879
|
+
ssh exe.dev "integrations add http-proxy --name notion \
|
|
1880
|
+
--target https://api.notion.com \
|
|
1881
|
+
--header 'Authorization:Bearer <token>' \
|
|
1882
|
+
--header 'Notion-Version:2025-09-03' \
|
|
1883
|
+
--attach vm:<agent>"
|
|
1884
|
+
```
|
|
1885
|
+
|
|
1886
|
+
The agent then calls `https://notion.int.exe.xyz` with no credential at all, and
|
|
1887
|
+
rotation happens in one place the client controls.
|
|
1888
|
+
|
|
1889
|
+
Two rules here, both learned expensively:
|
|
1890
|
+
|
|
1891
|
+
**Pin the API version the SPEC describes, not the one in a doc example.** A
|
|
1892
|
+
version header copied from documentation while the agent's OpenAPI spec
|
|
1893
|
+
describes a newer API produces errors that describe the wrong problem entirely.
|
|
1894
|
+
One real case cost most of a day: `Notion-Version: 2022-06-28` against a spec
|
|
1895
|
+
using `/v1/data_sources/...` returned `invalid_request_url`, intermittent 503s
|
|
1896
|
+
from search, and "not shared with the integration" — three different messages,
|
|
1897
|
+
none of them about the version mismatch, two of which look like a permissions
|
|
1898
|
+
or availability problem you can waste hours "fixing" on the client's side.
|
|
1899
|
+
|
|
1900
|
+
**Use `--header` for the token, never `--bearer=-`.** The stdin form mangles the
|
|
1901
|
+
value and the API answers 401 "token is invalid".
|
|
1902
|
+
|
|
1903
|
+
**Large specs with ambiguous ID schemes need a purpose-built tool, not a raw
|
|
1904
|
+
connection.** Notion's spec is ~1.2MB and splits `database_id` from
|
|
1905
|
+
`data_source_id` for the same object; a model deriving calls from it picks
|
|
1906
|
+
wrong. Pin the endpoint and the IDs in a small tool under `agent/tools/`, keep
|
|
1907
|
+
the generic connection for the long tail, and point the instructions at the
|
|
1908
|
+
tool. Reads went from unreliable to deterministic with about sixty lines.
|
|
1909
|
+
|
|
1910
|
+
### 11.6 The engineer layer, self-hosted
|
|
1911
|
+
|
|
1912
|
+
`--engineer` scaffolds a **builder subagent** that owns the build capability, so
|
|
1913
|
+
the root agent never gets a shell. It comes with the full production loop —
|
|
1914
|
+
workshop sandbox, Playwright, screenshots, visual verification, delivery — not a
|
|
1915
|
+
reduced version of the Vercel one.
|
|
1916
|
+
|
|
1917
|
+
- **Subagents own their sandbox; they do NOT inherit the root's.** A builder
|
|
1918
|
+
without its own `sandbox/sandbox.ts` gets a bare template and every screenshot
|
|
1919
|
+
fails with `Cannot find module 'playwright'` while the root's template is
|
|
1920
|
+
fine. `kyb doctor` checks for this.
|
|
1921
|
+
- Conversely, if the root agent runs no code, it needs **no** root sandbox.
|
|
1922
|
+
Leaving one there costs a full template prewarm on every deploy for nothing.
|
|
1923
|
+
|
|
1924
|
+
**Choosing a sandbox backend:**
|
|
1925
|
+
|
|
1926
|
+
`docker()` on the agent's own VM is the default and the safer choice. Reach for
|
|
1927
|
+
`exeSandbox()` — a whole exe.dev VM per session, with Docker available *inside*
|
|
1928
|
+
it and its own public URL — only when the work needs real isolation or
|
|
1929
|
+
per-session compute.
|
|
1930
|
+
|
|
1931
|
+
`exeSandbox()` carries one hard constraint you must raise with the client before
|
|
1932
|
+
choosing it: **it needs a full-permission account SSH key.** exe.dev keys
|
|
1933
|
+
registered through an API token inherit that token's command scope and cannot
|
|
1934
|
+
open a shell at all, so there is no scoped credential that can run commands. The
|
|
1935
|
+
key it does need grants shell to **every VM on that exe.dev account**. The
|
|
1936
|
+
backend therefore refuses to start unless the account is dedicated to this agent
|
|
1937
|
+
(its own VM plus its sandboxes), naming any foreign VMs it finds. Overriding
|
|
1938
|
+
that with `allowSharedAccount: true` is a decision for the client to make in
|
|
1939
|
+
writing, not for you to make on their behalf. VM lifecycle stays on a separately
|
|
1940
|
+
scoped token that cannot exec, so neither credential is sufficient alone.
|
|
1941
|
+
|
|
1942
|
+
### 11.7 Showing the client what the agent built
|
|
1943
|
+
|
|
1944
|
+
- **Vercel Blob refuses to serve HTML inline** — it forces a download. Use it
|
|
1945
|
+
for documents and exports, never to show a web page.
|
|
1946
|
+
- **exe.dev forwards ports 3000–9999** to `https://<vm>.exe.xyz:<port>/`, but a
|
|
1947
|
+
VM has exactly **one public port** and the agent's webhook already owns it.
|
|
1948
|
+
Alternate ports are account-gated: fine for the client reviewing work, not for
|
|
1949
|
+
the public.
|
|
1950
|
+
- **Anything genuinely public needs a deploy target** — the client's own Vercel
|
|
1951
|
+
token, or their hosting. Treat "public" as a deploy step, not a toggle, and
|
|
1952
|
+
price it into the scope.
|
|
1953
|
+
- A sandbox is a container: its ports are not reachable from the host, so a dev
|
|
1954
|
+
server inside it cannot be previewed directly. Copy the artifact out (the
|
|
1955
|
+
`preview` tool in `@kybernesis/exe`) or deploy it.
|
|
1956
|
+
|
|
1957
|
+
### 11.8 Prove the restart, every time
|
|
1958
|
+
|
|
1959
|
+
There is no deployment pipeline here to tell you a release landed. A restart
|
|
1960
|
+
that silently fails leaves the agent serving a stale build — new connections,
|
|
1961
|
+
tools, and instructions never appear — and **every test you run afterwards
|
|
1962
|
+
measures yesterday's agent.** One session lost an hour to exactly this.
|
|
1963
|
+
|
|
1964
|
+
`scripts/eve-server.sh` asserts the running process started *after* the build it
|
|
1965
|
+
should be serving, and fails loudly when it did not. Use it rather than
|
|
1966
|
+
`pkill` + `npx eve start` by hand. Related: `pkill -f <pattern>` over SSH kills
|
|
1967
|
+
your own session when the pattern appears in the SSH command line — and can take
|
|
1968
|
+
the agent down with it. Use the pidfile.
|
|
1969
|
+
|
|
1970
|
+
Also: a long-lived channel session caches the compiled agent. After changing
|
|
1971
|
+
capabilities, start a **fresh conversation** before deciding the change did not
|
|
1972
|
+
work.
|
|
1973
|
+
|
|
1974
|
+
### 11.9 Credential checklist — collect ALL of these from the client
|
|
1975
|
+
|
|
1976
|
+
Nothing here can be borrowed from another agent or another account.
|
|
1977
|
+
|
|
1978
|
+
1. **Host** — VM/server, plus the platform token if the agent provisions anything
|
|
1979
|
+
2. **Model source** — their LLM API key, gateway allocation, or connected subscription
|
|
1980
|
+
3. **Channel app** — their Slack app (bot + app token) / Photon project / bot token
|
|
1981
|
+
4. **Arcana** — workspaces + scoped `kb_` keys (one per brain, plus `-eval`)
|
|
1982
|
+
5. **Storage for deliverables** — their blob store, or a served host directory
|
|
1983
|
+
6. **Deploy target** — their Vercel token or hosting, if the agent ships sites
|
|
1984
|
+
7. **Sandbox credentials** — only if using `exeSandbox()` (see 11.6)
|
|
1985
|
+
8. **Control plane** — agent registered and the pilot cohort granted
|
|
1986
|
+
|
|
1987
|
+
### 11.10 Before calling it done
|
|
1988
|
+
|
|
1989
|
+
`kyb doctor` green (or every warning consciously accepted), the eval suite green
|
|
1990
|
+
against the client's `-eval` workspace, and a live turn on the real surface —
|
|
1991
|
+
sent from the client's own device, not yours.
|
|
1992
|
+
|
|
1993
|
+
## 12. Known gaps — state these plainly, do not sell around them
|
|
1720
1994
|
|
|
1721
1995
|
Being straight about these is a feature. Clients have met vendors who were not.
|
|
1722
1996
|
|
|
@@ -0,0 +1,146 @@
|
|
|
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.
|
|
97
|
+
|
|
98
|
+
## Third-party APIs: version pinning and spec-derived calls
|
|
99
|
+
|
|
100
|
+
Two failures here cost most of a day on the first deployment. Both look like
|
|
101
|
+
outages or permission problems and are neither.
|
|
102
|
+
|
|
103
|
+
**Pin the API version the SPEC describes, not the one in a doc example.**
|
|
104
|
+
Notion's OpenAPI spec describes their current API (`/v1/data_sources/…`), but
|
|
105
|
+
eve's docs example pins `Notion-Version: 2022-06-28`, where that endpoint does
|
|
106
|
+
not exist. The mismatch returns `invalid_request_url`, `service_unavailable`
|
|
107
|
+
(503) on search, and "not shared with the integration" — three different lies,
|
|
108
|
+
none of them about the actual problem. Verify by making the SAME call the agent
|
|
109
|
+
makes, headers included.
|
|
110
|
+
|
|
111
|
+
**When the agent and your manual test disagree, the difference between the two
|
|
112
|
+
requests IS the bug.** Diff them at the first contradiction. Repeatedly proving
|
|
113
|
+
"the token works" with a hand-written curl while the agent fails proves nothing
|
|
114
|
+
if your curl sends a different version header.
|
|
115
|
+
|
|
116
|
+
**An agent's error message is a hypothesis, not evidence.** It will confidently
|
|
117
|
+
report an outage or a permissions problem it has not verified. Read the actual
|
|
118
|
+
request and response before acting — and never change a client's permissions on
|
|
119
|
+
an agent's say-so.
|
|
120
|
+
|
|
121
|
+
**Large specs with ambiguous ID schemes need a purpose-built tool, not a raw
|
|
122
|
+
connection.** Notion's spec is ~1.2MB and splits `database_id` from
|
|
123
|
+
`data_source_id` for the same board; a model deriving calls from it picks the
|
|
124
|
+
wrong one. Pin the endpoint and the IDs in a small tool
|
|
125
|
+
(`agent/tools/<domain>.ts`), keep the generic connection for the long tail, and
|
|
126
|
+
point the instructions at the tool.
|
|
127
|
+
|
|
128
|
+
**Credential brokering (exe http-proxy) is the right default off-Vercel:**
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
integrations add http-proxy --name <svc> --target https://api.example.com \
|
|
132
|
+
--header 'Authorization:Bearer <token>' --header '<Version-Header>:<value>' \
|
|
133
|
+
--attach vm:<vm>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Use `--header` for the token, not `--bearer=-`: the stdin form mangles it and
|
|
137
|
+
the API answers 401 "token is invalid".
|
|
138
|
+
|
|
139
|
+
## Restarts must be proven, not assumed
|
|
140
|
+
|
|
141
|
+
A restart that silently fails leaves the agent serving a stale build — new
|
|
142
|
+
connections, tools, and instructions never appear, and every later test measures
|
|
143
|
+
yesterday's agent. Assert the process started AFTER the build it should serve
|
|
144
|
+
(`scripts/eve-server.sh` and the restart pattern in `@kybernesis/exe` do this).
|
|
145
|
+
Related: a long-lived channel session caches the compiled agent, so start a
|
|
146
|
+
fresh conversation after changing capabilities.
|