AI Initiative logoAI Initiative
Reading room / Manthan

Manthan × swarm_evolution — review + progressive integration

Records successive multi-model review rounds, source-verified findings, selection/fitness integration, rejected suggestions and the unresolved experiment needed to test outcome value.

Project explanation / technical method
This is the retained source text, not a new validation of its claims. Numerical results and comparative claims require the accompanying source review before public reuse.

Read the connected case study or article →

Not explicitly stated in source · Date not established

Provenance, authorship and editorial notes

Manthan × swarm_evolution — review + progressive integration

Id

manthan-docs-swarm-evolution-integration

Project

Manthan

Source path

manthan/docs/SWARM-EVOLUTION-INTEGRATION.md

Source sha256

aec810955cbc6ff6db0531211e41e4349b409aac9690aee55c5eaea19c3352c6

Local copy sha256

aec810955cbc6ff6db0531211e41e4349b409aac9690aee55c5eaea19c3352c6

Repo head

3a94084024a3434cef331fecac91c2c3b43f0313

Date
Not recorded
Date basis

Original publication date not established; Git revision date retained separately.

Author

Not explicitly stated in source

Author basis

Unknown; Git committer not substituted for document author.

Last source commit

65f052891f3b5057fbdecdfa5f4252873b0e8fc6

Last source commit date

2026-06-05T23:37:39+01:00

Document status

Project explanation / technical method

Summary

Records successive multi-model review rounds, source-verified findings, selection/fitness integration, rejected suggestions and the unresolved experiment needed to test outcome value.

Topics
  • Exploration architecture and selection
  • Round 1 — make the substrate alive + open the selection feedback loop
  • Swarm findings (ground-truthed)
  • The biggest defect the panel *under-weighted* (caught on ground-truth)
Theme

Exploration architecture and selection

Priority

core

Related ids
  • manthan-readme
  • manthan-docs-architecture
  • manthan-docs-business-overview
  • manthan-docs-demo-walkthrough
  • manthan-docs-principles
  • manthan-docs-qd-measurement-direction-plan
Evidence concerns
  • Recovered source text is preserved verbatim. Assertions, comparisons, commands and results belong to its original context; they were not re-executed or externally re-verified during this corpus review.
Word count

3952

Bytes

27847

Source relative

docs/SWARM-EVOLUTION-INTEGRATION.md

Remote mirror

manthan/docs/SWARM-EVOLUTION-INTEGRATION.md

Full text preserved

True

Manthan × swarm_evolution — review + progressive integration

Goal (operator): *review each feature of Manthan, run the cross-model swarm to
improve it, apply changes progressively, post every iteration. 2 rounds.*

The "swarm" is the conduir cross-model review panel (infra/scripts/
multi-agent-review.sh
in conduir-evolution-engine): the same packet is fanned
out to independent model families (gemini / deepseek / copilot) each with a
distinct lens; findings are then ground-truthed against the live repo before
anything is acted on. Panel output is ~50%+ false-positive by reputation, so
every finding below was re-verified against Manthan's actual code; verdicts that
the panel got wrong are recorded as such.

The thesis under test: *Manthan is a generative substrate (the "churn") with no
selection; swarm_evolution is a selection/fitness/quality-diversity engine
pointed only at code. Wire the second into the first and Manthan can actually
surface the "amrita" instead of accumulating artefacts forever.
*


Round 1 — make the substrate alive + open the selection feedback loop

Swarm findings (ground-truthed)

