@kybernesis/create 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/doctor.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
4
4
  const MARK = {
@@ -45,10 +45,13 @@ export async function doctor() {
45
45
  if (env.ARCANA_API_KEY && env.ARCANA_COMPANY_WORKSPACE)
46
46
  arcanaPairs.push({ key: env.ARCANA_API_KEY, ws: env.ARCANA_COMPANY_WORKSPACE, label: "company brain" });
47
47
  if (env.ARCANA_EVAL_API_KEY) {
48
- const evalWs = /ARCANA_COMPANY_WORKSPACE=([a-z0-9-]+)-eval|([a-z0-9-]+)-eval/.exec(pkg.scripts?.eval ?? "");
49
- const ws = evalWs?.[1] ? `${evalWs[1]}-eval` : evalWs?.[2] ? `${evalWs[2]}-eval` : null;
50
- if (ws)
51
- arcanaPairs.push({ key: env.ARCANA_EVAL_API_KEY, ws, label: "eval workspace" });
48
+ // Match both `WORKSPACE=name-eval` and the shell-default form
49
+ // `WORKSPACE=${ARCANA_EVAL_WORKSPACE:-name-eval}`.
50
+ const script = pkg.scripts?.eval ?? "";
51
+ const evalWs = /=\$\{[A-Z0-9_]+:-([a-z0-9][a-z0-9-]*-eval)\}/.exec(script) ??
52
+ /=([a-z0-9][a-z0-9-]*-eval)\b/.exec(script);
53
+ if (evalWs?.[1])
54
+ arcanaPairs.push({ key: env.ARCANA_EVAL_API_KEY, ws: evalWs[1], label: "eval workspace" });
52
55
  }
53
56
  for (const [k, v] of Object.entries(env)) {
54
57
  const m = /^ARCANA_([A-Z0-9_]+)_API_KEY$/.exec(k);
@@ -120,6 +123,51 @@ export async function doctor() {
120
123
  else
121
124
  add("warn", "no VERCEL_OIDC_TOKEN — local sandbox/eval runs cannot reach Vercel Sandbox", "vercel link && vercel env pull");
122
125
  }
126
+ // ── dispatch edges (agent-to-agent — checked only when present) ────────
127
+ const subagentsDir = join(cwd, "agent/subagents");
128
+ const edgeFiles = [];
129
+ if (existsSync(subagentsDir)) {
130
+ for (const entry of readdirSync(subagentsDir)) {
131
+ const flat = join(subagentsDir, entry);
132
+ const nested = join(subagentsDir, entry, "agent.ts");
133
+ const path = entry.endsWith(".ts") ? flat : existsSync(nested) ? nested : null;
134
+ if (!path)
135
+ continue;
136
+ const src = readFileSync(path, "utf8");
137
+ if (src.includes("remotePeer") || src.includes("defineRemoteAgent"))
138
+ edgeFiles.push(path);
139
+ }
140
+ }
141
+ const eveChannelPath = join(cwd, "agent/channels/eve.ts");
142
+ const eveChannelSrc = existsSync(eveChannelPath) ? readFileSync(eveChannelPath, "utf8") : null;
143
+ const hasDispatch = Boolean(deps["@kybernesis/dispatch"]) || edgeFiles.length > 0 ||
144
+ Boolean(eveChannelSrc && (eveChannelSrc.includes("dispatchChannel") || eveChannelSrc.includes("trustedForwarders")));
145
+ if (hasDispatch) {
146
+ for (const path of edgeFiles) {
147
+ const src = readFileSync(path, "utf8");
148
+ const name = path.split("/agent/subagents/")[1];
149
+ const envVar = /envVar:\s*"([A-Z0-9_]+)"/.exec(src)?.[1] ?? /process\.env\.([A-Z0-9_]+)/.exec(src)?.[1];
150
+ if (!envVar)
151
+ add("warn", `dispatch edge ${name}: no env-var URL found`, "use remotePeer({ envVar }) so the target is repointable");
152
+ else if (env[envVar])
153
+ add("pass", `dispatch edge ${name} → $${envVar} set locally`, "confirm it's also set on the Vercel project");
154
+ else
155
+ add("warn", `dispatch edge ${name}: $${envVar} unset locally`, `printf "<peer-url>" | vercel env add ${envVar} production (and vercel env pull)`);
156
+ if (src.includes("defineRemoteAgent") && !src.includes("forwardPrincipal"))
157
+ add("warn", `dispatch edge ${name}: forwardPrincipal not set`, "peer will see this app's service identity, not the human — use remotePeer() for the safe defaults");
158
+ }
159
+ if (edgeFiles.length > 0)
160
+ add("warn", "dispatch: verify BOTH ends run compatible eve versions", "an old receiver silently drops forwardPrincipal (runs as service identity)");
161
+ if (eveChannelSrc) {
162
+ if (/trustedForwarders:\s*(\(\s*\)|\([^)]*\))\s*=>\s*true/.test(eveChannelSrc))
163
+ add("fail", "eve channel: trustedForwarders is () => true", "any authenticated caller can assert any identity — enumerate peers (dispatchChannel)");
164
+ else if (eveChannelSrc.includes("dispatchChannel") || eveChannelSrc.includes("trustedForwarders"))
165
+ add("pass", "eve channel accepts forwarded principals from enumerated peers only");
166
+ }
167
+ else if (Boolean(deps["@kybernesis/dispatch"]) && edgeFiles.length === 0) {
168
+ add("warn", "@kybernesis/dispatch installed but no edges or dispatch channel found", "see the connect-agents skill");
169
+ }
170
+ }
123
171
  // ── eve discovery + local port ─────────────────────────────────────────
124
172
  const info = capture("npx", ["eve", "info"], cwd);
125
173
  if (info === null)
package/dist/upgrade.js CHANGED
@@ -5,6 +5,8 @@ const PACKAGES = [
5
5
  "@kybernesis/arcana",
6
6
  "@kybernesis/enterprise",
7
7
  "@kybernesis/multiplayer",
8
+ "@kybernesis/engineer",
9
+ "@kybernesis/dispatch",
8
10
  "@kybernesis/evals",
9
11
  ];
10
12
  function versionLt(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -40,5 +40,8 @@
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=20"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
43
46
  }
44
47
  }
@@ -33,7 +33,23 @@ model under test.
33
33
  - Stale sandbox state (migration errors, re-baking templates):
34
34
  `rm -rf .eve/sandbox-cache .eve/dev-runtime` and rerun.
35
35
  - Don't pipe the eval command through `tail` in scripts — it masks the exit
36
- code.
36
+ code (and `| tail -N` on a backgrounded run destroys the per-eval detail —
37
+ `tee` to a file instead).
38
+ - **Heavy-model suites: `maxConcurrency: 1` locally.** At 2, long opus turns
39
+ overload the local world-queue transport (`Queue delivery failed … fetch
40
+ failed`); crashed deliveries REPLAY subagent steps, surfacing as
41
+ `lost continuationToken` races and phantom failures that move between runs.
42
+ The deployed runtime uses real queue infra — this is a local-harness limit.
43
+ - **AI Gateway budget is a silent eval killer**: Vercel applies a default
44
+ per-project budget (e.g. $10/daily); a suite of real opus turns can exhaust
45
+ it MID-RUN → `MODEL_CALL_FAILED` on whatever ran last. Check/raise:
46
+ `vercel ai-gateway budgets list` / `budgets set project <name> --limit 30
47
+ --refresh-period monthly`.
48
+ - **"run parked on N unanswered input request(s)"** = the agent called a
49
+ human-in-the-loop tool (`approval: status=pending tool=ask_question` in the
50
+ turn log) — no one answers in an eval. Usually a behavior finding: the
51
+ fixture was self-contained and the agent asked instead of acting. Fix the
52
+ agent's bias-to-act instructions, not the fixture.
37
53
 
38
54
  ## eve version certification
39
55
 
@@ -0,0 +1,92 @@
1
+ ---
2
+ description: Use when connecting two deployed eve agents so one can delegate to the other — "connect agent A to agent B", agent-to-agent communication, remote peers, cross-deployment delegation. Wires @kybernesis/dispatch edges end to end.
3
+ ---
4
+
5
+ # Connecting two eve agents (@kybernesis/dispatch)
6
+
7
+ An **edge** lets one deployed eve agent call another as if it were a local
8
+ subagent, with the human's identity carried across the hop. One edge covers a
9
+ full question-and-answer round trip (the caller parks until the peer's callback
10
+ returns). Wire the mirror-image edge only if the other agent should also be
11
+ able to *initiate*.
12
+
13
+ ## Before wiring — gather the facts
14
+
15
+ 1. **Both repos' eve versions must be compatible** (`node_modules/eve/package.json`
16
+ in each). An old receiver silently drops principal forwarding and runs as
17
+ service identity — no error. Upgrade both ends together first if they differ.
18
+ 2. **Vercel identities** of both projects: team slug + project name as shown in
19
+ `npx vercel ls <project>` (slugs, not `team_…`/`prj_…` IDs).
20
+ 3. **Stable production URL** of the callee: `npx vercel inspect <latest-prod-url>`
21
+ → Aliases — then **verify the alias is OPEN before wiring it**:
22
+ `curl -s -o /dev/null -w "%{http_code}" <url>/eve/v1/health` must return
23
+ **200**. The `<project>-<team>.vercel.app` aliases commonly sit behind
24
+ Vercel SSO deployment protection (302 → vercel.com/sso-api) and CANNOT
25
+ receive dispatches; the shorter production alias is usually the open one.
26
+ 4. Both repos need `@kybernesis/dispatch` installed (`npm i @kybernesis/dispatch`).
27
+
28
+ ## Caller side — one file
29
+
30
+ `agent/subagents/<peer-name>.ts` (file name = tool name the model routes to):
31
+
32
+ ```ts
33
+ import { remotePeer } from "@kybernesis/dispatch";
34
+
35
+ export default remotePeer({
36
+ envVar: "GTM_AGENT_URL",
37
+ description: "…", // see below — this is the whole routing story
38
+ });
39
+ ```
40
+
41
+ **Write the description from the CALLEE's actual capabilities.** Read the peer
42
+ repo's `agent/instructions*`, subagent descriptions, and skills, then write the
43
+ concrete topics people ask about ("posting cadence, open GTM plays, outreach
44
+ targets, content drafting in the house voice") — not a generic blurb. If the
45
+ caller has local subagents with overlapping remits, differentiate explicitly or
46
+ routing will be ambiguous.
47
+
48
+ Set the env var on the caller's Vercel project:
49
+ `printf "<stable-prod-url>" | npx vercel env add GTM_AGENT_URL production`
50
+
51
+ ## Receiver side — one file
52
+
53
+ `agent/channels/eve.ts` on the callee:
54
+
55
+ ```ts
56
+ import { dispatchChannel } from "@kybernesis/dispatch";
57
+
58
+ export default dispatchChannel({
59
+ trustedPeers: [{ teamSlug: "<caller-team>", projectName: "<caller-project>" }],
60
+ });
61
+ ```
62
+
63
+ If the callee already has an authored `agent/channels/eve.ts` with app auth,
64
+ either migrate it to `dispatchChannel({ trustedPeers, extraAuth: […] })` or add
65
+ the peer by hand to BOTH the `vercelOidc({ subjects })` list and the
66
+ `trustedForwarders` predicate — they must never drift apart. Never write
67
+ `trustedForwarders: () => true`.
68
+
69
+ ## Verify
70
+
71
+ 1. `npx eve info` in both repos: 0 diagnostics; the caller's manifest gains a
72
+ `remoteAgents` entry (it does NOT appear in the local subagent count).
73
+ 2. `npm run typecheck` both.
74
+ 3. Deploy BOTH (`npx eve deploy` / git push per repo convention). The edge is
75
+ live only when both ends are.
76
+ 4. Live test from the caller's real surface (e.g. Slack): ask something only
77
+ the peer knows. Confirm delegation in the caller's reply, then check
78
+ telemetry (PostHog): the peer-side turn should carry the human's
79
+ distinct_id, plus the `eve:forwarded-by` attribute naming the caller.
80
+
81
+ ## Failure signatures
82
+
83
+ - **403 on dispatch** → receiver has no authored eve channel, or the caller
84
+ isn't in `trustedPeers`. Check team slug/project name spelling — a typo
85
+ silently rejects everything.
86
+ - **`principal_required` on the peer's user-scoped connections** → forwarding
87
+ isn't arriving: receiver predates forwarding, or the assertion was dropped.
88
+ - **Peer never gets called** → routing description too vague, or it collides
89
+ with a local subagent's remit. Rewrite from the callee's real capabilities.
90
+ - **Works locally, 401 in production** → caller's OIDC not accepted: the
91
+ receiver's `trustedPeers` names the wrong environment (default is
92
+ production-only) or wrong project.
@@ -85,6 +85,18 @@ Inherit NOTHING. Own tools/skills/connections/instructions/sandbox; on eve
85
85
  into that subagent). No channels/schedules; no user principal; whole job must
