Codebase Architecture · Living Progress Plan

Codebase Rewrite

Incrementally reduce this codebase's largest offenders — starting with dashboard/server.py, and extending to the CLI, headless, and websocket layers — to composition roots. Every side effect crosses an injected Interface; every behavior lives in a focused Object or pure function; every extraction is pinned by tests before it moves.

Created 2026-07-29 Owner: dashboard teams Method: strangler refactor No route-contract changes

Progress

0 / 55
Interface/Object slices extracted
55 active rows remain in this document.
10,486server.py lines at baseline
269top-level functions
233top-level assignments
1,062DashboardHandler lines
5,625test_server.py lines
532 pass7 skip; 6 dependency failures
First gate: make the baseline suite green. The six failures observed on 2026-07-29 are missing PyMuPDF, Pillow, and openpyxl in the dashboard venv. Refactoring does not begin until failures reliably mean regressions.

Operating rules

  1. Program to the Interface. Domain services import ports, never concrete adapters.
  2. Wire once. Concrete construction belongs in server.py or a dedicated application factory.
  3. TDD is mandatory. No production Interface or Object is written before its failing test. Work proceeds Red → Green → Refactor.
  4. Characterize before moving. Characterization tests pin public output, errors, caching, locking, and failure behavior before legacy code moves.
  5. Types are executable contracts. Python boundaries use strict Pydantic models; new browser code uses strict TypeScript.
  6. Keep pure code simple. Pure transforms remain functions; do not create an Interface without a boundary.
  7. Side effects require ports. Network, process, filesystem, database, clock, logging, metrics, and notifications are injected.
  8. One slice per change. Interface, Object, adapter, tests, wiring, and compatibility shim travel together.
  9. No live dependencies in unit tests. Use small fakes that implement the same port contract.
  10. Preserve HTTP contracts. Status, headers, JSON shapes, timeouts, and route names stay stable unless separately approved.
  11. Delete completed detail. Once a row meets its exit test, remove it from this ledger. Git is the completion history.
The plan intentionally gets shorter. Do not grow a permanent “Completed” table. Update the baseline total only when a genuinely new boundary is discovered; otherwise remove finished rows and let the progress calculation advance.

Strict typed contracts and test-first development

Non-negotiable: Pydantic v2 is mandatory for Python data crossing an Interface, process, persistence, or HTTP boundary. All new frontend code is TypeScript. The type checker and the tests are merge gates, not advisory tools.

Python contract policy

ABCs describe behavior; Pydantic models describe data. Commands, results, API requests/responses, repository records, configuration, events, and adapter payloads use named models derived from one strict base:

from pydantic import BaseModel, ConfigDict

class StrictModel(BaseModel):
    model_config = ConfigDict(strict=True, extra="forbid", frozen=True)

Static checking runs with pyright --project dashboard/pyrightconfig.json, where typeCheckingMode = "strict". Runtime Pydantic validation complements static checking; neither replaces the other.

TypeScript contract policy

TDD order for every ledger row

  1. RED: write the consumer behavior test and run it. Record the expected failure; import/contract absence counts as red for a new extraction.
  2. Add Characterization tests around the legacy seam so the behavior being preserved is explicit.
  3. Add Shared contract tests that every concrete adapter and fake must pass.
  4. Add Negative validation tests proving malformed types, missing fields, extra fields, and invalid variants are rejected.
  5. GREEN: add only enough typed Interface/Object code to make the focused tests pass.
  6. REFACTOR: remove duplication and improve names while the tests and type checkers stay green.
  7. Run the focused suite, complete Python suite, complete browser suite, static type checks, and coverage gate.

Required test layers

