Typed ProblemSpecs (Plan 2) + learning-loops run ledger L1 (Plan 3) - #212
Merged
Conversation
…issimo) Vendor both emitted variants into packages/schema/schemas/: the FULL variant as problemspec.schema.json (Piccolissimo.jl @ 9098f6f) and the OSS variant as problemspec.oss.schema.json (Piccolo.jl @ c3884122). Provenance shas live in *.schema.json.sha sidecars, kept OUT of the JSON so the vendored files stay byte-identical to the emitted schemas for the Task 9 cross-repo vendoring-drift gate. Register the FULL variant as the `problemspec` kind in SCHEMAS ONLY (NOT SUPPORTED_VERSIONS_BY_KIND — it is a top-level oneOf with an INTEGER schema_version enum [1] inside each branch, so it has no top-level properties.schema_version; plan review correction #6). Add a kindForFilename rule (problem.toml -> problemspec). Widen the version-map type to exclude problemspec alongside finished. TDD: test/problemspec.test.ts asserts the kind is registered, a valid control spec validates ok, an unknown template and an unknown top-level key are rejected, and problem.toml routes to problemspec. Add the valid golden fixture and update the SCHEMA_KINDS exact-set assertion. Full @amicode/schema suite + typecheck green (50 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…formance Task 4 (Plan 2). Adds 5 valid problemspec fixtures (x-control, energy-polish, min-time, free-phase, rollout) and 5 invalid (unknown-field, free-dt-bare-true, free-phase+bilinear, template<->pulse mismatch, bad-objective-kind). Extends the ajv sweep (test/problemspec.test.ts) and the JSONSchema.jl lane (julia/runtests.jl KINDS + a dedicated dual-validation testset) so both validators must agree on accept/reject for every fixture. Routes problem.toml -> problemspec in validate.jl. Verified: JSONSchema.jl v1.5.0 draft-07 oneOf + if/then matches ajv on all fixtures (no divergence). The plan's "R_ddu on SplinePulseProblem" negative is NOT schema-catchable (R_ddu is a plain optional number valid on every template; the applicability rule is a materialize-layer trait) — substituted bad-objective-kind and flagged the gap to Plan 1 emit_schema. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…guage) Task 5 (Plan 2). src/hashing.ts mirrors Piccolo's src/specs/hashes.jl byte-for-byte: canonicalJson (sorted ASCII keys, JSON string escaping, ECMAScript Number::toString via String(x) — the reference algorithm hashes.jl was hand-built to match) plus fullDict/structureFields, which apply the SAME parse_spec defaults + wire projection the Julia structs carry so smol-toml (int) and TOML.jl (float) parses hash the same logical spec. structureHash/problemHash are sha256hex over the canonical bytes. test/hashing.test.ts asserts both hashes equal the Julia-emitted sidecars for every fixture, and checks the landmine's canonical JSON is byte-identical to Julia's full_dict / structure_fields output (T=100.0 float -> bare "100"). Verified byte-for-byte, e.g. landmine.toml: structure_hash = ff3a6210ca7b094c2188e87bbc9c24e4eaac770d39b0067ab9afeb5756485b0d problem_hash = b7ec369a0952b7365bd182889a9ad5cacf427d48bef5aa4095c784d4b6d3c1c3 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend ac6_drift_check.sh with a problemspec perturbation case: canary the control branch's required (oneOf[0]) so a control fixture matches zero branches, and assert BOTH the TS amico-validate bin AND julia validate.jl flip accept->reject, then revert. Refactored the run case + new case into a shared assert_flip helper; the trap now reverts the whole schemas dir. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump solvespec to v4: $id .../solvespec/v4, schema_version enum
["1","2","3","4"], new `problem_spec` property (oneOf string|object), and a
top-level oneOf enforcing exactly one of {script_path, problem_spec}. lab_id
stays required; additionalProperties:false stays strict. problem_spec is the
typed Piccolo ProblemSpec runner target that amico-run routes to
Piccolo.Specs.solve_spec (Task 8).
Per review correction #4, SUPPORTED_VERSIONS_BY_KIND auto-derives from the
enum, so no schemas.ts edit is needed; fixed the stale index.ts header
comment. TDD: problem_spec-only (string+object) validate, both/neither fail,
lab_id required, strict-unknown holds. TS 79/79, Julia round-trip 35/35.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…spec A v4 solvespec may carry problem_spec (path or inline object) instead of script_path. Wire the scriptless path end-to-end (review correction #2): - types.ts: add problem_spec to SpecStamp; Executor.submit scriptPath optional - launch.ts: parse --spec up front, detect problem_spec, stand down the no-script guard + script read; stamp problem_spec onto opts.spec - gate.ts: skip the import scan for a scriptless spec (the ProblemSpec, validated against Piccolo's registries in step 1, is the entitlement surface) - subcommands.ts: estimate rejects a scriptless spec cleanly (no crash) - local_executor.ts: scriptPath optional; spawn `julia -e 'using Piccolo; Piccolo.Specs.solve_spec(ARGS[1]; run_dir=pwd())' <path>`; an inline object is serialized to <runDir>/problem.toml AFTER the manifest (run-dir contract: manifest FIRST, then problem.toml, solvespec.json, index, latest); a string path is passed straight through - remote_executor.ts: reject problem_spec (local-only until Phase 4) TDD (julia spawn mocked via fakeJulia): problem_spec path + inline object route to solve_spec with run.toml written first and ITER/DONE classified; a script_path spec still runs the script directly. amico-run 345/345, typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the schema-roundtrip job for problemspec (Task 9): - the existing runtests.jl lane already runs the problemspec dual-validation sweep (ajv == JSONSchema.jl) and the AC6 step now covers the problemspec conditional schema — update the job comment to reflect both. - add a cross-repo vendoring-drift gate: scripts/vendoring_drift_check.sh git-fetches each vendored problemspec schema's source at the sha recorded in its *.sha sidecar and cmp's byte-for-byte — it NEVER re-runs regenerate.jl (correction #1). The OSS variant is checked against public Piccolo tokenlessly (a reachable-but-differing source reds CI); the FULL variant (private Piccolissimo) is checked only when PICCOLISSIMO_SCHEMA_TOKEN is set, so amicode CI carries no mandatory private dep — its authoritative gate lives in Piccolissimo's own CI. Failure model: reachable source + byte mismatch → hard fail; source not reachable at the pinned sha → warn + skip (emission side Tasks 1-2 not pushed yet), so the gate auto-hardens once the source lands. Added a `path` field to the sidecars so the script reads the source path (no hardcoding), and a VENDOR_DRIFT_SRC_BASE local-test seam. Validated locally: YAML parses (11 steps); drift script exits 0 on identical source, 1 on drift, and soft-skips the currently-unreachable pinned source. The vendoring-drift step is push-gated on CI and auto-hardens when Piccolo Task 1 / Piccolissimo Task 2 are pushed and the sidecar shas point at real commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan 3 (learning-loops L1) Task 1. ledgerPath() = $AMICO_LEDGER || ~/.amico/ledger/runs.jsonl; appendRecord/readRecords over JSONL; the six discriminated record kinds (solve|verdict|attempt_error|fallback|override|burn). O_APPEND single-writer atomicity guarded by a per-record PIPE_BUF size ceiling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan 3 (learning-loops L1) Task 2. draft-07 oneOf discriminated on `type` over the six record kinds (solve|verdict|attempt_error|fallback|override|burn); source enum user|replay|simulated (simulated = Prova isolation bridge); override carries auto_accepted. Registered in SCHEMAS ONLY (never SUPPORTED_VERSIONS_BY_KIND — it is a top-level oneOf with no properties.schema_version, which would crash the version-map builder at load, same as problemspec — review correction #1). appendRecord now validates on write and throws on invalid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan 3 (learning-loops L1) Task 4. bucketN/bucketT (documented edges); primary key structure_hash x (n_bucket, t_bucket), fallback (platform, template, trajectory, levels, buckets) below K_MIN; source=user filter (excludes replay AND simulated); solve-verdict join on problem_hash; verified = count of matched solves whose problem_hash has an agree verdict; medians+IQR per numeric param (Q,R,du_bound,N, max_iter) + integrator mode; honest provenance 'n=<total> runs, <verified> verified'; mechanical confidence with the interim medium cap (any unverified run bars high — keeps ledger-sourced auto-apply unreachable until L-H). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan 3 (learning-loops L1) Task 3. `amico ledger append` (--json or stdin → validate → appendRecord; extension stanzas shell into this, never touch runs.jsonl directly) + `amico ledger query --structure-hash --n --t` (delegates to ledger_query.ts). Registered in SPINE_VERBS so CLI + MCP facade + --help are free. Bundle test spawns 24 concurrent subprocess appenders proving cross-process O_APPEND atomicity (no interleaving). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LocalExecutor.settle() now derives + appends one `solve` ledger record after writeFinished: structure_hash/problem_hash/versions/converged come from result.toml's [params]; the base summary AND the recommendable knobs (Q/R/du_bound/max_iter/integrator) come from the solvespec (the typed ProblemSpec run.toml's script_path resolves to) — without the knobs, ledger_query's medians are permanently empty, silently breaking Tasks 6/9. source defaults to "user", overridable via AMICO_LEDGER_SOURCE for L-I replay. Any read/parse/validation failure is caught and logged — a ledger hiccup must never fail a run. Plan 3 (learning-loops L1) Task 5. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…amping (L-A) amicode_recommend gains action="query": resolves the active workspace's most recent structure_hash (+ N/T) from its last run's result.toml / solvespec, shells `amico ledger query`, and returns medians/IQR with honest "n runs, m verified" provenance via a new ledger_client.ts. All ledger I/O shells to the `amico` CLI (single-writer discipline — opencode-plugin's Bun runtime can only resolve relative sibling imports, so @amicode/amico-run/@amicode/schema aren't reachable here regardless; structure_hash is read back from Task 5's result.toml stamp rather than recomputed). propose/outcome events are now stamped with structure_hash; an outcome="overridden" call additionally appends an `override` ledger stanza (recommended value recovered from the matching `proposed` event). Ledger-sourced confidence is clamped to at most "medium" — belt-and-suspenders on top of ledger_query.ts's own interim cap, so veloce (which auto-accepts only "high") can never auto-apply an unverified prior. Also re-exports structureHash/problemHash from @amicode/schema's package root (packages/schema/src/hashing.ts) — needed by this task, previously only reachable via a package-internal relative path. Plan 3 (learning-loops L1) Task 6. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds three pure, tested stanza builders to ledger_client.ts (attemptErrorStanza, fallbackStanza, verdictStanza) plus resolveRunHashes (reads a run dir's result.toml [params] for the structure_hash/problem_hash Task 5 already stamps there). All route through appendStanza → `amico ledger append` — the extension never writes runs.jsonl directly. Wires two new bookkeeping tools, amicode_report_attempt_error and amicode_report_fallback: neither a spec-validation failure nor a tier fallback has an in-process hook in the extension today (both happen inside a bash-invoked `amico run --spec`), so — mirroring amicode_recommend's own "record what happened elsewhere" doctrine — the agent reports what it observed in the CLI's output. Wires the `verdict` stanza into the existing amicode_verify tool (a real call site: agree/disagree + both fidelities are already its args), joined to the run's `solve` record via problem_hash. Plan 3 (learning-loops L1) Task 7. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds the `ledger` provenance source to confidence-rubric.md's resolution order (between own-precedent and demo) with the mechanical (n, IQR, verified-fraction) → high|medium|low table and the interim `medium` cap note (applied twice: ledger_query.ts's rankConfidence and again at the amicode_recommend tool boundary). Writes packages/amico-run/docs/ledger.md: single-writer discipline (and why the extension's Bun-runtime import constraints make shelling `amico ledger append` structural, not stylistic); ops-data-vs-vault- knowledge; why only source="user" feeds priors (replay = L-I nightly fleet, simulated = Prova's isolation bridge); no-ML/mechanical-only doctrine; and the charter/16 Tier-2-substrate + charter/18 severed- flywheel reconnection this ledger grounds. Plan 3 (learning-loops L1) Task 8. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Cross-package integration test (packages/extension/test/ledger_e2e.test.ts): two REAL LocalExecutor solves of the same structure (same structure_hash, distinct problem_hash — review correction #3), one gets an `agree` verdict via the real built `amico` CLI, and ledger_client.ts's queryLedger + selectRecommendations (the logic behind amicode_recommend action="query") return "n=2 runs, 1 verified" provenance with confidence capped at medium — spec success criteria 1 and 5, exercised end-to-end rather than mocked. CI: adds a ledger-record.json fixture + gates it through amico-validate in ci.yml's fast job. Confirmed (not just asserted) that the ledger suites are already swept: `pnpm -r run test` across schema (92/92), amico-run (374/374), and extension (766/766, 6 pre-existing skips) all green together. Plan 3 (learning-loops L1) Task 9 — L1 complete. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
structure_hash covers a problem's TYPE skeleton, deliberately not its task:
structureFields() takes system/trajectory/pulse/template/goal_treatment/
free_dt/free_phase/objective_kinds/integrator/wrappers/solver but never
goal.gate. So a CZ and an X gate on the same system, template and solver
hash identically. That is correct for warm-pool routing -- the gate does
not change the Julia type, which is the whole point of the hash -- but
wrong for priors: neither the primary key (structure_hash x N-bucket x
T-bucket) nor the fallback (platform/template/trajectory/levels + buckets)
discriminated the goal, while summary.goal was recorded and even required.
Net effect before this change: a hard CZ's median Q/R/du_bound/max_iter
were recommended for a trivially easier X gate and vice versa, averaging
two difficulty populations behind honest-looking provenance.
The fix belongs in the retrieval key, NOT in structure_hash -- the hash's
coarseness is load-bearing for routing and precompilation ("same
structure_hash => same concrete types"). The two jobs the hash serves want
different granularities; retrieval is where that difference is paid.
- QueryKey gains `goal?`, applied to BOTH primaryMatch and fallbackMatch.
- Omitting it stays legal and coarse, but provenance then reads "goal not
keyed" instead of implying a tighter key than was used -- L-A's doctrine
is visible provenance, so silent discrimination would make it lie.
- `amico ledger query --goal <g>`; usage + synopsis updated.
- resolveWorkspaceSpecContext extracts goal from the run's solvespec,
mirroring settle()'s derivation exactly (goal.gate, else goal.kind), since
goal is never stamped into result.toml's [params] -- without that read the
key would be silently inert in production.
- queryLedger passes --goal through; amicode_recommend action:"query" supplies it.
- confidence-rubric.md + docs/ledger.md document the goal leg and why the
hash is not the retrieval key.
Tests: amico-run 376 (+2: CZ/X separation on one structure_hash, incl. the
mixed-query median made visible; goal applied on the fallback path too),
extension 769 (+3: goal extracted from [goal].gate, [goal].kind fallback,
absent-goal stays undefined). schema 92 unchanged.
This was referenced Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two slices of the typed-ProblemSpecs initiative, in one branch because Plan 3 builds directly on Plan 2's hashing.
Green:
@amicode/schema92/92 ·@amicode/amico-run376/376 · extension 769 passed / 6 pre-existing skips. Verified across 4 consecutive fullpnpm -rsweeps.Plan 2 — typed specs reach the TypeScript side
problemspecschema vendored from the Julia registries, in two variants: the OSS one emitted from Piccolo (c3884122) and the FULL one from private Piccolissimo (9098f6f). Emitting the full variant from Piccolissimo rather than Piccolo is deliberate — a public Piccolo emission would leak private capability names.hashing.tsis byte-identical to Julia'sPiccolo.Specs.structure_hash/problem_hash, pinned against Julia-emitted sidecar fixtures. This works because JS'sNumber::toStringis the ECMAScript algorithmhashes.jlwas hand-built to match — please don't substitute another float formatter on either side.problem_spec(oneOf [string, object]), xor withscript_path, integerschema_versionenum.amico-runroutessolvespec.problem_spec→Piccolo.Specs.solve_spec.Schema-registration gotcha, documented in code:
problemspecandledger-recordregister inSCHEMASonly, neverSUPPORTED_VERSIONS_BY_KIND— both are top-leveloneOfshapes with noproperties.schema_version, and registering them there crashes the module at load.Plan 3 — the run ledger (learning loops L1)
Append-only JSONL at
~/.amico/ledger/runs.jsonl. Six record types:solve,verdict,attempt_error,fallback,override,burn. Atomic via PIPE_BUF +O_APPEND, schema-validated on every append.amico-runis the single writer. Every extension-side stanza shellsamico ledger append; the extension never writes the JSONL. A failed ledger write never fails a run.source:"user"solves (excluding bothreplayandsimulated), with "verified" := anagreeverdict joined onproblem_hash, visible provenance, and a mechanical(n, IQR)→ confidence rubric.high, applied twice. Veloce auto-accepts onlyhigh, so without the cap a tight-IQR verified prior would auto-apply the moment L-A shipped, with no per-structure trust gate in place. L-H earns that back.problem_hashand oneagreeverdict →"n=2 runs, 1 verified"with confidence capped atmedium.One real defect found and fixed here (
e164855)structure_hashcovers a problem's type skeleton, not its task — it never includesgoal.gate, so a CZ and an X gate on the same system/template/solver hash identically. That's correct for warm-pool routing (the gate doesn't change the Julia type) but wrong for priors: neither the primary nor the fallback retrieval key discriminated the goal, whilesummary.goalwas recorded and even required. A hard CZ's medianQ/max_iterwere being recommended for a trivially easier X gate.Fixed in the retrieval key, not the hash — the hash's coarseness is load-bearing for routing and precompilation. Omitting the goal stays legal but the provenance then reads
goal not keyedrather than implying a tighter key than was used.Divergences from the plan, all forced by reality
@amicode/schema'sstructureHashis unreachable fromamicode_tools.ts— its Bun plugin runtime resolves only relative sibling imports, not bare package specifiers. Sostructure_hashis read back from the prior run'sresult.toml(the valuesettle()already stamped) instead of recomputed. Arguably better: one writer of that hash, no chance of two implementations drifting.action:"query"needs ≥1 completed run in a workspace — there's no propose-time spec-assembly pipeline yet. Matches L-A's intent (priors are for a repeat formulation) but worth a human's confirmation.attempt_error/fallbackhad no in-process hook — both happen inside a bash-invokedamico run. Rather than invent one, this adds two bookkeeping tools,amicode_report_attempt_errorandamicode_report_fallback, followingamicode_recommend's record-what-happened-elsewhere pattern.verdicthad a real call site and is wired intoamicode_verify. This is new tool surface — a design call, flagged rather than buried.platformdoesn't exist inproblemspec.schema.json, sosettle()derives it fromsystem.template(MultiTransmonSystem→multi_transmon). Informational only, but the fallback retrieval key keys on it, so the heuristic is load-bearing there.Known caveats
packages/schemaregisters the FULLproblemspec.schema.jsonas the shippedproblemspeckind, with the OSS variant vendored alongside "for package-access staging." Only a comment says which to ship. An OSS extraction that keeps this package as-is would expose six private-only enum values (altissimobackend,continuation/stagedstrategies,hermite_bending_energy/hermite_c2objectives,robustwrapper). Harmless today since this repo is private — but it wants a build-time gate before the open-core split ships.allOf4→7). Hash sidecars are unaffected (they hash spec data, not the schema), but both vendored schema variants will need re-staging after it merges.amico-run's bundle-and-spawn tests are load-sensitive underpnpm -r: two flakes were observed and then failed to reproduce across 4 consecutive green sweeps (a different test each time, neither touching changed code paths). Pre-existing fragility; will eventually bite CI.🤖 Generated with Claude Code