#Finding (panel)Verdict vs live codeAction
F1seen_artefact_ids is an unbounded in-memory set, lost on restart (gemini CRIT, deepseek HIGH)TRUE. loop.agent_loop held set[str], grew forever, reset on restartR1: bounded SeenCache (LRU). Durable cross-restart cursor = documented follow-up
F2No selection/pruning; patina computed but never steers the loop (gemini HIGH)TRUE — but signal.py already ranks artefacts read-side; it just never feeds back into generationR1: selection.rank_candidates lets agents bias toward high read-side signal (default-off)
F3patina volume-dominated (log1p(authored) unbounded vs diversity/tenure capped at 1.0) (deepseek MED)TRUEDeferred to R2 fitness work (patina is a read-side score; superseded by the fitness vector)
F4patina counts every lineage relation equally — contradicts earns the same as refines; gameable (deepseek MED)TRUE for patina; signal.py does bucket relationsR2: relation-weighted engagement in the fitness vector
F5fetch_active_agents called once at startup; retired/dormant agents run forever (deepseek MED)TRUEDocumented follow-up (periodic re-fetch / NATS presence)
F6llm.generate writes empty-text artefacts when gateway returns no content (deepseek MED)TRUEDocumented follow-up (validate content + finish_reason)
F7extra_metadata vs metadata column mismatch drops the field (deepseek MED)FALSE POSITIVE — mapped_column("metadata", JSONB) aliases it correctlynone
F8prompt injection via persona_doc f-string (gemini HIGH)PARTLY — persona_doc is operator-seeded (trusted); the real vector is parent_artefact_text (any agent's output feeds another's prompt)Documented; design-inherent, mitigate in fitness/constitutional layer

The biggest defect the panel under-weighted (caught on ground-truth)

Embeddings are never authored. loop._post_artefact posted no
content_embedding; nothing else computed one. So:

results == [], the route comment says "vector search lands when the runtime
starts authoring embeddings");

signal to stand on**.

This is the keystone: nothing downstream in the evolution thesis works until the
vector substrate carries data. So R1 makes it the centerpiece.

Changes shipped (R1)

via the gateway /embeddings route. Best-effort, honest-failure: returns
None on a down/absent adapter, malformed response, or wrong dimension (a
wrong-dimension vector would silently poison every kNN query — worse than
none). Tolerates OpenAI / native / batched response shapes.

artefact (embedded: bool recorded in metadata). The pgvector substrate and
/ideas/search now carry data.

ε-exploration (default-off; pure recency when no signal map is supplied),
so generation can preferentially build on what the graph finds promising
without the high-signal artefact becoming a mode-collapse attractor (F2).

seen_cache_max, selection_signal_bias (default off), selection_epsilon.

guard, every failure path returns None, LRU eviction/refresh, explore-vs-
exploit ordering, recency-passthrough when signal absent.

All changes are additive and default to the existing behaviour (recency churn)
except embedding-authoring, so the recency baseline is preserved for the A/B
test below.

What the swarm changed about the plan (deepseek Part B — the load-bearing critique)

"swarm_evolution solves the machinery of selection (cycle state machine,
ledger, gates, MAP-Elites grid) but not the meaning of selection — what
makes an idea fit? Code fitness is objective and cheap; idea fitness is a
proxy and gameable. Any single scalar fitness will be gamed within days."

This is correct and reshapes Round 2:
1. No single scalar fitness. R2 fitness is a vector of proxies, explicitly
labelled as proxies.
2. MAP-Elites only after embeddings exist (R1), else its behaviour
descriptors are noise and the grid is "a colourful heatmap of garbage."
3. The thesis must be falsifiable, not theatre. Before deep integration, run
the A/B kill-test below.

Falsifiable kill-test (carried into R2, from deepseek)

Control = current recency churn (selection off). Treatment = signal/fitness
selection on. After N artefacts, archive the bottom X% from the served feed,
feature the top per cell. Human blind-scores 100 random artefacts/arm on "would
you read this voluntarily?" *Kill criterion: treatment not better than control
at p<0.05.
* If it fails, the thesis is dead — spend effort on persona prompts
and diversity scheduling instead of an evolutionary death march.

Open follow-ups (filed, not yet done)

manthan.bus.* subjects/presence/events are unused.


Round 2 — the quality-diversity selection brain

Round 2 builds on top of Round 1: novelty-based behaviour descriptors only
exist because R1 made embeddings live. The swarm was re-run (gemini + deepseek +
copilot, now under the kulbirminhas-aiinitiative account so it produced
output this round) to (a) adversarially review the R1 code I shipped and (b)
stress-test the R2 design before building it.

Changes shipped (R2)

a single public scalar: engagement (lineage children weighted by relation —
converge positive, contradicts mild, being replaces-d a penalty),
depth (converge-chain maturation), recency (14-day half-life). Carries an
explicit honesty block naming each component a proxy. The scalar is a
documented within-cell tie-break only.

