experiment-log
Skill0 starsChanged 45 days agoMITView on GitHub
Runs a command and appends one JSONL provenance line: time, cmd, exit, duration, git commit/dirty, versions, file hashes. Its SubagentStop hook logs every subagent run. Use when: "log this run", "record provenance", "what produced this output".
1 other project uses a version of this.
Adopted by
Most recently taken up by
- CharleneTStrain/product-ops-skillstheir own version, first seen Nov 8, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- CharleneTStrain/product-ops-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository34 other files it gives its agents.
- audit-codeSkill
- audit-docsSkill
- auditSkill
- bisect-driverSkill
- coding-conventionsSkill
- commit-gateSkill
- compact-ioSkill
- cron-task-manageSkill
- cve-auditSkill
- data-integrity-auditSkill
- decision-logSkill
- deck-builderSkill
- determinism-guardSkill
- etl-validateSkill
- fetch-firstSkill
- flaky-test-detectorSkill
- github-repo-polishSkill
- golden-lockSkill
- history-leak-scanSkill
- landing-checkSkill
- llm-eval-harnessSkill
- local-secrets-manageSkill
- long-taskSkill
- milestone-trackSkill
- opus-workersSkill
- path-quirk-auditSkill
- reorg-proposalSkill
- research-briefSkill
- seed-controlSkill
- shell-portabilitySkill
- skill-vetSkill
- small-taskSkill
- token-squeezeSkill
- venue-fitSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 45
Default log file: experiments.jsonl in cwd.
- Distinctive: line 147
Don't "fix" this into a fitted number.
- Very specific: line 158
Do NOT drive a run through node -e "<quoted code>" on this machine — PS 5.1 mangles quoted -e and leaves 0-byte junk files.
- Distinctive: line 173
Commit the file if you want the provenance trail in version control.
- Distinctive: line 224
Prompt-suggestion rows are not work: No type, no description, zero usage: excluded from every count except their own.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
experiment-log — reproducibility provenance for a run
The engine is experiment-log.js (portable Node, zero deps). One logged run
answers the question "what exact inputs, code, and versions produced this
result?" — it runs your command, then appends a single JSON line recording the
ISO timestamp, cwd, command, exit code, wall-clock duration, the cwd repo's git
commit + dirty flag, the node/python versions, and the sha256 of every input
file (hashed before the run) and output file (hashed after). The log is
append-only: it is never rewritten, so the provenance trail can't be silently
edited.
This is machine provenance, deliberately separate from the project's
narrative docs. It never writes to HANDOFF.md or the append-only record —
project-memory owns the story; this owns the reproducible facts of one run.
Commands
node experiment-log.js log --cmd "<command>" [--in a,b] [--out c,d] [--note "..."] [--file <path>] [--python <path>] [--no-run]
node experiment-log.js show [--file <path>]
node experiment-log.js --canary- log hashes the
--infiles, captures git + tool versions, runs--cmdthrough the shell (stdout/stderr inherited so you see it live), records the exit code and duration, then hashes the--outfiles. Appends one JSON line. - log --no-run records the same provenance without executing (exit code and
duration are
null) — useful to pin a manual or external run's inputs. - show pretty-prints entries oldest-first (newest last), one block per run.
--in/--outare comma-separated file lists, and are also repeatable (--in a.csv --in b.csv). A comma inside a FILENAME is resolved by asking the filesystem: if the whole value names a real file it is taken whole, otherwise it splits. Before that,--in "data,v2.csv"recorded{"data":null,"v2.csv":null}— the log asserting the declared input did not exist while the real file hashed fine.- Default log file:
experiments.jsonlin cwd. An absent input/output is recorded asnulland rendered(absent); one that exists but could not be read (EACCES, or EBUSY on a locked SQLite file) is recorded as"unreadable:<CODE>". Those are different facts and used to be the same one. Nothing is ever faked. tsis stamped when the entry is WRITTEN, i.e. AFTER the command finished.durationMsgives you the other end.--python <path>declares the interpreter the run actually used. Usually unnecessary: when--cmdstarts with a python interpreter it is parsed off the front automatically. Give it when the interpreter is hidden behind a wrapper (pytest ...,make ..., an activated venv,cd sub && python x.py) — the parser reads only the FIRST token and recordsnullrather than guess. The declared path is recorded asversions.pythonRun.
The case: a backtest with a frozen regression report
A frozen regression is pinned byte-exact — but a result is only reproducible if you know the exact inputs and versions behind it. Log the run:
node experiment-log.js log --cmd "python run_backtest.py --sleeve value --report" --in price_cache.db,config/value.yaml --out reports/value_report.txt --note "monthly rebalance, value sleeve"The line now carries the git commit (was the tree dirty?), the Python version,
the sha256 of price_cache.db and the config as they were at run time, and the
hash of the report it produced. Two runs with identical --in hashes, the same
commit, and the same Python version are expected to reproduce byte-for-byte — if
the report hash differs while all inputs match, that difference is real, not
environmental.
Other uses:
- A migration or seed run — pin the schema/seed file hashes and
the git commit behind a data-loading run:
log --cmd "python -m app.seed" --in db/seed.sql --out db/snapshot.sql. - Any ad-hoc script whose result you'll want to defend later — log it once so the inputs, commit, and versions are on record instead of in your memory.
The subagent hook (automatic, no command)
hooks/subagent-log.js is a SubagentStop hook: it appends one line to
~/.claude/agent-runs.jsonl every time a subagent finishes, with no invocation
from you. It answers a different question from the CLI above — not "what
produced this output?" but "what did this agent run cost, and did it catch
anything real?" — which is the measurement no published study provides for
subagent review gates.
Register it in ~/.claude/settings.json:
{ "hooks": { "SubagentStop": [ { "hooks": [
{ "type": "command",
"command": "node \"<skills-dir>/experiment-log/hooks/subagent-log.js\"" }
] } ] } }Each line carries ts, session_id, agent_id, agent_type, name,
description, model, cwd, input_new, output_tokens, context_peak,
tool_uses, turns, duration_ms, transcript, result_head, and
finding. transcript says whether the numbers beside it are real:
parsed (they are), absent, unreadable, no-usage, or none-declared.
Without it a missing transcript produced input_new:0, output_tokens:0,
turns:0 — byte-identical to a genuinely cheap run. But most all-zero rows
are not work at all: on one real 1,531-row dataset, 798 of 807 all-zero rows
had no agent_type, no description, and a result_head like do all, 3
or the literal <no suggestion> — the harness's prompt-suggestion agent
guessing the user's next message. Only 9 were work agents whose transcript was
lost. node agent-runs.js report separates the two; never count raw rows as
runs.
finding
is always null — it is yours to fill in by hand. That column is the point of
the exercise: cost is measured automatically, value is not.
Two derivation rules the hook exists to get right, both of which produced confident wrong numbers first:
cache_read_input_tokensis cumulative but NOT monotonic — summing it across turns inflates a 78K-token run to 517K, so it is never summed; but the final turn is not the peak either (28/409 real transcripts), and a terminal<synthetic>message with all-zero usage resets a last-turn reading to 0.context_peakis the running MAX.- One API message spans several transcript lines, one per content block, all
sharing
message.id. Deduping by it is fatal for content — it keeps the leadingthinkingblock and discards everytool_usebehind it, reading 0 tools on every multi-turn agent.tool_useblocks are counted across all lines. output_tokensis the one usage field that STREAMS across those lines. The first line carries a partial count and the last carries the total;input_tokens,cache_creationandcache_readare genuinely identical across all lines of a message. Measured over 409 transcripts:output_tokensdiffers in 4,855 of 6,335 multi-line ids, the other three differ in zero, and the max is on the last line 6,335/6,335 times. Taking the first line — the original implementation — undercounted by 86.3%. Usage is deduped per id, exceptoutput_tokens, which is taken as that id's MAX. This was shipped green: the canary's fixture gave every split line the same usage object, so it passed 15/15 while encoding the identical false assumption as the code. The fixture now streams 5 → 12 → 20 and asserts the result is not the first-line sum, so it fails against the old logic.
Not reproduced on purpose: the harness's own subagent_tokens aggregate. No
combination of transcript fields matched it across five real runs (closest was
14-105 tokens under, inconsistently) and its definition is undocumented, so the
exact components are logged instead and any aggregate can be computed later.
Don't "fix" this into a fitted number.
The hook never blocks a turn: every failure path exits 0 silently. Verify with
node hooks/subagent-log.js --canary — MUST print CANARY PASS 29/29.
Windows notes
- PowerShell 5.1: wrap the whole
--cmdvalue in double quotes:--cmd "python run_backtest.py --report". Comma-lists for--in/--outneed no quoting unless a path contains spaces (then quote the whole list value). - Git Bash: single quotes work for
--cmd. - Do NOT drive a run through
node -e "<quoted code>"on this machine — PS 5.1 mangles quoted-eand leaves 0-byte junk files. Point--cmdat a real script. pythonis resolved from PATH; if it isn't installed thepythonversion is recorded asnull(shown asMISSING) rather than guessed.
Storage layout
experiments.jsonl # one JSON object per line, append-only, oldest-firstEach line: ts, cwd, cmd, exitCode (null on --no-run), durationMs
(null on --no-run), git ({commit, dirty} or null outside a repo),
versions ({node, python}), in ({path: sha256|null}), out (same), note.
Commit the file if you want the provenance trail in version control.
git.dirty reflects the state of the experiment's tracked code, not the
logging artifact: the log file itself is excluded from the dirty check (via a
:(exclude) pathspec), so an untracked experiments.jsonl does not flip
dirty false→true on the second and later runs. Two runs at the same commit
with a clean tree therefore record the same dirty value.
Exit codes
0 ok · 1 nothing to show / canary failure · 2 usage error (e.g. missing
--cmd).
Verification (the done-check)
node experiment-log.js --canarySelf-tests both directions in a throwaway temp dir: a successful run records
exit 0, a failing run records its non-zero code, --no-run records null; two
identical runs produce identical input/output hashes with every field present;
and the log is proven append-only (earlier lines never rewritten). Hash
correctness is pinned against a precomputed sha256 literal, so a broken/constant
hash implementation fails the self-test rather than passing it circularly.
node experiment-log.js --canary MUST print CANARY PASS 57/57 before you
trust a result.
Reading the log, and recording what an agent was worth
agent-runs.js is the reader and the annotator. It never rewrites
agent-runs.jsonl — that file is append-only and live. Verdicts go to a
sidecar, ~/.claude/agent-findings.jsonl, joined at read time; the latest
verdict for an agent wins.
node agent-runs.js report # the real counts
node agent-runs.js list --unannotated # work agents still unjudged
node agent-runs.js annotate <agent_id> <verdict> <how you verified it>Verdicts: caught (at least one issue later confirmed, none refuted), mixed
(some confirmed, at least one refuted), wrong (refuted), clean (nothing
reported, nothing later found in its scope), n/a (not a review — extraction,
planning, drafting). Annotate only what you verified, and say how in the
note. A verdict from memory is a fabricated data point; the note is required
so the "how" can be checked later.
Two counting rules it enforces:
- A row is a stop, not an agent. An agent resumed with SendMessage is logged again. Its token count is usually cumulative but sometimes drops between stops, so the reader takes each agent's maximum — never a sum.
- Prompt-suggestion rows are not work. No type, no description, zero usage: excluded from every count except their own.
node agent-runs.js --canary — MUST print CANARY PASS 22/22 before you trust a result.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