LayerPurposeRequired evidence
Characterization testsFreeze current route/service behavior before movementSuccess, error, timeout, cache, and concurrency cases
Domain unit testsDrive service behavior through fake portsNo network, filesystem, process, clock, or database access
Shared contract testsHold real adapters and fakes to one behavioral contractParameterized suite runs against every implementation
Negative validation testsProve strict Pydantic/runtime TypeScript validationWrong, missing, extra, and ambiguous data fails closed
Adapter integration testsVerify serialization and external protocol mappingFixture servers/files/processes only; no production systems
Composition testsVerify every port is wired once to a valid concreteApplication builds and all routes register without serving
HTTP/browser testsProtect public routes, headers, JSON, navigation, and renderingBlack-box request and DOM assertions
New or extracted domain/application modules require 100% branch coverage. Coverage does not prove test quality: the test must also fail when the implemented behavior is removed or inverted. Adapter exceptions must be narrow and recorded in the decision log.

Target architecture

dashboard/
  server.py                 # construct adapters, start threads/server, compatibility exports
  http/                     # route registry, controllers, HTTP/WebSocket adapters
  agents/                   # Letta gateway, directory, messaging, agent health
  server_management/        # health probes, logs, SSH, restart orchestration
  model_usage/              # usage sources, history, provider health/failover
  intake/                   # scanners, classification, staging, dispatch, Trainer
  finance/                  # expense/receipt/report repositories and services
  voice/                    # existing transcription/cleanup pipeline
  tests/                    # mirrors the production domains above

The directory names are targets, not a mandate to move everything at once. A slice may begin as one flat module when that keeps the change reviewable.

Definition of done for the rewrite

Active Interface/Object ledger

Each work row is one reviewable extraction. The checkbox is deliberately visual rather than browser-persistent: completion is shared by deleting the row in source control, not by storing private state in one browser.

