Phased build-out of Substrate. This file remains a roadmap, but implemented phases are rewritten to match repository HEAD instead of earlier pre-rename drafts.
cmd/substrate/main.go
internal/
domain/ # Session / Plan / Task / Review domain types
repository/
interfaces.go # repository interfaces
sqlite/ # SQLite implementations
service/ # state machines, domain rules, transacter-wrapped repos
orchestrator/ # planning, implementation, review, foreman, resume, session registry
adapter/
linear/
manual/
gitlab/
github/
glab/
sentry/
ohmypi/
claudeagent/
codex/
app/ # adapter + harness wiring, remote detection
event/ # persisted channel bus
gitwork/ # git-work integration and workspace helpers
config/ # YAML config + secret hydration
tui/ # Bubble Tea UI
views/ # 60+ view and overlay files (incl. overlay_add_repo.go)
components/ # bunny, input, confirm, overlay frame, toast, etc.
bridge/omp-bridge.ts
migrations/
001_initial.sql
002_agent_sessions_canonical.sql
003_omp_session_meta.sql
004_sub_plan_planning_round.sql
005_review_artifacts.sql
~/.substrate/state.db
Shipped.
What exists today:
- typed config loading in
internal/config/ - global path helpers (
GlobalDir,GlobalDBPath,ConfigPath,SessionsDir) - defaulting + validation for
commit,plan,review,harness,adapters,foreman, andrepos - migration runner and
migrations/001_initial.sql cmd/substrate/main.gostartup that loads config, runs migrations, wires repos/services/bus/adapters/harnesses, and starts the TUI- secret hydration via
config.LoadSecrets
Current config model is richer than the original plan:
harness.defaultplus per-phase overrides forplanning,implementation,review, andforeman- adapter config blocks for
ohmypi,claude_code,codex,linear,glab,gitlab,github, andsentry foreman.question_timeout- per-repo
repos.<name>.doc_paths
Shipped, with naming updated from older drafts.
Current domain/storage split:
- root aggregate is
domain.Session - orchestration record is
domain.Plan - per-repo plan slice is
domain.TaskPlan - repo-scoped harness run is
domain.Task - review is
domain.ReviewCycle+domain.Critique - questions are
domain.Question - liveness is
domain.SubstrateInstance
Storage still uses legacy table names:
work_itemsstoresdomain.Sessionagent_sessionsstoresdomain.Task
Current schema details worth preserving:
plans.faqJSON column exists and backs the Foreman FAQ flowquestions.proposed_answerexists for escalated-question UXcritiques.suggestionexistsagent_sessions.owner_instance_idpoints atsubstrate_instancessystem_eventspersists rawdomain.SystemEventrowsgithub_pull_requestsandgitlab_merge_requestsstore provider-native PR/MR records (migration 005)session_review_artifactslinks work items to provider artifacts with dedup on(workspace_id, work_item_id, provider, provider_artifact_id)sub_plans.planning_roundtracked which planning round last modified each sub-plan (migration 004; dropped in migration 008)- migration 006 migrates OMP-specific session metadata to genericresume_info- migration 007 adds plan supersede model (partial unique index on non-superseded plans) and
plan_idtoagent_sessionspointing atplans(id) - migration 008 drops
sub_plans.planning_round agent_sessions.resume_infotracks native harness session state as generic resume metadata
SQLite implementations live in internal/repository/sqlite/ and accept generic.SQLXRemote. resources.go still groups transaction-bound repos into a Resources bundle for tests / transactional construction.
Shipped.
Current services and names:
-
SessionService -
PlanService -
TaskService -
ReviewService -
QuestionService -
WorkspaceService -
InstanceService -
EventService -
GithubPRService -
GitlabMRService -
SessionReviewArtifactServiceImportant current behavior: -
SessionServiceenforces root-session uniqueness and lifecycle transitions -
TaskServiceowns task lifecycle plusSearchHistory/ interrupted-owner queries -
QuestionServicesupportsEscalateWithProposalandUpdateProposal -
PlanServiceowns sub-plan transitions andAppendFAQthrough the repo boundary -
all services use the
atomic.Transacter[repository.Resources]pattern: every call runs insideTransact(ctx, func(ctx, res) error { ... })closures, giving services consistent transaction boundaries and read-after-write consistency
This layer is already using the renamed Session / Task model; older WorkItemService / AgentSessionService wording is obsolete.
Shipped, but in its current mixed form.
Current reality:
domain.SystemEventis the persisted event shapeevent.Busis a channel-based pub/sub bus with topic filteringworktree.creatingis the default pre-hook event type- pre-hooks and post-hooks are supported, with default 30s hook timeouts
- dispatch is non-blocking; full subscriber buffers yield
ErrRetryLaterunless a drop handler is installed - adapter
OnEventretries up to 3 times before publishingadapter.errorsystem events and surfacing best-effort TUI warnings - work item adapters subscribe to all events and self-filter in
OnEvent - repo lifecycle adapters subscribe only to
worktree.createdandwork_item.completed
Also important: not all events currently flow through the bus. Planning persists lifecycle events directly through EventService.Create (not through the bus). Adapters persist review artifact events the same way.
Shipped.
Current behavior:
- workspace identity uses
.substrate-workspace - workspace discovery walks upward from cwd
- repo discovery looks for direct child directories containing
.bare/ - planning preflight warns on plain git clones and failed pulls
- discoverer pull cooldown (5 minutes) avoids redundant pulls within the same window
- pull failures are recorded but do not stop discovery
- planning creates
<workspace>/.substrate/sessions/<planning-session-id>/plan-draft.md - implementation creates feature worktrees through
git-work checkout
Partially shipped.
Current production-quality path:
ohmypiis the default harness and the only path with verified interactive continuation behavior across planning, implementation, review, and foreman flows.
Currently wired but still parity-limited:
claudeagentcodex
Current router behavior in internal/app/harness.go:
- each phase resolves a single harness from config
- missing binaries cause that phase to be unavailable rather than silently pretending parity
Resumecurrently reuses the implementation harness choice- diagnostics are surfaced for settings/TUI consumption
The important naming update here is that the implementation talks about harness phases and AgentHarness instances, not the old single-harness assumption.
What is true today:
- runtime path is
internal/adapter/ohmypi/ - package name is still
omp - readiness checks validate bridge availability and Bun requirements for source-bridge mode
- the bridge emits structured events consumed by the Go harness session wrapper
- startup and selection are wired
- binary presence is checked
- do not treat it as equal to oh-my-pi for interactive continuation semantics unless verified by tests/runtime evidence
- supports compact and native resume via a TypeScript bridge (
@anthropic-ai/claude-agent-sdk, same architecture as OMP bridge)
- same current status as Claude Code: selectable and wired, supports messaging via thread resume and native resume, but steering returns
ErrSteerNotSupportedand compact is not supported
Current config shape is YAML, not TOML:
harness:
default: ohmypi
phase:
planning: ohmypi
implementation: ohmypi
review: ohmypi
foreman: ohmypiShipped.
Current planning flow in internal/orchestrator/planning.go:
- transition root
Sessionfromingestedtoplanning - load
Workspace - run preflight (
Discoverer.PreflightCheck) - pull
main/worktrees best-effort - discover repos and metadata
- read workspace-root
AGENTS.md - create workspace-local planning session dir and draft path
- render planning prompt
- start harness planning session
- wait for draft file or completion
- parse/validate the draft
- run correction loop up to
plan.max_parse_retries - persist
PlanandTaskPlans - transition root
Sessiontoplan_review - persist planning events
Current naming details:
- planning is launched for a root
Session, not aWorkItemtype - parsed repo slices are
TaskPlan, notSubPlanin the domain model PlanningContextusesWorkItemSnapshotas a projection name, but it snapshots theSessionaggregate
Shipped.
Current implementation flow in ImplementationService:
- requires
PlanApproved - loads the root
Sessionand itsWorkspace - discovers repository paths before mutating root-session state
- transitions root
Sessiontoimplementing - pre-creates unique worktrees sequentially to avoid same-wave races
- builds waves from
TaskPlan.Order - executes tasks in a wave concurrently via
errgroup - creates a durable
Taskrow before launching each harness session - forwards harness events to the bus while the task runs
- transitions the root
Sessiontoreviewingorfailedat the end
Current event nuance:
worktree.creatingandworktree.createdgo throughevent.Buswork_item.implementation_startedand task start/complete/fail events go throughevent.BusviaImplementationService.publishEvent
Shipped in current form.
What exists now:
Foremanmanages a persistent foreman-phase harness session per planStartForemanCmdis triggered from the TUI after plan approval and during review-driven reimplementation loops- questions are serialized through the foreman worker queue
- high-confidence answers are persisted immediately and appended to
Plan.FAQ - uncertain answers are escalated with
Question.ProposedAnswer - the TUI can keep iterating with the foreman before calling
ResolveEscalated question_timeoutis configurable throughforeman.question_timeout; config default is"0"(documented as indefinite, but runtime falls back to 60 s)- the Foreman is stopped on implementation completion and restarted for follow-up feedback
- it counts as a live session in the TUI status bar
- the Foreman receives the full composed plan document as its system prompt context
What exists now:
- review is modeled as
ReviewPipeline.ReviewSession(session domain.Task) - a review harness session is started in foreman mode
- output is parsed for
CRITIQUE/END_CRITIQUEblocks orNO_CRITIQUES - correction-loop retries reuse the same live review session
- major/critical critiques trigger reimplementation decisions via the review result path
- review outcome events are published through the bus
- review sessions run in
SessionModeAgentwith a review-only role instruction - parse failures are treated as no critiques (no correction loop in agent mode)
The implementation orchestrator now owns the per-repo review loop:
AutoFeedbackLoopconfig flag (defaulttrue) gates automatic reimplementation after critique- review critiques are reused as feedback when re-triggering implementation
- native harness sessions are resumed via
ResumeFromSessionIDandResumeInfofor review reimplementation - escalated sub-plans are persisted as failed before the work item is routed to reviewing
- bridge answer timeout is handled through
ohMyPiSession.SendAnswer
Shipped.
Current behavior:
InstanceServiceowns instance CRUD, heartbeat updates, liveness checks, and stale cleanup- orphan reconciliation runs on TUI startup via
ReconcileOrphanedTasksCmd— tasks whose owner instance is absent or stale are transitioned tointerrupted Resumption.ResumeSessioncreates a newTaskagainst the sameTaskPlanand worktree- the interrupted task remains interrupted for audit purposes
- resume context includes the last 50 log lines from
~/.substrate/sessions/<old-task-id>.log AgentSessionResumedis published through the busAbandonSessionterminalizes an interrupted task as failed- superseded interrupted sessions are failed once the replacement task is durable
deleteOrFailPendingSessionis the shared cleanup path for failed startsSessionRegistry.AbortAndDeregisteris the idempotent live-session teardown pathDeleteSessionMsgcancels pipelines, aborts registry sessions, stops Foreman when needed, then deletes task/work-item rows and artifacts- graceful agent abort on quit:
q/ctrl+c/SIGTERMtrigger a confirmation dialog when agents are running
The old InstanceManager has been deleted. Instance reconciliation is now split between InstanceService (row ownership and heartbeat liveness) and the TUI startup orphan-reconciliation command.
Still roadmap-oriented, but the terminology should be read as follows:
- browsing creates root
Sessionrecords - adapters resolve selections into
domain.Session - manual creation remains a separate explicit path
The capability-driven browse contract already exists in internal/adapter/types.go and interfaces.go; remaining work is mostly UI semantics and provider-scope parity.
Mixed state:
- GitLab, GitHub, Linear, Manual, Glab, and Sentry adapters exist
- work-item tracker mutation and repo-lifecycle automation are already split into different adapter contracts
internal/app/remotedetectroutes lifecycle adapters by detected provider- browse/filter parity across all providers remains a roadmap item rather than a finished uniform contract
For Sentry specifically, the roadmap here should now be read alongside 04-adapters.md: 07 owns the broader rollout picture, while 04 owns the shipped Sentry source-adapter contract, auth/config model, browse semantics, and settings integration details.
Substantial portions are shipped.
Current TUI reality to keep in mind:
- the default sidebar is root-session / work-item centric
- history search uses
SessionHistoryEntry - work-item completion and plan approval publish bus events from TUI command helpers
- settings pages rebuild services and rewire adapters/harnesses dynamically
- workspace init is a TUI flow, not just a CLI-only concern
- overview view with action cards, confirm triggers, and superseded-interrupted filtering
- session transcript view with structured rendering for assistant, prompt, tool, lifecycle, question, thinking, and foreman output
- log overlay with line numbers, wrapping, scroll, and clipboard copy (
ckey) - source items overlay with split list/preview pane
- empty state with animated ASCII bunny component
- mouse scroll support (
tea.WithMouseCellMotion) in overview, log overlay, source items overlay, and settings - quit confirmation dialog when agents are running (
q,ctrl+c,SIGTERM) - duplicate session dialog for choosing between duplicate work items
- centralized text input/textarea component with macOS-compatible word-movement bindings
- raw key debug logger (
SUBSTRATE_KEY_DEBUG) - Add Repository overlay for browsing and cloning remote repositories via GitHub, GitLab, and manual URL
- Sidebar filters (All/Active/Needs Attention/Completed), grouping dimensions, and sort direction controls with custom scrollbar
- Render caching for sidebar and status bar
- Grouped task sidebar entries under section headers
- Completed-session action card with Changes keybind
- Plan inspect key (
i) from planning sessions and plan review - Commit strategy injection into agent system prompts and residual-change commit/push after review pass
- Compact-before-critique for review reimplementation when harness supports it
Still the umbrella outcome: provider work item -> planning -> approval -> implementation -> review -> completion, with lifecycle automation routed by repository host.
The current codebase already has e2e coverage scaffolding under test/e2e/, but this phase remains the overall integration target rather than a finished “nothing left to do” claim.
Keep the validation split, but interpret it against current repo structure:
# Unit
go test ./...
go test -race ./...
go vet ./...
# Focused integration / e2e surfaces
go test -tags=integration ./internal/gitwork/...
go test -tags=integration ./internal/adapter/ohmypi/...
go test -tags=integration ./internal/adapter/linear/...
go test -tags=integration ./internal/adapter/gitlab/...
go test -tags=integration ./internal/adapter/github/...
go test -tags=integration ./internal/adapter/glab/...
go test -tags=integration,e2e -timeout=30m ./test/e2e/...The main live risks that still match current architecture are:
- harness parity drift between oh-my-pi and the alternative harnesses
- provider browse/filter semantics diverging across adapters
- event-bus partial delivery when
ErrRetryLaterhappens after some subscribers already received an event - SQLite contention and retry behavior under concurrent writes
- bridge / CLI output format drift in external tools
- foreman
question_timeoutconfig default"0"is documented as indefinite but runtime falls back to 60 s
Current gaps that remain accurate to call out:
- event-bus partial-delivery semantics are accepted and require idempotent consumers
- no dead-letter store for adapter errors;
adapter.errorevents are persisted but best-effort TUI warnings can be dropped when the message channel is full - pre-hook timeouts cannot kill a misbehaving goroutine that ignores context cancellation
- Linux sandboxing for the oh-my-pi bridge remains less mature than the macOS path
- some adapter / harness parity claims are intentionally held back until real-binary verification exists