@kybernesis/create 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/doctor.js
CHANGED
|
@@ -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 = {
|
|
@@ -120,6 +120,51 @@ export async function doctor() {
|
|
|
120
120
|
else
|
|
121
121
|
add("warn", "no VERCEL_OIDC_TOKEN — local sandbox/eval runs cannot reach Vercel Sandbox", "vercel link && vercel env pull");
|
|
122
122
|
}
|
|
123
|
+
// ── dispatch edges (agent-to-agent — checked only when present) ────────
|
|
124
|
+
const subagentsDir = join(cwd, "agent/subagents");
|
|
125
|
+
const edgeFiles = [];
|
|
126
|
+
if (existsSync(subagentsDir)) {
|
|
127
|
+
for (const entry of readdirSync(subagentsDir)) {
|
|
128
|
+
const flat = join(subagentsDir, entry);
|
|
129
|
+
const nested = join(subagentsDir, entry, "agent.ts");
|
|
130
|
+
const path = entry.endsWith(".ts") ? flat : existsSync(nested) ? nested : null;
|
|
131
|
+
if (!path)
|
|
132
|
+
continue;
|
|
133
|
+
const src = readFileSync(path, "utf8");
|
|
134
|
+
if (src.includes("remotePeer") || src.includes("defineRemoteAgent"))
|
|
135
|
+
edgeFiles.push(path);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const eveChannelPath = join(cwd, "agent/channels/eve.ts");
|
|
139
|
+
const eveChannelSrc = existsSync(eveChannelPath) ? readFileSync(eveChannelPath, "utf8") : null;
|
|
140
|
+
const hasDispatch = Boolean(deps["@kybernesis/dispatch"]) || edgeFiles.length > 0 ||
|
|
141
|
+
Boolean(eveChannelSrc && (eveChannelSrc.includes("dispatchChannel") || eveChannelSrc.includes("trustedForwarders")));
|
|
142
|
+
if (hasDispatch) {
|
|
143
|
+
for (const path of edgeFiles) {
|
|
144
|
+
const src = readFileSync(path, "utf8");
|
|
145
|
+
const name = path.split("/agent/subagents/")[1];
|
|
146
|
+
const envVar = /envVar:\s*"([A-Z0-9_]+)"/.exec(src)?.[1] ?? /process\.env\.([A-Z0-9_]+)/.exec(src)?.[1];
|
|
147
|
+
if (!envVar)
|
|
148
|
+
add("warn", `dispatch edge ${name}: no env-var URL found`, "use remotePeer({ envVar }) so the target is repointable");
|
|
149
|
+
else if (env[envVar])
|
|
150
|
+
add("pass", `dispatch edge ${name} → $${envVar} set locally`, "confirm it's also set on the Vercel project");
|
|
151
|
+
else
|
|
152
|
+
add("warn", `dispatch edge ${name}: $${envVar} unset locally`, `printf "<peer-url>" | vercel env add ${envVar} production (and vercel env pull)`);
|
|
153
|
+
if (src.includes("defineRemoteAgent") && !src.includes("forwardPrincipal"))
|
|
154
|
+
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");
|
|
155
|
+
}
|
|
156
|
+
if (edgeFiles.length > 0)
|
|
157
|
+
add("warn", "dispatch: verify BOTH ends run compatible eve versions", "an old receiver silently drops forwardPrincipal (runs as service identity)");
|
|
158
|
+
if (eveChannelSrc) {
|
|
159
|
+
if (/trustedForwarders:\s*(\(\s*\)|\([^)]*\))\s*=>\s*true/.test(eveChannelSrc))
|
|
160
|
+
add("fail", "eve channel: trustedForwarders is () => true", "any authenticated caller can assert any identity — enumerate peers (dispatchChannel)");
|
|
161
|
+
else if (eveChannelSrc.includes("dispatchChannel") || eveChannelSrc.includes("trustedForwarders"))
|
|
162
|
+
add("pass", "eve channel accepts forwarded principals from enumerated peers only");
|
|
163
|
+
}
|
|
164
|
+
else if (Boolean(deps["@kybernesis/dispatch"]) && edgeFiles.length === 0) {
|
|
165
|
+
add("warn", "@kybernesis/dispatch installed but no edges or dispatch channel found", "see the connect-agents skill");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
123
168
|
// ── eve discovery + local port ─────────────────────────────────────────
|
|
124
169
|
const info = capture("npx", ["eve", "info"], cwd);
|
|
125
170
|
if (info === null)
|
package/dist/upgrade.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -40,5 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=20"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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 → prefer the `<project>-<team>.vercel.app` form (survives redeploys).
|
|
22
|
+
4. Both repos need `@kybernesis/dispatch` installed (`npm i @kybernesis/dispatch`).
|
|
23
|
+
|
|
24
|
+
## Caller side — one file
|
|
25
|
+
|
|
26
|
+
`agent/subagents/<peer-name>.ts` (file name = tool name the model routes to):
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { remotePeer } from "@kybernesis/dispatch";
|
|
30
|
+
|
|
31
|
+
export default remotePeer({
|
|
32
|
+
envVar: "GTM_AGENT_URL",
|
|
33
|
+
description: "…", // see below — this is the whole routing story
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Write the description from the CALLEE's actual capabilities.** Read the peer
|
|
38
|
+
repo's `agent/instructions*`, subagent descriptions, and skills, then write the
|
|
39
|
+
concrete topics people ask about ("posting cadence, open GTM plays, outreach
|
|
40
|
+
targets, content drafting in the house voice") — not a generic blurb. If the
|
|
41
|
+
caller has local subagents with overlapping remits, differentiate explicitly or
|
|
42
|
+
routing will be ambiguous.
|
|
43
|
+
|
|
44
|
+
Set the env var on the caller's Vercel project:
|
|
45
|
+
`printf "<stable-prod-url>" | npx vercel env add GTM_AGENT_URL production`
|
|
46
|
+
|
|
47
|
+
## Receiver side — one file
|
|
48
|
+
|
|
49
|
+
`agent/channels/eve.ts` on the callee:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { dispatchChannel } from "@kybernesis/dispatch";
|
|
53
|
+
|
|
54
|
+
export default dispatchChannel({
|
|
55
|
+
trustedPeers: [{ teamSlug: "<caller-team>", projectName: "<caller-project>" }],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If the callee already has an authored `agent/channels/eve.ts` with app auth,
|
|
60
|
+
either migrate it to `dispatchChannel({ trustedPeers, extraAuth: […] })` or add
|
|
61
|
+
the peer by hand to BOTH the `vercelOidc({ subjects })` list and the
|
|
62
|
+
`trustedForwarders` predicate — they must never drift apart. Never write
|
|
63
|
+
`trustedForwarders: () => true`.
|
|
64
|
+
|
|
65
|
+
## Verify
|
|
66
|
+
|
|
67
|
+
1. `npx eve info` in both repos: 0 diagnostics; the caller's manifest gains a
|
|
68
|
+
`remoteAgents` entry (it does NOT appear in the local subagent count).
|
|
69
|
+
2. `npm run typecheck` both.
|
|
70
|
+
3. Deploy BOTH (`npx eve deploy` / git push per repo convention). The edge is
|
|
71
|
+
live only when both ends are.
|
|
72
|
+
4. Live test from the caller's real surface (e.g. Slack): ask something only
|
|
73
|
+
the peer knows. Confirm delegation in the caller's reply, then check
|
|
74
|
+
telemetry (PostHog): the peer-side turn should carry the human's
|
|
75
|
+
distinct_id, plus the `eve:forwarded-by` attribute naming the caller.
|
|
76
|
+
|
|
77
|
+
## Failure signatures
|
|
78
|
+
|
|
79
|
+
- **403 on dispatch** → receiver has no authored eve channel, or the caller
|
|
80
|
+
isn't in `trustedPeers`. Check team slug/project name spelling — a typo
|
|
81
|
+
silently rejects everything.
|
|
82
|
+
- **`principal_required` on the peer's user-scoped connections** → forwarding
|
|
83
|
+
isn't arriving: receiver predates forwarding, or the assertion was dropped.
|
|
84
|
+
- **Peer never gets called** → routing description too vague, or it collides
|
|
85
|
+
with a local subagent's remit. Rewrite from the callee's real capabilities.
|
|
86
|
+
- **Works locally, 401 in production** → caller's OIDC not accepted: the
|
|
87
|
+
receiver's `trustedPeers` names the wrong environment (default is
|
|
88
|
+
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
|
-
|
|
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
|