@ai-dossier/sched 0.33.0 → 0.34.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/README.md +26 -2
- package/dist/batch-dispatch.d.ts.map +1 -1
- package/dist/batch-dispatch.js +76 -7
- package/dist/batch-dispatch.js.map +1 -1
- package/dist/dispatch.d.ts +26 -0
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +62 -1
- package/dist/dispatch.js.map +1 -1
- package/dist/enqueue.d.ts +12 -0
- package/dist/enqueue.d.ts.map +1 -1
- package/dist/enqueue.js +25 -2
- package/dist/enqueue.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/persist.d.ts.map +1 -1
- package/dist/persist.js +59 -0
- package/dist/persist.js.map +1 -1
- package/dist/recovery.d.ts +2 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +4 -1
- package/dist/recovery.js.map +1 -1
- package/dist/state.d.ts +9 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +27 -0
- package/dist/state.js.map +1 -1
- package/dist/status.d.ts +6 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +7 -1
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +51 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,8 +89,8 @@ where every mechanical supervision decision is code, not remembered prose:
|
|
|
89
89
|
|
|
90
90
|
(`strong` sets only `model`, so its command falls back to the default claude template.)
|
|
91
91
|
What an engine is ACTUALLY running is always visible in two places, added in #680: the
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
`Dispatch (default): …` line in `sched status` (and its `--json` `dispatch` field), and the
|
|
93
|
+
`▶ sched dispatch (default): …` banner `sched start` prints once at startup — both show
|
|
94
94
|
`tier=agent/model` resolved exactly as spawns resolve. Note the inverse trap: running
|
|
95
95
|
the batch FROM opencode/GLM does not make it run ON opencode/GLM — without a
|
|
96
96
|
`dispatch.tiers` override every tier dispatches the default claude template regardless
|
|
@@ -283,6 +283,30 @@ Config schema moves to 1.8.0 (#565): a new top-level `default_batch_priority` ke
|
|
|
283
283
|
invalid value degrades the whole config file to built-in defaults, same contract as every
|
|
284
284
|
other field.
|
|
285
285
|
|
|
286
|
+
Config schema moves to 1.9.0 (#707): `dispatch.dispatch_profiles` names complete
|
|
287
|
+
dispatch configurations for batch runs. A profile has the same `command`, `tier_models`,
|
|
288
|
+
`prompt`, and optional per-tier `tiers` overrides as `dispatch`; the batch records the
|
|
289
|
+
profile name at enqueue time, and every member, tail, report, and recovery agent resolves
|
|
290
|
+
through that profile. For example:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"dispatch": {
|
|
295
|
+
"dispatch_profiles": {
|
|
296
|
+
"claude": { "command": ["claude", "-p", "--model", "{model}"], "tier_models": { "mechanical": "haiku", "mid": "sonnet", "strong": "opus" } },
|
|
297
|
+
"glm": { "command": ["opencode", "run", "-m", "{model}", "--"], "tier_models": { "mechanical": "glm-flash", "mid": "glm", "strong": "glm-strong" } }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Use `sched enqueue --mode slot --batch <id> --dispatch glm` to select a profile
|
|
304
|
+
explicitly. Without `--dispatch`, a new batch detects Claude Code from `CLAUDECODE` or a
|
|
305
|
+
configured agent binary in its parent process chain. Detection is convenience only: if
|
|
306
|
+
profiles exist and detection is inconclusive, enqueue fails and names the available
|
|
307
|
+
profiles rather than silently choosing the default. `--dispatch` is intentionally rejected
|
|
308
|
+
for full-cycle entries; no profiles means legacy dispatch behavior remains unchanged.
|
|
309
|
+
|
|
286
310
|
Two engine-safety policies were explicit product decisions on #464:
|
|
287
311
|
|
|
288
312
|
- **Pid identity is hybrid-verified (decision 1, option C).** Every spawn records the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAmBH,OAAO,
|
|
1
|
+
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/batch-dispatch.js
CHANGED
|
@@ -1374,7 +1374,10 @@ function runValidate(deps, config, dispatch, batchId, now, result) {
|
|
|
1374
1374
|
return;
|
|
1375
1375
|
}
|
|
1376
1376
|
const offender = outcome.offenders[0];
|
|
1377
|
-
const { state: fixing, dispatch: fixDispatch } = (0, recovery_1.beginFixAttempt)(attributed, batchId, offender, rDeps,
|
|
1377
|
+
const { state: fixing, dispatch: fixDispatch } = (0, recovery_1.beginFixAttempt)(attributed, batchId, offender, rDeps,
|
|
1378
|
+
// #707: `dispatch` is the batch's own (profile-resolved) dispatch — the
|
|
1379
|
+
// fix agent rides the same family the members ran on.
|
|
1380
|
+
{ config, tests: outcome.attributed.get(offender) ?? [], dispatch });
|
|
1378
1381
|
deps.store.withLock((s) => ({
|
|
1379
1382
|
state: applyBatchAndIssues(s, fixing, batchId, []),
|
|
1380
1383
|
result: undefined,
|
|
@@ -2791,6 +2794,67 @@ function teardownBatch(deps, batchId) {
|
|
|
2791
2794
|
function runBatchTick(deps, config, dispatch) {
|
|
2792
2795
|
const result = emptyResult();
|
|
2793
2796
|
const now = deps.now();
|
|
2797
|
+
// #707: each batch dispatches through ITS OWN recorded profile, not the
|
|
2798
|
+
// engine's default. Resolution is cached per tick (per runBatchTick call):
|
|
2799
|
+
// within one pass a profile resolves at most once, and a failed resolution
|
|
2800
|
+
// reacts at most once. A profile that no longer resolves (config edited
|
|
2801
|
+
// mid-run) never silently falls back — the silent fallback is precisely
|
|
2802
|
+
// the #680 incident shape (a run that looked like one agent family,
|
|
2803
|
+
// executed as another). The reaction depends on how far the batch got:
|
|
2804
|
+
// - pre-merge (`dissolving` is a legal edge): DISSOLVE with reason
|
|
2805
|
+
// `dispatch-profile-missing:<name>` — loud, deterministic, one-shot, and
|
|
2806
|
+
// the members requeue as full-cycle units on the config default, which is
|
|
2807
|
+
// a coherent recovery rather than a mislabelled continuation.
|
|
2808
|
+
// - post-merge (PR parked/merged — the product already shipped): dissolving
|
|
2809
|
+
// would discard a landed PR's bookkeeping, so the remaining dispatch (the
|
|
2810
|
+
// report agent) runs on the default and journals
|
|
2811
|
+
// `dispatch-profile-missing` every tick it holds the decision open — a
|
|
2812
|
+
// bounded phase (deployed → reported), so the event cannot flood.
|
|
2813
|
+
const profileCache = new Map();
|
|
2814
|
+
const dispatchFor = (batch) => {
|
|
2815
|
+
if (batch.dispatch_profile === null)
|
|
2816
|
+
return dispatch;
|
|
2817
|
+
const cached = profileCache.get(batch.dispatch_profile);
|
|
2818
|
+
if (cached !== undefined)
|
|
2819
|
+
return cached;
|
|
2820
|
+
let resolved;
|
|
2821
|
+
try {
|
|
2822
|
+
resolved = (0, dispatch_1.resolveProfiledDispatch)(config, batch.dispatch_profile);
|
|
2823
|
+
}
|
|
2824
|
+
catch (err) {
|
|
2825
|
+
if (!(err instanceof dispatch_1.DispatchProfileError))
|
|
2826
|
+
throw err;
|
|
2827
|
+
const current = deps.store.load();
|
|
2828
|
+
const fresh = (0, state_1.findBatch)(current, batch.id);
|
|
2829
|
+
const dissolvable = fresh !== undefined && (0, state_1.allowedBatchTransitions)(fresh.status).includes('dissolving');
|
|
2830
|
+
if (dissolvable) {
|
|
2831
|
+
const liveSlot = slotFor(current, batch.id);
|
|
2832
|
+
if (liveSlot !== undefined &&
|
|
2833
|
+
liveSlot.pid !== null &&
|
|
2834
|
+
deps.spawnDeps.isAlive(liveSlot.pid, liveSlot.pid_start ?? undefined)) {
|
|
2835
|
+
deps.spawnDeps.kill(liveSlot.pid, liveSlot.pid_start ?? undefined);
|
|
2836
|
+
}
|
|
2837
|
+
const outcome = (0, recovery_1.dissolveBatch)(current, batch.id, { strategy: 'full', reason: `dispatch-profile-missing:${batch.dispatch_profile}` }, recoveryDeps(deps, config, fresh, now));
|
|
2838
|
+
deps.store.withLock((s) => ({
|
|
2839
|
+
state: applyBatchAndIssues(s, outcome.state, batch.id, outcome.requeued),
|
|
2840
|
+
result: undefined,
|
|
2841
|
+
}));
|
|
2842
|
+
result.failed.push(unit(batch.id));
|
|
2843
|
+
// Resolution failed: skip this batch for the rest of the pass with the
|
|
2844
|
+
// base dispatch as a harmless placeholder — the batch is dissolved, so
|
|
2845
|
+
// no further arm can claim a slot for it this tick.
|
|
2846
|
+
resolved = dispatch;
|
|
2847
|
+
}
|
|
2848
|
+
else {
|
|
2849
|
+
journalEvent(deps, 'dispatch-profile-missing', unit(batch.id), {
|
|
2850
|
+
detail: `profile '${batch.dispatch_profile}' is no longer configured; the post-merge tail runs on the default dispatch`,
|
|
2851
|
+
});
|
|
2852
|
+
resolved = dispatch;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
profileCache.set(batch.dispatch_profile, resolved);
|
|
2856
|
+
return resolved;
|
|
2857
|
+
};
|
|
2794
2858
|
// #565: when more ready batches exist than free capacity, claim them in
|
|
2795
2859
|
// the same priority order `runnableUnits` would (desc priority → asc
|
|
2796
2860
|
// readiness age → anchor) — this loop never goes through
|
|
@@ -2822,10 +2886,15 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2822
2886
|
const state = deps.store.load();
|
|
2823
2887
|
const batch = (0, state_1.findBatch)(state, batchId);
|
|
2824
2888
|
if (batch && batch.status === 'ready' && slotFor(state, batchId) === undefined) {
|
|
2825
|
-
|
|
2889
|
+
// #707: batch-setup dispatches through the batch's own profile too — it
|
|
2890
|
+
// is the batch's first agent, not an engine-internal step.
|
|
2891
|
+
claimAndSetup(deps, config, dispatchFor(batch), batchId, now, result);
|
|
2826
2892
|
}
|
|
2827
2893
|
}
|
|
2828
2894
|
for (const batch of deps.store.load().batches) {
|
|
2895
|
+
// #707: resolve this batch's profile once per pass — or dissolve/fall
|
|
2896
|
+
// back loudly when it no longer resolves (see `dispatchFor` above).
|
|
2897
|
+
const batchDispatch = dispatchFor(batch);
|
|
2829
2898
|
const slot = slotFor(deps.store.load(), batch.id);
|
|
2830
2899
|
// #609: a TERMINAL batch (`done`/`dissolved`) still holding a slot matches
|
|
2831
2900
|
// none of the status arms below and would fall through their `continue`,
|
|
@@ -2850,7 +2919,7 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2850
2919
|
}
|
|
2851
2920
|
if (slot && (slot.status === 'running' || slot.status === 'assigned')) {
|
|
2852
2921
|
if (batch.status === 'executing') {
|
|
2853
|
-
reconcileMemberSlot(deps, config,
|
|
2922
|
+
reconcileMemberSlot(deps, config, batchDispatch, batch.id, slot, now, result);
|
|
2854
2923
|
}
|
|
2855
2924
|
else if (batch.status === 'fixing') {
|
|
2856
2925
|
reconcileFixSlot(deps, config, batch.id, slot, now, result);
|
|
@@ -2867,7 +2936,7 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2867
2936
|
continue; // live but neither running/assigned (e.g. mid-verify) — next tick
|
|
2868
2937
|
if (batch.status === 'validating') {
|
|
2869
2938
|
// A local suite run, not a provider dispatch — unaffected by `paused`.
|
|
2870
|
-
runValidate(deps, config,
|
|
2939
|
+
runValidate(deps, config, batchDispatch, batch.id, now, result);
|
|
2871
2940
|
continue;
|
|
2872
2941
|
}
|
|
2873
2942
|
// #629: every branch below spawns a provider agent — hold the wedge
|
|
@@ -2876,7 +2945,7 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2876
2945
|
if (paused)
|
|
2877
2946
|
continue;
|
|
2878
2947
|
if (batch.status === 'deployed') {
|
|
2879
|
-
spawnReportAgent(deps, config,
|
|
2948
|
+
spawnReportAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
2880
2949
|
}
|
|
2881
2950
|
else if (batch.status === 'executing') {
|
|
2882
2951
|
// A prior spawn threw, or `claimAndSpawn` found zero free capacity —
|
|
@@ -2887,11 +2956,11 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2887
2956
|
// pre-checks BEFORE any pool/git work, so a full scheduler (or stale
|
|
2888
2957
|
// member context) exits here without exec'ing, and `claimAndSpawn`
|
|
2889
2958
|
// remains the authoritative capacity gate.
|
|
2890
|
-
spawnMemberContinuation(deps, config,
|
|
2959
|
+
spawnMemberContinuation(deps, config, batchDispatch, batch.id, now, result);
|
|
2891
2960
|
}
|
|
2892
2961
|
else if (batch.status === 'reviewing' || batch.status === 'shipping') {
|
|
2893
2962
|
// Same wedge, for a dead-or-never-claimed tail agent.
|
|
2894
|
-
spawnTailAgent(deps, config,
|
|
2963
|
+
spawnTailAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
2895
2964
|
}
|
|
2896
2965
|
else if (batch.status === 'fixing') {
|
|
2897
2966
|
// `beginFixAttempt` already recorded this member's ONE attempt as
|