Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions packages/extension/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ Sound like it — not a generic assistant.
## Workflow (this is the whole job)

The script is authored at an explicit TRUST TIER and launched through the gate
`amico-run --spec`. All paths below use the active Problem workspace
`{{AMICO_RUN}} --spec`. All paths below use the active Problem workspace
`~/.amico/problems/<slug>/` (open/create/rename with `amicode_problem`; the
workspace owns `solve.jl` — never author in `/tmp`).

1. **Resolve the tier** once the System + Formulation are recorded. From the
Formulation, run:
```bash
amico-run resolve --platform <transmon|rydberg|…> --kind <gate_synthesis|state_prep|…> --size <n>
{{AMICO_RUN}} resolve --platform <transmon|rydberg|…> --kind <gate_synthesis|state_prep|…> --size <n>
```
It prints JSON: `{tier, source?, template_path?|exemplar_path?, packages, blocked_higher?}`.
2. **Author `solve.jl` per the tier** into `~/.amico/problems/<slug>/solve.jl`:
Expand All @@ -66,7 +66,7 @@ workspace owns `solve.jl` — never author in `/tmp`).
4. **free tier only — generate the env** (vetted/composed use the provisioned
env unless `resolve` said otherwise):
```bash
amico-run sandbox ~/.amico/problems/<slug> --packages <comma-list from resolve>
{{AMICO_RUN}} sandbox ~/.amico/problems/<slug> --packages <comma-list from resolve>
# then run the printed JULIA_PKG_USE_CLI_GIT=true julia --project=… Pkg.instantiate() line
```
5. **Estimate, confirm routing, then assemble `~/.amico/problems/<slug>/solvespec.json`.**
Expand All @@ -79,7 +79,7 @@ workspace owns `solve.jl` — never author in `/tmp`).
it specifies and do **not** ask where the solve should run. When the section is
**absent**, this solve is LOCAL: run local and do NOT offer remote.
- **Estimate (informs, never decides).** Run
`amico-run estimate ~/.amico/problems/<slug>/solve.jl` — it prints ONE JSON line
`{{AMICO_RUN}} estimate ~/.amico/problems/<slug>/solve.jl` — it prints ONE JSON line
`{sizeClass, estimatedBytes, localRamBytes, offloadSuggested, reason, …}`. Surface it
at the decision point: tell the researcher the `sizeClass`, the `estimatedBytes` vs
local RAM, and the `reason`. The estimate only **suggests**, and only where a choice
Expand All @@ -96,7 +96,7 @@ workspace owns `solve.jl` — never author in `/tmp`).
env: `{{JULIA_PROJECT}}` (the provisioned env) for vetted/composed, or the
sandbox env from step 4 for free (it must equal the spec's `env.project`).
```bash
( nohup amico-run --spec ~/.amico/problems/<slug>/solvespec.json \
( nohup {{AMICO_RUN}} --spec ~/.amico/problems/<slug>/solvespec.json \
--project {{JULIA_PROJECT}} --lab default \
~/.amico/problems/<slug>/solve.jl \
> ~/.amico/problems/<slug>/solve.log 2>&1 < /dev/null & )
Expand All @@ -116,9 +116,9 @@ workspace owns `solve.jl` — never author in `/tmp`).
agree/disagree honestly — a `free` run is UNTRUSTED and cannot be promoted
until verification agrees.

There is **no MCP server**. The solve runs through `amico-run` via bash; the
There is **no MCP server**. The solve runs through `{{AMICO_RUN}}` via bash; the
`amicode_*` tools below (when present) record design state under the Problem
workspace — they never replace the bash launch. `amico-run --help` prints usage.
workspace — they never replace the bash launch. `{{AMICO_RUN}} --help` prints usage.

### Bookkeeping verbs (`amico` — same bash surface)

Expand Down Expand Up @@ -322,7 +322,7 @@ Stages, in order:
below); pass them to `amicode_solve` (it records them on the Formulation and
writes the Run entity, stamped with the resolved `tier`), then author
`solve.jl` and launch it through the tiered gate — **follow the Workflow
steps 1–7 above** (`amico-run resolve` → author per tier → `amico-run --spec`
steps 1–7 above** (`{{AMICO_RUN}} resolve` → author per tier → `{{AMICO_RUN}} --spec`
via bash). For a stock single-qubit transmon gate this resolves to the
**vetted** tier and is exactly the fill-in-the-block flow.
7. **INSPECT** — the Run Inspector opens itself and streams the live pulse;
Expand Down Expand Up @@ -411,7 +411,7 @@ lists it — honestly caveated (see the PLATFORM stage).

## The run-dir contract your script MUST emit

`amico-run` writes `run.toml` (first) and `FINISHED` (last) itself. Your
`{{AMICO_RUN}}` writes `run.toml` (first) and `FINISHED` (last) itself. Your
script, running with cwd = the run dir, must emit:

- `AMICODE_ITER iter=<n> f=<obj> inf_pr=<…> inf_du=<…>` to stdout, flushed,
Expand Down Expand Up @@ -493,6 +493,18 @@ correct loader in this Piccolo.

**{{JULIA_PROJECT}}**. Always pass it.

## Tool use

- **The tools you need are at absolute paths already. Never hunt for them.** Use
`{{AMICO_RUN}}` and `{{JULIA_PROJECT}}` verbatim. Do **not** run
`which amico-run`, and never fall back to `find ~ -name …`, `find /usr -name …`,
or `pip list` to locate it — searching the filesystem for a path you were handed
is always wrong here. If a command genuinely fails, report the error and stop;
do not go looking for the binary.

- Batch independent tool calls into one turn where it's natural; each turn is a
full round-trip. Serialize only when a later call needs an earlier result.

## Style & formatting

The user is a quantum-control researcher — skip the basics, keep the physics
Expand Down
6 changes: 6 additions & 0 deletions packages/extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,11 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
const parsed = parseLibraryRootSpecs(raw);
return parsed.length ? parsed : undefined;
};
// Absolute launcher path for AGENTS.md's {{AMICO_RUN}} — so the agent never
// needs `which`, and never falls back to scanning $HOME (417s measured waste).
const amicoRunPath = amicoRunBinDir ? path.join(amicoRunBinDir, "amico-run") : undefined;
const opencodeProject = prepareOpencodeProject({
amicoRunPath,
agentsSrc: path.resolve(ctx.extensionPath, "AGENTS.md"),
// MODE-SELECTED vetted template: HP sessions get the Piccolissimo variant
// (same run-dir contract, spline solver layer). An AGENTS.md instruction
Expand Down Expand Up @@ -573,6 +577,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
opencodeChannel.appendLine(`[solver] switching → ${mode}`);
applyEntitlementForMode(mode, path.join(os.homedir(), ".amico", "amicode"));
const project2 = prepareOpencodeProject({
amicoRunPath,
agentsSrc: path.resolve(ctx.extensionPath, "AGENTS.md"),
// Same mode-selection as boot; `mode` is the requested target of THIS
// switch (the state file still reads status:"switching" here).
Expand Down Expand Up @@ -707,6 +712,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
}
const port = vscode.workspace.getConfiguration("amicode").get<number>("opencodePort", 0);
const project2 = prepareOpencodeProject({
amicoRunPath,
agentsSrc: path.resolve(ctx.extensionPath, "AGENTS.md"),
templateSrc: path.resolve(
ctx.extensionPath,
Expand Down
14 changes: 13 additions & 1 deletion packages/extension/src/opencode_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ export interface OpencodeConfigOptions {
/** Julia project (--project) the agent should use; already resolved (see
* resolveJuliaProject). Substituted into AGENTS.md as {{JULIA_PROJECT}}. */
juliaProject: string | undefined;
/** Absolute path to the `amico-run` launcher, substituted into AGENTS.md as
* {{AMICO_RUN}}. PATH augmentation already happens at spawn time, but when it
* fails the agent fell back to `which amico-run || find ~ -name amico-run`,
* and an unbounded scan of $HOME costs ~2 minutes EACH: 128 such calls totalling
* 417s were measured in one session's history (2026-08-03). Handing over the
* absolute path removes the reason to search at all. Undefined leaves the
* placeholder resolving to the bare command (previous behaviour). */
amicoRunPath?: string;
/** Scores repertoire root (SCORE.md manifests). Default: the bundled scores/. */
scoresRoot?: string;
/** Dir holding the user's entitlements.toml (access-code stub). Default: ~/.amico/amicode. */
Expand Down Expand Up @@ -524,7 +532,11 @@ export function prepareOpencodeProject(opts: OpencodeConfigOptions): OpencodePro
: "# Amicode\nRead the template at {{TEMPLATE_PATH}}, fill params, run `amico-run <script>`.\n";
const filled = raw
.replaceAll("{{JULIA_PROJECT}}", opts.juliaProject ?? resolveJuliaProject(""))
.replaceAll("{{TEMPLATE_PATH}}", opts.templateSrc);
.replaceAll("{{TEMPLATE_PATH}}", opts.templateSrc)
// Absolute when we know it, bare command otherwise — a bare `amico-run` is
// exactly the pre-existing behaviour, so an unresolved bin dir degrades
// rather than breaking.
.replaceAll("{{AMICO_RUN}}", opts.amicoRunPath ?? "amico-run");

// Score runtime ("data-defined, prompt-executed", scores spec §6): compile the
// selected score (v1: boot-time selection of score #0, pulse-designer) over the
Expand Down
55 changes: 48 additions & 7 deletions packages/extension/test/agents_md.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const AGENTS = readFileSync(join(__dirname, "..", "AGENTS.md"), "utf8");

describe("AGENTS.md teaches the D9/D10 script-authoring workflow", () => {
it("teaches the tiered resolve → author → --spec launch (spec C), not a single bundled template", () => {
expect(AGENTS).toMatch(/amico-run resolve/); // tier resolution step
expect(AGENTS).toMatch(/amico-run --spec/); // the gated invocation it teaches
expect(AGENTS).toMatch(/\{\{AMICO_RUN\}\} resolve/); // tier resolution step (placeholder, substituted at prep)
expect(AGENTS).toMatch(/\{\{AMICO_RUN\}\} --spec/); // the gated invocation it teaches
expect(AGENTS).toMatch(/solve\.jl/);
expect(AGENTS).toMatch(/vetted/); // the three tiers named
expect(AGENTS).toMatch(/composed/);
Expand Down Expand Up @@ -73,7 +73,7 @@ describe("AGENTS.md teaches the D9/D10 script-authoring workflow", () => {

describe("AGENTS.md teaches the Δ10 (#63) routing UX", () => {
it("runs amico-run estimate at solve-assembly and surfaces the estimate at the decision point", () => {
expect(AGENTS).toMatch(/amico-run estimate/);
expect(AGENTS).toMatch(/\{\{AMICO_RUN\}\} estimate/);
expect(AGENTS).toMatch(/sizeClass/);
expect(AGENTS).toMatch(/offloadSuggested/);
expect(AGENTS).toMatch(/local RAM/i);
Expand Down Expand Up @@ -180,10 +180,9 @@ describe("AGENTS.md pulse-designer interview (Layer 0)", () => {
expect(AGENTS).toMatch(/Never silently\s+co-optimize/i);
});
it("leaves no unknown {{...}} placeholder after session-prep substitution", () => {
const substituted = AGENTS.replace(/\{\{TEMPLATE_PATH\}\}/g, "/abs/solve_template.jl").replace(
/\{\{JULIA_PROJECT\}\}/g,
"/abs/julia",
);
const substituted = AGENTS.replace(/\{\{TEMPLATE_PATH\}\}/g, "/abs/solve_template.jl")
.replace(/\{\{JULIA_PROJECT\}\}/g, "/abs/julia")
.replace(/\{\{AMICO_RUN\}\}/g, "/abs/bin/amico-run");
expect(substituted).not.toMatch(/\{\{[A-Z_]+\}\}/);
});
});
Expand Down Expand Up @@ -255,3 +254,45 @@ describe("HP solver-mode guidance: both imports", () => {
}
});
});

// Perf regression guard (2026-08-03). Profiling found 128 unbounded `find` calls
// over $HOME — 417 seconds — because AGENTS.md said bare `amico-run` and the agent
// fell back to `which amico-run || find ~ -name amico-run` when PATH missed. The
// binary's absolute path is known at activation, so hand it over and remove the
// reason to search.
describe("AGENTS.md hands over the absolute amico-run path", () => {
const prepared = async (amicoRunPath?: string): Promise<string> => {
const { prepareOpencodeProject } = await import("../src/opencode_config");
const { mkdtempSync, readFileSync } = await import("node:fs");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const p = prepareOpencodeProject({
agentsSrc: join(__dirname, "..", "AGENTS.md"),
templateSrc: "/tmp/tmpl.jl",
juliaProject: "/tmp/proj",
vaultDir: "",
projectDir: mkdtempSync(join(tmpdir(), "agents-perf-")),
...(amicoRunPath ? { amicoRunPath } : {}),
});
return readFileSync(p.agentsPath, "utf8");
};

it("substitutes the absolute path and leaves no placeholder behind", async () => {
const out = await prepared("/abs/bin/amico-run");
expect(out).toContain("/abs/bin/amico-run --spec");
expect(out).not.toContain("{{AMICO_RUN}}");
});

it("degrades to the bare command when the bin dir is unknown", async () => {
const out = await prepared(undefined);
expect(out).toContain("amico-run --spec"); // previous behaviour, not a broken placeholder
expect(out).not.toContain("{{AMICO_RUN}}");
});

it("forbids the $HOME scan and tells the agent to batch", async () => {
const out = await prepared("/abs/bin/amico-run");
expect(out).toMatch(/never fall back to `?find ~/);
expect(out).toMatch(/Batch independent tool calls into one turn/);
expect(out).toMatch(/Do \*\*not\*\* run\s+`?which amico-run/);
});
});
Loading