@kybernesis/create 0.21.0 → 0.23.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/add.js CHANGED
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import { bold, dim, green, red, run, yellow } from "./util.js";
4
4
  import { addBuzz } from "./buzz.js";
5
+ import { applyDispatch, applyEngineer, modelConfigOf } from "./capabilities.js";
5
6
  import { CHANNEL_KINDS, arcanaMemorySlotTs, channelPlan } from "./templates.js";
6
7
  /**
7
8
  * `kyb add channel <kind>` — put another surface on an agent that already exists.
@@ -26,11 +27,15 @@ export async function add(what, rest) {
26
27
  // the provisioner and people naturally type, so both spellings land here.
27
28
  if (what === "buzz" || (what === "channel" && rest.find((a) => !a.startsWith("-")) === "buzz"))
28
29
  return addBuzz();
30
+ if (what === "dispatch" || what === "engineer")
31
+ return addCapability(what);
29
32
  if (what !== "channel") {
30
33
  console.error(red(`kyb add: don't know how to add "${what ?? ""}".`));
31
34
  console.log(dim(` kyb add channel <${CHANNEL_KINDS.filter((k) => k !== "none").join("|")}>`));
32
35
  console.log(dim(` kyb add memory ${dim("— an eve 0.49 memory slot backed by Arcana (recall before every turn)")}`));
33
36
  console.log(dim(` kyb add buzz ${dim("— the agent's own Buzz identity, tools and bridge, ready for its owner's community")}`));
37
+ console.log(dim(` kyb add dispatch ${dim("— call and be called by the agents the control plane allows (governed edges)")}`));
38
+ console.log(dim(` kyb add engineer ${dim("— the builder subagent: workshop sandbox, vision dev loop, file delivery")}`));
34
39
  process.exitCode = 1;
35
40
  return;
36
41
  }
@@ -145,3 +150,35 @@ export function addMemory(cwd = resolve(process.cwd())) {
145
150
  console.log(dim(` recall before every turn from ARCANA_COMPANY_WORKSPACE; capture off; ${extensionMounted ? "tools from the extension" : "remember/recall/search as memory__* tools"}`));
146
151
  console.log(dim(" needs eve >= 0.49 and @kybernesis/arcana >= 0.4 — kyb upgrade carries both. Then: npx eve info"));
147
152
  }
153
+ /**
154
+ * `kyb add dispatch` / `kyb add engineer` — what `kyb init` now builds in, put
155
+ * on an agent scaffolded before it did. Same code path as init, so an added
156
+ * capability is the same files a new agent gets.
157
+ */
158
+ function addCapability(what) {
159
+ const dir = resolve(process.cwd());
160
+ if (!existsSync(join(dir, "package.json")) || !existsSync(join(dir, "agent"))) {
161
+ console.error(red(`kyb add ${what}: run this inside an agent directory.`));
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+ const host = existsSync(join(dir, "scripts/eve-server.sh")) ? "exe" : "vercel";
166
+ console.log(bold(`kyb add ${what}`));
167
+ console.log(dim(` agent: ${agentNameOf(dir)}\n host: ${host}`));
168
+ if (what === "dispatch") {
169
+ for (const file of applyDispatch(dir))
170
+ console.log(green(` + ${file}`));
171
+ }
172
+ else {
173
+ if (existsSync(join(dir, "agent/subagents/builder"))) {
174
+ console.log(yellow(" agent/subagents/builder already exists — leaving it alone."));
175
+ return;
176
+ }
177
+ const { files, steps } = applyEngineer(dir, host, modelConfigOf(dir));
178
+ for (const file of files)
179
+ console.log(green(` + ${file}`));
180
+ if (steps.length)
181
+ console.log(`\n${bold("Then:")}\n${steps.map((s, i) => ` ${i + 1}. ${s}`).join("\n")}`);
182
+ }
183
+ console.log(dim("\n Type-check, build and restart the agent for it to take effect."));
184
+ }
@@ -0,0 +1,22 @@
1
+ import { type HostKind } from "./templates.js";
2
+ import type { ModelScaffoldConfig } from "./model-reach.js";
3
+ /**
4
+ * Agent-to-agent: the eve channel becomes the governed dispatch door (people
5
+ * still come in through kybernesisAuth in the same walk) and granted peers
6
+ * become tools. arpAuth is put back into the walk when the agent has the
7
+ * identity item, since the new channel replaces the file that carried it.
8
+ */
9
+ export declare function applyDispatch(dir: string): string[];
10
+ /**
11
+ * The engineer: a declared `builder` subagent with its own workshop sandbox,
12
+ * memory and skills, plus the browser and GitHub tools at the root.
13
+ */
14
+ export declare function applyEngineer(dir: string, host: HostKind, model: ModelScaffoldConfig): {
15
+ files: string[];
16
+ steps: string[];
17
+ };
18
+ /**
19
+ * The model config an existing agent was authored with, read back from its
20
+ * agent/agent.ts, so a builder added later runs the same route as its root.
21
+ */
22
+ export declare function modelConfigOf(dir: string): ModelScaffoldConfig;
@@ -0,0 +1,73 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { finalizeGithubToolsRegistryMount, wireArpAuth } from "./init.js";
4
+ import { DISPATCH_DEP, dispatchEveChannelTs, dispatchPeersTs, engineerPlan } from "./templates.js";
5
+ import { run, yellow } from "./util.js";
6
+ /**
7
+ * The capabilities every company agent carries, applied the same way by
8
+ * `kyb init` and by `kyb add` on an agent built before they were standard.
9
+ * Each returns what it wrote, so `add` can report it.
10
+ */
11
+ // Official eve-registry limbs installed with the engineer subagent.
12
+ // connection/vercel is Vercel-Connect-backed, so it is VERCEL-HOST ONLY: on a
13
+ // self-hosted agent it cannot get an OIDC token and the agent fails to boot.
14
+ const ENGINEER_ITEMS_ALL = ["extension/agent-browser", "extension/github-tools"];
15
+ const ENGINEER_ITEMS_VERCEL = ["connection/vercel"];
16
+ function write(dir, path, content) {
17
+ const full = join(dir, path);
18
+ mkdirSync(join(full, ".."), { recursive: true });
19
+ writeFileSync(full, content);
20
+ }
21
+ /**
22
+ * Agent-to-agent: the eve channel becomes the governed dispatch door (people
23
+ * still come in through kybernesisAuth in the same walk) and granted peers
24
+ * become tools. arpAuth is put back into the walk when the agent has the
25
+ * identity item, since the new channel replaces the file that carried it.
26
+ */
27
+ export function applyDispatch(dir) {
28
+ run("npm", ["install", DISPATCH_DEP, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
29
+ let channel = dispatchEveChannelTs();
30
+ if (existsSync(join(dir, "agent/channels/arp.ts")))
31
+ channel = wireArpAuth(channel) ?? channel;
32
+ write(dir, "agent/channels/eve.ts", channel);
33
+ write(dir, "agent/tools/peers.ts", dispatchPeersTs());
34
+ return ["agent/channels/eve.ts", "agent/tools/peers.ts"];
35
+ }
36
+ /**
37
+ * The engineer: a declared `builder` subagent with its own workshop sandbox,
38
+ * memory and skills, plus the browser and GitHub tools at the root.
39
+ */
40
+ export function applyEngineer(dir, host, model) {
41
+ const plan = engineerPlan(host, model);
42
+ run("npm", ["install", ...plan.deps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
43
+ const items = [...ENGINEER_ITEMS_ALL, ...(host === "vercel" ? ENGINEER_ITEMS_VERCEL : [])];
44
+ for (const item of items) {
45
+ const ok = run("npx", ["eve", "add", item, "--overwrite"], { cwd: dir, allowFail: true });
46
+ if (item === "extension/github-tools")
47
+ finalizeGithubToolsRegistryMount(dir, ok);
48
+ if (!ok)
49
+ console.log(yellow(` ! ${item} did not install cleanly — re-run: npx eve add ${item}`));
50
+ }
51
+ for (const file of plan.files)
52
+ write(dir, file.path, file.content);
53
+ // A declared subagent sees none of the root's skills, so the memory skills
54
+ // its instructions name are copied in beside it.
55
+ const skills = join(dir, "node_modules/@kybernesis/arcana/dist/extension/skills");
56
+ if (existsSync(skills))
57
+ cpSync(skills, join(dir, "agent/subagents/builder/skills"), { recursive: true });
58
+ else
59
+ console.log(yellow(` ! arcana skills not found at ${skills} — copy them into agent/subagents/builder/skills`));
60
+ return { files: plan.files.map((f) => f.path), steps: plan.steps };
61
+ }
62
+ /**
63
+ * The model config an existing agent was authored with, read back from its
64
+ * agent/agent.ts, so a builder added later runs the same route as its root.
65
+ */
66
+ export function modelConfigOf(dir) {
67
+ const source = existsSync(join(dir, "agent/agent.ts")) ? readFileSync(join(dir, "agent/agent.ts"), "utf8") : "";
68
+ const reach = /\breach\s*:\s*["']([a-z-]+)["']/.exec(source)?.[1];
69
+ const model = /\bmodel\s*:\s*["']([^"']+)["']/.exec(source)?.[1];
70
+ if (reach === "claude-sub")
71
+ return { reach: "claude-sub", model: (model ?? "claude-opus-5").replace(/^anthropic\//, "") };
72
+ return { reach: "default", model: model ?? "anthropic/claude-sonnet-5" };
73
+ }
package/dist/cli.js CHANGED
@@ -222,7 +222,7 @@ ${bold("kyb")} ${dim(VERSION)} — Kybernesis agent scaffolder & FDE toolkit
222
222
  ${dim(" Every command below exists in this build. If one is missing, the install is older than the docs:")}
223
223
  ${dim(" npm i -g @kybernesis/create@latest")}
224
224
 
225
- ${bold("kyb init [name]")} scaffold a Kybernesis eve agent (core: enterprise + arcana + evals)
225
+ ${bold("kyb init [name]")} scaffold a Kybernesis eve agent (core: enterprise + arcana + dispatch + evals)
226
226
  --channel=<kind> ${dim("none|slack|imessage|telegram|discord|web (default: none)")}
227
227
  --host=<kind> ${dim("vercel|exe (default: vercel)")}
228
228
  --subagents=a,b ${dim("department subagents (default: none)")}
@@ -235,6 +235,8 @@ ${dim(" npm i -g @kybernesis/create@latest")}
235
235
  ${dim("slack|imessage|telegram|discord|web — writes the channel, deps and env")}
236
236
  ${bold("kyb add buzz")} ${dim("the agent's own Buzz identity, tools and bridge (connected from the Channels page)")}
237
237
  ${bold("kyb add memory")} ${dim("eve 0.49 memory slot backed by Arcana: recall before every turn")}
238
+ ${bold("kyb add dispatch")} ${dim("agent-to-agent: governed door + control-plane peers as tools")}
239
+ ${bold("kyb add engineer")} ${dim("the builder subagent: workshop sandbox, vision dev loop, delivery")}
238
240
  ${bold("kyb doctor")} preflight checks (keys, issuer, envs, discovery)
239
241
  ${bold("kyb skills")} install/refresh the FDE skill suite for Claude Code
240
242
  --global ${dim("install to ~/.claude/skills instead of this repo")}
package/dist/doctor.js CHANGED
@@ -61,6 +61,8 @@ function modelReachCheck(reach) {
61
61
  return { verdict: "pass", label: "Model reach: exe (host LLM integration)" };
62
62
  case "grok-sub":
63
63
  return { verdict: "pass", label: "Model reach: grok-sub (direct xAI subscription credential)" };
64
+ case "chatgpt-sub":
65
+ return { verdict: "pass", label: "Model reach: chatgpt-sub (ChatGPT subscription through Codex)" };
64
66
  case "gateway":
65
67
  return { verdict: "pass", label: "Model reach: gateway" };
66
68
  case "direct-provider":
package/dist/init.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { appendFileSync, chmodSync, copyFileSync, 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, red, yellow, } from "./util.js";
4
- import { CHANNEL_KINDS, channelPlan, computerPlan, engineerPlan, envExample, evalFileTs, exeEvalConfigTs, evalScript, hostAgentTs, hostSteps, githubToolsMountTs, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
4
+ import { CHANNEL_KINDS, channelPlan, computerPlan, envExample, evalFileTs, exeEvalConfigTs, evalScript, hostAgentTs, hostSteps, githubToolsMountTs, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
5
5
  import { suiteDir } from "./skills.js";
6
+ import { applyDispatch, applyEngineer } from "./capabilities.js";
6
7
  import { configureArcana } from "./arcana.js";
7
8
  import { upsertEnv } from "./envfile.js";
8
9
  import { systemdRestartCommand } from "./systemd.js";
@@ -21,11 +22,6 @@ const CORE_ITEMS = ["enterprise", "arcana", "evals", "identity"];
21
22
  // Vercel function cannot keep a desktop alive, so there — and only there —
22
23
  // both are skipped, and init says so out loud.
23
24
  const CORE_EXE_ITEMS = ["vault"];
24
- // Official eve-registry limbs installed with the engineer subagent.
25
- // connection/vercel is Vercel-Connect-backed, so it is VERCEL-HOST ONLY: on a
26
- // self-hosted agent it cannot get an OIDC token and the agent fails to boot.
27
- const ENGINEER_ITEMS_ALL = ["extension/agent-browser", "extension/github-tools"];
28
- const ENGINEER_ITEMS_VERCEL = ["connection/vercel"];
29
25
  /**
30
26
  * Replace Eve's freshly installed registry mount with the Kybernesis-managed
31
27
  * token-aware form. A failed registry add must not create a capability file.
@@ -132,6 +128,9 @@ export async function init(rawName, options = {}) {
132
128
  for (const item of CORE_ITEMS) {
133
129
  run("npx", ["eve", "add", `@kybernesis/${item}`, "--overwrite"], { cwd: dir });
134
130
  }
131
+ // Core too: other agents the company allows can call this one, and it can
132
+ // call them, governed by edges granted in the control plane.
133
+ applyDispatch(dir);
135
134
  // Anthropic is always the exe eval judge. Both providers are installed on
136
135
  // every exe agent: exeModelReach can be switched to any route at boot, and
137
136
  // between them the routes call both.
@@ -177,18 +176,10 @@ export async function init(rawName, options = {}) {
177
176
  ` This agent cannot be connected to a desktop until they install.`));
178
177
  }
179
178
  }
180
- const engPlan = engineer ? engineerPlan(host, model) : null;
181
- if (engPlan) {
179
+ let engSteps = [];
180
+ if (engineer) {
182
181
  console.log(bold("\n2c Engineer subagent: workshop sandbox + vision dev loop …"));
183
- run("npm", ["install", ...engPlan.deps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
184
- const engItems = [...ENGINEER_ITEMS_ALL, ...(host === "vercel" ? ENGINEER_ITEMS_VERCEL : [])];
185
- for (const item of engItems) {
186
- const ok = run("npx", ["eve", "add", item, "--overwrite"], { cwd: dir, allowFail: true });
187
- if (item === "extension/github-tools")
188
- finalizeGithubToolsRegistryMount(dir, ok);
189
- if (!ok)
190
- console.log(yellow(` ! ${item} did not install cleanly — re-run: npx eve add ${item}`));
191
- }
182
+ engSteps = applyEngineer(dir, host, model).steps;
192
183
  }
193
184
  const compPlan = host === "exe" ? computerPlan(name) : null;
194
185
  if (host !== "exe") {
@@ -382,13 +373,6 @@ export async function init(rawName, options = {}) {
382
373
  mkdirSync(join(full, ".."), { recursive: true });
383
374
  writeFileSync(full, file.content);
384
375
  }
385
- if (engPlan) {
386
- for (const file of engPlan.files) {
387
- const full = join(dir, file.path);
388
- mkdirSync(join(full, ".."), { recursive: true });
389
- writeFileSync(full, file.content);
390
- }
391
- }
392
376
  if (compPlan) {
393
377
  for (const file of compPlan.files) {
394
378
  const full = join(dir, file.path);
@@ -412,6 +396,7 @@ export async function init(rawName, options = {}) {
412
396
  : yellow(" ! verify manually: npm run typecheck && npx eve info"));
413
397
  const parts = [
414
398
  "governed (enterprise)",
399
+ "agent-to-agent (dispatch)",
415
400
  "remembering (arcana)",
416
401
  "self-testing (evals)",
417
402
  channel === "none" ? null : `${channel} channel`,
@@ -427,7 +412,7 @@ export async function init(rawName, options = {}) {
427
412
  `Arcana: create workspaces (${name}-company, ${name}-eval${depts.map((d) => `, ${name}-${d}`).join("")}) + scoped kb_ keys; fill .env.local from .env.example`,
428
413
  ...hostSteps(host, name, model),
429
414
  ...plan.steps,
430
- ...(engPlan?.steps ?? []),
415
+ ...engSteps,
431
416
  ...(compPlan?.steps ?? []),
432
417
  `Control plane: register agent "${name}" at ${issuer}/agents + grant the pilot cohort`,
433
418
  `npm run eval → green → deploy → live smoke + the revoke demo`,
@@ -21,6 +21,8 @@ export type DoctorModelReach = {
21
21
  kind: "exe";
22
22
  } | {
23
23
  kind: "grok-sub";
24
+ } | {
25
+ kind: "chatgpt-sub";
24
26
  } | {
25
27
  kind: "gateway";
26
28
  } | {
@@ -1,3 +1,6 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
1
4
  export const MODEL_REACH_ENV = "KYB_MODEL_REACH";
2
5
  export const CLAUDE_SUBSCRIPTION_MODEL = "claude-opus-5";
3
6
  /** Resolve the one-shot scaffold selector. An explicit CLI value wins over Factory env. */
@@ -36,7 +39,7 @@ export function resolveModelScaffold(options) {
36
39
  export function switchedModelReach(source, env) {
37
40
  if (!/\bexeModelReach\s*\(/.test(source))
38
41
  return null;
39
- const authored = /\breach\s*:\s*["'](gateway|byok|claude-sub)["']/.exec(source)?.[1];
42
+ const authored = /\breach\s*:\s*["'](gateway|byok|claude-sub|grok-sub|chatgpt-sub)["']/.exec(source)?.[1];
40
43
  if (!authored)
41
44
  return { kind: "unresolved", reason: "exeModelReach has no literal default reach to read" };
42
45
  const model = env.EXE_MODEL ?? /\bmodel\s*:\s*["']([^"']+)["']/.exec(source)?.[1] ?? "";
@@ -45,6 +48,18 @@ export function switchedModelReach(source, env) {
45
48
  return { kind: "exe" };
46
49
  if (reach === "claude-sub")
47
50
  return { kind: "claude-sub" };
51
+ // Runs only once `grok login` has left a credential; the agent falls back otherwise.
52
+ if (reach === "grok-sub") {
53
+ const auth = env.GROK_AUTH_PATH?.trim() || join(homedir(), ".grok", "auth.json");
54
+ return existsSync(auth) ? { kind: "grok-sub" } : null;
55
+ }
56
+ // Needs eve's chatgpt passed in, and a Codex sign-in on the host.
57
+ if (reach === "chatgpt-sub") {
58
+ if (!/\bchatgpt\s*[,}]/.test(source))
59
+ return null;
60
+ const auth = join(env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "auth.json");
61
+ return existsSync(auth) ? { kind: "chatgpt-sub" } : null;
62
+ }
48
63
  if (reach !== "byok")
49
64
  return null;
50
65
  const provider = /^(anthropic|openai)\//.exec(model)?.[1];
@@ -41,6 +41,12 @@ export declare function channelPlan(kind: ChannelKind, name: string, host: HostK
41
41
  export type HostKind = "vercel" | "exe";
42
42
  export declare function hostAgentTs(host: HostKind, model: ModelScaffoldConfig): string;
43
43
  export declare function hostSteps(host: HostKind, name: string, model: ModelScaffoldConfig): string[];
44
+ /** agent/channels/eve.ts: people, granted agents, and the local eval runner, through one door. */
45
+ export declare function dispatchEveChannelTs(): string;
46
+ /** agent/tools/peers.ts: every agent this one may call, as tools, resolved per turn. */
47
+ export declare function dispatchPeersTs(): string;
48
+ /** Pinned to the eve line the scaffold certifies, like every other @kybernesis dep it installs. */
49
+ export declare const DISPATCH_DEP = "@kybernesis/dispatch@^0.7.0";
44
50
  export interface EngineerPlan {
45
51
  files: Array<{
46
52
  path: string;
@@ -49,6 +55,20 @@ export interface EngineerPlan {
49
55
  deps: string[];
50
56
  steps: string[];
51
57
  }
58
+ /**
59
+ * What the builder is told. A declared subagent inherits nothing from the
60
+ * root, instructions included, so without this it builds with no idea it is
61
+ * briefed second-hand, that it remembers, or where its authority stops.
62
+ * Agent-facing prose: no agent or company names, it ships to every customer.
63
+ */
64
+ export declare function builderInstructionsMd(): string;
65
+ /**
66
+ * The builder's memory: the company brain the root uses, not a workspace of
67
+ * its own. A company agent has one brain, and the agent that builds was the one
68
+ * remembering nothing about what it built. ARCANA_BUILDER_WORKSPACE splits it
69
+ * off for an agent that wants a separate one.
70
+ */
71
+ export declare function builderArcanaTs(): string;
52
72
  export declare function engineerPlan(host: HostKind, model: ModelScaffoldConfig): EngineerPlan;
53
73
  /**
54
74
  * The eval judge, for an exe.dev host.
package/dist/templates.js CHANGED
@@ -425,9 +425,10 @@ function agentModelTemplate(host, config) {
425
425
  throw new Error("claude-sub model templates require an exe host");
426
426
  if (host === "exe") {
427
427
  /**
428
- * Every exe agent takes the runtime switch: KYB_MODEL_REACH (gateway, byok
429
- * or claude-sub) picks the route at boot, so the setup route can change it
430
- * by writing env. `reach` here is what runs while it is unset, which is
428
+ * Every exe agent takes the runtime switch: KYB_MODEL_REACH (gateway, byok,
429
+ * claude-sub, grok-sub or chatgpt-sub) picks the route at boot, so the
430
+ * setup route can change it by writing env. `chatgpt` is passed so the
431
+ * ChatGPT subscription is one of those routes; nothing calls it until then. `reach` here is what runs while it is unset, which is
431
432
  * exactly what this scaffold used to hard-code.
432
433
  *
433
434
  * The subscription scaffold still names a gateway id, used only if the
@@ -439,8 +440,9 @@ function agentModelTemplate(host, config) {
439
440
  return {
440
441
  imports: `import { createAnthropic } from "@ai-sdk/anthropic";\n` +
441
442
  `import { createOpenAI } from "@ai-sdk/openai";\n` +
443
+ `import { chatgpt } from "eve/models/openai";\n` +
442
444
  `import { exeModelReach } from "@kybernesis/exe";\n\n`,
443
- fields: ` ...exeModelReach({ ${defaults}, createOpenAI, createAnthropic }),\n`,
445
+ fields: ` ...exeModelReach({ ${defaults}, createOpenAI, createAnthropic, chatgpt }),\n`,
444
446
  };
445
447
  }
446
448
  return { imports: "", fields: ` model: ${JSON.stringify(config.model)},\n` };
@@ -452,7 +454,7 @@ export function hostAgentTs(host, model) {
452
454
  : (model.reach === "claude-sub"
453
455
  ? `// By default a Claude subscription through the loopback OAuth proxy — no API key or exe gateway.\n`
454
456
  : `// By default the exe.dev LLM integration — no provider key on the host; exe\n// injects the credential (managed gateway, your API key, or a connected\n// ChatGPT subscription) server-side.\n`) +
455
- `// KYB_MODEL_REACH (gateway | byok | claude-sub) switches the route at boot\n// without editing this file; EXE_MODEL names the gateway or BYOK model.\n`;
457
+ `// KYB_MODEL_REACH (gateway | byok | claude-sub | grok-sub | chatgpt-sub) switches the route at boot\n// without editing this file; EXE_MODEL names the gateway or BYOK model.\n`;
456
458
  return `import { defineAgent } from "eve";
457
459
  ${rendered.imports}${comment}export default defineAgent({
458
460
  ${rendered.fields}${host === "exe" ? ` // This long-lived self-hosted process has no metered platform session ceiling.\n limits: { maxInputTokensPerSession: false },\n` : ""}});
@@ -486,6 +488,62 @@ export function hostSteps(host, name, model) {
486
488
  `npx eve deploy`,
487
489
  ];
488
490
  }
491
+ // ── Agent-to-agent (dispatch) ───────────────────────────────────────────
492
+ // Every agent is reachable by the agents its company allows, and can call
493
+ // them, with nothing named in code: the edge is granted in the control plane.
494
+ // The receiving side is the eve channel itself, so it REPLACES the plain
495
+ // governed channel the enterprise item writes; people still come in through
496
+ // kybernesisAuth in the same walk.
497
+ /** agent/channels/eve.ts: people, granted agents, and the local eval runner, through one door. */
498
+ export function dispatchEveChannelTs() {
499
+ return `import { localDev } from "eve/channels/auth";
500
+ import { dispatchChannel } from "@kybernesis/dispatch";
501
+ import { kybernesisAuth } from "@kybernesis/enterprise";
502
+
503
+ // One door, three kinds of caller, each its own decision:
504
+ // - people, through KYBER Studio, the phone and the web app: kybernesisAuth
505
+ // admits only an identity session holding a grant on this agent;
506
+ // - other agents: dispatchChannel's governed walk admits a short-lived A2A
507
+ // token minted for an edge granted in the control plane naming this agent
508
+ // as callee. Nothing is listed here; the grant lives in the control plane;
509
+ // - the eval runner: localDev() accepts loopback only and is inert in
510
+ // production. Local evals send no credentials at all.
511
+ //
512
+ // KYBERNESIS_AGENT must match the control-plane registration exactly,
513
+ // including case: the mint checks the callee by name.
514
+ export default dispatchChannel({
515
+ governed: {
516
+ issuer: process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai",
517
+ agent: process.env.KYBERNESIS_AGENT!,
518
+ },
519
+ extraAuth: [
520
+ kybernesisAuth({
521
+ issuer: process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai",
522
+ agent: process.env.KYBERNESIS_AGENT!,
523
+ }),
524
+ localDev(),
525
+ ],
526
+ });
527
+ `;
528
+ }
529
+ /** agent/tools/peers.ts: every agent this one may call, as tools, resolved per turn. */
530
+ export function dispatchPeersTs() {
531
+ return `import { governedPeers } from "@kybernesis/dispatch";
532
+
533
+ // Every agent this one is ALLOWED to call, as tools, resolved from the control
534
+ // plane at the start of each turn. Granting an edge there is the whole job:
535
+ // the tool appears on the next turn and goes when the edge is revoked, with no
536
+ // file to write and no redeploy.
537
+ //
538
+ // These calls carry THIS agent's identity. Where a peer holds personal data and
539
+ // should answer as the person instead, declare it with remotePeer() under
540
+ // agent/subagents/ (it forwards the principal) and list its name in
541
+ // \`declared\` here so it is not offered twice.
542
+ export default governedPeers();
543
+ `;
544
+ }
545
+ /** Pinned to the eve line the scaffold certifies, like every other @kybernesis dep it installs. */
546
+ export const DISPATCH_DEP = "@kybernesis/dispatch@^0.7.0";
489
547
  /** The workshop sandbox, per host. Same recipe; different backend. */
490
548
  function workshopSandbox(host) {
491
549
  if (host === "exe") {
@@ -597,6 +655,100 @@ export default defineSandbox(() =>
597
655
  );
598
656
  `;
599
657
  }
658
+ /**
659
+ * What the builder is told. A declared subagent inherits nothing from the
660
+ * root, instructions included, so without this it builds with no idea it is
661
+ * briefed second-hand, that it remembers, or where its authority stops.
662
+ * Agent-facing prose: no agent or company names, it ships to every customer.
663
+ */
664
+ export function builderInstructionsMd() {
665
+ return `# Identity
666
+
667
+ You are the builder. You write and ship software: prototypes, deliverables,
668
+ internal tools and fixes. You have the workshop: a sandbox with a shell, a
669
+ filesystem, a browser you can see through, and file delivery.
670
+
671
+ You receive fully briefed tasks from the agent you work for and never see the
672
+ original conversation. Treat the message as the whole brief.
673
+
674
+ # How you work
675
+
676
+ Verify by looking. You can screenshot what you built. A page you have not
677
+ looked at is a page you have not finished.
678
+
679
+ Start dev servers detached. A foreground server blocks the turn and hangs the
680
+ whole task.
681
+
682
+ Say what you actually ran: commands, versions, what passed. "Should work" is not
683
+ a report.
684
+
685
+ Deliver, don't describe. When the output is a file, deliver it and give the
686
+ plain URL. Markdown around a link breaks it in some chat apps.
687
+
688
+ Stop at production. Preview deploys are yours; promoting to production is
689
+ always a person's decision.
690
+
691
+ # Memory
692
+
693
+ You share the company's long-term memory (Arcana) through the \`arcana\`
694
+ connection: recall, search, timeline, remember and brain notes.
695
+
696
+ - Look it up before you start. Never claim something is unknown without
697
+ searching; an empty entity recall is not enough, so escalate to search.
698
+ - Record what you built, what you tried that failed, and the decisions behind a
699
+ stack choice. A project shipped and forgotten teaches nobody.
700
+ - Long-form material goes in a brain note: \`arcana_brain_write\` and
701
+ \`arcana_brain_add\`, always both, or nothing can find it.
702
+ - Load the \`recall\`, \`remember\` or \`brain-note\` skill when the task
703
+ is more than one obvious call.
704
+
705
+ # When the brief is thin
706
+
707
+ Build anyway. You cannot ask anyone; the person who asked is one step removed.
708
+ Take the most reasonable reading, say plainly what you assumed at the top, and
709
+ build what that reading deserves.
710
+ `;
711
+ }
712
+ /**
713
+ * The builder's memory: the company brain the root uses, not a workspace of
714
+ * its own. A company agent has one brain, and the agent that builds was the one
715
+ * remembering nothing about what it built. ARCANA_BUILDER_WORKSPACE splits it
716
+ * off for an agent that wants a separate one.
717
+ */
718
+ export function builderArcanaTs() {
719
+ return `import { defineMcpClientConnection } from "eve/connections";
720
+
721
+ // The company brain, shared with the root agent by design: knowing and doing
722
+ // are different jobs, the memory is not. No placeholder: a builder quietly
723
+ // addressing a workspace nobody owns is amnesia that looks like a model
724
+ // problem. Set it with \`kyb arcana\`.
725
+ const workspace = process.env.ARCANA_BUILDER_WORKSPACE ?? process.env.ARCANA_COMPANY_WORKSPACE;
726
+ if (!workspace) {
727
+ throw new Error("ARCANA_COMPANY_WORKSPACE is not set — run \`kyb arcana\` to set the workspace and its key.");
728
+ }
729
+
730
+ export default defineMcpClientConnection({
731
+ url: "https://mcp.arcana.kybernesis.ai/mcp",
732
+ description:
733
+ "The company's long-term memory (Arcana), shared with the agent you work for: what was built, what was tried, what failed, and the decisions behind it.",
734
+ auth: {
735
+ getToken: async () => {
736
+ // The eval brain is a different workspace with its own key (Arcana keys
737
+ // answer 403 outside their own workspace), recognised by name.
738
+ const evalWorkspace = process.env.ARCANA_EVAL_WORKSPACE;
739
+ const isEval = evalWorkspace ? workspace === evalWorkspace : workspace.endsWith("-eval");
740
+ const token =
741
+ (isEval ? process.env.ARCANA_EVAL_API_KEY : undefined) ??
742
+ process.env.ARCANA_BUILDER_API_KEY ??
743
+ process.env.ARCANA_API_KEY;
744
+ if (!token) throw new Error("ARCANA_API_KEY is not set.");
745
+ return { token };
746
+ },
747
+ },
748
+ headers: { "X-Kyberagent-Agent": async () => workspace },
749
+ });
750
+ `;
751
+ }
600
752
  export function engineerPlan(host, model) {
601
753
  const onExe = host === "exe";
602
754
  const renderedModel = agentModelTemplate(host, model);
@@ -650,6 +802,8 @@ export { default } from "@kybernesis/engineer";
650
802
  path: "agent/subagents/builder/sandbox/sandbox.ts",
651
803
  content: workshopSandbox(host),
652
804
  },
805
+ { path: "agent/subagents/builder/instructions.md", content: builderInstructionsMd() },
806
+ { path: "agent/subagents/builder/connections/arcana.ts", content: builderArcanaTs() },
653
807
  ];
654
808
  if (onExe) {
655
809
  files.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent \u2014 plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -89,9 +89,11 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
89
89
  it also carries the owner's setup routes (`GET /eve/v1/kyb/setup`,
90
90
  `POST .../setup/env`, `POST .../setup/action`): a `manage` grant only;
91
91
  allowlisted env (`EXE_MODEL`, `EXE_LLM_URL`, `ANTHROPIC_API_KEY`,
92
- `OPENAI_API_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`) and the actions
93
- `claude.login` (link, then the code on stdin), `linq.signup`/`linq.verify`,
94
- `link.login` and `vnc.password`. Every change applies at one restart that
92
+ `OPENAI_API_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, and since 0.20
93
+ `KYBERNESIS_VOICE_OPENAI_KEY`, the owner's key for voice calls, with a `voice`
94
+ setup item shown when @kybernesis/voice is installed) and the actions
95
+ `claude.login` (link, then the code on stdin), `linq.signup`/`linq.verify`, `linq.contacts`/`linq.contact.add`/`linq.contact.remove` (who can text a shared line, 0.15),
96
+ `link.login`/`link.logout`, `wallet.add` (0.16: adds the payments item to an agent with a computer, rebuilds, rolls back on a failed build, restarts only after a good one), `linq.add` (0.18: the same for the iMessage channel, offered while `agent/channels/linq.ts` is missing, so an agent built without iMessage can answer the line it signs up), `slack.add` (0.19: the control-plane Slack channel via `kyb add channel slack`, offered to a registered agent on its own host with no Slack channel; the client then asks the control plane to make the agent's Slack app), `buzz.add` (0.19: `kyb add buzz` plus a build, answered 202 at once because the Buzz CLI build takes minutes; the `buzz` item reports `busy`, then the Buzz card or why it failed) and `vnc.password`. Every change applies at one restart that
95
97
  waits until no conversation is in flight (deploy.sh's count). A BYOK key only
96
98
  takes effect when `agent/agent.ts` uses that provider; status says so.
97
99
  Since 0.12 it also takes `KYB_MODEL_REACH` (`gateway`, `byok`,
@@ -129,7 +131,12 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
129
131
  token, grants the person who ran it, idempotent by name),
130
132
  `deploy` (copy + install + restart + PROVE it; `eve deploy` on Vercel),
131
133
  `doctor`, `upgrade` (carries eve to the Kybernesis-CERTIFIED pin, never
132
- blind latest), `skills`. Ships THIS skill suite.
134
+ blind latest), `skills`. Ships THIS skill suite. Since 0.23 every `init`
135
+ carries dispatch (the governed `agent/channels/eve.ts` plus
136
+ `agent/tools/peers.ts` = `governedPeers()`), and `--engineer` writes a
137
+ declared `builder` subagent with its own instructions, the company Arcana
138
+ connection and the memory skills. `kyb add dispatch` and `kyb add engineer`
139
+ retrofit an agent built before that.
133
140
 
134
141
  ## Gotchas that each cost a real debugging session
135
142