Skip to content

feat(spec-5a): operational runtime — async/bg + scheduling + git-worktree isolation - #6

Merged
rz1989s merged 18 commits into
mainfrom
feat/spec-5a-operational-runtime
Jul 24, 2026
Merged

rz1989s merged 18 commits into
mainfrom
feat/spec-5a-operational-runtime

Conversation

@rz1989s

@rz1989s rz1989s commented Jul 24, 2026

Copy link
Copy Markdown
Member

SPEC-5a — Operational runtime

The 6th of 9 pipeline phases (PRD + research + SPEC-1..4 done; this PR → v0.5.0). Layers three capabilities above the unchanged SPEC-1..4 engine seam (ChildSessionFactory/BackendRegistry/runLifecycle — untouched):

  1. Async/background runs — fire a subagent/lifecycle without awaiting; runs on an isolated git worktree; a JSONL journal records progress; a crashed pi auto-resumes interrupted runs on next project open.
  2. Scheduling — cron / interval / one-shot schedules, session-scoped + PID-locked, fired in-process.
  3. Git-worktree isolation — one worktree per async/bg lifecycle, auto-committed to a branch on completion; worktree-diff discovers phase artifacts structurally (replaces the fragile prompt-baked Artifacts block for isolated runs).

Foreground sync subagent (v0.1..0.4) is unchanged — zero breaking change.

Brainstorm → spec → plan → implement (full cycle)

  • Brainstorm: 9 Q&A, locked (Q1=B Q2=A Q3=A Q4=A Q5=A Q6=C Q7=A Q8=A Q9=A) — specs/SPEC-5a-operational-runtime.md
  • Plan: 15 TDD tasks — plans/SPEC-5a-operational-runtime.md
  • Implement: 15 tasks + 1 integration fix, inline (executing-plans skill)

What's in it

  • src/worktree/ — WorktreeService (git worktree create/remove) + DiffService (worktree-diff artifact discovery: tracked + untracked)
  • src/runtime/ — RunJournal (JSONL append/replay/partial-line-skip/scan) + ConcurrencyPool (N-slot semaphore, default 3) + ResultsInbox (delivery queue + bounded hint) + AsyncRunner (the bg path) + resume (scan non-terminal journals)
  • src/scheduling/ — expressions (cron/interval/one-shot) + PidLock (session-scoped firing ownership + stale reclaim) + Scheduler (in-process firing)
  • src/vendor/cron-parser/ (vendored MIT, v1.1.1 dep-free — see deviation note) + NOTICE.md
  • src/tools/subagent.ts — background + schedule params (routing)
  • src/tools/fleet-results.ts — fleet.results({ runId? }) tool
  • src/panel/fleet-panel.ts + rows.ts — new scheduled tab + bg row status icons (▶ ⏸ ✓ ✗ ⏳ ●phase n/total)
  • src/index.ts — async runner + scheduler + resume-on-init + fleet.results tool wiring
  • scripts/spec-5a-smoke.mts + docs/SPEC-5a-smoke-checklist.md — manual end-to-end + TUI smoke

Gate

  • pnpm typecheck — clean
  • pnpm test:run227/227 pass (was 172 at branch start; +55 SPEC-5a tests)
  • All existing SPEC-1..4 tests pass unchanged (additive only)

Deviations from the plan (recorded)

  1. cron-parser version: the plan assumed v1.x/v4.x. v5.6.2 (latest) pulls in luxon at load time — vendoring just its dist wasn't self-contained. Pinned to v1.1.1 (latest 1.x, MIT, zero runtime deps, 4-file lib/ with all-relative requires). NOTICE.md records the version + the v2+-adds-luxon note. Cleaner vendoring than anticipated.
  2. Scheduler timing tests: node:test has no fake-timer support, so the fire-once/interval tests use real ~1-3s waits. Deterministic now; if CI flakes, a clock abstraction is the follow-up.
  3. bgRuns live map: added an onProgress callback to AsyncRunnerDeps (not in the original plan) so the /fleet fleet-tab bg rows actually populate as runs progress — a gap the plan didn't specify.

