@kybernesis/create 0.7.1 → 0.7.2

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.
@@ -1,2312 +0,0 @@
1
- # Kybernesis Forward-Deployment Playbook
2
-
3
- **Audience:** a Kybernesis forward-deployed engineer (FDE) running a client pilot
4
- engagement. Assumes you are a competent engineer who has **never seen this stack**.
5
- Everything you need is either in this document or behind a link in it.
6
-
7
- **Canonical location:** this Obsidian vault. An older, shorter copy may still exist at
8
- `~/Desktop/kybernesis-engagement-playbook.md` — that one is stale; this is the one.
9
-
10
- **Companion docs**
11
- - [[kybernesis-system-overview]] — what the whole system is and why. Read it once
12
- before your first engagement; you do not need it again during one.
13
- - [[kybernesis-architecture-and-studio-signin]] — the auth contract in full plus the
14
- Eve Studio sign-in brief.
15
-
16
- ---
17
-
18
- ## ⚡ The fast path: `kyb` (read this first, then use the rest as reference)
19
-
20
- Everything in §3–§4 of this playbook — scaffold, registry, all four packages,
21
- department subagents, memory mounts, eval wiring — is automated by
22
- **`@kybernesis/create`**, our CLI (`kyb`). The manual sections below remain the
23
- canonical reference for *what the CLI writes and why*, and for fixing anything by
24
- hand; but a normal engagement starts here.
25
-
26
- ### Starting from zero on the client's computer
27
-
28
- You will often be on a machine that has nothing on it. The complete bootstrap, in
29
- order — nothing else is required before the first command:
30
-
31
- ```bash
32
- # 1. Node.js 24 LTS (the only true prerequisite; installer from nodejs.org, or:)
33
- # macOS: brew install node@24 Windows: winget install OpenJS.NodeJS.LTS
34
- node -v # must be >= 24
35
-
36
- # 2. Scaffold the agent — no install step needed; npm fetches the CLI itself:
37
- npm create @kybernesis acme-atlas
38
- # …or, when the client wants an agent that BUILDS software (the engineer layer):
39
- npm create @kybernesis acme-atlas -- --engineer
40
- # Prompts: display name · department subagents · control-plane issuer.
41
- # (Non-interactive/CI: defaults apply when stdin is not a terminal.)
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
-
47
- # …or, when the client wants the desktop app (KYBER Studio) — see section 12:
48
- npm create @kybernesis acme-atlas -- --studio
49
-
50
- # 3. (Optional, for repeated use) put `kyb` on the PATH for the whole engagement:
51
- npm install -g @kybernesis/create
52
- ```
53
-
54
- That's it: `npm create @kybernesis <name>` downloads and runs the scaffolder in one
55
- step — the npm registry is the distribution channel, so a fresh laptop needs only
56
- Node. The Vercel CLI installs itself the first time an `npx vercel …` command runs
57
- (or `npm i -g vercel` if you prefer it resident).
58
-
59
- ### What `kyb init` leaves you with
60
-
61
- A complete agent directory — governed (`enterprise`), remembering (`arcana`),
62
- multiplayer Slack (`multiplayer`), self-testing (`evals`), with one generated
63
- subagent per department you named (routing description, instructions, its own
64
- Arcana connection, the three memory skills) — already typechecked and
65
- discovery-clean. It ends by printing **the human-steps checklist**, which is
66
- exactly §§2, 5, 6, and 7 of this playbook:
67
-
68
- 1. Arcana workspaces + scoped `kb_` keys → fill `.env.local` from `.env.example`
69
- 2. `vercel link` (the **client's** team) + envs (prod/preview Sensitive)
70
- 3. The Slack connector browser flow (§5.1)
71
- 4. Control-plane registration (▲ eve) + pilot-cohort grants (§6)
72
- 5. `npm run eval` → green → `npx eve deploy` → Slack smoke + the revoke demo (§7)
73
-
74
- ### The `kyb` command reference
75
-
76
- | Command | When | What it does |
77
- | --- | --- | --- |
78
- | `npm create @kybernesis <name>` | Day 1, once | Same as `kyb init <name>` without installing anything first |
79
- | `kyb init <name>` | Day 1, once | eve scaffold (pinned version) + registry + all four packages + generated departments + eval wiring + env template + checklist |
80
- | `kyb init <name> --engineer` | When the pilot includes building software | Everything above PLUS the engineer layer: workshop sandbox (Playwright baked into the template), the vision screenshot tool, build/ship skills, and the official limbs (agent-browser, github-tools, vercel connection) |
81
- | `kyb doctor` | **Constantly** — after every human step, before every deploy, whenever anything is weird | Live preflight: every Arcana key↔workspace pair validated against the API (with the specific fix per failure: wrong-key 403 vs missing-workspace 404), issuer JWKS reachability, `KYBERNESIS_AGENT`, Slack connector env, `eve info` discovery, port-2000 conflict. Exit 1 on failure → usable in CI |
82
- | `kyb upgrade` | Maintenance visits / after Kybernesis ships a package update | Compares installed `@kybernesis/*` against npm **and eve against the Kybernesis-certified version** (never blindly npm-latest — we certify eve releases in the platform repo first), installs what's behind, typechecks (+ `eve info` after a framework bump), then **runs the eval suite as the gate** — tells you to deploy only on green |
83
- | `kyb upgrade --skip-eval` | Never for production changes | Same, without the gate |
84
-
85
- **Habit to build:** `kyb doctor` is the debugging you would otherwise do by hand
86
- with curl — run it before asking why something doesn't work. Green doctor + green
87
- `npm run eval` = safe to deploy.
88
-
89
- ---
90
-
91
- ## 0. What you are actually doing
92
-
93
- You walk into a client company for roughly a week and leave behind:
94
-
95
- 1. **A company agent** — an [eve](https://eve.dev) agent, running on **the client's own
96
- Vercel team**, answering in **the client's own Slack workspace**.
97
- 2. **Department subagents** — finance, engineering, marketing, support, whatever their
98
- org chart says — each with its own long-term memory and its own tools.
99
- 3. **Governance** — the client's admin can invite an employee, grant them the agent,
100
- and revoke it, from a web UI, with revocation taking effect inside the token TTL.
101
- 4. **An eval suite** — the QA deliverable. It is how you and the client both know the
102
- agent still works after a change.
103
-
104
- The commercial shape of this matters to how you build it. Our pitch is **"your source →
105
- your runtime → your data → your token."** Everything above lives in *the client's*
106
- accounts. Kybernesis operates exactly one thing on the client's behalf — the **control
107
- plane** at `https://agent.kybernesis.ai` — and maintains the npm packages that make the
108
- rest possible. Do not create client resources inside Kybernesis accounts. If you catch
109
- yourself doing so, stop and fix it before it becomes the handover conversation.
110
-
111
- ### Placeholders used throughout
112
-
113
- | Placeholder | Meaning | Example |
114
- | --- | --- | --- |
115
- | `ACME` | the client company / control-plane org | Northwind |
116
- | `acme` | the client slug, lowercase | `northwind` |
117
- | `atlas` | the agent's name — whatever the client wants | `nora` |
118
- | `acme-atlas` | the repo / Vercel project name | `northwind-nora` |
119
- | `<dept>` | a department subagent | `finance` |
120
- | `slack/atlas` | the Vercel Connect connector UID | `slack/nora` |
121
-
122
- The agent's name is client-chosen and appears in three places that **must agree**: the
123
- `KYBERNESIS_AGENT` env var, the agent's registered name in the control plane, and the
124
- Slack app's display name (the last one is cosmetic but confusing if it differs).
125
-
126
- ### Timeline for a standard pilot
127
-
128
- | Day | Work |
129
- | --- | --- |
130
- | Day 0 (remote) | Phase 1 pre-engagement checklist |
131
- | Day 1 morning | Phase 2 discovery conversation |
132
- | Day 1 afternoon | Phase 3 environment setup, agent scaffolded and running locally |
133
- | Day 2–3 | Phase 4 build: instructions, subagents, memory, evals |
134
- | Day 3 | Phase 5 deploy + Phase 6 control-plane wiring |
135
- | Day 4 | Phase 7 pilot onboarding — the humans |
136
- | Day 5 | Phase 8 acceptance demo, Phase 9 handover |
137
-
138
- Pull days 2–3 longer if they have real proprietary systems to integrate; that work is a
139
- custom eve extension and is the billable heart of the engagement.
140
-
141
- ---
142
-
143
- ## 1. Pre-engagement checklist (do this remotely, ~1 hour, before you travel)
144
-
145
- Nothing here needs the client in the room, but several items need them to have clicked
146
- something. Chase them a week out — an unprovisioned Vercel team on Day 1 costs you a day.
147
-
148
- - [ ] **Client has a Vercel team.** Any plan tier works for a pilot; Pro if they want
149
- preview protection off the shelf. You need a member seat on it, or a named client
150
- engineer who can run `vercel` commands next to you.
151
- - [ ] **You have `node 24.x`, `npm`, and the Vercel CLI** on the machine you will build
152
- on. Verify:
153
- ```bash
154
- node -v # expect v24.x
155
- npm -v
156
- npm install -g vercel@latest
157
- vercel whoami
158
- ```
159
- - [ ] **You can log in to the control plane** at <https://agent.kybernesis.ai> and you
160
- have permission to create an org there. If ACME is self-hosting the control plane
161
- instead, confirm their issuer URL now — it changes `KYBERNESIS_ISSUER` everywhere.
162
- - [ ] **Create the control-plane org for ACME** and invite yourself as an admin of it.
163
- Do not build inside the Kybernesis org.
164
- - [ ] **Provision Arcana workspaces** — one per brain. The naming convention is
165
- `acme-company` for the shared root brain, `acme-<dept>` per subagent, and
166
- `acme-eval` for hermetic eval runs. Create each at
167
- <https://arcana.kybernesis.ai> and mint a **workspace-scoped `kb_` key** for each.
168
- - [ ] **Validate every key before you leave.** Keys are workspace-scoped: a key for one
169
- workspace returns `403` on any other, and finding that out during a live demo is
170
- avoidable. Read-only check, one per workspace:
171
- ```bash
172
- curl -s -o /dev/null -w "HTTP %{http_code}\n" \
173
- -H "Authorization: Bearer kb_REPLACE_ME" \
174
- -H "X-Kyberagent-Agent: acme-company" \
175
- "https://api.arcana.kybernesis.ai/brain/acme-company/timeline?limit=1"
176
- # expect HTTP 200
177
- ```
178
- - [ ] **Confirm who at ACME is the Slack workspace admin.** Creating the Slack connector
179
- requires someone who can approve a Slack app install. If that person is on holiday
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/`.
184
- - [ ] **Confirm package versions you will pin.** As of 2026-08-06:
185
- `@kybernesis/arcana@0.1.1`, `@kybernesis/enterprise@0.1.2`,
186
- `@kybernesis/multiplayer@0.1.0`, `@kybernesis/evals@0.2.1`,
187
- `@kybernesis/create@0.1.4`, `@kybernesis/engineer@0.2.0`, and
188
- `eve@0.30.8` (the Kybernesis-certified version). All public on npm;
189
- `kyb doctor` checks the wiring.
190
-
191
- ---
192
-
193
- ## 2. The discovery conversation (Day 1 morning, 90 minutes, with the client)
194
-
195
- This is a working session, not a requirements-gathering ritual. Your goal is to leave the
196
- room able to run Phase 3 without asking anyone anything. Bring a laptop and fill in the
197
- table below live.
198
-
199
- ### 2.1 The agent itself
200
-
201
- - **"What is it called?"** They pick. It appears in Slack, so it should be something
202
- people will actually type. Write it down as `atlas`.
203
- - **"What is its voice?"** Terse and factual, or warm? This becomes
204
- `agent/instructions/identity.md`. Ask for two or three examples of a good answer and a
205
- bad answer — those examples become evals.
206
- - **"What should it refuse to do?"** Anything they name here becomes an instruction and,
207
- if it matters, a hard guard in code rather than a prompt.
208
-
209
- ### 2.2 Departments
210
-
211
- - **"Which departments would ask this thing questions?"** Aim for three to five
212
- subagents in a pilot. More is a scoping conversation, not a build.
213
- - For each: **what does it know that nobody else does**, and **what systems does it need
214
- to read?** The first answer sizes its memory workspace; the second is either an
215
- off-the-shelf eve connection or a custom extension you will write.
216
- - Watch for a department that is really a *separate agent* (in our own setup, GTM was —
217
- it already had its own agent). Splitting is cheaper than untangling later.
218
-
219
- ### 2.3 Surfaces — don't assume Slack
220
-
221
- - **"Where do you actually talk?"** eve ships channels for Slack, iMessage
222
- (Photon), Telegram, Discord, Teams, SMS/phone (Twilio), GitHub, Linear, and
223
- a web chat — the agent can live on several at once (§4.3c has the table and
224
- install commands). Slack gets the richest treatment (our multiplayer group
225
- semantics); the rest are 1:1 surfaces today. Pick with the client, then ask
226
- the Slack questions below only if Slack made the list.
227
-
228
- ### 2.3a Slack specifics
229
-
230
- - **Workspace name and admin contact.**
231
- - **Which channels does the agent join?** For a pilot, one shared channel is usually
232
- right. Ask explicitly whether they want it in a channel with sensitive content.
233
- - **Do they want thread-following?** (People keep talking to the agent in a thread
234
- without re-mentioning it.) This is the default in our multiplayer package and it is
235
- what makes the agent feel like a colleague, but it needs extra Slack scopes —
236
- `message.channels` + `channels:history`, and the `groups` pair for private channels.
237
- Get scope approval in the same conversation as the app install.
238
- - **Do they want DMs?** DMs are a per-person surface with a separate memory workspace.
239
- Almost everyone says yes.
240
-
241
- ### 2.4 The pilot cohort
242
-
243
- - **"Name the five to ten people who will use this in week one."** Get names, emails, and
244
- Slack handles into a table. You will invite exactly these people in Phase 7.
245
- - **Who is the client-side admin?** They get the `manage` grant level and they are the
246
- person you train on invite/grant/revoke. Ideally two people, so a holiday does not
247
- block off-boarding.
248
- - **Who is the internal champion** who will answer "what do I even ask it?" for the
249
- cohort after you leave?
250
-
251
- ### 2.5 Data sensitivities — ask these out loud, take notes, put the answers in the doc
252
-
253
- - **What must never enter the agent's memory?** Credentials, obviously. But also: salary
254
- data, customer PII, unannounced M&A, health information. The memory instructions carry
255
- a no-secrets rule, but a rule in a prompt is not a control — if something must not be
256
- stored, do not connect the system it lives in.
257
- - **Which department brains must not read each other?** Each subagent gets its own
258
- Arcana workspace with its own scoped key, so this is free — but you need to know the
259
- boundaries to name the workspaces correctly.
260
- - **Is the shared channel brain readable by everyone in the workspace?** Today, yes:
261
- anyone in the Slack workspace who can see the bot can talk to it, and public-channel
262
- memory is shared. Say this plainly. If they need per-person gating on the Slack door,
263
- that is a known gap (§11) and you should scope the pilot around it.
264
- - **Where does their data physically live and does that matter?** Agent runtime and
265
- session data: their Vercel account. Long-term memory: Arcana, our SaaS. Identity and
266
- grants: the control plane. Model traffic: the AI Gateway provider their eve project is
267
- configured for. If any of those three placements is a problem, surface it now — the
268
- control plane is self-hostable and that is a different (and larger) engagement.
269
-
270
- ### 2.6 Leave the room with this table filled in
271
-
272
- | Field | Value |
273
- | --- | --- |
274
- | Agent name (`atlas`) | |
275
- | Repo / Vercel project name | |
276
- | Vercel team | |
277
- | Slack workspace + admin | |
278
- | Slack channels | |
279
- | Thread-following? DMs? | |
280
- | Departments (`<dept>` list) | |
281
- | Arcana workspaces + keys | |
282
- | Control-plane org | |
283
- | Pilot cohort (name / email / Slack id) | |
284
- | Client-side admins (`manage` grant) | |
285
- | Never-store list | |
286
- | Custom systems to integrate | |
287
-
288
- ---
289
-
290
- ## 3. Environment setup (Day 1 afternoon, ~45 minutes)
291
-
292
- > ⚡ **Automated by `kyb init`** (see the fast path at the top). Read this section to
293
- > understand what the CLI wrote, or to do it by hand.
294
-
295
- Everything below runs from a working directory of your choosing. Use an absolute path
296
- you will remember; this playbook writes `~/work/acme-atlas`.
297
-
298
- ### 3.1 Scaffold the agent
299
-
300
- ```bash
301
- mkdir -p ~/work && cd ~/work
302
- npx eve@latest init acme-atlas
303
- cd ~/work/acme-atlas
304
- ```
305
-
306
- `eve init` creates the project, installs dependencies, and initializes git. You now have
307
- an `agent/` directory. Confirm eve sees it:
308
-
309
- ```bash
310
- npx eve info
311
- ```
312
-
313
- `eve info` prints the resolved application — every tool, skill, subagent, schedule,
314
- channel, and route eve discovered, plus discovery diagnostics. **Run this whenever
315
- something behaves unexpectedly.** It is much faster than booting the dev server and it
316
- answers the single most common question ("did eve even find my file?").
317
-
318
- ### 3.2 Link it to the client's Vercel team
319
-
320
- ```bash
321
- cd ~/work/acme-atlas
322
- npx eve link
323
- ```
324
-
325
- Pick **the client's team**, then create a project named `acme-atlas`. This also pulls an
326
- AI Gateway credential (`VERCEL_OIDC_TOKEN` or `AI_GATEWAY_API_KEY`) into `.env.local`, so
327
- the agent can call a model.
328
-
329
- In CI or any non-interactive context, `eve link` will not work — use
330
- `vercel link --project acme-atlas --yes --non-interactive` instead. If your active Vercel
331
- scope is not the client's team, every subsequent `vercel` call needs
332
- `--scope <client-team-slug>`.
333
-
334
- ### 3.3 Register the Kybernesis registry
335
-
336
- ```bash
337
- cd ~/work/acme-atlas
338
- npx eve registry add @kybernesis=https://registry.kybernesis.ai/r/{name}.json
339
- ```
340
-
341
- Type `{name}` literally — it is a placeholder eve substitutes per item. This writes the
342
- mapping into `package.json#registries`. Confirm:
343
-
344
- ```bash
345
- npx eve registry list --registry @kybernesis
346
- npx eve registry view @kybernesis/arcana
347
- ```
348
-
349
- ### 3.4 Pin your versions
350
-
351
- Before you install anything else, decide and record the versions this engagement pins.
352
- Put them in the repo README. Pin `eve@0.30.8` — the **Kybernesis-certified** version
353
- (certification run 2026-08-06: full suite green, zero code changes). Never pin blind
354
- npm-latest; `kyb upgrade` carries a client to the certified pin behind their own eval
355
- gate, and that upgrade is a **deliberate, eval-gated step**, never something that
356
- happens by accident mid-pilot.
357
-
358
- ---
359
-
360
- ## 4. Build (Days 2–3)
361
-
362
- > ⚡ **The package installs and department scaffolds here are automated by `kyb init`.**
363
- > What remains genuinely manual in this section: tailoring instructions to the client's
364
- > voice, client-specific tools/connections, and any custom extension work.
365
-
366
- Build in this order — model, channel, memory, instructions, subagents, schedules,
367
- evals. Each step is testable on its own, and the order avoids the one real trap
368
- (installing the governance package *after* hand-authoring the file it overwrites).
369
-
370
- ### 4.0 Do the whole phase WITH Claude Code — this is the method, not a tip
371
-
372
- Open Claude Code in the agent repo and keep it open for all of Phase 4. The
373
- scaffold already carries the **FDE skill suite** in `.claude/skills/` (seeded
374
- by `kyb init`; refresh with `kyb skills`) — Claude loads the engagement flow,
375
- eve recipes, package gotchas, control-plane wiring, and eval discipline on
376
- demand, so it knows what Kybernesis is doing before you say a word. The
377
- scaffold's `AGENTS.md` additionally points it at the installed eve docs
378
- (`node_modules/eve/docs/` — the source of truth for the pinned version), so it
379
- authors against the real framework instead of guessing. The working rhythm:
380
-
381
- 1. **Tell it what you're building, paste the discovery table (§2.6).** "This
382
- agent is called Atlas, lives on Slack + Telegram, needs read access to
383
- their Postgres and their internal wiki, three departments."
384
- 2. **Make it read before it writes.** For anything eve-specific: "read
385
- `node_modules/eve/docs/channels/telegram.mdx`, then wire the channel."
386
- Every channel, connection type, and config surface has a doc page; the
387
- pattern `read the doc → write the file → npx eve info → test in eve dev`
388
- is the whole game.
389
- 3. **You review diffs and run the credential steps** (anything with a browser
390
- login or a client secret is yours); Claude writes files, runs `eve
391
- registry` searches, and iterates on eval failures.
392
- 4. **Never accept a claim without the check**: `npm run typecheck`, `npx eve
393
- info` (0 diagnostics), a turn in `eve dev` (§4.4b), evals green (§4.8).
394
-
395
- The eve CLI you'll both be living in:
396
-
397
- | Command | What it does |
398
- | --- | --- |
399
- | `npx eve dev` | boots the local runtime + opens the chat TUI (test turns here) |
400
- | `npx eve info` | compile + discovery truth: agents, tools, skills, diagnostics |
401
- | `npx eve registry list` / `search <term>` / `view <item>` | discover integrations before writing one |
402
- | `npx eve add <item>` | install a registry item (files + deps; may offer an interactive setup flow — rerun later with `--skip-install`) |
403
- | `npm run eval` | the hermetic suite (§4.8) — kill the dev server first |
404
- | `npx eve build` | production build locally (what the Vercel deploy runs) |
405
- | `vercel deploy --prod --yes` | ship it (§5) |
406
-
407
- ### 4.0b Pick and pin the model
408
-
409
- The model is agent config, not an env var: `agent/agent.ts` calls
410
- `defineAgent`. With **no** `agent.ts`, eve defaults to
411
- `anthropic/claude-sonnet-5`; the moment the file exists, `model` is required —
412
- so pin it deliberately and record it in the repo README with the §3.4 pins:
413
-
414
- ```ts
415
- // agent/agent.ts
416
- import { defineAgent } from "eve";
417
-
418
- export default defineAgent({
419
- model: "anthropic/claude-opus-4.8",
420
- });
421
- ```
422
-
423
- What to know when choosing:
424
-
425
- - The string form is a **Vercel AI Gateway id** (`provider/model` with a dot
426
- version — `anthropic/claude-opus-4.8`) — routed, no provider key handling.
427
- This is the default choice for client deploys.
428
- - Direct provider wiring exists when a client requires it (their own
429
- Anthropic account, data-processing terms): `npm install @ai-sdk/anthropic`,
430
- then `model: anthropic("claude-opus-4-8")` (note the provider's hyphenated
431
- id format) with the provider API key in env.
432
- - **Dynamic selection** is supported (`defineDynamic` with a `fallback` and a
433
- `session.started` handler — e.g. a bigger model for an enterprise-plan
434
- principal). Prefer `session.started` over per-turn switching: prompt caches
435
- are per model, and every switch re-ingests the conversation at uncached
436
- prices. Resolver failures degrade to the fallback, never fail the turn.
437
- - Kyber runs `anthropic/claude-opus-4.8` via the gateway; our eval judges are
438
- configured separately in `evals/evals.config.ts` and must **never** be the
439
- model under test.
440
-
441
- ### 4.1 Install `@kybernesis/enterprise` (governance) — do this FIRST
442
-
443
- ```bash
444
- cd ~/work/acme-atlas
445
- npx eve add @kybernesis/enterprise
446
- ```
447
-
448
- This installs the npm package and **writes `agent/channels/eve.ts`, replacing the
449
- scaffold's version.** That is why it goes first: if you hand-edit `agent/channels/eve.ts`
450
- and then run this, your edits are gone.
451
-
452
- The file it writes:
453
-
454
- ```ts
455
- // agent/channels/eve.ts
456
- import { eveChannel } from "eve/channels/eve";
457
- import { kybernesisAuth } from "@kybernesis/enterprise";
458
-
459
- export default eveChannel({
460
- auth: [
461
- kybernesisAuth({
462
- issuer: process.env.KYBERNESIS_ISSUER!,
463
- agent: process.env.KYBERNESIS_AGENT!,
464
- }),
465
- ],
466
- });
467
- ```
468
-
469
- What this does: every request to the agent's HTTP surface (`/eve/v1/*`) must carry a
470
- Kybernesis identity token **and** a policy bundle. The package verifies both **offline**
471
- against the control plane's JWKS, cross-checks that the bundle belongs to the token's
472
- user and org, and then requires the bundle's `agentGrants` to contain *this agent's*
473
- registered name.
474
-
475
- - no credentials, or invalid, or expired → **401**
476
- - valid credentials but no grant for this agent → **403 `agent_not_granted`**
477
-
478
- Note what is *not* in that auth array: the eve scaffold ships `localDev()` and
479
- `placeholderAuth()`. The governed file drops both. That is intentional — it fails closed
480
- — but it means the `eve dev` TUI cannot reach the HTTP door of a governed agent without
481
- credentials. You will still drive the agent locally through the TUI's own session; you
482
- just cannot `curl` it without a real token.
483
-
484
- ### 4.2 Install `@kybernesis/arcana` (memory)
485
-
486
- ```bash
487
- cd ~/work/acme-atlas
488
- npx eve add @kybernesis/arcana
489
- ```
490
-
491
- This installs the package and writes `agent/extensions/arcana.ts`. **The filename is the
492
- mount namespace** — leave it as `arcana.ts` unless you have a reason not to, because
493
- tool names derive from it.
494
-
495
- Edit it to use the client's workspace naming and, if they want the DM/channel memory
496
- split, a `resolveWorkspace` override:
497
-
498
- ```ts
499
- // agent/extensions/arcana.ts
500
- import arcana from "@kybernesis/arcana";
501
-
502
- export default arcana({
503
- apiKey: process.env.ARCANA_API_KEY!,
504
- workspace: process.env.ARCANA_COMPANY_WORKSPACE ?? "acme-company",
505
-
506
- // Public channels use the shared company brain; DMs use a personal workspace.
507
- // `surface` is a VERIFIED principal attribute stamped by the multiplayer Slack
508
- // channel — never anything the model can influence.
509
- resolveWorkspace: (ctx) =>
510
- ctx.session.auth.current?.attributes.surface === "dm"
511
- ? (process.env.ARCANA_DM_WORKSPACE ?? "acme-dm")
512
- : undefined,
513
- });
514
- ```
515
-
516
- What you get from the mount: an MCP connection to `https://mcp.arcana.kybernesis.ai/mcp`
517
- (with the required `X-Kyberagent-Agent: <workspace>` header), three skills
518
- (`recall`, `remember`, `brain-note`), and always-on memory instructions — recall-first
519
- lookups, never claim ignorance without searching, proactive fact storage, no secrets in
520
- memory. Those instruction rules are not decoration; each of them exists because an eval
521
- caught the agent doing the opposite.
522
-
523
- ### 4.3 Install `@kybernesis/multiplayer` (Slack)
524
-
525
- ```bash
526
- cd ~/work/acme-atlas
527
- npx eve add @kybernesis/multiplayer
528
- ```
529
-
530
- This writes `agent/channels/slack.ts` and `agent/instructions/multiplayer.md`, and
531
- declares the `SLACK_CONNECTOR_UID` env var.
532
-
533
- > **Status as of 2026-08-05:** the registry item is **live** (`eve add` resolves and
534
- > writes both files), and the package is dogfooded in `~/kyber` — but
535
- > `@kybernesis/multiplayer@0.1.0` is **not yet published to npm**, so the dependency
536
- > install step will fail until it is. Check first:
537
- > ```bash
538
- > npm view @kybernesis/multiplayer version # E404 means the publish hasn't landed
539
- > ```
540
- > If it 404s, install from the workspace checkout at `~/kyber/packages/multiplayer` (or
541
- > vendor the two files by hand — they are shown below and in
542
- > `agent/instructions/multiplayer.md`) and revisit once the publish lands.
543
-
544
- The whole Slack integration is one file:
545
-
546
- ```ts
547
- // agent/channels/slack.ts
548
- import { connectSlackCredentials } from "@vercel/connect/eve";
549
- import { multiplayerSlackChannel } from "@kybernesis/multiplayer/slack";
550
-
551
- export default multiplayerSlackChannel({
552
- credentials: connectSlackCredentials(process.env.SLACK_CONNECTOR_UID!),
553
- });
554
- ```
555
-
556
- Defaults give you the full multiplayer behavior. What that means concretely:
557
-
558
- - **A thread is one shared session with many verified speakers.** Every message
559
- re-authenticates: `auth.current` is *that message's* verified sender; `auth.initiator`
560
- stays pinned to whoever started the thread.
561
- - **Attributed thread context.** Messages between agent replies are injected with stable
562
- per-speaker Slack ids, so the model reads a real multi-party transcript.
563
- - **No re-mentions needed.** Once the agent is active in a thread, anyone can keep
564
- talking to it.
565
- - **Dual surface.** Channel sessions carry a verified `surface: "channel"` principal
566
- attribute; DMs carry `surface: "dm"`. Gate tools on it.
567
- - **`/new` in a DM** retires the session and starts fresh.
568
-
569
- Options, if the client asked for something different:
570
-
571
- | Option | Default | Change it when |
572
- | --- | --- | --- |
573
- | `continuation` | `"subscribed-threads"` | Set `"mention-only"` if they did not approve the history scopes, or find thread-following too chatty |
574
- | `dmReset` | `"/new"` | They want a different command, or `false` to disable |
575
- | `threadContext` | `"incremental"` | `"full"` for whole-thread-every-mention; `false` for triggering message only (no history scope needed) |
576
- | `events` | — | Passed through to the underlying eve Slack channel for custom handlers |
577
-
578
- Use the surface helpers to gate anything personal. This is the pattern for every
579
- "only in a DM" capability:
580
-
581
- ```ts
582
- import { defineTool } from "eve/tools";
583
- import { requireDm } from "@kybernesis/multiplayer";
584
- import { z } from "zod";
585
-
586
- export default defineTool({
587
- description: "Read the caller's personal task list.",
588
- inputSchema: z.object({}),
589
- async execute(_input, ctx) {
590
- requireDm(ctx.session); // throws a model-visible refusal on the channel surface
591
- // ...
592
- },
593
- });
594
- ```
595
-
596
- `requireDm` **fails closed** and the thrown message is visible to the model, so the agent
597
- relays "DM me for that" naturally. `sessionSurface(ctx.session)` returns
598
- `"channel" | "dm" | null` if you want to branch rather than refuse; the `eve dev` local
599
- principal counts as `"dm"` so you can exercise personal capabilities locally.
600
-
601
- **Enforce surface rules in code, never in the prompt.** A prompt is a suggestion; a
602
- throwing guard is a control.
603
-
604
- ### 4.3b Install `@kybernesis/engineer` (optional — when the agent should BUILD software)
605
-
606
- ```bash
607
- cd ~/work/acme-atlas
608
- npx eve add @kybernesis/engineer
609
- ```
610
-
611
- Writes TWO files: `agent/extensions/engineer.ts` (the mount: screenshot tool +
612
- six build/ship skills + engineering-conduct instructions) and
613
- `agent/sandbox/sandbox.ts` (the **workshop**: Playwright + Chromium baked into
614
- the sandbox template, domain allowlist on deployed sessions). Pair it with the
615
- official limbs if not using `kyb init --engineer`:
616
- `npx eve add extension/agent-browser extension/github-tools connection/vercel`
617
- (run individually).
618
-
619
- **Wire the Vercel connection** (preview deploys + a clickable link for every
620
- build — proven live 2026-08-06). After `vercel link` in the agent dir:
621
-
622
- ```bash
623
- vercel connect create mcp.vercel.com --name vercel
624
- vercel connect attach mcp.vercel.com/vercel --yes
625
- ```
626
-
627
- Then make `agent/connections/vercel.ts` use the **UID, not the short name**:
628
- `connect("mcp.vercel.com/vercel")`. The auth is user-scoped: the FIRST Vercel
629
- tool call posts a "Connect with…" OAuth link in the Slack thread, the turn
630
- parks, and it resumes after the click. Grant **All projects** (the agent
631
- creates new projects on deploy; a fixed project list can't cover them). To
632
- **narrow the grant later** there is no dashboard or CLI grant editor, and
633
- `revoke-tokens` does not kill provider-side tokens — the working recipe is
634
- `vercel connect detach` + `remove` + `create` (same UID) + `attach`: the
635
- authorization dies with the connector. Then trigger the re-auth from a
636
- **fresh session/thread** (existing sessions hold stale auth state and error
637
- without re-prompting) and select only the project(s) that now exist.
638
-
639
- > **Scoping — the client story:** the natural boundary is the CLIENT'S
640
- > VERCEL TEAM. The agent, connector, and OAuth grant all live in the
641
- > client's team, so "All projects" means all of *that client's* projects —
642
- > usually exactly right, and it also covers the new projects the agent
643
- > creates on deploy. What we verified live: an All-projects grant really
644
- > does reach every team project (the agent will happily enumerate them),
645
- > and there is no post-hoc grant editor — changing scope means the
646
- > connector reset above. If a client wants a boundary *tighter than their
647
- > team* (e.g. agent may touch only its own projects), do not promise the
648
- > consent picker — enforce it on our side with a policy wrapper on the
649
- > connection (allowlist of project names/IDs checked against tool
650
- > arguments). That wrapper is the auditable answer in a security review. The deploy tool
651
- takes an **inline file tree**, so the agent ships straight from its sandbox —
652
- no git remote and no token ever inside the VM. Previews sit behind Vercel
653
- Authentication by default; the agent posts a `?_vercel_share=` bypass link
654
- (~24 h) and must ASK before changing protection settings.
655
-
656
- **Wire file delivery** (the `deliver` tool — documents, exports, artifacts
657
- the client can open in a browser or download):
658
-
659
- ```bash
660
- vercel blob create-store acme-atlas-deliverables --access public --yes
661
- ```
662
-
663
- One command: creates the store, links the project, injects
664
- `BLOB_READ_WRITE_TOKEN`. Without it the tool fails with instructions rather
665
- than silently degrading to a memory note.
666
-
667
- Know before demoing:
668
-
669
- - **The sandbox template bakes at DEPLOY time** (Playwright + Chromium) — a
670
- broken bootstrap fails the Vercel build loudly instead of surfacing mid-demo.
671
- Deploys that rebuild the template take minutes; unchanged templates are
672
- cached and fast.
673
- - **The allowlist is the client's security posture** — deployed sessions can
674
- only reach the domains listed in `agent/sandbox/sandbox.ts`. A blocked host
675
- fails loudly; extend the list deliberately, and treat every addition as a
676
- security decision to note in the handover. The template ships the proven v5
677
- egress set: Ubuntu mirrors (the base image is Ubuntu), https-rewritten apt,
678
- and `storage.googleapis.com` (Chromium's CDN).
679
- - **Production promotion is human-approved by design** (the ship skill).
680
- Never soften this for demo convenience — the approval moment IS the demo.
681
- - The agent's projects live in `/workspace` and persist across sessions and
682
- redeploys — a build started Tuesday continues Thursday.
683
- - **Coach the agent in prose, not shell.** Slack messages containing raw
684
- shell syntax can be eaten by Cloudflare's WAF before they reach the agent.
685
-
686
- ### 4.3c Channels — put the agent on every surface the client uses
687
-
688
- Everything above wires *our* layer. This step and the two after it are where
689
- the agent becomes **the client's agent**. All of it assumes zero prior eve
690
- knowledge; every claim here is expandable by reading the named doc page in
691
- `node_modules/eve/docs/` — which is exactly what you tell Claude Code to do.
692
-
693
- A channel is one file under `agent/channels/`; the filename is the channel id.
694
- eve normalizes every surface into the same runtime — instructions, tools, and
695
- memory don't change per channel, so adding a second surface never means
696
- re-teaching the agent. What ships:
697
-
698
- | The client wants… | Channel | Get it |
699
- | --- | --- | --- |
700
- | Slack (mentions, DMs, threads, buttons) | Slack | our `@kybernesis/multiplayer` (§4.3) — group semantics, dual surface |
701
- | **iMessage** | Photon | `eve add channel/photon-imessage` |
702
- | Telegram bot | Telegram | `eve add channel/telegram` (worked example below) |
703
- | Discord (slash commands, components) | Discord | `eve add channel/discord` |
704
- | Microsoft Teams (+ Adaptive Cards) | Teams | `eve add channel/teams` |
705
- | SMS / phone (speech-transcribed) | Twilio | `eve add channel/twilio` |
706
- | GitHub @mentions, PR review | GitHub | `eve add channel/github` |
707
- | Linear issue delegation | Linear | `eve add channel/linear-agent` |
708
- | Web app / browser chat | eve HTTP + `useEveAgent` | built-in (route auth via enterprise) |
709
-
710
- Every channel's doc page (`node_modules/eve/docs/channels/<name>.mdx`) carries
711
- its **complete** setup: the file to write, the env vars, the webhook/app
712
- registration on the provider side, HITL behavior, and auth verification. The
713
- flow is always the same three steps — worked example, Telegram:
714
-
715
- **1. The channel file** (`eve add channel/telegram` writes it, or author it):
716
-
717
- ```ts
718
- // agent/channels/telegram.ts
719
- import { telegramChannel } from "eve/channels/telegram";
720
-
721
- export default telegramChannel({
722
- botUsername: "acme_atlas_bot",
723
- });
724
- ```
725
-
726
- **2. The provider-side credentials** (this part is always yours, not Claude's):
727
- create the bot with @BotFather, then set both envs (local `.env.local` AND
728
- Vercel, Sensitive):
729
-
730
- ```bash
731
- TELEGRAM_BOT_TOKEN=123456:... # from BotFather
732
- TELEGRAM_WEBHOOK_SECRET_TOKEN=... # any secret you generate
733
- ```
734
-
735
- **3. Point the provider at the deployed agent.** Each channel mounts a route
736
- (`POST /eve/v1/telegram` here); Telegram needs the webhook registered by hand:
737
-
738
- ```bash
739
- curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
740
- -H "Content-Type: application/json" \
741
- -d '{"url":"https://<deployed-app>/eve/v1/telegram",
742
- "secret_token":"'"$TELEGRAM_WEBHOOK_SECRET_TOKEN"'",
743
- "allowed_updates":["message","callback_query"]}'
744
- ```
745
-
746
- That pattern — file, credentials, point-the-provider-at-the-route — is every
747
- channel. Slack's version is the connector create/attach in §4.3; Discord/Teams
748
- have their own app-portal steps in their doc pages. Good group behavior to
749
- know: Telegram groups only wake the bot on a command, an @mention, or a reply
750
- to the bot; HITL renders as inline-keyboard buttons; replies over 4096 chars
751
- split automatically.
752
-
753
- Two honest caveats to state to the client: **multiplayer's group semantics
754
- (shared threads, speaker attribution, no-re-mention) are Slack-only today** —
755
- other surfaces are stock channels, excellent for 1:1; and each surface has its
756
- own provider terms and data flow — sensitive-data review (§2.5) is per
757
- channel, not per agent.
758
-
759
- ### 4.3d Connections — wire the client's actual systems
760
-
761
- A connection turns an external system into tools the model can call. Rule one:
762
- **search before you write** — most requests already exist as registry items:
763
-
764
- ```bash
765
- npx eve registry list # official catalog + our @kybernesis source
766
- npx eve registry search postgres # capability search (also searches skills.sh)
767
- npx eve registry view connection/linear # ALWAYS inspect before installing
768
- npx eve add linear # multi-part items let you pick components
769
- ```
770
-
771
- `eve add` installs dependencies and writes the connection file; official items
772
- may then offer an **interactive setup flow** (accounts, OAuth clients) — run
773
- it, or resume a skipped one later with `eve add <item> --skip-install`.
774
-
775
- When the registry has nothing (a client's internal service), you write one
776
- file. Two shapes, pick by what the service exposes:
777
-
778
- - **MCP server** → `defineMcpClientConnection` (the server publishes tools).
779
- - **OpenAPI 3.x document** → `defineOpenAPIConnection` (each operation
780
- becomes a tool; filter the operations you actually want).
781
-
782
- And four auth modes, pick by who the agent acts as:
783
-
784
- | Mode | When | Shape |
785
- | --- | --- | --- |
786
- | **Static token** | org service accounts, internal systems — the pilot default | `auth: { getToken: async () => ({ token: process.env.X_TOKEN! }) }` |
787
- | **Vercel Connect, user-scoped** | the agent acts as *the person* (their Linear, their calendar) | `auth: connect("<connector-uid>")` — first use posts an OAuth link in-thread, turn parks + resumes (§4.3b showed this live) |
788
- | **Vercel Connect, app-scoped** | the agent acts as *itself* against an OAuth service | `connect({ connector: "<uid>", principalType: "app" })` — non-interactive |
789
- | **None** | public/read-only APIs | omit `auth` |
790
-
791
- The full static-token example (an internal MCP service):
792
-
793
- ```ts
794
- // agent/connections/wiki.ts
795
- import { defineMcpClientConnection } from "eve/connections";
796
-
797
- export default defineMcpClientConnection({
798
- url: "https://wiki.internal.acme.com/mcp",
799
- description:
800
- "Acme's internal wiki: search pages, read content, list owners.",
801
- auth: {
802
- getToken: async () => {
803
- const token = process.env.ACME_WIKI_TOKEN;
804
- if (!token) throw new Error("ACME_WIKI_TOKEN is not set.");
805
- return { token };
806
- },
807
- },
808
- });
809
- ```
810
-
811
- Decide three things per connection, at install time, and write them down:
812
-
813
- 1. **Auth scope** (table above). Subagents have **no user principal** — inside
814
- them only static-token or app-scoped Connect works.
815
- 2. **Surface gating.** Personal tools get `requireDm`-style fail-closed checks
816
- (§4.4's surface rules only exist if connections enforce them).
817
- 3. **Approval.** Destructive or spend-shaped tools get an `approval` gate —
818
- per-connection HITL, rendered as buttons on Slack/Telegram.
819
-
820
- The `description` matters more than it looks: it's how the model decides to
821
- reach for the connection at all. Write it like a capability, name the systems.
822
-
823
- ### 4.3e Skills — teach the client's procedures (and mine skills.sh)
824
-
825
- A skill is a markdown procedure the model loads **on demand** (eve advertises
826
- each skill's description; the model calls `load_skill` when a turn matches).
827
- Anything the client's team does "the same way every time" — their release
828
- checklist, escalation path, report format — is a skill, not an always-on
829
- instruction. Keep always-on for identity and rules; skills for procedures.
830
-
831
- Three authoring forms, in order of reach:
832
-
833
- ```md
834
- <!-- 1. Flat file: agent/skills/escalation.md — smallest possible skill.
835
- First body line doubles as the routing description. -->
836
- Use when an incident needs escalation: who to page, in what order, and what
837
- the first Slack message must contain.
838
- ...procedure...
839
- ```
840
-
841
- ```md
842
- <!-- 2. Packaged: agent/skills/weekly-report/SKILL.md + references/ dir.
843
- description frontmatter is REQUIRED here. -->
844
- ---
845
- description: Use when someone asks for the weekly ops report.
846
- ---
847
- Pull the numbers in this order... (see references/template.md)
848
- ```
849
-
850
- TypeScript (`defineSkill` from `eve/skills`) is the third form, only for
851
- generated content or typed sibling files — start with markdown.
852
-
853
- The rules that make skills actually fire:
854
-
855
- - The `description` is a **routing hint, not a label** — write it as the
856
- triggering task ("Use when…"), and test it in `eve dev` by asking the
857
- question *without naming the skill* (§4.4b).
858
- - Skills are **scoped per agent** — subagents can't see the root's skills;
859
- copy what each needs (or use the subagent-local extension mounts that ship
860
- them, §4.5).
861
- - Loading a skill adds instructions, never tools — typed behavior is a tool.
862
-
863
- **skills.sh** — the community skills marketplace — is built into eve's search
864
- as the `@skills` source:
865
-
866
- ```bash
867
- npx eve registry search "react best practices" # hits skills.sh too
868
- npx eve add @skills/vercel-labs/agent-skills/vercel-react-best-practices
869
- ```
870
-
871
- Community skills are third-party project files: **read the source and the
872
- diff before running the agent**, same as any dependency. For client work,
873
- prefer authoring the client's own procedures; pull from skills.sh for generic
874
- craft (framework best practices, review checklists) after review.
875
-
876
- ### 4.3f Install `@kybernesis/dispatch` (optional — when the client runs MORE THAN ONE agent)
877
-
878
- When the client has (or grows into) a second deployed agent — an ops agent
879
- next to the company assistant, a specialist per business unit — they will ask
880
- for the agents to talk to each other. Dispatch is the governed way: one
881
- declared **edge** per direction, human identity carried across the hop.
882
-
883
- The concept in one breath: the caller mounts the peer as a remote subagent
884
- (`remotePeer` under `agent/subagents/` — eve's `defineRemoteAgent` underneath,
885
- durable park→callback dispatch, so a reply comes back on the SAME edge); the
886
- receiver authors `agent/channels/eve.ts` with `dispatchChannel({ trustedPeers })`,
887
- which feeds one peer list into BOTH the OIDC subjects allowlist and
888
- `trustedForwarders`. Forwarding is on by default: the receiving agent runs as
889
- the human who asked, so Arcana scoping, per-user connections, and PostHog
890
- attribution compose across the hop unchanged (`eve:forwarded-by` records the
891
- edge for audit).
892
-
893
- **Don't hand-wire it — use the `connect-agents` Claude Code skill** (in the
894
- seeded `.claude/skills/`): tell Claude "connect <agent A> to <agent B>" and it
895
- reads both repos, writes the edge with a routing description derived from the
896
- callee's REAL capabilities, sets the URL env var, and walks the deploy+verify
897
- steps. `kyb doctor` then checks the edges (env var set, no `() => true`
898
- trust, forwardPrincipal present).
899
-
900
- Client-conversation rules of thumb:
901
-
902
- - One edge = ask-and-answer in one direction. Mirror-image edge only if the
903
- other agent should also INITIATE. Quote them separately.
904
- - **Both ends must run compatible eve versions** — an old receiver silently
905
- drops principal forwarding and runs the session as the calling app's
906
- service identity. Upgrade edges as a unit (`kyb upgrade` both repos).
907
- - Peers are pinned to production deployments of named Vercel projects.
908
- Previews never get trust implicitly. The client's Vercel team is still the
909
- outer boundary, same as §4.3b.
910
- - Cross-ORG edges (client agent ↔ another company's agent) are a different
911
- product conversation — purpose-scoped grants, §2.5 disclosures. Don't wire
912
- one as if it were internal.
913
-
914
- **Governed mode (dispatch ≥0.2.1 + enterprise ≥0.2.0 + the client's control
915
- plane) — the preferred form.** Edges become GRANTS in the admin instead of
916
- code: register both agents (/agents, OPEN production alias, health 200), grant
917
- the edge on the CALLEE's panel (caller + purpose + optional expiry), mint each
918
- agent's credential (shown once) into KYBERNESIS_AGENT_CREDENTIAL on its
919
- deployment. Code shrinks to remotePeer({ callee: "<EXACT registered name —
920
- case-sensitive>", governed: { issuer }, envVar, fallbackUrl }) and
921
- dispatchChannel({ governed: { issuer, agent } }). Outbound auth is a 300 s A2A
922
- token minted per edge; the callee URL comes from the registry (discovery), env
923
- var still wins. THE DEMO: revoke the edge in the admin → the caller is refused
924
- (edge_not_granted) within 5 minutes, no redeploy; re-grant → restored. Run it
925
- for the client — it's the whole governance story in one minute. Full lifecycle
926
- proven live 2026-08-07 (kyber ↔ eve-gtm). Budget note: the deployed agent and
927
- local eval runs share the project's AI Gateway budget — size it for both.
928
-
929
- ### 4.4 Author the agent's identity and instructions
930
-
931
- How instructions work in eve (30 seconds of mechanics): a flat
932
- `agent/instructions.md` is the whole prompt; an `agent/instructions/`
933
- **directory** combines entries alphabetically (root file first) and accepts
934
- both `.md` and `.ts` files — a `.ts` entry wraps `defineInstructions` (built
935
- once at compile time) or `defineDynamic` (resolved per session, like the
936
- surface.ts example below). Keep always-on instructions to identity, tone, and
937
- standing rules; procedures belong in skills (§4.3e) — the model loads those on
938
- demand instead of paying for them every turn.
939
-
940
- Crib the structure from `~/kyber/agent/instructions/identity.md`, which has
941
- three sections worth copying:
942
-
943
- 1. **Identity** — who the agent is, and *how to write for Slack*: short paragraphs,
944
- bullets, no headings unless the answer is genuinely long. Slack is a chat surface, not
945
- a document editor. Agents default to essay mode; say otherwise explicitly.
946
- 2. **Delegation** — name each subagent and say when to route to it. Critically:
947
- *"a specialist sees none of this conversation, so pack everything it needs into the
948
- message."* Subagents do not inherit context. Without this line the agent delegates
949
- half-briefed and the specialist answers badly.
950
- 3. **Surfaces** — what public channels mean versus DMs, and what to say when someone
951
- asks for something personal in a channel.
952
-
953
- For per-session context, `defineDynamic` on `session.started` lets you inject
954
- surface-specific instructions. `~/kyber/agent/instructions/surface.ts` is a working
955
- example: it greets a DM session by the caller's verified name and reminds a channel
956
- session that everything it posts is public.
957
-
958
- **Author these with Claude Code (§4.0), and judge drafts by test, not by
959
- reading** — paste the discovery notes, have it draft identity.md and the
960
- client skills, then run §4.4b and the evals. Instructions are prompts under
961
- test: every rule in our own identity.md exists because an eval or a live turn
962
- caught the opposite behavior.
963
-
964
- ### 4.4b Test-drive in `eve dev` — before any connector exists
965
-
966
- You do not need Slack, credentials, or a deploy to exercise the agent:
967
-
968
- ```bash
969
- cd ~/work/acme-atlas
970
- npm run dev # boots the local runtime and opens the dev TUI
971
- ```
972
-
973
- Talk to it in the TUI and walk the behaviors you just authored, in roughly
974
- this order — each line catches a different class of wiring mistake:
975
-
976
- 1. **Identity**: "who are you, what can you do?" — does the persona match
977
- identity.md, and does it write chat-length answers?
978
- 2. **Skill routing**: ask something a skill covers *without naming the skill*
979
- — watch for the `load_skill` call. If it doesn't fire, the skill's
980
- `description` isn't written as a triggering task.
981
- 3. **Delegation**: ask a department question — watch the subagent call and
982
- check the answer came back briefed (§4.4's "pack everything" rule).
983
- 4. **Memory**: "remember that X" then ask for it back — the recall-first rule
984
- in action against the real (eval-workspace!) Arcana.
985
- 5. **Engineer layer** (if installed): ask for a small page + screenshot — the
986
- first local sandbox turn proves the hosted-sandbox credentials work.
987
-
988
- Notes that save an afternoon: the TUI's local principal counts as a **DM**
989
- surface (so `requireDm` tools are reachable locally); `npx eve info` is the
990
- compile/discovery truth (0 diagnostics before moving on); **kill the dev
991
- server before `npm run eval`** — a running instance makes evals attach to it
992
- (§4.8); and the TUI is NOT the deployed agent — Slack runs the deployed build,
993
- redeploy after every change (§11).
994
-
995
- ### 4.5 Build the department subagents
996
-
997
- One directory per department under `agent/subagents/<dept>/`:
998
-
999
- ```
1000
- agent/subagents/finance/
1001
- ├── agent.ts
1002
- ├── instructions.md
1003
- ├── connections/
1004
- │ └── arcana.ts
1005
- └── skills/
1006
- ├── recall/SKILL.md
1007
- ├── remember/SKILL.md
1008
- └── brain-note/SKILL.md
1009
- ```
1010
-
1011
- `agent.ts` — **the `description` is the routing signal.** The root agent sees only this
1012
- string when deciding whether to delegate. Write it as a list of the nouns people
1013
- actually say:
1014
-
1015
- ```ts
1016
- import { defineAgent } from "eve";
1017
-
1018
- export default defineAgent({
1019
- description:
1020
- "Finance specialist: financials, budgets, spend, revenue, invoices, runway, and " +
1021
- "financial reporting. Keeps the finance team's own memory workspace. Delegate any " +
1022
- "finance-shaped task, question, or reporting request here.",
1023
- model: "anthropic/claude-sonnet-5",
1024
- });
1025
- ```
1026
-
1027
- **Two hard constraints on subagents, both of which will bite you:**
1028
-
1029
- 1. **Subagents inherit nothing — give each its own mount.** On eve ≥0.30 a
1030
- declared subagent mounts extensions locally: drop
1031
- `agent/subagents/<dept>/extensions/arcana.ts` (an `arcana({ apiKey,
1032
- workspace })` mount with that department's scoped key) and only that
1033
- subagent gets the connection + skills + instructions. This is the default
1034
- pattern now. The plain-connection alternative below still works (it's what
1035
- pre-0.30 required, and what you'll find in older kyber subagents) when you
1036
- want the connection without the shipped skills:
1037
-
1038
- ```ts
1039
- // agent/subagents/finance/connections/arcana.ts
1040
- import { arcanaBrain } from "../../../lib/arcana";
1041
-
1042
- export default arcanaBrain({
1043
- description:
1044
- "The finance team's long-term memory (Arcana): remember, recall, search, " +
1045
- "timeline, and brain notes for financial work.",
1046
- defaultWorkspace: "acme-finance",
1047
- workspaceEnvVar: "ARCANA_FINANCE_WORKSPACE",
1048
- keyEnvVar: "ARCANA_FINANCE_API_KEY",
1049
- });
1050
- ```
1051
-
1052
- The factory resolves the key in this order: the eval key when the workspace has been
1053
- overridden to `acme-eval`, then the department's own key, then the root
1054
- `ARCANA_API_KEY` as a fallback. That ordering is what makes hermetic eval runs work
1055
- without a second copy of every file.
1056
-
1057
- 2. **Subagents have no user principal, so Vercel Connect OAuth is impossible in them.**
1058
- Any connection a subagent needs must use a static token from an env var — an org
1059
- service account, not a per-user grant. Plan the client's integrations accordingly.
1060
-
1061
- ### 4.6 Schedules (optional)
1062
-
1063
- `agent/schedules/*.ts` for anything recurring — a Monday pipeline summary, a Friday
1064
- financial report. **Schedules live on the root agent only**; a scheduled root turn
1065
- delegates to the subagent that owns the work. `~/kyber/agent/schedules/friday-financials.ts`
1066
- is the working example: it fires Friday 02:00 UTC, delegates to `finance`, and DMs the
1067
- result to a configured Slack user. Note that DMing a user from a schedule needs the
1068
- `im:write` scope on the Slack connector — add it during Phase 5 or the first run fails
1069
- silently at the last step.
1070
-
1071
- ### 4.6b Observability — evlog → PostHog (the Operate-phase deliverable)
1072
-
1073
- One hook file gives the agent per-turn structured telemetry — who talked,
1074
- which tools/subagents fired, timings, token usage, outcome — with message
1075
- text redacted and tool-failure turns always kept:
1076
-
1077
- ```ts
1078
- // agent/hooks/evlog.ts
1079
- import { defineEvlogHook } from "evlog/eve";
1080
- import { createPostHogDrain } from "evlog/posthog";
1081
-
1082
- export default defineEvlogHook({
1083
- init: { env: { service: "acme-atlas" } },
1084
- // mode "events" is REQUIRED for dashboards: the default "logs" mode
1085
- // ships OTLP to the separate PostHog Logs product — invisible to
1086
- // Activity/insights, and it looks exactly like "no events arriving".
1087
- drain: createPostHogDrain({ mode: "events" }),
1088
- redactMessage: true,
1089
- });
1090
- ```
1091
-
1092
- Env: `POSTHOG_API_KEY` = the **project** key (`phc_…`, ingestion-only — a
1093
- `phx_…` personal key is account-privileged and wrong here). The default host
1094
- is `https://us.i.posthog.com`; EU-hosted projects need
1095
- `POSTHOG_HOST=https://eu.i.posthog.com` or events silently vanish. To verify
1096
- region + key in one shot, curl a test event at each region's `/batch/` and
1097
- see which appears in Activity. Turns then land as `evlog_wide_event` — build
1098
- the starter insights on its properties: turns/day by surface, tool failure
1099
- rate, delegation mix, p50/p95 duration.
1100
-
1101
- **Person attribution (optional — a DISCLOSURE item, §2.5):** wide events carry no
1102
- userId by default, so PostHog sees one anonymous actor named after the service. To
1103
- attribute turns to the verified speaker, add a sibling hook that stamps the
1104
- per-message-authenticated principal via evlog's `useLogger` — on `step.started`, not
1105
- `turn.started`, so evlog's turn state exists regardless of hook ordering (crib
1106
- `~/kyber/agent/hooks/attribution.ts`). Then a one-time $identify per person maps ids
1107
- to names. Per-employee telemetry must be a deliberate, disclosed choice at a client.
1108
-
1109
- ### 4.7 Environment variables
1110
-
1111
- Two places must agree: `.env.local` for local development, and the Vercel project's
1112
- environment for anything deployed.
1113
-
1114
- ```bash
1115
- # .env.local (never commit; .gitignore already covers it)
1116
- KYBERNESIS_ISSUER=https://agent.kybernesis.ai
1117
- KYBERNESIS_AGENT=atlas
1118
-
1119
- SLACK_CONNECTOR_UID=slack/atlas
1120
-
1121
- ARCANA_API_KEY=kb_... # root / company brain key
1122
- ARCANA_COMPANY_WORKSPACE=acme-company
1123
- ARCANA_DM_WORKSPACE=acme-dm
1124
-
1125
- ARCANA_FINANCE_API_KEY=kb_...
1126
- ARCANA_FINANCE_WORKSPACE=acme-finance
1127
- ARCANA_ENGINEERING_API_KEY=kb_...
1128
- ARCANA_ENGINEERING_WORKSPACE=acme-engineering
1129
-
1130
- ARCANA_EVAL_API_KEY=kb_... # the acme-eval workspace key
1131
- ```
1132
-
1133
- Push them to Vercel (repeat per environment; mark secrets **Sensitive**):
1134
-
1135
- ```bash
1136
- cd ~/work/acme-atlas
1137
- vercel env add ARCANA_API_KEY production
1138
- vercel env add ARCANA_API_KEY preview
1139
- vercel env add ARCANA_API_KEY development
1140
- ```
1141
-
1142
- > **Read this twice.** `eve deploy` runs a `vercel env pull` afterwards and
1143
- > **overwrites `.env.local`**. The Vercel project environment is the source of truth. If
1144
- > you add a variable locally and then deploy, your local edit is gone. Always add to
1145
- > Vercel first, then pull.
1146
-
1147
- ### 4.8 Evals — the QA deliverable
1148
-
1149
- The baseline suite is a package. Do **not** hand-write or copy eval files:
1150
-
1151
- ```bash
1152
- cd ~/work/acme-atlas
1153
- eve add @kybernesis/evals
1154
- ```
1155
-
1156
- That installs `@kybernesis/evals` and writes two files: `evals/evals.config.ts`
1157
- (judge model — **never the model under test** — generous timeout, gentle
1158
- concurrency) and `evals/kybernesis.eval.ts`, which you configure for this agent:
1159
-
1160
- ```ts
1161
- // evals/kybernesis.eval.ts
1162
- import { kybernesisBaseline } from "@kybernesis/evals";
1163
-
1164
- export default kybernesisBaseline({
1165
- agentDisplayName: "Atlas",
1166
- // engineer: true, // when the engineer layer is installed — adds the vision-loop eval
1167
- routing: [
1168
- { subagent: "finance" },
1169
- { subagent: "marketing" },
1170
- { subagent: "engineering" },
1171
- ],
1172
- });
1173
- ```
1174
-
1175
- You get: a **smoke** eval (boots, replies, identifies itself), five **memory**
1176
- evals (greeting must NOT thrash memory; explicit remember never refused;
1177
- proactive store of company decisions; brain-note write+index two-step, in
1178
- order; a fact stored in one session recalled **unprompted** from a brand-new
1179
- session), and one **routing** eval per department you list. Every fixture
1180
- encodes a hardening lesson we paid for in production (in-test nonces, per-run
1181
- unique keys, company-general wording, suffix-based tool matching, realistic
1182
- delegation timeouts) — the package README explains each one. **Do not "clean
1183
- up" the fixture patterns**; each odd-looking choice fixes a real bug.
1184
-
1185
- Wire the hermetic run into `package.json` so every Arcana workspace is forced
1186
- to `acme-eval` and evals never write into a real brain:
1187
-
1188
- ```jsonc
1189
- "scripts": {
1190
- "eval": "ARCANA_COMPANY_WORKSPACE=acme-eval ARCANA_DM_WORKSPACE=acme-eval ARCANA_FINANCE_WORKSPACE=acme-eval ARCANA_ENGINEERING_WORKSPACE=acme-eval eve eval",
1191
- "typecheck": "tsc"
1192
- }
1193
- ```
1194
-
1195
- Run them:
1196
-
1197
- ```bash
1198
- cd ~/work/acme-atlas
1199
- npm run eval
1200
- ```
1201
-
1202
- Certification-run hygiene (each of these ate a real run on 2026-08-06):
1203
-
1204
- - **Kill any stale `eve dev` first** — a leftover dev server makes `eve eval`
1205
- attach to the old instance and silently run stale code (or refuse to run).
1206
- - **Do not touch the repo while the suite runs.** The dev runtime watches
1207
- `agent/`; an edit mid-run breaks the rebuild and kills the remaining evals.
1208
- - **No Docker anywhere.** The workshop backend is pinned to Vercel Sandbox —
1209
- local eval runs create hosted sandboxes with the linked project's
1210
- credentials (`vercel link` + `vercel env pull` first; `kyb doctor` checks
1211
- the token) and reuse the deploy-prewarmed template (engineer eval ≈3–4 min
1212
- warm, measured). If you eval BEFORE the first deploy, the first run bakes
1213
- the hosted template — evals ≥0.2.1 budget 20 minutes for that.
1214
- - If eve complains about a sandbox migration or templates re-bake every run,
1215
- the local cache is stale from a framework version hop:
1216
- `rm -rf .eve/sandbox-cache .eve/dev-runtime` and rerun.
1217
-
1218
- Client-specific evals go in separate files beside `kybernesis.eval.ts`, built
1219
- from the same primitives the package exports (`isResultFrom`,
1220
- `MEMORY_READ_SUFFIXES`, …). Two traps the package can NOT absorb for you — see
1221
- §10: a running dev server on port 2000 makes `eve eval` exit early, and
1222
- governed agents (`@kybernesis/enterprise`) need `localDev()` in their auth walk
1223
- for local eval runs (the registry's enterprise template includes it — don't
1224
- remove it).
1225
-
1226
- ---
1227
-
1228
- ## 5. Deploy (Day 3, ~30 minutes)
1229
-
1230
- ### 5.1 Slack connector (needs the client's Slack admin present)
1231
-
1232
- ```bash
1233
- cd ~/work/acme-atlas
1234
- vercel connect create slack --triggers --name atlas
1235
- ```
1236
-
1237
- This opens a browser flow against **the client's Slack workspace**. The display name you
1238
- set here is what employees see. Two things to do inside that flow:
1239
-
1240
- - Open **Advanced** and add `message.channels` under **Trigger Event Types** and
1241
- `channels:history` under **Bot Scopes** — these are what make thread-following work.
1242
- Private channels also need `message.groups` and `groups:history`. Add `im:write` if you
1243
- built a schedule that DMs someone.
1244
- - Note the connector UID it gives you (e.g. `slack/atlas`) and put it in
1245
- `SLACK_CONNECTOR_UID`.
1246
-
1247
- Then re-point the trigger at eve's Slack route. eve does not serve Connect's default
1248
- path, so this detach/attach pair is mandatory:
1249
-
1250
- ```bash
1251
- vercel connect detach slack/atlas --yes
1252
- vercel connect attach slack/atlas --triggers --trigger-path /eve/v1/slack --yes
1253
- ```
1254
-
1255
- ### 5.2 Pre-flight, then deploy
1256
-
1257
- ```bash
1258
- cd ~/work/acme-atlas
1259
- npm run typecheck # 0 errors
1260
- npx eve info # 0 discovery diagnostics
1261
- npx eve deploy
1262
- ```
1263
-
1264
- Treat "0 errors, 0 warnings" as a gate, not a goal. `eve info` warnings are almost always
1265
- a file eve did not discover the way you thought.
1266
-
1267
- ### 5.3 Verify the deployment
1268
-
1269
- ```bash
1270
- curl -s https://acme-atlas.vercel.app/eve/v1/health
1271
- # expect {"ok":true}
1272
- ```
1273
-
1274
- Substitute the real deployment URL that `eve deploy` printed. Keep that URL — Phase 6
1275
- needs it.
1276
-
1277
- > **The single most common failure mode in this entire playbook:** you change something,
1278
- > test it in `eve dev`, it works, you try it in Slack, and it does not. **Slack talks to
1279
- > the deployed build.** Every change needs `npx eve deploy` before Slack sees it. Say this
1280
- > out loud to yourself once per day.
1281
-
1282
- ### 5.4 Green eval baseline
1283
-
1284
- ```bash
1285
- cd ~/work/acme-atlas
1286
- npm run eval
1287
- ```
1288
-
1289
- A green suite is the deliverable you hand the client. Wire it into their CI before you
1290
- leave (a GitHub Action running `npm run typecheck && npm run eval` on pull requests,
1291
- with the Arcana eval key and gateway credential as repository secrets).
1292
-
1293
- ---
1294
-
1295
- ## 6. Control-plane wiring (Day 3, ~15 minutes, in the browser)
1296
-
1297
- Go to <https://agent.kybernesis.ai> and switch to the **ACME org**.
1298
-
1299
- ### 6.1 Register the agent
1300
-
1301
- **Agents → Register agent.**
1302
-
1303
- - **Name:** `atlas` — this **must** exactly equal `KYBERNESIS_AGENT` in the deployed
1304
- environment. A mismatch produces `403 agent_not_granted` for a user who genuinely has
1305
- the grant, and it is a maddening thing to debug at a demo.
1306
- - **Runtime:** **▲ eve deployment** (the default).
1307
- - **Deployment URL:** the URL from §5.3.
1308
-
1309
- The row now shows an **▲ eve** badge, the deployment URL, and a live health dot the
1310
- control plane probes from `<deploymentUrl>/eve/v1/health`. If the dot is red, the control
1311
- plane cannot reach the deployment — check Vercel deployment protection before you check
1312
- anything else.
1313
-
1314
- ### 6.2 Invite the humans
1315
-
1316
- **Users → Invite.** Invite the client's admins first, then the pilot cohort from your
1317
- discovery table. Give the client admins `manage`; everyone else gets `use`.
1318
-
1319
- ### 6.3 Grant the agent
1320
-
1321
- **Grants.** Grant `atlas` to each pilot user (or to a team, if you created one). Level
1322
- `use` for the cohort, `manage` for admins.
1323
-
1324
- Alternatively, set the agent's access tier to `org` so every org member gets it
1325
- implicitly. For a pilot, prefer explicit grants — the whole demo in §8 depends on grants
1326
- being visibly individual.
1327
-
1328
- ### 6.4 The one timing rule you must internalize
1329
-
1330
- **Grants are resolved at mint time.** The identity token and policy bundle a user holds
1331
- are a snapshot of their access at the moment they signed in.
1332
-
1333
- - Grant someone *after* they signed in → they do not have it until their session is
1334
- re-minted (a refresh, or a fresh sign-in).
1335
- - Revoke someone → their **current** token keeps working until it expires. Default TTL is
1336
- 1 hour (`IDENTITY_TOKEN_TTL_SECONDS`). **That TTL is the off-boarding SLA. Tell the
1337
- client the number.** If an hour is too long for them, tune it — the cost is more
1338
- frequent refreshes.
1339
- - **Suspend** someone → minting refuses entirely. That is the immediate lever, and it is
1340
- the one to use for a real off-boarding.
1341
-
1342
- ---
1343
-
1344
- ## 7. Pilot onboarding — the humans (Day 4)
1345
-
1346
- The technical work is done. This day decides whether the pilot succeeds.
1347
-
1348
- ### 7.1 Set up the shared channel
1349
-
1350
- Invite the agent to the client's chosen channel (`/invite @atlas`). Post a short pinned
1351
- message the champion can point at:
1352
-
1353
- > `@atlas` is our company agent. Mention it in this channel to ask something, and it will
1354
- > keep replying in that thread without needing another mention — so you can just talk.
1355
- > DM it for anything personal; DMs use a separate memory from this channel. Type `/new` in
1356
- > a DM to start over. It remembers what you tell it, so telling it something once is
1357
- > enough.
1358
-
1359
- ### 7.2 Run a 30-minute group session with the cohort
1360
-
1361
- Do this live, in the shared channel, with everyone watching. Demonstrate, in order:
1362
-
1363
- 1. **Mention it and ask a real question.** Use something from their actual work, not a
1364
- toy.
1365
- 2. **Have a second person reply in the same thread without mentioning it.** This is the
1366
- moment the room understands it is not a chatbot. Point out that the agent knows who
1367
- said what.
1368
- 3. **Tell it a fact** — "our Q3 board meeting is on the 12th" — then start a **fresh
1369
- thread** and ask about it. Cross-session recall is the second moment.
1370
- 4. **Ask a department question** and let it delegate. Show that the finance specialist
1371
- has its own knowledge.
1372
- 5. **Ask something personal in the channel** and let it refuse and redirect to a DM. Then
1373
- do it in a DM. This teaches the surface model better than any explanation.
1374
-
1375
- ### 7.3 Train the client admins separately (15 minutes)
1376
-
1377
- Walk the two admins through the control plane themselves — do not do it for them:
1378
-
1379
- - Invite a user.
1380
- - Grant `atlas`.
1381
- - Revoke it, and watch what happens (§8.3).
1382
- - Suspend a user, and watch minting refuse.
1383
-
1384
- Tell them the timing rule from §6.4 in these words: *"Suspend is immediate. Revoke takes
1385
- effect within an hour. Grant takes effect the next time the person signs in."*
1386
-
1387
- ### 7.4 Set expectations honestly
1388
-
1389
- Say these four things to the cohort, in plain language:
1390
-
1391
- - Anyone in this Slack workspace can talk to the agent. Per-person access control on the
1392
- Slack door is not built yet; the control-plane grants govern the HTTP/desktop door.
1393
- - Approval buttons in a shared thread can be clicked by anyone in that thread. Do not use
1394
- the agent for anything destructive in a shared channel yet.
1395
- - Everything it learns in a public channel is shared with everyone in that channel.
1396
- - It is a week old at your company. It will be wrong. Tell the champion when it is, and
1397
- those corrections become instructions and evals.
1398
-
1399
- ---
1400
-
1401
- ## 8. Acceptance demo script (Day 5, ~20 minutes, in front of the client's sponsor)
1402
-
1403
- Run this as a script. Rehearse it once alone first. Every step below has been executed
1404
- against production.
1405
-
1406
- ### 8.1 Slack — the agent works (5 min)
1407
-
1408
- - [ ] Mention `@atlas` in the shared channel with a real question → coherent reply in
1409
- thread.
1410
- - [ ] A second person replies in the thread with no mention → agent continues, and its
1411
- answer reflects who is speaking.
1412
- - [ ] DM `@atlas` → it responds as a personal assistant.
1413
- - [ ] In the DM, type `/new` → "Started a fresh conversation."
1414
-
1415
- ### 8.2 Memory — it actually learns (5 min)
1416
-
1417
- - [ ] In the channel: tell it a company fact.
1418
- - [ ] Start a **fresh thread**: ask about that fact → it recalls it.
1419
- - [ ] Ask a department question → it delegates, and the answer comes back synthesized.
1420
- - [ ] Show the client their own memory, in their own workspace:
1421
- ```bash
1422
- curl -s -H "Authorization: Bearer $ARCANA_API_KEY" \
1423
- -H "X-Kyberagent-Agent: acme-company" \
1424
- "https://api.arcana.kybernesis.ai/brain/acme-company/timeline?limit=5"
1425
- ```
1426
-
1427
- ### 8.3 Governance — the demo that sells the product (10 min)
1428
-
1429
- This is the part the sponsor remembers. Do it live; do not screenshot it.
1430
-
1431
- **Step 1 — sign in as a granted employee.** Start the device flow:
1432
-
1433
- ```bash
1434
- curl -s -X POST https://agent.kybernesis.ai/api/oauth/device \
1435
- -H 'content-type: application/json' \
1436
- -d '{"deviceId":"fde-demo","deviceLabel":"FDE laptop"}'
1437
- ```
1438
-
1439
- You get back `device_code`, `user_code`, `verification_uri`,
1440
- `verification_uri_complete`, `expires_in: 600`, `interval: 5`. Open
1441
- `verification_uri_complete` in a browser and approve as the employee.
1442
-
1443
- **Step 2 — exchange the device code for a session:**
1444
-
1445
- ```bash
1446
- curl -s -X POST https://agent.kybernesis.ai/api/oauth/token \
1447
- -H 'content-type: application/json' \
1448
- -d '{"device_code":"PASTE_DEVICE_CODE"}'
1449
- ```
1450
-
1451
- Before approval this returns RFC 8628 error codes (`authorization_pending`, and so on).
1452
- After approval it returns an **IdentitySession** plus a `refresh_token`:
1453
-
1454
- ```json
1455
- { "issuer": "https://agent.kybernesis.ai",
1456
- "token": "<compact JWS — iss, sub (userId), org, email, org_name, exp>",
1457
- "bundle": "<compact JWS — user, org, agentGrants:[{agent,level}], allowedAdapters, exp>",
1458
- "jwks": { "keys": [ "...public ES256 keys..." ] } }
1459
- ```
1460
-
1461
- **Step 3 — call the agent with the two headers:**
1462
-
1463
- ```bash
1464
- TOKEN=... # the "token" field
1465
- BUNDLE=... # the "bundle" field
1466
-
1467
- curl -s -i -X POST https://acme-atlas.vercel.app/eve/v1/sessions \
1468
- -H "authorization: Bearer $TOKEN" \
1469
- -H "x-kybernesis-bundle: $BUNDLE" \
1470
- -H 'content-type: application/json' \
1471
- -d '{"input":"Hello"}'
1472
- ```
1473
-
1474
- The session streams. Say out loud what just happened: *the agent verified that token
1475
- offline, against a public key, with no call back to us.*
1476
-
1477
- **Step 4 — show the failure mode first.** Call the same endpoint with no headers:
1478
-
1479
- ```bash
1480
- curl -s -o /dev/null -w "%{http_code}\n" -X POST https://acme-atlas.vercel.app/eve/v1/sessions
1481
- # 401
1482
- ```
1483
-
1484
- **Step 5 — the revoke.** In the admin UI, revoke that employee's grant for `atlas`. Then
1485
- have them sign in again (repeat steps 1–2) and call the agent with the **fresh** token:
1486
-
1487
- ```
1488
- HTTP/1.1 403 Forbidden
1489
- {"error":"agent_not_granted", ...}
1490
- ```
1491
-
1492
- **Step 6 — the suspend.** Suspend the user in the admin UI. Try to mint again — the
1493
- control plane refuses to issue a session at all. This is the off-boarding lever.
1494
-
1495
- **Step 7 — restore.** Re-activate and re-grant, sign in once more, and show access
1496
- returning. Leave the client's world as you found it.
1497
-
1498
- Then state the SLA plainly: *"Suspension is immediate. A revoked employee's already-issued
1499
- token stops working within the token TTL — one hour by default, tunable."*
1500
-
1501
- ### 8.4 Sign-off checklist
1502
-
1503
- - [ ] Slack: mention, thread continuation, DM, `/new`
1504
- - [ ] Memory: store, cross-session recall, delegation, visible in their Arcana workspace
1505
- - [ ] Governance: 401 → grant → 200 → revoke → 403 → suspend → mint refused → restored
1506
- - [ ] Health: `<deployment>/eve/v1/health` returns `{"ok":true}` and the control plane's
1507
- health dot is green
1508
- - [ ] Evals: `npm run eval` green, running in their CI
1509
- - [ ] Admins have done an invite, a grant, and a revoke **with their own hands**
1510
-
1511
- ---
1512
-
1513
- ## 9. Handover (Day 5)
1514
-
1515
- ### 9.1 What the client owns and receives
1516
-
1517
- - **The repo** — in their GitHub org, if they want it. Push it there and add their
1518
- engineers. It is their source.
1519
- - **The Vercel project** — theirs already. Confirm their team owns it, not your personal
1520
- scope, and that at least two of their people have deploy access.
1521
- - **The Slack app** — installed in their workspace, owned by their admin.
1522
- - **Their Arcana workspaces** and the scoped keys. Hand these over through a password
1523
- manager, not Slack.
1524
- - **Control-plane admin access** — invite, grant, revoke, suspend is their entire
1525
- operational surface, and after §7.3 they know how to use it.
1526
- - **The eval suite**, running in their CI.
1527
- - **Optionally Eve Studio** for employees who do not live in Slack — note that Studio
1528
- sign-in against the control plane is specced but **not built yet** (§11).
1529
-
1530
- ### 9.2 What Kybernesis keeps doing
1531
-
1532
- - **Operating the control plane** at `agent.kybernesis.ai` — issuer, keys, grants,
1533
- audit. (Unless they self-host, in which case we support their instance.)
1534
- - **Maintaining and versioning the packages** — `@kybernesis/arcana`,
1535
- `@kybernesis/enterprise`, `@kybernesis/multiplayer`. Version bumps are our work; the
1536
- client's eval suite is the gate that proves an upgrade is safe.
1537
- - **Operating Arcana** — the memory SaaS their brains live in.
1538
- - **Per-client eve version pins** (`eve_agent_deployment.eve_version_pin`) so a framework
1539
- release never surprises a client mid-quarter.
1540
- - **The maintenance retainer** — which is what all of the above justifies. Frame it that
1541
- way in the handover conversation: they own the agent, we own the platform underneath it.
1542
-
1543
- ### 9.3 Leave-behind document
1544
-
1545
- Write a one-page README in their repo covering: the deployment URL, the control-plane
1546
- org URL, which Arcana workspace maps to which subagent, the env var list (names only,
1547
- never values), how to run the evals, and — in bold — **redeploy after every change,
1548
- because Slack runs the deployed build.**
1549
-
1550
- ---
1551
-
1552
- ## 10. Troubleshooting appendix
1553
-
1554
- Grouped by where the pain shows up. Every entry here cost someone real time.
1555
-
1556
- ### Environment and deployment
1557
-
1558
- **My `.env.local` changes disappeared.**
1559
- `eve deploy` runs `vercel env pull` afterwards and overwrites `.env.local`. The Vercel
1560
- project environment is the source of truth. Add to Vercel first, then pull.
1561
-
1562
- **I changed something and Slack still does the old thing.**
1563
- Slack talks to the **deployed** build. Run `npx eve deploy`. This is the number one
1564
- support question and it will be yours too.
1565
-
1566
- **`eve dev` won't start / `eve eval` exits immediately.**
1567
- Something is already on port 2000 (eve dev's default). Find and kill it:
1568
- ```bash
1569
- lsof -ti tcp:2000 | xargs kill
1570
- ```
1571
- `eve eval` boots its own host and exits early if the port is taken.
1572
-
1573
- **Vercel CLI does nothing useful in a script.**
1574
- Non-interactive `vercel` calls need the team: add `--scope <team-slug>`. `eve link` is
1575
- interactive-only; in CI use `vercel link --project <name> --yes --non-interactive`.
1576
-
1577
- **The control plane's health dot is red but `curl` works for me.**
1578
- Vercel deployment protection. A protected preview or production deployment rejects the
1579
- control plane's unauthenticated health probe. Check the project's Deployment Protection
1580
- settings.
1581
-
1582
- ### Arcana / memory
1583
-
1584
- **Everything Arcana returns 403.**
1585
- `kb_` keys are **workspace-scoped**. A key minted for `acme-finance` gets `403` on
1586
- `acme-company`. Check that the key env var and the workspace env var for that mount refer
1587
- to the same workspace. Validate in isolation:
1588
- ```bash
1589
- curl -s -o /dev/null -w "HTTP %{http_code}\n" \
1590
- -H "Authorization: Bearer kb_..." \
1591
- -H "X-Kyberagent-Agent: acme-finance" \
1592
- "https://api.arcana.kybernesis.ai/brain/acme-finance/timeline?limit=1"
1593
- ```
1594
-
1595
- **Memory tools are missing entirely from a subagent.**
1596
- Subagents inherit nothing from the root — the root's arcana mount does not reach them.
1597
- Give the subagent its own local extension mount
1598
- (`agent/subagents/<id>/extensions/arcana.ts`, eve ≥0.30) or a plain connection file
1599
- plus skill copies (§4.5).
1600
-
1601
- **The agent says "I don't have anything stored about that" without looking.**
1602
- The `@kybernesis/arcana` instructions carry a never-claim-ignorance-without-searching
1603
- rule and a recall→search escalation rule (an empty entity recall does **not** mean nothing
1604
- is stored). If you overrode or trimmed the instructions, you dropped those rules. Put
1605
- them back — an eval caught this exact failure.
1606
-
1607
- **Arcana MCP tool names don't match the docs.**
1608
- Tool names are qualified by the mount namespace. Mounted as `agent/extensions/arcana.ts`,
1609
- `arcana_remember` is addressed as `arcana__memory__arcana_remember`. Run `npx eve info` to
1610
- see the actual resolved names rather than guessing.
1611
-
1612
- **Should I use Vercel Connect OAuth for Arcana?**
1613
- No. Use a static `kb_` key. Connect OAuth works in the `eve dev` TUI and is broken in
1614
- production (Connect beta: "couldn't find this authorization request"; grants also do not
1615
- cross environments). This is settled — see the `arcana-eve` skill for the full analysis.
1616
-
1617
- ### Governance / auth
1618
-
1619
- **A user who definitely has the grant gets `403 agent_not_granted`.**
1620
- Three causes, in order of likelihood: (1) the agent's registered name in the control plane
1621
- does not exactly equal `KYBERNESIS_AGENT` in the deployed environment; (2) the grant was
1622
- added *after* the user signed in and their token predates it — grants resolve at mint
1623
- time, so refresh or re-sign-in; (3) you are pointing at the wrong org's issuer.
1624
-
1625
- **Everything returns 401.**
1626
- Missing, malformed, or expired credentials — or only one of the two headers. Both are
1627
- required: `authorization: Bearer <token>` **and** `x-kybernesis-bundle: <bundle>`. Also
1628
- check `KYBERNESIS_ISSUER` matches the issuer that minted the token.
1629
-
1630
- **A revoked employee can still use the agent.**
1631
- Expected, for up to the token TTL (1h default). Grants resolve at mint; already-issued
1632
- tokens are not revoked mid-flight. For immediate cut-off, **suspend** the user — minting
1633
- refuses entirely. Tune `IDENTITY_TOKEN_TTL_SECONDS` if the client needs a tighter SLA.
1634
-
1635
- **I can't `curl` my own agent locally.**
1636
- The governed `agent/channels/eve.ts` drops `localDev()` and `placeholderAuth()` — it fails
1637
- closed by design. Drive it through the `eve dev` TUI, or mint a real token via the device
1638
- flow.
1639
-
1640
- ### Slack
1641
-
1642
- **Slack delivers nothing.**
1643
- The trigger is still on Connect's default path. eve does not serve that path. Run the
1644
- detach/attach pair with `--trigger-path /eve/v1/slack` (§5.1).
1645
-
1646
- **Only @mentions arrive; thread-following doesn't work.**
1647
- Missing scopes. The connector needs the `message.channels` trigger event and the
1648
- `channels:history` bot scope (plus `message.groups` / `groups:history` for private
1649
- channels). `threadContext` needs the same history scopes.
1650
-
1651
- **The agent replies to itself, or to other bots.**
1652
- eve drops messages authored by the installed app before your hook runs, but *other* bots
1653
- are still visible. The multiplayer package filters `message.author?.isBot`; if you hand-
1654
- rolled a hook, do the same.
1655
-
1656
- **A scheduled DM never arrives.**
1657
- The connector needs `im:write`. The schedule otherwise runs fine and fails at the last
1658
- step, which makes it look like the schedule did not fire.
1659
-
1660
- ### Evals
1661
-
1662
- **Evals pass locally then fail identically twice in a row after a fix.**
1663
- eve caches compiled eval modules across runs. Avoid module-level nonces (no
1664
- `const id = Date.now()` at module scope) — generate per-run values inside `test()`.
1665
-
1666
- **A routing eval times out.**
1667
- Delegation does real memory work in the subagent. Routing evals need roughly six-minute
1668
- timeouts. Set `timeoutMs` in `evals.config.ts` or pass `--timeout`.
1669
-
1670
- **An eval fails because the agent refused.**
1671
- Certain phrasings trip the model's own safety behavior — "canary codeword" reads as a
1672
- secret-extraction attempt. Use neutral wording ("project codename").
1673
-
1674
- **Evals wrote test data into the real brain.**
1675
- The `eval` npm script must override **every** Arcana workspace env var to `acme-eval`.
1676
- Miss one and that subagent writes into production memory. Check the script against your
1677
- actual list of subagents.
1678
-
1679
- **`npm run eval` looked green in CI but the job passed when it shouldn't have.**
1680
- Piping eval output to `tail` (or anything else) masks the exit code. Use
1681
- `set -o pipefail`, or don't pipe.
1682
-
1683
- ### Packages and the registry
1684
-
1685
- **`eve add @kybernesis/...` 404s.**
1686
- Either the registry namespace is not registered in this project (`eve registry add
1687
- @kybernesis=https://registry.kybernesis.ai/r/{name}.json`), or that item is not published
1688
- yet. Check what actually exists with `npx eve registry list --registry @kybernesis`, or
1689
- hit the item URL directly: `curl -s -o /dev/null -w "%{http_code}\n"
1690
- https://registry.kybernesis.ai/r/multiplayer.json`.
1691
-
1692
- **`eve add` resolved the item but the npm install failed.**
1693
- The registry item and the npm package are published separately, and the registry item can
1694
- land first. That is exactly the state `@kybernesis/multiplayer` is in as of 2026-08-05.
1695
- `npm view @kybernesis/<name> version` tells you which half is missing.
1696
-
1697
- **`npm install @kybernesis/<something>` says the version doesn't exist, right after publish.**
1698
- New packages and versions take one to three minutes to propagate to anonymous reads, even
1699
- after `npm access` reports them public. Wait, then retry.
1700
-
1701
- **Publishing a new package in the `@kybernesis` scope fails.**
1702
- Only the `kybernesis` npm account can **create** packages in the scope; `ianborders` can
1703
- publish new versions of existing ones. Publishing needs Ian's browser auth (npm web-login
1704
- flow). This is a Kybernesis-internal step, not something to do at a client site.
1705
-
1706
- **A published package imports fine locally but breaks on clean install.**
1707
- `tsc` does not rewrite import specifiers. An extensionless ESM re-export in `dist/` works
1708
- in a workspace and fails from the registry. `@kybernesis/enterprise@0.1.0` shipped with
1709
- exactly this bug; `0.1.1` fixed it with explicit `.js` extensions. Always test a package
1710
- change with a **clean install into a scratch project**, never only from the workspace.
1711
-
1712
- **A script's JSON output is polluted with warnings.**
1713
- pnpm writes engine warnings to stdout. Pipe through `pnpm --silent` and, if needed,
1714
- `sed -n '/^{/,$p'` to strip everything before the first JSON line. (This mostly bites in
1715
- the control-plane repo's seed scripts.)
1716
-
1717
- ### General
1718
-
1719
- **Something isn't being picked up and I can't see why.**
1720
- `npx eve info` first, always. It prints exactly what eve discovered plus diagnostics, and
1721
- it is far faster than booting the dev server. `npx eve logs` reads the last `eve dev`
1722
- diagnostic log if you need stderr, tool failures, and rebuild lines.
1723
-
1724
- ### Self-hosted hosts (exe.dev / client infrastructure)
1725
-
1726
- **I deployed a change and the agent still does the old thing.**
1727
- The restart did not take. Check that the running process started *after* the build
1728
- (`scripts/eve-server.sh` asserts this and fails loudly). A silently-failed restart is the
1729
- single most expensive failure mode off Vercel, because every test you run afterwards
1730
- measures the previous build. If the process is current, start a **fresh conversation** —
1731
- a long-lived channel session caches the compiled agent.
1732
-
1733
- **Every sandbox tool fails with `SandboxTemplateNotProvisionedError`.**
1734
- Either Docker is installed but disabled (`sudo systemctl enable --now docker` — exeuntu
1735
- ships it disabled), or the server was started as `node .output/server/index.mjs`, which
1736
- skips the CLI's template prewarm. Start with `npx eve start`.
1737
-
1738
- **The subagent's screenshots fail with `Cannot find module 'playwright'` but the root
1739
- agent is fine.**
1740
- Subagents own their sandbox; they do not inherit the root's. Give the subagent its own
1741
- `sandbox/sandbox.ts`.
1742
-
1743
- **My env vars are missing under `eve start`.**
1744
- `eve start` does not read `.env.local` the way `eve dev` does. Use
1745
- `scripts/eve-server.sh`, which exports it into the process.
1746
-
1747
- **I killed the agent and my own SSH session at the same time.**
1748
- `pkill -f <pattern>` matches the SSH command line that contains the pattern. Keep kill
1749
- patterns inside a script on the host; never pass them on an `ssh` command line.
1750
-
1751
- **A sandbox VM boots and shows `running`, but every command answers "command not found".**
1752
- SSH is landing on exe.dev's lobby REPL rather than the VM. exe.dev reuses VM names, so a
1753
- name deleted and recreated within a few minutes can keep resolving to the lobby, and a
1754
- stale `known_hosts` entry does the same thing. Use a fresh name, and never reuse the
1755
- caller's `known_hosts` for sandbox VMs.
1756
-
1757
- **A command works when I SSH in by hand but not from the agent.**
1758
- A non-interactive SSH command does not source the login profile, so toolchains installed
1759
- onto the profile PATH (nvm, pyenv, cargo) are invisible. Run through `bash -lc` — but not
1760
- for file I/O, whose stdout must stay byte-clean.
1761
-
1762
- **exe.dev refuses to run commands with my API token.**
1763
- It cannot. A key registered through an API token inherits that token's command scope, and
1764
- shell exec is not a scoped command. Running commands needs a full-permission account key;
1765
- see 11.6 for the isolation this demands.
1766
-
1767
- ### Third-party APIs and connections
1768
-
1769
- **The agent reports that a service is down or that a resource "isn't shared" with it.**
1770
- Verify before you believe it, and never change the client's permissions on an agent's
1771
- say-so. An agent's error message is a hypothesis, not evidence — one real case reported an
1772
- outage, then a permissions problem, while the data was reachable the entire time and the
1773
- actual cause was a version header. Read the request and response yourself.
1774
-
1775
- **My manual `curl` works but the agent's identical call fails.**
1776
- It is not identical — and the difference between the two requests *is* the bug. Diff them
1777
- at the first contradiction rather than proving again that the token works. A version
1778
- header, a content type, or a base URL differing by one path segment will do it.
1779
-
1780
- **Calls derived from a large OpenAPI spec fail in ways that make no sense.**
1781
- Specs with ambiguous ID schemes (Notion's `database_id` vs `data_source_id` for the same
1782
- object) make the model pick wrong, and the API's errors describe a different problem.
1783
- Write a small purpose-built tool with the endpoint and IDs pinned; keep the generic
1784
- connection for the long tail.
1785
-
1786
- ---
1787
-
1788
-
1789
- ## 11. Self-hosted deployments (when the client will not use Vercel)
1790
-
1791
- Vercel is the default path and the proven one. Take this track when the client
1792
- **cannot or will not** put the agent on Vercel — procurement, data residency, an
1793
- existing VM estate — or when they want inference billed to a subscription they
1794
- already pay for rather than to a gateway.
1795
-
1796
- Everything else in this playbook still applies. This section covers only what
1797
- changes.
1798
-
1799
- **The governing rule: every credential comes from the CLIENT's accounts.** If a
1800
- step works only because you happen to hold a token, that step is a bug in the
1801
- deployment, not a shortcut — it will fail on the real engagement, in front of
1802
- the client. Assume you have no Vercel connection, no blob store, and no API
1803
- keys of your own.
1804
-
1805
- ### 11.1 Scaffold
1806
-
1807
- ```bash
1808
- kyb init <name> --host=exe --channel=<imessage|slack|telegram|none> --engineer
1809
- cd <name> && kyb doctor
1810
- ```
1811
-
1812
- `--host=exe` swaps the host bindings; everything else is the same product —
1813
- same memory, same evals, same control-plane wiring, same engineer layer.
1814
- `kyb doctor` knows every failure mode below and fails loudly on each.
1815
-
1816
- ### 11.2 What Vercel gives you that a client host does not
1817
-
1818
- | Capability | On Vercel | Self-hosted replacement |
1819
- | --- | --- | --- |
1820
- | Model access | AI Gateway | exe.dev LLM integration (`exeModel`) — managed, BYO key, or a **ChatGPT subscription** |
1821
- | Slack / Photon / Linear credentials | Vercel Connect | **static credentials the client issues** |
1822
- | Sandbox | `vercel()` | `docker()` on the host, or `exeSandbox()` per-task VMs |
1823
- | File delivery | Vercel Blob | Blob **or** `DELIVER_DIR` + `DELIVER_BASE_URL` |
1824
- | Public URLs | deployments | a deploy target, or an account-gated preview |
1825
- | Secrets | Vercel env | host env + the platform's own secret injection |
1826
-
1827
- **Vercel Connect does not work off-Vercel — at all.** It authenticates via
1828
- Vercel OIDC, which does not exist on another host. That applies to Slack, the
1829
- Vercel MCP connection, Linear, everything. Each becomes a static credential
1830
- someone must issue and rotate, and `kyb doctor` fails if a `@vercel/connect`
1831
- import survives into a self-hosted agent.
1832
-
1833
- ### 11.3 Host setup (exe.dev)
1834
-
1835
- ```bash
1836
- ssh exe.dev new --name <agent>
1837
- ssh exe.dev share port <agent> 8000 && ssh exe.dev share set-public <agent>
1838
- ```
1839
-
1840
- Make the host public **before** registering any webhook — webhooks need
1841
- anonymous access, and a provider that fails verification at registration time
1842
- usually will not tell you why.
1843
-
1844
- Then, on the VM: Node 24, `npm ci`, `npx eve build`, and start through
1845
- `scripts/eve-server.sh` from `@kybernesis/exe`.
1846
-
1847
- Three things that will cost you an afternoon if you skip them:
1848
-
1849
- - **`eve start` does not read `.env.local`** the way `eve dev` does. The
1850
- supervision script exports it into the process for you.
1851
- - **Start via `npx eve start`, not `node .output/server/index.mjs`.** Sandbox
1852
- template prewarm lives in the CLI, not the built server. Starting the server
1853
- directly gives you cleaner logs and no prewarm, so every sandbox tool then
1854
- fails with `SandboxTemplateNotProvisionedError`.
1855
- - **Docker ships disabled on some images**, exeuntu among them. `docker
1856
- --version` answers happily while nothing can actually run. Fix with `sudo
1857
- systemctl enable --now docker`.
1858
-
1859
- ### 11.4 Model: billing inference to the client's subscription
1860
-
1861
- exe.dev brokers the model, so no provider key sits on the host:
1862
-
1863
- ```bash
1864
- ssh exe.dev integrations setup chatgpt --name work # once, device-code flow
1865
- ssh exe.dev integrations edit llm --openai=chatgpt --openai-account=work
1866
- ```
1867
-
1868
- Then `exeModel()` from `@kybernesis/exe` points the agent at it. A Codex-backed
1869
- subscription requires `store: false` on every request; `exeModel` forces it.
1870
-
1871
- This is the single biggest commercial difference in the self-hosted track: a
1872
- client with an existing ChatGPT or Claude subscription pays no incremental
1873
- inference cost for the pilot. Say the number out loud in the discovery
1874
- conversation — it changes the shape of the deal.
1875
-
1876
- **Grok, on a SuperGrok or X Premium+ subscription.** Same arrangement, without
1877
- the broker: xAI's Grok Build CLI does a device login and writes a credential
1878
- that is a valid bearer for `https://api.x.ai/v1`. Proven in production on Sid —
1879
- twelve evals, twenty-nine gates, green on the subscription.
1880
-
1881
- ```bash
1882
- # on the host, as the unix user the agent runs as
1883
- curl -fsSL https://x.ai/cli/install.sh | bash
1884
- grok login # device flow → ~/.grok/auth.json
1885
- ```
1886
-
1887
- ```ts title="agent/agent.ts"
1888
- import { createOpenAI } from "@ai-sdk/openai";
1889
- import { grokSubscription } from "@kybernesis/exe";
1890
-
1891
- export default defineAgent({
1892
- model: grokSubscription({ model: "grok-4.6", createOpenAI }),
1893
- modelContextWindowTokens: 400_000,
1894
- });
1895
- ```
1896
-
1897
- Three things to know before you promise it to a client:
1898
-
1899
- - The credential is **per-machine and per-user**. It lives in a home directory.
1900
- A different unix user cannot see it; a new host needs its own login.
1901
- - It **expires in six hours** and the CLI refreshes it in place, so the agent
1902
- must re-read the file per request. `grokSubscription` does this in a `fetch`
1903
- wrapper. (Do not reach for a Proxy around the model object — the AI SDK's
1904
- methods depend on their own `this` and every call dies inside the SDK.)
1905
- - **Unattended refresh over days is unverified.** If nobody runs `grok` on that
1906
- host for a week, it is an open question, and it would present to the client as
1907
- the agent breaking for no reason.
1908
-
1909
- **The model will lie about which model it is.** Sid, running Grok, stated it was
1910
- "Claude Opus 4.6, Anthropic" and attributed it to an instruction that exists
1911
- nowhere in its context. Verify from the host — the configured model id and the
1912
- credential in use — never by asking the agent. Expect a client to ask it in a
1913
- demo, and have the real answer ready.
1914
-
1915
- ### 11.5 Third-party APIs: broker the credential, pin the version
1916
-
1917
- Do not put a client's API token on the agent host. Put it in an exe.dev
1918
- http-proxy integration, which injects headers server-side:
1919
-
1920
- ```bash
1921
- ssh exe.dev "integrations add http-proxy --name notion \
1922
- --target https://api.notion.com \
1923
- --header 'Authorization:Bearer <token>' \
1924
- --header 'Notion-Version:2025-09-03' \
1925
- --attach vm:<agent>"
1926
- ```
1927
-
1928
- The agent then calls `https://notion.int.exe.xyz` with no credential at all, and
1929
- rotation happens in one place the client controls.
1930
-
1931
- Two rules here, both learned expensively:
1932
-
1933
- **Pin the API version the SPEC describes, not the one in a doc example.** A
1934
- version header copied from documentation while the agent's OpenAPI spec
1935
- describes a newer API produces errors that describe the wrong problem entirely.
1936
- One real case cost most of a day: `Notion-Version: 2022-06-28` against a spec
1937
- using `/v1/data_sources/...` returned `invalid_request_url`, intermittent 503s
1938
- from search, and "not shared with the integration" — three different messages,
1939
- none of them about the version mismatch, two of which look like a permissions
1940
- or availability problem you can waste hours "fixing" on the client's side.
1941
-
1942
- **Use `--header` for the token, never `--bearer=-`.** The stdin form mangles the
1943
- value and the API answers 401 "token is invalid".
1944
-
1945
- **Large specs with ambiguous ID schemes need a purpose-built tool, not a raw
1946
- connection.** Notion's spec is ~1.2MB and splits `database_id` from
1947
- `data_source_id` for the same object; a model deriving calls from it picks
1948
- wrong. Pin the endpoint and the IDs in a small tool under `agent/tools/`, keep
1949
- the generic connection for the long tail, and point the instructions at the
1950
- tool. Reads went from unreliable to deterministic with about sixty lines.
1951
-
1952
- ### 11.6 The engineer layer, self-hosted
1953
-
1954
- `--engineer` scaffolds a **builder subagent** that owns the build capability, so
1955
- the root agent never gets a shell. It comes with the full production loop —
1956
- workshop sandbox, Playwright, screenshots, visual verification, delivery — not a
1957
- reduced version of the Vercel one.
1958
-
1959
- - **Subagents own their sandbox; they do NOT inherit the root's.** A builder
1960
- without its own `sandbox/sandbox.ts` gets a bare template and every screenshot
1961
- fails with `Cannot find module 'playwright'` while the root's template is
1962
- fine. `kyb doctor` checks for this.
1963
- - Conversely, if the root agent runs no code, it needs **no** root sandbox.
1964
- Leaving one there costs a full template prewarm on every deploy for nothing.
1965
-
1966
- **Choosing a sandbox backend:**
1967
-
1968
- `docker()` on the agent's own VM is the default and the safer choice. Reach for
1969
- `exeSandbox()` — a whole exe.dev VM per session, with Docker available *inside*
1970
- it and its own public URL — only when the work needs real isolation or
1971
- per-session compute.
1972
-
1973
- `exeSandbox()` carries one hard constraint you must raise with the client before
1974
- choosing it: **it needs a full-permission account SSH key.** exe.dev keys
1975
- registered through an API token inherit that token's command scope and cannot
1976
- open a shell at all, so there is no scoped credential that can run commands. The
1977
- key it does need grants shell to **every VM on that exe.dev account**. The
1978
- backend therefore refuses to start unless the account is dedicated to this agent
1979
- (its own VM plus its sandboxes), naming any foreign VMs it finds. Overriding
1980
- that with `allowSharedAccount: true` is a decision for the client to make in
1981
- writing, not for you to make on their behalf. VM lifecycle stays on a separately
1982
- scoped token that cannot exec, so neither credential is sufficient alone.
1983
-
1984
- ### 11.7 Showing the client what the agent built
1985
-
1986
- - **Vercel Blob refuses to serve HTML inline** — it forces a download. Use it
1987
- for documents and exports, never to show a web page.
1988
- - **exe.dev forwards ports 3000–9999** to `https://<vm>.exe.xyz:<port>/`, but a
1989
- VM has exactly **one public port** and the agent's webhook already owns it.
1990
- Alternate ports are account-gated: fine for the client reviewing work, not for
1991
- the public.
1992
- - **Anything genuinely public needs a deploy target** — the client's own Vercel
1993
- token, or their hosting. Treat "public" as a deploy step, not a toggle, and
1994
- price it into the scope.
1995
- - A sandbox is a container: its ports are not reachable from the host, so a dev
1996
- server inside it cannot be previewed directly. Copy the artifact out (the
1997
- `preview` tool in `@kybernesis/exe`) or deploy it.
1998
-
1999
- ### 11.8 Prove the restart, every time
2000
-
2001
- There is no deployment pipeline here to tell you a release landed. A restart
2002
- that silently fails leaves the agent serving a stale build — new connections,
2003
- tools, and instructions never appear — and **every test you run afterwards
2004
- measures yesterday's agent.** One session lost an hour to exactly this.
2005
-
2006
- `scripts/eve-server.sh` asserts the running process started *after* the build it
2007
- should be serving, and fails loudly when it did not. Use it rather than
2008
- `pkill` + `npx eve start` by hand. Related: `pkill -f <pattern>` over SSH kills
2009
- your own session when the pattern appears in the SSH command line — and can take
2010
- the agent down with it. Use the pidfile.
2011
-
2012
- Also: a long-lived channel session caches the compiled agent. After changing
2013
- capabilities, start a **fresh conversation** before deciding the change did not
2014
- work.
2015
-
2016
- **A restart script must do two more things, and both were learned from a
2017
- stranded user.**
2018
-
2019
- *Serialize restarts.* `@kybernesis/manage` fires one 20s after any change, and
2020
- you will also run one by hand. Two overlapping runs both finish killing before
2021
- either starts, and you end up with **two supervisors and two servers writing to
2022
- one durable store** — two executors racing over the same runs. That is not a
2023
- slow agent, it is a corrupt one. Take a `flock` at the top of the script, and
2024
- assert exactly one server process at the bottom.
2025
-
2026
- *Wait for in-flight turns.* eve does **not** resume a step killed mid-flight.
2027
- Restart into a live turn and that turn never emits another event, the session
2028
- never parks, and every later message queues behind a turn that will never
2029
- finish. The user watches a spinner forever, and no further restart fixes it,
2030
- because the session is stranded rather than stuck. Poll
2031
- `.eve/.workflow-data/runs/*.json` for a `turnWorkflow` in `running` state and
2032
- wait for it to clear — with a cap, so a wedged turn cannot block the restart
2033
- that would clear it.
2034
-
2035
- *And build before you restart.* Proving the process started after the BUILD says
2036
- nothing about whether the build reflects the SOURCE. A production agent ran for a
2037
- day on a build ten hours older than its files, reporting "OK: serving the current
2038
- build" every time. Worse, `@kybernesis/manage` calls the restart script after
2039
- writing files — so **every capability installed from Studio reported success and
2040
- changed nothing.** Build when the source has moved, and refuse to restart into a
2041
- build that failed.
2042
-
2043
- *And measure it correctly.* `pgrep -f 'server/index.mjs'` run over ssh matches
2044
- **the shell running the pgrep** — the pattern is in its own command line — so it
2045
- reports two servers when there is one. An entire investigation went into hunting
2046
- a phantom supervisor that a `ps -eo pid,ppid,args` would have dismissed in
2047
- thirty seconds. Same family as `pkill -f` killing its caller. Inside a script
2048
- file it is safe (the script's command line is `bash restart.sh`); typed at a
2049
- shell or sent over ssh it is not. When a process count surprises you, **list the
2050
- matches before believing the number.**
2051
-
2052
- The escape from an already-stranded session is a **session reset**
2053
- (`ClientSession.reset()`, or Reset in Studio's agent settings), which releases
2054
- the durable owner so the next message starts a fresh conversation. Cancelling
2055
- often does not help: the executor that would honour the cancel is the one that
2056
- died.
2057
-
2058
- ### 11.9 Credential checklist — collect ALL of these from the client
2059
-
2060
- Nothing here can be borrowed from another agent or another account.
2061
-
2062
- 1. **Host** — VM/server, plus the platform token if the agent provisions anything
2063
- 2. **Model source** — their LLM API key, gateway allocation, or connected subscription
2064
- 3. **Channel app** — their Slack app (bot + app token) / Photon project / bot token
2065
- 4. **Arcana** — workspaces + scoped `kb_` keys (one per brain, plus `-eval`)
2066
- 5. **Storage for deliverables** — their blob store, or a served host directory
2067
- 6. **Deploy target** — their Vercel token or hosting, if the agent ships sites
2068
- 7. **Sandbox credentials** — only if using `exeSandbox()` (see 11.6)
2069
- 8. **Control plane** — agent registered and the pilot cohort granted
2070
-
2071
- ### 11.10 Before calling it done
2072
-
2073
- `kyb doctor` green (or every warning consciously accepted), the eval suite green
2074
- against the client's `-eval` workspace, and a live turn on the real surface —
2075
- sent from the client's own device, not yours.
2076
-
2077
- ## 12. KYBER Studio — the desktop surface
2078
-
2079
- Slack and iMessage reach an agent where the client already works. KYBER Studio
2080
- is the third door: a desktop app for people who do not live in a chat tool, and
2081
- the only surface where an agent can work on the user's own files.
2082
-
2083
- Reach for it when the client says any of: *"not everyone here uses Slack"*,
2084
- *"I want it on my laptop"*, *"can it look at our repo"*, or when the pilot
2085
- involves someone technical who will hand the agent real work.
2086
-
2087
- ### 12.1 What it is
2088
-
2089
- - **The same agent.** Studio does not run anything. It talks to the agent you
2090
- deployed — same memory, same tools, same subagents. Nothing to deploy twice.
2091
- - **Governed by the same grants.** Sign-in is control-plane device flow, so
2092
- desktop access is the grant you already manage. Revoke it and the desktop goes
2093
- with it.
2094
- - **Optionally hands and eyes.** With `@kybernesis/local` the agent can search,
2095
- read, edit, write, and run commands on the user's machine, with consent.
2096
- - **Optionally self-modifying.** With `@kybernesis/manage` the client can
2097
- install capabilities and write routines from the app instead of asking you.
2098
-
2099
- ### 12.2 The two packages, and why they are separate
2100
-
2101
- | | What it lets happen | Installed on |
2102
- | --- | --- | --- |
2103
- | `@kybernesis/local` | The agent acts on the USER's machine | the agent |
2104
- | `@kybernesis/manage` | A client changes THE AGENT — deps and source | the agent |
2105
-
2106
- Different blast radius, so they are separate items an engagement chooses
2107
- independently. A reporting agent might want `local` and never `manage`. Neither
2108
- is installed by default, because both let a client reach further than chat does.
2109
-
2110
- ```bash
2111
- kyb init acme-agent --host=exe --studio # both, at scaffold time
2112
- npx eve add local # or either one, later
2113
- npx eve add manage
2114
- ```
2115
-
2116
- `kyb doctor` checks both: the relay secret for local, and `KYBERNESIS_AGENT` for
2117
- manage, since it cannot check a grant for a name it does not know.
2118
-
2119
- ### 12.3 Prerequisites, in order
2120
-
2121
- 1. **The agent is registered in the control plane** and the pilot users are
2122
- granted. Studio lists exactly what a user has a grant for — an agent that is
2123
- registered but ungranted is invisible, which is the correct behaviour and a
2124
- confusing one if you forget you did it.
2125
- 2. **The agent has a URL on file.** Studio reads `/api/me/agents`; an agent with
2126
- no deployment URL appears as unreachable rather than silently missing.
2127
- 3. **For `manage`: a writable working copy.** Installing edits the repo and
2128
- rebuilds, so it works on a VM and refuses on a read-only serverless bundle,
2129
- with that reason. Set `restartCommand` in `agent/channels/kyb.ts` or an
2130
- install completes without taking effect.
2131
- 4. **For `local`: nothing to configure.** Setup is one switch in Studio — the
2132
- agent's settings, *Work on this computer*. Behind it, Studio mints the
2133
- agent's credential from the control plane, installs it over the manage
2134
- channel, and records a standing grant for that machine; the agent restarts
2135
- once to load it. Never hand anyone a credential to paste into an env file.
2136
- The admin UI's "mint agent credential (shown once)" button remains for
2137
- recovery and is not the path: a setup step that asks someone to carry a
2138
- secret between two screens gets done wrong or skipped.
2139
-
2140
- ### 12.4 What consent looks like for the user
2141
-
2142
- Studio asks per **effect** — run a command, read a file, write a file, list a
2143
- directory — not per tool, and not per turn. Approving `read-file` once covers
2144
- every tool that reads a file out, which is why adding a tool later cannot dodge
2145
- a decision the user already made.
2146
-
2147
- The default is ask. A working folder can be set, but it is a starting directory
2148
- rather than a fence: permission to act on the machine is granted once, and the
2149
- agent may work wherever it is asked to. Whether it builds in its own sandbox or
2150
- on the user's files is decided by the ask, not by a mode — the same way a
2151
- colleague knows "build me a demo" from "look at my repo".
2152
-
2153
- ### 12.5 State this plainly to the client
2154
-
2155
- - **Two things gate a laptop, and they fail differently.** *Identity* is the
2156
- agent's signed credential — "the local-execution relay rejected my
2157
- credentials" means that. *Consent* is a standing per-device grant — "you have
2158
- not allowed this agent to work on this computer" means that. Neither alone
2159
- reaches anything. The grant is permanent on purpose: "always allow" means
2160
- always, from a chat window, a schedule, or a message sent from a phone, and it
2161
- ends on revoke, device removal, or disabling the agent.
2162
- - **Reaching a desktop is still not its own revocable capability.** "May talk to
2163
- this agent" and "may run commands on my laptop" remain one decision, taken
2164
- when the person allows the machine. Say so at a client who would treat it as a
2165
- surprise.
2166
- - **Installing the credential restarts the agent**, and a turn in flight during
2167
- that restart is lost for good — eve does not resume a step killed mid-flight.
2168
- It reads as a spinner that never resolves, often alongside a "credential is
2169
- unset" error from the process that was replaced. Send a new message, and reset
2170
- the conversation if the session itself is stranded. §11.8 is why a restart
2171
- script must wait for in-flight turns.
2172
- - **Reading a file sends it to the model.** Execution is local; the reasoning is
2173
- not. Fine for most work, and a conversation to have before a Studio points at
2174
- a regulated repository.
2175
- - **Management routes let a client change the agent.** That is the point, and it
2176
- means the repository is no longer only yours. Agree who reviews what Studio
2177
- writes — routines land as source files, so a normal review works.
2178
-
2179
- ### 12.7 Connectors — the apps library
2180
-
2181
- The Apps tab in Studio is a shelf of services a person connects in one click:
2182
- Gmail, Calendar, Drive, Slack, Notion, Linear, GitHub, Attio, Outlook, HubSpot.
2183
- Connecting one makes its tools appear in that person's next session.
2184
-
2185
- **Setup is one field, and it belongs to the client.** The org's own Composio key
2186
- goes in their control plane at **Settings → Connectors**, set by an owner, the
2187
- same way SSO is. It is never a deployment env var and never ours: each control
2188
- plane belongs to one company, and nobody's people connect their mailboxes under
2189
- another org's account. Direct them to composio.dev → Settings → API Keys.
2190
-
2191
- **What makes it one click** is that Composio has already registered the OAuth
2192
- app for each service. Without a broker, every client has to create a developer
2193
- app per provider — which is exactly the hour lost to Notion on the first
2194
- deployment, version pin and all.
2195
-
2196
- **Two things on every card, because both are load-bearing:**
2197
-
2198
- *Connects as you* versus *for the company*. A user-scoped connection cannot fire
2199
- from a schedule — a routine at 8am has no signed-in person. Anything a briefing
2200
- depends on must be the company's connection.
2201
-
2202
- *An admin must approve*. True for Slack, Notion, and Google Workspace. Say it on
2203
- the card; a client who discovers it at the end of a redirect chain reads the
2204
- product as broken.
2205
-
2206
- **How tools reach the agent.** `@kybernesis/connectors` mounts a dynamic
2207
- resolver in `agent/tools/connectors.ts`. It resolves per session from the
2208
- principal on the turn, asks the control plane what that person has connected,
2209
- and calls back through it to execute. The agent never holds the broker key — it
2210
- proves which agent it is with its own credential, and the control plane decides
2211
- whose account the call runs against.
2212
-
2213
- Resolution is per session, not per turn: a tool set is part of the prompt, and
2214
- rebuilding it every turn re-ingests the conversation at uncached prices. Pass
2215
- `perTurn: true` where people connect things mid-conversation and expect them to
2216
- work immediately.
2217
-
2218
- **Say this to the client.** Their Composio account holds refresh tokens for
2219
- their Google Workspace and Slack — a fourth party alongside the model provider,
2220
- the host, and us. Most will not blink; a regulated one will, and the answer for
2221
- them is `eve-connect`, native eve connections with no broker. That is why every
2222
- card carries a `provider`.
2223
-
2224
- **And watch the bill.** Composio prices per action. An agent in a loop is a very
2225
- different cost profile from a person clicking, and that belongs in the pricing
2226
- conversation before the first invoice, not after.
2227
-
2228
- ### 12.8 MCP servers — the client's own tools, local and remote
2229
-
2230
- The MCP tab is the escape hatch from the shelf: anything with an MCP server
2231
- becomes agent tools, whether it runs on the person's laptop or on a URL.
2232
-
2233
- **Local** — a command Studio runs on that machine (`npx -y @acme/mcp`, with env
2234
- vars if the server needs them). Studio keeps it alive, and the deployed agent
2235
- reaches it through the same relay as local execution. This is how a client's
2236
- internal tooling — the CLI nobody will ever expose to the internet — becomes
2237
- something the agent can use, without opening a port.
2238
-
2239
- **Remote** — a URL and optional headers. Studio runs the handshake before
2240
- saving, so a bad URL fails at the moment someone types it rather than in the
2241
- middle of a demo.
2242
-
2243
- Consent is **per server**, and approving one does not approve the next. The
2244
- discovery call (listing what a server offers) is exempt — otherwise a person is
2245
- asked to approve something before they can see what it is.
2246
-
2247
- The things that cost real sessions here:
2248
-
2249
- - **The command in a vendor's README is often the installer**, not the server.
2250
- Plaud's documented line runs an `install` subcommand and exits; the stdio
2251
- server is the bare command. If a server "connects" and never answers, check
2252
- that you are running the server.
2253
- - **A server declares its arguments and you must honour them.** Studio passes
2254
- the published `inputSchema` through to the model (`@kybernesis/local` ≥0.5.0).
2255
- Before that it did not, and watching the result is the best argument for the
2256
- fix: nine consecutive calls guessing the name of an argument the server had
2257
- documented, steered only by error strings.
2258
- - **Discovery must have a deadline.** These resolvers run before a turn and
2259
- reach across a network to a laptop that might be shut. Budgeted at 6s with a
2260
- five-minute cache; without that, one closed lid makes every turn hang.
2261
-
2262
- ## 13. Known gaps — state these plainly, do not sell around them
2263
-
2264
- Being straight about these is a feature. Clients have met vendors who were not.
2265
-
2266
- 1. **Slack access is workspace membership, not a grant.** Anyone in the client's Slack
2267
- workspace who can see the bot can talk to it. Control-plane grants govern the HTTP and
2268
- desktop doors, not the Slack door. The fix is a planned
2269
- `governedSlackChannel()` module in `@kybernesis/enterprise` plus `external_identity`
2270
- mapping (the schema exists; the module does not). Scope pilots to shared channels where
2271
- workspace membership is an acceptable boundary.
2272
-
2273
- 2. **HITL approvals are session-scoped, not person-scoped.** eve renders approval buttons
2274
- in the thread, and any thread member can click them. Do not gate destructive actions on
2275
- thread-visible approvals in a shared channel. Person-scoped approvals — only the
2276
- requester or a `manage`-grant holder may approve — are the planned governance half in
2277
- `@kybernesis/enterprise`.
2278
-
2279
- 3. **The desktop door is built** — KYBER Studio, signed and notarized, with device-flow
2280
- sign-in and in-app updates. What is NOT built is a second consent system talking to
2281
- the first: the control plane holds the standing per-device grant, Studio holds
2282
- per-effect permissions in a local file, and revoking in one does not revoke the
2283
- other. An off-boarding story that says "we revoke access centrally" must be qualified
2284
- at any client that asks the follow-up question.
2285
-
2286
- 4. **Off-boarding SLA equals the token TTL** (1h default) for already-minted sessions.
2287
- Suspension is immediate; revocation is not. Tune `IDENTITY_TOKEN_TTL_SECONDS` to the
2288
- client's appetite and tell them the number.
2289
-
2290
- 5. **Multiplayer is Slack-only in v1.** The `/discord` and `/whatsapp` subpaths are
2291
- reserved for the same core with thin adapters, but nothing is built. Also: one turn at
2292
- a time per session — simultaneous speakers resolve in arrival order, with mid-turn
2293
- messages folded into the next turn best-effort.
2294
-
2295
- 6. **Per-user OAuth and local-file work are BUILT** — §12.6 and §12.7, both proven end
2296
- to end. The remaining edge is the one that bites unattended: anything without a
2297
- signed-in person (a schedule, a subagent) has no user principal, so a user-scoped
2298
- connection is not available to it. A morning briefing built on someone's personal
2299
- Gmail connection does not fail loudly — it quietly has no tools. Company-scoped
2300
- connections are the answer, and that path has not yet been exercised in production.
2301
-
2302
- Two more, worth saying because a client will meet them:
2303
-
2304
- - **Tool volume is unmanaged.** Gmail and Calendar alone are 51 tool definitions in
2305
- every prompt. Real tokens per turn, and measurably worse tool selection as a client
2306
- connects more. Curation is designed, not shipped — connect what the pilot needs.
2307
- - **Local MCP servers are per-machine.** A person's second laptop silently has a
2308
- different set, and nothing in the UI says which machine a server is on.
2309
-
2310
- 7. **DM memory is per-workspace, not per-employee, unless you build it.** Splitting DMs
2311
- into one Arcana workspace per person needs a Slack-user-id → workspace-slug map in the
2312
- header resolver plus workspace provisioning. Doable; not shipped as a package.