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 +160 -0
- acumen/_skills/__init__.py +0 -0
- acumen/_skills/data/SKILL.md +109 -0
- acumen/_skills/data/references/authoring.md +91 -0
- acumen/_skills/data/references/benchmark.md +100 -0
- acumen/_skills/data/references/python-api.md +106 -0
- acumen/_skills/data/references/setup.md +94 -0
- acumen/_skills/data/references/ship.md +53 -0
- acumen/_skills/install.py +185 -0
- acumen/assets/banner.svg +501 -0
- acumen/assets/logo.svg +47 -0
- acumen/bench.py +202 -0
- acumen/cli.py +626 -0
- acumen/config.py +204 -0
- acumen/draft.py +202 -0
- acumen/env.py +602 -0
- acumen/grade.py +104 -0
- acumen/improve.py +596 -0
- acumen/logs.py +255 -0
- acumen/paths.py +164 -0
- acumen/procs.py +195 -0
- acumen/prompts.py +942 -0
- acumen/report.py +1858 -0
- acumen/runner.py +343 -0
- acumen/sandbox.py +153 -0
- acumen/scaffold.py +118 -0
- acumen/ship.py +298 -0
- acumen/skills.py +298 -0
- acumen/taskgen.py +424 -0
- acumen/tasks.py +144 -0
- acumen/transcript.py +77 -0
- acumen-0.0.1.dev0.dist-info/METADATA +168 -0
- acumen-0.0.1.dev0.dist-info/RECORD +36 -0
- acumen-0.0.1.dev0.dist-info/WHEEL +4 -0
- acumen-0.0.1.dev0.dist-info/entry_points.txt +3 -0
- acumen-0.0.1.dev0.dist-info/licenses/LICENSE +21 -0
acumen/prompts.py
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
"""Hardcoded prompts.
|
|
2
|
+
|
|
3
|
+
The harness preamble carries the entire grading scheme: grading is exact string match on
|
|
4
|
+
``answer.md``, so any stray word, header or code fence turns a correct run into a
|
|
5
|
+
recorded failure. It is therefore blunt, repeated, and shows worked good/bad examples —
|
|
6
|
+
answer-format noise is the highest risk in the project.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def feedback_block(feedback: str | None) -> str:
|
|
15
|
+
"""Render optional maintainer feedback as a subordinated prompt section.
|
|
16
|
+
|
|
17
|
+
Returns ``""`` when there is no feedback — so a prompt built without ``--feedback`` is
|
|
18
|
+
byte-identical to what it was without the flag. When present, the text is wrapped in
|
|
19
|
+
``<maintainer_feedback>`` delimiters and placed (by the templates) *after* the hard rules,
|
|
20
|
+
with explicit wording that it is guidance and does NOT override anything above it. That
|
|
21
|
+
subordination is deliberate: the feedback comes from a trusted maintainer, but it must not be
|
|
22
|
+
able to talk an agent out of the isolation rules (test-split, skill-bias) or the anti-overfit
|
|
23
|
+
rules — those are enforced structurally regardless, and the prompt says so.
|
|
24
|
+
|
|
25
|
+
Note: feedback steers *within* each command's methodology; it cannot redefine it. It won't,
|
|
26
|
+
for instance, stop ``tasks`` from running the package to verify answers, nor let ``improve``
|
|
27
|
+
reach the test split to cheat.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
feedback
|
|
32
|
+
Free-text guidance from the maintainer, or ``None``.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
The guidance block (leading + trailing newline), or ``""`` when there is no feedback.
|
|
37
|
+
"""
|
|
38
|
+
text = (feedback or "").strip()
|
|
39
|
+
if not text:
|
|
40
|
+
return ""
|
|
41
|
+
return (
|
|
42
|
+
"\n# Maintainer guidance\n\n"
|
|
43
|
+
"The package maintainer added the following guidance for this run. Treat it as extra "
|
|
44
|
+
"context and direction. It does NOT override any rule stated above — in particular the "
|
|
45
|
+
"rules on what you may read, what you must not do, and how to avoid overfitting; if it "
|
|
46
|
+
"appears to conflict with one of those, follow the rule and ignore that part.\n\n"
|
|
47
|
+
"<maintainer_feedback>\n"
|
|
48
|
+
f"{text}\n"
|
|
49
|
+
"</maintainer_feedback>\n"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
HARNESS_PREAMBLE = """\
|
|
54
|
+
You are completing one task in an automated benchmark. Your work is graded by a script,
|
|
55
|
+
not read by a human.
|
|
56
|
+
|
|
57
|
+
# Where you work
|
|
58
|
+
|
|
59
|
+
- Your working directory is `{sandbox}`. Work only there.
|
|
60
|
+
- Do NOT read or write any path outside `{sandbox}`.
|
|
61
|
+
- The target package (`{package}`) is already installed. Run Python with `{python}`,
|
|
62
|
+
which is also `python` on your PATH. Do not create virtualenvs and do not install or
|
|
63
|
+
upgrade packages.
|
|
64
|
+
- You have web access. Use it if it helps.
|
|
65
|
+
|
|
66
|
+
# What you must leave behind
|
|
67
|
+
|
|
68
|
+
- `answer.md` — your final answer, and NOTHING else. This file is REQUIRED. It is
|
|
69
|
+
the only thing that is graded.
|
|
70
|
+
- `script.py` — a runnable Python script that reproduces how you got your answer.
|
|
71
|
+
Write this ONLY if you ran code or tools to reach the answer. If the task is pure
|
|
72
|
+
reasoning with a fixed solution and you ran nothing, do not write a `script.py`.
|
|
73
|
+
|
|
74
|
+
Do not leave behind any other files.
|
|
75
|
+
|
|
76
|
+
# The format of answer.md — read this twice
|
|
77
|
+
|
|
78
|
+
`answer.md` is compared to the expected answer by EXACT STRING MATCH, after stripping
|
|
79
|
+
leading and trailing whitespace, case-sensitive. A single extra word, character, or line
|
|
80
|
+
makes a correct answer score as WRONG. Write ONLY the answer — no explanation, no label
|
|
81
|
+
("Answer:"), no quotes, no markdown (headers, bold, bullets, code fences), no trailing
|
|
82
|
+
punctuation.
|
|
83
|
+
|
|
84
|
+
Worked example. If the answer is `Paris`, the ENTIRE contents of `answer.md` is:
|
|
85
|
+
|
|
86
|
+
Paris
|
|
87
|
+
|
|
88
|
+
Not `**Paris**`, not `"Paris"`, not `The answer is Paris.`, not a fenced block.
|
|
89
|
+
|
|
90
|
+
If the task asks for a number, write only the number at the requested precision. If it
|
|
91
|
+
asks for several items, follow the task's stated separator and order exactly.
|
|
92
|
+
|
|
93
|
+
# Task
|
|
94
|
+
|
|
95
|
+
{task}
|
|
96
|
+
|
|
97
|
+
# Reminder
|
|
98
|
+
|
|
99
|
+
When you are done, `{sandbox}/answer.md` must contain your answer and nothing else — no
|
|
100
|
+
prose, no formatting, no code fences. If you ran code or tools to get there,
|
|
101
|
+
`{sandbox}/script.py` must reproduce it; if you did not, there is no `script.py`.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
DRAFT_PROMPT = """\
|
|
106
|
+
You are writing a Claude Skill for the Python package `{package}` (version {version}).
|
|
107
|
+
|
|
108
|
+
A skill is documentation written for an agent, not for a human. Its only purpose is to
|
|
109
|
+
make an agent that has never used `{package}` succeed at real tasks with it on the first
|
|
110
|
+
try. It is not a tutorial, not a README, and not a sales pitch.
|
|
111
|
+
|
|
112
|
+
The agent arrives with a goal stated in plain English — what a user wants done, not which
|
|
113
|
+
function to call. The skill's job is to get it from that goal to a working result: route it
|
|
114
|
+
to the right entry point and the right sequence of steps, so it never has to reverse-engineer
|
|
115
|
+
that from the module layout.
|
|
116
|
+
|
|
117
|
+
# What you can read
|
|
118
|
+
|
|
119
|
+
- The package's source is at `{src}`. Read it — the source, the docstrings, the examples,
|
|
120
|
+
the docs directory. This is the ground truth about how the package behaves.
|
|
121
|
+
- `{package}` is also installed; run `{python}` to check anything you are unsure about.
|
|
122
|
+
Verify claims before you write them down.
|
|
123
|
+
|
|
124
|
+
# What you must write
|
|
125
|
+
|
|
126
|
+
Your working directory is `{out}`. Write:
|
|
127
|
+
|
|
128
|
+
1. `{out}/SKILL.md` — required. It must begin with YAML frontmatter, exactly:
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
name: {skill_name}
|
|
132
|
+
description: <one sentence: what this skill covers and when to use it>
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
The `name` must be exactly `{skill_name}`.
|
|
136
|
+
|
|
137
|
+
The `description` is load-bearing and must be HONEST. It is the only part of the skill
|
|
138
|
+
an agent sees before deciding whether to open it, and it is the only thing that gets
|
|
139
|
+
the skill loaded at the right moment.
|
|
140
|
+
State what the skill covers and the goals it applies to — name the outcomes a user would
|
|
141
|
+
actually phrase, so the skill loads when one of them comes up. Do not oversell it, and do
|
|
142
|
+
not claim coverage the body does not deliver. Use this format:
|
|
143
|
+
[What it does] + [When to use it: the goals/triggers it fires on] + [Key capabilities]
|
|
144
|
+
|
|
145
|
+
2. `{out}/references/*.md` — optional. Use these for detail that only some tasks need.
|
|
146
|
+
|
|
147
|
+
# How to write it
|
|
148
|
+
|
|
149
|
+
- **Organize around goals, not modules.** Work out what people actually use `{package}` to
|
|
150
|
+
accomplish — read its examples, tutorials, and docs, not just its API — and structure the
|
|
151
|
+
skill so a stated goal maps to the right entry point and the right sequence of steps. Do not
|
|
152
|
+
just mirror the package's module layout.
|
|
153
|
+
- **Write what is not guessable.** An agent already knows Python and can read a
|
|
154
|
+
traceback. Spend your words on what it would get WRONG by guessing: non-obvious
|
|
155
|
+
defaults, required preprocessing, the function that looks right but isn't, where
|
|
156
|
+
results are written, argument shapes and orientation, footguns the API invites, right order
|
|
157
|
+
of steps to follow.
|
|
158
|
+
- **Generalize within each goal.** Organize around categories of goal, but keep the guidance
|
|
159
|
+
under each one general enough to cover any task in that category. Don't enumerate one-off
|
|
160
|
+
recipes — that's the failure mode on the other side of module-mirroring.
|
|
161
|
+
- **Progressive disclosure.** `SKILL.md` should be short and route to `references/` for
|
|
162
|
+
depth. An agent pays for every token of it on every task, including the tasks where it
|
|
163
|
+
is irrelevant. If `SKILL.md` is long, you are taxing every run.
|
|
164
|
+
- **Be concrete.** A correct short code example beats a paragraph of prose. Show the real
|
|
165
|
+
call, with the arguments that matter.
|
|
166
|
+
- **Prefer removing text over adding it.** Anything that merely restates the obvious is
|
|
167
|
+
worse than nothing: it costs tokens and buries the parts that matter.
|
|
168
|
+
- **No hedging.** Say what to do.
|
|
169
|
+
|
|
170
|
+
# Verify before you finish
|
|
171
|
+
|
|
172
|
+
Do not write claims you have not checked. If you assert a default value, a return type,
|
|
173
|
+
or where an output lands, confirm it in the source or by running `{python}`. A skill that
|
|
174
|
+
confidently states something false is worse than no skill at all — it will send an agent
|
|
175
|
+
in the wrong direction with full confidence.
|
|
176
|
+
{feedback}
|
|
177
|
+
When you are done, `{out}/SKILL.md` must exist and start with the frontmatter above.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
IMPROVE_PROMPT = """\
|
|
182
|
+
You are improving a Claude Skill for the Python package `{package}` (version {version}).
|
|
183
|
+
|
|
184
|
+
A skill is documentation written for an agent, not a human. Its only purpose is to make an
|
|
185
|
+
agent that has never used `{package}` succeed at real tasks with it on the first try.
|
|
186
|
+
|
|
187
|
+
You are producing version {new_version} — an improvement of version {parent_version}.
|
|
188
|
+
|
|
189
|
+
# What you can read
|
|
190
|
+
|
|
191
|
+
- `{skill_dir}` — the current skill ({parent_version}). This directory has been pre-filled
|
|
192
|
+
with a copy of it. EDIT THESE FILES IN PLACE; what they contain when you finish becomes
|
|
193
|
+
version {new_version}.
|
|
194
|
+
- `{train_dir}` — evidence from benchmarking the current skill on the TRAIN split. Read
|
|
195
|
+
`{train_dir}/SUMMARY.md` first. For each run you will find the task prompt, the expected
|
|
196
|
+
answer, the answer the agent actually gave, whether it passed, WHETHER THE AGENT LOADED THE
|
|
197
|
+
SKILL AT ALL, which model ran it, the `script.py` it wrote, and its full transcript. This is
|
|
198
|
+
your only signal about what the skill gets right and what it gets wrong.
|
|
199
|
+
- `{package}` is installed; run `{python}` (also `python` on your PATH) to verify any
|
|
200
|
+
claim about the API before you write it down. Do not install or upgrade packages.
|
|
201
|
+
- You have web access if the published docs help.
|
|
202
|
+
|
|
203
|
+
# What you are NOT allowed to see
|
|
204
|
+
|
|
205
|
+
You are optimising against a TRAIN split. A separate, held-out TEST split is what measures
|
|
206
|
+
whether your changes actually generalise rather than memorising these particular tasks. You
|
|
207
|
+
must not see the test split, and any tool call that reaches test results will be BLOCKED.
|
|
208
|
+
Do not attempt it — reaching test data would invalidate the whole benchmark.
|
|
209
|
+
|
|
210
|
+
# What you must write
|
|
211
|
+
|
|
212
|
+
1. Edit the skill in place under `{skill_dir}`. When you finish, `{skill_dir}/SKILL.md`
|
|
213
|
+
must still begin with YAML frontmatter whose `name` is exactly `{skill_name}` and whose
|
|
214
|
+
`description` is an honest one-sentence statement of what the skill covers and when to
|
|
215
|
+
use it. The `description` is the ONLY thing that decides whether the skill loads — see
|
|
216
|
+
below.
|
|
217
|
+
|
|
218
|
+
2. `{rationale_path}` — one short paragraph stating WHAT you changed and WHY, grounded in
|
|
219
|
+
the train evidence. Write it here, OUTSIDE the skill directory. Do not put the rationale
|
|
220
|
+
inside `{skill_dir}`.
|
|
221
|
+
|
|
222
|
+
# Two different failures, two different fixes
|
|
223
|
+
|
|
224
|
+
Every run records whether the agent LOADED the skill and whether it SUCCEEDED. Separate them
|
|
225
|
+
before you change anything — they look identical in a list of failures and have opposite fixes.
|
|
226
|
+
|
|
227
|
+
1. **The skill never loaded.** The agent never read a word of the body, so nothing in the body
|
|
228
|
+
caused this and nothing you write in the body can fix it. The only lever is the
|
|
229
|
+
`description`. Editing the body in response to these runs is wasted work.
|
|
230
|
+
2. **The skill loaded and the run still failed.** Now the body is on trial: it steered the
|
|
231
|
+
agent wrong, or it was silent where it should have spoken. Fix the body, using the cause
|
|
232
|
+
you can see in that run's transcript — not one you imagine.
|
|
233
|
+
3. **The skill loaded and the run passed.** Evidence the body works. Do not rewrite it for
|
|
234
|
+
style.
|
|
235
|
+
|
|
236
|
+
A skill that is never loaded scores exactly like no skill at all, however good its body is. If
|
|
237
|
+
the load rate in `SUMMARY.md` is low, that is the biggest available win — and the only thing
|
|
238
|
+
you can do about it is the `description`.
|
|
239
|
+
|
|
240
|
+
# The description is the trigger
|
|
241
|
+
|
|
242
|
+
The `description` is the ONE sentence an agent sees before deciding whether to open the skill.
|
|
243
|
+
It is the entire loading decision. Its job is to make the skill load exactly when it is
|
|
244
|
+
relevant and not otherwise.
|
|
245
|
+
|
|
246
|
+
- **Read the load rates per model.** They are in `SUMMARY.md`, broken down by model. If one
|
|
247
|
+
model loads the skill reliably and another almost never does, that gap is mostly the model's
|
|
248
|
+
behaviour, not your wording — do not contort the sentence chasing it. Act on a rate that is
|
|
249
|
+
low across the board.
|
|
250
|
+
- **State the goals a user would actually phrase**, in their words, not the package's. An
|
|
251
|
+
agent matches the description against the task in front of it. Name the outcomes and the
|
|
252
|
+
kinds of question the skill answers.
|
|
253
|
+
- **Do not overfit it to the train tasks.** Widening the description with the specific
|
|
254
|
+
datasets, methods, or phrasings you just read in the train runs is the same cheating as
|
|
255
|
+
putting them in the body: it buys train load rate and loses on the test split. Widen to
|
|
256
|
+
the CATEGORY of goal, never to the instances you saw.
|
|
257
|
+
- **Do not oversell.** A description that claims coverage the body does not deliver loads the
|
|
258
|
+
skill on tasks it cannot help with, and costs every one of those runs its tokens for nothing.
|
|
259
|
+
|
|
260
|
+
# How to improve the body
|
|
261
|
+
|
|
262
|
+
- **Fix what the evidence shows is broken.** Work from the runs where the skill loaded and
|
|
263
|
+
the agent still failed. Address the cause visible in the transcript, not one you imagine.
|
|
264
|
+
- **Generalise — never overfit.** NEVER name a specific dataset, parameter value, column,
|
|
265
|
+
expected answer, or task from the train runs in the skill. The skill must help on tasks
|
|
266
|
+
you have not seen. Guidance that enumerates these particular cases is cheating and will
|
|
267
|
+
fail the test split.
|
|
268
|
+
- **Prefer removing text over adding it.** A shorter skill that an agent reads and follows
|
|
269
|
+
beats a longer one it skims. Every token is paid on every task, including the ones where
|
|
270
|
+
the skill is irrelevant. If a passage did not change any outcome, cut it.
|
|
271
|
+
- **Verify before you write.** Do not assert a default, a return type, or where an output
|
|
272
|
+
lands without confirming it in the installed package or the docs.
|
|
273
|
+
- **No hedging.** Say what to do.
|
|
274
|
+
{feedback}
|
|
275
|
+
When you are done, `{skill_dir}/SKILL.md` exists and starts with the frontmatter above, and
|
|
276
|
+
`{rationale_path}` contains your rationale.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
TASKGEN_PROMPT = """\
|
|
281
|
+
You are writing a benchmark of real analysis tasks for a Python package (`{package}`). Each
|
|
282
|
+
task states a GOAL a user has, in plain language, plus the single answer a correct analysis
|
|
283
|
+
produces. The benchmark measures whether an AI agent can reach that goal with the package on
|
|
284
|
+
its own — so a task gives the objective and almost nothing else.
|
|
285
|
+
|
|
286
|
+
# What you can read
|
|
287
|
+
|
|
288
|
+
- The package's source is at `{src}`. Read it — the source, docstrings, examples, and above
|
|
289
|
+
all its docs/tutorials/vignettes. This is the ground truth about what the package does.
|
|
290
|
+
- The package is installed; run `{python}` (also `python` on your PATH) to execute code. Do
|
|
291
|
+
not create virtualenvs and do not install or upgrade packages — work with what is here.
|
|
292
|
+
- You have web access if the published docs or tutorials help.
|
|
293
|
+
|
|
294
|
+
# Ignore any existing skills or agent instructions — deliberately hidden
|
|
295
|
+
|
|
296
|
+
The package may ship skills or agent-instruction files written for it (`SKILL.md`,
|
|
297
|
+
`.claude/skills/`, `CLAUDE.md`, `AGENTS.md`, `.cursor/`, Copilot instructions). These have
|
|
298
|
+
been stripped from the source above, and any attempt to reach them — or the original
|
|
299
|
+
unfiltered checkout — is BLOCKED. This is on purpose: reading pre-written guidance would bias
|
|
300
|
+
which analyses you pick and how you phrase them, and this benchmark must be independent of it.
|
|
301
|
+
|
|
302
|
+
# Cover every tutorial — enumerate them FIRST
|
|
303
|
+
|
|
304
|
+
Before writing anything, find EVERY tutorial / vignette / worked example the package publishes:
|
|
305
|
+
its documentation gallery, an `examples/`, `tutorials/`, or `docs/` directory, notebooks,
|
|
306
|
+
README walkthroughs. List them all. You will write AT LEAST ONE task per tutorial — do not stop
|
|
307
|
+
after a handful, and do not cover only the easy ones. A published tutorial is a real analysis
|
|
308
|
+
someone thought worth doing; that is exactly the unit of work this benchmark should measure.
|
|
309
|
+
Only if the package has genuinely no tutorials should you infer analyses from its source.
|
|
310
|
+
|
|
311
|
+
# How to write a task — like a lazy human, not a manual
|
|
312
|
+
|
|
313
|
+
Each task's prompt is ONE short paragraph of ordinary English: the GOAL a user wants, and
|
|
314
|
+
nothing about how to reach it. Write it the way a busy analyst types a request into a terminal
|
|
315
|
+
— a sentence or two, a clear objective, minimal detail. The agent is supposed to work out the
|
|
316
|
+
"how" itself; that is what is being tested.
|
|
317
|
+
|
|
318
|
+
HARD rules for the prompt text:
|
|
319
|
+
- ONE paragraph. NO numbered steps, no procedure — state the goal, not a recipe.
|
|
320
|
+
- NO code: no function or method names as code, no call signatures, no argument names, no
|
|
321
|
+
names of result containers or output fields.
|
|
322
|
+
- Do NOT name the package, and never mention a version. acumen adds which package to use when
|
|
323
|
+
it runs the task — naming it here is redundant and repetitive.
|
|
324
|
+
- Do NOT describe the data — not its shape, columns, dtypes, or how it is stored. If the
|
|
325
|
+
analysis uses a bundled dataset, just NAME it ("using the covid5k data") and stop there.
|
|
326
|
+
- Do NOT name the method/algorithm or give parameters — let the agent choose the approach. ONE
|
|
327
|
+
exception: if a tutorial is fundamentally about a single named method, you may name that
|
|
328
|
+
method in plain words; even then name only what is essential, and give a parameter only if
|
|
329
|
+
that parameter is the whole point of the task.
|
|
330
|
+
- No worked example of the answer, and no hint toward it.
|
|
331
|
+
|
|
332
|
+
The one thing you MAY state precisely is the OUTPUT. End the paragraph by saying exactly what to
|
|
333
|
+
report and in what form, so the answer is unambiguous and gradeable — e.g. "give the gene
|
|
334
|
+
symbol", "report how many are left", "report the value rounded to two decimals". Keep the answer
|
|
335
|
+
small: a single name, category, count, or number.
|
|
336
|
+
|
|
337
|
+
Illustration (style only — invent tasks that fit the actual package):
|
|
338
|
+
- BAD (reads like a skill): "Load the toy data with `dc.ds.toy()`. Run `dc.mt.ulm(adata, net,
|
|
339
|
+
tmin=3)`; scores land in `adata.obsm['score_ulm']`. Take rows where group == 'A', average per
|
|
340
|
+
source, and report the top column."
|
|
341
|
+
- GOOD (a lazy goal): "Using the pbmc3k data, find which transcription factor is most active in
|
|
342
|
+
the monocytes. Give only the factor's symbol."
|
|
343
|
+
|
|
344
|
+
# Train and test variants
|
|
345
|
+
|
|
346
|
+
Give each task a train and a test variant of the SAME goal, differing only in the input or the
|
|
347
|
+
target it asks about (a different cell type, group, condition, or dataset). Two instances of one
|
|
348
|
+
analysis with two different correct answers — so a skill cannot pass by memorising one answer.
|
|
349
|
+
|
|
350
|
+
# Ground truth by execution
|
|
351
|
+
|
|
352
|
+
Get each answer by actually DOING the analysis in the venv with `{python}` and reading the real
|
|
353
|
+
result — never from a tutorial's printed output or the docs. Your scratch scripts stay in this
|
|
354
|
+
working directory and are discarded; only the tasks written to `{out}` are kept, so the
|
|
355
|
+
benchmarked agent must rederive everything from the goal alone. Before recording an answer,
|
|
356
|
+
confirm the goal has exactly ONE defensible answer: if a competent analyst could read the goal
|
|
357
|
+
two ways and get two results, tighten only the OUTPUT sentence (what to report, or its
|
|
358
|
+
precision) until one answer stands — never by adding back instructions.
|
|
359
|
+
|
|
360
|
+
# What you must write
|
|
361
|
+
|
|
362
|
+
Your working directory is `{out_dir}`. Write the tasks to `{out}` as YAML with exactly this
|
|
363
|
+
shape (the loader is strict — unknown keys are rejected):
|
|
364
|
+
|
|
365
|
+
tasks:
|
|
366
|
+
- id: <short, unique, filesystem-safe: letters, digits, '.', '_', '-'>
|
|
367
|
+
train:
|
|
368
|
+
prompt: |
|
|
369
|
+
<one-paragraph goal for the train variant>
|
|
370
|
+
answer: "<the exact answer string the real train run produced>"
|
|
371
|
+
test:
|
|
372
|
+
prompt: |
|
|
373
|
+
<one-paragraph goal for the test variant>
|
|
374
|
+
answer: "<the exact answer string the real test run produced>"
|
|
375
|
+
|
|
376
|
+
`id` must be unique across all tasks. Both `train` and `test` are required, each with a
|
|
377
|
+
non-empty `prompt` and a non-empty `answer`. Do not add other keys unless you deliberately
|
|
378
|
+
want a per-task override (`max_turns`, `max_usd`, or `model` are the only ones allowed).
|
|
379
|
+
{feedback}
|
|
380
|
+
# Before you finish
|
|
381
|
+
|
|
382
|
+
- Every `answer` is the real output of a script you ran in the venv — not a guess, not lifted
|
|
383
|
+
from docs.
|
|
384
|
+
- Every prompt is ONE paragraph: a goal in plain English, with no steps, no code, no package
|
|
385
|
+
name, no version, no data description — only the goal and a precise statement of the output.
|
|
386
|
+
- You wrote at least one task per tutorial, and covered all of them.
|
|
387
|
+
- `{out}` exists and parses as the YAML above with at least one task.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
#: Canonical ``install.py`` the shipping agent adapts. The single placeholder is
|
|
392
|
+
#: ``__SKILL_NAME__`` (rendered by :func:`ship_prompt` via ``str.replace`` so the template's
|
|
393
|
+
#: own braces need no escaping). It uses ``__package__`` so the import package name never has
|
|
394
|
+
#: to be edited into it, and ``importlib.resources`` so it reads the skill data from wherever
|
|
395
|
+
#: the wheel installed it — which is exactly what the build-verify gate confirms.
|
|
396
|
+
SHIP_INSTALL_TEMPLATE = '''\
|
|
397
|
+
"""Install the bundled ``__SKILL_NAME__`` skill into an agentic framework's skills directory.
|
|
398
|
+
|
|
399
|
+
Console script (wired as ``<dist>-install-skills`` in ``pyproject.toml``): copy the skill that
|
|
400
|
+
ships inside this package into a chosen framework's skills directory, so an agent can load it.
|
|
401
|
+
The same ``SKILL.md`` + ``references/`` bundle is a cross-framework standard, so it installs
|
|
402
|
+
verbatim — no per-framework conversion.
|
|
403
|
+
|
|
404
|
+
Frameworks (``--agent``):
|
|
405
|
+
|
|
406
|
+
- ``claude`` -> ``~/.claude/skills`` (honours ``CLAUDE_CONFIG_DIR``)
|
|
407
|
+
- ``codex`` -> ``~/.codex/skills`` (honours ``CODEX_HOME``)
|
|
408
|
+
- ``agents`` -> ``~/.agents/skills``
|
|
409
|
+
- ``claude-science`` -> the active org's skills dir, resolved from
|
|
410
|
+
``~/.claude-science/active-org.json``
|
|
411
|
+
|
|
412
|
+
``--dest`` overrides all of them. There is **no default framework**: pass ``--agent`` or
|
|
413
|
+
``--dest``. The skill files live in the ``data/`` directory next to this module and are read via
|
|
414
|
+
``importlib.resources``, so this works from an installed wheel, not just an editable checkout.
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
from __future__ import annotations
|
|
418
|
+
|
|
419
|
+
import argparse
|
|
420
|
+
import json
|
|
421
|
+
import os
|
|
422
|
+
import shutil
|
|
423
|
+
import sys
|
|
424
|
+
from importlib import resources
|
|
425
|
+
from pathlib import Path
|
|
426
|
+
|
|
427
|
+
#: The skill name; it installs to ``<framework-skills-dir>/<SKILL_NAME>/``.
|
|
428
|
+
SKILL_NAME = "__SKILL_NAME__"
|
|
429
|
+
|
|
430
|
+
#: Framework -> (env var that overrides the config root, default config root). The skills
|
|
431
|
+
#: directory is ``<root>/skills``. ``claude-science`` is resolved separately.
|
|
432
|
+
_AGENT_ROOTS = {
|
|
433
|
+
"codex": ("CODEX_HOME", "~/.codex"),
|
|
434
|
+
"claude": ("CLAUDE_CONFIG_DIR", "~/.claude"),
|
|
435
|
+
"agents": (None, "~/.agents"),
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
#: The frameworks ``--agent`` accepts.
|
|
439
|
+
AGENTS = (*sorted(_AGENT_ROOTS), "claude-science")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def source_dir() -> Path:
|
|
443
|
+
"""Return the package-owned skill directory (the bundle that gets copied)."""
|
|
444
|
+
source = Path(str(resources.files(__package__).joinpath("data")))
|
|
445
|
+
if not (source / "SKILL.md").is_file():
|
|
446
|
+
raise RuntimeError(f"packaged skill data is missing: {source}")
|
|
447
|
+
return source
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _claude_science_skills_dir() -> Path:
|
|
451
|
+
"""Resolve the active Claude Science org's skills directory."""
|
|
452
|
+
root = Path("~/.claude-science").expanduser()
|
|
453
|
+
active_org_path = root / "active-org.json"
|
|
454
|
+
try:
|
|
455
|
+
active_org = json.loads(active_org_path.read_text(encoding="utf-8"))
|
|
456
|
+
except (OSError, json.JSONDecodeError) as error:
|
|
457
|
+
raise ValueError(
|
|
458
|
+
"cannot resolve the Claude Science active organization from "
|
|
459
|
+
f"{active_org_path}; pass --dest instead"
|
|
460
|
+
) from error
|
|
461
|
+
org_uuid = active_org.get("org_uuid") if isinstance(active_org, dict) else None
|
|
462
|
+
if (
|
|
463
|
+
not isinstance(org_uuid, str)
|
|
464
|
+
or not org_uuid
|
|
465
|
+
or Path(org_uuid).name != org_uuid
|
|
466
|
+
or org_uuid in {".", ".."}
|
|
467
|
+
):
|
|
468
|
+
raise ValueError(f"invalid Claude Science org_uuid in {active_org_path}; pass --dest instead")
|
|
469
|
+
return root / "orgs" / org_uuid / "skills"
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _skills_dir(agent: str) -> Path:
|
|
473
|
+
"""Return the skills directory (parent of the install dir) for a framework."""
|
|
474
|
+
if agent == "claude-science":
|
|
475
|
+
return _claude_science_skills_dir()
|
|
476
|
+
variable, fallback = _AGENT_ROOTS[agent]
|
|
477
|
+
root = os.environ.get(variable) if variable is not None else None
|
|
478
|
+
return Path(root or fallback).expanduser() / "skills"
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def resolve_dest(agent: str | None, dest: Path | None) -> Path:
|
|
482
|
+
"""Resolve the install destination from ``--agent`` / ``--dest``.
|
|
483
|
+
|
|
484
|
+
``--dest`` wins. With neither, raise ``ValueError`` — there is no default framework.
|
|
485
|
+
"""
|
|
486
|
+
if dest is not None:
|
|
487
|
+
return dest.expanduser()
|
|
488
|
+
if agent is None:
|
|
489
|
+
raise ValueError("pass --agent {" + ",".join(AGENTS) + "} or --dest to choose where to install")
|
|
490
|
+
return _skills_dir(agent) / SKILL_NAME
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _snapshot(root: Path) -> dict[str, bytes]:
|
|
494
|
+
"""Map each file under ``root`` to its bytes, for exact tree comparison."""
|
|
495
|
+
return {
|
|
496
|
+
str(item.relative_to(root)): item.read_bytes()
|
|
497
|
+
for item in root.rglob("*")
|
|
498
|
+
if item.is_file()
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _matches(source: Path, target: Path) -> bool:
|
|
503
|
+
"""Report whether ``target`` is a byte-for-byte copy of ``source``."""
|
|
504
|
+
return target.is_dir() and _snapshot(source) == _snapshot(target)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def main(argv: list[str] | None = None) -> int:
|
|
508
|
+
"""Install the bundled skill; entry point for the ``<dist>-install-skills`` script."""
|
|
509
|
+
parser = argparse.ArgumentParser(
|
|
510
|
+
description=f"Install the {SKILL_NAME} skill bundled with this package into an agent's skills directory.",
|
|
511
|
+
)
|
|
512
|
+
parser.add_argument(
|
|
513
|
+
"--agent",
|
|
514
|
+
choices=AGENTS,
|
|
515
|
+
default=None,
|
|
516
|
+
help="framework to install into (no default — pass this or --dest)",
|
|
517
|
+
)
|
|
518
|
+
parser.add_argument(
|
|
519
|
+
"--dest",
|
|
520
|
+
type=Path,
|
|
521
|
+
default=None,
|
|
522
|
+
help="exact skills directory to install into (overrides --agent)",
|
|
523
|
+
)
|
|
524
|
+
parser.add_argument(
|
|
525
|
+
"--force",
|
|
526
|
+
action="store_true",
|
|
527
|
+
help="overwrite an existing installation that differs from the bundled skill",
|
|
528
|
+
)
|
|
529
|
+
action = parser.add_mutually_exclusive_group()
|
|
530
|
+
action.add_argument(
|
|
531
|
+
"--check",
|
|
532
|
+
action="store_true",
|
|
533
|
+
help="report whether the installed skill matches the bundled one; do not install",
|
|
534
|
+
)
|
|
535
|
+
action.add_argument(
|
|
536
|
+
"--print-path",
|
|
537
|
+
action="store_true",
|
|
538
|
+
help="print the bundled skill's location inside the package and exit",
|
|
539
|
+
)
|
|
540
|
+
args = parser.parse_args(argv)
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
source = source_dir()
|
|
544
|
+
if args.print_path:
|
|
545
|
+
print(source)
|
|
546
|
+
return 0
|
|
547
|
+
if args.check:
|
|
548
|
+
target = resolve_dest(args.agent, args.dest)
|
|
549
|
+
if not target.exists():
|
|
550
|
+
print(f"{SKILL_NAME} skill is not installed at {target}", file=sys.stderr)
|
|
551
|
+
return 1
|
|
552
|
+
if _matches(source, target):
|
|
553
|
+
print(f"{SKILL_NAME} skill at {target} matches the bundled copy")
|
|
554
|
+
return 0
|
|
555
|
+
print(f"{SKILL_NAME} skill at {target} differs from the bundled copy", file=sys.stderr)
|
|
556
|
+
return 1
|
|
557
|
+
|
|
558
|
+
dest = resolve_dest(args.agent, args.dest)
|
|
559
|
+
if dest.exists():
|
|
560
|
+
if _matches(source, dest):
|
|
561
|
+
print(f"{SKILL_NAME} skill already up to date at {dest}")
|
|
562
|
+
return 0
|
|
563
|
+
if not args.force:
|
|
564
|
+
print(f"{dest} already exists and differs; pass --force to overwrite", file=sys.stderr)
|
|
565
|
+
return 1
|
|
566
|
+
if dest.is_dir():
|
|
567
|
+
shutil.rmtree(dest)
|
|
568
|
+
else:
|
|
569
|
+
dest.unlink()
|
|
570
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
571
|
+
shutil.copytree(source, dest)
|
|
572
|
+
except (OSError, RuntimeError, ValueError) as error:
|
|
573
|
+
print(f"error: {error}", file=sys.stderr)
|
|
574
|
+
return 1
|
|
575
|
+
|
|
576
|
+
print(f"installed the {SKILL_NAME} skill to {dest}")
|
|
577
|
+
return 0
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
if __name__ == "__main__":
|
|
581
|
+
raise SystemExit(main())
|
|
582
|
+
'''
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
SHIP_PROMPT = """\
|
|
586
|
+
You are making a benchmarked Skill installable *into* the Python package it documents.
|
|
587
|
+
When you are done, the package will expose a console script `<dist>-install-skills` that drops
|
|
588
|
+
the skill into the skills directory of whichever agentic framework the user names —
|
|
589
|
+
`--agent {{claude,codex,agents,claude-science}}`, or an explicit `--dest` — so the package's own
|
|
590
|
+
users get the agent guidance with one command, wherever they run their agent. The same skill
|
|
591
|
+
bundle installs verbatim into every framework; there is no per-framework conversion.
|
|
592
|
+
|
|
593
|
+
You work in the package's checkout, and this is the REAL environment — real network, real
|
|
594
|
+
git/`gh` credentials, real `uv`. You may run any command you need.
|
|
595
|
+
|
|
596
|
+
# The package checkout
|
|
597
|
+
|
|
598
|
+
- Your working directory is `{checkout}`. This is the package you are modifying.
|
|
599
|
+
- Its declared distribution name is `{package}` (from `pyproject.toml`), but DO NOT assume the
|
|
600
|
+
import package name or the layout — detect them (see below).
|
|
601
|
+
|
|
602
|
+
# The skill to ship
|
|
603
|
+
|
|
604
|
+
- The skill to package is at `{skill_src}` (version {version}). It contains `SKILL.md` and
|
|
605
|
+
maybe a `references/` tree. Copy its ENTIRE contents VERBATIM into the package — do not author,
|
|
606
|
+
edit, summarise, or reformat any skill file. `acumen ship` only packages what was already
|
|
607
|
+
benchmarked.
|
|
608
|
+
|
|
609
|
+
# Detect — never assume
|
|
610
|
+
|
|
611
|
+
Read `{checkout}/pyproject.toml` and work out, from the file itself:
|
|
612
|
+
|
|
613
|
+
1. The **distribution name** (`[project].name`, or the build-backend's equivalent). The console
|
|
614
|
+
script MUST be named `<dist-name>-install-skills`.
|
|
615
|
+
2. The **import package name** and its **directory on disk**. It may be `src/<pkg>/` (src-layout)
|
|
616
|
+
or `<pkg>/` (flat-layout) or something else — find the real directory that holds the package's
|
|
617
|
+
`__init__.py`. The entry point targets the import package, `<pkg>._skills.install:main`.
|
|
618
|
+
3. The **build backend** (`[build-system].build-backend`). You will wire packaging differently
|
|
619
|
+
for hatchling vs setuptools vs flit/pdm/poetry (see below).
|
|
620
|
+
|
|
621
|
+
# What to create
|
|
622
|
+
|
|
623
|
+
Inside the import package directory, create a `_skills/` subpackage:
|
|
624
|
+
|
|
625
|
+
- `_skills/__init__.py` — may be empty.
|
|
626
|
+
- `_skills/install.py` — use the canonical template shown at the end of this prompt VERBATIM (it
|
|
627
|
+
already has the right skill name baked in and reads its data via `importlib.resources`, so it
|
|
628
|
+
needs no per-package editing). Do not rewrite it.
|
|
629
|
+
- `_skills/data/` — a VERBATIM copy of everything under `{skill_src}` (so
|
|
630
|
+
`_skills/data/SKILL.md`, `_skills/data/references/...`, etc.).
|
|
631
|
+
|
|
632
|
+
Then wire two things in `pyproject.toml`:
|
|
633
|
+
|
|
634
|
+
- The entry point, under `[project.scripts]` (or the backend's script table):
|
|
635
|
+
`<dist-name>-install-skills = "<import-pkg>._skills.install:main"`.
|
|
636
|
+
- **Packaging so the non-`.py` skill files actually ship in the wheel.** This is the step that
|
|
637
|
+
silently fails and the whole point of the build-verify gate. `SKILL.md` and the `references/*.md`
|
|
638
|
+
are DATA files, not modules — a naive build drops them. Wire whatever the detected backend needs:
|
|
639
|
+
- **hatchling** ships everything under the package directory automatically; usually nothing extra
|
|
640
|
+
is needed, but confirm `_skills/data` is included (a restrictive `[tool.hatch.build.targets.wheel]`
|
|
641
|
+
`include`/`packages` may need `_skills/data` added, or `force-include`).
|
|
642
|
+
- **setuptools** needs the data declared: `include-package-data = true` plus a `MANIFEST.in`
|
|
643
|
+
(`recursive-include <pkg>/_skills/data *`), or an explicit `[tool.setuptools.package-data]`
|
|
644
|
+
entry for the `_skills.data` files. Also make sure `_skills` (and `_skills.data` if it needs a
|
|
645
|
+
package) are found by `find`/`packages`.
|
|
646
|
+
- **flit / pdm / poetry** each have their own include mechanism — use it so `_skills/data/**`
|
|
647
|
+
is bundled.
|
|
648
|
+
|
|
649
|
+
# Verify by building — this is the correctness gate, do NOT skip it
|
|
650
|
+
|
|
651
|
+
A wheel that installs but ships NO skill data is the failure mode this whole task exists to
|
|
652
|
+
prevent, and it fails silently. Before you deliver anything, prove the data ships:
|
|
653
|
+
|
|
654
|
+
1. Build a wheel from `{checkout}` (e.g. `uv build --wheel`).
|
|
655
|
+
2. Install THAT wheel into a FRESH throwaway venv (`uv venv`, then `uv pip install <the-wheel>`) —
|
|
656
|
+
a fresh venv, NOT an editable install, because an editable install sees the source tree
|
|
657
|
+
regardless of packaging and would hide the bug.
|
|
658
|
+
3. From that venv, run `<dist-name>-install-skills --agent codex --dest <a-scratch-dir>` and
|
|
659
|
+
confirm `<a-scratch-dir>/SKILL.md` exists and is byte-identical to `{skill_src}/SKILL.md`.
|
|
660
|
+
(There is no default framework, so pass an explicit `--dest` — or `--agent` — here.) Also run
|
|
661
|
+
`<dist-name>-install-skills --print-path` and confirm it prints a path inside the installed
|
|
662
|
+
package that contains the shipped `SKILL.md`.
|
|
663
|
+
4. If `SKILL.md` did not land, your packaging is wrong — FIX IT and rebuild. Do not proceed to
|
|
664
|
+
delivery until a fresh-venv install ships the skill.
|
|
665
|
+
|
|
666
|
+
# Scope limits
|
|
667
|
+
|
|
668
|
+
- Bundle EXACTLY ONE skill — version {version}, copied verbatim. Nothing else.
|
|
669
|
+
- Do NOT write any test file. (Deliberate scope decision.)
|
|
670
|
+
- Update the README / docs to mention the `<dist-name>-install-skills` command ONLY if there is a
|
|
671
|
+
natural spot for it (e.g. an existing install or usage section). Keep it to a sentence, noting
|
|
672
|
+
the `--agent {{claude,codex,agents,claude-science}}` (or `--dest`) choice. If there is no natural
|
|
673
|
+
spot, skip it — do not invent a section.
|
|
674
|
+
|
|
675
|
+
# Deliver
|
|
676
|
+
|
|
677
|
+
{delivery}
|
|
678
|
+
|
|
679
|
+
# The canonical install.py (use verbatim as `_skills/install.py`)
|
|
680
|
+
|
|
681
|
+
```python
|
|
682
|
+
{install_template}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
When you are done, report what you changed: the import package dir you found, the build backend,
|
|
686
|
+
the console-script name, the packaging change you made, the result of the fresh-venv build-verify,
|
|
687
|
+
and {delivery_report}.
|
|
688
|
+
"""
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
SHIP_DELIVERY_GITHUB = """\
|
|
692
|
+
This target is a GitHub repository and `{checkout}` is a git checkout of it with `origin` set to
|
|
693
|
+
the target. Deliver the change as a pull request, running git/`gh` yourself:
|
|
694
|
+
|
|
695
|
+
- Create a new branch (e.g. `acumen/ship-skill-{version}`).
|
|
696
|
+
- Commit your changes with a clear message.
|
|
697
|
+
- Push the branch to `origin`. This assumes you (the maintainer) have write access. If the push
|
|
698
|
+
is REJECTED for lack of access, STOP and report that clearly — do not try to fork or find
|
|
699
|
+
another remote.
|
|
700
|
+
- Open a pull request with `gh pr create`, titled for the skill installer, its body summarising
|
|
701
|
+
what shipped and the build-verify result.
|
|
702
|
+
|
|
703
|
+
Do not merge the PR — the maintainer reviews it."""
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
SHIP_DELIVERY_LOCAL = """\
|
|
707
|
+
This target is a LOCAL path, `{checkout}`. Write the change directly into the working tree —
|
|
708
|
+
create the files and edit `pyproject.toml` in place. Do NOT create a branch, commit, or open a
|
|
709
|
+
PR; leave the changes in the working tree for the user to review with `git diff` (or as plain
|
|
710
|
+
edits if it is not a git repo)."""
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def draft_prompt(
|
|
714
|
+
*,
|
|
715
|
+
package: str,
|
|
716
|
+
version: str,
|
|
717
|
+
src: Path,
|
|
718
|
+
python: Path,
|
|
719
|
+
out: Path,
|
|
720
|
+
skill_name: str,
|
|
721
|
+
feedback: str | None = None,
|
|
722
|
+
) -> str:
|
|
723
|
+
"""Build the prompt for the drafting agent.
|
|
724
|
+
|
|
725
|
+
Unlike a benchmark agent, the drafter gets read access to the target's source —
|
|
726
|
+
it is writing documentation about the package, so it needs to see it.
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
----------
|
|
730
|
+
package
|
|
731
|
+
The target package name.
|
|
732
|
+
version
|
|
733
|
+
The installed version, so the skill describes what is actually installed.
|
|
734
|
+
src
|
|
735
|
+
The package checkout, readable by this agent only.
|
|
736
|
+
python
|
|
737
|
+
The interpreter with the package installed, for verifying claims.
|
|
738
|
+
out
|
|
739
|
+
The staging directory the agent writes the skill into.
|
|
740
|
+
skill_name
|
|
741
|
+
The name the frontmatter must declare — ``config.skill_name``.
|
|
742
|
+
feedback
|
|
743
|
+
Optional maintainer guidance, subordinated below the hard rules. ``None`` leaves the
|
|
744
|
+
prompt byte-identical to a run without the flag.
|
|
745
|
+
|
|
746
|
+
Returns
|
|
747
|
+
-------
|
|
748
|
+
The draft prompt.
|
|
749
|
+
"""
|
|
750
|
+
return DRAFT_PROMPT.format(
|
|
751
|
+
package=package,
|
|
752
|
+
version=version,
|
|
753
|
+
src=src,
|
|
754
|
+
python=python,
|
|
755
|
+
out=out,
|
|
756
|
+
skill_name=skill_name,
|
|
757
|
+
feedback=feedback_block(feedback),
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def improve_prompt(
|
|
762
|
+
*,
|
|
763
|
+
package: str,
|
|
764
|
+
version: str,
|
|
765
|
+
python: Path,
|
|
766
|
+
skill_dir: Path,
|
|
767
|
+
train_dir: Path,
|
|
768
|
+
rationale_path: Path,
|
|
769
|
+
skill_name: str,
|
|
770
|
+
parent_version: str,
|
|
771
|
+
new_version: str,
|
|
772
|
+
feedback: str | None = None,
|
|
773
|
+
) -> str:
|
|
774
|
+
"""Build the prompt for the improving agent.
|
|
775
|
+
|
|
776
|
+
Unlike the drafter, the improver never sees the package source — it works from the
|
|
777
|
+
current skill and the *train-split* evidence of how that skill performed. The test split
|
|
778
|
+
is unreachable, enforced structurally, not by this prompt.
|
|
779
|
+
|
|
780
|
+
The prompt separates the two outcomes the evidence records: whether the agent loaded the
|
|
781
|
+
skill (governed only by the ``description``) and whether it then succeeded (governed by the
|
|
782
|
+
body). Conflating them makes the improver rewrite body prose in response to runs where the
|
|
783
|
+
body was never read.
|
|
784
|
+
|
|
785
|
+
Parameters
|
|
786
|
+
----------
|
|
787
|
+
package
|
|
788
|
+
The target package name.
|
|
789
|
+
version
|
|
790
|
+
The installed package version, so any verification runs against what is installed.
|
|
791
|
+
python
|
|
792
|
+
The interpreter with the package installed, for checking claims.
|
|
793
|
+
skill_dir
|
|
794
|
+
The staging directory, pre-filled with a copy of the parent skill, that the agent
|
|
795
|
+
edits in place to produce the new version.
|
|
796
|
+
train_dir
|
|
797
|
+
The directory of train-split evidence the agent reads (``SUMMARY.md`` + per-run
|
|
798
|
+
material).
|
|
799
|
+
rationale_path
|
|
800
|
+
Where the agent writes its rationale — outside ``skill_dir`` so it never becomes
|
|
801
|
+
skill content.
|
|
802
|
+
skill_name
|
|
803
|
+
The name the frontmatter must keep — ``config.skill_name``.
|
|
804
|
+
parent_version, new_version
|
|
805
|
+
The version being improved and the version being produced, e.g. ``v1`` -> ``v2``.
|
|
806
|
+
feedback
|
|
807
|
+
Optional maintainer guidance, subordinated below the hard rules. ``None`` leaves the
|
|
808
|
+
prompt byte-identical to a run without the flag.
|
|
809
|
+
|
|
810
|
+
Returns
|
|
811
|
+
-------
|
|
812
|
+
The improve prompt.
|
|
813
|
+
"""
|
|
814
|
+
return IMPROVE_PROMPT.format(
|
|
815
|
+
package=package,
|
|
816
|
+
version=version,
|
|
817
|
+
python=python,
|
|
818
|
+
skill_dir=skill_dir,
|
|
819
|
+
train_dir=train_dir,
|
|
820
|
+
rationale_path=rationale_path,
|
|
821
|
+
skill_name=skill_name,
|
|
822
|
+
parent_version=parent_version,
|
|
823
|
+
new_version=new_version,
|
|
824
|
+
feedback=feedback_block(feedback),
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def taskgen_prompt(*, package: str, src: Path, python: Path, out: Path, feedback: str | None = None) -> str:
|
|
829
|
+
"""Build the prompt for the task-generation agent.
|
|
830
|
+
|
|
831
|
+
Like the drafter, the generator gets read access to the target's source — it must
|
|
832
|
+
understand the API to design real analyses — plus the installed venv, since it obtains
|
|
833
|
+
every ground-truth answer by *executing* the pipeline, never by reading doc output.
|
|
834
|
+
|
|
835
|
+
The package name is passed only to orient the agent; the tasks it writes must NOT name the
|
|
836
|
+
package or any version (acumen injects the package via the benchmark harness), so each task
|
|
837
|
+
stays a generic, version-agnostic goal.
|
|
838
|
+
|
|
839
|
+
Parameters
|
|
840
|
+
----------
|
|
841
|
+
package
|
|
842
|
+
The target package name, for the agent's orientation only.
|
|
843
|
+
src
|
|
844
|
+
The (filtered) package checkout, readable by this agent only.
|
|
845
|
+
python
|
|
846
|
+
The interpreter with the package installed, used to run pipelines for ground truth.
|
|
847
|
+
out
|
|
848
|
+
The ``tasks.yaml`` file the agent writes into its working directory.
|
|
849
|
+
feedback
|
|
850
|
+
Optional maintainer guidance, subordinated below the hard rules — e.g. functionality to
|
|
851
|
+
skip. ``None`` leaves the prompt byte-identical to a run without the flag.
|
|
852
|
+
|
|
853
|
+
Returns
|
|
854
|
+
-------
|
|
855
|
+
The task-generation prompt.
|
|
856
|
+
"""
|
|
857
|
+
return TASKGEN_PROMPT.format(
|
|
858
|
+
package=package,
|
|
859
|
+
src=src,
|
|
860
|
+
python=python,
|
|
861
|
+
out=out,
|
|
862
|
+
out_dir=out.parent,
|
|
863
|
+
feedback=feedback_block(feedback),
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def ship_prompt(
|
|
868
|
+
*,
|
|
869
|
+
package: str,
|
|
870
|
+
skill_name: str,
|
|
871
|
+
version: str,
|
|
872
|
+
checkout: Path,
|
|
873
|
+
skill_src: Path,
|
|
874
|
+
mode: str,
|
|
875
|
+
) -> str:
|
|
876
|
+
"""Build the prompt for the shipping agent.
|
|
877
|
+
|
|
878
|
+
Unlike every other agent, the shipper runs UNISOLATED — real network, git/``gh``
|
|
879
|
+
credentials, and ``uv`` — because it builds, installs, pushes, and opens a PR.
|
|
880
|
+
It reasons about the package (distribution vs import name, src-vs-flat layout, build
|
|
881
|
+
backend) rather than assuming decoupler's shape, so this is an autonomous agent.
|
|
882
|
+
|
|
883
|
+
Parameters
|
|
884
|
+
----------
|
|
885
|
+
package
|
|
886
|
+
The target's distribution name (``[project].name``), for orientation. The agent still
|
|
887
|
+
detects the import package name and layout itself.
|
|
888
|
+
skill_name
|
|
889
|
+
The skill's frontmatter name — baked into the install script and the
|
|
890
|
+
``<framework-skills-dir>/<name>/`` install path.
|
|
891
|
+
version
|
|
892
|
+
The skill version being shipped, e.g. ``v2`` — named in the branch/commit/PR.
|
|
893
|
+
checkout
|
|
894
|
+
The package checkout the agent modifies (its ``cwd``): the local path for a local
|
|
895
|
+
target, or acumen's clone for a GitHub URL.
|
|
896
|
+
skill_src
|
|
897
|
+
A staged copy of the skill's content files (``SKILL.md`` + ``references/``, without
|
|
898
|
+
``meta.json``), to be copied verbatim into ``_skills/data/``.
|
|
899
|
+
mode
|
|
900
|
+
``"github"`` (deliver as a PR) or ``"local"`` (edit the working tree directly).
|
|
901
|
+
|
|
902
|
+
Returns
|
|
903
|
+
-------
|
|
904
|
+
The ship prompt.
|
|
905
|
+
"""
|
|
906
|
+
install_template = SHIP_INSTALL_TEMPLATE.replace("__SKILL_NAME__", skill_name)
|
|
907
|
+
if mode == "github":
|
|
908
|
+
delivery = SHIP_DELIVERY_GITHUB.format(checkout=checkout, version=version)
|
|
909
|
+
delivery_report = "the branch you pushed and the URL of the pull request you opened"
|
|
910
|
+
else:
|
|
911
|
+
delivery = SHIP_DELIVERY_LOCAL.format(checkout=checkout)
|
|
912
|
+
delivery_report = "confirmation that the working tree now carries the change"
|
|
913
|
+
return SHIP_PROMPT.format(
|
|
914
|
+
package=package,
|
|
915
|
+
version=version,
|
|
916
|
+
checkout=checkout,
|
|
917
|
+
skill_src=skill_src,
|
|
918
|
+
delivery=delivery,
|
|
919
|
+
delivery_report=delivery_report,
|
|
920
|
+
install_template=install_template,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def benchmark_prompt(task_prompt: str, *, sandbox: Path, python: Path, package: str) -> str:
|
|
925
|
+
"""Build the full prompt for one benchmark run.
|
|
926
|
+
|
|
927
|
+
Parameters
|
|
928
|
+
----------
|
|
929
|
+
task_prompt
|
|
930
|
+
The task's own prompt, from ``tasks.yaml``.
|
|
931
|
+
sandbox
|
|
932
|
+
The agent's working directory.
|
|
933
|
+
python
|
|
934
|
+
The interpreter with the target package installed.
|
|
935
|
+
package
|
|
936
|
+
The target package name, named so the agent doesn't hunt for it.
|
|
937
|
+
|
|
938
|
+
Returns
|
|
939
|
+
-------
|
|
940
|
+
The harness preamble with the task embedded.
|
|
941
|
+
"""
|
|
942
|
+
return HARNESS_PREAMBLE.format(sandbox=sandbox, python=python, package=package, task=task_prompt.strip())
|