86
86
  fit one delegation call. Docs: `docs/subagents.mdx`.
87
87
 
88
+ **Sandbox layout trap:** a FLAT `agent/sandbox.ts` is discovered but scopes to
89
+ the ROOT agent only — subagents silently fall back to the default backend
90
+ chain (Docker → microsandbox → just-bash), which surfaces as
91
+ `opening sandbox session "subagents/<id>" on backend "docker"` in eval logs.
92
+ Use the directory form `agent/sandbox/sandbox.ts` — that one is app-level and
93
+ subagents get it free. (Cost a debugging session on eve-gtm, 2026-08-07.)
94
+
95
+ **Parallel same-subagent delegation collides** (`Session … lost
96
+ continuationToken … to session …`, failed subagent-result actions): two
97
+ delegations to the SAME subagent fired in one step race on child sessions.
98
+ Instruct serial delegation ("one draft at a time, wait for each result").
99
+
88
100
  ## Test in eve dev
89
101
 
90
102
  `npx eve dev` boots the local runtime + chat TUI. Walk: identity → skill
@@ -866,6 +866,43 @@ diff before running the agent**, same as any dependency. For client work,
866
866
  prefer authoring the client's own procedures; pull from skills.sh for generic
867
867
  craft (framework best practices, review checklists) after review.
