File size: 3,548 Bytes
42e32ed
 
 
 
 
32a601f
42e32ed
5424fe6
42e32ed
 
 
5424fe6
42e32ed
 
 
32a601f
42e32ed
5424fe6
42e32ed
 
 
 
 
 
 
 
 
 
 
 
 
32a601f
 
42e32ed
 
 
32a601f
42e32ed
 
 
 
 
 
 
5424fe6
 
 
 
 
 
 
 
 
42e32ed
 
 
 
 
32a601f
 
 
 
 
42e32ed
5424fe6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from __future__ import annotations

from collections import Counter

from src.core.events import Event, event_summary
from src.core.governor import Governor
from src.core.projections import StageProjection
from src.scenarios.base import Scenario


def render_stage(projection: StageProjection) -> str:
    goal = f"> **Goal:** {projection.goal}\n\n" if projection.goal else ""
    artifacts = "\n".join(f"- {item}" for item in projection.user_artifacts) or "- No visitor artifacts yet."
    notes = "\n".join(f"- {item}" for item in projection.agent_notes) or "- Agents are waiting."
    verdicts = "\n".join(f"- {item}" for item in projection.judge_notes) or "- No verdict yet."
    return f"""## Current Clearing

{goal}{projection.current_scene}

### Visitor Disturbances
{artifacts}

### Agent Activity
{notes}

### Judge Notes
{verdicts}
"""


def render_event_log(events: tuple[Event, ...]) -> str:
    if not events:
        return "(ledger is empty)"
    return "\n".join(event_summary(event) for event in events)


def render_stats(events: tuple[Event, ...], governor: Governor | None = None) -> str:
    by_kind = Counter(event.kind for event in events)
    by_actor = Counter(event.actor for event in events)
    lines = ["Events by kind:"]
    lines.extend(f"  {key}: {value}" for key, value in sorted(by_kind.items()))
    lines.append("")
    lines.append("Events by actor:")
    lines.extend(f"  {key}: {value}" for key, value in sorted(by_actor.items()))

    # Structured-output health: how often the parser had to fall back to raw text.
    content = [e for e in events if e.actor not in ("conductor", "visitor")]
    fallbacks = sum(1 for e in content if e.payload.get("_raw_fallback"))
    if content:
        lines.append("")
        lines.append(f"Structured output: {len(content) - fallbacks}/{len(content)} clean JSON "
                     f"({fallbacks} raw fallback)")

    lines.append("")
    lines.append("Hackathon constraints:")
    lines.append("  runtime model cap: <=32B")
    lines.append("  tiny mode target: <=4B")
    lines.append("  UI target: custom Gradio")
    if governor is not None:
        lines.append("")
        lines.append("Governor:")
        for k, v in governor.stats.items():
            lines.append(f"  {k}: {v}")
    return "\n".join(lines)


def render_config(scenario: Scenario, profile_models: dict[str, str] | None = None) -> str:
    """The 'config as data' panel β€” the live, declarative makeup of the run.

    Everything shown here comes from YAML (config/), not code: the cast that
    participates, each agent's model tier, what it may emit, and its tool grants.
    """

    rows = ["| agent | role | model | emits | tools |", "|---|---|---|---|---|"]
    for agent in scenario.agents:
        manifest = getattr(agent, "manifest", None)
        if manifest is None:
            rows.append(f"| {agent.name} | (legacy) | β€” | β€” | β€” |")
            continue
        emits = ", ".join(manifest.may_emit) or "β€”"
        tools = ", ".join(manifest.tools) or "β€”"
        rows.append(
            f"| `{manifest.name}` | {manifest.role} | `{manifest.model_profile}` | {emits} | {tools} |"
        )
    table = "\n".join(rows)

    profile_block = ""
    if profile_models:
        profile_lines = "\n".join(f"- `{p}` β†’ `{m}`" for p, m in profile_models.items())
        profile_block = f"\n\n**Model profiles**\n{profile_lines}"

    goal = f"\n\n**Goal**\n{scenario.goal}" if scenario.goal else ""
    return f"### Cast (from `config/`)\n\n{table}{profile_block}{goal}"