@kybernesis/create 0.12.1 → 0.13.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
@@ -275,6 +275,32 @@ export async function doctor() {
275
275
  else {
276
276
  add("fail", `self-hosted: ${shortQueueTimeouts.join(" and ")} left at the 30s default`, "one queue delivery holds a connection open for the entire turn, so any turn slower than the timeout is redelivered and its steps re-run — the agent answers the same question twice, with two different answers, and nothing reports an error. Set both to 900000 in .env.local and restart the server");
277
277
  }
278
+ /**
279
+ * Instructions that name a tool the agent does not have.
280
+ *
281
+ * `deliver` mounts with the engineer layer, which is mounted on the builder
282
+ * subagent — so an agent whose ROOT is told to hand over files (by its own
283
+ * instructions, or by our playbook, which says to) promises a file it
284
+ * cannot send. It cannot see the mismatch either: it writes the contents to
285
+ * a memory note, or apologises without a cause, and the delivery
286
+ * infrastructure passes every check while nobody can receive anything.
287
+ */
288
+ const rootDeliver = existsSync(join(cwd, "agent/tools/deliver.ts"));
289
+ const instructionsMentionDeliver = (() => {
290
+ try {
291
+ const dir = join(cwd, "agent/instructions");
292
+ return readdirSync(dir).some((file) => readFileSync(join(dir, file), "utf8").includes("deliver"));
293
+ }
294
+ catch {
295
+ return false;
296
+ }
297
+ })();
298
+ if (instructionsMentionDeliver && !rootDeliver) {
299
+ add("fail", "instructions tell this agent to deliver files, but the root has no deliver tool", 'add agent/tools/deliver.ts: export { deliver as default } from "@kybernesis/engineer/tools" — the engineer layer mounts it on the builder subagent only');
300
+ }
301
+ else if (rootDeliver) {
302
+ add("pass", "the agent that talks to people can also hand them a file");
303
+ }
278
304
  // The exe VM sandbox backend needs a credential that cannot be scoped.
279
305
  // Surface the blast radius here, where it is still cheap to change course.
280
306
  const sandboxFile = join(cwd, "agent/sandbox/sandbox.ts");
@@ -350,7 +376,11 @@ export async function doctor() {
350
376
  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");
351
377
  }
352
378
  if (existsSync(join(builderDir, "extensions/engineer.ts"))) {
353
- add("pass", "engineer mounted locally on the subagent (root keeps no shell)");
379
+ // Not "the root keeps no shell": every eve agent has a sandbox with bash,
380
+ // read_file, write_file, glob and grep. What the local mount withholds is
381
+ // the build loop and any shell ON THE HOST. This sentence is one people
382
+ // reason about security boundaries from, so it says the true thing.
383
+ add("pass", "engineer mounted locally on the subagent (root gets no host shell, no build loop)");
354
384
  }
355
385
  else {
356
386
  add("warn", "engineer extension not mounted on the subagent", "agent/subagents/builder/extensions/engineer.ts");
package/dist/templates.js CHANGED
@@ -513,18 +513,39 @@ export default defineAgent({
513
513
  "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.",
514
514
  model: ${JSON.stringify(model)},
515
515
  });
516
+ `,
517
+ },
518
+ {
519
+ path: "agent/tools/deliver.ts",
520
+ content: `// Handing a file to the person you are talking to.
521
+ //
522
+ // The engineer layer mounts on \`builder\`, so building stays there — but
523
+ // DELIVERING is not building. It reads one file from this agent's own sandbox
524
+ // and copies it to storage, runs no commands and writes nothing back, and the
525
+ // agent that needs it is the one talking to the user.
526
+ //
527
+ // Without this the root is told (by its instructions, and by our own playbook)
528
+ // to deliver files and has no such tool. It cannot detect the mismatch, so it
529
+ // promises a file and then quietly puts the contents somewhere nobody can
530
+ // reach — a memory note, or an apology with no cause given.
531
+ export { deliver as default } from "@kybernesis/engineer/tools";
516
532
  `,
517
533
  },
518
534
  {
519
535
  path: "agent/subagents/builder/extensions/engineer.ts",
520
536
  content: `// Engineer layer mounted LOCALLY on this subagent (eve >=0.30): screenshot,
521
537
  // deliver, and the trade-school skills belong to \`builder\` alone, so the root
522
- // agent has no shell.
538
+ // gets no shell ON THE HOST and none of the build loop.
539
+ //
540
+ // Not "the root has no shell" — every eve agent has a sandbox with bash,
541
+ // read_file, write_file, glob and grep, and this app builds two templates
542
+ // (root and builder). The root's shell is its own container. The distinction
543
+ // that matters is host access, and the old wording blurred exactly the line
544
+ // people read this comment to understand.
523
545
  //
524
546
  // The root DOES get a browser and GitHub tools — the engineer layer installs
525
547
  // extension/agent-browser and extension/github-tools at the root, deliberately,
526
548
  // because reading a page is not the same blast radius as running a command.
527
- // This comment used to claim otherwise, which is worse than saying nothing.
528
549
  export { default } from "@kybernesis/engineer";
529
550
  `,
530
551
  },
package/dist/upgrade.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { upsertEnv } from "./envfile.js";
4
4
  import { EVE_VERSION, bold, capture, dim, green, red, run, yellow } from "./util.js";
@@ -87,6 +87,98 @@ function repairLocalQueueTimeouts(cwd, deps) {
87
87
  ` ${dim("slower than 30s was redelivered and its steps re-run — the agent answered twice.")}\n` +
88
88
  ` ${dim("Takes effect on the next server restart.")}\n`);
89
89
  }
90
+ /**
91
+ * Finish a Buzz install that a version bump alone leaves half-done.
92
+ *
93
+ * @remarks
94
+ * A capability that arrives in a package but needs four manual steps to switch
95
+ * on has not really shipped. That is what happened here: `kyb upgrade` moved an
96
+ * agent to a version whose whole point was acting in a workspace, and the agent
97
+ * went on being unable to, because the extension was not mounted, the process
98
+ * had none of the environment the bridge had, and the CLI was not on the host.
99
+ * Everything looked upgraded and nothing was different.
100
+ *
101
+ * The person who hit it was an engineer with a terminal, and it still took a
102
+ * hand-written prompt to sort out. A client would not have got there at all —
103
+ * they would have concluded the feature did not work.
104
+ *
105
+ * So each of those is repaired here, from what the host can already tell us:
106
+ * the bridge's own service file holds the relay and the identity, and the rest
107
+ * is a file and a binary.
108
+ */
109
+ function repairBuzzSetup(cwd, deps) {
110
+ if (!deps["@kybernesis/buzz"])
111
+ return;
112
+ const done = [];
113
+ // 1. The extension mount. Without it the tools do not exist, and nothing says so.
114
+ const mount = join(cwd, "agent/extensions/buzz.ts");
115
+ if (!existsSync(mount)) {
116
+ mkdirSync(join(cwd, "agent/extensions"), { recursive: true });
117
+ writeFileSync(mount, `// The agent's hands in Buzz: projects, issues, pull requests, patches, repos,
118
+ ` +
119
+ `// long-form notes, channel canvases, workflows, the feed, media — everything the
120
+ ` +
121
+ `// workspace has beyond talking, which the bridge alone does not provide.
122
+ ` +
123
+ `//
124
+ ` +
125
+ `// Actions are signed with THIS AGENT's key and appear under its name.
126
+ ` +
127
+ `export { default } from "@kybernesis/buzz/extension";
128
+ `);
129
+ done.push("mounted the Buzz extension (agent/extensions/buzz.ts)");
130
+ }
131
+ /**
132
+ * 2. The environment. The bridge runs with the relay and the key; the AGENT
133
+ * process runs with neither, because nothing ever needed it to until the
134
+ * tools existed. Read from the service file rather than asked for, because
135
+ * the answer is already on the host and a person retyping it will eventually
136
+ * mistype it.
137
+ */
138
+ const envPath = join(cwd, ".env.local");
139
+ const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
140
+ if (!/^BUZZ_RELAY=/m.test(env)) {
141
+ const unit = capture("sh", [
142
+ "-c",
143
+ "grep -ohE 'BUZZ_(RELAY|KEYFILE)=[^ \"]+' /etc/systemd/system/*buzz-bridge.service 2>/dev/null | head -2",
144
+ ]);
145
+ const values = {};
146
+ for (const line of (unit ?? "").split("\n")) {
147
+ const [name, ...rest] = line.trim().split("=");
148
+ if (name && rest.length)
149
+ values[name] = rest.join("=").replace(/^"|"$/g, "");
150
+ }
151
+ if (values.BUZZ_RELAY) {
152
+ upsertEnv(cwd, {
153
+ BUZZ_RELAY: values.BUZZ_RELAY,
154
+ ...(values.BUZZ_KEYFILE ? { BUZZ_KEYFILE: values.BUZZ_KEYFILE } : {}),
155
+ });
156
+ done.push("gave the agent process the relay and identity its bridge already had");
157
+ }
158
+ else {
159
+ console.log(` ${yellow("!")} Buzz tools need BUZZ_RELAY and BUZZ_KEYFILE in .env.local — the same ` +
160
+ `values the bridge service uses. Could not read them from this host.`);
161
+ }
162
+ }
163
+ // 3. The CLI itself. Built once, in a container, because no binary is published.
164
+ if (!existsSync(join(cwd, ".buzz/bin/buzz"))) {
165
+ const hasDocker = capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes";
166
+ if (hasDocker) {
167
+ console.log(dim(" building the Buzz CLI for this host (first time only, a few minutes) …"));
168
+ const ok = run("npx", ["kybernesis-buzz", "install-cli"], { cwd, allowFail: true, quiet: true });
169
+ done.push(ok ? "installed the Buzz CLI (.buzz/bin/buzz)" : "could NOT install the Buzz CLI — run: npx kybernesis-buzz install-cli");
170
+ }
171
+ else {
172
+ console.log(` ${yellow("!")} No docker here, so the Buzz CLI cannot be built. Without it the agent ` +
173
+ `can read the workspace but not act in it. Install docker, or set BUZZ_CLI_URL to a ` +
174
+ `binary built for this platform, then: npx kybernesis-buzz install-cli`);
175
+ }
176
+ }
177
+ if (done.length > 0) {
178
+ console.log(` ${green("+")} Buzz: ${done.join("; ")}`);
179
+ console.log(` ${dim("Takes effect after the next build and restart.")}\n`);
180
+ }
181
+ }
90
182
  export async function upgrade(skipEval) {
91
183
  const cwd = process.cwd();
92
184
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
@@ -94,6 +186,7 @@ export async function upgrade(skipEval) {
94
186
  console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
95
187
  warnIfStale();
96
188
  repairLocalQueueTimeouts(cwd, deps);
189
+ repairBuzzSetup(cwd, deps);
97
190
  const toUpgrade = [];
98
191
  const unresolved = [];
99
192
  for (const name of kybernesisPackages(deps)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.12.1",
3
+ "version": "0.13.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",
@@ -178,9 +178,12 @@ something. Chase them a week out — an unprovisioned Vercel team on Day 1 costs
178
178
  - [ ] **Confirm who at ACME is the Slack workspace admin.** Creating the Slack connector
179
179
  requires someone who can approve a Slack app install. If that person is on holiday
180
180
  your Day 2 is Slack-less.
181
- - [ ] **Read the reference implementation.** `~/kyber` is our own production agent and
182
- the canonical example of everything in Phase 4. Skim `agent/agent.ts`,
183
- `agent/instructions/`, `agent/subagents/finance/`, and `evals/`.
181
+ - [ ] **Know the shape you are aiming for** before writing any of it: one
182
+ `agent/agent.ts` that picks a model and mounts extensions, instructions
183
+ split into identity and per-surface, one subagent per department that owns
184
+ its own tools and skills, and an `evals/` suite that gates the deploy.
185
+ Every later phase adds to that skeleton; building it in a different shape
186
+ is what makes the eval suite and the routing evals fight you.
184
187
  - [ ] **Confirm package versions you will pin.** As of 2026-08-06:
185
188
  `@kybernesis/arcana@0.1.1`, `@kybernesis/enterprise@0.1.2`,
186
189
  `@kybernesis/multiplayer@0.1.0`, `@kybernesis/evals@0.2.1`,
@@ -436,9 +439,9 @@ What to know when choosing:
436
439
  principal). Prefer `session.started` over per-turn switching: prompt caches
437
440
  are per model, and every switch re-ingests the conversation at uncached
438
441
  prices. Resolver failures degrade to the fallback, never fail the turn.
439
- - Kyber runs `anthropic/claude-opus-4.8` via the gateway; our eval judges are
440
- configured separately in `evals/evals.config.ts` and must **never** be the
441
- model under test.
442
+ - Whatever the agent runs, eval judges are configured separately in
443
+ `evals/evals.config.ts` and must **never** be the model under test — a model
444
+ grading its own output measures agreement, not correctness.
442
445
 
443
446
  ### 4.1 Install `@kybernesis/enterprise` (governance) — do this FIRST
444
447
 
@@ -532,16 +535,11 @@ npx eve add @kybernesis/multiplayer
532
535
  This writes `agent/channels/slack.ts` and `agent/instructions/multiplayer.md`, and
533
536
  declares the `SLACK_CONNECTOR_UID` env var.
534
537
 
535
- > **Status as of 2026-08-05:** the registry item is **live** (`eve add` resolves and
536
- > writes both files), and the package is dogfooded in `~/kyber` but
537
- > `@kybernesis/multiplayer@0.1.0` is **not yet published to npm**, so the dependency
538
- > install step will fail until it is. Check first:
538
+ > Published and installable `eve add` resolves the registry item and writes both
539
+ > files. Confirm what you are pinning before you promise a version to a client:
539
540
  > ```bash
540
- > npm view @kybernesis/multiplayer version # E404 means the publish hasn't landed
541
+ > npm view @kybernesis/multiplayer version
541
542
  > ```
542
- > If it 404s, install from the workspace checkout at `~/kyber/packages/multiplayer` (or
543
- > vendor the two files by hand — they are shown below and in
544
- > `agent/instructions/multiplayer.md`) and revisit once the publish lands.
545
543
 
546
544
  The whole Slack integration is one file:
547
545
 
@@ -968,7 +966,7 @@ token minted per edge; the callee URL comes from the registry (discovery), env
968
966
  var still wins. THE DEMO: revoke the edge in the admin → the caller is refused
969
967
  (edge_not_granted) within 5 minutes, no redeploy; re-grant → restored. Run it
970
968
  for the client — it's the whole governance story in one minute. Full lifecycle
971
- proven live 2026-08-07 (kyber eve-gtm). Budget note: the deployed agent and
969
+ proven live 2026-08-07 between two governed agents. Budget note: the deployed agent and
972
970
  local eval runs share the project's AI Gateway budget — size it for both.
973
971
 
974
972
  ### 4.4 Author the agent's identity and instructions
@@ -982,8 +980,7 @@ surface.ts example below). Keep always-on instructions to identity, tone, and
982
980
  standing rules; procedures belong in skills (§4.3e) — the model loads those on
983
981
  demand instead of paying for them every turn.
984
982
 
985
- Crib the structure from `~/kyber/agent/instructions/identity.md`, which has
986
- three sections worth copying:
983
+ Give `agent/instructions/identity.md` three sections:
987
984
 
988
985
  1. **Identity** — who the agent is, and *how to write for Slack*: short paragraphs,
989
986
  bullets, no headings unless the answer is genuinely long. Slack is a chat surface, not
@@ -996,9 +993,9 @@ three sections worth copying:
996
993
  asks for something personal in a channel.
997
994
 
998
995
  For per-session context, `defineDynamic` on `session.started` lets you inject
999
- surface-specific instructions. `~/kyber/agent/instructions/surface.ts` is a working
1000
- example: it greets a DM session by the caller's verified name and reminds a channel
1001
- session that everything it posts is public.
996
+ surface-specific instructions greeting a DM session by the caller's verified
997
+ name, and reminding a channel session that everything it posts is public. The
998
+ same agent should not talk to a room the way it talks to one person.
1002
999
 
1003
1000
  **Author these with Claude Code (§4.0), and judge drafts by test, not by
1004
1001
  reading** — paste the discovery notes, have it draft identity.md and the
@@ -1077,7 +1074,7 @@ export default defineAgent({
1077
1074
  workspace })` mount with that department's scoped key) and only that
1078
1075
  subagent gets the connection + skills + instructions. This is the default
1079
1076
  pattern now. The plain-connection alternative below still works (it's what
1080
- pre-0.30 required, and what you'll find in older kyber subagents) when you
1077
+ pre-0.30 required, and what you'll find in subagents written before it) when you
1081
1078
  want the connection without the shipped skills:
1082
1079
 
1083
1080
  ```ts
@@ -1107,9 +1104,9 @@ export default defineAgent({
1107
1104
 
1108
1105
  `agent/schedules/*.ts` for anything recurring — a Monday pipeline summary, a Friday
1109
1106
  financial report. **Schedules live on the root agent only**; a scheduled root turn
1110
- delegates to the subagent that owns the work. `~/kyber/agent/schedules/friday-financials.ts`
1111
- is the working example: it fires Friday 02:00 UTC, delegates to `finance`, and DMs the
1112
- result to a configured Slack user. Note that DMing a user from a schedule needs the
1107
+ delegates to the subagent that owns the work. A worked example: fire at 02:00 UTC
1108
+ on Friday, delegate to a `finance` subagent, and DM the result to a configured
1109
+ Slack user. Note that DMing a user from a schedule needs the
1113
1110
  `im:write` scope on the Slack connector — add it during Phase 5 or the first run fails
1114
1111
  silently at the last step.
1115
1112
 
@@ -1147,8 +1144,8 @@ rate, delegation mix, p50/p95 duration.
1147
1144
  userId by default, so PostHog sees one anonymous actor named after the service. To
1148
1145
  attribute turns to the verified speaker, add a sibling hook that stamps the
1149
1146
  per-message-authenticated principal via evlog's `useLogger` — on `step.started`, not
1150
- `turn.started`, so evlog's turn state exists regardless of hook ordering (crib
1151
- `~/kyber/agent/hooks/attribution.ts`). Then a one-time $identify per person maps ids
1147
+ `turn.started`, so evlog's turn state exists regardless of hook ordering. Then a
1148
+ one-time $identify per person maps ids
1152
1149
  to names. Per-employee telemetry must be a deliberate, disclosed choice at a client.
1153
1150
 
1154
1151
  ### 4.7 Environment variables
@@ -1931,8 +1928,8 @@ conversation — it changes the shape of the deal.
1931
1928
 
1932
1929
  **Grok, on a SuperGrok or X Premium+ subscription.** Same arrangement, without
1933
1930
  the broker: xAI's Grok Build CLI does a device login and writes a credential
1934
- that is a valid bearer for `https://api.x.ai/v1`. Proven in production on Sid —
1935
- twelve evals, twenty-nine gates, green on the subscription.
1931
+ that is a valid bearer for `https://api.x.ai/v1`. Proven in production on a
1932
+ self-hosted agent — twelve evals, twenty-nine gates, green on the subscription.
1936
1933
 
1937
1934
  ```bash
1938
1935
  # on the host, as the unix user the agent runs as
@@ -1962,8 +1959,8 @@ Three things to know before you promise it to a client:
1962
1959
  host for a week, it is an open question, and it would present to the client as
1963
1960
  the agent breaking for no reason.
1964
1961
 
1965
- **The model will lie about which model it is.** Sid, running Grok, stated it was
1966
- "Claude Opus 4.6, Anthropic" and attributed it to an instruction that exists
1962
+ **The model will lie about which model it is.** A self-hosted agent running Grok
1963
+ stated it was "Claude Opus 4.6, Anthropic" and attributed it to an instruction that exists
1967
1964
  nowhere in its context. Verify from the host — the configured model id and the
1968
1965
  credential in use — never by asking the agent. Expect a client to ask it in a
1969
1966
  demo, and have the real answer ready.
@@ -2055,9 +2052,23 @@ tool. Reads went from unreliable to deterministic with about sixty lines.
2055
2052
  ### 11.6 The engineer layer, self-hosted
2056
2053
 
2057
2054
  `--engineer` scaffolds a **builder subagent** that owns the build capability, so
2058
- the root agent never gets a shell. It comes with the full production loop
2059
- workshop sandbox, Playwright, screenshots, visual verification, delivery — not a
2060
- reduced version of the Vercel one.
2055
+ the root agent gets **no shell on the host and no build loop**. It comes with the
2056
+ full production loop — workshop sandbox, Playwright, screenshots, visual
2057
+ verification, delivery — not a reduced version of the Vercel one.
2058
+
2059
+ > Say it that way rather than "the root never gets a shell". Every eve agent has
2060
+ > a sandbox with `bash`, `read_file`, `write_file`, `glob` and `grep`, and an
2061
+ > `--engineer` app builds two templates. The root's shell is its own container.
2062
+ > The line that matters is host access, and the shorter phrasing blurs exactly
2063
+ > the boundary people quote this passage to explain.
2064
+
2065
+ **Delivery belongs to the root as well.** The engineer layer mounts on `builder`,
2066
+ but the agent that talks to people is the one asked for files — and delivering
2067
+ reads one file and copies it, which is not the blast radius that justified
2068
+ keeping the build loop away from the root. `kyb init --engineer` now scaffolds
2069
+ `agent/tools/deliver.ts` for exactly this. An agent instructed to hand over files
2070
+ without that mount cannot tell it is missing: it writes the contents to a memory
2071
+ note and reports success.
2061
2072
 
2062
2073
  - **Subagents own their sandbox; they do NOT inherit the root's.** A builder
2063
2074
  without its own `sandbox/sandbox.ts` gets a bare template and every screenshot