acumen 0.0.1.dev0__py3-none-any.whl

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.
acumen/__init__.py ADDED
@@ -0,0 +1,160 @@
1
+ """Build, benchmark, and optimize Claude skills for Python packages."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ from acumen.bench import PlannedRun, build_matrix, pending, run_matrix, summarize
6
+ from acumen.config import Config, ConfigError, load_config, parse_config
7
+ from acumen.draft import DraftError, DraftResult, draft_skill
8
+ from acumen.env import (
9
+ AuthMode,
10
+ EnvError,
11
+ Target,
12
+ api_auth_available,
13
+ auth_available,
14
+ build_agent_env,
15
+ check_auth,
16
+ prepare_target,
17
+ resolve_auth_mode,
18
+ scrubbed_env,
19
+ session_auth_available,
20
+ )
21
+ from acumen.grade import Grade, Reason, grade_answer, grade_run
22
+ from acumen.improve import (
23
+ ImproveError,
24
+ ImproveResult,
25
+ TrainRun,
26
+ collect_train_runs,
27
+ find_test_access,
28
+ improve_skill,
29
+ make_test_guard,
30
+ )
31
+ from acumen.logs import LiveLog
32
+ from acumen.paths import RunKey, Split, arm_name, is_complete, parse_run_dir, run_dir, skill_from_arm
33
+ from acumen.report import (
34
+ Report,
35
+ ReportError,
36
+ SkillTests,
37
+ arm_metrics,
38
+ build_report,
39
+ load_results,
40
+ resolve_palette,
41
+ skill_tests,
42
+ tradeoff_figure,
43
+ )
44
+ from acumen.runner import RunOutcome, run_once
45
+ from acumen.sandbox import Sandbox, install_skill, sandbox
46
+ from acumen.scaffold import InitError, scaffold
47
+ from acumen.ship import ShipError, ShipResult, installer_exists, ship_skill
48
+ from acumen.skills import (
49
+ Skill,
50
+ SkillError,
51
+ SkillMeta,
52
+ available_versions,
53
+ latest_version,
54
+ load_skill,
55
+ next_version,
56
+ skill_content,
57
+ skill_hash,
58
+ )
59
+ from acumen.taskgen import (
60
+ TaskGenError,
61
+ TaskGenResult,
62
+ build_filtered_source,
63
+ dump_tasks,
64
+ find_skill_access,
65
+ generate_tasks,
66
+ make_skill_guard,
67
+ )
68
+ from acumen.tasks import Task, TaskError, TaskSplit, load_tasks, parse_tasks
69
+ from acumen.transcript import locate_transcript, render_transcript
70
+
71
+ __all__ = [
72
+ "AuthMode",
73
+ "Config",
74
+ "ConfigError",
75
+ "DraftError",
76
+ "DraftResult",
77
+ "EnvError",
78
+ "Grade",
79
+ "ImproveError",
80
+ "ImproveResult",
81
+ "InitError",
82
+ "LiveLog",
83
+ "PlannedRun",
84
+ "Reason",
85
+ "Report",
86
+ "ReportError",
87
+ "RunKey",
88
+ "RunOutcome",
89
+ "Sandbox",
90
+ "ShipError",
91
+ "ShipResult",
92
+ "Skill",
93
+ "SkillError",
94
+ "SkillMeta",
95
+ "Split",
96
+ "Target",
97
+ "Task",
98
+ "TaskError",
99
+ "TaskGenError",
100
+ "TaskGenResult",
101
+ "TaskSplit",
102
+ "TrainRun",
103
+ "__version__",
104
+ "api_auth_available",
105
+ "arm_metrics",
106
+ "arm_name",
107
+ "auth_available",
108
+ "available_versions",
109
+ "check_auth",
110
+ "build_agent_env",
111
+ "build_filtered_source",
112
+ "build_matrix",
113
+ "build_report",
114
+ "collect_train_runs",
115
+ "draft_skill",
116
+ "dump_tasks",
117
+ "find_skill_access",
118
+ "find_test_access",
119
+ "generate_tasks",
120
+ "grade_answer",
121
+ "grade_run",
122
+ "improve_skill",
123
+ "install_skill",
124
+ "installer_exists",
125
+ "is_complete",
126
+ "make_skill_guard",
127
+ "make_test_guard",
128
+ "latest_version",
129
+ "load_config",
130
+ "locate_transcript",
131
+ "load_results",
132
+ "load_skill",
133
+ "load_tasks",
134
+ "next_version",
135
+ "parse_config",
136
+ "parse_run_dir",
137
+ "parse_tasks",
138
+ "pending",
139
+ "prepare_target",
140
+ "render_transcript",
141
+ "resolve_auth_mode",
142
+ "resolve_palette",
143
+ "run_dir",
144
+ "run_matrix",
145
+ "run_once",
146
+ "sandbox",
147
+ "scaffold",
148
+ "scrubbed_env",
149
+ "session_auth_available",
150
+ "ship_skill",
151
+ "SkillTests",
152
+ "skill_content",
153
+ "skill_from_arm",
154
+ "skill_hash",
155
+ "skill_tests",
156
+ "summarize",
157
+ "tradeoff_figure",
158
+ ]
159
+
160
+ __version__ = version("acumen")
File without changes
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: acumen
3
+ description: Use for any question or task involving the Python package `acumen` (its CLI or its API) — setting up a benchmark project against a target package, writing or generating benchmark tasks, drafting/improving/hand-editing an agent Skill, running and interpreting skill-vs-baseline benchmark passes, shipping a skill into the target package, diagnosing a run, and choosing what to do next in that loop — open it before answering or running anything, because acumen's defaults, guardrails and correct next step are not guessable from the command names.
4
+ ---
5
+
6
+ # acumen
7
+
8
+ One project directory targets one Python package. Everything is driven by the `acumen`
9
+ CLI (a thin shell over an importable API — see `references/python-api.md`). Commands are
10
+ run from the project dir and default to `config.yaml`, `tasks.yaml`, `skills/`, `runs/`,
11
+ `logs/`.
12
+
13
+ ```bash
14
+ acumen init # scaffold config.yaml + tasks.yaml (placeholders the user must fill)
15
+ acumen tasks # optional: mine the package for tasks, run them for ground truth
16
+ acumen bench --no-skill # baseline arm (this is also the default arm)
17
+ acumen draft # agent reads the package source -> skills/v1/
18
+ acumen bench --skill v1
19
+ acumen improve # agent reads v1's TRAIN runs -> skills/v2/
20
+ acumen bench --skill v2
21
+ acumen report # report.html + report.csv over every arm on disk
22
+ acumen ship --skill v2 # wire a `<dist>-install-skills` script into the target package
23
+ ```
24
+
25
+ That is the shape of the loop, not a script to execute unattended, and not a fixed order:
26
+ several stages have more than one correct next move, and some stages call for talking to
27
+ the user rather than running the next command at all.
28
+
29
+ ## The loop is collaborative — propose, discuss, then run
30
+
31
+ `bench` bills the Anthropic API for every cell and the other commands drive long
32
+ autonomous agents, so the user decides each step and when to stop. At every stage: state
33
+ what you would do next and why, and let the user choose. Never chain commands
34
+ unattended, and never decide on your own that a skill is good enough.
35
+
36
+ **Before running anything, get from the user what acumen cannot infer** — the target, the
37
+ models, the budgets, whether tasks and results look right, whether to ship. Guessing those
38
+ is the most common way to waste a pass. The step after a command is often a question, not
39
+ a command.
40
+
41
+ | Just happened | What to propose next |
42
+ |---|---|
43
+ | `acumen init` | Both files are placeholders (`repo: OWNER/REPO`, `REPLACE_ME` answers). Ask the user for what only they can supply — the target repo/path, ref, models, budgets — and fill in `config.yaml` with their answers. Do not guess a target or run the next command. |
44
+ | `config.yaml` filled | Offer both ways to get tasks: `acumen tasks` to generate them, or writing `tasks.yaml` by hand. Review generated tasks with the user before benching. |
45
+ | config + tasks ready | `acumen bench --no-skill` and `acumen draft` are **both** correct next steps — they are independent, and you need the baseline arm and a skill arm before any comparison means anything. Offer both. |
46
+ | a skill version exists | `acumen bench --skill vN`. |
47
+ | any `bench` finished | `acumen report` — the per-arm, per-split numbers come from the report; do not judge a version by eyeballing runs or by one arm's pass count. |
48
+ | `report` written | Discuss the results with the user and propose next steps: `improve` + re-bench if it has not beaten the baseline or the previous version, `ship` once a version has proven out, or stop. The user decides. |
49
+ | user has a skill dir from elsewhere | Copy it into `skills/v1/` (next unused version) inside the acumen project — versions are only ever read from `skills/`; there is no import command and no external path flag. |
50
+
51
+ ## Route by goal
52
+
53
+ | Goal | Command | Depth |
54
+ |---|---|---|
55
+ | Start a project for package X | `acumen init`, then fill `config.yaml` with the user | `references/setup.md` |
56
+ | Get benchmark tasks without writing them | `acumen tasks [--force] [--feedback "…"]` | `references/setup.md` |
57
+ | Write tasks by hand | edit `tasks.yaml` | `references/setup.md` |
58
+ | Get a first skill | `acumen draft [--feedback "…"]` | `references/authoring.md` |
59
+ | Measure whether the skill helps | `acumen bench --no-skill` and `acumen bench --skill vN` | `references/benchmark.md` |
60
+ | Make the skill better | `acumen improve [--from vN]` then bench the new version | `references/authoring.md` |
61
+ | See results / decide when to stop | `acumen report` | `references/benchmark.md` |
62
+ | Hand-edit a skill version | copy `skills/vN` → `skills/v(N+1)`, edit, bench it | `references/authoring.md` |
63
+ | Give package users the skill | `acumen ship --skill vN` | `references/ship.md` |
64
+ | Script any of this in Python | `from acumen import …` | `references/python-api.md` |
65
+ | A run failed / the skill did nothing | inspect `runs/…/result.json`, `logs/*.jsonl` | `references/benchmark.md` |
66
+
67
+ ## Preconditions
68
+
69
+ - Python ≥ 3.12, and **`uv` on PATH** — acumen builds the target's venv with it.
70
+ - The target (`repo`: a GitHub URL or a local path) must be pip-installable and declare
71
+ `[project].name` in `pyproject.toml`. A local `repo` path is resolved **relative to
72
+ `config.yaml`**.
73
+ - Credentials: `bench` **always bills the Anthropic API** and refuses to run without
74
+ `ANTHROPIC_API_KEY` (or `ANTHROPIC_AUTH_TOKEN` / Bedrock / Vertex) — it records real
75
+ per-run `cost_usd`, and it has no `--auth` flag. `tasks`/`draft`/`improve`/`ship`
76
+ default to the Claude subscription if you are logged in (`--auth {auto,session,api}`).
77
+ - The target is cloned + installed into a venv cached under `~/.cache/acumen`, keyed by
78
+ (repo, ref). Use `--refresh-target` after changing the target's own source.
79
+
80
+ ## What guessing gets wrong
81
+
82
+ 1. **Grading is exact string match on `answer.md` after `strip()`, case-sensitive.**
83
+ Nothing is normalized. `**TOKEN**` against `TOKEN` fails (recorded as `format_error`,
84
+ not `wrong_answer`). Task answers must be one short unambiguous token.
85
+ 2. **A pass is models × tasks × splits × replicates.** The scaffolded config lists 3
86
+ models and `n_replicates: 3`, so *one* task costs 18 agent runs *per arm*. Check with
87
+ `acumen bench --dry-run` (it plans and exits, spending nothing) and agree the size with
88
+ the user before spending. Trim with `models:`, `n_replicates: 1`, `--task ID`, `--split`.
89
+ 3. **`max_turns`/`max_usd` in `config.yaml` cap benchmark agents only.** `draft`,
90
+ `improve`, `tasks`, and `ship` are **unbounded** unless you pass `--max-turns`/`--max-usd`.
91
+ 4. **Skill versions are immutable.** `draft` refuses (exit 2) if any `skills/vN` exists;
92
+ `improve` always writes the next unused directory. Never edit a benched version in
93
+ place — its hash is recorded in every `result.json`.
94
+ 5. **`SKILL.md` frontmatter `name` must equal `config.skill_name`**, which defaults to the
95
+ repo's last path component, slugified and lowercased (`.../My_Pkg` → `my_pkg`). A
96
+ mismatch makes `bench`/`improve`/`ship` fail on load. `description` must be non-empty.
97
+ 6. **`improve` needs benched train evidence for its parent.** Run
98
+ `acumen bench --skill vN` before `acumen improve`, or it errors with nothing to read.
99
+ 7. **Never leak the test split.** `improve` is structurally and hook-blocked from
100
+ `runs/*/test/`; don't defeat that by pasting test answers into `--feedback`. A widening
101
+ train/test gap in the report is the overfitting signal you are watching for.
102
+ 8. **Task prompts must not name the target package** — the harness already tells the agent
103
+ which package to use and that it is installed.
104
+ 9. **Resume is automatic**: a run is "complete" when its `result.json` exists and is
105
+ non-empty, and completed runs are skipped. `--no-resume` re-runs them. Renaming a task
106
+ `id` orphans its old runs (the id is a path component).
107
+ 10. **Confirm the skill actually loaded.** `bench` prints `skill loaded in N/M runs` from
108
+ per-run `skill_loaded` evidence, and warns when a skill arm never fired the Skill tool
109
+ (that arm measures nothing) or when the baseline did.
@@ -0,0 +1,91 @@
1
+ # Authoring skill versions: draft, improve, hand-edit
2
+
3
+ ## The skill directory contract
4
+
5
+ ```
6
+ skills/v1/
7
+ SKILL.md # required, YAML frontmatter with `name` and `description`
8
+ references/*.md # optional
9
+ meta.json # acumen bookkeeping — NOT part of the skill
10
+ ```
11
+
12
+ - `name` must equal `config.skill_name` exactly; `description` must be non-empty. Both are
13
+ enforced on load by `bench`, `improve`, and `ship`.
14
+ - `meta.json` (`version`, `parent`, `rationale`, `hash`, optional `feedback`) is written by
15
+ acumen. It is excluded from the content hash and is never copied to a consuming agent.
16
+ - The content hash covers every other file's relative path and bytes, and is recorded in
17
+ every `result.json` — so editing a version after benching it silently invalidates the
18
+ comparison. **Versions are immutable; always make a new one.**
19
+
20
+ ## `acumen draft`
21
+
22
+ ```bash
23
+ acumen draft [--force] [--feedback "…"] [--model M] [--max-turns N] [--max-usd X]
24
+ [--stream] [--log-dir logs] [--auth auto|session|api]
25
+ ```
26
+
27
+ One agent that reads the target's **source** (docstrings, examples, docs) and writes
28
+ `skills/v1/`. It works in a staging dir; only a skill that loads and validates is promoted,
29
+ so a failed draft leaves no broken version behind. Exits 2 if any version already exists —
30
+ pass `--force` to add another, or use `improve` to build on the latest.
31
+
32
+ ## `acumen improve`
33
+
34
+ ```bash
35
+ acumen improve [--from vN] [--feedback "…"] [--model M] [--max-turns N] [--max-usd X]
36
+ [--stream] [--log-dir logs] [--auth auto|session|api]
37
+ ```
38
+
39
+ Reads **how the parent version performed on the train split** — not the package source —
40
+ and edits a copy of it into the next version.
41
+
42
+ - Defaults to the latest version; `--from vN` picks another. Always writes the next unused
43
+ directory.
44
+ - Requires `runs/skill_vN/train/**/result.json` to exist: bench the parent first.
45
+ - The agent gets a curated copy of train runs (`SUMMARY.md` with failures first, plus each
46
+ run's prompt, expected, actual, `script.py`, and transcript). The real `runs/` tree is not
47
+ in scope, and a `PreToolUse` hook denies any path resolving under `runs/*/test/`.
48
+ - The CLI warns if the new version is byte-identical to its parent (the improver changed
49
+ nothing) — that is a signal to give it sharper evidence or `--feedback`.
50
+
51
+ ## `--feedback` on `tasks` / `draft` / `improve`
52
+
53
+ Free text injected into the agent's prompt as *subordinate* guidance — it never overrides
54
+ the isolation or anti-overfit rules. Use it for what the agent cannot infer: package
55
+ context, what to emphasise, which functionality to skip. For `draft`/`improve` it is
56
+ recorded in `meta.json` and shown in the report. **Never paste test-split answers into
57
+ `improve --feedback`** — that defeats the split.
58
+
59
+ ## Writing or editing a version by hand
60
+
61
+ Perfectly supported; the agents are a convenience, not a requirement. A skill authored
62
+ outside the project is benched the same way: copy the directory in as the next unused
63
+ `skills/vN` — `bench` only ever loads versions from the project's `skills/` root.
64
+
65
+ ```bash
66
+ cp -r skills/v2 skills/v3 # then edit skills/v3/, and delete its inherited meta.json
67
+ acumen bench --skill v3
68
+ ```
69
+
70
+ A hand-made version with no `meta.json` benches fine; it just has no rationale/diff in the
71
+ report. What makes a version score well:
72
+
73
+ - The `description` decides whether the skill loads at all — name the goals a user would
74
+ actually phrase, honestly. An unloaded skill is a wasted arm.
75
+ - Keep `SKILL.md` short and push depth into `references/`; every token is paid on every
76
+ task, including ones where the skill is irrelevant.
77
+ - Spend words on what an agent would get wrong by guessing: non-obvious defaults, required
78
+ preprocessing, the function that looks right but isn't, argument shapes/orientation,
79
+ where output lands, the right order of steps.
80
+ - **Never name a dataset, parameter value, column, or expected answer from the train
81
+ tasks.** That is overfitting, and the test split will catch it.
82
+ - Verify every claim against the installed package before writing it down.
83
+
84
+ ## Watching a long agent run
85
+
86
+ `tasks`/`draft`/`improve`/`ship` each write `logs/acumen-<command>-<YYYYMMDD-HHMMSS>.jsonl`,
87
+ one compact event per SDK message, **flushed as it goes** — read that file to follow
88
+ progress instead of streaming into your context. Tool results are recorded by status and
89
+ size, not inlined. `--stream` mirrors the conversation to the terminal; `--log-dir` moves
90
+ the logs. A rendered `.html` transcript lands beside the jsonl at the end (skipped, with a
91
+ note, if `claude-code-log` is missing).
@@ -0,0 +1,100 @@
1
+ # Benchmarking and reading results
2
+
3
+ ## `acumen bench`
4
+
5
+ ```bash
6
+ acumen bench [--no-skill | --skill v1] [--split train|test]... [--task ID]...
7
+ [--replicates N] [--max-concurrency N] [--dry-run] [--no-resume]
8
+ [--keep-sandboxes] [--refresh-target]
9
+ [--config config.yaml] [--tasks tasks.yaml] [--runs runs] [--skills skills]
10
+ [--cache ~/.cache/acumen]
11
+ ```
12
+
13
+ - Neither `--no-skill` nor `--skill` → the **baseline** arm (they are mutually exclusive).
14
+ - `--split` and `--task` are repeatable; omitted means all.
15
+ - `--dry-run` prints the planned matrix and exits **before** target prep and before any
16
+ agent runs — free, and the right way to check the size of a pass.
17
+ - `--replicates` / `--max-concurrency` override the config for this pass only.
18
+ - Always bills the API (`ANTHROPIC_API_KEY` etc.); there is no `--auth` here, because the
19
+ recorded `cost_usd` must be real metered spend.
20
+ - Ctrl-C is safe: completed runs are preserved and the next invocation resumes.
21
+
22
+ **Arm parity is the whole point.** Both arms get an identical prompt, tools, caps, and
23
+ environment; the only difference is that a skill arm copies the skill's content files into
24
+ `<sandbox>/.claude/skills/<skill_name>/` where project settings discovery finds it.
25
+ `meta.json` is never copied. Each run gets a fresh empty sandbox with the target venv on
26
+ PATH, a throwaway `HOME` and `CLAUDE_CONFIG_DIR` — no repo source, no user settings, no
27
+ `CLAUDE.md` memories, no visibility of other runs.
28
+
29
+ ## The run tree
30
+
31
+ ```
32
+ runs/{arm}/{split}/{model}/{task_id}/rep_{n}/
33
+ ```
34
+
35
+ `arm` is `noskill` or `skill_v1`, `skill_v2`, …; `model` is slugified. Each completed leaf
36
+ holds five files:
37
+
38
+ | File | What |
39
+ |---|---|
40
+ | `answer.md` | What the agent wrote — the only thing graded |
41
+ | `script.py` | The agent's reproduction script (absent if it ran no code) |
42
+ | `transcript.jsonl` | SDK-native transcript |
43
+ | `transcript.html` | Rendered transcript (via `claude-code-log`) |
44
+ | `result.json` | The unit of record — written **last** |
45
+
46
+ `result.json` presence + non-zero size is what marks a run complete, which is what makes
47
+ resume safe. Useful fields: `success`, `reason`, `answer`, `expected`, `model`, `turns`,
48
+ `cost_usd`, `input_tokens`, `output_tokens`, `duration_s`, `skill_hash`, `skill_name`,
49
+ `skill_loaded`, `pkg_version`, `commit`, `session_id`, `subtype`, `error`.
50
+
51
+ ## Reasons
52
+
53
+ | `reason` | Meaning |
54
+ |---|---|
55
+ | `ok` | Exact match — the only success |
56
+ | `wrong_answer` | Content differs |
57
+ | `format_error` | Content would match but formatting broke the exact match (bold, quotes, "Answer:", code fence, trailing period) |
58
+ | `no_answer_file` | The agent never wrote `answer.md` |
59
+ | `budget` / `max_turns` | Cap breached — a **failure regardless** of what `answer.md` contains |
60
+ | `error` | The agent crashed or produced no result |
61
+
62
+ A run of `format_error`s means the harness's answer-format instruction is losing to the
63
+ task prompt; a run of `no_answer_file` usually means the task is too big for `max_turns`.
64
+
65
+ ## Diagnosing a disappointing arm
66
+
67
+ 1. `skill loaded in 0/N runs` on a skill arm → the arm measured nothing. The skill's
68
+ `description` is what decides whether it loads; make it name the goals the task prompts
69
+ actually phrase.
70
+ 2. The Skill tool firing in *baseline* runs is also flagged — that baseline is not clean.
71
+ 3. Open `runs/.../transcript.html` for a failing run, or re-run one cell with
72
+ `--task ID --split train --replicates 1 --keep-sandboxes` and inspect the sandbox.
73
+ 4. Compare `answer` vs `expected` in `result.json` before blaming the skill — a systematic
74
+ near-miss is a task-authoring problem, not a skill problem.
75
+
76
+ ## `acumen report`
77
+
78
+ ```bash
79
+ acumen report [--runs runs] [--tasks tasks.yaml] [--skills skills] [--out report.html]
80
+ [--palette claude-opus-5=#3b7ea1]...
81
+ ```
82
+
83
+ - Writes `report.html` **and a sidecar `report.csv`** next to it, overwriting both; the
84
+ report always reflects every run currently on disk across every arm.
85
+ - Reads only `result.json` files — never transcripts. Fails if `runs/` holds none.
86
+ - **The figures show the TEST split** (the held-out measure). The per-run table below them
87
+ lists both splits.
88
+ - Sections: Overview (success rate, tokens, cost, time per arm, bars coloured by model),
89
+ Per-task breakdown, Runs table (links to each `transcript.html`), and — when `--skills`
90
+ resolves — Skill versions with each version's rationale and diff against its parent.
91
+ - `--tasks` / `--skills` are optional; missing ones only drop their sections (it says so).
92
+ - `--palette MODEL=COLOUR` is repeatable and accepts comma-separated pairs. A key that
93
+ matches no model in the data, or an unparseable colour, is rejected up front.
94
+
95
+ Run `acumen report` after every bench — it is how a version is judged, and the only view
96
+ that compares arms and splits side by side. Then take the results to the user: say what
97
+ improved, what did not, and what you would do next (`improve` and re-bench, more tasks,
98
+ `ship`, or stop). Train improving while test does not is overfitting — that gap is exactly
99
+ what the split exists to expose — but the call on when to stop and what to ship is the
100
+ user's.
@@ -0,0 +1,106 @@
1
+ # Python API
2
+
3
+ Everything is re-exported from the top level: `from acumen import build_report, load_config, …`.
4
+ The CLI (`acumen.cli:main`) is a thin shell over it, so anything the CLI does is scriptable.
5
+ `main(argv)` returns an exit code and converts the library's error types into `error: …` on
6
+ stderr — call the functions directly if you want the exceptions.
7
+
8
+ ## Loading config and tasks
9
+
10
+ ```python
11
+ from pathlib import Path
12
+ from acumen import load_config, load_tasks, parse_config, parse_tasks
13
+
14
+ cfg = load_config(Path("config.yaml")) # validates; resolves a local repo relative to the file
15
+ tasks = load_tasks(Path("tasks.yaml")) # list[Task]; task.split("train") -> TaskSplit(prompt, answer)
16
+ ```
17
+
18
+ `Config` is a frozen dataclass — override with `dataclasses.replace(cfg, n_replicates=1)`.
19
+ `parse_config` / `parse_tasks` take already-parsed dicts, for building them in memory.
20
+
21
+ ## Preparing the target
22
+
23
+ ```python
24
+ from acumen import prepare_target
25
+ from acumen.env import DEFAULT_CACHE_ROOT
26
+
27
+ target = prepare_target(cfg, DEFAULT_CACHE_ROOT, refresh=False)
28
+ target.python # venv interpreter with the package installed
29
+ target.bin_dir # what goes on an agent's PATH
30
+ target.fingerprint # "<pkg> <version>", as recorded in result.json
31
+ ```
32
+
33
+ Needs `uv` on PATH. Cached by (repo, ref) under `~/.cache/acumen`.
34
+
35
+ ## Planning and running a pass
36
+
37
+ ```python
38
+ import asyncio
39
+ from acumen import build_matrix, pending, run_matrix, load_skill, summarize
40
+
41
+ skill = load_skill(Path("skills"), "v1", expect_name=cfg.skill_name) # None for the baseline
42
+ planned = build_matrix(cfg, tasks, skill="v1", splits=("train",), task_ids=["t1"])
43
+ todo = pending(planned, Path("runs"), resume=True)
44
+ outcomes = asyncio.run(run_matrix(
45
+ todo, target=target, runs_root=Path("runs"),
46
+ max_concurrency=cfg.max_concurrency, auth_mode="api", skill=skill,
47
+ on_start=lambda p: None, on_done=lambda o: None,
48
+ env_passthrough=cfg.env_passthrough,
49
+ ))
50
+ summarize(outcomes) # {"ok": 5, "wrong_answer": 1, ...}
51
+ ```
52
+
53
+ `run_matrix` and `run_once` are coroutines. `run_once` records agent crashes as failed runs
54
+ rather than raising, so one bad run never kills a pass. `run_once` raises `ValueError` if
55
+ `key.arm` and the `skill` argument disagree — the arm is the source of truth.
56
+
57
+ `build_matrix` (and `--dry-run`) is pure planning: no agents, no network, no cost.
58
+
59
+ ## Grading and paths
60
+
61
+ ```python
62
+ from acumen import grade_answer, grade_run, run_dir, parse_run_dir, is_complete, RunKey, arm_name
63
+
64
+ grade_answer("**SPI1**", "SPI1") # Grade(success=False, reason='format_error', answer='**SPI1**')
65
+ run_dir(Path("runs"), RunKey(arm=arm_name("v1"), split="test", model="claude-opus-5",
66
+ task_id="t1", rep=1))
67
+ is_complete(d) # a non-empty result.json is what "done" means
68
+ ```
69
+
70
+ ## The meta-agents
71
+
72
+ `draft_skill`, `improve_skill`, `generate_tasks`, `ship_skill` are all coroutines taking
73
+ keyword-only args (`cfg=`, `target=`, plus their own roots) and returning a result dataclass
74
+ (`DraftResult`, `ImproveResult`, `TaskGenResult`, `ShipResult`) carrying the new `Skill`,
75
+ `cost_usd`, `turns`, and log paths. `max_turns`/`max_usd` default to `None` = **unbounded**.
76
+ Pass a `LiveLog` as `log=` for the JSONL feed:
77
+
78
+ ```python
79
+ from acumen import LiveLog, draft_skill
80
+ log = LiveLog.open(Path("logs"), "draft", stream=False)
81
+ with log:
82
+ result = asyncio.run(draft_skill(cfg=cfg, target=target, skills_root=Path("skills"),
83
+ auth_mode="session", log=log))
84
+ ```
85
+
86
+ ## Aggregating results
87
+
88
+ ```python
89
+ from acumen import load_results, arm_metrics, build_report
90
+
91
+ df = load_results(Path("runs")) # one row per result.json, + total_tokens, arm_label
92
+ arm_metrics(df[df["split"] == "test"]) # per-arm rate, stderr, tokens, cost, time, n
93
+ report = build_report(Path("runs"), Path("report.html"), tasks, skills_root=Path("skills"))
94
+ report.n_runs, report.results # the DataFrame behind the HTML
95
+ ```
96
+
97
+ `build_report` writes `report.html` **and `report.csv`** (same stem) and returns `Report`.
98
+ Filter `df` to a split yourself before `arm_metrics` — it does not.
99
+
100
+ ## Introspection helpers
101
+
102
+ `available_versions`, `latest_version`, `next_version`, `skill_hash`, `skill_content`
103
+ (for diffing), `installer_exists(src_dir)`, `collect_train_runs(runs_root, arm, tasks)`,
104
+ and the two pure guards `find_test_access` / `find_skill_access` (testable without an agent).
105
+ `scrubbed_env` / `build_agent_env` / `sandbox` / `install_skill` let you reproduce a run's
106
+ exact isolated environment by hand.
@@ -0,0 +1,94 @@
1
+ # Project setup: `config.yaml` and `tasks.yaml`
2
+
3
+ `acumen init [--dir DIR] [--force]` writes both files as annotated placeholders. It refuses
4
+ to clobber either unless `--force`. Both loaders are **strict: unknown keys are rejected**.
5
+
6
+ Nothing else can run until `config.yaml` is filled in: the scaffold ships
7
+ `repo: https://github.com/OWNER/REPO` and a `REPLACE_ME` example task. Ask the user for the
8
+ values only they know (target repo/path, ref, extras, which models, budgets) instead of
9
+ inventing them.
10
+
11
+ ## config.yaml
12
+
13
+ Only `repo` is required; delete a line to take its default.
14
+
15
+ | Key | Default | Notes |
16
+ |---|---|---|
17
+ | `repo` | — | GitHub URL (`https://`, `git@`, `ssh://`, `git://`) or a local path. A local path is resolved relative to `config.yaml` and must exist. |
18
+ | `ref` | `main` | Branch/tag/commit; ignored for local paths. |
19
+ | `extras` | `[]` | pip extras installed with the target, e.g. `[test]`. |
20
+ | `python` | `"3.12"` | Interpreter for the target's venv (quote it — `3.10` unquoted is a float). |
21
+ | `env_passthrough` | `[]` | Extra env var names agents may keep. The agent env is a clean allowlist (auth/proxy/TLS only); **everything else from your shell is blanked**. A target needing `OMP_NUM_THREADS`, `R_HOME`, a service key, etc. must name it here. |
22
+ | `models` | `[claude-opus-5]` | Benchmark models. Duplicates rejected. `models[0]` is the default for the four `*_model` keys below. |
23
+ | `n_replicates` | `3` | Runs per (model, task, split) cell. |
24
+ | `max_concurrency` | `4` | Simultaneous benchmark agents. |
25
+ | `max_turns` | `40` | **Benchmark agents only.** |
26
+ | `max_usd` | `3.0` | **Benchmark agents only.** |
27
+ | `draft_model` / `improve_model` / `tasks_model` / `ship_model` | `models[0]` | Meta-agent models; overridable per command with `--model`. |
28
+ | `skill_name` | repo basename, slugified + lowercased | Must equal the `name:` in the skill's frontmatter. |
29
+
30
+ The scaffold's `models:` line lists three models — with `n_replicates: 3` that is 18 runs
31
+ per task per arm. Cut it down before the first real pass.
32
+
33
+ ## tasks.yaml
34
+
35
+ ```yaml
36
+ tasks:
37
+ - id: some_analysis # unique, filesystem-safe: [A-Za-z0-9._-]; used as a path component
38
+ train:
39
+ prompt: >-
40
+ One paragraph: the goal, the input, and exactly what to report.
41
+ answer: ONE_TOKEN
42
+ test:
43
+ prompt: >-
44
+ The same analysis on a different input / target.
45
+ answer: ANOTHER_TOKEN
46
+ # optional per-task overrides — the only other allowed keys:
47
+ # max_turns: 60
48
+ # max_usd: 5.0
49
+ # model: claude-sonnet-5
50
+ ```
51
+
52
+ Both `train` and `test` are required, each with non-empty `prompt` and `answer`. Both
53
+ splits always run in a pass; only train results ever reach `improve`.
54
+
55
+ ### Writing a task that measures anything
56
+
57
+ - **Answers are graded by exact string match after `strip()`, case-sensitive.** Keep the
58
+ answer to one token: a name, a category, a count, a number at a stated precision. End the
59
+ prompt by stating exactly what to report and in what form.
60
+ - **Do not name the target package**, and do not mention a version — the harness preamble
61
+ already tells the agent the package is installed and which interpreter to use.
62
+ - **State the goal, not the recipe.** No numbered steps, no function/argument names, no
63
+ description of the data's shape or columns. What is being measured is whether the agent
64
+ can find the "how" itself; a prompt that spells out the calls measures nothing.
65
+ - Train and test must be **the same analysis on different inputs**, with different correct
66
+ answers — otherwise a skill can pass by memorizing one answer.
67
+ - A good task is one the target package solves and the **no-skill baseline gets wrong** —
68
+ a task the baseline already passes leaves no room to show a gain. The `--no-skill` arm is
69
+ what tells you; it can be run before or after the first skill exists.
70
+ - Renaming an `id` orphans every existing run under it.
71
+
72
+ ## `acumen tasks` — generate tasks.yaml
73
+
74
+ ```bash
75
+ acumen tasks [--out tasks.yaml] [--force] [--feedback "skip the plotting API"] \
76
+ [--model M] [--max-turns N] [--max-usd X] [--stream] [--log-dir logs]
77
+ ```
78
+
79
+ One autonomous agent that reads the package source **and executes it in the target venv**:
80
+ every ground-truth answer comes from a script it actually ran, not from docs. It enumerates
81
+ the package's tutorials/vignettes and writes at least one task per tutorial, so expect a
82
+ sizeable file. Unbounded in turns and cost by default.
83
+
84
+ - Refuses to overwrite an existing `--out` without `--force` (the check runs *before* the
85
+ costly target prep).
86
+ - **There is no append mode.** The generator never reads your existing tasks file, so
87
+ appending would silently duplicate. To combine, generate to a separate path and merge by
88
+ hand.
89
+ - Existing skills and agent-instruction files (`SKILL.md`, `.claude/`, `CLAUDE.md`,
90
+ `AGENTS.md`, `.cursor/`, root `skills/`) are stripped from the source copy it reads and
91
+ blocked by a hook, so pre-written guidance cannot bias which tasks it picks.
92
+ - The output is validated through the strict tasks loader before it is written — it can
93
+ never emit a file the rest of the pipeline would reject.
94
+ - **Review what it produces.** The answers are only as good as the scripts it ran.