868
868
 
869
+ ### 4.3f Install `@kybernesis/dispatch` (optional — when the client runs MORE THAN ONE agent)
870
+
871
+ When the client has (or grows into) a second deployed agent — an ops agent
872
+ next to the company assistant, a specialist per business unit — they will ask
873
+ for the agents to talk to each other. Dispatch is the governed way: one
874
+ declared **edge** per direction, human identity carried across the hop.
875
+
876
+ The concept in one breath: the caller mounts the peer as a remote subagent
877
+ (`remotePeer` under `agent/subagents/` — eve's `defineRemoteAgent` underneath,
878
+ durable park→callback dispatch, so a reply comes back on the SAME edge); the
879
+ receiver authors `agent/channels/eve.ts` with `dispatchChannel({ trustedPeers })`,
880
+ which feeds one peer list into BOTH the OIDC subjects allowlist and
881
+ `trustedForwarders`. Forwarding is on by default: the receiving agent runs as
882
+ the human who asked, so Arcana scoping, per-user connections, and PostHog
883
+ attribution compose across the hop unchanged (`eve:forwarded-by` records the
884
+ edge for audit).
885
+
886
+ **Don't hand-wire it — use the `connect-agents` Claude Code skill** (in the
887
+ seeded `.claude/skills/`): tell Claude "connect <agent A> to <agent B>" and it
888
+ reads both repos, writes the edge with a routing description derived from the
889
+ callee's REAL capabilities, sets the URL env var, and walks the deploy+verify
890
+ steps. `kyb doctor` then checks the edges (env var set, no `() => true`
891
+ trust, forwardPrincipal present).
892
+
893
+ Client-conversation rules of thumb:
894
+
895
+ - One edge = ask-and-answer in one direction. Mirror-image edge only if the
896
+ other agent should also INITIATE. Quote them separately.
897
+ - **Both ends must run compatible eve versions** — an old receiver silently
898
+ drops principal forwarding and runs the session as the calling app's
899
+ service identity. Upgrade edges as a unit (`kyb upgrade` both repos).
900
+ - Peers are pinned to production deployments of named Vercel projects.
901
+ Previews never get trust implicitly. The client's Vercel team is still the
902
+ outer boundary, same as §4.3b.
903
+ - Cross-ORG edges (client agent ↔ another company's agent) are a different
904
+ product conversation — purpose-scoped grants, §2.5 disclosures. Don't wire
905
+ one as if it were internal.
869
906
 
870
907
  ### 4.4 Author the agent's identity and instructions
871
908
 
@@ -1009,6 +1046,44 @@ result to a configured Slack user. Note that DMing a user from a schedule needs
1009
1046
  `im:write` scope on the Slack connector — add it during Phase 5 or the first run fails
1010
1047
  silently at the last step.
1011
1048
 
1049
+ ### 4.6b Observability — evlog → PostHog (the Operate-phase deliverable)
1050
+
1051
+ One hook file gives the agent per-turn structured telemetry — who talked,
1052
+ which tools/subagents fired, timings, token usage, outcome — with message
1053
+ text redacted and tool-failure turns always kept:
1054
+
1055
+ ```ts
1056
+ // agent/hooks/evlog.ts
1057
+ import { defineEvlogHook } from "evlog/eve";
1058
+ import { createPostHogDrain } from "evlog/posthog";
1059
+
1060
+ export default defineEvlogHook({
1061
+ init: { env: { service: "acme-atlas" } },
1062
+ // mode "events" is REQUIRED for dashboards: the default "logs" mode
1063
+ // ships OTLP to the separate PostHog Logs product — invisible to
1064
+ // Activity/insights, and it looks exactly like "no events arriving".
1065
+ drain: createPostHogDrain({ mode: "events" }),
1066
+ redactMessage: true,
1067
+ });
1068
+ ```
1069
+
1070
+ Env: `POSTHOG_API_KEY` = the **project** key (`phc_…`, ingestion-only — a
1071
+ `phx_…` personal key is account-privileged and wrong here). The default host
1072
+ is `https://us.i.posthog.com`; EU-hosted projects need
1073
+ `POSTHOG_HOST=https://eu.i.posthog.com` or events silently vanish. To verify
1074
+ region + key in one shot, curl a test event at each region's `/batch/` and
1075
+ see which appears in Activity. Turns then land as `evlog_wide_event` — build
1076
+ the starter insights on its properties: turns/day by surface, tool failure
1077
+ rate, delegation mix, p50/p95 duration.
1078
+
1079
+ **Person attribution (optional — a DISCLOSURE item, §2.5):** wide events carry no
1080
+ userId by default, so PostHog sees one anonymous actor named after the service. To
1081
+ attribute turns to the verified speaker, add a sibling hook that stamps the
1082
+ per-message-authenticated principal via evlog's `useLogger` — on `step.started`, not
1083
+ `turn.started`, so evlog's turn state exists regardless of hook ordering (crib
1084
+ `~/kyber/agent/hooks/attribution.ts`). Then a one-time $identify per person maps ids
1085
+ to names. Per-employee telemetry must be a deliberate, disclosed choice at a client.
1086
+
1012
1087
  ### 4.7 Environment variables
