docs(examples): make self-improvement demo genuinely self-evolving (#29)

This commit is contained in:
Zhang Jason authored and GitHub committed 2026-07-22 22:04:52 +08:00
1 parent 6d25adfef4
commit 81d8c0045c
8 files changed
+728 -102

No files matched your search

+90 -37
View File
@@ -3,36 +3,60 @@
# Example: an Agent that improves itself (local, on an AMD GPU via Ollama)
This example is the **"Recursive Self-Improvement"** pillar in runnable code. Using only the
PenguinHarness SDK, it runs one turn of the self-improvement loop:
PenguinHarness SDK, the agent runs the self-improvement loop on **itself** — and the key point is
that the improvement is authored by the *agent*, not hardcoded by the script:
1. **Evaluate** a constrained writing task and score it against a rubric.
2. **Diagnose** which rubric points were lost (from the run itself).
3. **Edit** the agent's own `AGENTS.md` to address the failure (version N+1).
4. **Re-evaluate** the same task and keep the change only if the score improved.
1. **Evaluate** — the agent attempts a constrained task and is scored against a rubric (plain code).
2. **Diagnose** — *the agent itself* reads a failing result next to a passing example.
3. **Edit** — *the agent itself* infers the missing rule and writes it into its own `AGENTS.md`.
4. **Re-evaluate** — the same task again; the change is kept only if the mean score improved.
Everything runs on a **local open-weight model** — `qwen3:8b` served by Ollama — so no cloud API
Everything runs on a **local open-weight model** — `qwen3.6:35b` served by Ollama — so no cloud API
and no data leaving the machine. Ollama's ROCm backend runs this natively on AMD GPUs.
## Why a deterministic rubric, and why averaging
## The three scripts (from mechanism to real self-evolution)
- The **rubric is plain code you can read** (`score()` in `self-improve.ts`): file actually
written · overview ≤ 2 sentences · exactly 3 bullets · under 60 words · key facts present. No
hidden judge — the before/after numbers are objective and reproducible. In the full product the
Evaluator is driven by the `agent-evaluation` skill against a *private* rubric; this example
distills that idea to its runnable core.
- A local model is **nondeterministic**, so the example runs each version several times and
averages — which is exactly why real benchmarks use a `runs` count per case. A single run can
swing; the mean is what tells you whether the edit actually helped.
| Script | What it demonstrates | `AGENTS.md` edited by |
| --- | --- | --- |
| `self-improve.ts` | The **scoring loop** in miniature (simplest) | the **script** (hardcoded) |
| `self-evolve.ts` | **Genuine** single-round self-evolution | the **agent** itself |
| `self-evolve-recursive.ts` | **Multi-round recursion** — the main line (`pnpm start`) | the **agent**, over two rounds |
`self-improve.ts` is the honest baseline: it shows the evaluate → edit → re-evaluate machinery, but
the edit is a human-written `DISCIPLINE` string the script writes to disk. That demonstrates the
*loop*, not self-evolution. The two `self-evolve*` scripts move the diagnosis **and** the edit into
the agent — the script only supplies a failure signal and one worked example; the model does the
learning and persists it into its own identity file.
## The task, and why the baseline stably fails
The task looks trivial: *summarize `notes.txt` into `summary.md` with a 2-sentence overview and
exactly 3 key facts — and follow your team's standard report format.* The catch is the last clause:
the "team format" is an **arbitrary house convention** (a marker line, a `# Report: <subject>`
title, a `Classification: INTERNAL` line, a `Reviewed-by: Aurora Team` footer) that appears **only
in `AGENTS.md`** and **cannot be inferred from the task**.
This is the design trick that makes the baseline *stably* bad — for any model, strong or weak. The
rubric (`score()`, plain code) has 10 atomic points: **5 content points** any capable model earns
from the task alone, and **5 convention points** that are knowable only from `AGENTS.md`. With a
blank `AGENTS.md` the agent cannot guess the convention, so it stably loses those 5 points. This is
an **information gap, not a capability gap** — the reason a stronger model can't just "figure it
out". It also mirrors the full product, where the Evaluator is driven by the `agent-evaluation`
skill against a *private* rubric.
The agent closes the gap by **learning the convention from a passing example** and writing it into
its own `AGENTS.md` — and in the recursive script, by locking down the fixed constants once it has
seen several examples.
## 1–2. Serve the model and point PenguinHarness at it
```bash
export HIP_VISIBLE_DEVICES=0 # optional: pin a specific AMD GPU
ollama serve &
ollama pull qwen3:8b
ollama pull qwen3.6:35b
penguin config model add \
--model-id qwen3:8b \
--model-id qwen3.6:35b \
--provider custom --client-type openai \
--base-url http://localhost:11434/v1 \
--api-key ollama --set-default
@@ -43,35 +67,64 @@ penguin config model add \
```bash
pnpm install
pnpm build
pnpm --dir examples/self-improving-agent start
# or directly: npx tsx examples/self-improving-agent/self-improve.ts
pnpm --dir examples/self-improving-agent start # the recursive main line
# or a single round: npx tsx examples/self-improving-agent/self-evolve.ts
# or the scoring-loop baseline: npx tsx examples/self-improving-agent/self-improve.ts
```
## What you should see
```text
BASELINE (blank AGENTS.md): 3 runs
run 1: 0/5
run 2: 0/5
run 3: 0/5
BASELINE mean: 0.00/5
N+1 (with working discipline): 3 runs
run 1: 5/5
run 2: 5/5
run 3: 5/5
N+1 mean: 5.00/5
=== Self-improvement result ===
baseline: 0.00/5 → N+1: 5.00/5
Mean score improved — keep version N+1. ✔
N BASELINE (blank AGENTS.md): 5 runs
... scores: [5, 4, 5, 5, 5] mean: 4.80/10 # loses every convention point
=== REFLECT round 1: infer STRUCTURE from a single accepted report ===
----- after round 1: AGENTS.md the agent authored -----
## Report-Publishing Convention ...
<!-- <UPPERCASE_PROJECT_ID> --> # structure learned, constants still guessed
Reviewed-by: <Team Name>
N+1 (structure learned): 5 runs
... scores: [6, 7, 6, 6, 8] mean: 6.60/10
=== REFLECT round 2: RECURSE on own AGENTS.md — lock CONSTANTS from 3 examples ===
----- after round 2: AGENTS.md the agent authored -----
### Fixed Constants (verbatim)
| 1 | <!-- ACME-DATA-PLATFORM --> | # constants now locked to literals
| Last line | Reviewed-by: Aurora Team |
N+2 (constants locked): 5 runs
... scores: [10, 10, 9, 10, 10] mean: 9.80/10
=== Recursive self-evolution trajectory ===
N (baseline): 4.80/10
N+1 (structure): 6.60/10 (+1.80)
N+2 (constants): 9.80/10 (+3.20)
Monotonic improvement across two self-authored rounds — recursive self-evolution. ✔
```
With a blank `AGENTS.md`, `qwen3:8b` tends to *narrate* the summary in chat and never call the
tool to write the file — so the rubric scores it 0. Adding a short "working discipline" section
(read first, restate the constraints, actually write the file, self-check) flips that. Exact
numbers vary run to run; the averaged direction is the point.
The trajectory is the point: from one passing example the agent infers the **structure** but can't
tell which tokens are fixed constants vs per-report fields (a single example is ambiguous), so it
stalls mid-way. Given **several** accepted reports that share the same marker and sign-off, it
infers "whatever is identical across all of them is a fixed constant", reads its **own** round-1
`AGENTS.md`, and refines it — recursion in the true sense: `state_{n+1} = agent.reflect(state_n,
new_evidence)`. Exact numbers vary run to run; the monotonic direction is the point.
## Honest boundaries
- **Self-evolution needs a capable model.** Authoring a correct rule and then *following it* is
harder than benefiting from a hardcoded one. A weaker model (e.g. `qwen3:8b`) often infers the
rule but executes it unreliably (placeholders left unfilled, or narrating instead of writing the
file), so its N+1 can *regress* and the script rolls back. That rollback is the loop being honest,
not a bug.
- **Evidence sufficiency is real.** N+1 stalls at a middling score because one example genuinely
under-determines the fixed constants; N+2 only climbs because more examples make them inferable.
The gain is information-driven learning, not noise.
## Notes
- Uses a dedicated agent id (`self-improve-demo`), created on the fly — your own agents are
untouched.
- Re-running updates that demo agent in place.
- The script never writes the convention itself; it only supplies the failing report, the accepted
example(s), and the keep/roll-back signal.
- Re-running resets the demo agent's `AGENTS.md` to blank and starts the loop over.