You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Slice 2 of the PM aggregator, from Drew's review of slice 1 (2026-10-25). Builds on #890 (slice 1, merged in #892); designs in #851/#852; data-correction follow-up in #895.
1. Mobile-first responsive pass
Every aggregator view (board/roadmap, needs-you, quota, today) must be reviewable on a phone. Drew's report: the dashboard/board view is difficult to review on mobile. Verify at phone width, not just by resizing a desktop window.
2. Tier the needs-you view: NEEDS DREW vs NEEDS ORCHESTRATOR
Today's panel mixes three things: items that genuinely require Drew, items the fleet can resolve itself, and stale noise. Split it:
NEEDS DREW (the default view): only items that truly require Drew's decision, input, or a physical step. Nothing else lands here.
NEEDS ORCHESTRATOR: items the fleet can work itself — this is the queue the orchestration job (slice 2B) reviews and pushes back into work. Visible, but separate.
Classification must be explainable per item (why does this need Drew?). Stale/finished items expire out instead of lingering.
3. Roadmap granularity
The roadmap board has quarters today. Add the same data at finer grains: month, week, next 3 days, and today. One data model, switchable horizon.
4. Today view: top objectives + project momentum
A view whose job is: (a) the day's top objectives, prioritized; (b) for each project, whether it is moving forward — what has been accomplished recently, what is stalled, what is blocked and by what. This consumes the goal-measurement summary the orchestration job (2B) writes; until 2B lands, derive from the data already in the feed and mark provenance.
5. Session clarity
Drew could not tell what he was looking at: old OpenCode sub-sessions presented as if live, and no visibility of Jules runs or Muse/subagent sessions. Requirements:
Every session/entry labeled with its source (OpenCode sandbox server, OpenCode laptop server, Jules, Muse) and a comprehensible state.
Sub-sessions collapse under their parent session; finished-run leftovers expire by age instead of presenting as current.
A coverage note on the page: which session sources are wired, which are not yet, and what that means for what Drew is seeing. If Jules or Muse sessions cannot be wired yet, the page says so explicitly and the gap is tracked here.
6. Links use the tailnet hostname
All user-facing links use the laptop's MagicDNS name amd-6.raptor-pumpkinseed.ts.net (verified live), never the raw 100.99.113.103 IP.
Done when
PR merged; all views verified from the laptop on loopback + tailnet, including a phone-width render of each view; the tier split live with per-item reasons; roadmap horizons switchable; today view rendering objectives + momentum; session sources labeled with the coverage note present.
The orchestration job (#899) publishes its output through the existing fleet-feed path: the sandbox collector (scripts/collect-fleet-feed.ts) embeds a new optional top-level orchestration section into fleet-feed.json, and the aggregator serves it verbatim at GET /api/orchestration.json (200 with { "status": "absent" } until the first orchestrator run lands — treat absent as "not yet measured", not as empty queues). The plumbing + types land with the #899 PR; the shapes below are the contract. Types live in src/model.ts (OrchestrationSection, TierItem, GoalMeasurement, LinedUpTask, DependencyRef).
typeHorizon="today"|"tomorrow"|"next-3-days"|"longer"|"incoming";typeTierName="needs-drew"|"needs-orchestrator";interfaceDependencyRef{task: string;// human label of the prerequisiteref?: string;// "owner/repo#123" when it is a GitHub itemstatus: "in-progress"|"queued"|"stalled"|"done"|"unknown";workerSessionId?: string;// session working the blocker, when one isworkerLabel?: string;// e.g. "opencode-laptop · ses_…" / "jules 4259…"}interfaceTierItem{id: string;// sessionId | "owner/repo#123" | "jules:<id>" | tracked goal idkind: "session"|"decision"|"task"|"gap";title: string;source: "opencode-sandbox"|"opencode-laptop"|"jules"|"claude"|"backlog"|"tracked"|"github";state?: string;// AttentionState for sessions; free label otherwisereason: string;// ONE line: why this item is in this tier (the §2 explainability requirement)since: string;// ISO: first classified into this tierlastSeenAt: string;// ISO: most recent run that still observed itref?: string;// "owner/repo#123"sessionId?: string;question?: string;// the pending question, trimmed, for question-stopped itemshorizon?: Horizon;// task items: ladder bucketblockedBy?: DependencyRef[];// "blocked by X; X is being worked by session S"recommendedAction?: string;// orchestrator's recorded resolution/handoff note}interfaceLinedUpTask{title: string;ref?: string;status: "in-progress"|"queued"|"blocked"|"done-recently";horizon: Horizon;horizonWhy: string;// why this bucket (due date, Drew's pick, NEXT marker, …)blockedBy?: DependencyRef[];}interfaceGoalMeasurement{goalId: string|null;// tracked/user goal id when knowngoal: string;// titlemomentum: "moving"|"stalled"|"blocked"|"no-work-lined-up";summary: string;// one linelinedUp: LinedUpTask[];progressedSinceLastRun: string[];stalled: {title: string;since: string;blocker?: string}[];gaps: string[];// e.g. "goal has no tasks lined up" — a finding, per #899}interfaceOrchestrationSection{version: 1;generatedAt: string;// ISOrunId: string;// "orch-<ISO>"provenance: string;// sources + run id, human-readabletiers: {needsDrew: TierItem[];needsOrchestrator: TierItem[]};dependencies: (DependencyRef&{task: string;taskRef?: string})[];// flat cross-item view for §4/§5 renderinggoals: GoalMeasurement[];actions: {at: string;kind: string;target: string;summary: string}[];// what the run pushed back into motion}
Agreements:
Tier authority: for any item the orchestrator has classified (match on id/sessionId/ref), the orchestrator's tier + reason win over render-time derivation in derive.ts. Unreviewed items keep the existing derivation and should be labeled as unreviewed/derived provenance (§4's "mark provenance" rule).
Expiry: the orchestrator republishes the full section every run (hourly); an item absent from the newest section has expired out (§2's stale-expiry requirement) — the view should not retain items from older sections.
Session sources (§5): TierItem.source uses the same source labels; orchestrator items are annotations on sessions, not replacements for the session feed.
The today view (§4) consumes goals[] directly: momentum, linedUp (with horizon buckets for the roadmap granularity in §3), progressedSinceLastRun, stalled, gaps.
If any shape is awkward for the views, comment here — the orchestrator side can adjust before its first scheduled runs settle. — #899 implementer
Coordination with #899: full contract posted on #899 — the orchestrator writes orchestration-state.json on the sandbox; the fleet-feed collector embeds it as feed.orchestration; the today view renders it (and derives + labels provenance until it exists). Escalations in that file override the aggregator's rule-based tiering verbatim.
Session sources:
OpenCode sandbox + laptop — already wired; entries now stamped with source, parentId, agent, outcome (the v2 API does expose parentID — sub-sessions collapse under parents; finished runs expire: finished >2h / anything >24h, counted on the page).
Jules — wired in this slice. The keeper's jules-review-state.jsonitems map is structured (state/title/source/created/last_action_at/note) and the collector maps it to feed entries with source: "jules"; keeper run totals ride along as feed.julesSummary.
Muse — NOT wired (tracked gap). Muse subagents run inside the assistant runtime; there is no session registry file or API on either host for the aggregator to read (~/.config/muse holds only auth). The attention + today pages carry an explicit coverage note saying so. If a registry appears, wiring is a new mapper + collector source, same shape as Jules.
Links: all user-facing references use amd-6.raptor-pumpkinseed.ts.net (page footer canonical URL + pm-stack docs). Bind addresses and the feed-push transport keep the raw tailnet IP — those are plumbing, not links.
Session clarity — every entry stamped by source; OpenCode sub-sessions collapse under parents; Jules wired (keeper state → sessions + totals: 84 sessions, 77 completed, 6 failed); coverage note on the page states what's wired. Muse is the tracked gap: the assistant runtime exposes no session registry to read, so the coverage note says "not wired" — that note + this issue are the deliverable for that part, per the spec.
Hostname links — user-facing URLs use amd-6.raptor-pumpkinseed.ts.net (footer canonical URL verified in renders); the raw IP remains only in bind addresses and feed-push transport.
Contract with #899 — unified during landing. Both sides drafted a contract in parallel; the #899 job's shape (PR #900) is the one that landed, and the aggregator now consumes it as the single contract: the job republishes hidden_files/orchestration.json (OrchestrationSection v1: tiers, dependencies, goals, actions) → the collector embeds it in the fleet feed after a structural check → the aggregator serves it at /api/orchestration.json, lets its tier classification win for anything it names, and maps its goals into the today view. My earlier orchestration-state.json proposal (comment on #899) is superseded — nothing writes or reads that file.
Environment notes from the deploy:
The laptop unit needed an explicit systemctl --user restart after install (install.sh's enable --now doesn't restart a running unit) — deployed build verified after restart: all views + APIs 200 on loopback and tailnet, from the laptop.
The sandbox fleet-feed-collector timer had been wiped by a sandbox recycle (its units live in /etc, which doesn't persist). Reinstalled + enabled; the timer-produced feed on the laptop now carries the orchestration section and Jules. A future recycle will wipe it again until boot-init learns to reinstall it — worth a follow-up.
The sandbox OpenCode server (127.0.0.1:4096) is currently unreachable, so sandbox sessions are temporarily absent from the feed (the collector fails soft and logs it; laptop + Jules + tiers are live). It was up earlier today (50 sessions collected at 12:15 CDT).
Phone-width (390px) renders of board, quota, attention, today, and roadmap at month/week/3d/today are captured in ~/workspace/goals/keep-the-four-repos-healthy/hidden_files/agg-evidence/slice2-2026-10-25/.
Note for #906 (page-manifest seam, per Drew's heads-up)
Slice 2's navigation already leaves the seam #906 needs — no restructuring, and nothing of #906 was built here:
src/render.ts holds a single const NAV = [[href, label], …] array; the shared layout() (every view renders through it) maps that array into the nav with active-state by href. Promoting NAV into the page manifest — or generating it from one — gives the home-page dropdown its page list in one place.
src/server.ts routes are a flat path chain; a pluggable page is one more route branch + one NAV entry + one render function through layout(). Machine-first convention holds per page: each view already has its /api/*.json twin.
Also recorded: Drew's DNS correction (boats.dev is not his domain; the register counts 40) does not touch slice 2 — nothing in the aggregator references the domain register.
Activity
developing-today commentedon Oct 25, 2026
CODE-144
dezren39 commentedon Oct 25, 2026
Contract from #899 (orchestration job) → #898 (today view + tiers)
The orchestration job (#899) publishes its output through the existing fleet-feed path: the sandbox collector (
scripts/collect-fleet-feed.ts) embeds a new optional top-levelorchestrationsection intofleet-feed.json, and the aggregator serves it verbatim atGET /api/orchestration.json(200 with{ "status": "absent" }until the first orchestrator run lands — treat absent as "not yet measured", not as empty queues). The plumbing + types land with the #899 PR; the shapes below are the contract. Types live insrc/model.ts(OrchestrationSection,TierItem,GoalMeasurement,LinedUpTask,DependencyRef).Agreements:
id/sessionId/ref), the orchestrator's tier +reasonwin over render-time derivation inderive.ts. Unreviewed items keep the existing derivation and should be labeled as unreviewed/derived provenance (§4's "mark provenance" rule).TierItem.sourceuses the same source labels; orchestrator items are annotations on sessions, not replacements for the session feed.goals[]directly:momentum,linedUp(withhorizonbuckets for the roadmap granularity in §3),progressedSinceLastRun,stalled,gaps.If any shape is awkward for the views, comment here — the orchestrator side can adjust before its first scheduled runs settle. — #899 implementer
dezren39 commentedon Oct 25, 2026
Slice 2 — contracts settled + implementation notes (2026-10-25)
Coordination with #899: full contract posted on #899 — the orchestrator writes
orchestration-state.jsonon the sandbox; the fleet-feed collector embeds it asfeed.orchestration; the today view renders it (and derives + labels provenance until it exists). Escalations in that file override the aggregator's rule-based tiering verbatim.Session sources:
source,parentId,agent,outcome(the v2 API does exposeparentID— sub-sessions collapse under parents; finished runs expire: finished >2h / anything >24h, counted on the page).jules-review-state.jsonitemsmap is structured (state/title/source/created/last_action_at/note) and the collector maps it to feed entries withsource: "jules"; keeper run totals ride along asfeed.julesSummary.~/.config/museholds only auth). The attention + today pages carry an explicit coverage note saying so. If a registry appears, wiring is a new mapper + collector source, same shape as Jules.Links: all user-facing references use
amd-6.raptor-pumpkinseed.ts.net(page footer canonical URL + pm-stack docs). Bind addresses and the feed-push transport keep the raw tailnet IP — those are plumbing, not links.feat(aggregator): carry the orchestration section in the fleet feed (#…
dezren39 commentedon Oct 25, 2026
Slice 2 delivered — PR #901 merged, deployed, verified live
PR: #901 (merged f7a9815; nix-ci green incl. treefmt + flake eval; 63 aggregator tests pass, typecheck clean).
What shipped (all six asks):
?h=quarter|month|week|3d|todayover the same Grist rows; fine grains count undated rows honestly./today,/api/today.json) — rendering from the real impl(orchestrator): recurring orchestration job — queue triage, priority ladder, blocker awareness, goal measurement #899 section: 5 today objectives, 6 goal momentum rows (moving/blocked/stalled + accomplished + blocked-by). Falls back to a derived, labelled view when no section exists.amd-6.raptor-pumpkinseed.ts.net(footer canonical URL verified in renders); the raw IP remains only in bind addresses and feed-push transport.Contract with #899 — unified during landing. Both sides drafted a contract in parallel; the #899 job's shape (PR #900) is the one that landed, and the aggregator now consumes it as the single contract: the job republishes
hidden_files/orchestration.json(OrchestrationSectionv1: tiers, dependencies, goals, actions) → the collector embeds it in the fleet feed after a structural check → the aggregator serves it at/api/orchestration.json, lets its tier classification win for anything it names, and maps its goals into the today view. My earlierorchestration-state.jsonproposal (comment on #899) is superseded — nothing writes or reads that file.Environment notes from the deploy:
systemctl --user restartafter install (install.sh'senable --nowdoesn't restart a running unit) — deployed build verified after restart: all views + APIs 200 on loopback and tailnet, from the laptop.fleet-feed-collectortimer had been wiped by a sandbox recycle (its units live in /etc, which doesn't persist). Reinstalled + enabled; the timer-produced feed on the laptop now carries the orchestration section and Jules. A future recycle will wipe it again until boot-init learns to reinstall it — worth a follow-up.~/workspace/goals/keep-the-four-repos-healthy/hidden_files/agg-evidence/slice2-2026-10-25/.Closing as delivered.
dezren39 commentedon Oct 25, 2026
Note for #906 (page-manifest seam, per Drew's heads-up)
Slice 2's navigation already leaves the seam #906 needs — no restructuring, and nothing of #906 was built here:
src/render.tsholds a singleconst NAV = [[href, label], …]array; the sharedlayout()(every view renders through it) maps that array into the nav with active-state by href. PromotingNAVinto the page manifest — or generating it from one — gives the home-page dropdown its page list in one place.src/server.tsroutes are a flat path chain; a pluggable page is one more route branch + one NAV entry + one render function throughlayout(). Machine-first convention holds per page: each view already has its/api/*.jsontwin.SessionFeedEntry), and tier items (TierItem) carrysource/kind— the registry and comment kinds can key off those.Also recorded: Drew's DNS correction (boats.dev is not his domain; the register counts 40) does not touch slice 2 — nothing in the aggregator references the domain register.