1013
1088
 
1014
1089
  Two places must agree: `.env.local` for local development, and the Vercel project's
@@ -1,10 +1,10 @@
1
1
  ---
2
- description: Use when installing, configuring, or debugging any @kybernesis package — arcana (memory), enterprise (governance), multiplayer (Slack), engineer (build+ship), evals (QA), create (kyb CLI) — or the Kybernesis registry. Includes every production-learned gotcha.
2
+ description: Use when installing, configuring, or debugging any @kybernesis package — arcana (memory), enterprise (governance), multiplayer (Slack), engineer (build+ship), dispatch (agent-to-agent), evals (QA), create (kyb CLI) — or the Kybernesis registry. Includes every production-learned gotcha.
3
3
  ---
4
4
 
5
5
  # The Kybernesis packages
6
6
 
7
- Six packages, npm-public under `@kybernesis`, Apache-2.0, monorepo
7
+ Seven packages, npm-public under `@kybernesis`, Apache-2.0, monorepo
8
8
  `KybernesisAI/platform`. Registry: `https://registry.kybernesis.ai`
9
9
  (`eve registry add @kybernesis=https://registry.kybernesis.ai/r/{name}.json`,
10
10
  then `eve add @kybernesis/<item>`). Each covers one axis:
@@ -38,6 +38,17 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
38
38
  domain allowlist = the client's security posture. Ship loop: preview deploys
39
39
  via the Vercel MCP connection (inline file tree, no git needed, no token in
40
40
  the VM); production promotion is ALWAYS human-approved.
41
+ - **dispatch** — agent-to-agent. `remotePeer({ envVar, description })` under
42
+ `agent/subagents/` = a separately DEPLOYED eve agent as a callable peer
43
+ (eve's `defineRemoteAgent` underneath: durable park→callback dispatch);
44
+ `dispatchChannel({ trustedPeers, extraAuth? })` as `agent/channels/eve.ts` =
45
+ the receiver, one declaration feeding BOTH the OIDC subjects allowlist and
46
+ `trustedForwarders`. Principal forwarding ON by default — the peer runs as
47
+ the human who asked. `() => true` trust is not expressible. Peers are
48
+ production-environment by default. BOTH ends must run compatible eve
49
+ versions (old receivers silently drop forwarding → service identity).
50
+ Composes with enterprise via `extraAuth: [kybernesisAuth(...)]`. See the
51
+ `connect-agents` skill for the end-to-end wiring flow.
41
52
  - **evals** — QA. `kybernesisBaseline({ agentDisplayName, routing,
42
53
  engineer? })` = smoke + 5 memory + routing per dept + optional vision-loop
43
54
  eval. Judge model ≠ model under test. Hermetic runs force all workspaces to