Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Evolve → run round-trip

What

Loop: episodes → evolve → archive → seed run → write-back fitness.

Why

It’s not enough for castellan evolve to find a better genome once — the round-trip closes so a production run can seed from that genome, and its outcome feeds the archive for the next evolve pass. See Topology evolution and Organism model.

Overview

Castellan closes the loop between topology evolution and production runs in three steps:

  1. castellan evolve — mutates harness topology from the episode corpus; writes .castellan/topology_archive.json.
  2. castellan run --seed-archive — seeds topology/coordination from the nearest archive entry (matched by plugin + goal environment_fingerprint); logs a genome_id and a genome_seed event in the episode.
  3. Write-back — after a successful run, merges the new episode’s fitness into the matching archive entry (matched by genome_id), then re-sorts the archive by success score.

Write-back is enabled automatically whenever --seed-archive is used, or explicitly with --write-back. Pass --no-write-back to disable the automatic path (useful when you want to seed from a known-good genome without letting a noisy or exploratory run pollute its fitness history).

Anatomy: what’s in an archive entry

Each TopologyEntry in .castellan/topology_archive.json carries:

FieldMeaning
idArchive entry UUID
coordinationHarnessCoordination — the topology + wake/decay parameters this entry expresses
fitnessFitnessVector — plugin-grounded scores, updated by write-back
generationWhich evolve generation produced this entry
environment_fingerprintPlugin + goal signature used by nearest() to match seed candidates
genome_id / parent_genome_idLineage — see Organism model
quarantinedExcludes this entry from nearest() regardless of fitness — see Organism model
decay_lambda, zone_decay, zone_weightsPer-zone tuning this genome expresses
flowchart LR
    EP[episode corpus] --> EV[castellan evolve]
    EV --> ARCH[topology_archive.json]
    ARCH --> SEED[castellan run --seed-archive]
    SEED --> RUN[episode: genome_seed event]
    RUN -->|Satisfied + fitness| WB[write_back_fitness]
    WB --> ARCH

    classDef evolve stroke:#d97706,stroke-width:2px
    classDef runtime stroke:#0969da,stroke-width:2px

    class EP,EV,ARCH,WB evolve
    class SEED,RUN runtime

How operators use it

1. Quick reproduce from a fixture corpus:

EVOLVE_FIX="tests/fixtures/evolve-episodes"
mkdir -p .castellan/episodes
cp "$EVOLVE_FIX"/*.json .castellan/episodes/

castellan evolve --episodes 10 --workspace .castellan/episodes
castellan run --goal "reach target" --plugin gridworld --seed-archive --json

Expect in the resulting episode JSON:

  • genome_id — seeded from the nearest archive entry
  • fitness — plugin-grounded success score for this run
  • events[] containing a genome_seed event, and — when write-back applies — an archive_write_back event

2. Seed from a specific genome instead of “nearest”:

castellan run --goal "reach target" --plugin gridworld --genome <genome_id>

--genome and --seed-archive are mutually exclusive — pick one.

3. Seed without letting the run mutate the archive (e.g. reproducing a past result exactly):

castellan run --goal "reach target" --plugin gridworld --genome <genome_id> --no-write-back

4. Point at a non-default archive path (e.g. testing a candidate archive before promoting it):

castellan run --goal "reach target" --plugin gridworld --seed-archive --archive .castellan/topology_archive.candidate.json

Write-back semantics

  • Trigger: GoalStatus::Satisfied and both genome_id and fitness present on the episode — a failed or inconclusive run never writes back.
  • Merge: TopologyArchive::write_back_fitness updates the matching entry by id, then re-sorts the archive by success score so future nearest() calls prefer the freshest evidence.
  • Archive path: controlled by --archive (default .castellan/topology_archive.json).

JSON events

With --json, archive write-back emits a typed archive_write_back CastellanEvent line, immediately before episode_end. Automation watching the NDJSON stream can key off this event to know the archive changed without re-reading the file.

Recipes

When proving the round-trip works end to end (e.g. for a demo or CI check), use the fixture corpus reproduction above — it’s deterministic and doesn’t depend on a live LLM or long-running episodes.

When you want continuous improvement in a daily-driver loop, always pass --seed-archive on production runs (write-back is then automatic) so every successful run compounds into the next evolve generation.

When comparing “does seeding actually help,” run the same goal once with --seed-archive and once without, then diff fitness in the two episode JSONs — the delta is the seeding effect for that goal/plugin pair.

When you need a stable baseline for regression testing, seed with an explicit --genome <id> and --no-write-back so the archive never drifts between test runs.

Failure paths / troubleshooting

SymptomLikely causeFix
--seed-archive seeds nothing (no genome_seed event)No archive entry matches the plugin+goal environment_fingerprint, or all matching entries are quarantinedRun castellan evolve first to populate the archive; check castellan genome list for non-quarantined entries
error: specify only one of --genome or --seed-archiveBoth flags passed togetherPick one — explicit genome or nearest-match auto-seed
No archive_write_back event even though the run succeededEpisode’s fitness or genome_id missing, or --no-write-back was setConfirm the plugin’s verify_goal sets GoalStatus::Satisfied; drop --no-write-back if present
Write-back seems to “lose” a good fitness scoreA later, worse run wrote back to the same genome_id and got re-sorted lowerUse --no-write-back when reproducing/benchmarking a known-good genome so exploratory runs can’t overwrite it
--archive <path> run can’t find the filePath doesn’t exist yet — archive is created by castellan evolve, not by runRun castellan evolve --workspace .castellan/episodes once to create the archive file first