(kind, novelty_band); novelty is the descriptor, not the fitness, so
"gibberish maximises distance" buys a far cell but never wins it. Bounded
long-tail protection (a young incumbent is shielded only within
grace_days AND a sub-evict_margin lead) protects the slow-maturing
"amrita" without freezing the archive. elites_from_lineage computes the
whole thing from the lineage graph; should_archive is the A/B-test primitive.

instead of naive recency.

(+ R2 folds below). 57 tests green across runtime + api.

Swarm findings folded (ground-truthed; convergent = ≥2 families agreed)

#FindingFamiliesVerdictAction
R2-1_extract_vector runs float() outside the try → a non-numeric vector crashes the tick; NaN/Inf passes the length guard and poisons kNNcopilot+deepseek+geminiTRUE (violated my own "bad vector worse than none")_to_finite_floats: total + finite-only; error-envelope guard. 7 tests
R2-2rank_candidates checks seen with raw id but looks up signal with str(id) → UUID/None mismatch + "None" collisiondeepseek+copilot+gemini (3/3)TRUEnormalise id once; drop id-less candidates. 2 tests
R2-3engagement/depth gamed by self-citation / Sybil chainscopilot+gemini+deepseek (3/3)TRUEauthor-diversity damping on positive engagement (self-ring keeps ≤20%); penalty undamped. 2 tests
R2-4SeenCache is FIFO but docstring says "LRU"deepseekTRUE (doc bug)corrected to bounded-FIFO + single-loop-async note
R2-5long-tail protection = unbounded "death spiral" / squattingdeepseek+copilot+gemini (3/3, named the "cheapest kill")ALREADY MITIGATEDimplemented EliteArchive bounds protection by grace_days and it expires (proven by test_protection_expires_for_aged_incumbent) — exactly their recommended "hard deadline"
R2-6scalarising the vector contradicts "keep it a vector"3/3ACCEPTED TRADE-OFFvector kept separate + inspectable; scalar is a documented within-cell tie-break with explicit tunable weights — the path all three list as acceptable
R2-7(kind, novelty-band) cells too coarse/sparse; fixed n_bands3/3TRUE, deferredfollow-up: adaptive quantile band edges + min cell occupancy
R2-8EMBEDDING_DIM hardcoded → ABI driftcopilotTRUE, deferredfollow-up: source dim from substrate contract + startup probe; current dim-guard already fails safe (returns None)
—gemini "tenant hardcoded in embed()"geminiFALSE — the loop passes a per-persona tenant; the module default is only a fallbacknone

Residual risks recorded honestly (not solved by reuse)

quality/dedup gate before novelty counts is the real fix (deferred).

LLM-circularity failure mode. That caps how "smart" fitness can be; the
falsifiable A/B kill-test (control = recency, treatment = /elites, human
blind-score, kill if not better at p<0.05) is the gate before trusting it.

Net

Two progressive rounds: R1 made the substrate carry data; R2 turned that data
into a diverse-and-good selection surface that defuses the panel's three
top-rated attacks (gibberish-novelty, self-citation, archive-squatting) by
construction, with the rest recorded as honest follow-ups rather than hidden.
The swarm didn't just find bugs — deepseek's "machinery vs meaning" critique and
the 3/3 gaming findings shaped the design, which is the whole point of
running it.


Round 3 — sweep the un-reviewed components + close the end-to-end gaps

R1/R2 covered the runtime churn + the api selection brain. R3 ran the full panel
(gemini + deepseek + copilot) over the substrate data layer and the
runtime lifecycle — the components not yet reviewed — and closed the gaps
that stopped the R1/R2 work from functioning end-to-end.

Swarm findings (ground-truthed; all the top ones were 3/3 convergent)

#FindingFamiliesAction
R3-1embeddings authored but not exposed or searchable — ArtefactRead/lineage omit them and there is no vector-search route, so /elites novelty + /ideas/search stay darkdeepseek+gemini+copilot (3/3)/lineage/graph?include_embeddings=true + has_embedding; new POST /artefacts/search (pgvector <=> kNN, excludes NULL-embedding rows); /elites now requests embeddings → novelty cells light up
R3-2run_all fetches active agents once — retired agents run forever, new ones never start3/3supervisor run_all + pure reconcile_agents; re-fetch every agent_refetch_seconds
R3-3empty/whitespace LLM output is written as an artefact (others then react to it)3/3is_postable_text guard — mark parent seen, skip the tick, write nothing
R3-4rank_candidates (signal-biased selection) built in R2 but never wired into the loop3/3wired behind selection_signal_bias (default-off) with best-effort /signal/feed fetch → clean recency fallback
R3-5embedding <=> dimension can be wrongcopilot+deepseek/artefacts/search rejects non-EMBEDDING_DIM queries (422)

