Andrej — the observe→analyze→act iteration loop
Andrej is the empirical decision-discipline you run inside every
iteration when you are driving a live system toward a goal — a fix, a perf win,
a converged run, a root cause. Instead of guessing a change from intuition or a
sparse log, you gather ground-truth signal, interpret it against a mental
model, make exactly ONE bounded change, measure a scalar metric, and keep-or-
revert — then write down what happened and loop. The data picks the next
action; you never guess.
Like its sibling terry, the skill carries a human name: Andrej nods to
Andrej Karpathy and the autonomous edit → run a fixed budget → measure a
scalar → commit-or-revert (via git) agent loop it generalizes (see
References). This skill is that loop made explicit and domain-agnostic: it
applies to CI triage, performance tuning, flaky-test hunts, hyperparameter
search, incident response, and babysitting any long-running autonomous run.
It is the generalization of five primitives wired into one loop:
- signal — ground-truth observation: a trace, a metric stream, a status
endpoint, a repro, a probe of the live code path (NOT a buffered log grep)
- scalar metric — the one number that mechanically decides keep-vs-revert
(failing-test count, p99 latency, val loss, checkpoint reached)
- hypothesis — a written, falsifiable guess at the cause, carried 1–2 at a
time, not a shotgun of patches
- bounded experiment — ONE variable changed, run to a FIXED budget
(ticks/steps/iterations/time), so any metric delta has an unambiguous cause
- ledger — an append-only record of every trial (change → metric → kept?)
so the loop is forensically reconstructable and never re-litigates dead ends
When to use
Use Andrej whenever you are about to change something and re-run to see
if it helped — i.e. any iterative loop against a system whose behavior you can
observe and measure. Typical shapes:
- a bug/test failure you're tempted to "fix and re-run and hope"
- performance tuning (which change actually moved p99?)
- a flaky / non-deterministic failure you want to make reliable
- hyperparameter / config search toward a metric
- babysitting a long-running autonomous run (an agent, a training job, a
long-running simulation) — poll its live state each iteration and steer from data
- root-causing an unclear blocker where you don't yet know the mechanism
When NOT to use
- A single quick, obvious change with an obvious result — just do it; no loop.
- Work with no observable signal and no measurable outcome — you can't run
the loop without a metric to gate on. (First make the system observable, THEN
loop — see
references/metrics-and-hotspot-analysis.md.)
- A pure design / planning task with nothing to run yet.
- A genuinely one-shot, irreversible action where you can't keep-or-revert.
The observe→analyze→act loop (3 steps)
Each iteration runs exactly these three steps, then appends to the ledger and
loops:
- OBSERVE — gather ground-truth signal from the LIVE system, not from
intuition or a stale log. Prefer a structured channel (a status endpoint, a
machine-readable metric/event stream, a runtime probe) over grepping a
buffered log. If the root cause is unclear, your first act is to ADD
instrumentation to the exact live code path and read the trace — instrument
before you hypothesize. Reproduce the failure before you try to fix it.
- ANALYZE (the orient step teams skip) — interpret the signal through a
mental model. Find the hotspot/sink (the most-visited node, the longest
span, the highest-failure step), spot oscillation (a back-and-forth
that's burning iterations), read the final state, and write down 1–2
FALSIFIABLE hypotheses for the cause. Don't act on a stale or polluted
picture — a fast loop on a wrong model is worse than a slower well-oriented
one.
- ACT — make exactly ONE bounded change targeting the top hypothesis. Run
it to a FIXED budget. Diff the scalar metric against the baseline: improved →
KEEP (commit); not → REVERT (git). Then append the trial to the ledger and
loop. If the same blocker survives the SECOND attempt (don't start a 3rd
serial tweak), STOP serial iteration and escalate (read the source/spec,
web-search, or fan out one experiment per hypothesis — the escalation ladder).
Full detail (what to instrument, the orient failure-taxonomy, the
keep-or-revert gate, the stall escape-hatch): references/observe-analyze-act-loop.md.
Core disciplines
Six rules, each domain-agnostic and each hard-won. Internalize them; they are
what separate this loop from "tweak and pray."
- Instrument before you hypothesize. Unclear blocker? Add observability to
the exact live code path (per-iteration index / state / inputs / return
value), run ONCE, READ the trace, resolve the mechanism from fact — THEN
write the fix. Guessing-then-rerunning is the #1 time sink. (measure, don't
guess.)
- Empirical probe over static analysis. For facts about the running system
(reachability, collision, what a flag means right now), trust a runtime probe
of the live system over a static model (a decoded data file, a unioned
table, a simulator with idealized inputs). Static-vs-runtime disagreement is
itself the clue — the model is wrong, not the system.
- Structured polling over log-grep. Expose live state on a structured
channel (a
/state endpoint returning JSON; a --metrics counter stream)
and query THAT. Log grep is buffered, unparsed, and noisy; a status endpoint +
a mechanical analyzer give instant, unambiguous hotspot/oscillation/final-
state facts so the next action is data-driven.
- Escalation when stuck. After the SECOND failed attempt on the SAME wall
(i.e. don't start a 3rd serial tweak), STOP tweaking. Climb the ladder: read
the source/spec for the subsystem → web-search how others solved it → save a
checkpoint and fan out one experiment per distinct hypothesis
(parallel-hypotheses) in parallel, then merge the winner. Serial iteration on
a hard multi-cause blocker is the wrong shape.
- Determinism as a tool. A bounded/seeded system is deterministic: same
inputs + same budget ⇒ same output. PROVE a fix with TWO byte-identical runs
(matching output hashes) at a FIXED budget. Run-to-run divergence is NEVER
"flaky" — it's a real cause (a wall-clock branch, a code change between runs,
shared mutable state). Hunt it; don't shrug.
- Verify before claim. Never report a result (it's fixed / it passes / the
metric is X) from a celebratory narrative or a misleading log line. Read the
actual end-state artifact — the flag value, the exit code, the metric readout
— and paste it. Evidence precedes assertion, always.
Mental model: the scientific method on a tick budget
Map the loop to disciplined experimental science:
- Observation = your signal. Logs, traces, metrics, a reproduced failure —
the raw data you must explain. No data → instrument first.
- Hypothesis = a falsifiable cause. Written down, 1–2 at a time. A good
debugger carries few hypotheses, not a shotgun.
- Experiment = ONE variable, FIXED budget. Change one thing so the result
has an unambiguous cause; cap the run so trials are comparable and the loop
can't run away.
- Measurement = the scalar metric. The single number that mechanically
accepts or rejects — not a vibe.
- Replication = the determinism proof. Two identical runs = real result;
divergence = a bug to root-cause, not noise.
- Lab notebook = the ledger. Every trial appended: change → metric → kept?
So you never re-run a dead end and can reconstruct how you got here.
It is also OODA (Observe–Orient–Decide–Act) with Orient promoted to a
first-class step: the failure taxonomy in
references/observe-analyze-act-loop.md lets you diagnose WHICH phase of a
stalled loop is broken (missing signal? stale model? incoherent change? botched
run?).
Worked example: one observe→analyze→act cycle (a flaky-test hunt)
A CI job fails intermittently. The instinct is "re-run it." Instead, run one
loop. The ledger before this iteration:
// research_state.json (excerpt)
"goal": "make integration test suite deterministic (0 flaky failures / 20 runs)",
"metric": { "name": "failures_per_20_runs", "direction": "min", "baseline": 6 },
"budget": "20 sequential runs at a FIXED seed",
"hypotheses": [
{ "id": "h-shared-tmpdir", "status": "open",
"text": "tests share a tmp dir; order-dependent collision" }
],
"trials": [] // append-only; empty so far
The iteration:
- OBSERVE — don't re-run and hope. INSTRUMENT first: enable per-test
timing + the resource each test touches, and run the suite to the FIXED
budget (20 runs, fixed seed). Capture a structured artifact (a JUnit/JSON
report), not a scrollback grep. The failure reproduces 6/20 — repro
confirmed.
- ANALYZE — feed the 20 reports to a mechanical analyzer (the
analyze-metrics.py template generalized): the hotspot is one test file
(5 of 6 failures land there); the oscillation is a pass↔fail flip
correlated with test-execution ORDER. Mental model: a shared mutable
resource, order-dependent. Top FALSIFIABLE hypothesis: h-shared-tmpdir
(two tests write the same tmp path). Second: a wall-clock branch.
- ACT — change exactly ONE variable: give each test an isolated tmp dir
(don't also "fix" the clock thing — one variable). Re-run to the SAME budget.
Metric: 6 → 0 failures/20. Improved → KEEP (
git commit). PROVE it: two more
20-run passes produce byte-identical reports → deterministic. VERIFY BEFORE
CLAIM: paste "0/20, two identical report hashes," not "should be fixed now."
Append the trial to the ledger; h-shared-tmpdir → confirmed.
Same shape fits perf tuning (signal = flamegraph; metric = p99; one knob per
run), babysitting a long agent run (signal = curl /state; metric = checkpoint
reached; one steer per poll), or hyperparameter search (signal = val-loss curve;
metric = val loss; one hyperparameter per run).
Quick start
Define the goal, the metric, and the budget — copy the state template
and fill the top three keys. Without a scalar metric and a fixed budget you
cannot run the loop.
cp skills/andrej/templates/research_state.json research_state.json
# edit: goal, metric{name,direction,baseline}, budget (a FIXED unit)
git add research_state.json && git commit -m "andrej: init ledger"
Make the system observable — add a structured signal channel before you
start guessing. A status endpoint and/or a machine-readable metric stream:
# OBSERVE via a structured channel, not a log grep:
curl -s http://127.0.0.1:8765/state | python3 -m json.tool # live state
<your-run> --metrics > run.log 2>&1 # emit counters
Run one cycle and analyze it mechanically — turn the run's signal into
root-cause facts (hotspot / oscillation / final state) instead of eyeballing:
cp skills/andrej/templates/analyze-metrics.py analyze-metrics.py
python3 analyze-metrics.py run.log # most-visited node, top bounces, final state
Make ONE bounded change, gate on the metric, log it. Change one variable,
run to the FIXED budget, diff the scalar vs baseline, keep-or-revert, append
the trial:
<your-run> --max-iters 100000 > trial.log # ONE change, FIXED budget
# metric improved? → git commit -m "trial N: <change> moved <metric> X→Y"
# metric worse/same? → git revert / git checkout -- .
# then append {change, metric, kept} to research_state.json "trials"
After the 2nd failed attempt on the same wall, escalate — stop serial
tweaking (don't start a 3rd); read the source/spec, web-search, or fan out one
experiment per hypothesis from a common checkpoint and merge the winner (see
the escalation ladder).
References
references/observe-analyze-act-loop.md — the 3-step loop in depth: the OODA
orient/failure-taxonomy, instrument-before-hypothesize, reproduce-before-fix,
one-variable + fixed-budget + keep-or-revert, the escalation ladder, and a
copy-paste loop checklist.
references/metrics-and-hotspot-analysis.md — how to make a system
observable and turn a raw run into root-cause facts: structured polling vs
log-grep, the metric/signal/budget mapping across domains, hotspot / sink /
oscillation / spike detection, the empirical-probe-over-static rule.
references/lessons-and-antipatterns.md — the hard-won failure modes of the
loop itself (guess-and-rerun, tunneling past the stall, static-over-runtime,
"flaky," claim-without-evidence, stale signal) as Symptom→Root cause→Fix, with
a quick triage table.
External sources
github.com/yibie/awesome-autoresearch — a curated index of the
observe→analyze→act autoresearch methodology and tooling.
- Boyd's OODA loop (Observe–Orient–Decide–Act) and the scientific-method /
hypothesis-driven debugging tradition — the general lineage this loop draws on.
Templates
templates/research_state.json — the append-only research ledger /
experiment-tracking state file shape, self-documented via _-prefixed keys.
templates/analyze-metrics.py — a generic metric/event-stream analyzer
skeleton: parse a run → hotspot/sink, oscillation/bounce transitions, final
state, top-N. Adapt the parse patterns to your domain.
templates/iteration-prompt.md — a fill-in-the-blanks contract for running
ONE disciplined observe→analyze→act iteration (yourself or a dispatched
worker), with a filled example.
Relationship to terry
Andrej is the SIBLING of terry, not a subset. Terry is the
orchestration manager — the priority-queue heartbeat that dispatches a fleet
of worker agents across git worktrees and merges their branches. Andrej
is the brain of the empirical work that happens inside that fleet:
- It is exactly what a terry worker runs in its own context to drive its ONE
scoped task to a result (observe→analyze→act until the metric gate is met).
- It is also what terry itself runs in the OBSERVE → INTEGRATE part of each
heartbeat tick — poll live state via structured channels, verify the evidence
line before claiming a merge.
- Terry's
references/lessons-and-antipatterns.md and this skill's share DNA
(determinism-not-flaky, verify-before-claim, parallel-hypotheses-when-stuck):
terry frames them at the fleet level; Andrej frames them at the single-
iteration level. Use Andrej to decide what one change to make next;
use terry to run many such loops in parallel and integrate the winners.
1---2name: andrej3description: Invoke when iterating toward a fix/optimization/answer against a live system and tempted to guess from sparse signals — an observe→analyze→act loop over instrumentation + a scalar metric + one-variable bounded experiments + a keep-or-revert gate + an append-only ledger; covers instrument-before-you-hypothesize, the stall escape-hatch, and the guess-and-rerun / stale-context / "flaky" / claim-without-evidence anti-patterns that make iteration spin.4---56# Andrej — the observe→analyze→act iteration loop78Andrej is the **empirical decision-discipline** you run inside every9iteration when you are driving a live system toward a goal — a fix, a perf win,10a converged run, a root cause. Instead of guessing a change from intuition or a11sparse log, you **gather ground-truth signal, interpret it against a mental12model, make exactly ONE bounded change, measure a scalar metric, and keep-or-13revert** — then write down what happened and loop. The data picks the next14action; you never guess.1516Like its sibling *terry*, the skill carries a human name: **Andrej** nods to17Andrej Karpathy and the autonomous **edit → run a fixed budget → measure a18scalar → commit-or-revert (via git)** agent loop it generalizes (see19References). This skill is that loop made explicit and domain-agnostic: it20applies to CI triage, performance tuning, flaky-test hunts, hyperparameter21search, incident response, and babysitting any long-running autonomous run.2223It is the generalization of five primitives wired into one loop:2425- **signal** — ground-truth observation: a trace, a metric stream, a status26 endpoint, a repro, a probe of the live code path (NOT a buffered log grep)27- **scalar metric** — the one number that mechanically decides keep-vs-revert28 (failing-test count, p99 latency, val loss, checkpoint reached)29- **hypothesis** — a written, falsifiable guess at the cause, carried 1–2 at a30 time, not a shotgun of patches31- **bounded experiment** — ONE variable changed, run to a FIXED budget32 (ticks/steps/iterations/time), so any metric delta has an unambiguous cause33- **ledger** — an append-only record of every trial (change → metric → kept?)34 so the loop is forensically reconstructable and never re-litigates dead ends3536## When to use3738Use Andrej whenever you are about to **change something and re-run to see39if it helped** — i.e. any iterative loop against a system whose behavior you can40observe and measure. Typical shapes:4142- a bug/test failure you're tempted to "fix and re-run and hope"43- performance tuning (which change actually moved p99?)44- a flaky / non-deterministic failure you want to make reliable45- hyperparameter / config search toward a metric46- babysitting a long-running autonomous run (an agent, a training job, a47 long-running simulation) — poll its live state each iteration and steer from data48- root-causing an unclear blocker where you don't yet know the mechanism4950## When NOT to use5152- A single quick, obvious change with an obvious result — just do it; no loop.53- Work with **no observable signal and no measurable outcome** — you can't run54 the loop without a metric to gate on. (First make the system observable, THEN55 loop — see `references/metrics-and-hotspot-analysis.md`.)56- A pure design / planning task with nothing to run yet.57- A genuinely **one-shot, irreversible** action where you can't keep-or-revert.5859## The observe→analyze→act loop (3 steps)6061Each iteration runs exactly these three steps, then appends to the ledger and62loops:63641. **OBSERVE** — gather ground-truth signal from the LIVE system, not from65 intuition or a stale log. Prefer a structured channel (a status endpoint, a66 machine-readable metric/event stream, a runtime probe) over grepping a67 buffered log. If the root cause is unclear, your first act is to ADD68 instrumentation to the exact live code path and read the trace — instrument69 before you hypothesize. Reproduce the failure before you try to fix it.702. **ANALYZE** (the orient step teams skip) — interpret the signal through a71 mental model. Find the **hotspot/sink** (the most-visited node, the longest72 span, the highest-failure step), spot **oscillation** (a back-and-forth73 that's burning iterations), read the **final state**, and write down 1–274 FALSIFIABLE hypotheses for the cause. Don't act on a stale or polluted75 picture — a fast loop on a wrong model is worse than a slower well-oriented76 one.773. **ACT** — make exactly ONE bounded change targeting the top hypothesis. Run78 it to a FIXED budget. Diff the scalar metric against the baseline: improved →79 KEEP (commit); not → REVERT (git). Then append the trial to the ledger and80 loop. If the same blocker survives the SECOND attempt (don't start a 3rd81 serial tweak), STOP serial iteration and escalate (read the source/spec,82 web-search, or fan out one experiment per hypothesis — the escalation ladder).8384Full detail (what to instrument, the orient failure-taxonomy, the85keep-or-revert gate, the stall escape-hatch): **`references/observe-analyze-act-loop.md`**.8687## Core disciplines8889Six rules, each domain-agnostic and each hard-won. Internalize them; they are90what separate this loop from "tweak and pray."9192- **Instrument before you hypothesize.** Unclear blocker? Add observability to93 the exact live code path (per-iteration index / state / inputs / return94 value), run ONCE, READ the trace, resolve the mechanism from fact — THEN95 write the fix. Guessing-then-rerunning is the #1 time sink. *(measure, don't96 guess.)*97- **Empirical probe over static analysis.** For facts about the running system98 (reachability, collision, what a flag means right now), trust a runtime probe99 of the live system over a static model (a decoded data file, a unioned100 table, a simulator with idealized inputs). Static-vs-runtime disagreement is101 itself the clue — the model is wrong, not the system.102- **Structured polling over log-grep.** Expose live state on a structured103 channel (a `/state` endpoint returning JSON; a `--metrics` counter stream)104 and query THAT. Log grep is buffered, unparsed, and noisy; a status endpoint +105 a mechanical analyzer give instant, unambiguous hotspot/oscillation/final-106 state facts so the next action is data-driven.107- **Escalation when stuck.** After the SECOND failed attempt on the SAME wall108 (i.e. don't start a 3rd serial tweak), STOP tweaking. Climb the ladder: read109 the source/spec for the subsystem → web-search how others solved it → save a110 checkpoint and **fan out one experiment per distinct hypothesis**111 (parallel-hypotheses) in parallel, then merge the winner. Serial iteration on112 a hard multi-cause blocker is the wrong shape.113- **Determinism as a tool.** A bounded/seeded system is deterministic: same114 inputs + same budget ⇒ same output. PROVE a fix with TWO byte-identical runs115 (matching output hashes) at a FIXED budget. Run-to-run divergence is NEVER116 "flaky" — it's a real cause (a wall-clock branch, a code change between runs,117 shared mutable state). Hunt it; don't shrug.118- **Verify before claim.** Never report a result (it's fixed / it passes / the119 metric is X) from a celebratory narrative or a misleading log line. Read the120 actual end-state artifact — the flag value, the exit code, the metric readout121 — and paste it. Evidence precedes assertion, always.122123## Mental model: the scientific method on a tick budget124125Map the loop to disciplined experimental science:126127- **Observation = your signal.** Logs, traces, metrics, a reproduced failure —128 the raw data you must explain. No data → instrument first.129- **Hypothesis = a falsifiable cause.** Written down, 1–2 at a time. A good130 debugger carries few hypotheses, not a shotgun.131- **Experiment = ONE variable, FIXED budget.** Change one thing so the result132 has an unambiguous cause; cap the run so trials are comparable and the loop133 can't run away.134- **Measurement = the scalar metric.** The single number that mechanically135 accepts or rejects — not a vibe.136- **Replication = the determinism proof.** Two identical runs = real result;137 divergence = a bug to root-cause, not noise.138- **Lab notebook = the ledger.** Every trial appended: change → metric → kept?139 So you never re-run a dead end and can reconstruct how you got here.140141It is also OODA (Observe–Orient–Decide–Act) with **Orient** promoted to a142first-class step: the failure taxonomy in143`references/observe-analyze-act-loop.md` lets you diagnose WHICH phase of a144stalled loop is broken (missing signal? stale model? incoherent change? botched145run?).146147## Worked example: one observe→analyze→act cycle (a flaky-test hunt)148149A CI job fails intermittently. The instinct is "re-run it." Instead, run one150loop. The ledger before this iteration:151152```jsonc153// research_state.json (excerpt)154"goal": "make integration test suite deterministic (0 flaky failures / 20 runs)",155"metric": { "name": "failures_per_20_runs", "direction": "min", "baseline": 6 },156"budget": "20 sequential runs at a FIXED seed",157"hypotheses": [158 { "id": "h-shared-tmpdir", "status": "open",159 "text": "tests share a tmp dir; order-dependent collision" }160],161"trials": [] // append-only; empty so far162```163164The iteration:1651661. **OBSERVE** — don't re-run and hope. INSTRUMENT first: enable per-test167 timing + the resource each test touches, and run the suite to the FIXED168 budget (20 runs, fixed seed). Capture a structured artifact (a JUnit/JSON169 report), not a scrollback grep. The failure reproduces 6/20 — repro170 confirmed.1712. **ANALYZE** — feed the 20 reports to a mechanical analyzer (the172 `analyze-metrics.py` template generalized): the **hotspot** is one test file173 (5 of 6 failures land there); the **oscillation** is a pass↔fail flip174 correlated with test-execution ORDER. Mental model: a shared mutable175 resource, order-dependent. Top FALSIFIABLE hypothesis: `h-shared-tmpdir`176 (two tests write the same tmp path). Second: a wall-clock branch.1773. **ACT** — change exactly ONE variable: give each test an isolated tmp dir178 (don't also "fix" the clock thing — one variable). Re-run to the SAME budget.179 Metric: 6 → 0 failures/20. Improved → KEEP (`git commit`). PROVE it: two more180 20-run passes produce byte-identical reports → deterministic. VERIFY BEFORE181 CLAIM: paste "0/20, two identical report hashes," not "should be fixed now."182 Append the trial to the ledger; `h-shared-tmpdir` → `confirmed`.183184Same shape fits perf tuning (signal = flamegraph; metric = p99; one knob per185run), babysitting a long agent run (signal = `curl /state`; metric = checkpoint186reached; one steer per poll), or hyperparameter search (signal = val-loss curve;187metric = val loss; one hyperparameter per run).188189## Quick start1901911. **Define the goal, the metric, and the budget** — copy the state template192 and fill the top three keys. Without a scalar metric and a fixed budget you193 cannot run the loop.194195 ```bash196 cp skills/andrej/templates/research_state.json research_state.json197 # edit: goal, metric{name,direction,baseline}, budget (a FIXED unit)198 git add research_state.json && git commit -m "andrej: init ledger"199 ```2002012. **Make the system observable** — add a structured signal channel before you202 start guessing. A status endpoint and/or a machine-readable metric stream:203204 ```bash205 # OBSERVE via a structured channel, not a log grep:206 curl -s http://127.0.0.1:8765/state | python3 -m json.tool # live state207 <your-run> --metrics > run.log 2>&1 # emit counters208 ```2092103. **Run one cycle and analyze it mechanically** — turn the run's signal into211 root-cause facts (hotspot / oscillation / final state) instead of eyeballing:212213 ```bash214 cp skills/andrej/templates/analyze-metrics.py analyze-metrics.py215 python3 analyze-metrics.py run.log # most-visited node, top bounces, final state216 ```2172184. **Make ONE bounded change, gate on the metric, log it.** Change one variable,219 run to the FIXED budget, diff the scalar vs baseline, keep-or-revert, append220 the trial:221222 ```bash223 <your-run> --max-iters 100000 > trial.log # ONE change, FIXED budget224 # metric improved? → git commit -m "trial N: <change> moved <metric> X→Y"225 # metric worse/same? → git revert / git checkout -- .226 # then append {change, metric, kept} to research_state.json "trials"227 ```2282295. **After the 2nd failed attempt on the same wall, escalate** — stop serial230 tweaking (don't start a 3rd); read the source/spec, web-search, or fan out one231 experiment per hypothesis from a common checkpoint and merge the winner (see232 the escalation ladder).233234## References235236- `references/observe-analyze-act-loop.md` — the 3-step loop in depth: the OODA237 orient/failure-taxonomy, instrument-before-hypothesize, reproduce-before-fix,238 one-variable + fixed-budget + keep-or-revert, the escalation ladder, and a239 copy-paste loop checklist.240- `references/metrics-and-hotspot-analysis.md` — how to make a system241 observable and turn a raw run into root-cause facts: structured polling vs242 log-grep, the metric/signal/budget mapping across domains, hotspot / sink /243 oscillation / spike detection, the empirical-probe-over-static rule.244- `references/lessons-and-antipatterns.md` — the hard-won failure modes of the245 loop itself (guess-and-rerun, tunneling past the stall, static-over-runtime,246 "flaky," claim-without-evidence, stale signal) as Symptom→Root cause→Fix, with247 a quick triage table.248249### External sources250251- `github.com/yibie/awesome-autoresearch` — a curated index of the252 observe→analyze→act autoresearch methodology and tooling.253- Boyd's **OODA loop** (Observe–Orient–Decide–Act) and the **scientific-method /254 hypothesis-driven debugging** tradition — the general lineage this loop draws on.255256## Templates257258- `templates/research_state.json` — the append-only research ledger /259 experiment-tracking state file shape, self-documented via `_`-prefixed keys.260- `templates/analyze-metrics.py` — a generic metric/event-stream analyzer261 skeleton: parse a run → hotspot/sink, oscillation/bounce transitions, final262 state, top-N. Adapt the parse patterns to your domain.263- `templates/iteration-prompt.md` — a fill-in-the-blanks contract for running264 ONE disciplined observe→analyze→act iteration (yourself or a dispatched265 worker), with a filled example.266267## Relationship to terry268269Andrej is the SIBLING of `terry`, not a subset. **Terry** is the270orchestration *manager* — the priority-queue heartbeat that dispatches a fleet271of worker agents across git worktrees and merges their branches. **Andrej**272is the *brain* of the empirical work that happens inside that fleet:273274- It is exactly what a terry worker runs in its own context to drive its ONE275 scoped task to a result (observe→analyze→act until the metric gate is met).276- It is also what terry itself runs in the **OBSERVE → INTEGRATE** part of each277 heartbeat tick — poll live state via structured channels, verify the evidence278 line before claiming a merge.279- Terry's `references/lessons-and-antipatterns.md` and this skill's share DNA280 (determinism-not-flaky, verify-before-claim, parallel-hypotheses-when-stuck):281 terry frames them at the fleet level; Andrej frames them at the single-282 iteration level. Use Andrej to decide *what one change to make next*;283 use terry to run *many such loops in parallel and integrate the winners*.