Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions kalshi_bot/dashboard/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from ..evo.fitness import group_by_fractions
from ..evo.models import (
EvoAgent,
EvoBirth,
EvoCohort,
EvoCohortMember,
EvoConfigVersion,
Expand Down Expand Up @@ -295,6 +296,69 @@ def _classify(agent, *, live: bool, has_strategy: bool, open_positions: int, ope
return STATUS_IDLE


def _lineage(session, agent_uuids: list[str]) -> dict[str, dict]:
"""Where each bot came from, in words.

The roster showed `Name · Surname · G1 · CODE` and nothing else, so a founder
running for weeks, a survivor carried over, a clone of last generation's
winner, and a bot born minutes ago all looked identical. `origin` alone is
not enough either: `grow_to_target` creates its agents as origin='wildcard'
too, so the scheduled diversity slot and an operator-driven fleet expansion
are indistinguishable without the birth slot_key (`wildcard:N` vs
`growth:<cohort>:N`)."""
if not agent_uuids:
return {}
agents = {
a.agent_uuid: a
for a in session.scalars(
select(EvoAgent).where(EvoAgent.agent_uuid.in_(agent_uuids))
)
}
parent_uuids = [a.parent_uuid for a in agents.values() if a.parent_uuid]
parents = {
p.agent_uuid: p
for p in session.scalars(
select(EvoAgent).where(EvoAgent.agent_uuid.in_(parent_uuids))
)
} if parent_uuids else {}
slots = {
b.child_uuid: (b.slot_key or "")
for b in session.scalars(
select(EvoBirth).where(EvoBirth.child_uuid.in_(agent_uuids))
)
}
newest_cohort_id = session.scalar(select(func.max(EvoCohort.id)))

out: dict[str, dict] = {}
for au, a in agents.items():
slot = slots.get(au, "")
parent = parents.get(a.parent_uuid or "")
parent_name = parent.display_name.split(" · ")[0] if parent else None
if a.origin == "child":
kind = "child"
label = (f"child of {parent_name}" if parent_name else "child")
if parent is not None:
label += f" · inherits the {parent.surname} line"
elif slot.startswith("growth:"):
kind = "growth"
label = "new — added to grow the fleet"
elif a.origin == "wildcard":
kind = "wildcard"
label = "wildcard — fresh line, no parent"
else:
kind = "founder"
label = "founder — original fleet"
out[au] = {
"kind": kind,
"label": label,
"parent_name": parent_name,
"parent_code": parent.agent_code if parent else None,
"born_cohort_id": a.birth_cohort_id,
"is_new": a.birth_cohort_id == newest_cohort_id,
}
return out


def _bot_rows(session, settings: EvoSettings, cohort, *, now: datetime) -> list[dict]:
"""One fully-costed row per bot in the generation. Batched: a fixed number of
queries regardless of fleet size."""
Expand All @@ -315,6 +379,7 @@ def _bot_rows(session, settings: EvoSettings, cohort, *, now: datetime) -> list[
fitness = _latest_interim_fitness(session, cohort.id) if cohort else {}
groups = _projected_groups(fitness, settings)
previous = _previous_results(session, uuids, cohort.id if cohort else -1)
lineage = _lineage(session, uuids)

rows: list[dict] = []
for agent in agents:
Expand All @@ -337,6 +402,10 @@ def _bot_rows(session, settings: EvoSettings, cohort, *, now: datetime) -> list[
"family": agent.surname,
"generation": agent.generation,
"origin": agent.origin,
"lineage": lineage.get(uuid, {"kind": agent.origin, "label": agent.origin,
"parent_name": None, "parent_code": None,
"born_cohort_id": agent.birth_cohort_id,
"is_new": False}),
"status": _classify(
agent, live=uuid in live, has_strategy=strategy is not None,
open_positions=p["trades_open"], open_experiments=experiments.get(uuid, 0),
Expand Down
4 changes: 3 additions & 1 deletion kalshi_bot/dashboard/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,9 @@ <h1 id="d-name">—</h1>
return `<tr class="bot" tabindex="0" role="button" data-uuid="${esc(b.uuid)}"
aria-selected="${STATE.selected === b.uuid}"
aria-label="Open detail for ${esc(b.name)}">
<td><b>${esc(b.name)}</b><span class="sub">${esc(b.family)} · G${esc(b.generation)} · ${esc(b.code)}</span></td>
<td><b>${esc(b.name)}</b>${b.lineage && b.lineage.is_new ? ` <span class="st">new</span>` : ""}
<span class="sub">${esc(b.family)} · G${esc(b.generation)} · ${esc(b.code)}</span>
${b.lineage ? `<span class="sub dim">${esc(b.lineage.label)}</span>` : ""}</td>
<td>${b.strategy_name ? `<b>${esc(b.strategy_name)}</b>` : `<span class="dim">no armed strategy</span>`}
<span class="sub clamp" title="${esc(b.strategy_summary || "")}">${esc(b.strategy_summary || "")}</span></td>
<td class="r">${esc(trades)}<span class="sub">closed/open</span></td>
Expand Down
9 changes: 9 additions & 0 deletions scripts/railway_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@
"EVO_ROUTINE_HEARTBEATS_PER_DAY", "EVO_DEEP_REFLECTIONS_PER_DAY",
"EVO_STRATEGIC_REVIEW_HOURS",
"EVO_WEEKLY_TOKEN_BUDGET", "EVO_WEEKLY_LLM_CEILING_USD", "EVO_MAX_ACTIVE_AGENTS",
"EVO_MAX_GROWTH_PER_BOUNDARY",
# Research ceilings. These bound how much EVIDENCE an agent can gather in a cohort,
# and unlike the LLM budgets they cost CPU against our own DB rather than dollars.
# Live-settable because exhausting them is invisible from the outside: the fleet's
# backtest counters simply stop moving and read as "the agents lost interest" when
# they are in fact blocked (observed: all three agents pinned at sandbox_runs 50/50
# for days while an agent filed a ticket saying its evidence base had been
# invalidated and it had no budget left to rebuild it).
"EVO_WEEKLY_SANDBOX_RUNS", "EVO_WEEKLY_DATA_READS", "EVO_WEEKLY_MARKET_SCANS",
# Which tier runs on which backend/model. Readable + settable so a bad model id
# or a mis-set tier can be diagnosed and corrected without a deploy. The API KEY
# is deliberately NOT here — it is a credential, so it stays UI-only (this tool
Expand Down
116 changes: 116 additions & 0 deletions tests/test_dashboard_lineage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Where did this bot come from? Readable without a DB query.

The roster showed `Name · Surname · G1 · CODE-G1-044` and nothing else, so four
materially different kinds of bot were indistinguishable at a glance:

- an original founder that has been running for weeks
- a survivor carried over from the previous generation
- a child cloned from last generation's winner
- a brand-new bot born minutes ago (wildcard, or added to grow the fleet)

All six live bots read "G1" in the UI, which made it look like reproduction had
stopped working. It had — but you could not tell that from the roster, and the
operator's actual question ("I thought the winner's children keep the surname")
was unanswerable from what was on screen.

`origin` also could not separate the scheduled diversity wildcard from an agent
added by `grow_to_target`, because both are stored as origin='wildcard'. The
birth slot_key distinguishes them (`wildcard:N` vs `growth:<cohort>:N`), so the
roster reads it rather than guessing.
"""

from __future__ import annotations

import random
from datetime import datetime, timezone

from sqlalchemy import select

import kalshi_bot.evo.models as em
from kalshi_bot.dashboard import data as dash
from kalshi_bot.evo.cohorts import ensure_current_cohort
from kalshi_bot.evo.config import EvoSettings
from kalshi_bot.evo.evolution import bootstrap_founders, create_agent
from tests.test_evo_foundations import _mk_agent

NOW = datetime.now(timezone.utc)
S = EvoSettings(_env_file=None, population_size=3, max_active_agents=3)


def _lineage_of(session, agent_uuid: str) -> dict:
rows = dash._lineage(session, [agent_uuid])
return rows[agent_uuid]


def test_a_founder_says_so(evo_session):
bootstrap_founders(evo_session, S)
a = evo_session.scalars(select(em.EvoAgent)).first()
lin = _lineage_of(evo_session, a.agent_uuid)
assert lin["kind"] == "founder"
assert "founder" in lin["label"].lower()
assert lin["parent_name"] is None


def test_a_child_names_the_parent_it_was_cloned_from(evo_session):
"""The operator's question: do the winner's children keep the surname? Yes —
and the roster should say whose child it is, not just show a shared word."""
parent = _mk_agent(evo_session, surname="Blackwood")
cohort = ensure_current_cohort(evo_session, S)
child = create_agent(
evo_session, S, cohort, random.Random(1),
origin="child", parent=parent, slot_key=parent.agent_uuid,
)
assert child.surname == "Blackwood", "children inherit the parent surname"
assert child.generation == parent.generation + 1

lin = _lineage_of(evo_session, child.agent_uuid)
assert lin["kind"] == "child"
assert lin["parent_name"] == parent.display_name.split(" · ")[0]
assert "child of" in lin["label"].lower()
assert parent.surname in lin["label"]


def test_a_scheduled_wildcard_and_a_growth_hire_are_told_apart(evo_session):
"""Both are stored as origin='wildcard'; only the birth slot_key separates a
diversity injection from a bot added because the operator grew the fleet."""
cohort = ensure_current_cohort(evo_session, S)
rng = random.Random(2)
wc = create_agent(evo_session, S, cohort, rng, origin="wildcard",
slot_key="wildcard:1")
grown = create_agent(evo_session, S, cohort, rng, origin="wildcard",
slot_key=f"growth:{cohort.id}:1")

assert _lineage_of(evo_session, wc.agent_uuid)["kind"] == "wildcard"
grown_lin = _lineage_of(evo_session, grown.agent_uuid)
assert grown_lin["kind"] == "growth"
assert "grow" in grown_lin["label"].lower()


def test_a_bot_born_this_generation_is_marked_new(evo_session):
cohort = ensure_current_cohort(evo_session, S)
newborn = create_agent(evo_session, S, cohort, random.Random(3),
origin="wildcard", slot_key="wildcard:1")
lin = _lineage_of(evo_session, newborn.agent_uuid)
assert lin["born_cohort_id"] == cohort.id
assert lin["is_new"] is True


def test_a_carried_over_survivor_is_not_marked_new(evo_session):
"""A bot that survived into this generation must not read as a fresh hire —
that is exactly the distinction the roster could not make."""
veteran = _mk_agent(evo_session)
veteran.birth_cohort_id = -1 # born in some earlier cohort
evo_session.flush()
lin = _lineage_of(evo_session, veteran.agent_uuid)
assert lin["is_new"] is False


def test_the_roster_row_carries_lineage(evo_session, evo_settings):
"""It has to reach the payload the page renders, not just exist in a helper."""
bootstrap_founders(evo_session, evo_settings)
cohort = ensure_current_cohort(evo_session, evo_settings)
rows = dash._bot_rows(evo_session, evo_settings, cohort, now=NOW)
assert rows
for r in rows:
assert "lineage" in r
assert r["lineage"]["label"]
Loading