β.1: amico-run thin process orchestrator (closes #1) - #7
Conversation
…ension Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ract enforcement Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ash spike (S31 grep green) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ontract end-to-end Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Quick question before I review, why are we converting to pnpm workspace? Or maybe rather, what do you mean by that? |
|
I understand it as packaging this best we should use a monorepo type space (it would be easier to package and give to customers), see claude's argument below:
The alternatives were considered explicitly in the design round: a second esbuild entry inside the extension's src/ (no restructuring, but boundary-by-convention and you still do the conversion for 0.1), and a standalone repo (maximum isolation, but it reintroduces the sibling-tree path resolution that made the spike hard-fail on clean machines, and splits CI). Workspace was the middle that pays for itself. As for pnpm specifically — no new decision there: the repo was already pnpm (pnpm-lock.yaml predates us), so its workspace feature was the zero-friction option. |
There was a problem hiding this comment.
β.1 looks good . Pulled it locally: build + typecheck (incl. the extension type-checking against @amicode/amico-run) green, fast tier 44/44, and a real-julia run through the bundle emitted a run dir that validates against all three provisional schemas. Contract enforcement (manifest-first, FINISHED-terminal, abort lanes, the §6 matrix) is exactly right, and the tests are behavioral rather than smoke.
One correction to confirm: the Piccolo 0.11 references (slow-tier solve_common.jl header + the PR body) look mistaken, should those be Piccolo 1.19, or are they actually Piccolissimo.jl version references?
Inline notes below are all non-blocking. The only one worth a decision is the updateLatest tmp-name race (latent until β.5 can submit concurrently), but maybe that doesn't matter while we are in phase 1 trying to roll this out to schuster? I'll leave that up to your discretion
| } | ||
|
|
||
| export function updateLatest(runsRoot: string, runId: string): void { | ||
| const tmp = join(runsRoot, '.latest.tmp') |
There was a problem hiding this comment.
Fixed tmp name → races concurrent same-lab submits; scope to runId.
| } | ||
|
|
||
| export function appendIndex(runsRoot: string, runId: string, createdAt: string, scriptPath: string): void { | ||
| appendFileSync(join(runsRoot, 'index'), `${runId}\t${createdAt}\t${scriptPath}\n`) |
There was a problem hiding this comment.
Unescaped tab/newline in script_path corrupts index.
| return f.exitCode === 0 ? 1 : f.exitCode | ||
| } | ||
|
|
||
| main(process.argv.slice(2)).then(c => { process.exitCode = c }) |
There was a problem hiding this comment.
No top-level .catch → unexpected throw exits 1, not 64.
| type RunStatus, type SubmitOpts, | ||
| } from './types.js' | ||
|
|
||
| const ORCHESTRATOR_VERSION = '0.1.0' |
There was a problem hiding this comment.
Hardcoded version duplicates package.json; will drift.
| expect(validateFinished(readToml(join(runDir, 'FINISHED'))).ok).toBe(true) | ||
| const result = readToml(join(runDir, 'result.toml')) | ||
| expect(validateResult(result).ok).toBe(true) | ||
| expect(result.fidelity as number).toBeGreaterThan(0.99) |
There was a problem hiding this comment.
Asserts >0.99 but PR body claims >0.9999.
There was a problem hiding this comment.
I am actually fine with this, don't address right now, leaving for later
|
|
||
| # Per-iter callback machinery lives in DirectTrajOpt's internal IpoptSolverExt | ||
| # module (not re-exported by Piccolo); reach it through the solve! method table. | ||
| const IpoptSolverExt = first(filter( |
There was a problem hiding this comment.
Fragile reach-through; don't canonize in β.3 template especially if we are going to end up mainly supporting MadNLP, and Altissimo!
…xpected-throw→exit 64, updateLatest per-runId temp (concurrent-submit race), version single-sourced from package.json; solve_common.jl on Piccolo 1.19 via public Piccolo.Callbacks (no reach-through) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (no reach-through); rollout+subspace fidelity (F=0.99994, ≤1) Re-vetted against Piccolo 1.19 (what Pkg.add installs today, per Jack's #7 note). Kills the methods(solve!) IpoptSolverExt reach-through — Callbacks is public in 1.19. Fidelity now = unitary_fidelity over the computational subspace from a fresh unitary_rollout (was reading >1 from raw-propagator integrator drift). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks Jack — pushed fixes (force-update on the rebased stack):
Fast tier 45/45. The struck-through >0.99/>0.9999 note I left as-is per your call. |
β.1:
amico-runthin process orchestratorCloses #1.
Implements the D9 thin orchestrator per the spec
spec-20260610-101245-amico-run-thin-orchestrator(plan:plan-20260610-103608-amico-run-thin-orchestrator).What this is
packages/extension/(no behavior change).packages/amico-run/(@amicode/amico-run): TypeScript library (Executor.submit(scriptPath, opts) → RunHandle) + CLI (amico-run <script.jl> [--executor local] [--lab <id-or-path>] [--runs-root] [--julia] [--project] [--sysimage]), bundled to a single zero-dependencydist/amico-run.jswith a bash launcher that resolves node or exits 64.manifest.tomlwritten first (atomic),runs/indexappended,latestsymlink swung, julia spawned with cwd = run dir, stdout/stderr relayed (AMICODE_ITER→ iter events) intorun.log,FINISHED{status, exit_code}written last (atomic) keyed purely off the process result. The orchestrator's verdict overwrites any FINISHED a script fakes.~/.amico/runs/<lab-id>/(survives reboot, per-lab partition; resolves Q124). NoAMICO_*env vars — all argv (resolves Q90, satisfies S37). Lab pointer passed through verbatim, never parsed (D10); derivedlab_idrecorded in the manifest.manifest.toml/FINISHED/result.toml(superseded by Phase 0' SchemaPackage; snake_case keys are the frozen contract).bin/amico-runbash spike.Acceptance criteria → evidence
local_executor.test.ts(manifest-before-submit-resolves, schema-validated),failure_lanes.test.ts(manifest-before-spawn fixture, tight-loop no-partial-file reader), slow tierfailure_lanes.test.ts(9 matrix rows incl. spawn-failure-127, external-signal-143, bogus-FINISHED-overwrite),abort.test.ts(SIGTERM 143 / SIGKILL-after-grace 137 / idempotency)s31.test.tsgrep rule, runs in CIcli.test.ts— 6 end-to-end tests spawning the real bundleExecutor.submit → RunHandleconsumed by the extensionpackages/extension/src/executor_check.tstype-level check, compiled in CIFast tier: 44 tests green (
pnpm --filter @amicode/amico-run test), no Julia required. Slow tier:AMICO_TEST_JULIA_PROJECT=<piccolo env> pnpm --filter @amicode/amico-run test:slow.Notes for the β.5 implementer
run_id,exit_code,lab_id); the TS API is camelCase. This supersedes the architecture §3 sketch (exitCode,runId).sizeClass(architecture §3/Q47) is intentionally absent from the manifest — underivable under D10; the Phase 1 Scheduler must obtain it elsewhere.FINISHED, neverresult.toml. Orchestrator hard-kill (SIGKILL/power loss) leaves manifest-without-FINISHED — Phase 1 stale-run detection (Q70) handles that; β.1 doesn't recover it.solve_common.jldoubles as the seed for the β.3 AGENTS.md template (AMICODE_ITER via DirectTrajOpt'sIpoptSolverExt.Callbacks, atomic result.toml, JLD2 export).🤖 Generated with Claude Code