Deliberately NOT done (panel agreed — gold-plating / unproven)

intentional edge case).

follow-up, not load-bearing.

All three families explicitly confirmed *none of these fixes regress the
honest-failure / no-fabrication posture
— the empty-content guard strengthens*
it.

Tests

65 green across runtime + api (pure logic: reconcile, content-guard, signal-map
parse, novelty-cells-light-up-with-embeddings). The two substrate routes are
exercised by import + pgvector-comparator resolution; full e2e needs Postgres
(same posture as the pre-existing ideas_search skeleton test).

Status after R3

The selection brain is now wired end-to-end in principle: embeddings are
authored (R1), exposed + searchable (R3), turned into a fitness vector + QD
archive (R2), and the generative loop can consume read-side signal (R3,
default-off). What remains is runtime validation (the falsifiable A/B
kill-test on a live stack) and the smaller deferred items above — not new
architecture.


Round 4 — read-side scoring hardening + convergence

R4 ran the full panel over the last un-reviewed product-layer components:
signal.py (signal-from-noise, now also feeding parent-selection), patina.py
(reputation), and maestro-bridge/translate.py.

This was the convergence round. gemini reported no MEDIUM+ findings
("architectural design … appears sound"); deepseek surfaced nothing actionable;
copilot found a small set of real edge-cases. Folded (ground-truthed):

#FindingVerdictAction
R4-1signal KeyError on a malformed edge blanks /signal/feedTRUE (honest-failure gap)skip malformed edges
R4-2patina naive ISO timestamp → "can't subtract naive & aware" crashTRUE (real crash)normalise parsed datetimes to UTC-aware
R4-3maestro inbound text can be a dict/list → violates substrate content_text: strTRUE (HIGH)coerce body to string
R4-4maestro outbound bare KeyError on missing id/content_textTRUEdefensive read + clear ValueError
R4-5signal replaces direction likely invertedTRUE but dormant (no producer emits replaces)flagged in-code, not flipped — confirm against a real producer first
R2-carriedsignal category masking (superseded hides contested)TRUEmulti-label categories (primary category kept)
R2-carriedsignal _weak_components unions contradictionsTRUEunion on converge relations only ("isolated" = not in a lineage)
R2-carriedsignal max_patina==0 "phantom boost"FALSE — all-zero gives a uniform 0.5 floor; ranking-neutralrejected, with a test pinning the behaviour

19 new tests (signal, patina, maestro-bridge). 79 green across runtime + api

Convergence reached

Across four rounds the panel went from many CRITICAL/HIGH findings (R1/R2/R3) to
near-zero in R4 (one family found nothing at all). The remaining backlog is
either explicitly panel-agreed gold-plating (PATCH backfill, per-tick DB
dedup, children_count distinctness, adaptive novelty bands, Pareto-per-cell) or
requires a live Postgres + gateway stack (the falsifiable A/B kill-test,
substrate-route e2e) — i.e. runtime validation, not further code architecture.

Stopping here is the honest call (for the churn/selection components):
another swarm round on them would manufacture make-work. The integration is
architecturally complete and tested; the next real step is operational (stand up
the stack, run the A/B kill-test), not another review pass.


Round 5 — the infra / entrypoint components (db, llm, budget, the bridge)

R4 saturated the product-logic components, but the *infrastructure /
entrypoint
layer had never been swept. R5 ran the panel over substrate/db.py,
runtime/llm.py, runtime/budget.py, and maestro-bridge/main.py (NATS
subscriber + promotion poller). This round found
real* issues, so saturation was
not yet global.

Folded (ground-truthed; gemini + deepseek convergent):