Status Interface / Port Object / Adapter Current seam Exit test
0 — Test and composition foundation
ITestEnvironmentDashboardTestEnvironmentVenv capability setup and test isolationFull suite is green or optional capabilities skip explicitly
Pydantic boundary contractStrictModelUntyped dicts, tuples, and optional fields crossing seamsStrict/frozen/extra-forbid models cover every new boundary
IPayloadCodecPydanticPayloadCodecManual JSON decoding, coercion, and response shapingNegative contract tests reject wrong and extra fields
ITypeCheckGatePyrightAndTypeScriptGateDashboard Python/JS currently outside strict project type gatesStrict Pyright and tsc --noEmit pass in one command
IContractTestSuiteSharedContractTestSuiteAdapter and fake behavior can drift independentlyEvery port implementation runs the same contract suite
IClockSystemClock, FixedClocktime.time(), datetime.now(), TTLsTime-sensitive services run against FixedClock
ICommandRunnerSubprocessCommandRunnerDirect subprocess.run/PopenServices assert commands without spawning processes
IHttpTransportUrllibHttpTransportDirect urllib.request callsTimeout, HTTP error, and JSON contracts have adapter tests
IActivityRecorderJsonActivityRecorder, NullActivityRecorderJSON log helpers and cross-cutting eventsNo service imports a concrete log writer
1 — HTTP boundary
IRouteControllerRouteRegistrydo_GET/do_POST condition chainsEvery API route is registered and duplicate routes fail startup
IRequestBodyReaderHttpRequestBodyReaderBody length, decoding, JSON parsingMalformed/empty/oversized bodies have contract tests
IResponseWriterDashboardResponseWriterjson_response, error_response, cache headersStatus, headers, and response shapes match characterization tests
IStaticAssetStoreLocalStaticAssetStoreHERE/REPO_ROOT file resolution and servingTraversal, type, missing-file, and no-cache tests pass
ITerminalSessionPtyTerminalSessionPTY spawn, resize, reap, WebSocket bridgeFrame/session lifecycle tested without a real shell
IDashboardApplicationDashboardApplicationStartup threads, caches, handler globalsserver.py only constructs and starts the application
2 — Letta agents
ILettaGatewayUrllibLettaGatewayAgents, thoughts, tool calls, and writes; model reads migrated behind AgentModelOptionsService on 2026-07-30 and raw message retrieval migrated on 2026-07-31No agent service constructs a Letta URL; remaining callers keep this row active
IAgentDirectoryCachedAgentDirectoryLETTA_AGENTS, discovery, cache refreshRegistry ordering and cache behavior are unit tested
IAgentMessageServiceAgentMessageService/api/test and message shapingReset/send/fallback behavior uses a fake gateway
IAgentHealthProbeAgentHealthProbeRequired tools, SDK endpoint, send errorsHealth matrix is deterministic without live agents
IAgentPromptRunnerLettaCodePromptRunnerHeadless CLI command and prompt validationPermission mode, timeout, and final-result tests stay pinned
IAgentSystemMessageRepositoryFileAgentSystemMessageRepositoryPer-agent system message filesMissing and unsafe paths fail closed
3 — Server Management
IHealthProbeHttpHealthProbe, TcpHealthProbe, CommandHealthProbeHEALTH_CHECKS and check variantsAdding a probe requires an adapter plus one registry entry
IServerRegistryServerRegistrySERVERS config and validationInvalid dependencies/check names fail at construction
IServerHealthServiceServerHealthServiceStatus computation, dependency roll-up, cacheAll green/yellow/red transitions have clock-controlled tests
IServerControllerServerControllerRestart/start command dispatchUnknown, blocked, starting, and success outcomes are tested
IServerLogSourceFileServerLogSource, RemoteServerLogSourceTailing, filters, remote Letta log pullSequence/filter behavior runs against fixtures
ISshGatewayOpenSshGatewaySSH health, Windows/WSL commandsNo health/controller object assembles raw SSH commands
IAvailabilityTrackerAvailabilityTrackerStarting windows and down duration globalsState transitions use injected clock and isolated state
IPcMetricsSourceLocalPcMetricsSource, SshPcMetricsSourcePC monitor extraction, cache, rate calculationParsing is pure and collection uses fake sources
4 — Model usage and provider health
IModelUsageSourceCodexUsageSource, ClaudeUsageSource, AntigravityUsageSourceToken extraction and remote source registryEach source passes one shared contract suite
IModelUsageServiceModelUsageServicemodel_stats, labels, classificationService contains no provider-specific condition chain
IUsageHistoryStoreJsonUsageHistoryStoreSamples, pruning, burn rate, slow leak historyCorrupt/missing history degrades safely
IProviderHealthMonitorChatGptProviderHealthMonitorZero-token polling and fleet error flagsOne probe updates the complete provider fleet deterministically
IProviderFailoverStrategyChatGptTokenFailoverStrategyStandby headroom, swap command, cooldownDecision logic and command adapter are independently tested
5 — Scanner and intake workflow
IScannerDeviceWindowsWiaScannerDevice selection, locking, WIA invocationScanner choice never depends on enumeration order
IScannerRegistryScannerRegistryWindow/Freezer configurationUnknown and duplicate devices fail at construction
IScannerDiagnosticsHpScannerDiagnostics2,232-line scanner/fix/diagnostic regionEvery LED and remediation message has fixture tests
IPrinterRepairServiceDeskJetPrinterRepairServicefix_deskjet_printerRepair never claims success without device evidence
IDocumentClassifierIntakeFacadeClassifierFacade invocation and classification shapingReady/busy/offline/error cases use one typed result
IDocumentStagerLocalAndRemoteDocumentStagerLocal copy and Win10 mirrorLocal authority and nonfatal mirror failure are pinned
IAgentConversationFactoryMazdaConversationFactoryPer-scan Letta conversation creationConversation failure stops dispatch without side effects
IDocumentDispatcherMazdaDocumentDispatcher482-line scan message and notify callMessage builder is pure; gateway owns delivery
ITrainerNotifierDetachedTrainerNotifier, NullTrainerNotifierTrainer command and detached spawnTests cannot spawn a real Trainer by construction
IIntakeEventStoreJsonIntakeEventStoreRecent report pointer and expense-stored eventsMerge, dedupe, pruning, and corrupt JSON are tested
IDocumentIntakeServiceDocumentIntakeServiceprocess_scanned_document, process_pdf_documentWorkflow is an injected coordinator with no module globals
6 — ROL Finance
IExpenseRepositoryMySqlExpenseRepositoryExpense lookup, category changes, notesServices never import DB connection helpers
IReceiptRepositoryFileReceiptRepositoryReceipt mounts, index, matching, URL mappingMount/index policy has contract tests and injected clock
IReportRepositoryFileReportRepositoryReport aliases, discovery, status, row lookupPath safety and ambiguity rules are isolated
IReportRowWriterHtmlReportRowWriterStatic row category/color updatesWrites are idempotent and fixture snapshots stay stable
IExpenseCategorizationServiceExpenseCategorizationServicerecategorize_expense and undo orchestrationDB, report, taxonomy, undo ports are constructor-injected
IReceiptLookupServiceReceiptLookupServiceLookup/presence/source-document resolutionEvery matching tier is tested without real files or DB
ISupportingDocumentServiceSupportingDocumentServiceSupporting-document lookup/open flow. 2026-08-06 startup-repair checkpoint: a broken uncommitted composition-root import referenced the absent supporting_document_application.py, so the dashboard crashed before binding port 8765. Restored the existing in-server compatibility implementation and removed the dead adapter wiring: server.py 11,126→11,066 (−60, −0.539% observed working-file delta; slice-attributable −60), 11,066 vs 10,486 baseline (+580, +5.53%). Focused supporting-document suite: 21 passed. The full paired run had one unrelated pre-existing annotation fixture failure. Next deletion target remains the legacy in-server flow after a real typed module is added and tested.Uses existing annotation port and injected repositories
IFinanceReportServiceFinanceReportServiceRecent intake/report and receipt-only HTML. 2026-08-02 partial: equivalent duplicate-row projection moved behind pure recent_intake_view.py policy; live Window scan 561/1519 now collapses without merging different merchants. server.py 11,046→11,050 (+4, +0.036%); 11,050 vs 10,486 baseline (+564, +5.38%). Last three commits: +90, +53, 0 lines (net +143); HEAD→worktree +4. Non-negative because this slice adds composition-root wiring for the extracted policy; next deletion target is the remaining build_recent_intake_html renderer into FinanceReportService.Rendering is separate from data acquisition 2026-08-12 Toyota receptionist slice: frontend transcript state and injected cheap-model intent strategy are complete, but this is a voice/receptionist boundary rather than a FinanceReportService extraction. Server composition wiring added voice/receptionist.py and /api/receptionist-intent; HEAD→worktree is 11,359→11,234 lines (−125 observed, including unrelated concurrent intake/report deletion), with +12 lines attributable to the receptionist import/route. Focused JS 50 passed; focused Python 26 passed.
IRecentScanRepositoryMySqlRecentScanRepositoryRecent scans and month status queriesQuery results map to typed records behind one port
IVendorReviewServiceVendorReviewServiceVendor keys, pending review, set vendorValidation, categorization, and persistence are isolated

