Skip to content

refactor: extract engine package to separate frontend from backend - #4

Merged
takhir-iota merged 15 commits into
mainfrom
claude/refactor-architecture-layers-U8mFG
Feb 12, 2026
Merged

takhir-iota merged 15 commits into
mainfrom
claude/refactor-architecture-layers-U8mFG

Conversation

@diyor28

@diyor28 diyor28 commented Feb 9, 2026 •

Copy link
Copy Markdown
Contributor

Что нового для пользователя

Архитектура «движок + интерфейс»

Вся логика дебатов вынесена в отдельный модуль-движок. Интерфейс (TUI) теперь лишь отображает то, что движок решает. Это значит, что в будущем можно будет подключить веб-интерфейс, REST API или запуск без экрана — вся логика останется одной и той же.

Живая лента активности во время интервью с Opus

Раньше, пока Opus формулировал вопросы или дорабатывал тему, экран был практически пустым — только строка статуса и тусклые логи. Теперь:

  • Видно, что происходит — в ленте появляются записи «Opus думает…» со спиннером, а после ответа — краткая выдержка из того, что он написал.
  • Стриминг в реальном времени — пока Opus генерирует ответ (30–120 сек), вы видите текст по мере его появления, а не ждёте пустой экран.
  • Прогресс по шагам — в заголовке показывается «Discovery — Turn 2/5», чтобы было понятно, сколько шагов осталось до начала дебатов.

Вопросы Opus не закрывают экран

Раньше, когда Opus задавал уточняющие вопросы, вся лента исчезала и оставалась только форма ответа. Теперь экран делится пополам: сверху — лента с контекстом, снизу — вопросы. Вы видите, о чём шла речь, пока отвечаете. На маленьких терминалах всё по-прежнему показывается в полный экран.

Системные события в ленте

Теперь в ленте явно отмечается:

  • когда Opus задал вопросы и на каком шаге
  • когда тема была обновлена (и сколько символов)
  • когда бриф зафиксирован и дебаты начинаются

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
@coderabbitai

coderabbitai Bot commented Feb 9, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This 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

Cohort / File(s) Summary
Application Entry & Dependencies
cmd/warroom/main.go, go.mod
Refactors API access to use model.Engine.API() and model.Engine.SetAPI() instead of direct field assignment; updates Go toolchain to 1.24.7 and removes indirect testify dependency.
Engine State & Phases
internal/engine/state.go, internal/engine/action.go
Introduces Phase type with 13 constants (init, api_key_setup, topic_input, etc.), Activity struct for event logging, and Action interface with 11 concrete action types (CallAPIAction, CallDiscoveryAction, BatchAction, ErrorAction, etc.) for asynchronous work.
Engine Core & Lifecycle
internal/engine/engine.go
Adds comprehensive 874-line Engine struct implementing Debate interface with debate state machine, lifecycle handlers (Init, HandleAPIResult, HandleDiscoveryResult, HandleTopicLoaded, etc.), discovery flow, topic updates, and extensive logging and activity tracking.
Format & Prompt Abstractions
internal/engine/format.go, internal/engine/prompt.go
Introduces Format interface and DefaultFormat implementation for debate structure/model registry; adds PromptBuilder interface with DefaultPromptBuilder for round-specific system/user prompts, research dossiers, and prior content synthesis; includes helpers (BuildResearchDossier, BuildPriorRoundContent, etc.).
Session & Tool Management
internal/engine/session.go, internal/engine/tools.go, internal/engine/context.go
Adds SessionWriter interface with DefaultSessionWriter for filesystem-backed persistence; ToolRegistry interface with DefaultToolRegistry for tool definitions and execution; ContextGatherer interface and ContextItem struct for contextual data gathering.
Engine Tests
internal/engine/engine_test.go, internal/engine/format_test.go, internal/engine/prompt_helpers_test.go
Comprehensive test coverage (754 lines for engine_test.go) verifying initialization paths, topic handling, discovery flow, round progression, session resume, state snapshots, cost tracking, activity lifecycle, and action dependency propagation; tests for format and prompt helpers.
TUI Layer Updates
internal/tui/layout.go, internal/tui/model_test.go
Replaces config imports with engine imports; changes roundStatusIndicator signature from string phase to engine.Phase; updates model color lookup via DefaultFormat.ModelColor; refactors Model tests to use Engine-centric API (Engine.API(), Engine.State()) and new action types.
Test Snapshots
internal/sim/testdata/snapshots/init.golden
Updates elapsed time in snapshot header from 0s to 1s; all other content remains identical.

Sequence Diagrams

sequenceDiagram
    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
Loading
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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A state machine hops through debate,
Formats and prompts align their fate,
Discovery tunnels, rounds take their turn,
Sessions persist, and lessons we learn,
The Engine runs swift—no need to wait!


Note

🎁 Summarized by CodeRabbit Free

Your 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 @coderabbitai help to get the list of available commands and usage tips.

claude and others added 6 commits February 9, 2026 10:33
- 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>
@diyor28
diyor28 requested a review from Copilot February 9, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/engine package with Engine, Action types, 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.

Comment thread internal/engine/engine.go Resolved
Comment thread internal/engine/engine.go Resolved
Comment thread internal/engine/engine.go Resolved
Comment thread internal/engine/tools.go Resolved
Comment thread internal/engine/tools.go Resolved
Comment thread internal/tui/view.go Resolved
claude and others added 8 commits February 9, 2026 19:25
- 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

@takhir-iota takhir-iota left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@takhir-iota
takhir-iota merged commit e9eb4c4 into main Feb 12, 2026
4 checks passed
@takhir-iota
takhir-iota deleted the claude/refactor-architecture-layers-U8mFG branch February 12, 2026 06:03
diyor28 added a commit that referenced this pull request Feb 14, 2026
- 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)
diyor28 pushed a commit that referenced this pull request Feb 15, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants