This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The repo ships with a curated reading order. Skim these before non-trivial work:
AGENTS.md— operating instructions for AI agents. This is the source of truth for invariants, process rules, and the "do-not-do" list. If anything here conflicts withAGENTS.md,AGENTS.mdwins.docs/plans/overview.md— what Beacon is and why; contains a defense-acronym crash course (CJADC2, MEDEVAC 9-line, LOGSTAT, DDIL, supply classes, ATAK, AIP). Read it if you don't know those terms — don't guess.docs/plans/agent-spec.md— the design contract for the agent (class hierarchy, model selection, tool registry, system prompt, I/O schemas, eval framework). Governs anything touching the agent or its tools.docs/plans/implementation.md— the task-by-task TDD build plan, Phases 0–10. Follow it in order. Phase 0 is validation gates and must pass before Phase 1.docs/plans/dashboard-mockup.html— the visual spec for the dashboard (Phase 6). Match its layout, colors, animations, and demo flow.docs/plans/glossary.md— military + technical terms. First stop for unfamiliar acronyms. Note: the glossary now describes Lemonade as a multimodal server (LLM + Whisper STT + Kokoro TTS via OpenAI-compatible endpoints); Beacon callsgaia.audio.lemonade_audio.transcribe()rather than running Whisper in-process.docs/superpowers/specs/— in-flight design notes for the mesh + dashboard integration (two-lane EventBus, peer-on-map, etc.).
Phases 1–8 and Phase 10 are implemented and the agent works end-to-end on
Lemonade. 397 tests collect; 4 are e2e-gated behind BEACON_E2E=1. The
two-lane EventBus refactor (sync + trigger) has landed alongside
MeshEnvelope / ConfirmPayload / RejectPayload schemas; mesh and AIP
integrations subscribe through the bus rather than poking state directly.
v0.1.0 cuts the first tagged release — full reasoning-trace coverage on
every query lifecycle, 90 s per-query agent-runner timeout, orphaned-row
reaper at lifespan startup, prompt v8.
What's wired today (verify with the named test file before you touch it):
- Mesh (Phase 8) —
src/beacon/mesh.pyisUDPMeshTransport(aliasBeaconMeshAdapter). It consumesMeshService/mesh/transport.UDPTransport, subscribes tostate.subscribe_syncto broadcastMeshEnvelopeJSON, and pipes inbound packets throughstate.apply_remote_event(mesh-id filtered, loopback-dropped, bus-silenced)./mesh/{status,peers,enable, disable,test/ping,test/event}endpoints are live. Tested intest_mesh_adapter.py,test_mesh_endpoints.py,test_mesh_envelope.py,test_mesh_id_filter.py,test_mesh_identity.py.BEACON_MESH=1auto-starts at lifespan;BEACON_MESH=0is a hard env lock that no-ops/mesh/enable. - AIP (Phase 10) —
src/beacon/aip.pysubscribes tosubscribe_triggeronly (never sync). Onconfirmenvelopes it re-fetches the canonical record byref_event_uuidand pushes; rejects are no-op./aip/entitiespulls the overlay; whenAIP_BASE_URLis unset it returns{entities:[], connected:false}— the real "not configured" state, not a mock. Tested intest_aip.pywith FakeHTTP. - Voice (Phase 7) —
src/beacon/voice.pyis a thin wrapper overgaia.audio.lemonade_audio.transcribe()./voice/transcribereturns 200 + transcript on success, 502 on backend down — not 503, no in-process Whisper fallback. Tested intest_voice.py. The dashboard push-to-talk button was removed;dashboard/src/voice.tsremains for headset clients that POST WAV bytes directly. Reports in the dashboard come in via text or the demo runner. - RAG —
BeaconAgentinheritsRAGToolsMixin, which registers 9 tools (query_documents,index_document,rag_status, …) into GAIA's module-global_TOOL_REGISTRYalongside Beacon's 7 doctrinal tools (request_medevac,request_fires,report_consumption,report_event,forecast_burndown,recommend_reroute,query_state). Eager-indexesdoctrine/*.pdfnext tobeacon.dbat startup./rag/*endpoints return 503 /enabled:falsewhenself.rag is None. - Autonomous loop —
src/beacon/agent_runner.pyowns an asyncio.Queue (cap 64) and exposes/agent/{enqueue,status,start,pause,resume,stop}. The dashboard's status pill (OFF · IDLE · PROCESSING · PAUSED · ERROR) reads from here. 90 s per-query wall-clock cap (BEACON_AGENT_TIMEOUT_S, set 0 to disable); a wedged Lemonade call is abandoned, the row is marked error, and the runner keeps draining.reap_orphaned_processing()fires from lifespan startup before the runner spawns — flips rows stuck inprocessing(from a prior SIGKILL / OOM / reload) toerrorwith an honest reason so the dashboard reflects reality on every boot. - Insights + reasoning trace —
src/beacon/insights.pyis now agent-only (heuristics deleted, commit95e64d6+ the agent-only rewrite). It subscribes to the trigger lane and runsagent.analyze()(tool-less, JSON-array contract) on each confirmed state change, with a 1s trailing debounce that coalesces multi-tool-call bursts from one operator query into a single analyzer call. No periodic timer, no rate gate beyond debounce.src/beacon/reasoning.pyis a 500-step in-memory ring buffer streamed at/agent/reasoning/stream. As of v0.1.0, every query lifecycle bookends visibly: AgentRunner emitsOBSERVEon dequeue andSURFACE(orDECIDE/error) on completion;_execute_toolrecordsTOOL(dispatching + result phases); pending-confirm tool returns upgrade toPROPOSE;/agent/confirmand/agent/rejectrecord an operator-dispositionOBSERVEstep; InsightsEngine recordsQUERY/SURFACE;_capture_reasoning_contentrecordsREASONING(THINKING in the dashboard) when an LLM round-trip comes back with areasoning_contentpayload — dormant under the Qwenenable_thinking=Falsedefault, ready when streaming-thinking is wired.
Phase 9.2 (eval framework) is implemented (src/beacon/eval/__main__.py,
beacon-eval CLI, uses gaia.eval.claude.ClaudeClient + gaia.eval.scorecard)
with 24 scenarios across 8 categories (iran-showcase, mixed, rag,
medevac, logstat, doctrinal, edge, showcase). Spec target was ~40
across more axes; treat the corpus as a sanity gate, not a release gate.
Phase 0 hardware probes were not run because the dev box is a Mac M4 Pro, not Ryzen AI silicon. Lemonade still works (CPU/GPU path) — see Smoke baseline below.
Head-to-head bench across 4 candidate models on the iran-showcase corpus (6 multi-event scenarios), same hardware, same prompt v6:
model pass avg batch p50/scenario
Qwen3-4B-Instruct-2507-GGUF 5/6 6.7 58s ~5s ← default
Gemma-4-E4B-it-GGUF 6/6 7.0 170s 14s
Qwen3.5-4B-GGUF 4/6 6.4 80s 9s
Gemma-4-E2B-it-GGUF 0/6 5.2 64s 10s
Qwen3-4B-Instruct-2507 wins the tradeoff: highest pass-rate-per-second,
sub-5s on 4 of 6 scenarios on M4 Pro, AND it's a non-thinking-only model
per the HF card — so no enable_thinking patch tower is needed (eliminates
the chain-of-thought-leakage class of failures we saw on Qwen3.5).
Gemma-4-E4B-it-GGUF remains the highest-accuracy alternate at the cost of
3× wall-time; pick it via BEACON_MODEL=Gemma-4-E4B-it-GGUF when the
multi-axis-summary stress test must pass.
Latency on this dev box is not representative of fielding hardware (Ryzen AI NPU acceleration is Windows/Linux only). On a Toughbook-class laptop the spec's 5s p50 / 8s p95 target should be reachable.
# Setup
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run (4 separate processes)
lemonade-server serve # local LLM backend, required unless BEACON_USE_OPENAI=1
beacon seed # populate SQLite with synthetic demo data
beacon serve # FastAPI on :8888 (Lemonade owns :8001/:8002)
beacon ask --auto-confirm "Bravo Co at 38SMB12345678, 2 wounded gunshot urgent surgical"
cd dashboard && npm install && npm run dev # Vite dev server (default :5173, but :5173 is Claudia
# in this dev box — start with `--port 5174`)
# Confirm-required tools (request_medevac, recommend_reroute, request_fires) land in pending_actions.
# Operator confirms via the dashboard, or:
curl -X POST http://localhost:8888/agent/confirm/<event_uuid>
curl -X POST http://localhost:8888/agent/reject/<event_uuid>
# MeshService standalone server (cross-device demos; the in-process
# UDPMeshTransport at src/beacon/mesh.py is what /mesh/* runs against):
cd MeshService
uv run --with-requirements requirements.txt \
python server.py --id alpha --name "Alice" --port 5681 --http-port 8000
# Tests
pytest tests/ -xvs # all, verbose, fail-fast
pytest tests/test_state.py -v # one file
pytest tests/test_state_two_lane_bus.py -v # the new two-lane bus tests
pytest tests/test_state.py::test_apply_remote_event_idempotent -v # one test
BEACON_E2E=1 pytest tests/test_agent_e2e.py # live LLM tests
# (Lemonade running OR
# BEACON_USE_OPENAI=1 with key OR
# BEACON_USE_CLAUDE=1 with key)
# Eval (the runner is implemented; the judge is always Claude)
# Two judge backends are supported:
# --judge-backend claude-code shells out to `claude -p`, uses your Claude Code
# subscription auth — NO ANTHROPIC_API_KEY NEEDED.
# Prefer this when the dev box already has Claude Code.
# --judge-backend anthropic-sdk uses gaia.eval.claude.ClaudeClient and requires
# ANTHROPIC_API_KEY (default backend).
beacon-eval --output eval/runs/baseline-$(date +%s)/ --judge-backend claude-code # subscription auth
beacon-eval --category mixed --output eval/runs/mixed-only/ --judge-backend claude-code
beacon-eval --judge claude-sonnet-4-5-20250929 # override judge model (anthropic-sdk only)
# Lint
ruff check .CI (.github/workflows/test.yml) runs ruff check . then pytest tests/ -v on Python 3.11 for push and PR to main.
A single Python agent feeds a single SQLite event log; that log is the canonical state, and several transports / engines fan out from it via a two-lane EventBus:
voice/text → BeaconAgent (Lemonade-served LLM + GAIA tools + RAG mixin)
│
▼ Pydantic-validated tool calls
StateManager (SQLite + two-lane EventBus)
┌──────┴──────┐
▼ ▼
subscribe_sync subscribe_trigger
(peers see (only confirmed
pending too) / non-pending)
│ │
▼ ▼
Mesh transport AIP push + InsightsEngine
(UDP via (Palantir (agent-only,
MeshService) REST) 1s debounce)
→ FastAPI/SSE → dashboard (reads /state, /state/stream,
/insights/stream,
/agent/reasoning/stream)
Key seams that span multiple files:
-
BeaconAgent(src/beacon/agent.py) subclassesgaia.agents.base.agent.Agent(withRAGToolsMixin) and registers 7 tools fromsrc/beacon/tools/:request_medevac,request_fires(call-for-fire, FM 3-09; confirm-required like medevac),report_consumption,report_event,forecast_burndown,recommend_reroute,query_state(read-only lookup so the agent can answer "what happened with X?" without keeping everything in conversation memory). Plus 9 RAG tools from the mixin whenself.rag is not None. Tools follow the factory patternmake_<tool_name>(state)so state is injected, not imported. Doctrinal pin-cites are attached to each tool step in the reasoning trace (FM 4-02.2, FM 3-09, FM 4-0, ADP 6-0, FM 3-90). -
Parallel-tool-call interceptor (
agent.pylines 406–555): GAIA's parser raises on paralleltool_calls; Beacon stashes #2..N in_pending_tool_calls, dedupes by(tool, args)signature across LLM retries, and monkey-wrapschat.send_messagesto drain the queue without round-tripping. Tied toamd/gaia#944— delete-when-upstream-lands. -
Schema-first: every structured output is a Pydantic model in
src/beacon/schemas.py. Doctrinal correctness is enforced by Pydantic, not by prompting. Tools return one of{"status": "ok"|"submitted"|"pending_confirm", ...}on success or{"status": "error", "error": str(e)}onValidationError. The schema set now also includesMeshEnvelope(typed wrapper for everything that crosses a transport),MedevacPayload/ConsumptionPayload/EventPayload/ReroutePayload/ConfirmPayload/RejectPayload/PingPayload/TestEventPayload/MeshPayload/ChatPayload, andRerouteActionenum. -
Two-lane EventBus (
StateManager.subscribe_syncandsubscribe_trigger):- Sync lane fires on every state change including pending. Mesh transports subscribe here so peers mirror situational awareness immediately.
- Trigger lane fires only on confirmed / non-pending writes. AIP push (and any other irreversible side-effect) subscribes here. This is the AGENTS.md § 2.2 enforcement seam — getting it wrong leaks pending proposals to the data fabric.
- Subscribers receive
MeshEnvelopeinstances. Failures are swallowed per subscriber so a broken transport never blocks local writes. - The legacy
subscribe()shim still exists; it maps tosubscribe_triggerwith a deprecation warning.
-
Confirm-required flow:
request_medevac,recommend_reroute, andrequest_fireswrite the proposed record topending_actionsand returnstatus="pending_confirm". The trigger lane stays silent. The dashboard confirms viaPOST /agent/confirm/{uuid}(promotes to canonical table, fires trigger lane) orPOST /agent/reject/{uuid}(drops the row, no emission). Both endpoints record an operator-dispositionOBSERVEstep into the reasoning log so the panel closes the loop visibly. For eval/test runs,BeaconAgent(confirm_mode="never")auto-confirms in the agent's_execute_tooloverride and strips theconfirm_requiredflag from the return value. -
GAIA adaptations (the spec's assumed API differs from reality):
Agent.__init__takesmodel_id,base_url,skip_lemonade,use_claude,use_chatgpt— NOTsystem_promptormodel. Override_get_system_prompt()for the prompt.- Entry point is
process_query(text), notrun(text).BeaconAgent.run()is a thin wrapper. - Tools live in module-global
_TOOL_REGISTRYkeyed byf.__name__. TwoBeaconAgentinstances in one process rebind every tool to the second agent's state — known limitation, would require forking GAIA to fix. Agent.__init__callsLemonadeManager.ensure_ready()unlessskip_lemonade=True. Tests passskip_lemonade=True.- GAIA dependency is currently pinned to
feat/lemonade-audio-endpoints(not@main) because Beacon's voice layer depends ongaia.audio.lemonade_audio. Switch back to@mainwhen that branch lands upstream.uv.lockcarries the cached SHA so existing checkouts stay reproducible.
-
Lazy server agent construction:
beacon.serverdoes NOT build the agent at import; it builds in the FastAPI lifespan startup./healthworks even if Lemonade is down. SetBEACON_DEFER_AGENT=1to defer construction to first/agent/runcall. -
Mesh sync (
MeshService/+src/beacon/mesh.py): theMeshService/UDP transport (UDPTransport) is implemented, tested, and consumed by Beacon throughUDPMeshTransport(aliasBeaconMeshAdapter). The adapter (a) wrapsUDPTransport, (b) subscribes tostate.subscribe_syncto broadcastMeshEnvelopeJSON, and (c) decodes incoming UDP packets throughstate.apply_remote_event(envelope). Mesh-id namespacing keeps separate pools from talking; node-id stamping on every envelope drives "no-loopback" via the_silence()context manager during peer-applied writes. The standaloneMeshService/server.py(with flat-namespaced/status,/peers,/messages,/send,/broadcast,/ws) still runs for cross-device demos. -
AIP push (
src/beacon/aip.py): subscribes tosubscribe_triggeronly (per AGENTS.md § 2.2 — pending proposals must never reach the data fabric). Onconfirmenvelopes it re-fetches the canonical record byref_event_uuidand pushes; rejects are no-op. Beacon-kind → AIP object-type mapping: medevac→MedevacRequest, consumption→SupplyStatus, event→BattlefieldEvent, reroute→RerouteRecommendation./aip/entitiespulls the overlay; offline state whenAIP_BASE_URLis unset is a real{connected:false}payload, not a mock. -
InsightsEngine + ReasoningLog:
src/beacon/insights.pyis a trigger-lane subscriber with a 1s trailing debounce that callsagent.analyze(snapshot_json)— a tool-less LLM pass that returns a JSON array ofInsight. Heuristics were removed (commit95e64d6+ the agent-only rewrite perdocs/superpowers/specs/2026-05-03-remove-heuristic-insights-design.md). Per AGENTS.md § 2.3, parse failures and connection errors return[]and log; we do not fabricate insights to fill the gap.src/beacon/reasoning.pyis a 500-step in-memory ring buffer;_execute_toolwrites TOOL steps, the InsightsEngine writes QUERY/SURFACE. The dashboard's reasoning column streams from/agent/reasoning/stream. -
Local-first inference: Lemonade Server is the default. The agent's
DEFAULT_MODELisQwen3-4B-Instruct-2507-GGUF(Qwen 3 4B Instruct, July 2025 release, GGUF-quantised) — picked from the head-to-head bench above because it ties the throughput sweet spot (5/6 PASS in 58s) with being a non-thinking-only model (noenable_thinkingpatches needed, no chain-of-thought leakage). Same Hermes tool-calling + Qwen sampling distribution as Qwen3.5, so the parallel-call interceptor and Qwen-specific sampling overrides apply unchanged. Override withBEACON_MODEL=…(other tested options:Gemma-4-E4B-it-GGUFfor highest accuracy at 3× latency cost,Qwen3.5-4B-GGUFfor vision support). The 30B-class model the spec originally called for is still too large for current dev hardware; swap when Phase 0.1 is re-run on Ryzen AI silicon. OpenAI fallback is opt-in viaBEACON_USE_OPENAI=1; Claude fallback isBEACON_USE_CLAUDE=1. If Lemonade is unreachable and no opt-in is set, the server returns 502 with an actionable detail — never silently fall back. The backend lifespan POSTs Lemonade/api/v1/loadat startup withctx_size=BEACON_CTX_SIZE(default 32768) so the active model serves with enough context for multi-turn extraction — Lemonade's server-widectx_sizeconfig (set once vialemonade config set ctx_size=32768) overrides per-request values, so changingBEACON_CTX_SIZEalone won't take effect unless the server config matches.Qwen-specific tuning (lives in
BeaconAgent._intercept_chat_send, fires whenever"qwen" in self.model_id.lower()):- Thinking mode is force-disabled. Qwen3-family GGUFs ship with reasoning ON by default — the model emits a multi-page
Thinking Process:chain-of-thought before any answer. With thinking ON,agent.analyze()'s JSON-array-output contract burns 4+ minutes generating reasoning and never reaches the final array (content stays empty, 0 insights). We passchat_template_kwargs={"enable_thinking": False}on every send and bumpmax_tokensto 16384 so non-thinking outputs have room. If/when token-level streaming to the dashboard reasoning panel lands (Phase 2), re-enable thinking and use_capture_reasoning_contentto surfacereasoning_contentlive asREASONINGreasoning steps. - Server-generated
insight_uuid. Qwen3-* routinely emits placeholder UUIDs from training data (a1b2c3d4-e5f6-4g7h-...) that fail Pydantic's hex validation and silently throw away otherwise-valid insights._parse_insights_responseoverwrites the LLM's value with a freshuuid4()— safe because dedupe is keyed ondedupe_key, citations usesources(event UUIDs), and the dashboard treatsinsight_uuidonly as a React key + ack/dismiss target. min_context_sizeis 32768, not 131072. Earlier 131072 causedlemonade_manager._try_reload_with_ctxto silently fail withreported=0 (need 131072), leaving Lemonade in a state where chat calls hang for 5min before timing out. 32K matches Qwen3.5-4B's native context and the lifespan/api/v1/loadPOST. Override viaBEACON_CTX_SIZEwhen running with a model + hardware that can do better.
- Thinking mode is force-disabled. Qwen3-family GGUFs ship with reasoning ON by default — the model emits a multi-page
These come straight from AGENTS.md; they're non-obvious and easy to violate by accident:
- Schema-first, then test, then code, then commit. Update or add the Pydantic schema in
schemas.pybefore writing the tool. Write the failing test before the implementation. Never invert this order. - Never bypass the confirm flow for
request_medevac,recommend_reroute, orrequest_firesin tests, demos, or eval runs — except via the explicitconfirm_mode="never"opt-in. The trigger lane is the enforcement point. - No mocks in shipped code. If a backend dep isn't available, surface a real "not configured" / "offline" /
502state — not fake data. Test fakes (FakeHTTP, monkeypatched modules) are fine; they live in tests/. - No silent fallbacks. No broad
except Exception. The only place catching-and-returning-error-dict is acceptable is GAIA_TOOL_REGISTRYtool functions — that's the contract for tool error reporting. Everywhere else, raise a typed Beacon error with what failed and what the caller should do. - Synthetic data only. No real OPORDs, casualty data, unit positions, MGRS coordinates, supply consumption rates, or any real military information. New demo data must be fictional and disclaimed.
- Do not vendor or fork GAIA. It is consumed via
pip installfromgit+https://github.com/amd/gaia.git@<branch>. File upstream issues; don't copy code. - No AI-attribution in any artifact — no
🤖 Generated with Claude Codefooters, noCo-Authored-By: Claudetrailers, no "Authored by AI" mentions in commits, PRs, code comments, or docs. The human contributor is the author of record. - Eval before merging prompt or schema changes. Every system-prompt or tool-schema change requires a baseline-vs-iter eval comparison. Prompt versions live as full files (
v1.py,v2.py, …) so diffs show actual text changes. - Don't change the LOTR brand framing ("Beacons of Gondor", "we feed the palantíri") without discussing — it's deliberately tied to partner-pitch resonance.
- The judge is always Claude. The agent backend can be Lemonade / OpenAI / Claude; the eval judge is
gaia.eval.claude.ClaudeClientregardless.ANTHROPIC_API_KEYis required for any eval run.
All endpoints below are wired to real backends — no stub routes returning fixed JSON. "Offline" means a dependency is unreachable or unconfigured; the response shape carries that as data, not a 503.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Liveness probe; works even when Lemonade is down |
GET |
/state |
Full snapshot (medevacs, consumption, events, reroutes, pending) |
GET |
/state/stream |
SSE stream of state snapshots, ~1Hz polling |
POST |
/agent/run |
Submit a free-form report; returns {response} (state arrives via SSE) |
POST |
/agent/confirm/{uuid} |
Promote a pending action; fires the trigger lane |
POST |
/agent/reject/{uuid} |
Drop a pending action; no emission |
POST |
/agent/enqueue |
Non-blocking submit into AgentRunner's asyncio.Queue (cap 64) |
GET |
/agent/status |
AgentRunner state — OFF · IDLE · PROCESSING · PAUSED · ERROR + queue depth |
POST |
/agent/start /pause /resume /stop |
AgentRunner control |
GET |
/agent/reasoning |
Recent reasoning steps (in-memory ring buffer) |
GET |
/agent/reasoning/stream |
SSE per-turn reasoning trace |
POST |
/voice/transcribe |
Multipart WAV → gaia.audio.lemonade_audio.transcribe(); 502 on backend down |
GET |
/mesh/status |
UDP transport health + peer table |
GET |
/mesh/peers |
Peers subset of /mesh/status |
POST |
/mesh/enable /disable |
Start/stop the transport (respects BEACON_MESH=0 env lock) |
POST |
/mesh/test/ping /test/event |
Broadcast debug envelopes |
GET |
/aip/entities |
Pull AIP overlay (units / OPORDs / FOBs); {connected:false} if AIP_BASE_URL unset |
GET |
/insights |
Current insight list |
GET |
/insights/stream |
SSE diff stream |
POST |
/insights/{uuid}/acknowledge /dismiss |
Operator actions on insights |
GET |
/rag/status |
Corpus stats; enabled:false when self.rag is None |
GET |
/rag/list |
Per-document listing (503 when RAG disabled) |
POST |
/rag/index |
Upload + index PDF/txt; 415 on encrypted PDF |
DELETE |
/rag/index/{filename} |
Remove a document |
POST |
/rag/query |
Semantic search debug surface |
POST |
/demo/play /stop /pause /resume /reset /inject |
Timeline playback + custom queries |
GET |
/demo/status |
Playback snapshot |
| What | Where |
|---|---|
| New Pydantic schema | src/beacon/schemas.py |
| New tool | src/beacon/tools/<name>.py (factory make_<name>(state)) |
| State manager change | src/beacon/state.py (touch subscribe_sync / subscribe_trigger carefully) |
| Server endpoint | src/beacon/server.py |
| CLI subcommand | src/beacon/cli.py |
| Mesh adapter (Beacon side) | src/beacon/mesh.py (consumes MeshService/mesh/transport.UDPTransport) |
| Mesh transport itself | MeshService/mesh/ — keep the standalone service intact |
| AIP integration | src/beacon/aip.py (subscribes to trigger lane only) |
| Voice (Lemonade-routed) | src/beacon/voice.py |
| Autonomous loop | src/beacon/agent_runner.py |
| Insights (agent-only) | src/beacon/insights.py |
| Reasoning trace | src/beacon/reasoning.py |
| Demo runner / playback | src/beacon/demo.py |
| Identity / node fingerprint | src/beacon/identity.py |
| Dashboard component | dashboard/src/<Component>.tsx (MeshPanel, MeshStatusChip, AIPPanel, InsightsPanel, AgentReasoningPanel, AgentStatusPill, BurndownPanel, DoctrineCorpusPanel, DemoControls, CounterfactualPane already exist) |
| Eval scenario | eval/scenarios/<category>/<id>.yaml |
| Demo data | demo/ |
| Architecture diagram | docs/architecture-diagram.html (single-file, mirrors dashboard styling) |
| Test for module X | tests/test_<module>.py (one file per module) |
beacon serve --db <path>unconditionally writesBEACON_DB_PATHfrom the CLI default (beacon.db) and clobbers any env var the shell already set. Pass--db <abs-path>explicitly until the CLI is fixed to honor an existing env var.beacon serve --reloadre-importsbeacon.serveron every change, which re-runs Lemonade probing and re-decorates tools into GAIA's module-global_TOOL_REGISTRY. Expect surprises.- Lemonade owns ports 8001/8002 when its workers are warm — the dashboard / API on those ports will collide. Beacon defaults to
:8888. - Vite default port
:5173collides with Claudia in this dev environment. Start the dashboard withnpm run dev -- --port 5174. tests/test_tools_reroute.py:46has(8001, 6000)as MGRS easting/northing — not a port. Don't sed-replace it.
Conventional commits: feat(scope):, fix(scope):, docs(scope):, test(scope):, chore(scope):, refactor(scope):. Imperative mood, short summary, optional longer body. Examples in AGENTS.md § 3.5.