Established examples — do not move backward

These are outside the active count because they already demonstrate the intended shape.

ICategoryTaxonomyStatic, MySQL, and fallback implementations in category_taxonomy.py.
Category undo portsRepository and store contracts with compare-and-swap orchestration in category_undo.py.
IExpenseDocumentAnnotationServiceFormat strategies and composition factory in document_annotation.py.
Voice pipelineTranscription, cleanup, Letta adapter, pipeline, and edge-tts synthesis strategy separated under voice/.
Statement reviewReview behavior already isolated in statement_review.py.
Frontend GoF layerInterfaces under js/abstract/, concretes under js/implementation/.

One-slice workflow

  1. Choose exactly one active row; record its current callers and mutable globals.
  2. Write the failing consumer test first, run it, and save the RED command plus failure.
  3. Add characterization, shared-contract, and negative-validation tests before production code.
  4. Define the smallest fully typed ABC and strict Pydantic models in the owning domain. The abstract module imports no concrete adapter.
  5. Implement only enough typed behavior for GREEN, including a minimal fake that passes the same contract tests.
  6. Inject the port into the service/controller; construct the concrete only at the composition root.
  7. Leave a temporary forwarding function in server.py when compatibility requires it.
  8. Move relevant tests from test_server.py into the matching domain test file.
  9. Refactor with tests green, then run Pyright, TypeScript, focused tests, full suites, coverage, and the relevant manual smoke check.
  10. Record current line counts, delete this work row, and commit the shrinking plan with the implementation.