#FindingVerdictAction
R5-1maestro/main.py _promoted_ids is an unbounded set (slow memory leak; /stats exposed its growth)TRUE (CRITICAL)BoundedIdSet (FIFO, in translate.py, tested)
R5-2duplicated _patina formula had already drifted — missing R4's UTC-aware fix → same naive-timestamp crashTRUEfixed + strengthened the extract-to-shared note
R5-3inbound_loop lacked the top-level handler promotion_loop has → one bad message kills the subscriberTRUEadded symmetric try/except
R5-4_read_api_get dead NotImplementedError stubTRUEremoved
R5-5llm.generate ignores finish_reason; int(usage…) can crash the tickTRUEcapture finish_reason (+ .truncated); _safe_int
R5-6db.py no pool_recycle → stale long-idle connectionsTRUE (minor)pool_recycle=1800
—budget.py "data race"FALSE — single-loop asyncio, no await in record() (deepseek self-rejected)none
—httpx.AsyncClient per callgold-plating (perf nitpick)none
—budget/_promoted_ids lost on restartdocumented roadmap (v0.6→v0.7 persistence), not a defectnone

19 cases added; 85 tests green across all four services.

Saturation reached (global)

Five rounds, descending yield: R1–R3 many CRITICAL/HIGH → R4 near-zero on product
logic → R5 a final real batch on infra, now also drained. What remains is
exclusively documented roadmap (persistent budget / promoted-ids, extract the
shared patina package, NATS durable subscriptions) or *panel-agreed
gold-plating
*. No component has an un-actioned real defect. The honest call is
that the codebase is saturated against this review process — the next
increments are operational (run the stack + the A/B kill-test) and product-roadmap
decisions, not more review passes.

(Update: a later directive reopened the loop in exploratory mode — see R6+.)


Round 6 — config / bootstrap / entrypoints, Six-Hats (critical + exploratory)

