docs(examples): make self-improvement demo genuinely self-evolving (#29)
This commit is contained in:
1 parent
6d25adfef4
commit
81d8c0045c
8 files changed
+728
-102
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user