@foldrun/core 2.12.1 → 2.13.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/src/completions.js +1 -0
- package/dist/src/frontmatter-fields.js +4 -2
- package/dist/src/frontmatter-schema.js +3 -0
- package/dist/src/resources.d.ts +59 -0
- package/dist/src/resources.js +142 -0
- package/dist/src/run-container.d.ts +48 -1
- package/dist/src/run-container.js +120 -7
- package/dist/src/runner.js +61 -14
- package/dist/src/starter.js +1 -0
- package/dist/src/store.d.ts +18 -0
- package/package.json +1 -1
- package/src/completions.ts +1 -0
- package/src/frontmatter-fields.ts +4 -2
- package/src/frontmatter-schema.ts +3 -0
- package/src/resources.ts +151 -0
- package/src/run-container.ts +138 -7
- package/src/runner.ts +60 -14
- package/src/starter.ts +1 -0
- package/src/store.ts +11 -0
package/src/runner.ts
CHANGED
|
@@ -19,7 +19,8 @@ import { cascadeLimits, toolOwners } from "./limits.ts";
|
|
|
19
19
|
import { resolveAgentPath } from "./confine.ts";
|
|
20
20
|
import { browserPodLine, podLossDecision, RERAN_ON_FULL } from "./browser-pod.ts";
|
|
21
21
|
import { eventUrl } from "./webhook.ts";
|
|
22
|
-
import { runStepInContainer,
|
|
22
|
+
import { runStepInContainer, resolveResources, memoryGiB, killRunSandboxes, type StepTiming } from "./run-container.ts";
|
|
23
|
+
import { readResources, hasOverride } from "./resources.ts";
|
|
23
24
|
import { hostSafeEnv, platformApiUrl } from "./host-env.ts";
|
|
24
25
|
import { resolveModelCredential, credentialLine, accountModelBlock, isClaudeLoginToken, isModelEnvName, DECLARED_ENV_PREFIX } from "./model-credential.ts";
|
|
25
26
|
import { parseJudge } from "./judge-verdict.ts";
|
|
@@ -1296,9 +1297,10 @@ export function agentContext(
|
|
|
1296
1297
|
// is large — the install's configured limits — so existing agents change
|
|
1297
1298
|
// nothing. `size: small` is the opt-in for agents that wait on APIs more
|
|
1298
1299
|
// than they work; `size: heavy` for a rendered browser or real data in
|
|
1299
|
-
// memory. Memory
|
|
1300
|
-
//
|
|
1301
|
-
// line in any trace
|
|
1300
|
+
// memory. Memory and cores are hard ceilings per class and bill at the
|
|
1301
|
+
// class rate (cores capped since 26 Oct 2026), so heavy both may use more
|
|
1302
|
+
// and costs more. The actuals line in any trace — busy, throttled, peak —
|
|
1303
|
+
// is the evidence for right-sizing.
|
|
1302
1304
|
const size: "small" | "large" | "heavy" =
|
|
1303
1305
|
front.size === "small" ? "small" : front.size === "heavy" ? "heavy" : "large";
|
|
1304
1306
|
|
|
@@ -2311,6 +2313,16 @@ async function runStep(
|
|
|
2311
2313
|
// A retry that moved the step up a class (an evicted or OOM-killed
|
|
2312
2314
|
// attempt) wins over the agent's own `size:` for the attempts after.
|
|
2313
2315
|
const size = step.sizeUp ?? agentSize;
|
|
2316
|
+
// `resources:` over the class's figures, key by key. A value check would
|
|
2317
|
+
// refuse is dropped here too (the class's figure holds) and said so, so
|
|
2318
|
+
// `foldrun check` and the sandbox never disagree. After a retry moved up
|
|
2319
|
+
// a class, an override never holds the step below the new class.
|
|
2320
|
+
const asked = readResources(front.resources);
|
|
2321
|
+
for (const p of asked.problems) push("error", `resources: ${p}`);
|
|
2322
|
+
const limits = resolveResources(size, asked.value, { atLeastClass: !!step.sizeUp });
|
|
2323
|
+
// What the sandbox holds while it exists — `step.holding`, what the
|
|
2324
|
+
// account's compute in use sums (Settings, `foldrun usage compute`).
|
|
2325
|
+
const held = { cpus: Number(limits.cpus) || 2, memGiB: memoryGiB(limits.memory) };
|
|
2314
2326
|
|
|
2315
2327
|
// oauth2 secrets become live access tokens here — the one async moment
|
|
2316
2328
|
// in secret resolution, at the last host-side point before anything is
|
|
@@ -2345,7 +2357,10 @@ async function runStep(
|
|
|
2345
2357
|
const noKey = !providerLabel && !platform.platformKeyAllowed(tenant);
|
|
2346
2358
|
const credential = providerLabel || noKey ? null : resolveModelCredential(isolatedRun() ? platformModelEnv() : process.env);
|
|
2347
2359
|
step.credential = credential ? credential.label : noKey ? "none — this account has no model key" : `provider: ${providerLabel}`;
|
|
2348
|
-
|
|
2360
|
+
// Core's own Docker container has no login to fall back to (below):
|
|
2361
|
+
// there, "none set" is refused with what to do, not described.
|
|
2362
|
+
const noCredentialInContainer = credential?.kind === "none" && process.env.FOLDRUN_RUN_ISOLATION === "container";
|
|
2363
|
+
if (credential && !noCredentialInContainer) {
|
|
2349
2364
|
const line = credentialLine(credential);
|
|
2350
2365
|
push(line.type, line.text);
|
|
2351
2366
|
}
|
|
@@ -2372,6 +2387,24 @@ async function runStep(
|
|
|
2372
2387
|
return;
|
|
2373
2388
|
}
|
|
2374
2389
|
}
|
|
2390
|
+
// A step in core's own Docker container (self-hosting) has no machine
|
|
2391
|
+
// login to fall back to: with no credential at all it could only fail at
|
|
2392
|
+
// the model, with Claude Code's own "Not logged in · Please run /login".
|
|
2393
|
+
// Refused here, before a sandbox is rented, saying what to set (a
|
|
2394
|
+
// self-hosted install with no key, 26 Oct 2026). A local `foldrun run`
|
|
2395
|
+
// keeps the machine's login; a registered executor brings its own.
|
|
2396
|
+
if (noCredentialInContainer) {
|
|
2397
|
+
push(
|
|
2398
|
+
"error",
|
|
2399
|
+
"no model credential: this install has none and the account has no key of its own. Set ANTHROPIC_API_KEY " +
|
|
2400
|
+
"(or CLAUDE_CODE_OAUTH_TOKEN from `claude setup-token`) in the platform's environment — for Docker Compose, " +
|
|
2401
|
+
"self-hosting/.env, then `docker compose up -d` — or add the account's key in Settings → Model " +
|
|
2402
|
+
"(`foldrun account model set <provider> --key …`)",
|
|
2403
|
+
);
|
|
2404
|
+
step.status = "failed";
|
|
2405
|
+
save();
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2375
2408
|
for (const w of providerWarnings) push("error", w);
|
|
2376
2409
|
// A level that wrote a language nobody can read was skipped, not obeyed.
|
|
2377
2410
|
for (const l of language.lines) push("info", l);
|
|
@@ -3080,6 +3113,7 @@ async function runStep(
|
|
|
3080
3113
|
// executor asks this before it creates one (pod-room.ts).
|
|
3081
3114
|
...(stopRequested ? { stopRequested } : {}),
|
|
3082
3115
|
size,
|
|
3116
|
+
limits,
|
|
3083
3117
|
// The pod's network, for the executor's own wall (the k8s tier's
|
|
3084
3118
|
// network policy): a filtered pod reaches the proxy and DNS only.
|
|
3085
3119
|
network: { level: network.level, enforced: forward?.enforce === true },
|
|
@@ -3096,6 +3130,8 @@ async function runStep(
|
|
|
3096
3130
|
// saves the record, so this only updates the fields.
|
|
3097
3131
|
checkpoint: (ref: string, consumed: number) => {
|
|
3098
3132
|
step.sandbox = { kind: isolation!, ref, consumed, since: step.sandbox?.since ?? new Date().toISOString() };
|
|
3133
|
+
// The sandbox exists: past the room gate, it now holds its figures.
|
|
3134
|
+
if (consumed === 0) step.holding = held;
|
|
3099
3135
|
if (consumed === 0) save();
|
|
3100
3136
|
},
|
|
3101
3137
|
});
|
|
@@ -3111,9 +3147,23 @@ async function runStep(
|
|
|
3111
3147
|
// The arguments of the attempt whose outcome is current — what a
|
|
3112
3148
|
// re-run on the full image repeats, on the same model supply.
|
|
3113
3149
|
let lastArgs: typeof firstArgs = firstArgs;
|
|
3114
|
-
const runTracked = (a: typeof firstArgs) => {
|
|
3150
|
+
const runTracked = async (a: typeof firstArgs) => {
|
|
3115
3151
|
lastArgs = a;
|
|
3116
|
-
|
|
3152
|
+
// An executor that cannot report its sandbox (Docker: no room gate,
|
|
3153
|
+
// it starts at once) holds from the call; one that can says so at
|
|
3154
|
+
// its first checkpoint, after any wait for room.
|
|
3155
|
+
if (!platform.sandboxResumable(isolation!)) {
|
|
3156
|
+
step.holding = held;
|
|
3157
|
+
save();
|
|
3158
|
+
}
|
|
3159
|
+
try {
|
|
3160
|
+
return await runIsolated(a);
|
|
3161
|
+
} finally {
|
|
3162
|
+
// Saved now: the clean-up after an attempt (the log drain, the
|
|
3163
|
+
// lease, publishing) can take a while, and the sandbox is gone.
|
|
3164
|
+
step.holding = null;
|
|
3165
|
+
save();
|
|
3166
|
+
}
|
|
3117
3167
|
};
|
|
3118
3168
|
let attemptFrom = step.events.length;
|
|
3119
3169
|
let outcome = await runTracked(firstArgs);
|
|
@@ -3306,6 +3356,7 @@ async function runStep(
|
|
|
3306
3356
|
push(
|
|
3307
3357
|
"info",
|
|
3308
3358
|
`resources: cpu busy ${outcome.res.busyCpuSecs?.toFixed(1) ?? "?"}s · ` +
|
|
3359
|
+
(outcome.res.throttledSecs ? `throttled ${outcome.res.throttledSecs.toFixed(1)}s · ` : "") +
|
|
3309
3360
|
`peak mem ${mb(outcome.res.peakMemBytes)}MB · ` +
|
|
3310
3361
|
`net ${mb(outcome.res.rxBytes)}MB in / ${mb(outcome.res.txBytes)}MB out`,
|
|
3311
3362
|
);
|
|
@@ -3326,13 +3377,8 @@ async function runStep(
|
|
|
3326
3377
|
// is per core-second and per GiB-second, and what was held must come
|
|
3327
3378
|
// from the record, never from what the config says at billing time.
|
|
3328
3379
|
step.size = size;
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
const m = lim.memory.match(/^(\d+(?:\.\d+)?)\s*(Gi?|Mi?|g|m)?/i);
|
|
3332
|
-
const n = m ? Number(m[1]) : 2;
|
|
3333
|
-
return (m?.[2] ?? "Gi").toLowerCase().startsWith("m") ? n / 1024 : n;
|
|
3334
|
-
})();
|
|
3335
|
-
step.reserved = { cpus: Number(lim.cpus) || 2, memGiB: gib };
|
|
3380
|
+
if (hasOverride(asked.value)) step.resources = asked.value;
|
|
3381
|
+
step.reserved = held;
|
|
3336
3382
|
} else {
|
|
3337
3383
|
// Same withholding as the sandbox path — the in-process step is what
|
|
3338
3384
|
// `foldrun run --test` drives on a laptop, where there is no proxy
|
package/src/starter.ts
CHANGED
|
@@ -490,6 +490,7 @@ owner: ana@example.com # who answers for it; else the workspace's, else the
|
|
|
490
490
|
model: default # fast | default | max
|
|
491
491
|
effort: low # how hard to think about it
|
|
492
492
|
size: large # small | large | heavy — the sandbox it rents
|
|
493
|
+
resources: { memory: 4Gi } # optional: cpu, memory, disk in numbers, over the size class
|
|
493
494
|
tools: [web, read, ask, peers, my-folder-tool] # built-ins and your own tools/, one list; \`ask\` lets it ask you mid-step, \`peers\` lets it message the steps running beside it
|
|
494
495
|
disallowedTools: [Bash] # subtract from what it would otherwise have
|
|
495
496
|
skills: [house-style] # from skills/
|
package/src/store.ts
CHANGED
|
@@ -3533,6 +3533,14 @@ export interface StepRecord {
|
|
|
3533
3533
|
* from before sizes existed — billing falls back to the flat rate. */
|
|
3534
3534
|
size?: "small" | "large" | "heavy";
|
|
3535
3535
|
reserved?: { cpus: number; memGiB: number } | null;
|
|
3536
|
+
/** The agent's `resources:` as applied (resources.ts), when it set any:
|
|
3537
|
+
* why `reserved` differs from the class's figures. Absent before 26 Oct
|
|
3538
|
+
* 2026 and on steps of an agent that wrote none. */
|
|
3539
|
+
resources?: { cpu?: number; memory?: string; disk?: string };
|
|
3540
|
+
/** What the step's sandbox holds while it exists — set once it is past
|
|
3541
|
+
* the room gate, cleared when the attempt ends. What an account's
|
|
3542
|
+
* compute in use sums; `reserved` is the bill's record of the same. */
|
|
3543
|
+
holding?: { cpus: number; memGiB: number } | null;
|
|
3536
3544
|
/** Token counts behind costUsd, kept so usage reports can say "how many"
|
|
3537
3545
|
* and not only "how much" — the price of a token changes, the count is
|
|
3538
3546
|
* the fact. Absent on steps recorded before this existed. */
|
|
@@ -3557,6 +3565,9 @@ export interface StepRecord {
|
|
|
3557
3565
|
actual?: {
|
|
3558
3566
|
busyCpuSecs: number | null;
|
|
3559
3567
|
peakMemBytes: number | null;
|
|
3568
|
+
/** Seconds the CPU cap (the class's cores) held the sandbox back, from
|
|
3569
|
+
* cpu.stat. Absent before 26 Oct 2026. */
|
|
3570
|
+
throttledSecs?: number | null;
|
|
3560
3571
|
rxBytes: number | null;
|
|
3561
3572
|
txBytes: number | null;
|
|
3562
3573
|
} | null;
|