The defect-axis saturated at R5, but the config/bootstrap/entrypoint layer
had never been reviewed, and a new directive asked for a critical AND
exploratory
pass (de Bono's Six Hats). The panel (gemini + deepseek + copilot)
applied white/black/yellow/green/red/blue hats over shared/settings.py,
shared/logging.py, substrate/scripts/seed.py, and the FastAPI entrypoints.

Folded (ground-truthed; bounded, testable unit-improvements):

#Hat(s)FindingAction
R6-1BLACK/RED (3/3)postgres_password ships an insecure dev defaultmanthan_env + model_validator refuses the default outside dev/local/test/ci
R6-2BLACK/BLUEraw DSN breaks on @:/# in credentialsURL-encode user+password in postgres_dsn/_sync
R6-3BLACK/WHITElog_format typo silently degradesLiteral["json","console"] — fail fast on a bad value
R6-4WHITE/GREEN (mine)manthan_default_embedding_model was dead config; embed_model="" let the gateway pick a maybe-wrong-dim model → dim-guard silently disables embeddingsembed now defaults to the shared model (matches EMBEDDING_DIM)
R6-5BLACK/BLUEruntime loop crash died silently; /healthz hardcoded the name; shutdown suppressed all Exceptionloop-crash done_callback; service_name; suppress only CancelledError (same fix in maestro)
R6-6BLACK/admin/pause-all unauthenticatedoptional admin_api_token gate (default off = unchanged dev)
R6-7WHITE/BLACKsubstrate had no readiness check/readyz with a real SELECT 1 DB ping
R6-8WHITEseed skipped on any artefact; stale persona never updatedseed-provenance-scoped skip + persona upsert

Rejected with reason: env_prefix="MANTHAN_" and extra="forbid" (both break
the existing .env contract), trace_id middleware (gemini cross-referenced the
engine repo's GEMINI.md, not manthan's), llm_model pinning (documented design
choice). 23 cases added; 94 tests green (runtime 46 / api 40 / substrate 1 /
maestro-bridge 7).

Takeaway: the exploratory axis is not yet saturated even where the defect
axis was — Six-Hats surfaced real, bounded unit-improvements (secret-handling,
dead-config wiring, readiness, admin-safety) that pure defect-hunting missed.
Remaining un-swept: the Next.js viewer, schema/migration, and infra
(Dockerfiles/compose) — next rounds.


Round 7 — schema + the Next.js viewer (Six-Hats)

The last two un-reviewed components: the DB schema (models + migration) and
the viewer frontend. The panel independently re-derived the schema
constraints (copilot/gemini/deepseek all flagged self-loop + non-blank — which I
had implemented from my own ground-truth, a nice cross-check) and surfaced real
frontend defects.

Schema (CI-verified where possible):

#FindingAction
R7-1empty/whitespace artefact can be persisted by any writercontent_text Field(min_length=1, max_length=100_000) + non-blank validator (422, tested) + DB CHECK length(btrim)>0 (migration 0002)
R7-2lineage allows parent_id == child_id (self-loop corrupts graph walks)DB CHECK parent_id <> child_id (migration 0002)

Viewer (TypeScript — no frontend test harness yet, so review-verified):

#Hat(s)FindingAction
R7-3RED/BLACKuseFetch setInterval → overlapping requests + stale late responses, no abortself-scheduling setTimeout-after-settle + AbortController + cleanup
R7-4BLACKcategoryPill destructures an unguarded map → crashes the Signal feed on any new category?? ['#222','#888'] fallback (mirrors the existing kindColor guard)
R7-5GREEN/BLACKrelativeTime renders NaNd ago for bad timestampsNumber.isFinite guard → unknown time
R7-6BLACK/BLUEclickable rows are mouse-onlyrole="button" + tabIndex + Enter/Space handlers
R7-7REDfooter claimed "every 5–10s" but intervals are 5/10/15scorrected to "periodically (5–15s)"

Rejected/deferred: inline-styles→CSS-modules (gemini: "not critical" for a demo),
explicit PrimaryKeyConstraint (SQLAlchemy handles it — stylistic), touching the
already-applied migration 0001 (DROP TYPE … CASCADE, tags default — uncertain

no XSS (dangerouslySetInnerHTML absent).

Global saturation — restated

Every component now reviewed: backend logic (R1–R5), config/bootstrap/entrypoints
(R6), schema + frontend (R7). Across seven rounds the yield fell to bounded
edge-cases. What remains is infra polish (compose resource limits) and
documented roadmap (persistence, shared-patina extraction, a frontend test
harness) — not unit-improvements to existing logic. The codebase is saturated
on both the defect and exploratory axes against this review process. Note: the
viewer + maestro-bridge are not in the merge-blocking CI matrix (python =
substrate/runtime/api only), so viewer changes are review-verified, not CI-gated.


Round 8 — infra / containers / compose (FINAL, Six-Hats)

The last component category: service Dockerfiles, docker-compose.yml, Makefile.
Panel (deepseek + copilot; gemini empty this run). Folded the bounded,
safe-to-apply-without-a-build items:

#Hat(s)FindingAction
R8-1BLACK/BLUE (HIGH)all 5 service images ran as rootnon-root appuser (uid 10001) + chown + USER in all 4 Python Dockerfiles
R8-2BLACK/REDapp services had restart but no healthcheck → a wedged uvicorn stays "running"/healthz healthcheck on the x-python-base anchor + dependents now wait service_healthy
R8-3BLACK/BLUEno CPU/memory caps on always-on servicesmem_limit: 512m + cpus: 1.0 on the python anchor
R8-4BLACK/BLUEPostgres/NATS ports bound on all interfacesloopback-bind local-service: (local-service tooling still works; not world-exposed)
R8-5BLUE/BLACKmake nuke deleted all volumes with no guardrequires CONFIRM=destroy (tested)

Deferred (needs a build/lockfile, can't verify here): multi-stage slim images +
purge build tools (R8 image-bloat), install from a generated lock/constraints
file (dep-drift), digest-pin base images. Left as-is with reason: compose DB
password default (the R6 manthan_env app-guard already refuses it in prod;
requiring it would break dev make up).


FINAL STATUS — saturation across every component

Eight rounds, every component category reviewed with both a critical and (R6+)
an exploratory Six-Hats lens, every finding ground-truthed (false positives
rejected with reasons each round):

substrate routes, signal/patina, maestro bridge, llm/budget/db.

The yield per new area followed the same curve: a real batch on first review,
then drained to bounded edge-cases. Everything still open is now one of:
documented roadmap (persistent budget/promoted-ids, extract the shared patina
package, NATS durable subscriptions, a frontend test harness, a dep-lock file) or
ops choices that can't be unit-verified in this partial-push repo (multi-stage
images, digest pinning). No component carries an un-actioned, in-scope,
unit-improvable defect.

The codebase is saturated against this review process. The next increments
are operational (stand up the stack; run the falsifiable A/B kill-test that
decides whether selection-over-fitness beats recency) and product-roadmap
decisions — not further review passes.

Continue exploring