mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-08 17:58:13 +01:00
feat(ship-budget): a light diff keeps its segment instead of delegating it (GSTA-167)
check --runtime-lines <n> continues at the Step 7 and 12 boundaries when the diff has at most 50 runtime lines and the segment's calibrated calls at this run's per-call charge cost at most 12M: a child's fixed 2-4.5M (gstack-evals #34) saves at most 3/8 of each parent call. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
cc28dd35c5
commit
dbe700417c
5 files changed
+104
-8
No files matched your search
@@ -13,7 +13,7 @@ import * as path from 'node:path';
|
||||
import { resultLine } from './result-codes';
|
||||
import {
|
||||
BudgetError, DEFAULT_TAIL, DELEGATE_SEGMENT_CALL, DELEGATE_START_CALL, METER_CONTRACTS, SEGMENTS, SHIP_STEPS,
|
||||
admits, applyReading, budgetError, budgetLine, ledgerFileFor, loadCalibration, measureSettled, mintRunId, mutateLedger, newEpoch,
|
||||
admits, applyReading, budgetError, budgetLine, keepsSmallRun, ledgerFileFor, loadCalibration, measureSettled, mintRunId, mutateLedger, newEpoch,
|
||||
parseNext, parseRecordBlock, parseStep, parseTokens, pointMarker, projector, readLedger, reserveIn, restoreRun, reviewPaths,
|
||||
runDirFor, settleIn, stepProjection, tally, unitConstants, unreservedDispatches, usageError, writeLedger,
|
||||
type Ledger, type MeterKind, type Next,
|
||||
@@ -94,7 +94,7 @@ export function writeReturnFile(file: string, lines: string[]): void {
|
||||
}
|
||||
|
||||
/** Admission and the delegate rule for one boundary; reserves the admitted work under a cap. */
|
||||
export function decide(l: Ledger, step: string, next: Next, call: number | null, startThreshold = false): Outcome {
|
||||
export function decide(l: Ledger, step: string, next: Next, call: number | null, startThreshold = false, runtimeLines?: number): Outcome {
|
||||
const p = projector(l);
|
||||
const nextId = next.kind === 'step' ? next.step : `dispatch:${next.roles.join(',')}`;
|
||||
const { prior, tokens: projected } = p.project(next);
|
||||
@@ -102,7 +102,8 @@ export function decide(l: Ledger, step: string, next: Next, call: number | null,
|
||||
return { stdout: stopLines(l, { call, next: nextId, step, projected, reason: 'budget_exhausted' }), exit: 3 };
|
||||
}
|
||||
const boundary = next.kind === 'step' && (startThreshold ? next.step === '1' : next.step === '7' || next.step === '12');
|
||||
const wants = boundary && (startThreshold ? call !== null && call >= DELEGATE_START_CALL : call === null || call >= DELEGATE_SEGMENT_CALL);
|
||||
const small = boundary && !startThreshold && next.kind === 'step' ? keepsSmallRun(loadCalibration(l.calibration), { runtimeLines, from: next.step, to: SEGMENTS[next.step]!, call }) : null;
|
||||
const wants = boundary && !small && (startThreshold ? call !== null && call >= DELEGATE_START_CALL : call === null || call >= DELEGATE_SEGMENT_CALL);
|
||||
if (wants && (l.role === 'child' || l.delegate === 'never')) {
|
||||
return { stdout: stopLines(l, { call, next: nextId, step, projected, reason: 'context_threshold' }), exit: 3 };
|
||||
}
|
||||
@@ -110,7 +111,7 @@ export function decide(l: Ledger, step: string, next: Next, call: number | null,
|
||||
return { stdout: [budgetLine(l, { call, next: nextId, projected, verdict: 'delegate' }),
|
||||
`DELEGATE: Steps ${next.step}-${SEGMENTS[next.step]} go to a fresh child: gstack-ship-budget handoff --run ${l.run} --from ${next.step} --to ${SEGMENTS[next.step]} --base origin/<base> (ship/sections/budget-handoff.md)`], exit: 0 };
|
||||
}
|
||||
const lines = [budgetLine(l, { call, next: nextId, projected, verdict: 'continue' })];
|
||||
const lines = [budgetLine(l, { call, next: nextId, projected, verdict: 'continue' }), ...(small ? [small] : [])];
|
||||
if (l.cap !== null) {
|
||||
const r = reserveIn(l, { kind: next.kind, step, next: nextId, tokens: projected, prior, ...(next.kind === 'dispatch' ? { roles: next.roles } : {}) });
|
||||
lines.push(`RESERVATION: id=${r.id} next=${nextId} tokens=${projected}`);
|
||||
@@ -190,7 +191,9 @@ export function cmdStart(argv: string[], ctx: Ctx): Outcome {
|
||||
* reviewer consumed), measure them, then admit the next step or dispatch.
|
||||
*/
|
||||
export function cmdCheck(argv: string[], ctx: Ctx): Outcome {
|
||||
const o = parseArgs(argv, ['run', 'record', 'step', 'next', 'reading', 'prior-reading', 'returned']);
|
||||
const o = parseArgs(argv, ['run', 'record', 'step', 'next', 'reading', 'prior-reading', 'returned', 'runtime-lines']);
|
||||
const runtimeRaw = str(o, 'runtime-lines');
|
||||
if (runtimeRaw !== undefined && !/^\d+$/.test(runtimeRaw)) throw usageError(`--runtime-lines '${runtimeRaw}' is not a line count; pass runtime_lines from gstack-review-plan --json`);
|
||||
const step = parseStep(str(o, 'step'));
|
||||
const next = parseNext(str(o, 'next'));
|
||||
const returned = (str(o, 'returned') ?? '').split(',').filter(Boolean);
|
||||
@@ -206,7 +209,7 @@ export function cmdCheck(argv: string[], ctx: Ctx): Outcome {
|
||||
if (done) settleIn(l, r);
|
||||
}
|
||||
const drift = freshReading(l, o) ? measureSettled(l) : [];
|
||||
const out = decide(l, step, next, call);
|
||||
const out = decide(l, step, next, call, false, runtimeRaw === undefined ? undefined : Number(runtimeRaw));
|
||||
return { stdout: [...drift, ...out.stdout], exit: out.exit };
|
||||
});
|
||||
}
|
||||
|
||||
+28
-1
@@ -25,7 +25,7 @@ import { resolveStateRoot } from './state-root';
|
||||
import { resultLine, type ResultCodeName } from './result-codes';
|
||||
import { capyDriveFolder } from './autoplan-checkpoint';
|
||||
import { BIN_DIR, runScriptSync } from './script-invocation';
|
||||
import type { Role } from './review-depth';
|
||||
import { SMALL_DIFF_LINES, type Role } from './review-depth';
|
||||
|
||||
export const SHIP_STEPS = ['0.9', '1', '2', '3', '4', '5', '6', '6.5', '7', '8', '9', '10', '11', '11.5', '12', '13', '14', '14.5', '15', '16', '17', '18', '19', '20', '21', '22'] as const;
|
||||
export const DISPATCH_ROLES = ['specialist', 'red-team', 'adversarial-native', 'outside-voice', 'fix-check'] as const satisfies readonly Role[];
|
||||
@@ -199,6 +199,33 @@ export function segmentEstimate(cal: Calibration, unit: string, from: string, to
|
||||
return SHIP_STEPS.slice(i, j + 1).reduce((sum, s) => sum + stepProjection(cal, unit, s) + (cal.segment_dispatches[s] ?? 0) * k.per_dispatch, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Small-run rule (GSTA-167 PR 4). A delegation pays a fixed child cost (boot, install check, verify-handoff,
|
||||
* section reads, reconcile: gstack-evals #34's two children cost 4.53M, 2-4.5M each) to save the parent's
|
||||
* context above the host floor on each call of the segment. #34's parent calls ran 0.2-0.3M over a 0.15M
|
||||
* floor, so a fresh child saves at most about 3/8 of a call, and a segment under CHILD_FIXED_COST over that
|
||||
* share (12M) cannot pay for its child. A light-depth diff (runtime lines at most SMALL_DIFF_LINES) keeps
|
||||
* its segment when the segment's calibrated calls at this run's own per-call charge stay under that.
|
||||
*/
|
||||
export const SMALL_RUN_LINES = SMALL_DIFF_LINES;
|
||||
export const CHILD_FIXED_COST = 4_500_000;
|
||||
export const SMALL_RUN_SEGMENT = CHILD_FIXED_COST / 0.375;
|
||||
/** #34's per-call charge midpoint, used when this call's charge is unknown. */
|
||||
export const SMALL_RUN_CALL = 250_000;
|
||||
|
||||
export function smallRunSegment(cal: Calibration, from: string, to: string, call: number | null): number {
|
||||
const steps = SHIP_STEPS.slice(SHIP_STEPS.indexOf(from as any), SHIP_STEPS.indexOf(to as any) + 1);
|
||||
return steps.reduce((n, s) => n + (cal.step_calls[s] ?? cal.step_calls.default ?? 0), 0) * (call ?? SMALL_RUN_CALL);
|
||||
}
|
||||
|
||||
/** The SMALL_RUN line when a segment boundary should continue instead of delegating; null keeps the delegate rule. */
|
||||
export function keepsSmallRun(cal: Calibration, o: { runtimeLines: number | undefined; from: string; to: string; call: number | null }): string | null {
|
||||
if (o.runtimeLines === undefined || o.runtimeLines > SMALL_RUN_LINES) return null;
|
||||
const segment = smallRunSegment(cal, o.from, o.to, o.call);
|
||||
if (segment > SMALL_RUN_SEGMENT) return null;
|
||||
return `SMALL_RUN: runtime_lines=${o.runtimeLines}<=${SMALL_RUN_LINES} segment=${o.from}-${o.to} projected=${segment}<=${SMALL_RUN_SEGMENT}; continuing here: a child's fixed ${CHILD_FIXED_COST} would cost more than it saves`;
|
||||
}
|
||||
|
||||
export const runDirFor = (env: NodeJS.ProcessEnv, run: string) => path.join(resolveStateRoot(env), 'ship-runs', run);
|
||||
export function mintRunId(now = new Date()): string {
|
||||
return `ship-${now.toISOString().replace(/[-:]/g, '').slice(0, 15)}-${crypto.randomBytes(3).toString('hex')}`;
|
||||
|
||||
Reference in new issue
Block a user