Required evidence in each handoff or PR

Slice:
Observed RED:
RED command and failure:
Interface:
Pydantic model(s):
TypeScript type(s):
Concrete Object(s):
Callers migrated:
Compatibility shim:
Focused tests:
Contract/negative tests:
Pyright result:
TypeScript result:
Branch coverage:
Full Python result:
Full JavaScript result:
server.py lines before/after:
test_server.py lines before/after:
Ledger row removed:

Decision log

2026-08-03 SSH compatibility slice: The Windows 10 Host SSH probe now routes through an injected ISshGateway port and OpenSshGateway Adapter, with ConfiguredIdentityStrategy owning deployment-specific key selection and ICommandRunner isolating subprocess execution. This replaces the previous target-specific command branch and makes the credential policy testable without a network. The focused gateway/server suite is green (368 tests), and all live dashboard instances now return CONNECTED — DESKTOP-SHDBATI. The working file measured 11,061 lines / 518,879 bytes after this slice versus 11,046 / 518,026 at HEAD: +15 lines, +853 bytes, +0.136%; the observed +15 includes +11 lines from the already-present SSH fallback patch and unrelated working-tree edits, so the slice-attributable delta is +4 lines (the compatibility shim/import). Current count is 575 above the 10,486 historical baseline (+5.48%). Last three commits are +90, +53, 0 lines; HEAD→worktree is +15 lines / +853 bytes. The non-negative delta is explained by introducing the port/adapter and its tests before deleting the compatibility shim; the next deletion target is ssh_test forwarding and the remaining raw SSH configuration in server.py.

2026-07-29
Use an incremental ports-and-adapters rewrite; do not replace the stdlib HTTP server or change routes as part of extraction.
2026-07-29
Interfaces are required at meaningful boundaries and all cross-cutting side effects. Pure transforms remain functions.
2026-07-29
Completed work rows are deleted instead of archived here, so this page becomes smaller as the rewrite advances.
2026-07-29
Begin with the green test baseline, then model usage as the pilot production slice.
2026-07-29
Adopt strict Pydantic v2 models for every Python boundary and strict TypeScript for all new frontend work; static and runtime validation are both required.
2026-07-29
Adopt strict TDD: witness RED before production code, reach GREEN minimally, then refactor; new domain/application modules require 100% branch coverage.
2026-07-30
Completed the model-read sub-slice of ILettaGateway: RED was pytest -q tests/test_letta_gateway.py failing because the agents boundary did not exist; GREEN was 14 focused gateway/model-option tests. Added strict frozen Pydantic contracts, FakeLettaGateway, UrllibLettaGateway, composition-root wiring, and a temporary agent_model_payload forwarding shim. The ledger row remains because messages, thoughts, tool calls, and write paths are not migrated.
2026-07-31
Completed the raw-message-read sub-slice of ILettaGateway: RED was pytest -q tests/test_letta_gateway.py failing to import the not-yet-created LettaAgentMessage; GREEN was 34 focused gateway tests plus 360 test_server.py tests. Added a strict immutable normalized message contract, shared fake/urllib behavior, fail-closed envelope and field validation, URL ownership in UrllibLettaGateway, and a temporary letta_messages forwarding shim. Installed/configured Pyright and branch coverage for the rewritten agents/ boundary; strict checking reports zero errors and the focused suite covers 229 statements and 90 branches at 100%. GoF dependency tests now prohibit application-service imports of concrete adapters and require the composition-root gateway plus compatibility shim to expose ILettaGateway explicitly; UrllibLettaGateway is constructed only in server.py. The public activity-view shapes and 25-second timeout remain unchanged. The ledger row remains for roster, thought/tool-call application services, and write paths.