@kybernesis/create 0.7.3 → 0.7.8
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 +2 -1
- package/dist/deploy.d.ts +1 -0
- package/dist/deploy.js +50 -4
- package/dist/envfile.d.ts +10 -0
- package/dist/envfile.js +30 -0
- package/dist/init.js +58 -5
- package/dist/templates.d.ts +13 -0
- package/dist/templates.js +56 -4
- package/package.json +1 -1
- package/skills/self-hosting/SKILL.md +19 -0
package/dist/cli.js
CHANGED
|
@@ -53,7 +53,7 @@ switch (command) {
|
|
|
53
53
|
await register({ name: flag(rest, "name"), url: flag(rest, "url") });
|
|
54
54
|
break;
|
|
55
55
|
case "deploy":
|
|
56
|
-
await deploy({ host: flag(rest, "host") });
|
|
56
|
+
await deploy({ host: flag(rest, "host"), noEnv: rest.includes("--no-env") });
|
|
57
57
|
break;
|
|
58
58
|
case "upgrade":
|
|
59
59
|
await upgrade(rest.includes("--skip-eval"));
|
|
@@ -86,6 +86,7 @@ ${bold("kyb")} — Kybernesis agent scaffolder & FDE toolkit
|
|
|
86
86
|
--url=<url> ${dim("defaults to https://$EXE_VM_NAME.exe.xyz")}
|
|
87
87
|
${bold("kyb deploy")} copy to the host, install, restart, prove it took
|
|
88
88
|
--host=<target> ${dim("ssh target; defaults to $EXE_VM_NAME.exe.xyz")}
|
|
89
|
+
--no-env ${dim("do not send .env.local (host manages its own secrets)")}
|
|
89
90
|
${bold("kyb upgrade")} bump @kybernesis/* packages, gated on evals
|
|
90
91
|
--skip-eval ${dim("skip the eval gate")}
|
|
91
92
|
|
package/dist/deploy.d.ts
CHANGED
package/dist/deploy.js
CHANGED
|
@@ -77,11 +77,14 @@ export async function deploy(options) {
|
|
|
77
77
|
* not run on the host. `.eve` is the durable store — conversations, turn
|
|
78
78
|
* history, the workflow queue — and overwriting it with a local copy is how
|
|
79
79
|
* a deployment eats its own production state.
|
|
80
|
+
*
|
|
81
|
+
* `.env.local` is excluded from the SOURCE sync and sent separately below.
|
|
82
|
+
* It is not source: rsync --delete would remove a host-only file, and a
|
|
83
|
+
* half-matching copy is worse than a deliberate one.
|
|
80
84
|
*/
|
|
81
|
-
console.log(bold("1/
|
|
85
|
+
console.log(bold("1/4 Copying source …"));
|
|
82
86
|
const ok = run("rsync", [
|
|
83
87
|
"-az",
|
|
84
|
-
"--delete",
|
|
85
88
|
"--exclude",
|
|
86
89
|
"node_modules",
|
|
87
90
|
"--exclude",
|
|
@@ -100,7 +103,50 @@ export async function deploy(options) {
|
|
|
100
103
|
process.exitCode = 1;
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
103
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The agent cannot boot without its environment.
|
|
108
|
+
*
|
|
109
|
+
* This step was missing, and the failure was exactly as opaque as it sounds:
|
|
110
|
+
* source copied, dependencies installed, build succeeded, and the server died
|
|
111
|
+
* on `Invalid extension config: apiKey: expected string, received undefined`
|
|
112
|
+
* — an error about a mount, thirty lines into a log, when the actual cause
|
|
113
|
+
* was that no configuration had ever reached the machine.
|
|
114
|
+
*
|
|
115
|
+
* Sent separately from the source sync so `--delete` cannot touch it, and
|
|
116
|
+
* skippable for deployments whose secrets are managed on the host.
|
|
117
|
+
*/
|
|
118
|
+
if (!options.noEnv) {
|
|
119
|
+
const envFile = join(dir, ".env.local");
|
|
120
|
+
if (existsSync(envFile)) {
|
|
121
|
+
console.log(bold("\n2/4 Environment …"));
|
|
122
|
+
/**
|
|
123
|
+
* Only when the host has none.
|
|
124
|
+
*
|
|
125
|
+
* The host's copy is authoritative: it holds credentials minted ON the
|
|
126
|
+
* host, values a laptop never had, and secrets nobody keeps in a working
|
|
127
|
+
* tree. A local stub overwriting it does not misconfigure a deployment,
|
|
128
|
+
* it destroys one — that happened here, to a production agent, and
|
|
129
|
+
* `rsync --delete` took the backup with it in the same run.
|
|
130
|
+
*/
|
|
131
|
+
const hasRemote = spawnSync("ssh", [target, `test -s ${remote}/.env.local`]).status === 0;
|
|
132
|
+
if (hasRemote) {
|
|
133
|
+
console.log(dim(" host has its own — left untouched"));
|
|
134
|
+
}
|
|
135
|
+
else if (!run("scp", ["-q", envFile, `${target}:${remote}/.env.local`])) {
|
|
136
|
+
console.log(red(" Could not copy .env.local — the agent will not start without it."));
|
|
137
|
+
process.exitCode = 1;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(dim(" sent (host had none)"));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(yellow("\n ! No .env.local here. The host needs one, or the agent will fail to boot\n" +
|
|
146
|
+
" on the first config it dereferences. Run kyb arcana, or create it there."));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
console.log(bold("\n3/4 Installing dependencies on the host …"));
|
|
104
150
|
if (!run("ssh", [target, `cd ${remote} && npm install --no-audit --no-fund`])) {
|
|
105
151
|
console.log(red(" npm install failed on the host."));
|
|
106
152
|
process.exitCode = 1;
|
|
@@ -114,7 +160,7 @@ export async function deploy(options) {
|
|
|
114
160
|
* exists to prevent. setsid + nohup + no stdin is the difference between a
|
|
115
161
|
* deploy and an outage.
|
|
116
162
|
*/
|
|
117
|
-
console.log(bold("\
|
|
163
|
+
console.log(bold("\n4/4 Restarting (detached) …"));
|
|
118
164
|
/**
|
|
119
165
|
* Find the restart script rather than assuming its name.
|
|
120
166
|
*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set values in a project's .env.local, replacing in place.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because two commands need it and both were getting it wrong
|
|
5
|
+
* in the same way: a registry item's `envVars` only appends NAMES, so a
|
|
6
|
+
* scaffold could finish with `KYBERNESIS_AGENT=` empty and every later command
|
|
7
|
+
* reading it would report the value as missing — which reads as a step the
|
|
8
|
+
* person skipped rather than one they were never offered.
|
|
9
|
+
*/
|
|
10
|
+
export declare function upsertEnv(dir: string, values: Record<string, string>): void;
|
package/dist/envfile.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Set values in a project's .env.local, replacing in place.
|
|
5
|
+
*
|
|
6
|
+
* Its own module because two commands need it and both were getting it wrong
|
|
7
|
+
* in the same way: a registry item's `envVars` only appends NAMES, so a
|
|
8
|
+
* scaffold could finish with `KYBERNESIS_AGENT=` empty and every later command
|
|
9
|
+
* reading it would report the value as missing — which reads as a step the
|
|
10
|
+
* person skipped rather than one they were never offered.
|
|
11
|
+
*/
|
|
12
|
+
export function upsertEnv(dir, values) {
|
|
13
|
+
const path = join(dir, ".env.local");
|
|
14
|
+
let text = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
15
|
+
for (const [key, value] of Object.entries(values)) {
|
|
16
|
+
const line = `${key}="${value}"`;
|
|
17
|
+
const existing = new RegExp(`^${key}=.*$`, "m");
|
|
18
|
+
if (existing.test(text)) {
|
|
19
|
+
// A function replacer, never a string: `$&` and `` $` `` are special in a
|
|
20
|
+
// replacement string, and a value containing either silently splices in
|
|
21
|
+
// part of the file. That has happened twice, and both times it produced
|
|
22
|
+
// a file that looked plausible and did not parse.
|
|
23
|
+
text = text.replace(existing, () => line);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
text += (text.endsWith("\n") || text === "" ? "" : "\n") + line + "\n";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
writeFileSync(path, text);
|
|
30
|
+
}
|
package/dist/init.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, yellow, } from "./util.js";
|
|
4
|
-
import { CHANNEL_KINDS, channelPlan, engineerPlan, envExample, evalFileTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
|
|
4
|
+
import { CHANNEL_KINDS, channelPlan, engineerPlan, envExample, evalFileTs, exeEvalConfigTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
|
|
5
5
|
import { suiteDir } from "./skills.js";
|
|
6
6
|
import { configureArcana } from "./arcana.js";
|
|
7
|
+
import { upsertEnv } from "./envfile.js";
|
|
7
8
|
/**
|
|
8
9
|
* The always-installed core. Everything else — channels, subagents, engineer,
|
|
9
10
|
* host bindings — is opt-in, because assuming them means the FDE deletes files
|
|
@@ -64,7 +65,12 @@ export async function init(rawName, options = {}) {
|
|
|
64
65
|
for (const item of CORE_ITEMS) {
|
|
65
66
|
run("npx", ["eve", "add", `@kybernesis/${item}`, "--overwrite"], { cwd: dir });
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
// @ai-sdk/anthropic is for the EVAL JUDGE, not the agent: an agent judged
|
|
69
|
+
// by the model it runs on is a weak test.
|
|
70
|
+
const extraDeps = [
|
|
71
|
+
...plan.deps,
|
|
72
|
+
...(host === "exe" ? ["@kybernesis/exe", "@ai-sdk/openai", "@ai-sdk/anthropic"] : []),
|
|
73
|
+
];
|
|
68
74
|
if (extraDeps.length) {
|
|
69
75
|
console.log(bold(`\n2b Installing for ${channel}/${host}: ${extraDeps.join(", ")} …`));
|
|
70
76
|
run("npm", ["install", ...extraDeps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
|
|
@@ -78,10 +84,28 @@ export async function init(rawName, options = {}) {
|
|
|
78
84
|
// client change THIS AGENT — its dependencies and its source. Different
|
|
79
85
|
// blast radius, so an agent can have one without the other.
|
|
80
86
|
console.log(bold("\n2b2 KYBER Studio: local execution + management routes …"));
|
|
87
|
+
const studioFailures = [];
|
|
81
88
|
for (const item of ["local", "manage"]) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
// The @kybernesis/ prefix is load-bearing: a bare name resolves against
|
|
90
|
+
// eve OWN registry, which has no such item, so both installs failed with
|
|
91
|
+
// "not found" — and allowFail swallowed it. --studio therefore did
|
|
92
|
+
// nothing at all, silently, and the first sign was Studio refusing to
|
|
93
|
+
// connect an agent that looked correctly scaffolded.
|
|
94
|
+
const ok = run("npx", ["eve", "add", `@kybernesis/${item}`, "--overwrite"], {
|
|
95
|
+
cwd: dir,
|
|
96
|
+
allowFail: true,
|
|
97
|
+
});
|
|
98
|
+
if (!ok) {
|
|
99
|
+
studioFailures.push(item);
|
|
100
|
+
console.log(yellow(` ! ${item} did not install — re-run: npx eve add @kybernesis/${item}`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Said again, loudly, at the end. A warning printed sixty lines before a
|
|
104
|
+
// green summary is a warning nobody reads — and the agent that results
|
|
105
|
+
// looks correctly scaffolded right up until KYBER Studio refuses it.
|
|
106
|
+
if (studioFailures.length) {
|
|
107
|
+
console.log(yellow(`\n ! --studio did NOT complete: ${studioFailures.join(", ")} missing.\n` +
|
|
108
|
+
` This agent cannot be connected to a desktop until they install.`));
|
|
85
109
|
}
|
|
86
110
|
}
|
|
87
111
|
const engPlan = engineer ? engineerPlan(host, DEFAULT_MODEL) : null;
|
|
@@ -112,9 +136,38 @@ export async function init(rawName, options = {}) {
|
|
|
112
136
|
writeFileSync(join(dir, "agent/agent.ts"), hostAgentTs(host, DEFAULT_MODEL));
|
|
113
137
|
writeFileSync(join(dir, "agent/extensions/arcana.ts"), rootArcanaTs());
|
|
114
138
|
writeFileSync(join(dir, "evals/kybernesis.eval.ts"), evalFileTs(displayName, depts));
|
|
139
|
+
// A self-hosted agent judges through its own integration; the default
|
|
140
|
+
// resolves through a gateway it has no key for, and only the judged gates
|
|
141
|
+
// fail — which reads as a half-broken agent rather than a missing config.
|
|
142
|
+
if (host === "exe")
|
|
143
|
+
writeFileSync(join(dir, "evals/evals.config.ts"), exeEvalConfigTs());
|
|
115
144
|
// Ask for the memory keys HERE, while the person is still standing in the
|
|
116
145
|
// scaffold — not in a printed next-step they will read after the context has
|
|
117
146
|
// gone. Skipped with --yes, which is for CI and takes no input by design.
|
|
147
|
+
/**
|
|
148
|
+
* Write the values this command already knows.
|
|
149
|
+
*
|
|
150
|
+
* These were left blank, so the first thing a person met after a clean
|
|
151
|
+
* scaffold was `kyb register` reporting "No agent name" — a step they were
|
|
152
|
+
* never offered, reading as one they had skipped.
|
|
153
|
+
*/
|
|
154
|
+
const known = { KYBERNESIS_ISSUER: issuer, KYBERNESIS_AGENT: name };
|
|
155
|
+
if (host === "exe") {
|
|
156
|
+
// Usually the agent's name, and usually not. Everything downstream — the
|
|
157
|
+
// deploy target, the registered URL — derives from this one value.
|
|
158
|
+
known.EXE_VM_NAME = options.yes ? name : await ask("exe.dev VM name?", name);
|
|
159
|
+
/**
|
|
160
|
+
* Left empty on purpose rather than guessed.
|
|
161
|
+
*
|
|
162
|
+
* Model ids are per-integration and carry a provider prefix. A hardcoded
|
|
163
|
+
* default put one host's id on another, where an unknown id answers
|
|
164
|
+
* `404 unsupported endpoint` — an error about the endpoint for a problem
|
|
165
|
+
* with the model. Empty fails honestly; the host lists its own with
|
|
166
|
+
* curl https://llm.int.exe.xyz/models.json
|
|
167
|
+
*/
|
|
168
|
+
known.EXE_MODEL = "";
|
|
169
|
+
}
|
|
170
|
+
upsertEnv(dir, known);
|
|
118
171
|
if (!options.yes)
|
|
119
172
|
await configureArcana({ dir, suggest: name, depts });
|
|
120
173
|
/**
|
package/dist/templates.d.ts
CHANGED
|
@@ -37,3 +37,16 @@ export interface EngineerPlan {
|
|
|
37
37
|
steps: string[];
|
|
38
38
|
}
|
|
39
39
|
export declare function engineerPlan(host: HostKind, model: string): EngineerPlan;
|
|
40
|
+
/**
|
|
41
|
+
* The eval judge, for an exe.dev host.
|
|
42
|
+
*
|
|
43
|
+
* Without this the judge resolves through Vercel's AI Gateway and fails with
|
|
44
|
+
* "Unauthenticated request to AI Gateway" on a host that has no gateway key
|
|
45
|
+
* and does not need one — the agent passes every gate the judge is not part
|
|
46
|
+
* of, which reads as a half-broken agent rather than a missing config.
|
|
47
|
+
*
|
|
48
|
+
* Judged by a DIFFERENT provider from the one under test. A model grading its
|
|
49
|
+
* own output is a weak test, and the Anthropic path is a plain Messages
|
|
50
|
+
* endpoint, so none of the subscription backend's constraints apply.
|
|
51
|
+
*/
|
|
52
|
+
export declare function exeEvalConfigTs(): string;
|
package/dist/templates.js
CHANGED
|
@@ -69,7 +69,15 @@ export function subagentArcanaTs(dept) {
|
|
|
69
69
|
const upper = dept.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
70
70
|
return `import { defineMcpClientConnection } from "eve/connections";
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// Same reasoning as the root brain: no placeholder. A subagent quietly
|
|
73
|
+
// addressing a workspace nobody owns is amnesia that looks like a model
|
|
74
|
+
// problem rather than a missing value. Set it with \`kyb arcana\`.
|
|
75
|
+
const workspace = process.env.ARCANA_${upper}_WORKSPACE;
|
|
76
|
+
if (!workspace) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"ARCANA_${upper}_WORKSPACE is not set — run \`kyb arcana\` to set this subagent workspace and key.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
73
81
|
|
|
74
82
|
export default defineMcpClientConnection({
|
|
75
83
|
url: "https://mcp.arcana.kybernesis.ai/mcp",
|
|
@@ -97,7 +105,16 @@ export function rootArcanaTs() {
|
|
|
97
105
|
// One brain for shared surfaces, optionally a second for DMs. Hermetic eval
|
|
98
106
|
// runs override ARCANA_COMPANY_WORKSPACE to "<client>-eval", which switches
|
|
99
107
|
// to the eval workspace's own key automatically.
|
|
100
|
-
|
|
108
|
+
// No placeholder fallback. A default here does not prevent a mistake, it
|
|
109
|
+
// hides one: memory silently addresses a workspace nobody owns, and the agent
|
|
110
|
+
// looks like it has amnesia rather than like it is misconfigured. Set with
|
|
111
|
+
// \`kyb arcana\`.
|
|
112
|
+
const COMPANY = process.env.ARCANA_COMPANY_WORKSPACE;
|
|
113
|
+
if (!COMPANY) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"ARCANA_COMPANY_WORKSPACE is not set — run \`kyb arcana\` to set the workspace and its key.",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
101
118
|
const DM = process.env.ARCANA_DM_WORKSPACE ?? COMPANY;
|
|
102
119
|
|
|
103
120
|
export default arcana({
|
|
@@ -501,8 +518,13 @@ export default defineAgent({
|
|
|
501
518
|
{
|
|
502
519
|
path: "agent/subagents/builder/extensions/engineer.ts",
|
|
503
520
|
content: `// Engineer layer mounted LOCALLY on this subagent (eve >=0.30): screenshot,
|
|
504
|
-
// deliver, and the trade-school skills belong to \`builder\` alone
|
|
505
|
-
// agent
|
|
521
|
+
// deliver, and the trade-school skills belong to \`builder\` alone, so the root
|
|
522
|
+
// agent has no shell.
|
|
523
|
+
//
|
|
524
|
+
// The root DOES get a browser and GitHub tools — the engineer layer installs
|
|
525
|
+
// extension/agent-browser and extension/github-tools at the root, deliberately,
|
|
526
|
+
// because reading a page is not the same blast radius as running a command.
|
|
527
|
+
// This comment used to claim otherwise, which is worse than saying nothing.
|
|
506
528
|
export { default } from "@kybernesis/engineer";
|
|
507
529
|
`,
|
|
508
530
|
},
|
|
@@ -534,3 +556,33 @@ export { default } from "@kybernesis/engineer";
|
|
|
534
556
|
],
|
|
535
557
|
};
|
|
536
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* The eval judge, for an exe.dev host.
|
|
561
|
+
*
|
|
562
|
+
* Without this the judge resolves through Vercel's AI Gateway and fails with
|
|
563
|
+
* "Unauthenticated request to AI Gateway" on a host that has no gateway key
|
|
564
|
+
* and does not need one — the agent passes every gate the judge is not part
|
|
565
|
+
* of, which reads as a half-broken agent rather than a missing config.
|
|
566
|
+
*
|
|
567
|
+
* Judged by a DIFFERENT provider from the one under test. A model grading its
|
|
568
|
+
* own output is a weak test, and the Anthropic path is a plain Messages
|
|
569
|
+
* endpoint, so none of the subscription backend's constraints apply.
|
|
570
|
+
*/
|
|
571
|
+
export function exeEvalConfigTs() {
|
|
572
|
+
return `import { defineEvalConfig } from "eve/evals";
|
|
573
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
574
|
+
|
|
575
|
+
const exe = createAnthropic({
|
|
576
|
+
baseURL: process.env.EXE_LLM_URL ?? "https://llm.int.exe.xyz/v1",
|
|
577
|
+
apiKey: "exe-integration",
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
export default defineEvalConfig({
|
|
581
|
+
// A different provider from the agent under test, on purpose.
|
|
582
|
+
judge: { model: exe(process.env.EXE_JUDGE_MODEL ?? "claude-sonnet-4-6") },
|
|
583
|
+
// Real model and real memory on every turn: generous timeout, gentle concurrency.
|
|
584
|
+
timeoutMs: 300_000,
|
|
585
|
+
maxConcurrency: 1,
|
|
586
|
+
});
|
|
587
|
+
`;
|
|
588
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
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",
|
|
@@ -88,6 +88,25 @@ What this arrangement costs you, and it is worth saying to the client:
|
|
|
88
88
|
|
|
89
89
|
## The failure modes, each of which cost a real session
|
|
90
90
|
|
|
91
|
+
- **`EXE_MODEL` is deliberately empty after `kyb init`.** Set it from the
|
|
92
|
+
host: `curl https://llm.int.exe.xyz/models.json`. Nothing can guess it —
|
|
93
|
+
ids are per-integration.
|
|
94
|
+
- **Model ids carry a provider prefix**: `openai/gpt-5.6-sol`, not
|
|
95
|
+
`gpt-5.6-sol`. Get them from `curl https://llm.int.exe.xyz/models.json` on
|
|
96
|
+
the host, and note its `preferred_model`.
|
|
97
|
+
- **An unknown model id on the responses surface returns**
|
|
98
|
+
`404 unsupported endpoint: /v1/responses` — an error about the ENDPOINT for
|
|
99
|
+
a problem with the MODEL. The endpoint is fine. Check the prefix before
|
|
100
|
+
believing that 404; chasing it cost hours and produced two wrong fixes.
|
|
101
|
+
- **Subscription-backed models are Responses-API only.** models.json reports
|
|
102
|
+
`"apis": ["openai_responses"]`, and chat-completions answers `Model … is
|
|
103
|
+
not in this integration's model list` for a model that plainly is listed.
|
|
104
|
+
- **Evals must run ON the host.** `llm.int.exe.xyz` is internal to exe.dev, so
|
|
105
|
+
a laptop cannot reach it — every model call fails with a connection error to
|
|
106
|
+
the cloud metadata address, which looks like a broken agent and is not.
|
|
107
|
+
- **An integration has to be attached to the VM** (`integrations attach llm
|
|
108
|
+
<vm>`, or `auto:all`). Check with `ssh exe.dev integrations list`.
|
|
109
|
+
|
|
91
110
|
- **Docker ships disabled on some images.** exe.dev's exeuntu runs
|
|
92
111
|
`systemctl disable docker.service`, so `docker --version` works while nothing
|
|
93
112
|
can run. Every sandbox call fails with `SandboxTemplateNotProvisionedError`.
|