Design decisions (locked, do not re-litigate)

  • Q1=B session-bound process + durable JSONL state + auto-resume (no daemon)
  • Q2=A per-lifecycle worktree, async/bg-only; foreground unchanged (no breaking change)
  • Q3=A worktree-diff for isolated runs; prompt-baked Artifacts retained for foreground
  • Q4=A two concurrency pools (foreground single-slot unchanged; async/bg N-slot, default 3)
  • Q5=A cron + interval + one-shot; session-scoped, PID-locked, in-process; no catch-up
  • Q6=C notify + results inbox + fleet.results() tool; bounded "N results ready" hint
  • Q7=A JSONL journal per run (append-only, partial-line skip); schedules in schedules.json
  • Q8=A minimal TUI — scheduled tab + bg row status; no live widget (SPEC-5b)
  • Q9=A vendor cron-parser (MIT); worktree lifecycle greenfield

Deferred (recorded with landing SPEC)

  • FleetView, live widget, conversation viewer, mid-run steering → SPEC-5b
  • Cost-aware tiers, quality gates, workflows-as-code, event-bus/RPC → SPEC-6

Release

After merge: bump to 0.5.0 on main + tag v0.5.0 → release.yml publishes @getpipher/armory-fleet@0.5.0 (mirrors v0.3.0/v0.4.0). Then the term-driven TUI smoke (docs/SPEC-5a-smoke-checklist.md).

rz1989s added 18 commits July 24, 2026 22:50
…tree isolation)

Brainstorm output (9 Q&A, locked: Q1=B Q2=A Q3=A Q4=A Q5=A Q6=C Q7=A Q8=A Q9=A).
Layer above the unchanged SPEC-1..4 engine seam: async/bg runs on isolated
git worktrees (per-lifecycle, foreground unchanged), cron/interval/one-shot
scheduling (session-scoped, PID-locked, in-process), JSONL run journal +
auto-resume after crash, worktree-diff artifact discovery (replaces the
prompt-baked Artifacts block for isolated runs), results inbox + fleet.results()
auto-delivery, /fleet scheduled tab + bg row status. Vendored cron-parser (MIT)
+ greenfield worktree service. Targets v0.5.0.
…g + scheduling)

15 tasks: WorktreeService, DiffService, RunJournal, ConcurrencyPool,
ResultsInbox, vendor cron-parser + expressions, PidLock, Scheduler,
AsyncRunner, Resume, subagent background/schedule params, fleet.results tool,
/fleet scheduled tab + bg row status, index wiring + resume-on-init,
end-to-end smoke + TUI checklist. Self-reviewed (spec coverage, placeholders,
type consistency — one cross-task amendment flagged: WorktreeService.pathFor
public exposure).
…overy) + clean up completed-run worktrees

Code-review findings (self-review before merge):
- Important: Q3=A was not realized — DiffService was in AsyncRunnerDeps but never called; isolated
  runs used the same parseArtifacts path as foreground. Added an optional artifactDiscovery hook
  to LifecycleRunDeps + worktreePath/baseRef to LifecycleRunOpts (additive — foreground unchanged).
  The index.ts async adapter now wires DiffService.diffPhase as the discovery fn for isolated runs.
  +2 tests (hook used when worktreePath set; NOT used for foreground).
- Important: completed runs leaked the worktree dir (.pi/fleet/worktrees/<runId> accumulated).
  Added WorktreeService.removeWorktree (git worktree remove, KEEPS the branch for merge/inspection);
  the async runner calls it on completion. +1 test (worktree dir gone, branch kept).
- Minor: dropped unused existsSync import in async-runner.ts.
@rz1989s
rz1989s merged commit 52e3477 into main Jul 24, 2026
1 check passed
@rz1989s
rz1989s deleted the feat/spec-5a-operational-runtime branch July 24, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant