Branch: codex/operation-trigger-queue-audit
Base: local dev at 73b764e9 after origin/main was merged into dev.
Only decision source of truth:
docs/plans/2026-07-06-operation-trigger-queue-decisions.md.
Treat that decision note as authoritative over old plans, docs, wiki pages, README prose, current tests, and current implementation shape.
Deep-audit the current CodeAlmanac codebase against the decided model:
- product nouns: trigger, run, run kind, queued run, worker, repository, local database, schedule,
- run kinds:
build,ingest,garden, - local database:
~/.codealmanac/codealmanac.db, - no
registry.jsonfallback, - exact-root targeting,
- read-only exact-cwd auto-registration,
- strict operation trigger targeting,
initas deterministic setup plus abuildrun trigger,- one machine-level SQLite run queue,
- worker drain semantics,
- scheduled sync/garden behavior,
- terminal output identity/result requirements,
--waitas queue-then-attach shorthand,- no foreground/background execution mode.
Primary artifact: this file. Do not edit old docs or the almanac/ wiki during
the audit.
Default stance: notice and note discrepancies. Fix code only if the fix clearly follows the decision note and improves code quality. Do real behavior/UX checks before changing tests.
Code quality bar:
- Code quality is paramount.
- Names and function names should be simple.
- Refactor until diminishing returns, then stop.
- If a product decision forces ugly code, record that as product/design pressure.
- Do not preserve compatibility aliases, duplicate storage paths, root hopping, foreground/background thinking, or generic resolvers that hide targeting policy.
- Current branch is
codex/operation-trigger-queue-audit. - Source
pyproject.tomlversion is0.3.4. - Installed binary is
/Users/rohan/.local/bin/codealmanac. - Installed binary reports
codealmanac 0.3.1.
Finding:
Deployed UX tests currently hit old 0.3.1, not the merged source shape. Before
treating deployed writer behavior as current evidence, redeploy/install a current
package and record the exact version and binary.
Severity: testing blocker.
Decision note:
`--wait` is shorthand for "create the run, then attach until it finishes."
Evidence:
src/codealmanac/cli/parser/run_commands.pydefinesinit,ingest, andgardenflags.- None of those parsers includes
--wait. - Safe source CLI checks showed:
codealmanac init <path> --waitexits 2.codealmanac ingest note.md --waitexits 2.codealmanac garden --waitexits 2.- Error is
unrecognized arguments: --wait.
Expected:
- Command creates a normal run.
- With
--wait, terminal attaches to that same run and streams until terminal. - This must not become a second foreground execution mode.
Severity: must-fix product mismatch.
Decision note:
`codealmanac jobs` reads this machine-level queue by default.
Evidence:
src/codealmanac/cli/parser/jobs.pyuses subparsers but does not setrequired=True, so barejobsworks.- Safe source CLI check showed bare
codealmanac jobsprints# 0 jobs. - Help renders usage as:
codealmanac jobs ... {show,logs,attach,cancel} ....
Expected:
- Help should show that listing runs is the default behavior.
Severity: UX mismatch.
Decision note:
- On attach, show run kind, run id, repository, source/follow.
- On finish, show a finished/failed block with status, changed files, health, or logs.
Evidence:
src/codealmanac/cli/render/job_logs.py::render_run_attach_stream()renders raw numbered events.- Terminal completion is only
status: ..., plus optionalsummary:orerror:. RunAttachUpdatelacks repository identity, and the renderer does not resolve repository info.
Expected:
- Attach starts with a run identity block.
- Attach ends with the decided result block.
Severity: must-fix terminal-output mismatch.
Decision note:
When a run is queued, show run kind, run id, repository, queue position/runs ahead, and attach/follow command.
Evidence:
src/codealmanac/cli/render/sync.py::render_sync_status()prints:started <repo>: <run_id> (<n> transcript(s)).SyncStarteddoes not carry runs-ahead or follow command data.SyncIngestQueue.run()queues runs and spawns a worker, but does not return the sameRunQueueStartResultshape as manual triggers.
Expected:
- Sync-created ingest runs should surface the same queued-run identity as manual runs, or provide equivalent follow instructions per run.
Severity: important terminal-output mismatch.
Evidence:
src/codealmanac/cli/render/run_commands.py::render_scheduled_garden()prints only queued/skipped counts, run ids, skipped names, worker pid, and worker error.- It does not show repository, runs-ahead, or attach/follow command per run.
ScheduledGardenResultcarriesruns,skipped,worker, andworker_error, not run-identity view data.
Expected:
- Scheduled garden should not have a weaker run-output path than manual triggers.
Severity: important terminal-output mismatch.
Decision note:
init owns deterministic setup only:
- create repository row,
- create
almanac/, - create minimal
README.mdandtopics.yaml.
If deterministic setup fails, init fails. If the agent fails, the build run fails and the repository still has its minimal wiki home.
Evidence:
BuildWorkflow.prepare()calls:reject_existing_almanac(target),ensure_tracking_available(target.root_path),ensure_ready(request.harness),- then
register_target(...), - then
wiki.initialize(...).
- Tests encode this old behavior:
test_queue_build_fails_before_queueing_when_harness_is_brokenexpects no repository and noalmanac/.test_run_build_rejects_non_git_repo_without_registeringexpects no repository and noalmanac/.
Expected:
- Minimal init setup should not depend on Codex/Claude readiness.
- The build run should own agent failure.
- After agent failure, the repo should still be registered with minimal wiki files.
Severity: must-fix architecture/product mismatch.
Code quality:
BuildWorkflow.prepare()hides two product concepts behind one verb: deterministic init setup and build-run preflight.
Decision note:
Operation triggers use strict registered-root selection. Read commands may auto-register exact cwd only.
Evidence:
dispatch_ingest()anddispatch_garden()callload_cli_config(...)before queueing.load_cli_config()callsConfigService.load(...).ConfigService.project_config_path()callsrepositories.select_for_read(...).- Safe source probe from an unregistered exact cwd with valid
almanac/showeddispatch_garden(...)auto-registered the repo and queued a garden run.
Expected:
- Config loading for operation triggers must not mutate repository registration.
- Strict operation target selection must happen before any read convenience.
Severity: must-fix targeting mismatch.
Code quality:
- This is the generic-resolver smell the decision note warns about.
Evidence:
services/topics/repository.py::resolve_topic_repository()always callsselect_for_read().- Topic mutations use that helper for create/describe/link/unlink/rename/delete.
TaggingServicecallsPagesService.show().PagesService.show()usesselect_for_read().- Safe source probe from an unregistered exact cwd with valid
almanac/showedtopics.create(...)auto-registered the repo andtagging.tag(...)rewrote page frontmatter. tests/test_architecture.pycurrently asserts"select_for_read(" in repository_text.
Expected:
- Deterministic metadata writes need an explicit targeting policy.
- If following the decision note strictly, write commands should not inherit read-command auto-registration.
Severity: important targeting/code-quality mismatch.
Evidence:
RunQueue.start_scheduled_garden()loops overself.repositories.list().- It skips only repositories with active garden runs.
- It does not check
repository_state(...)formissing_repoormissing_almanac. - It queues by repository name, so a stale registered repository can become a queued run.
Expected:
- The product needs a clear rule here.
- If the decision remains "garden all registered repositories", the failure must be explicit and useful.
- If user trust wins, scheduled garden should skip unavailable repositories before queue creation and report their state.
Severity: important reliability/UX gap.
Product pressure:
- The decision note's "garden all registered repositories" may be too simple for real machine state.
Evidence:
SyncIngestQueue.run()queues ingest runs first.- It then calls
queue.spawn_worker(worker_cwd). - If worker spawn fails, it appends transcript skipped reasons:
worker-spawn-failed: .... - The run records remain queued in the local database.
Expected:
- Output should say "queued, but worker failed to start" with recovery/follow instructions, or the queued runs should be marked failed.
- Calling transcripts skipped while queued runs exist is conceptually wrong.
Severity: important UX/state mismatch.
Decision note:
Queued run: a run waiting to start.
Evidence:
RunStore.create()writes statusqueuedwithspec_json = NULL.RunStore.queue()writes statusqueuedwith durableRunSpec.next_queued_run()selects onlystatus = queued AND spec_json IS NOT NULL.IngestWorkflow.start()andGardenWorkflow.start()still callRunsService.start(StartRunRequest(...)), creating spec-less queued records for old inline paths.- Tests still name one spec-less queued record
foreground.
Expected:
- One meaning for queued worker work.
- If direct inline starts survive internally, their state should not look like queued worker work in run listings.
Severity: must-fix code-model smell.
Decision note:
Use simple nouns in code unless a narrower name is genuinely clearer. Avoid
Job when it obscures the product model.
Evidence:
- Public
codealmanac jobsis explicitly in the decision note and can stay. - Internal code still has:
services/viewer/jobs.py,ViewerJob*,/api/jobs,server/assets/viewer/jobs.js,- terminal labels like
job:, # 0 jobs.
- Automation uses
ScheduledJob, which may be justified by launchd vocabulary, but setup/UX copy also says "CodeAlmanac jobs" where "runs" may be clearer.
Expected:
- Internal product model should say runs.
- Keep "jobs" only as the public CLI surface or where scheduler vocabulary is genuinely narrower.
Severity: naming/code-quality debt.
Decision note:
Derive machine-level paths once at the composition root as LocalStatePaths.
Services should receive concrete paths from that value.
Evidence:
LocalStatePathshasdatabase_path,config_path,state_dir, andupdate_lock_path, but nologs_dir.AutomationJobFactory.job_for_task()deriveshome = request.home or home_dir()andlogs_dir = logs_dir_for(home).tests/test_architecture.pycurrently asserts"logs_dir_for(" in jobs_text.
Expected:
- Composition root should provide local state/log paths.
- If automation needs user home for launchd plists, separate user home from CodeAlmanac state paths.
Severity: important composition-root/code-quality mismatch.
Evidence:
services/health/runtime.pytreats bothalmanac/jobsandalmanac/runsas runtime-state leaks.- Tests create
almanac/jobsand expect validation to report it.
Assessment:
- This is not active queue storage.
- It is defensive cleanup for retired runtime state.
- If kept, the message/test should frame it as retired-state cleanup, not current model language.
Severity: low naming residue.
Evidence:
- Topic architecture test requires
select_for_read(). - Automation architecture test requires
logs_dir_for()in job construction. - Build tests expect broken harness/non-git to prevent minimal init setup.
- Run service tests use
StartRunRequestto create spec-less queued records and name oneforeground. - CLI jobs tests assert
job:output and jobs vocabulary forRunRecord.
Expected:
- Tests should become contracts for the decision note.
- Per audit rule, do not edit these first. Fix behavior/code shape first, then update tests.
Severity: important test-design debt.
RunKind.BUILDexists.RunSpecaccepts build specs without inputs.RunQueue.queue_build()persists build as durable queued work.RunQueueWorker.run_queued()dispatches build/ingest/garden.dispatch_init()callsapp.workflows.queue.start_build(...).- Manual init now prints queued-run output instead of foreground wording.
- No
registry.jsonsource fallback was found. - Exact-cwd read auto-registration does not parent-hop.
- Sync transcript matching compares transcript cwd to registered repository root paths, not parent roots.
Quality judgment:
- Moving build/init into the shared run queue was the right architectural move.
- The remaining work is conceptual cleanup: target selection side effects, ambiguous queued state, attach/result rendering, and old job vocabulary.
- Run current installed/deployed package only after redeploying from current source, then record version/binary.
- Do safe source probes for:
- nested cwd read and operation commands,
- invalid
--wiki, - missing repo and missing
almanac/, - worker spawn failure output,
- jobs show/logs/attach/cancel terminal output,
- automation status/install output with isolated HOME where possible.
- Do not edit tests until real behavior/code shape is understood.
Probe style:
- In-process CLI calls through
codealmanac.cli.main.main(...). - Isolated temp database.
- Fake worker spawner for safe success paths.
- Broken worker spawner for spawn-failure path.
- No real Codex/Claude run.
Evidence:
- From exact registered repo root,
search Notereturnednote. - From nested
repo/src,search Notefailed with:No repository selected. Run from a registered repository root or pass --wiki <name>. - From nested
repo/src,search Note --wiki reporeturnednote. - From nested
repo/src,gardenfailed with the same no-repository-selected message. - From repo root,
garden --wiki missingfailed withrepository not found: missing.
Assessment:
- The basic root-hopping ban works for direct command execution.
- This does not clear the config-loading auto-registration bug for unregistered exact cwd; that remains a separate finding.
Severity: no discrepancy found for this subcase.
Decision note model:
- A command always creates a run.
- Worker claims queued runs and executes them.
- Terminal output should show useful run identity and follow/log commands.
Evidence:
- Probe replaced the worker spawner with one that raises
OSError("fake worker spawn failed"). - Running
gardenraised rawOSError('fake worker spawn failed'); the CLI did not convert it to acodealmanac:product error becausemain()only catchesCodeAlmanacErrorand PydanticValidationError. - A queued garden run remained in the local DB:
garden-..., kindgarden, statusqueued. - Source code confirms real spawner uses raw
subprocess.Popen(...)inintegrations/runs/process.py, so real spawn failures can be rawOSError.
Expected:
- Manual trigger should either:
- queue the run and render "queued, but worker failed to start" with
jobs attach/jobs showrecovery instructions, or - mark the run failed if no worker can ever claim it.
- queue the run and render "queued, but worker failed to start" with
- It must not crash with a raw Python exception after creating durable queued work.
Severity: must-fix failure-mode/UX mismatch.
Code quality:
RunQueue.start_build/start_ingest/start_gardenassume spawn success. That makes worker startup a hidden fatal side effect of trigger handling.- Scheduled garden catches spawn errors, but manual triggers do not. Same concept has two error policies.
Evidence from terminal probe:
jobs show <run>printed:job: <run-id>kind: ingeststatus: donelogs: codealmanac jobs logs <run-id>
jobs logs <run>printed numbered raw events.jobs attach <run>printed the same raw events plus:status: donesummary: done summary
jobs cancel <done-run>printed:job already done: <run-id>
Expected:
- Public command can remain
jobs, but renderer should describe run records as runs. - Attach should render identity/result blocks from the decision note.
- Cancel output should probably say
run already doneunless the public command label is intentionally leaking into every record label.
Severity: important terminal-output/naming mismatch.
Evidence:
jobslisted a terminal run in a table:ID KIND STATUS ELAPSED TITLE.
Assessment:
- The default list behavior is good and matches the decision note.
- The mismatch is help text and vocabulary, not the underlying list behavior.
Severity: no functional discrepancy for bare list behavior.
Current audit has already found the main conceptual mismatches:
- missing
--wait, - attach/result terminal output,
- sync/scheduled garden output,
- init deterministic setup mixed with build preflight,
- operation auto-registration through config loading,
- topic/tag write auto-registration,
- scheduled garden missing-repo behavior,
- worker spawn failure raw crash,
- spec-less queued records,
- job vocabulary leakage,
- automation path derivation outside
LocalStatePaths, - tests encoding old behavior.
Further useful audit should be targeted, not infinite:
- deployed current-version install/redeploy check,
- setup/automation isolated-HOME UX pass,
- real provider readiness/auth failure pass,
- one real
init --guidance "keep it tiny and finish quickly"pass after deployed/source version is current.
Probe style:
- Source CLI help through
uv run codealmanac ... --help. - Direct in-process setup service probe with:
- isolated temp
HOME, - isolated temp
CODEALMANACdatabase/config paths, - fake scheduler,
- fake Codex/Claude harness readiness,
- no real launchd install,
- no real provider run.
- isolated temp
Evidence:
src/codealmanac/cli/parser/setup.pyhas independent--targetand--runnerflags.src/codealmanac/cli/dispatch/setup_tui.py::default_setup_selections()parses--target, but choosesDEFAULT_HARNESSunless--runneris passed.DEFAULT_HARNESSis Codex.- Safe source probe showed:
--target all --yes-> instruction targetscodex, claude, runnercodex, modelgpt-5.5.--target claude --yes-> instruction targetclaude, runnercodex, modelgpt-5.5.--target claude --runner claude --yes-> instruction targetclaude, runnerclaude, modelclaude-sonnet-4-6.
Expected:
- If a non-interactive user says "Claude only", the default runner should not silently remain Codex unless the product intentionally separates "instruction target" from "maintenance runner" and makes that explicit in terminal output.
- This is the exact flag-combination class of bug that can make setup look successful while later run creation fails on the wrong provider.
Severity: must-fix setup UX/product mismatch.
Code quality:
targetandrunnerare currently separate product concepts with names that do not explain the difference. If they remain separate, the nouns need to be sharper than "target" and "runner".
Evidence:
- In-process probe with fake unavailable Codex and available Claude showed:
- instruction targets:
codex,claude, - default runner:
codex, - readiness result:
codex False codex missing in probe, - both temp Codex and Claude instruction files were written.
- instruction targets:
SetupService.runner_readiness()callsself._runner_probe.readiness( request.harness)for only one harness.- It does not probe every installed instruction target.
Expected:
- If setup installs instructions for multiple agents but configures exactly one maintenance runner, output must make that distinction impossible to miss.
- If the chosen runner is unavailable and another installed target is available, the user needs a direct recovery path.
Severity: important setup failure-mode mismatch.
Evidence:
setup --helpsays--runneris the "agent that runs CodeAlmanac jobs".runner_options()displays "runs CodeAlmanac jobs".ai_runner_step()renders "<model>will run CodeAlmanac jobs".
Expected:
- Public
codealmanac jobscan remain, because the decision note names it. - Setup copy should describe the product model as runs, not jobs, unless it is specifically naming the public command.
Severity: naming/code-quality debt.
Evidence:
wizard_selections()prompts:- Agent instructions,
- AI runner,
- Runner model,
- Wiki maintenance,
- Product updates,
- Agent change handling.
render_setup_choice_screen()hardcodes[step/6].
Assessment:
- The operation-trigger decision note does not define setup's question count, so this is not a direct discrepancy from that file.
- It is still worth recording because setup is now carrying more product
decisions than the user may expect from
--yes, and hidden defaults directly affect run creation.
Severity: product-UX pressure, not a direct run-queue discrepancy.
Evidence:
selected_setup_tasks()defaults to sync, garden, and update unless disabled.- Safe source probe showed default
setup --yeswould install tasks:sync,garden,update. - Scheduled commands are:
python -m codealmanac.cli.main sync,python -m codealmanac.cli.main __garden-scheduler,python -m codealmanac.cli.main update --scheduled.
Assessment:
- This matches the broad product model: sync/garden are machine-level triggers, and update is machine maintenance.
- The concern is not task selection; the concern is output/readiness clarity and launchd environment failure handling.
Severity: no discrepancy for default scheduled task set.
Evidence:
- Already noted
AutomationJobFactory.job_for_task()callshome_dir()andlogs_dir_for(home). - This pass also confirmed it builds launchd
PATHfrom:- current process
PATHor requestenv_path, ~/.local/bin,~/.bun/bin,- hardcoded launchd fallback paths.
- current process
LocalStatePathsstill does not own logs path or launch environment inputs.
Expected:
- Machine state paths should come from the composition root.
- Launchd environment policy should be explicit and testable, not hidden in the job factory.
Severity: important composition-root/code-quality mismatch.
Evidence:
automation install --helpexposes taskssync,garden,update.- Human output from
render_automation_install()says:automation installed,<task> interval,<task> command,<task> plist.
- It does not explain that scheduled sync and scheduled garden are machine-level triggers that create normal runs in the same queue.
Expected:
- The output should connect automation to the run model, especially for garden:
"scheduled garden queues normal garden runs; follow them with
codealmanac jobs".
Severity: UX clarity gap.
Evidence:
UpdatesService.run_scheduled()callsactive_run_count(database_path).active_run_count()counts every run with statusqueuedorrunning.- That includes the spec-less
queuedrecords from the old direct-start path already noted in thequeued has two meaningsfinding. active_runs_message()renders:scheduled update skipped: <n> CodeAlmanac job(s) are active.
Expected:
- Output should say runs.
- Active-run counting should not be distorted by the old spec-less queued state if that state remains temporarily.
Severity: important naming/state-model mismatch.
Evidence:
- Source grep found no CLI
--nowflag outside the decision note.
Assessment:
- The decision note says
--nowis a future manual trigger option. - No current discrepancy.
Severity: no discrepancy.
Evidence:
dispatch_config()callsapp.config.set(...).ConfigService.set()writes onlylocal_state.config_path.- It does not call
select_for_read().
Assessment:
- Standalone
config setdoes not auto-register repositories. - The targeting bug remains in operation-trigger config loading:
dispatch_ingest()anddispatch_garden()callload_cli_config(), andConfigService.load()discovers project config throughselect_for_read().
Severity: no new discrepancy beyond the existing operation-trigger auto-registration finding.
Probe style:
- Source inspection of worker, sync, scheduled garden, jobs, and run transition code.
- CLI help through
.venv/bin/python -m codealmanac.cli.main ... --help. - Isolated in-process worker probe with temp
HOME, temp local database, temp git repo, and fake failing harness. - No real provider run.
Decision note:
The worker claims the next queued run, executes it, records the result, and moves on.
On failure, the run is failed and removed from active queue processing.
Evidence:
RunQueueWorker.drain()loops overself.run_queued(queued).BuildWorkflow.run_started(),IngestWorkflow.run_started(), andGardenWorkflow.run_started()catch exceptions, calloperations.fail(...), then re-raise.RunQueueWorker.run_queued()does not catch those re-raised failures.- Safe isolated probe queued two garden runs with a fake harness that raises.
Result:
- drain raised
RuntimeError fake harness failure, - first run became
failed, - second run stayed
queued.
- drain raised
Expected:
- A failed run should be recorded as failed and the worker should continue to the next queued run unless the failure is a worker-level/system-level failure.
- If the product wants one failure to stop the worker, that needs to be a deliberate policy, because it contradicts the current decision note.
Severity: must-fix worker semantics mismatch.
Code quality:
- Operation failures and worker failures need separate control flow. Right now a normal run failure is still represented as an exception that aborts the whole drain loop.
Evidence:
SubprocessRunWorkerSpawner.spawn()starts the child process withcwd=request.cwd.- It also passes
__run-worker --cwd <cwd>. dispatch_run_worker()ignoresargs.cwdand callsapp.workflows.queue.drain(DrainRunQueueRequest()).- Safe isolated CLI probe showed:
__run-worker --cwd /definitely/not/a/real/pathexited 0 with an empty queue.
Assessment:
- In the real spawn path,
cwdmatters becausesubprocess.Popen(..., cwd=request.cwd)uses it before the hidden command starts. - The hidden CLI flag itself is dead after startup.
Expected:
- Either remove the hidden flag and rely on process cwd, or use it explicitly for diagnostics/worker identity.
- A required hidden flag that does nothing teaches the wrong model to future maintainers.
Severity: code-quality/diagnostic mismatch.
Evidence:
dispatch_run_worker()discards theRunQueueDrainResult.RunQueueWorker.drain()returnsRunQueueDrainResult(lock_acquired=False)when another worker lock is active.- The hidden CLI has no
--jsonoutput and no human output.
Expected:
- Hidden launchd commands can be quiet on success, but lock contention and worker diagnostics need some observable trail when debugging scheduled runs.
- If the command remains intentionally silent,
doctororautomation statusneeds to expose enough worker/queue state to explain why queued runs are not moving.
Severity: failure-mode/diagnostic gap.
Evidence:
src/codealmanac/cli/parser/run_commands.pycreatessync_subcommandsbut does not make it required.- Bare
syncis the actual operation trigger. - Help renders usage as:
codealmanac sync ... {status} ....
Expected:
- Help should make it clear that bare
syncstarts the scheduled-sync-style trigger manually, andsync statusis optional inspection.
Severity: UX mismatch.
Evidence:
SyncIngestQueue.run()queues ingest runs, then callsqueue.spawn_worker().- If spawn fails, it appends
worker-spawn-failed: ...skipped reasons. - It still calls
self.state_store.record_completed(now)after the spawn failure handling. - The queued runs remain in the database.
Expected:
- Sync state should not advance past transcripts merely because runs were queued if the worker did not start and no run processed them.
- At minimum, terminal output should say the transcripts are queued but not yet processed, instead of mixing "started" and transcript-level skipped reasons.
Severity: must-fix sync state/worker failure mismatch.
Code quality:
- Queue creation, worker startup, and sync watermark advancement are three different product facts. The current method compresses them into one "completed" side effect.
Evidence:
jobs cancelhelp says "cancel one queued or running job".RunsService.cancel()only changes the run record state.- No code path signals the worker process or harness process.
finish_run()preserves acancelledrecord, so a later finish call does not overwrite the status, but the agent may already have continued mutating files.
Expected:
- If cancellation is only "mark this run cancelled and ignore final status", help/output should not imply the running provider is stopped.
- If users expect cancellation to stop work, the worker/harness layer needs a real cancellation mechanism.
Severity: important UX/failure-mode mismatch.
Evidence:
jobs --helpsays "inspect local jobs".- Subcommands say "show one job record", "show one job log", "stream one job log until the job exits", and "cancel one queued or running job".
- Renderers still print
# 0 jobs,job:, andjob already ....
Expected:
- Public command may remain
jobs. - The record being inspected should be called a run.
Severity: naming/code-quality debt.
Evidence:
- Hidden
__garden-schedulerparser has no--json. dispatch_scheduled_garden()always callsrender_scheduled_garden(result).render_scheduled_garden()prints human-only scheduler output.
Assessment:
- This is hidden automation, so lack of JSON may be acceptable.
- It still means scheduled-garden diagnostics are weaker than manual trigger diagnostics and cannot easily be consumed by tests or support scripts.
Severity: low-to-important diagnostic gap.
Evidence:
SyncEvaluator.evaluate()buildsrepositories_by_pathfrom registered repository root paths.- It matches transcripts by exact normalized
transcript.cwd. - It records
unregistered-cwdwhen the transcript cwd does not exactly match.
Assessment:
- This matches the decision note's "scheduled sync must not root-hop" rule.
Severity: no discrepancy.
Probe style:
- Source inspection of wiki/read parsers, dispatch, services, and renderers.
- CLI help through
.venv/bin/python -m codealmanac.cli.main ... --help. - Isolated CLI probe with temp
HOME, temp local database/config, temp repo, and hand-writtenalmanac/. - No real provider run.
Evidence:
SearchService.search()andPagesService.show()userepositories.select_for_read(...).- Safe isolated probe:
codealmanac list --jsonfrom an unregistered repo with validalmanac/returned[].codealmanac search hello --jsonfrom that exact repo root returned the page and auto-registered the repo.codealmanac list --jsonthen showed the repo as registered and available.codealmanac search hellofromrepo/srcfailed with:No repository selected. Run from a registered repository root or pass --wiki <name>.
Assessment:
- This matches the decision note for read-only exact-cwd auto-registration and no parent root hopping.
Severity: no discrepancy.
Evidence:
src/codealmanac/cli/parser/wiki.pycreatestopic_subcommandsbut does not make it required.dispatch_topic_rewrite()falls through toapp.topics.list(...)when no topic subcommand is present.- Help renders usage as:
codealmanac topics ... {show,create,describe,link,unlink,rename,delete} ....
Expected:
- Help should show that bare
topicslists topics, just like barejobslists runs and baresynctriggers sync.
Severity: UX mismatch.
Evidence:
services/topics/repository.py::resolve_topic_repository()always callsselect_for_read().- Topic reads (
list,show) and topic writes (create,describe,link,unlink,rename,delete) all use that helper. - This is the same root cause behind the earlier finding that topic mutations
can auto-register an exact cwd with
almanac/.
Expected:
- Topic reads can use the read-selection policy.
- Topic writes need an explicit deterministic-write policy; they should not inherit read auto-registration accidentally.
Severity: important targeting/code-quality mismatch.
Code quality:
- A helper named
resolve_topic_repositoryhides the product decision. It should be obvious at the call site whether a command is using read selection or strict/write selection.
Evidence:
TaggingService.tag()andTaggingService.untag()callPagesService.show().PagesService.show()usesselect_for_read().- The command then rewrites page frontmatter through
rewrite_page_topics(...).
Expected:
- Tagging is a write command and should not depend on a read command's auto-registration convenience.
Severity: important targeting/code-quality mismatch.
Evidence:
topics --helpexposes only--wiki, no--json.tag --helpexposes only--wiki, no--json.untagfollows the same parser shape astag.- Renderers print human-only text from
render_topics,render_topic_mutation,render_topic_rewrite_mutation, andrender_tagging.
Expected:
- If these remain user-facing organization commands, JSON parity should be considered with the rest of the read/inspect surface.
- If JSON is not needed, document that these are human-only mutation helpers.
Severity: low-to-important output consistency gap.
Evidence:
servecreates the server withcreate_server_app(app, Path.cwd(), args.wiki).- Viewer API calls use
ViewerRepositoryScope.select(...). - If no
--wikiis provided andselect_for_read(cwd, None)raisesNotFoundError,ViewerRepositoryScope.select_default()returns the first available registered repository.
Assessment:
- This is not an operation trigger, so it does not directly violate the strict operation-targeting rule.
- It does violate the general "exact cwd or --wiki" mental model if a user runs
servefrom an unrelated directory and silently sees some other registered repo.
Expected:
- Decide whether viewer convenience is allowed to break exact-cwd targeting.
- If allowed, terminal output should say which repository was selected and why.
Severity: product-UX targeting pressure.
Evidence:
HealthService.validate()usesselect_for_read().IndexService.reindex()usesselect_for_read().- Both commands can refresh/rebuild derived index state in the local database.
Assessment:
- These commands do not write page prose or committed wiki source.
- They are probably acceptable under the decision note's "read commands may refresh derived local index state" rule.
- Still, they are not purely read-only, so their auto-registration behavior should be intentional and documented in tests.
Severity: no direct discrepancy; test/contract clarity gap.
Evidence:
render_page()returns fullPageViewimmediately whenargs.jsonis true.- It does not apply
--body,--meta,--lead,--links,--backlinks,--files, or--topicsprojections in JSON mode.
Assessment:
- This is not directly covered by the operation-trigger decision note.
- It is a JSON/human output inconsistency worth remembering while cleaning the command surface.
Severity: low output consistency gap.
Probe style:
- Read installed binary path and version.
- Read source package version.
- Run current source as a uv tool without replacing the global install:
uv tool run --from /Users/rohan/Desktop/Projects/codealmanac codealmanac .... - Do not mutate global
codealmanacinstall.
Evidence:
which codealmanac->/Users/rohan/.local/bin/codealmanac./Users/rohan/.local/bin/codealmanacis a uv tool shim into/Users/rohan/.local/share/uv/tools/codealmanac/bin/python3.uv tool listreportscodealmanac v0.3.1.codealmanac --versionreportscodealmanac 0.3.1.pyproject.tomlin the current source saysversion = "0.3.4".
Expected:
- Do not treat global CLI smoke as current-code evidence until the tool is upgraded or intentionally run through a current-source tool command.
Severity: testing blocker for deployed-product claims.
Evidence:
uv tool run --from /Users/rohan/Desktop/Projects/codealmanac codealmanac --versionbuilt the local source and reportedcodealmanac 0.3.4.- After that smoke, plain
codealmanac --versionstill reportedcodealmanac 0.3.1, so the global install was not replaced.
Assessment:
- For current-code UX checks that should look like an installed CLI, prefer
uv tool run --from <repo> codealmanac ...over the broken local.venvcommand and over mutating the user-level global tool.
Severity: useful audit/testing path.
Evidence from uv tool run --from ...:
codealmanac init --helpin current source has no--wait.codealmanac jobs --helpstill renders subcommands as if one is required and still says "job" in help copy.codealmanac sync --helpstill renders{status}as if status is required even though baresyncis the trigger.
Assessment:
- These are not artifacts of the stale global
0.3.1binary. - They exist in the current
0.3.4source package shape.
Severity: confirms existing findings.
Evidence:
.venv/bin/python -m codealmanac.cli.main --versionreportscodealmanac 0+unknown.- Earlier
uv run codealmanac ...commands repeatedly warned about missingRECORDfiles for stalecodealmanac-0.1.2.dist-infoandcodealmanac-0.3.4.dist-info.
Expected:
- Do not use
.venvversion output as deployed-version evidence. - Use source inspection,
pyproject.toml, anduv tool run --fromfor current-source smoke.
Severity: test-environment hygiene gap.
Source-level audit is now near diminishing returns.
Covered at least once:
- init/build queue shape,
- manual ingest/garden triggers,
- sync and scheduled garden,
- worker drain and worker spawn failure,
- jobs list/show/logs/attach/cancel,
- setup
--yes, runner/target selection, automation install, - config/update/doctor basics,
- read commands and organization writes,
- JSON/human output mismatches,
- deployed/source version mismatch.
Remaining high-value work is no longer more source reading. It is real smoke:
- upgrade or explicitly run current-source CLI for any deployed-product claim,
- real
initwriter run with--guidanceasking the agent to return early, - missing/broken provider binary and bad-auth behavior with real Codex/Claude adapters,
- launchd/PATH behavior from an actual scheduled job.
Do not keep expanding source audit unless a new concrete question appears.
Probe style:
- Current source only:
uv tool run --from /Users/rohan/Desktop/Projects/codealmanac codealmanac .... - Temp
HOME. - Temp
CODEALMANAC_DATABASE_PATHandCODEALMANAC_CONFIG_PATH. - Temp git repo.
- No real user wiki or global install mutation.
initused guidance:Smoke test only. Return early, keep it tiny, do not spend time polishing.
Evidence:
- Command:
codealmanac init <temp-repo> --guidance "Smoke test only. Return early, keep it tiny, do not spend time polishing." --json - Current-source version in the smoke was
codealmanac 0.3.4. - Temp repo was a valid git repo.
- Temp HOME caused Codex readiness to fail with:
harness codex is not available: WARNING: proceeding, even though we could not create PATH aliases: Refusing to create helper binaries under temporary dir ... -- ... or sign in with codex login; or switch harness: codealmanac config set harness.default claude. - Exit code was
1. - After failure:
almanac/did not exist,almanac/README.mddid not exist,codealmanac list --jsonreturned[],codealmanac jobs --jsonreturned[].
Expected:
- Per the decision note,
initshould perform deterministic setup first: repository row,almanac/, minimalREADME.md, andtopics.yaml. - Provider failure should become a failed
buildrun, not prevent minimal setup and queue creation.
Severity: confirms the must-fix init/build discrepancy with real current-source behavior.
Evidence:
- In the same isolated temp HOME where Codex readiness failed,
doctor --jsonexited0. - It reported:
- version,
- Python,
- database path,
- manual package,
- wiki selection status.
- It did not report Codex/Claude readiness.
Expected:
- If
initcan fail before deterministic setup because a provider is unavailable,doctorshould expose provider readiness or setup should be the explicit readiness command.
Severity: important diagnostics gap.
Evidence:
- Command:
codealmanac setup --yes --skip-instructions --no-auto-update --sync-off --garden-off --json - Exit code was
0. - JSON wrote config values:
auto_commit = true,harness.default = codex,harness.model = gpt-5.5.
- JSON also reported:
runner_readiness.kind = codex,runner_readiness.available = false,- repair text suggesting
codex --version,codex login, or switching to Claude.
Assessment:
- This is useful structured output, but it still leaves the machine configured to use an unavailable default runner.
- Combined with
initfailing before deterministic setup, this makes the happy path brittle for users who runsetup --yesin an environment where Codex is not actually ready.
Expected:
- Either
setup --yesshould fail when the chosen runner is unavailable, or it should configure a ready runner when one exists, or it should make the unavailable state impossible to miss in human output and next steps.
Severity: important setup/provider-readiness mismatch.
Evidence:
- First current-source smoke under temp HOME downloaded Python and dependencies.
- stderr included many
WARN Skipping file ...package-index messages before the CodeAlmanac command output.
Assessment:
- This is uv/tooling noise, not CodeAlmanac product output.
- It does mean current-source smoke through
uv tool run --fromis good for audit evidence but noisy for polished UX screenshots.
Severity: test-harness note, not a product discrepancy.
At this point, the audit has crossed into diminishing returns for this branch.
Concrete source and behavior evidence now covers the requested command families:
- job-starting commands:
init,ingest,garden, - queue inspection/control:
jobs,jobs show,jobs logs,jobs attach,jobs cancel, - worker internals:
__run-worker, - scheduled triggers:
sync,sync status,__garden-scheduler, - machine setup/configuration:
setup,automation,config,doctor,update, - read/organization commands:
search,show,topics,tag,untag,health,validate,reindex,serve, - deployed/current-source version mismatch,
- real provider-readiness smoke with current source.
Do not continue broad audit without a new concrete question. The next useful work should be deciding fixes and implementation order from these findings, not finding one more variant of the same mismatch.
The user clarified the queue decision: the important part is not merely that a
run is attached to jobs. The attached view must stream readable agent output
as it happens.
Expected behavior:
initcreates deterministic wiki scaffolding, registers the repo, queues a build run, and then the build command path takes over.build,ingest, andgardenshould be equivalent job operations at the run/job layer: queued background run, attachable job id, same prompt/manual machinery, same operation runner, same readable attached stream, same final success/failure semantics.buildmay do build-specific minimal scaffolding,ingestmay resolve source inputs, andgardenmay inspect index/health context. Those are payload differences, not different job semantics.jobs attach <run_id>should render normalized harness events continuously: assistant text deltas, tool starts/results, tool summaries, helper-agent events, provider session, usage, done/error.- The attached stream should use the same blue CodeAlmanac visual language as the queued-run output, but the stream body should be easy to read rather than raw numbered log rows.
Archived normalized event model:
archive/code/src/harness/events.tsdefined one provider-neutralHarnessEventunion withtext_delta,text,tool_use,tool_result,tool_summary,context_usage,provider_session,error,done,agent_spawned,agent_wait_started, andagent_completed.- Tool events carried a normalized display object: kind, title, path, command, cwd, status, exit code, duration, summary, provider thread/turn ids.
- Actor fields made root/helper-agent events distinguishable without exposing provider-specific raw payloads as the primary UI contract.
Current Python status:
src/codealmanac/services/harnesses/events.pyalready mirrors the archived normalized event model closely.- Claude and Codex adapters both consume streaming provider messages internally:
src/codealmanac/integrations/harnesses/claude/client.pyiterates the Claude stream, andsrc/codealmanac/integrations/harnesses/codex/app_server.pyreads Codex JSON-RPC notifications. - The adapter contract in
src/codealmanac/services/harnesses/ports.pystill returns one completedHarnessRunResult. OperationRunner.execute()records harness events only afterself.harnesses.run(...)returns.RunAttachStreamerpolls persisted run events, sojobs attachcan only show events after they are written to the run log.- Therefore the current code has the right normalized event vocabulary but does not yet stream provider output live through attached jobs.
Current build/ingest/garden equivalence:
- They are equivalent after a run is started: all render prompts from
BASE_KERNELplus an operation prompt, include manual inventory, and execute throughOperationRunner.execute(). - The queue layer supports all three through durable
RunSpecvalues and the run worker dispatchesBUILD,INGEST, andGARDEN. - They are not equivalent before queue/start:
BuildWorkflow.prepare()performs mutation tracking and harness readiness before the queued build run exists, while ingest/garden defer provider failure into the run path. - This explains the confusing init behavior observed in smoke testing: current
initcan fail before scaffolding/registering/queueing when the harness is unavailable, but the intended model is deterministic init scaffolding followed by an attachable build run whose failure is visible through jobs.
Implementation implication:
- Keep the normalized
HarnessEventmodel. - Change the harness/run boundary so providers can emit normalized events via a callback or iterator while the run is active.
- Record each emitted event immediately into the run log, then have
jobs attachrender those events with the polished blue stream UI. - Move build harness readiness out of pre-queue
prepare()and into the started build run path, so init/build/ingest/garden share job semantics.
The user clarified the product requirement again: init should become build,
and build, ingest, and garden should be equivalent queue-backed commands.
They should not have hidden pre-command differences that change
whether a run exists, whether a worker is assigned, or whether jobs attach
can explain the failure.
Readiness placement:
- Readiness should exist as an independent capability for
setup,doctor, and human guidance. - Blocking readiness for a queued job should happen after the worker
has claimed a queued run and after the run is marked
running. - If readiness fails, the failure should be a run event and terminal failed run,
visible through
jobs show,jobs logs, andjobs attach. - Readiness should not prevent
initfrom creating deterministic scaffolding, registering the repo, creating the queued run row, or printing the attach command.
Current code status:
- The normal CLI paths for
init,ingest, andgardenall go throughapp.workflows.queue.start_*. syncqueues ingest runs throughSyncIngestQueue, then spawns a worker.- The queue worker drains durable
RunSpecrecords and dispatchesBUILD,INGEST, andGARDEN. RunStore.queue()creates a durable queued run withspec_json.RunQueueWorker.run_queued()marks the queued run running throughOperationRunner.begin().
Queue bypass surfaces:
IngestWorkflow.run()still creates a run withRunsService.start()and immediately callsrun_started()in-process.GardenWorkflow.run()has the same direct synchronous path.- Those direct paths create queued-looking runs without durable
RunSpecand bypass worker assignment. - Tests still rely on the direct paths:
tests/test_ingest_workflow.pyandtests/test_garden_workflow.pycallapp.workflows.ingest.run(...)/app.workflows.garden.run(...). BuildWorkflowdoes not expose the same directrun()path, so the three commands are not internally symmetrical.
Build-specific pre-queue mismatch:
RunQueue.queue_build()callsBuildWorkflow.prepare().BuildWorkflow.prepare()rejects an existingalmanac/, checks harness readiness, registers the repository, and initializes the wiki before the queued run exists.tests/test_build_workflow.py::test_queue_build_fails_before_queueing_when_harness_is_brokenexplicitly preserves this old behavior.- That test conflicts with the clarified job requirement.
Archived behavior:
- Archive setup checked agent readiness during setup/doctor/default-agent selection, so readiness was still visible before normal operations.
- Archive jobs accepted an operation spec, wrote a queued job record and log,
spawned a worker, claimed the queued job, and passed an
onEventhook into the harness execution path. - Archive job execution wrote harness events as they happened through the job event logger.
- This is closer to the desired model than current Python: readiness guidance exists outside jobs, while command failure belongs inside the job.
Target architecture:
- Public command entry points should queue only:
start_build,start_ingest,start_garden. - Direct synchronous workflow
.run()methods should either be removed, made test-only private helpers, or converted to queue-and-drain helpers whose name makes the bypass explicit. - There should be one durable run path:
queued row with
RunSpec-> worker lock -> claim next queued -> mark running -> harness readiness -> live harness event recording -> terminal status. - Build-specific deterministic setup should be split from agent readiness: register/scaffold/queue first; readiness failure belongs to the queued build run.
These are the concrete agreements from the follow-up discussion.
- The user-facing commands are
init,ingest, andgarden. initis the user-facing command that creates the deterministic local wiki shell and then queues abuildrun.build,ingest, andgardenare equivalent run kinds once they enter the queue.- The equivalence means: same run table, same queue, same worker claim path, same attach/logs surface, same harness event streaming surface, and same terminal success/failure semantics.
- Their payloads differ:
- build receives new-wiki context and optional guidance,
- ingest receives source refs and source runtime context,
- garden receives existing wiki/index/health context.
- Those payload differences must not create different job behavior.
- There should be one normal way to run build/ingest/garden work: add a durable
RunSpecjob to the queue. - Public command APIs should queue work, not execute it synchronously.
IngestWorkflow.run()andGardenWorkflow.run()are bypass-shaped product paths and should be removed, made private test helpers, or converted to queue-and-drain helpers with explicit names.run_started(...)/execute_started(...)can remain as worker-internal methods only.- A run should move: queued -> claimed by worker -> running -> done/failed/cancelled.
- Only a running run should become done or failed.
- A queued run without a durable
RunSpecis not a valid job.
- Readiness is still a real capability.
setupanddoctorshould check readiness and explain what is ready or broken.- That setup/doctor readiness is advisory and diagnostic.
- Blocking readiness for
build,ingest, andgardenbelongs inside the running job, after the worker has claimed the queued run. - Readiness failure should produce run log events and a failed run that
jobs attach,jobs logs, andjobs showcan explain. - Readiness should not prevent
initfrom creating deterministic scaffolding, registering the repo, queueing the build run, or printing the attach command.
initshould do deterministic setup first:- resolve target repo,
- reject invalid target shape,
- register repo,
- create minimal
almanac/scaffold.
- Then
initqueues a build run. - Build-specific harness readiness must not happen before the queued run exists.
- The current
BuildWorkflow.prepare()mixes deterministic setup with harness readiness; that boundary should be split.
- Attached jobs must stream normalized harness events as they happen.
- The event vocabulary should stay provider-neutral:
text_delta,text,tool_use,tool_result,tool_summary,context_usage,provider_session,agent_spawned,agent_wait_started,agent_completed,done, anderror. - Harness adapters may understand Claude/Codex-specific protocols, but the run
log and attach renderer should consume normalized
HarnessEventvalues. - The current Python event model is close to the archived model.
- The current weakness is that adapters return a completed
HarnessRunResult, andOperationRunner.execute()records events after completion. - The target is live event emission: adapter emits normalized events during
execution -> operation runner records each event immediately ->
jobs attachrenders a readable blue CodeAlmanac stream.
- Keep the distinction between:
- queued jobs,
- harness service / harness event contract,
- provider-specific Claude/Codex adapters,
- setup/doctor readiness.
- Do not leak Claude SDK, Codex app-server, Codex CLI, or provider-specific protocol details into workflows or run services.
- Do not rename everything to provider. The harness boundary remains the product execution boundary; adapters hide provider details.
flowchart TD
subgraph CLI["CLI commands"]
Init["init"]
Ingest["ingest"]
Garden["garden"]
JobsAttach["jobs attach"]
SetupDoctor["setup / doctor"]
end
subgraph Setup["Setup and diagnostics"]
AdvisoryReady["advisory harness readiness"]
end
subgraph Queue["Run queue"]
Scaffold["init deterministic scaffold/register"]
QueueBuild["queue build RunSpec"]
QueueIngest["queue ingest RunSpec"]
QueueGarden["queue garden RunSpec"]
RunDB["runs DB: RunRecord + RunSpec + log events"]
end
subgraph Worker["Worker"]
Lock["acquire worker lock"]
Claim["claim next queued run"]
Running["mark run running"]
end
subgraph Operation["Operation execution"]
Context["build/ingest/garden context"]
Ready["blocking harness readiness"]
Execute["execute harness task"]
Finish["finish done/failed"]
end
subgraph Harness["Harness boundary"]
HarnessService["HarnessesService"]
EventContract["normalized HarnessEvent stream"]
Claude["Claude adapter"]
Codex["Codex adapter"]
end
subgraph UI["User-visible job UI"]
AttachStream["blue readable attach stream"]
Logs["jobs logs/show"]
end
Init --> Scaffold --> QueueBuild
Ingest --> QueueIngest
Garden --> QueueGarden
QueueBuild --> RunDB
QueueIngest --> RunDB
QueueGarden --> RunDB
RunDB --> Lock --> Claim --> Running
Running --> Context --> Ready --> Execute --> Finish
Finish --> RunDB
Ready --> HarnessService
Execute --> HarnessService
HarnessService --> EventContract
Claude --> EventContract
Codex --> EventContract
EventContract --> RunDB
JobsAttach --> AttachStream
RunDB --> AttachStream
RunDB --> Logs
SetupDoctor --> AdvisoryReady
AdvisoryReady -.does not gate queueing.-> QueueBuild
Commands create jobs. Workers run jobs. Readiness fails inside jobs. Harness adapters stream normalized events. Attach explains everything.
The user asked to remove the overprotective mutation safety gate entirely.
Agreement:
- Do not block jobs by snapshotting Git before/after the agent writes.
- Do not fail jobs because files outside
almanac/changed. - Do not add branch/HEAD safety checks.
- Do not treat user branch switching or concurrent user edits as a CodeAlmanac job failure.
- Trust the prompt and the model: build/ingest/garden agents should be guided to
write only inside
almanac/. - If something unexpected changes, the user can inspect the normal Git diff.
Current code to remove or simplify when implementing:
src/codealmanac/workflows/operations/mutation.pyOperationRunner.preflight(...)calls and event wording insrc/codealmanac/workflows/operations/service.pymutation_policy.validate(...)calls after harness execution.ensure_tracking_available(...)inBuildWorkflow.prepare().- Claude/Codex adapter Git before/after snapshots whose only purpose is enforcing changed-file safety.
Replacement rule:
- Jobs run the selected harness in the repo with clear instructions about the allowed wiki root.
- Job success/failure comes from runner availability, harness result, prompt result, and normal operation errors, not protective Git policing.
Use "runner readiness" for the selected Claude/Codex harness being locally usable.
Runner readiness means:
- Is the selected runner installed?
- Is it authenticated?
- Can its status command return within the timeout?
- If not, what repair command should the user run?
Current code:
src/codealmanac/services/harnesses/service.pyHarnessesService.readiness(...)returns readiness info.HarnessesService.ensure_ready(...)raises when unavailable.HarnessesService.run(...)currently callsensure_ready(...)before adapter execution.
src/codealmanac/integrations/harnesses/claude/adapter.pyClaudeSdkHarnessAdapter.check()runsclaude auth statusand falls back toANTHROPIC_API_KEY.
src/codealmanac/integrations/harnesses/codex/adapter.pyCodexAppServerHarnessAdapter.check()runscodex login status.
src/codealmanac/services/setup/service.pySetupService.runner_readiness(...)reports readiness through setup.
Architecture note:
setupanddoctormay show runner readiness as guidance.- Job queueing should not be blocked by runner readiness.
- The running job should perform blocking readiness, record the result, and fail visibly if the runner is unavailable.
The user clarified that terminal attach and Python viewer jobs should be inspired by the archived viewer, not by raw event streaming.
Agreement:
- Persist normalized harness events.
- Do not render them one-for-one as the primary UI.
- Project raw events into readable transcript steps first.
- Use that projection for both terminal
jobs attachand viewer job details.
Archived behavior to preserve:
archive/code/viewer/jobs-transcript.jsconverts normalized events into transcript steps.- Consecutive
text_delta/textevents from the same actor become one assistant bubble. done.resultcan be folded into the assistant bubble when it is the final assistant text.tool_useand matchingtool_resultbecome one tool card.- Tool cards infer kind, icon, title, target, preview, status, and error state.
tool_summaryandcontext_usageare hidden in normal mode and shown in debug mode.- helper-agent events become agent/status transcript rows.
- errors become error status rows with repair detail where available.
Archived projection support:
archive/code/src/jobs/projections/log-events.tsparses wrapped job log events and preserves sequence/timestamp/actor metadata.archive/code/src/jobs/projections/view.tsderives display title, subtitle, helper-agent traces, and job warnings.archive/code/viewer/jobs-view.jsrenders the projected transcript, not raw event JSON.
Current Python gap:
src/codealmanac/cli/render/job_logs.pyprints raw numbered log rows.src/codealmanac/services/viewer/jobs.pyexposes raw run events.src/codealmanac/server/assets/viewer/jobs.jsrenders event rows and dumps harness fields.- There is no shared Python projection layer that turns
RunLogEvent+HarnessEventinto assistant bubbles, tool cards, status rows, warnings, and agent traces.
Target render architecture:
- Add a run transcript projection in Python services, near the run/job read model rather than inside terminal or browser UI.
- The projection should output stable UI-neutral transcript steps: assistant message, tool card, status row, error row, agent row.
- Terminal attach should render these projected steps in the blue CodeAlmanac style.
- Viewer jobs should render the same projected steps in HTML.
The user clarified that the product does not need a raw/debug event surface.
Revised target:
- Raw normalized events remain an internal persisted log format.
- The user-facing terminal and viewer should show the actual projected transcript only.
- Do not bring over archive's debug/raw event UI as product behavior.
- Bring over the best part of archive: grouping raw events into readable assistant bubbles, tool cards, status rows, and error rows.
- Treat archive's older product vocabulary and raw-event-first surfaces as retired thinking.