Target Architecture
Date: 2026-06-08
Overall Verdict#
The codebase is not globally misshapen. The top-level vocabulary is mostly
honest: wiki, operations, process, harness, capture, agent,
platform, viewer, config, connectors, and ingest are real product
areas. The operation -> process manager -> harness provider flow is a good
center of gravity. The indexer is also a coherent subsystem.
The ugly parts are not from the broad folder structure. They come from local exceptions that stayed after their original slice moved on:
- a hand-written sqlite-free CLI router beside Commander;
- capture start state flowing through rendered stdout;
- two transcript discovery models for one product noun;
- duplicated provider identity/default catalogs;
- stale connector runtime fields inside the provider-neutral run spec;
- raw Codex protocol payloads leaking into durable normalized logs;
- viewer and CLI read-model SQL drifting because
src/wiki/query/is too thin; - docs that describe a stricter invariant than the code actually follows;
- Composio and local
ghGitHub ingest paths competing for product truth.
The correct refactor is not a wholesale domain/application/adapters rewrite.
It is a sequence of small boundary corrections that make current product nouns
true again.
Target Mental Model#
CLI / viewer / scheduler
-> small named use-case functions
-> operation specs, capture policy, ingest resolution, query read models
-> filesystem, SQLite, provider runtimes, launchd, external toolsConcrete directories should stay concrete:
src/cli/commands/ parse flags and render command results
src/viewer/ local HTTP adapter and viewer payload assembly
src/operations/ Build / Absorb / Garden spec construction
src/capture/ session discovery, capture start, sweep policy, ledger
src/ingest/ source-ref parsing, source resolution, source context
src/process/ run lifecycle, queue, records, logs, snapshots
src/harness/ provider-neutral runtime spec and provider adapters
src/agent/ readiness, auth, prompts, global instruction files
src/wiki/indexer/ markdown -> SQLite projection
src/wiki/query/ shared read models for CLI and viewer
src/wiki/topics/ topic storage, DAG rules, frontmatter rewrites
src/wiki/health/ report composition over wiki integrity checks
src/platform/automation/ scheduler task catalog and OS scheduler adapter
src/platform/install/ global install and launcher runtime mechanics
src/platform/update/ update check/install/notifier state
src/config/ TOML schema, codec, store, originsThe important rule is dependency direction, not folder purity. Command files and viewer routes may know UI/output shape. Provider adapters may know provider protocols. The process manager may know run lifecycle. Inner policy modules should not know Commander objects, rendered stdout, provider JSON-RPC payloads, or launchd plist details.
Boundaries To Preserve#
Operation specs are the right lifecycle boundary#
src/operations/run.ts builds provider-neutral AgentRunSpec values and keeps
Build, Absorb, and Garden out of provider protocol details. Keep that shape.
Future lifecycle behavior should extend operation spec construction rather than
bypass it from command files.
Process manager owns durable run lifecycle#
src/process/ is the right owner for queued/background execution, foreground
single-writer behavior, records, logs, snapshots, cancellation, page-change
summaries, and reindex-on-success. Do not move observability into provider
transcripts or command output.
The one ownership leak is semantic operation-output parsing inside
src/process/manager.ts. The manager should call a neutral summary projection
provided by the operation spec or operation module, not import a concrete
almanac_operation_report_v1 parser directly.
Runtime providers and readiness providers should stay split#
Runtime execution lives under src/harness/providers/. Setup/status/model
readiness lives under src/agent/readiness/. That split is correct.
The refactor should remove duplicated identity facts, not merge the two lifecycles. A small shared provider identity module is enough:
src/providers.ts
ProviderId
PROVIDERS
isProviderId()
providerDisplayName()Runtime capability metadata can remain in src/harness/providers/metadata.ts.
Readiness can add executable/status/model-choice behavior. Setup can consume the
same identity/default labels instead of hard-coding them again.
SQLite index remains a generated read model#
.almanac/pages/, .almanac/topics.yaml, .almanac/review.yaml, and
.almanac/runs/ remain the inspectable local artifacts. index.db remains
derived and disposable. The indexer can stay large if it keeps that single job.
The missing piece is a fuller read-model layer under src/wiki/query/.
Boundaries To Fix#
1. Replace command stdout coupling with typed lifecycle start APIs#
capture sweep should not parse runCaptureCommand() output to find a run id.
The target is a typed start function that both CLI and sweep can use.
Preferred simple shape:
src/capture/start.ts
startCaptureRun(options) -> CaptureStartResult
src/ingest/start.ts
startIngestRun(options) -> IngestStartResultThe CLI wrappers render those results. Sweep consumes CaptureStartResult
directly. Ingest-specific report output and source-specific runtime options move
with ingest, not into src/cli/commands/operations.ts.
Do not add a command bus. Exported functions are enough.
2. Collapse transcript discovery to one source model#
Manual capture and scheduled sweep should consume the same candidate model:
src/capture/discovery/
claude.ts
codex.ts
index.ts
types.tsManual capture can choose the latest matching candidate. Sweep can choose every quiet eligible candidate. Those are selection policies over the same discovery data, not separate scanners.
Manual capture --app codex should either work through this model or the flag
should disappear until it works.
3. Make sqlite-free recovery a Commander import problem, not a second CLI#
The need is legitimate: setup, doctor, update, uninstall, and automation repair
commands must work when better-sqlite3 cannot load.
The current solution is ugly: src/cli/sqlite-free.ts mirrors command parsing.
Target:
- Register command shapes without importing SQLite-heavy implementations.
- Lazy-import command implementations inside action handlers.
- Keep the early ABI guard in
bin/codealmanac.ts. - Keep only the truly pre-Commander setup shortcut if Commander import itself cannot be made safe.
If that cannot be done cleanly in one pass, shrink sqlite-free.ts to a small
documented recovery surface rather than mirroring setup and automation flags.
4. Tighten provider data contracts#
Provider cleanup should do four things:
- Replace duplicated provider id/default/display facts with one small identity catalog.
- Rename readiness provider types so they do not look like runtime providers.
ReadinessProvideris clearer thanAgentProvider. - Remove
AgentRunSpec.connectorsunless a production runtime truly consumes connector identity. KeepnetworkAccessor a neutral runtime requirement. - Remove nested
display.rawfrom persisted harness events. If thread/turn ids are useful, normalize them as first-class fields.
Readiness should return structured status data such as readiness,
accountLabel, installFix, and loginFix. The readiness view should not
parse provider prose.
5. Expand src/wiki/query/#
The viewer and CLI need shared read models:
src/wiki/query/pages.ts
PageSummary
recentPages()
pagesBySlug()
searchPages()
pagesMentioningPath()
src/wiki/query/topics.ts
topicSummaries()
topicDetail()
pagesDirectlyTagged()
pagesForSubtree()src/wiki/query/search.ts can keep FTS expression builders. CLI and viewer can
still choose different FTS expressions, but pagination, archive filters, topic
attachment, path mention semantics, and summary shapes should not be duplicated.
6. Admit that almanac serve is a local console or scope it down#
Current src/viewer/api.ts is not only a current-repo wiki viewer. It exposes
global registry browsing, jobs, review decisions, connector status, and wiki
graph data.
There are two coherent choices:
- Keep the global local console and rename docs/help accordingly.
- Scope
serveto the current wiki and move global console behavior behind a different command or flag.
The product already seems closer to "local Almanac console." If that is the choice, split implementation modules:
src/viewer/wiki-api.ts
src/viewer/jobs-api.ts
src/viewer/review-api.ts
src/viewer/connections-api.ts
src/viewer/global-api.ts
src/viewer/server.tsAlso warn or document clearly when binding beyond localhost because the exposed data is broader than a rendered wiki page.
7. Move health repair/source parsing out of the health index#
src/wiki/health/index.ts should compose health reports. Source/citation
checks and deterministic legacy source-frontmatter repair should live in a
source maintenance module.
Target:
src/wiki/sources/health.ts
src/wiki/sources/maintenance.ts
src/wiki/health/index.tsKeep health --fix narrow. Do not let health become a general wiki-editing
surface.
Product Decisions To Make Before Refactoring#
GitHub source access#
The current tree contains both local gh ingest guidance and Composio-backed
GitHub source commands. That is the clearest product-level split.
Recommended decision: keep local GitHub ingest on gh for the local OSS CLI,
and treat Composio as an experimental connector/source-tool surface unless a
current product requirement needs it. Hosted GitHub maintenance should not be
forced through the local Composio shape; the wiki already points toward GitHub
App/MCP or native GitHub tooling for hosted jobs.
If Composio stays:
- keep it out of
AgentRunSpecuntil runtime adapters actually consume it; - make it a source-tool boundary, not provider runtime identity;
- remove stale wiki/docs claims that ingest currently uses Composio if tests and
source still use local
gh.
If Composio does not stay:
- remove
connect github,source github, connector config fields, viewer connections payloads, and stale runtime connector tests in one intentional slice.
Scheduled Garden default#
Scheduled capture has a strong correctness reason: quiet agent sessions become eligible evidence. Scheduled Garden is broader and more expensive. It asks an agent to reshape the graph every few hours by default.
Recommended decision: make setup default to capture-only unless Garden has a clear trigger policy such as accumulated page changes, health issues, or an explicit user opt-in.
Deprecated aliases#
The repo still carries deprecated paths such as almanac ps, capture status,
show --raw, and old almanac set forms.
Recommended decision: choose either a real removal window or declare them permanent compatibility. The current half-state keeps tests and parser branches alive without saying why.
Invariant Rewrite#
The current invariant says normal CLI commands never read or write page content. That is too broad.
Recommended wording:
Only lifecycle operations invoke AI or write page prose. Read commands may refresh derived local index state and read committed markdown for display or validation. Organization commands may deterministically rewrite wiki metadata through explicit verbs such as
tag,topics,review, andhealth --fix.
That wording matches the code and keeps the important safety line: no hidden AI and no hidden page-prose writes.
What Not To Do#
- Do not rename the repo into
domain/,application/,ports, andadapters. - Do not add a command bus, class-per-command framework, or plugin loader.
- Do not wrap every filesystem or SQLite call in generic repositories.
- Do not make the viewer a source of truth.
- Do not move run records into SQLite just because they are queryable.
- Do not keep compatibility shims merely because tests import them.
- Do not preserve one-off product paths without choosing whether they are real features.