@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.
- package/dist/cli.js +15 -0
- package/dist/deploy.d.ts +4 -0
- package/dist/deploy.js +169 -0
- package/dist/register.d.ts +5 -0
- package/dist/register.js +110 -0
- package/dist/skills.js +13 -1
- package/package.json +1 -1
- package/skills/fde-engagement/references/playbook.md +11 -0
- package/skills/kybernesis-packages/SKILL.md +7 -3
- package/skills/self-hosting/SKILL.md +13 -3
- package/skills/.claude/skills/certification/SKILL.md +0 -75
- package/skills/.claude/skills/connect-agents/SKILL.md +0 -119
- package/skills/.claude/skills/control-plane/SKILL.md +0 -80
- package/skills/.claude/skills/eve-building/SKILL.md +0 -106
- package/skills/.claude/skills/fde-engagement/SKILL.md +0 -35
- package/skills/.claude/skills/fde-engagement/references/playbook.md +0 -2312
- package/skills/.claude/skills/kybernesis-packages/SKILL.md +0 -148
- package/skills/.claude/skills/self-hosting/SKILL.md +0 -206
- package/skills/.claude/skills/source-of-truth/SKILL.md +0 -60
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* self-testing eve agent (also: npm create @kybernesis)
|
|
7
7
|
* kyb doctor preflight an agent project: keys, issuer, envs, discovery
|
|
8
8
|
* kyb skills [--global] install/refresh the FDE Claude Code skill suite
|
|
9
|
+
* kyb register register this agent with the control plane (device flow)
|
|
10
|
+
* kyb deploy put this repo on its host and restart it, with proof
|
|
9
11
|
* kyb upgrade bump @kybernesis/* to latest, gated on the eval suite
|
|
10
12
|
* --skip-eval skip the eval gate (not for production changes)
|
|
11
13
|
*/
|
|
@@ -14,6 +16,8 @@ import { init } from "./init.js";
|
|
|
14
16
|
import { doctor } from "./doctor.js";
|
|
15
17
|
import { upgrade } from "./upgrade.js";
|
|
16
18
|
import { installSkills } from "./skills.js";
|
|
19
|
+
import { deploy } from "./deploy.js";
|
|
20
|
+
import { register } from "./register.js";
|
|
17
21
|
function flag(rest, key) {
|
|
18
22
|
const hit = rest.find((a) => a.startsWith(`--${key}=`));
|
|
19
23
|
return hit ? hit.slice(key.length + 3) : undefined;
|
|
@@ -40,6 +44,12 @@ switch (command) {
|
|
|
40
44
|
case "skills":
|
|
41
45
|
installSkills({ global: rest.includes("--global") });
|
|
42
46
|
break;
|
|
47
|
+
case "register":
|
|
48
|
+
await register({ name: flag(rest, "name"), url: flag(rest, "url") });
|
|
49
|
+
break;
|
|
50
|
+
case "deploy":
|
|
51
|
+
await deploy({ host: flag(rest, "host") });
|
|
52
|
+
break;
|
|
43
53
|
case "upgrade":
|
|
44
54
|
await upgrade(rest.includes("--skip-eval"));
|
|
45
55
|
break;
|
|
@@ -65,6 +75,11 @@ ${bold("kyb")} — Kybernesis agent scaffolder & FDE toolkit
|
|
|
65
75
|
${bold("kyb doctor")} preflight checks (keys, issuer, envs, discovery)
|
|
66
76
|
${bold("kyb skills")} install/refresh the FDE skill suite for Claude Code
|
|
67
77
|
--global ${dim("install to ~/.claude/skills instead of this repo")}
|
|
78
|
+
${bold("kyb register")} register this agent with the control plane
|
|
79
|
+
--name=<name> ${dim("defaults to KYBERNESIS_AGENT in .env.local")}
|
|
80
|
+
--url=<url> ${dim("defaults to https://$EXE_VM_NAME.exe.xyz")}
|
|
81
|
+
${bold("kyb deploy")} copy to the host, install, restart, prove it took
|
|
82
|
+
--host=<target> ${dim("ssh target; defaults to $EXE_VM_NAME.exe.xyz")}
|
|
68
83
|
${bold("kyb upgrade")} bump @kybernesis/* packages, gated on evals
|
|
69
84
|
--skip-eval ${dim("skip the eval gate")}
|
|
70
85
|
|
package/dist/deploy.d.ts
ADDED
package/dist/deploy.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { bold, dim, green, red, yellow } from "./util.js";
|
|
5
|
+
/**
|
|
6
|
+
* `kyb deploy` — put this repo on its host and restart it, with proof.
|
|
7
|
+
*
|
|
8
|
+
* On Vercel this is `eve deploy` and always was. Off Vercel it was a paragraph
|
|
9
|
+
* in a skill telling people to rsync and then run a script, which is the sort
|
|
10
|
+
* of step that gets done differently by each person doing it — and the
|
|
11
|
+
* differences are exactly where the outages came from.
|
|
12
|
+
*/
|
|
13
|
+
function env(dir) {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const file of [".env.local", ".env"]) {
|
|
16
|
+
const p = join(dir, file);
|
|
17
|
+
if (!existsSync(p))
|
|
18
|
+
continue;
|
|
19
|
+
for (const line of readFileSync(p, "utf8").split("\n")) {
|
|
20
|
+
const m = /^\s*([A-Z0-9_]+)\s*=\s*(.*)$/.exec(line);
|
|
21
|
+
if (m?.[1] && out[m[1]] === undefined)
|
|
22
|
+
out[m[1]] = (m[2] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function hostOf(dir) {
|
|
28
|
+
const pkg = join(dir, "package.json");
|
|
29
|
+
const deps = existsSync(pkg)
|
|
30
|
+
? (JSON.parse(readFileSync(pkg, "utf8")).dependencies ?? {})
|
|
31
|
+
: {};
|
|
32
|
+
return deps["@kybernesis/exe"] ? "exe" : "vercel";
|
|
33
|
+
}
|
|
34
|
+
/** The ssh target: an explicit flag, then EVE_SSH_HOST, then the exe VM name. */
|
|
35
|
+
function sshTarget(dir, explicit) {
|
|
36
|
+
const e = env(dir);
|
|
37
|
+
if (explicit)
|
|
38
|
+
return explicit;
|
|
39
|
+
if (e.EVE_SSH_HOST)
|
|
40
|
+
return e.EVE_SSH_HOST;
|
|
41
|
+
if (e.EXE_VM_NAME)
|
|
42
|
+
return `${e.EXE_VM_NAME}.exe.xyz`;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export async function deploy(options) {
|
|
46
|
+
const dir = options.dir ?? process.cwd();
|
|
47
|
+
if (!existsSync(join(dir, "agent"))) {
|
|
48
|
+
console.log(red("Not an eve agent project (no agent/ directory)."));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(bold("kyb deploy"));
|
|
53
|
+
if (hostOf(dir) === "vercel") {
|
|
54
|
+
console.log(dim(" host: vercel — handing over to eve deploy\n"));
|
|
55
|
+
const r = spawnSync("npx", ["eve", "deploy"], { cwd: dir, stdio: "inherit" });
|
|
56
|
+
process.exitCode = r.status ?? 0;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const target = sshTarget(dir, options.host);
|
|
60
|
+
if (!target) {
|
|
61
|
+
console.log(red(" No host to deploy to."));
|
|
62
|
+
console.log(dim(" Set EXE_VM_NAME (or EVE_SSH_HOST) in .env.local, or pass --host=<ssh target>."));
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const name = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).name ?? "agent";
|
|
67
|
+
const remote = `~/${name}`;
|
|
68
|
+
console.log(dim(` host: ${target} path: ${remote}\n`));
|
|
69
|
+
const run = (cmd, args) => {
|
|
70
|
+
const r = spawnSync(cmd, args, { cwd: dir, stdio: "inherit" });
|
|
71
|
+
return (r.status ?? 1) === 0;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Never copy node_modules, .eve, or a build.
|
|
75
|
+
*
|
|
76
|
+
* node_modules is platform-specific and native modules built on a laptop do
|
|
77
|
+
* not run on the host. `.eve` is the durable store — conversations, turn
|
|
78
|
+
* history, the workflow queue — and overwriting it with a local copy is how
|
|
79
|
+
* a deployment eats its own production state.
|
|
80
|
+
*/
|
|
81
|
+
console.log(bold("1/3 Copying source …"));
|
|
82
|
+
const ok = run("rsync", [
|
|
83
|
+
"-az",
|
|
84
|
+
"--delete",
|
|
85
|
+
"--exclude",
|
|
86
|
+
"node_modules",
|
|
87
|
+
"--exclude",
|
|
88
|
+
".eve",
|
|
89
|
+
"--exclude",
|
|
90
|
+
".output",
|
|
91
|
+
"--exclude",
|
|
92
|
+
".git",
|
|
93
|
+
"--exclude",
|
|
94
|
+
".env.local",
|
|
95
|
+
`${dir}/`,
|
|
96
|
+
`${target}:${remote}/`,
|
|
97
|
+
]);
|
|
98
|
+
if (!ok) {
|
|
99
|
+
console.log(red(" rsync failed — is the host reachable, and is the path writable?"));
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log(bold("\n2/3 Installing dependencies on the host …"));
|
|
104
|
+
if (!run("ssh", [target, `cd ${remote} && npm install --no-audit --no-fund`])) {
|
|
105
|
+
console.log(red(" npm install failed on the host."));
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Detached, always.
|
|
111
|
+
*
|
|
112
|
+
* `ssh host "script"` sends SIGHUP when the connection ends, which kills the
|
|
113
|
+
* restart halfway — leaving exactly the half-restarted state the script
|
|
114
|
+
* exists to prevent. setsid + nohup + no stdin is the difference between a
|
|
115
|
+
* deploy and an outage.
|
|
116
|
+
*/
|
|
117
|
+
console.log(bold("\n3/3 Restarting (detached) …"));
|
|
118
|
+
/**
|
|
119
|
+
* Find the restart script rather than assuming its name.
|
|
120
|
+
*
|
|
121
|
+
* `kyb init` installs scripts/eve-server.sh, but agents predating that have
|
|
122
|
+
* their own — and a deploy that fails on a naming difference is a deploy
|
|
123
|
+
* people stop using. Falls back to installing the packaged script, so a
|
|
124
|
+
* project that has none ends up with the hardened one rather than an error.
|
|
125
|
+
*/
|
|
126
|
+
const remoteScript = [
|
|
127
|
+
'SCRIPT=""',
|
|
128
|
+
// A loop, not a chain of `[ -f x ] && echo x || …` — that keeps evaluating
|
|
129
|
+
// after the first hit and yields every match, so SCRIPT becomes three
|
|
130
|
+
// filenames and `bash "$SCRIPT"` fails on a name nothing has.
|
|
131
|
+
"for f in scripts/eve-server.sh scripts/restart.sh eve-server.sh restart.sh; do",
|
|
132
|
+
' if [ -f "$f" ]; then SCRIPT="$f"; break; fi',
|
|
133
|
+
"done",
|
|
134
|
+
'if [ -z "$SCRIPT" ] && [ -f node_modules/@kybernesis/exe/scripts/eve-server.sh ]; then',
|
|
135
|
+
" mkdir -p scripts",
|
|
136
|
+
" cp node_modules/@kybernesis/exe/scripts/eve-server.sh scripts/",
|
|
137
|
+
" chmod +x scripts/eve-server.sh",
|
|
138
|
+
' SCRIPT="scripts/eve-server.sh"',
|
|
139
|
+
"fi",
|
|
140
|
+
'if [ -z "$SCRIPT" ]; then echo "FAILED: no restart script on the host"; exit 1; fi',
|
|
141
|
+
'echo "using $SCRIPT"',
|
|
142
|
+
'setsid nohup bash "$SCRIPT" > /tmp/kyb-deploy.log 2>&1 < /dev/null &',
|
|
143
|
+
"sleep 2",
|
|
144
|
+
"echo started",
|
|
145
|
+
].join("\n");
|
|
146
|
+
run("ssh", [target, `cd ${remote}\n${remoteScript}`]);
|
|
147
|
+
console.log(dim("\n Waiting for the restart to report …"));
|
|
148
|
+
let last = "";
|
|
149
|
+
for (let i = 0; i < 40; i++) {
|
|
150
|
+
const out = execFileSync("ssh", [target, `tail -6 /tmp/kyb-deploy.log 2>/dev/null || true`], {
|
|
151
|
+
encoding: "utf8",
|
|
152
|
+
});
|
|
153
|
+
last = out;
|
|
154
|
+
if (/health:|FAILED/.test(out))
|
|
155
|
+
break;
|
|
156
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
157
|
+
}
|
|
158
|
+
const healthy = /health:\s*200/.test(last);
|
|
159
|
+
console.log(last
|
|
160
|
+
.split("\n")
|
|
161
|
+
.filter((l) => /pid=|build:|OK:|health:|FAILED|SOURCE IS NEWER|^built/.test(l))
|
|
162
|
+
.map((l) => ` ${l}`)
|
|
163
|
+
.join("\n"));
|
|
164
|
+
console.log(healthy
|
|
165
|
+
? green("\n ✓ deployed and serving the current build")
|
|
166
|
+
: yellow("\n ! the restart did not report health — check /tmp/kyb-deploy.log on the host"));
|
|
167
|
+
if (!healthy)
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
}
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { bold, dim, green, red, yellow } from "./util.js";
|
|
4
|
+
function envOf(dir) {
|
|
5
|
+
const out = {};
|
|
6
|
+
for (const file of [".env.local", ".env"]) {
|
|
7
|
+
const p = join(dir, file);
|
|
8
|
+
if (!existsSync(p))
|
|
9
|
+
continue;
|
|
10
|
+
for (const line of readFileSync(p, "utf8").split("\n")) {
|
|
11
|
+
const m = /^\s*([A-Z0-9_]+)\s*=\s*(.*)$/.exec(line);
|
|
12
|
+
if (m?.[1] && out[m[1]] === undefined)
|
|
13
|
+
out[m[1]] = (m[2] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
/** Sign in with the device flow and return an identity token. */
|
|
19
|
+
async function signIn(issuer) {
|
|
20
|
+
const started = await fetch(`${issuer}/api/oauth/device`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "content-type": "application/json" },
|
|
23
|
+
body: JSON.stringify({ deviceId: `kyb-cli-${process.pid}`, deviceLabel: "kyb CLI" }),
|
|
24
|
+
}).catch(() => null);
|
|
25
|
+
if (!started?.ok) {
|
|
26
|
+
console.log(red(` Could not start sign-in at ${issuer}.`));
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const body = (await started.json());
|
|
30
|
+
const url = String(body.verification_uri_complete ?? body.verification_uri ?? "");
|
|
31
|
+
const code = String(body.user_code ?? "");
|
|
32
|
+
const deviceCode = String(body.device_code ?? "");
|
|
33
|
+
let interval = Number(body.interval ?? 5) * 1000;
|
|
34
|
+
const deadline = Date.now() + Number(body.expires_in ?? 600) * 1000;
|
|
35
|
+
console.log(`\n Approve this in your browser:\n`);
|
|
36
|
+
console.log(` ${bold(url)}`);
|
|
37
|
+
if (code)
|
|
38
|
+
console.log(` code: ${bold(code)}\n`);
|
|
39
|
+
while (Date.now() < deadline) {
|
|
40
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
41
|
+
const res = await fetch(`${issuer}/api/oauth/token`, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "content-type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
45
|
+
}).catch(() => null);
|
|
46
|
+
if (!res)
|
|
47
|
+
continue;
|
|
48
|
+
const out = (await res.json().catch(() => ({})));
|
|
49
|
+
if (res.ok && typeof out.token === "string")
|
|
50
|
+
return out.token;
|
|
51
|
+
if (out.error === "slow_down")
|
|
52
|
+
interval += 5000;
|
|
53
|
+
// authorization_pending is the normal case; anything else is fatal.
|
|
54
|
+
if (out.error && out.error !== "authorization_pending" && out.error !== "slow_down") {
|
|
55
|
+
console.log(red(` Sign-in failed: ${String(out.error)}`));
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
console.log(red(" Sign-in timed out."));
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
export async function register(options) {
|
|
63
|
+
const dir = options.dir ?? process.cwd();
|
|
64
|
+
const env = envOf(dir);
|
|
65
|
+
const issuer = (env.KYBERNESIS_ISSUER || "https://agent.kybernesis.ai").replace(/\/$/, "");
|
|
66
|
+
// The registered name must equal KYBERNESIS_AGENT exactly — it is what the
|
|
67
|
+
// agent checks grants against, and a mismatch is a 403 with no clue in it.
|
|
68
|
+
const name = options.name ?? env.KYBERNESIS_AGENT;
|
|
69
|
+
const url = options.url ??
|
|
70
|
+
env.EVE_PUBLIC_URL ??
|
|
71
|
+
(env.EXE_VM_NAME ? `https://${env.EXE_VM_NAME}.exe.xyz` : undefined);
|
|
72
|
+
console.log(bold("kyb register"));
|
|
73
|
+
console.log(dim(` issuer: ${issuer}`));
|
|
74
|
+
if (!name) {
|
|
75
|
+
console.log(red(" No agent name. Set KYBERNESIS_AGENT in .env.local, or pass --name=<name>."));
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!url) {
|
|
80
|
+
console.log(red(" No deployment URL. Set EXE_VM_NAME in .env.local, or pass --url=<https://…>."));
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(dim(` agent: ${name}\n url: ${url}`));
|
|
85
|
+
const token = await signIn(issuer);
|
|
86
|
+
if (!token) {
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const res = await fetch(`${issuer}/api/agents/register`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
93
|
+
body: JSON.stringify({ name, url }),
|
|
94
|
+
}).catch(() => null);
|
|
95
|
+
if (!res?.ok) {
|
|
96
|
+
const detail = res ? (await res.json().catch(() => ({}))).error : "unreachable";
|
|
97
|
+
console.log(red(`\n Registration failed: ${detail ?? res?.status}`));
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const out = (await res.json());
|
|
102
|
+
console.log(green(out.created
|
|
103
|
+
? `\n ✓ registered "${name}" and granted you access`
|
|
104
|
+
: `\n ✓ "${name}" already existed — its URL now points at ${url}`));
|
|
105
|
+
console.log(dim(" Grant others in the admin; they do not inherit yours."));
|
|
106
|
+
if (!env.KYBERNESIS_AGENT_CREDENTIAL) {
|
|
107
|
+
console.log(yellow("\n ! This agent has no credential yet. Turn on 'Work on this computer' in\n" +
|
|
108
|
+
" KYBER Studio to mint and install one — do not paste a credential by hand."));
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { bold, dim, green } from "./util.js";
|
|
6
6
|
/** The skill suite shipped inside this package (skills/ beside dist/). */
|
|
@@ -26,6 +26,18 @@ export function installSkills(opts = {}) {
|
|
|
26
26
|
const target = opts.global
|
|
27
27
|
? join(homedir(), ".claude", "skills")
|
|
28
28
|
: join(process.cwd(), ".claude", "skills");
|
|
29
|
+
/**
|
|
30
|
+
* Never install the suite into itself.
|
|
31
|
+
*
|
|
32
|
+
* Run from inside the package's own skills/ directory, this copies the suite
|
|
33
|
+
* to skills/.claude/skills — which then ships inside the published tarball,
|
|
34
|
+
* so every consumer installs a duplicate suite nested one level down. That
|
|
35
|
+
* happened, got committed, and was one `npm publish` from being everyone's.
|
|
36
|
+
*/
|
|
37
|
+
if (resolve(target).startsWith(resolve(src))) {
|
|
38
|
+
console.error("Refusing to install the suite into itself — run kyb skills from an agent repo, not from the package.");
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
29
41
|
mkdirSync(target, { recursive: true });
|
|
30
42
|
const names = readdirSync(src).filter((n) => !n.startsWith("."));
|
|
31
43
|
for (const name of names) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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",
|
|
@@ -1804,6 +1804,17 @@ keys of your own.
|
|
|
1804
1804
|
|
|
1805
1805
|
### 11.1 Scaffold
|
|
1806
1806
|
|
|
1807
|
+
The whole path is commands now — there is no step where an FDE is expected to
|
|
1808
|
+
improvise a deploy:
|
|
1809
|
+
|
|
1810
|
+
```bash
|
|
1811
|
+
kyb init <name> --host=exe --channel=none --engineer --studio
|
|
1812
|
+
kyb register # device flow; grants you; idempotent by name
|
|
1813
|
+
kyb deploy # copy + install + restart + prove
|
|
1814
|
+
kyb doctor && npm run eval
|
|
1815
|
+
```
|
|
1816
|
+
|
|
1817
|
+
|
|
1807
1818
|
```bash
|
|
1808
1819
|
kyb init <name> --host=exe --channel=<imessage|slack|telegram|none> --engineer
|
|
1809
1820
|
cd <name> && kyb doctor
|
|
@@ -86,9 +86,13 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
|
|
|
86
86
|
engineer? })` = smoke + 5 memory + routing per dept + optional vision-loop
|
|
87
87
|
eval. Judge model ≠ model under test. Hermetic runs force all workspaces to
|
|
88
88
|
`<name>-eval` via the npm script.
|
|
89
|
-
- **create** — the `kyb` CLI
|
|
90
|
-
(
|
|
91
|
-
`
|
|
89
|
+
- **create** — the `kyb` CLI, and the whole lifecycle of an agent:
|
|
90
|
+
`init` (scaffold; `--host=exe` also installs the hardened restart script),
|
|
91
|
+
`register` (control plane, via device flow — no admin session, no pasted
|
|
92
|
+
token, grants the person who ran it, idempotent by name),
|
|
93
|
+
`deploy` (copy + install + restart + PROVE it; `eve deploy` on Vercel),
|
|
94
|
+
`doctor`, `upgrade` (carries eve to the Kybernesis-CERTIFIED pin, never
|
|
95
|
+
blind latest), `skills`. Ships THIS skill suite.
|
|
92
96
|
|
|
93
97
|
## Gotchas that each cost a real debugging session
|
|
94
98
|
|
|
@@ -15,11 +15,21 @@ the deployment, not a shortcut. It will fail on the real engagement.
|
|
|
15
15
|
## Scaffold
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
kyb init <name> --host=exe --channel=<imessage|slack|telegram|none> --engineer
|
|
18
|
+
kyb init <name> --host=exe --channel=<imessage|slack|telegram|none> --engineer --studio
|
|
19
|
+
kyb register # control plane: device flow, grants you, idempotent by name
|
|
20
|
+
kyb deploy # copy + install + restart, and prove it took
|
|
21
|
+
kyb doctor # preflight; it knows the self-hosted failure modes below
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
`--host=exe` swaps the bindings
|
|
22
|
-
|
|
24
|
+
`--host=exe` swaps the bindings and installs `scripts/eve-server.sh` — the
|
|
25
|
+
restart script, with every lesson below already in it. `--studio` adds local
|
|
26
|
+
execution and the management routes.
|
|
27
|
+
|
|
28
|
+
**Deploying is `kyb deploy`, not a hand-rolled rsync.** It refuses to copy
|
|
29
|
+
`node_modules` (native modules built on a laptop do not run on the host) or
|
|
30
|
+
`.eve` (the durable store — copying over it eats production state), restarts
|
|
31
|
+
detached so a dropped connection cannot SIGHUP the restart halfway, and waits
|
|
32
|
+
for the script to report health rather than reporting success on exit code.
|
|
23
33
|
|
|
24
34
|
## What Vercel gives you that a client host does not
|
|
25
35
|
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Use when running evals, certifying an agent or an eve version bump, debugging eval failures, or preparing a release — the Kybernesis QA discipline and its run hygiene.
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Certification & eval discipline
|
|
6
|
-
|
|
7
|
-
The rule: **evals gate every deploy, and the consuming agent's suite is the
|
|
8
|
-
release gate for every package change.** Nothing ships on "it looks right" —
|
|
9
|
-
green suite or it doesn't go.
|
|
10
|
-
|
|
11
|
-
## The suite
|
|
12
|
-
|
|
13
|
-
`kybernesisBaseline()` from `@kybernesis/evals` in `evals/kybernesis.eval.ts`:
|
|
14
|
-
smoke (boots, replies, identifies itself), five memory evals (no memory
|
|
15
|
-
thrash on greetings; explicit remember never refused; proactive storage;
|
|
16
|
-
brain-note two-step in order; cross-session unprompted recall), one routing
|
|
17
|
-
eval per department, and with `engineer: true` the vision-loop eval
|
|
18
|
-
(screenshot tool fires and the judge confirms the model SAW the render).
|
|
19
|
-
Judge model is configured in `evals/evals.config.ts` and must NEVER be the
|
|
20
|
-
model under test.
|
|
21
|
-
|
|
22
|
-
## Run hygiene (each rule ate a real run)
|
|
23
|
-
|
|
24
|
-
- `npm run eval` — always through the npm script: it forces every Arcana
|
|
25
|
-
workspace to `<name>-eval` so evals never write into a real brain.
|
|
26
|
-
- **Kill any running dev server first** (`pkill -f "eve dev"`) — eve eval
|
|
27
|
-
attaches to an existing instance and runs stale code.
|
|
28
|
-
- **Never edit the repo mid-run** — the dev runtime watches `agent/`; an
|
|
29
|
-
edit breaks the rebuild and kills remaining evals.
|
|
30
|
-
- Engineer eval: hosted Vercel sandbox (no Docker), needs `vercel link` +
|
|
31
|
-
`vercel env pull` (VERCEL_OIDC_TOKEN). Warm template ≈3–4 min; a
|
|
32
|
-
pre-first-deploy cold bake is budgeted 20 min.
|
|
33
|
-
- Stale sandbox state (migration errors, re-baking templates):
|
|
34
|
-
`rm -rf .eve/sandbox-cache .eve/dev-runtime` and rerun.
|
|
35
|
-
- Don't pipe the eval command through `tail` in scripts — it masks the exit
|
|
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.
|
|
53
|
-
|
|
54
|
-
## eve version certification
|
|
55
|
-
|
|
56
|
-
Clients pin the **Kybernesis-certified** eve version (`kyb upgrade` carries
|
|
57
|
-
them there — never blind npm-latest). Certifying a new eve: bump in a branch
|
|
58
|
-
→ typecheck → `npx eve info` → full suite → live smoke on the deployed
|
|
59
|
-
surface → advance the pin in @kybernesis/create → record the certification.
|
|
60
|
-
|
|
61
|
-
## When an eval fails
|
|
62
|
-
|
|
63
|
-
Read the eval's transcript before touching fixtures. Order of suspicion:
|
|
64
|
-
(1) environment (stale dev server, missing env, cold template), (2) a real
|
|
65
|
-
behavior regression — fix the agent, (3) only THEN the fixture — and if a
|
|
66
|
-
fixture changes, the reason becomes a comment on it. A failure that reveals
|
|
67
|
-
a new failure mode becomes a new fixture: that is how the suite grew every
|
|
68
|
-
guard it has.
|
|
69
|
-
|
|
70
|
-
## Release flow (packages)
|
|
71
|
-
|
|
72
|
-
Edit in `~/platform` → build → bump → human publishes (browser auth) →
|
|
73
|
-
consuming agent bumps → **full suite green** → deploy → registry item update
|
|
74
|
-
+ deploy if install files changed. Then propagate the lesson (see the
|
|
75
|
-
`source-of-truth` skill).
|
|
@@ -1,119 +0,0 @@
|
|
|
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.
|
|
93
|
-
|
|
94
|
-
## Governed mode (control-plane edges — preferred when the client runs the admin)
|
|
95
|
-
|
|
96
|
-
Instead of hand-enumerated peers, edges are GRANTED in the Kybernesis control
|
|
97
|
-
plane and enforced with 300s A2A tokens. Full lifecycle proven live 2026-08-07
|
|
98
|
-
(grant → dispatch → revoke → refused ≤5 min → re-grant → restored, no deploys).
|
|
99
|
-
|
|
100
|
-
Setup, per agent (admin UI /agents):
|
|
101
|
-
1. Register both agents as eve deployments — URL must be the OPEN production
|
|
102
|
-
alias (health 200; forms sanitize pasted punctuation as of this writing).
|
|
103
|
-
2. Grant the edge on the CALLEE's panel ("Agent-to-agent edges" → allow calls
|
|
104
|
-
from <caller> + purpose; expiry optional — self-destructing edge).
|
|
105
|
-
3. Mint each agent's credential (shown ONCE) → set as Sensitive env
|
|
106
|
-
`KYBERNESIS_AGENT_CREDENTIAL` on that agent's Vercel project.
|
|
107
|
-
|
|
108
|
-
Then in code (dispatch ≥0.2.1 + enterprise ≥0.2.0 both installed):
|
|
109
|
-
- caller: `remotePeer({ callee: "<EXACT registered name>", governed: { issuer },
|
|
110
|
-
envVar, fallbackUrl, description })` — envVar/fallbackUrl kept as overrides;
|
|
111
|
-
registry supplies the URL when the credential is present.
|
|
112
|
-
- receiver: `dispatchChannel({ governed: { issuer, agent: "<own name>" } })`.
|
|
113
|
-
|
|
114
|
-
Gotchas: names are case-sensitive ("Kyber" ≠ "kyber" — copy from the admin);
|
|
115
|
-
credentials are one JWS line ~3 segments, an ES256 signature is 86 base64url
|
|
116
|
-
chars — a truncated paste fails verification silently, so length-check it;
|
|
117
|
-
scheduled/cron turns still forward no human (service identity at the peer);
|
|
118
|
-
the deployed agent's model spend shares the project's AI Gateway budget with
|
|
119
|
-
local eval runs — size the budget for BOTH or production goes model-dead.
|