Files
MultiRoombaRover/plans/overseer v2 detailed architecture.md
T
2026-05-02 15:25:01 -04:00

387 lines
13 KiB
Markdown

# Overseer v2 Detailed Architecture
## Purpose
This document is the durable architecture spec for Overseer v2 so design intent is not lost across chats, sessions, or contributors.
Overseer v2 is a **new, separate service** focused on controlling the room/experience, not just commenting on rover activity.
## Non-Goals
- Do not replace or delete `llmCommentaryService`.
- Do not run two LLM overseer systems simultaneously in normal operation.
- Do not rely on giant unbounded context windows.
## Legacy Service Position
- Keep `llmCommentaryService` as a preserved legacy service.
- Keep it disable-able via config.
- New v2 service should be the primary runtime when enabled.
- Legacy service remains available for fallback/testing.
## Core Product Direction
Overseer v2 is "control-first":
- It can act on room systems and chat.
- Chat is one capability, not the whole job.
- It should feel ominous/playful and intentional, not spammy.
High-level behavior goals:
- Not overbearing.
- Does things when it can make moments interesting.
- Always responds when directly addressed.
- Does not always obey.
- Does not ramble.
## Primary Inputs Overseer Must Understand
Overseer must see all three lanes every decision cycle:
1. People activity
- Who is active now.
- Who is driving what.
- Activity bursts vs quiet periods.
- Direct addresses toward Overseer.
2. Conversation context
- **Actual recent chat conversation** (human + bot messages), not only tags.
- Last N human messages (minimum 5).
- Last 1-2 bot messages for anti-repeat context.
3. World/control state
- Rover states/events.
- Lift state/cooldowns.
- Neato state/cooldowns.
- Home Assistant entity states/availability.
- Safety/lock policy state.
## Runtime Model Strategy (Hardware-Aware)
Given modest hardware and ~30s generation on `mistral-small:24b` today:
- Use one LLM system only.
- Use deterministic program logic gate before LLM calls.
- Keep LLM calls sparse and meaningful.
- Keep context bounded and compact.
### Why Not Tiny-LLM Gate
Default gate should be deterministic logic, not another LLM:
- less latency
- less complexity
- fewer failure modes
- lower cumulative compute
A tiny LLM gate can be reconsidered later only for borderline cases.
## Continuous Loop Design (Never Stops)
System runs continuously but in two stages:
1. Fast gate loop (1-3s)
- No LLM call.
- Evaluates trigger conditions.
- If no trigger: continue.
2. LLM decision loop (on trigger or heartbeat)
- Triggered by gate or max-interval heartbeat.
- Builds bounded conversational context + state update.
- Model decides and optionally calls tools.
### Trigger Examples
- Direct address to Overseer in chat.
- Chat burst / topic change / challenge language.
- High-signal world event (dock/undock/hazard/control transition).
- Heartbeat timeout reached (for ambient presence).
## Decision Modes
The orchestrator should normalize model behavior to one of:
- `SKIP`
- `CHAT`
- `ACTION`
- `ACTION+CHAT`
Meaning:
- `SKIP`: no output, no tool call.
- `CHAT`: post chat only.
- `ACTION`: run tool(s) only.
- `ACTION+CHAT`: run tool(s) and post chat.
## Tool Output vs Chat Output
These are separate channels by design:
- Tool calls are structured actions.
- Chat text is explicit messaging output.
- A tool call does not automatically produce chat.
This separation is required to avoid chat spam and keep control behavior deliberate.
## Conversational Context Format (Important)
Overseer should use:
- Stable `system` prompt with identity/rules/policy.
- Real rolling transcript (users + overseer).
- A compact `STATE_UPDATE` message each decision cycle.
### Bounded Window Rules
To protect latency:
- Keep rolling window bounded (for example 20-40 recent turns, or last 5-10 min).
- Always include last 5 human messages minimum.
- Include last 1-2 bot messages.
- Drop/summarize older content.
## Tool Availability Contract
Critical refinement: do not advertise unusable tools as available.
Each cycle split into:
1. `available_tools`
- callable now
2. `blocked_tools`
- not callable now with reason:
- cooldown
- busy
- unavailable/offline
- policy lock
- rate limit
Example:
- If Neato is cooling down, remove `neato_*` from `available_tools` and list under `blocked_tools` with remaining seconds.
This prevents wasted model turns and impossible tool attempts.
## Initial Tool Surface
Planned callable tools (subject to runtime availability):
- `chat_say(text)`
- `lift_up()` / `lift_down()`
- `neato_start()` / `neato_send_home()` / `neato_locate()` / `neato_clear_errors()`
- `ha_set_entity(entity_id, state)` for configured controllable entities
- memory tools:
- `memory_read()`
- `memory_write(slot, text)` where slot ∈ {1,2,3}
Optional later:
- controlled button-box count adjustment tool (strictly bounded and auditable)
## Persistent Memory (Tiny, Explicit)
Use tiny explicit memory store:
- exactly 3 slots
- string lines only
- explicit read/write via tools
- no autonomous unbounded memory growth
Model decides which slot to replace when writing.
## Mode and Policy Locks (Tool Disabling Rules)
Overseer tool availability must respect global site mode and room-light policy locks.
Hard requirements:
- When site mode is `admin` or `lockdown`, disable Overseer action tools for:
- Neato controls (`neato_*`)
- Lift controls (`lift_*`)
- Room controls (`ha_set_entity` and related room-light toggles)
- When room lights are locked on by policy, disable room control tools even if site mode is otherwise open.
How this must appear to the model:
- Disabled tools must be removed from `available_tools`.
- Disabled tools must be present in `blocked_tools` with clear reason codes.
Recommended reason codes:
- `policy_lock:site_mode_admin`
- `policy_lock:site_mode_lockdown`
- `policy_lock:lights_locked_on`
Execution-time enforcement:
- Executor must re-check these locks immediately before executing any tool call.
- If a lock changed after context build, block execution and record a blocked action event.
## Safety and Control Guardrails
Must-have guardrails before enabling actions:
1. Hard allowlist
- Only approved tool names callable.
2. Tool-specific cooldowns
- e.g., lift/neato/HA each with independent cooldown rules.
3. Global action budget
- max actions per minute (or window).
4. Policy lock checks
- block actions during admin lock/safety states.
5. Execution-time validation
- arguments validated before invoking any control service.
6. Logging + audit trail
- every attempted/blocked/executed tool call recorded.
## Admin Debug UI Requirement
This is a core requirement.
Need a dedicated v2 admin panel that shows:
- enabled/running status
- loop phase (`idle`, `gate_check`, `awaiting_model`, `tool_exec`, `posted`, `failed`)
- last trigger reason
- last state-update payload snapshot
- model input summary
- model raw output + normalized decision mode
- tool calls attempted/executed/blocked with reasons
- cooldowns and remaining times
- recent action history
- last error/failure
### UI Switching Behavior
- If v2 enabled: show v2 panel.
- If legacy enabled: show legacy panel.
- If neither enabled: show disabled state.
Define a separate v2 status schema (do not overload legacy status object).
## Chat System Plumbing Changes
Keep the existing chat/Discord integration path and extend it minimally.
Decisions:
- Keep current internal bus-based chat flow as the canonical path (`chat:message` / `chat:typing`).
- Do not introduce a second parallel bridge path for v2.
- Add `bot: true|false` on chat message payloads.
- Overseer v2 must publish chat output through the same existing chat pipeline so Discord relay continues to work automatically.
- Keep legacy compatibility while migrating.
Goal:
- Any service can emit chat messages through one standardized path, with bot identity represented by `bot: true`.
## Example Message Topology Per LLM Decision
1. `system`: stable Overseer identity/rules/style/tool policy
2. `user`: compact `STATE_UPDATE`
3. transcript turns: recent chat + overseer messages
4. tool availability block (`available_tools`, `blocked_tools`)
5. model output: decision/tool calls/chat
6. executor: validates, executes tools, posts chat if applicable
7. append tool results and continue loop
## Implementation Phases
### Phase 0: Scaffolding
- Create new service module (suggested name: `overseerControlService`).
- Add config section and runtime enable switch.
- Keep legacy untouched.
### Phase 1: Observe-Only
- Build gate loop and context builder.
- Run model decisions with no real actions (dry-run tools).
- Log what would have happened.
### Phase 2: Chat + Safe Tools
- Enable chat output and lowest-risk tools.
- Verify anti-spam and direct-address behavior.
### Phase 3: Full Control Surface
- Enable lift/neato/HA with strict cooldowns and budgets.
- Add memory tool operations.
### Phase 4: Tuning
- Tune gate thresholds, cadence, cooldowns.
- Tune prompt style and anti-repeat behavior.
- Evaluate model alternatives only after gating/cadence tuning.
## Metrics To Track
- LLM calls per minute
- average LLM latency
- skipped vs acted decision ratio
- stale response rate (acted too late)
- blocked tool call rate and reasons
- action frequency per tool
- chat message frequency per minute
- repeated-line rate
## Rollback Plan
- Single config flip disables v2.
- Legacy overseer can be re-enabled without code rollback.
- Keep migration steps isolated and reversible.
## Practical Defaults (Starting Point)
- fast gate loop: 2s
- heartbeat model run: 30s
- min human chat context: 5 latest messages
- include latest bot messages: 2
- max tool calls per decision: 1 (start conservative)
- global action budget: low (start strict)
## Open Questions (Track During Build)
- Exact per-tool cooldown values for lift/neato/HA.
- Whether button-box count adjustment is worth enabling at all.
- Which model gives best tool reliability per watt on your machine.
- Ideal transcript window (turn-count vs time-window).
- Whether to include lightweight topic tags in addition to raw messages.
## One-Line Summary
Overseer v2 should be a separate, always-running, control-first orchestrator that uses deterministic gating + bounded real conversation context + dynamically available tools, with strict guardrails and a first-class debug UI.
## Naming Configuration Requirement
Overseer name must be configurable from server config.
Requirements:
- Add a config field for Overseer display/invocation name (for example under the v2 service config).
- The configured name must propagate everywhere it matters:
- in-chat bot nickname
- system prompt identity text
- direct-address recognition rules
- Prompt file should remain editable by non-code changes, so use a placeholder token in prompt text (for example `<NAME>`) and replace it at runtime.
- The prompt should still support fallback behavior if name config is missing (default name).
Implementation intent notes (future):
- Keep a single source of truth for name resolution (config + default).
- Avoid scattering hardcoded names in service code.
- If legacy and v2 coexist in codebase, ensure each service can use configured naming without breaking compatibility.
## Canonical "What the Bot Sees" Example
Use this as the concrete reference for message formatting per decision cycle.
```txt
[system]
You are <NAME>, a control-first room AI.
- You may CHAT and/or call tools.
- Respect safety/cooldowns/allowlists.
- Prefer restraint; avoid spam.
- Respond when directly addressed.
- If nothing meaningful changed, SKIP.
[user]
STATE_UPDATE
time: 2026-05-02T22:14:10-04:00
trigger: chat_burst
cooldowns: lift=ready, neato=12s, ha=ready
rovers:
- rover1: driving, driver=alex, docked=false
- rover2: driving, driver=sam, docked=false
- rover3: docked=true, charging=true
lift: down, busy=false
neato: connected=true, state=idle, charging=true
lights:
- shelf=on, bench=off, corner=on, color=purple, aux1=off, aux2=on
[user] alex: overseer you watching this drift?
[user] sam: dont kill my lights again
[assistant] <NAME>: Concrete lane stays lit. Earn the darkness.
[user] alex: i dare you to make this harder
[user] sam: bot pick a side
[assistant] <NAME>: Lift is staying put. Chaos has standards.
[user] alex: sam is about to bonk the dock
[user]
available_tools:
- chat_say(text)
- lift_up()
- lift_down()
- ha_set_entity(entity_id, state)
- memory_read()
- memory_write(slot, text)
[user]
blocked_tools:
- neato_start() reason=cooldown remaining=12s
- neato_send_home() reason=cooldown remaining=12s
- neato_locate() reason=cooldown remaining=12s
- neato_clear_errors() reason=cooldown remaining=12s
```
Notes:
- The conversation transcript is real recent chat (humans + bot), bounded by window rules.
- `STATE_UPDATE` is compact current truth, regenerated each decision cycle.
- Tools must be split into `available_tools` and `blocked_tools`; unavailable tools are not advertised as callable.
- `<NAME>` is runtime-substituted from config.