Repository navigation
refactor: extract engine package to separate frontend from backend - #4
Conversation
Create internal/engine/ with a synchronous state machine that owns all debate orchestration logic. The TUI is now a thin adapter that converts engine Actions to tea.Cmd and feeds tea.Msg events back. Key interfaces added for future extensibility: - Format: pluggable debate protocols (rounds, speaking orders) - PromptBuilder: customizable prompt assembly - SessionWriter: swappable persistence backend (disk, DB, etc.) - ToolRegistry: extensible debate tools - ContextGatherer: forward-looking interface for file/browser exploration The engine returns Action values (CallAPIAction, CallDiscoveryAction, etc.) that frontends convert to their native async mechanism. This enables building web UIs, REST APIs, or headless runners against the same engine. https://claude.ai/code/session_01GMQzCUHDQP4NQAQwXVhBaP
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis pull request introduces a comprehensive Engine-based state machine for debate workflows. It adds abstractions for debate formats, prompt building, session persistence, tools, actions, and lifecycle states. The main entry point is refactored to use Engine-based API access instead of direct field manipulation. Changes
Sequence DiagramssequenceDiagram
participant Client
participant Engine
participant API as OpenRouter API
participant SessionWriter
participant PromptBuilder
Client->>Engine: Init(hasAPIKey)
alt No API Key
Engine->>Client: Return APIKeySetupAction
else API Key Present
alt No Topic
Engine->>Client: Return TopicInputAction
else Topic Set
Engine->>PromptBuilder: SystemPrompt(model, round, context)
PromptBuilder-->>Engine: Prompt text
Engine->>Client: Return CallAPIAction
Client->>API: Send request with prompt
API-->>Client: Response + tokens
Client->>Engine: HandleAPIResult(content, tokens, error)
Engine->>SessionWriter: WriteRound(results)
SessionWriter-->>Engine: Success
Engine->>Client: Return next action
end
end
sequenceDiagram
participant Client
participant Engine
participant API as OpenRouter API
participant Tools as ToolRegistry
participant SessionWriter
Client->>Engine: HandleTopicInput(topic, withDiscovery=true)
Engine->>Engine: Phase = discovery
Engine->>API: Send discovery prompt
API-->>Engine: Tool calls + content
Engine->>Engine: HandleDiscoveryResult(content, toolCalls)
alt Tool = update_topic
Engine->>SessionWriter: Update topic
Engine->>Engine: Continue discovery loop
Engine->>Client: Return CallDiscoveryAction
else Tool = ask_user
Engine->>Client: Return AskUserAction
else No tools
Engine->>Engine: Phase = smoke_test
Engine->>Client: Return CallAPIAction (smoke test)
end
sequenceDiagram
participant Client
participant Engine
participant API as OpenRouter API
participant PromptBuilder
participant SessionWriter
loop For each Round
loop For each Model in Round
Client->>Engine: HandleAPIResult(model, content, tokens)
Engine->>SessionWriter: WriteRoundModel(result)
Engine->>Engine: Move to next model
Engine->>PromptBuilder: SystemPrompt(nextModel, round, context)
Engine->>Client: Return CallAPIAction
end
Client->>Engine: HandleSummaryDone(roundSummary)
Engine->>SessionWriter: WriteRoundSummary(summary)
Client->>Engine: HandleAutoModDone(autoModNote)
Engine->>SessionWriter: WriteRound (finalized)
alt All rounds complete
Engine->>SessionWriter: WriteVerdict(result)
Engine->>Client: Return complete
else More rounds
Engine->>Engine: Advance to next round
Engine->>Client: Return next CallAPIAction
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
- Split Format god-interface into DebateStructure + ModelRegistry - Replace PromptContext closure with Codenames map for cleaner data flow - Remove duplicated phase constants from TUI (use engine.Phase directly) - Move Activity presentation data (codename, round name) to TUI render time - Add State() snapshot to reduce accessor sprawl; remove unused Debate interface - Make Actions self-contained: carry API/executor/loader refs so frontends don't reach back into engine for execution dependencies - Remove unused engine accessors (RoundResults, ToolRegistry, CostTracker, SessionLoaderRef, RecordUsageFunc) - Remove unused roundResults param from CallWithToolsCmd https://claude.ai/code/session_01GMQzCUHDQP4NQAQwXVhBaP
- Add engine_test.go (34 tests): state machine transitions, round progression, error handling, discovery flow, resume, activity tracking, cost recording, and self-contained action verification - Add format_test.go (11 tests): DefaultFormat round names, speaking order, max tokens, tool enablement, model registry - Rewrite model_test.go to use State() snapshots instead of individual accessors; add GetState integration test https://claude.ai/code/session_01GMQzCUHDQP4NQAQwXVhBaP
- Remove dead legacy wrappers from view.go (renderFeedContent, renderLogLines — never called) - Remove unused engine accessors: Anonymizer, DiscoveryPending, DiscoveryResultsMap, GetMappingText, MasterMap, ReverseMap, KnowledgeBase, BasicCost, PromptBuilder, SetTopic, SessionDir, DiscoveryTurns - Delete tui/context_builders.go (re-export shims + dead helpers DefaultResearchAssignments, DefaultSpeakingOrderByRound) - Move context builder tests to engine/prompt_helpers_test.go where the functions actually live - Replace MasterMap() call in promptContext with direct field access - Clean up "backward compatibility" comments https://claude.ai/code/session_01GMQzCUHDQP4NQAQwXVhBaP
…serve main) Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- engine: pass prompts.InfraBlock/ToolsBlock to BuildSetupBlock; pass config.SpeakingOrderByRound and verdict labels to WriteRound/WriteVerdict - engine SessionWriter: add speakingOrder [][]string and verdict title/labels - tui: use config.SpeakingOrderByRound[0] for roster; cast Phase to engine.Phase for roundStatusIndicator; add SetAPI, ModelOpts.SessionLoader - cmd: cassette mode uses model.API, model.Topic, model.SetAPI(recorder) - tests: fix model_test for Model without Engine; inject SessionLoader via opts - sim: regenerate view snapshots for roster order (round 0 order) - lint: check req.Body.Close() in openrouter client; remove unused codename, firstLine, envWithAPIKey Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Refactors the app by extracting debate orchestration into a new internal/engine synchronous state machine, making the TUI a thin adapter and enabling additional frontends (web/headless) to share the same core logic.
Changes:
- Added
internal/enginepackage withEngine,Actiontypes, format/prompt/tool/session abstraction interfaces, and extensive tests. - Updated TUI rendering/tests to consume engine phases/orders and refreshed simulation golden snapshots accordingly.
- Improved OpenRouter transport body close error handling and adjusted main to use a setter for swapping APIs.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/tui/view.go | Updates roster/stats rendering to align with new engine/config usage. |
| internal/tui/model.go | Adds SessionLoader override plumbing and introduces SetAPI for main/cassettes. |
| internal/tui/layout.go | Switches phase typing for round indicator and sources model colors via engine format. |
| internal/tui/model_test.go | Updates tests to work with engine phases and adds an engine prompt override test. |
| internal/sim/testdata/snapshots/smoke_test.golden | Snapshot updated for new roster ordering/rendering. |
| internal/sim/testdata/snapshots/round_summary.golden | Snapshot updated for new roster ordering/rendering. |
| internal/sim/testdata/snapshots/round_automod.golden | Snapshot updated for new roster ordering/rendering. |
| internal/sim/testdata/snapshots/round.golden | Snapshot updated for new roster ordering/rendering. |
| internal/sim/testdata/snapshots/loading_topic.golden | Snapshot updated for new roster ordering/rendering. |
| internal/sim/testdata/snapshots/init.golden | Snapshot updated for new roster ordering/rendering. |
| internal/sim/testdata/snapshots/complete.golden | Snapshot updated for new roster ordering/rendering. |
| internal/openrouter/client.go | Preserves body-close errors when request body read succeeds. |
| internal/engine/tools.go | Introduces ToolRegistry abstraction and default registry wrapper. |
| internal/engine/state.go | Adds engine Phase and structured Activity events. |
| internal/engine/session.go | Adds SessionWriter abstraction with default filesystem implementation. |
| internal/engine/prompt_helpers_test.go | Moves/updates prompt helper tests into the engine package and expands cases. |
| internal/engine/prompt.go | Adds PromptBuilder + default implementation and migrates prompt context builders. |
| internal/engine/format_test.go | Adds tests for the default debate format and model registry behavior. |
| internal/engine/format.go | Adds Format/DebateStructure/ModelRegistry abstractions and DefaultFormat. |
| internal/engine/engine_test.go | Adds broad engine state machine test suite. |
| internal/engine/engine.go | Implements the engine state machine, actions, discovery loop, and persistence hooks. |
| internal/engine/context.go | Adds forward-looking ContextGatherer interface. |
| internal/engine/action.go | Adds typed Action values for frontends to execute async work. |
| go.mod | Removes an indirect testify requirement entry (dependency is still used directly elsewhere). |
| cmd/warroom/main.go | Swaps direct API field mutation for Model.SetAPI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Preset implements DebateStructure interface (RoundCount, RoundName, SpeakingOrder, MaxTokens, UseTools) — bridges preset system with engine - PresetFormat composes DebateStructure + config-backed ModelRegistry, enabling any preset to be used as an engine.Format - TUI uses engine.Phase type instead of duplicated string constants - TUI uses engine.Build* context builders instead of local duplicates (deleted tui/context_builders.go) - All hardcoded round counts (5, 6) replaced with Preset.RoundCount() - All hardcoded max tokens replaced with Preset.MaxTokens() - View uses Preset.RoundName/SpeakingOrder instead of direct map/config access - Engine no longer imports config package (only format.go does for concrete ModelRegistry implementations) - Engine builds speakingOrderByRound from Format instead of leaking config.SpeakingOrderByRound - Added preset_test.go: registry, DebateStructure contract, helpers - Added PresetFormat tests in format_test.go https://claude.ai/code/session_01GMQzCUHDQP4NQAQwXVhBaP
- Engine as single backend: TUI always uses Engine for debate flow; removed all 'if m.Engine != nil' branches and legacy-only handlers in Update/startPhase. - SessionWriter: add ResumableSessionWriter with SetDir(); HandleSessionLoaded uses it instead of type-assert to DefaultSessionWriter. - Format: add VerdictTitle, VerdictSynthesisLabel, VerdictVoteLabel; Engine finishDebate uses them and performs R4 synthesis garbage cleanup before verdict. - PresetPromptBuilder: new engine.PromptBuilder impl from preset templates; PresetFormat extended with verdict labels via NewPresetFormatWithLabels. - TUI: NewModel builds Engine with PresetFormat, PresetPromptBuilder, DefaultSessionWriter, DefaultToolRegistry; syncStateFromEngine maps engine state to model; runAction maps engine actions to tea.Cmd. - Remove dead code: ContextGatherer (context.go), legacy helpers (sessionDir, ensureSessionDir, discoveryStartCmd, finishDiscovery, runCurrentModelCmd, finalizeRoundAndAdvance, systemPrompt/roundPrompt, retryWithoutToolsCmd, appendDebugLog, addActivity/addModelActivity, firstNLines, trunc), Model.SystemPromptFunc/RoundPromptFunc/ShortRetried. - Sim: NewModelWithEnv(opts, env) builds full Model with Engine; sim tests use it. openrouter.APICaller wraps OpenRouterAPI as ChatCaller. - Update sim view snapshot goldens for Engine-driven flow. Co-authored-by: Cursor <cursoragent@cursor.com>
…tart screen, better AskUser - AskUser: always-visible "Other" text input (no extra click), auto-select on typing, sorted map iteration for deterministic output - Spinner: bootstrap tick chain in runAction(), widen phase guard to all active phases (discovery, smoke test, round, summary, automod) - Streaming: new CallWithToolsStreaming using go-openai's streaming API, throttled preview events, 3-line rolling preview in activity feed, safe fallback to non-streaming on first-iteration errors - Starting screen: double-border banner, colored model lineup, round pipeline, narrow terminal fallback (<80 cols) - Audit fixes: UTF-8 safe truncation, bounds-checked SpeakingOrder access, correct tool call index collection, dead code removal
…skUser Bring discovery/interview phase to parity with debate rounds: - Engine emits structured Activity entries (Round=-1, ModelKey="opus") throughout the discovery flow — calling/done/system events - Add CallDiscoveryStepStreaming for real-time streaming preview while Opus thinks (reuses streamOneCall, falls back on error) - Wire streaming in TUI when LiveTicks=true; sim uses non-streaming path - Discovery feed renders "DISCOVERY" section header with turn progress (turn X/5) instead of model position - Inline AskUser: activity feed stays visible above questions with a separator; falls back to full-screen on narrow terminals (<15 rows) - Header shows "Discovery — Turn X/5" instead of blank center - Regenerated golden snapshots for new header text
- Update preset_test.go to expect 4 presets (added Polymarket) - Fix unchecked resp.Body.Close() error in polymarket client - Convert if-else chain to tagged switch in session VoteTally
- Replace duplicate Simpson's paradox (#4) with sunk cost fallacy trap - Remove answer-revealing constraints from #0, #1, #2 (model must derive insights) - Rebalance dominated options in #13 (cloud costs) and #14 (prioritization) - Add ethical dimension to #6 (government censorship client) - Add data ambiguity to #10 (contested Phase 1 biomarker subgroup)
Core resilience: - #1: Add React ErrorBoundary wrapping the app — render errors now show a styled fallback with reload button instead of a white screen - #2: Wire onUpdateDownloaded from preload into useDebate state so the renderer can display an update-available banner - #4: Remove dead submitTopic from useDebate hook and DebatePage - #5: Add backend crash detection — main process forwards Go exit event via IPC, preload exposes onBackendExited, useDebate sets connected=false and shows error banner when backend dies - #8: Fix route param changes being silently ignored — remove stale connectedRef guard; useEffect now disconnects previous session and reconnects on topic/resume change - #11: Fix O(n²) activity scan per stream tick — walk backwards and break on first match instead of .map + .slice().some() per element - #19: Add beforeunload handler during active debates to prevent accidental window close / data loss Accessibility: - #6: Add ARIA attributes across all components — role="alert" on ErrorBanner, role="status" + aria-label on GlowDot, role="complementary" on ModelRoster, role="progressbar" on progress bars, aria-label on textarea and dialog, aria-checked on QuestionStep options - #12: Add focus management to AskUserOverlay — saves previous focus, auto-focuses first interactive element on mount, restores focus on unmount, implements keyboard focus trap (Tab/Shift+Tab) - #14: Surface JSON parse errors in AskUserOverlay — shows visible error state with dismiss button instead of silently fabricating a question - #15: Fix partial answer submission — check raw answers array for all-empty instead of checking formatted string against "(no answer)" UX polish: - #3: Delete dead LoadingState component (zero imports) - #7: Lower noise-overlay z-index from 50 to 1 to avoid stacking conflict with titlebar drag region - #9: Add "Open Folder" button to SessionListPage using the existing but previously unused openSessionsFolder preload API - #10: Extract renderTranscriptEvent and roleBadge as pure functions outside DebateDashboard component to avoid recreation on every render - #13: Replace hardcoded Tailwind colors in StatusBadge with theme tokens (text-success, text-error, text-warning) - #16: Add window.confirm() before shutdown to prevent accidental cancellation of in-progress debates - #18: Add refresh() to useSessionList hook, wire refresh button and visibilitychange auto-refresh to SessionListPage - #21: Remove defaultOpen from transcript Collapsibles in SessionDetailPage — long transcripts now load collapsed https://claude.ai/code/session_01QPxpz4R7j4MFwGAqLEkwpb
Что нового для пользователя
Архитектура «движок + интерфейс»
Вся логика дебатов вынесена в отдельный модуль-движок. Интерфейс (TUI) теперь лишь отображает то, что движок решает. Это значит, что в будущем можно будет подключить веб-интерфейс, REST API или запуск без экрана — вся логика останется одной и той же.
Живая лента активности во время интервью с Opus
Раньше, пока Opus формулировал вопросы или дорабатывал тему, экран был практически пустым — только строка статуса и тусклые логи. Теперь:
Вопросы Opus не закрывают экран
Раньше, когда Opus задавал уточняющие вопросы, вся лента исчезала и оставалась только форма ответа. Теперь экран делится пополам: сверху — лента с контекстом, снизу — вопросы. Вы видите, о чём шла речь, пока отвечаете. На маленьких терминалах всё по-прежнему показывается в полный экран.
Системные события в ленте
Теперь в ленте явно отмечается: