Skip to content

refactor(store): WIP one RunStore per agent run, FlowStore chains them - #1282

Closed
gewenyu99 wants to merge 3 commits into
posthog/split-p5-harnessfrom
posthog/split-p6-run-store
Closed

gewenyu99 wants to merge 3 commits into
posthog/split-p5-harnessfrom
posthog/split-p6-run-store

Conversation

@gewenyu99

Copy link
Copy Markdown
Collaborator

One RunStore per agent run; FlowStore chains runs on top and mirrors the active one.

Stacked on #1278. Evidence in the comments.


Created with PostHog Desktop

A RunStore holds the state of one agent run. FlowStore owns the flow, gates,
interrupts, and the session every run inherits, starts runs with startRun, and
mirrors the active run. Runners and control hooks build the task stream per run.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
@gewenyu99

Copy link
Copy Markdown
Collaborator Author

Evidence: one RunStore per agent run, FlowStore chains them

A store is the state of one agent run. The flow that chains runs, its gates, interrupts, and the session every run inherits live in FlowStore, which starts each run with startRun(session) and mirrors the active one. The task stream is store level: one TaskStreamPush over one RunStore. Nothing changes on the wire: GET /state still reads the flow store, and a ledger record is still that state at run end.

Gates

  • pnpm typecheck: clean. pnpm lint: 0 errors. pnpm test: 213 files, 3259 tests, all green.
  • pnpm build and pnpm build:ci: both smoke tests pass; the chunk manifest fixtures are unchanged.
  • Goldens: frames, flow traces, analytics sequences, decision traces, and the harness snapshots are byte identical. The one snapshot that changed is the architecture boundary list: resetRunState leaves, startRun enters.
  • WizardStore is renamed FlowStore everywhere; git grep WizardStore and git grep resetRunState return nothing.

One store per run: why each piece exists

Top to bottom is the lifetime of a run: who starts it, what outlives it, what is only its own, and who writes it. A store is one agent run; the flow that chains runs sits above it.

flowchart TB
  classDef wave_entry fill:#fff8e6,stroke:#d9a400,stroke-width:2px,color:#d9a400;
  classDef card_entry fill:#fff1c7,stroke:#d9a400,stroke-width:1px,color:#1d1d1f;
  classDef wave_cli fill:#fff1e8,stroke:#e8632c,stroke-width:2px,color:#e8632c;
  classDef card_cli fill:#ffe1cc,stroke:#e8632c,stroke-width:1px,color:#1d1d1f;
  classDef wave_tui fill:#e9f0ff,stroke:#2f6fed,stroke-width:2px,color:#2f6fed;
  classDef card_tui fill:#d6e4ff,stroke:#2f6fed,stroke-width:1px,color:#1d1d1f;
  classDef wave_agent fill:#f1e9ff,stroke:#7a3fe0,stroke-width:2px,color:#7a3fe0;
  classDef card_agent fill:#e6d8ff,stroke:#7a3fe0,stroke-width:1px,color:#1d1d1f;
  classDef wave_store fill:#e8f8ee,stroke:#1f9d55,stroke-width:2px,color:#1f9d55;
  classDef card_store fill:#d4f2df,stroke:#1f9d55,stroke-width:1px,color:#1d1d1f;
  classDef wave_control fill:#e4f5f3,stroke:#148f7a,stroke-width:2px,color:#148f7a;
  classDef card_control fill:#cfeeea,stroke:#148f7a,stroke-width:1px,color:#1d1d1f;
  classDef wave_harness fill:#f2f2f2,stroke:#6e6e73,stroke-width:2px,color:#6e6e73;
  classDef card_harness fill:#e6e6e6,stroke:#6e6e73,stroke-width:1px,color:#1d1d1f;
  classDef wave_guard fill:#fbe9e7,stroke:#c0392b,stroke-width:2px,color:#c0392b;
  classDef card_guard fill:#f8d7d2,stroke:#c0392b,stroke-width:1px,color:#1d1d1f;
  subgraph W1["WHO CHAINS RUNS: the flow runner in the TUI process, or the parent on the socket"]
    direction LR
    W1C1("<b>src/cli/runners</b><br/>sequence independent runs: scope the session,<br/>start a RunStore, hand it to the agent and the stream")
    W1C2("<b>control-hooks.ts  startRun</b><br/>the same chain for a parent: POST /runs is one<br/>fresh RunStore, one agent run, one ledger record")
    W1C3("<b>src/tui screens</b><br/>commit setup and answers on the FlowStore; see<br/>the active run through its mirrored session")
    W1C1 ~~~ W1C2 ~~~ W1C3
  end
  class W1 wave_cli
  class W1C1,W1C2,W1C3 card_cli
  subgraph W2["src/store/state  FlowStore: everything that outlives a run"]
    direction LR
    W2C1("<b>flow · gates · interrupts</b><br/>the screens as data, what latches once,<br/>what overlays: the shape of the whole session")
    W2C2("<b>cross-run session</b><br/>credentials, detection, setup flags, and the dashboard<br/>and notebook a run created: inherited by every later run")
    W2C3("<b>active run mirror</b><br/>session overlays RUN_SESSION_KEYS from the run;<br/>each run commit re-emits as one flow commit")
    W2C1 ~~~ W2C2 ~~~ W2C3
  end
  class W2 wave_store
  class W2C1,W2C2,W2C3 card_store
  subgraph W3["src/store/state  RunStore: one agent run, nothing before or after it"]
    direction LR
    W3C1("<b>session copy</b><br/>runPhase, outroData, pendingQuestion, taskNotice,<br/>mintHandoff, settings conflicts: this run only")
    W3C2("<b>run atoms</b><br/>tasks, status messages, event plan, handoff<br/>text, token usage: empty when the run starts")
    W3C3("<b>resolvers</b><br/>the questions and notices this run asked;<br/>answered on this run, so a new run cannot inherit them")
    W3C1 ~~~ W3C2 ~~~ W3C3
  end
  class W3 wave_store
  class W3C1,W3C2,W3C3 card_store
  subgraph W4["WHO WRITES AND PUBLISHES ONE RUN"]
    direction LR
    W4C1("<b>src/agent  runAgent</b><br/>reports through WizardUI; StoreUI turns<br/>every call into a RunStore commit")
    W4C2("<b>src/store/task-stream</b><br/>store level: TaskStreamPush over one RunStore,<br/>shut down before the ledger records the result")
    W4C3("<b>src/store/control</b><br/>GET /state reads the FlowStore mirror;<br/>RunRecord.result is that state when the run ended")
    W4C1 ~~~ W4C2 ~~~ W4C3
  end
  class W4 wave_agent
  class W4C1,W4C2,W4C3 card_agent
  W1 -->|"FlowStore.startRun(session) → RunStore<br/>the flow hands each run its own store; the run never learns about the flow or the run before it"| W2
  W2 -->|"subscribe (run commits bubble up) · delegating setters (run keys go down)<br/>FlowStore.setRunPhase, setTasks, requestQuestion, … act on the active run, adding the interrupt and analytics"| W3
  W3 -->|"WizardUI (StoreUI writes) · RunStateSource (the stream reads) · run.session (the agent reads)<br/>three consumers of one RunStore; none of them can see another run"| W4
Loading

Interfaces, one sheet each

┌─ RunStore ───────────────────────────────────────────────────────────────────────────────────┐
│ src/store/state/run-store.ts · the state of one agent run                                    │
│                                                                                              │
│ new RunStore(session)    started by FlowStore.startRun as { ...inherited,                    │
│   ...RUN_SESSION_DEFAULTS }                                                                  │
│ RUN_SESSION_KEYS: runPhase · outroData · outroDismissed · pendingQuestion · taskNotice ·     │
│   mintHandoff ·                                                                              │
│                   settingsConflicts · settingsOverrideKeys   (dashboardUrl, notebookUrl stay │
│   on the flow)                                                                               │
│ atoms: tasks · statusMessages · eventPlan · handoffText · tokenUsage (+ finalTokenCostUsd)   │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ · setters: setRunPhase, setOutroData, setTasks, updateTask, syncTodos, pushStatus,           │
│   addTokenUsage, setEventPlan, …                                                             │
│ · requestQuestion / showTaskNotice / showSettingsOverride keep their resolver on the run;    │
│   the flow adds the interrupt                                                                │
│ · subscribe, getSnapshot, version: the same contract as the FlowStore; every commit bumps    │
│   the version once                                                                           │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ FlowStore ──────────────────────────────────────────────────────────────────────────────────┐
│ src/store/state/store.ts · the flow, the cross-run session, and the run it mirrors           │
│                                                                                              │
│ new FlowStore(flow)    startRun(session): RunStore    get run    get session = { ...flow,    │
│   ...pickRunSession(run) }                                                                   │
│ flow: switchProgram · getGate · waitUntil · pushInterrupt · popInterrupt · currentScreen ·   │
│   requestRun · run hooks                                                                     │
│ delegated to the active run: setRunPhase · setTasks · setOutroData · pushStatus ·            │
│   addTokenUsage · syncTodos · …                                                              │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ · startRun cancels a pending question and notice, drops the old subscription, subscribes the │
│   new run, emits once                                                                        │
│ · set session writes flow keys to the flow and run keys to the active run (pickRunSession);  │
│   no key lands twice                                                                         │
│ · requestQuestion = run.requestQuestion + pushInterrupt + analytics; resolvePendingQuestion  │
│   pops the interrupt                                                                         │
│ · completeRunStep(stepId) records the composed step and idles the run; resetRunState no      │
│   longer exists                                                                              │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ RunStateSource ─────────────────────────────────────────────────────────────────────────────┐
│ src/store/task-stream/types.ts · what the task stream reads                                  │
│                                                                                              │
│ RunStateSource = Pick<RunStore, 'session' | 'tasks' | 'eventPlan' | 'handoffText' |          │
│   'subscribe' | 'setEventPlan'>                                                              │
│ TaskStreamPush({ store: RunStateSource, programId, skillId?, destinations, … })              │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ · built per run by the runner (beginRun) or the hook (runStream(config, run)); attached      │
│   before the agent starts                                                                    │
│ · shut down after the run settles and its cleanups ran, before RunRecord.result is read      │
│ · a fake with those six members drives the stream tests; no FlowStore is needed to test      │
│   publishing                                                                                 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─ beginRun ───────────────────────────────────────────────────────────────────────────────────┐
│ src/cli/runners/*.ts and src/cli/control-hooks.ts · how every run starts                     │
│                                                                                              │
│ run-wizard.ts            beginRun(runConfig, runSession): RunStore   start the run, build    │
│   and attach its stream                                                                      │
│ run-non-interactive.ts   beginRun(): RunStore  headless; the controlled surface starts runs  │
│   from the hook                                                                              │
│ control-hooks.ts         startRun(req): store.startRun(runSession) →                         │
│   run.setRunPhase(Running) → stream → runAgent                                               │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ · runAgent(runConfigFor(config), run.session, { composed }): the agent gets the run's own    │
│   session copy                                                                               │
│ · a composed flow (self-driving) starts a new RunStore per run step; completeRunStep marks   │
│   the step done                                                                              │
│ · the ledger records RunRecord.result = readState() after the stream has shut down           │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

The stores in a TUI process and what each is attached to

Boxes are objects, not layers. Every edge is the call that attaches one to the other, with its file and line.

flowchart TB
  classDef n_entry fill:#fff1c7,stroke:#d9a400,stroke-width:1.5px,color:#1d1d1f;
  classDef n_cli fill:#ffe1cc,stroke:#e8632c,stroke-width:1.5px,color:#1d1d1f;
  classDef n_tui fill:#d6e4ff,stroke:#2f6fed,stroke-width:1.5px,color:#1d1d1f;
  classDef n_agent fill:#e6d8ff,stroke:#7a3fe0,stroke-width:1.5px,color:#1d1d1f;
  classDef n_store fill:#d4f2df,stroke:#1f9d55,stroke-width:1.5px,color:#1d1d1f;
  classDef n_control fill:#cfeeea,stroke:#148f7a,stroke-width:1.5px,color:#1d1d1f;
  classDef n_harness fill:#e6e6e6,stroke:#6e6e73,stroke-width:1.5px,color:#1d1d1f;
  classDef n_guard fill:#f8d7d2,stroke:#c0392b,stroke-width:1.5px,color:#1d1d1f;
  screens("<b>Ink screens + App</b><br/>read store (FlowStore) and ui (UiStore)<br/>commit through FlowStore setters only")
  class screens n_tui
  ui("<b>UiStore  ·  src/tui/ui-store.ts</b><br/>presentation state: active screen, nav direction,<br/>status expanded, learn card, token HUD")
  class ui n_tui
  flow("<b>FlowStore  ·  src/store/state/store.ts</b><br/>flow, gates, interrupts, cross-run session<br/>(credentials, detection, setup, artefact urls)<br/>the active RunStore, mirrored into session")
  class flow n_store
  run("<b>RunStore  ·  src/store/state/run-store.ts</b><br/>one agent run: runPhase, outro, questions,<br/>notices, tasks, status, event plan, handoff,<br/>token usage")
  class run n_store
  storeui("<b>StoreUI  ·  src/store/ui/store-ui.ts</b><br/>the WizardUI the agent calls; every call is<br/>a FlowStore setter")
  class storeui n_store
  stream("<b>TaskStreamPush  ·  src/store/task-stream</b><br/>store-level publisher over one RunStore<br/>(RunStateSource)")
  class stream n_store
  control("<b>control server  ·  src/store/control</b><br/>dev TUI with --control-socket, headless always;<br/>reads the FlowStore, commits one setter per action")
  class control n_control
  agent("<b>runAgent  ·  src/agent</b><br/>one run; sees run.session (a copy) and<br/>getUI() only")
  class agent n_agent
  cli("<b>cli runner  ·  src/cli/runners/run-wizard.ts</b><br/>owns the chain: startTUI, one startRun per<br/>agent run, runAgent, completeRunStep")
  class cli n_cli
  cli -->|"startTUI(version, program)  run-wizard.ts:136<br/>new FlowStore(flowFor(program).flow)  start-tui.ts:37<br/>constructor: _initGates, first RunStore, subscribe  store.ts:125"| flow
  flow -->|"startRun(session) → new RunStore(...session, ...RUN_SESSION_DEFAULTS)  store.ts:162<br/>first run in the constructor  store.ts:128; run.subscribe(() => emitChange)  store.ts:129, :167"| run
  cli -->|"per agent run: run = activeTui.store.startRun(runSession)  run-wizard.ts:276<br/>composed step  :77 → runAgent(…, composed: true)  :81 → completeRunStep  :82<br/>main run  :341 → runAgent(runConfigFor(config), run.session)  :342"| run
  ui -->|"new UiStore(store)  start-tui.ts:39<br/>store.subscribe(() => _onStoreChange())  ui-store.ts:25"| flow
  screens -->|"render(createElement(App, store, ui))  start-tui.ts:44"| ui
  screens -->|"props.store: setters per key handler (confirmSetup, resolvePendingQuestion, …)"| flow
  storeui -->|"new StoreUI(store); setUI(inkUI)  start-tui.ts:41<br/>this.store.pushStatus / setOutroData / requestQuestion …  store-ui.ts:34"| flow
  flow -->|"run-level setters delegate: this._run.setTasks, setRunPhase, setOutroData, …  (41 sites)<br/>+ pushInterrupt and analytics for questions and notices"| run
  agent -->|"getUI().startRun() / setTasks / requestQuestion / outro  linear.ts:78, orchestrator-runner.ts:667"| storeui
  cli -->|"runAgent(runConfigFor(config), run.session)  run-wizard.ts:81, :86, :342"| agent
  stream -->|"new TaskStreamPush(store: run, programId, skillId)  run-wizard.ts:277<br/>attach: target.subscribe(() => onStoreChange())  task-stream-push.ts:180"| run
  control -->|"attachControlServer(activeTui.store, hooks)  run-wizard.ts:219<br/>GET /state = readState(store); POST /actions/:id = one setter"| flow
Loading

How the TUI process spawns the stores, in order

  1. src/cli/runners/run-wizard.ts:136 calls startTUI(WIZARD_VERSION, config.id).
  2. src/tui/start-tui.ts:37 builds new FlowStore(flowFor(program).flow). The constructor (src/store/state/store.ts:125-130) derives the gates from the flow, creates a first idle RunStore from the initial session, and subscribes to it so every run commit re-emits as one flow commit.
  3. start-tui.ts:39 builds new UiStore(store); src/tui/ui-store.ts:25 subscribes to the FlowStore and derives the active screen, the nav direction, and the presentation atoms.
  4. start-tui.ts:41-42 builds new StoreUI(store) and installs it with setUI. Each WizardUI method is one FlowStore setter call (src/store/ui/store-ui.ts:31).
  5. start-tui.ts:44-46 renders App with { store, ui }. Screens read both and commit only through FlowStore setters. startTUI returns the FlowStore in its handle.
  6. run-wizard.ts:219, dev build with --control-socket only: attachControlServer(activeTui.store, { hooks }) mirrors the FlowStore.
  7. run-wizard.ts:272-291 defines beginRun: activeTui.store.startRun(runSession) (line 276) creates the RunStore for one agent run; new TaskStreamPush({ store: run, … }) (line 277) attaches to that RunStore (src/store/task-stream/task-stream-push.ts:180 subscribes).
  8. store.ts:162-170 is startRun: cancel a pending question and notice, unsubscribe the previous run, create new RunStore({ ...session, ...RUN_SESSION_DEFAULTS }), subscribe, emit once, return the run.
  9. run-wizard.ts:318 sends composed steps through advanceStep (lines 65-82): beginRun, then runAgent(…, run.session, { composed: true }), then store.completeRunStep(step.id) (store.ts:177-184 records the step and idles the run).
  10. run-wizard.ts:341-342 starts the main run the same way: beginRun, then runAgent(runConfigFor(config), run.session).
  11. The agent writes through getUI() only: StoreUI, then the FlowStore setter, then the delegated this._run setter (41 delegation sites), then RunStore emit, FlowStore emit, UiStore version bump, Ink re-render.
  12. Headless twin: src/cli/runners/run-non-interactive.ts:218 FlowStore, :226 StoreUI (controlled) or :228 HeadlessUI, :234 TaskStreamPush, :252 startRun, :302 attachControlServer, :421 runAgent. Each POST /runs goes through src/cli/control-hooks.ts:116 startRun and :122 runAgent.

What each store is attached to:

Store Constructed by Attached to Through
FlowStore startTUI (TUI) or the headless runner (cli) the flow; the active RunStore; UiStore, StoreUI, screens, and the control server read or commit it constructor, startRun, subscribe, setters
RunStore FlowStore.startRun per agent run (first one in the constructor) the FlowStore that owns it; TaskStreamPush; the agent's run.session copy subscribe, RunStateSource, runAgent(config, run.session)
UiStore startTUI the FlowStore store.subscribe
StoreUI startTUI or the headless runner the FlowStore setUI; every WizardUI call is a setter
TaskStreamPush beginRun in the runners, runStream in the control hooks one RunStore attach subscribes
control server the cli runners, dev TUI or headless the FlowStore attachControlServer, readState, one setter per action

Frame diffs against P5 that are intended

  • The second run of a composed flow starts with an empty status log. P5 showed the first run's tail (PostHog integration complete) on the github screen until the second run pushed its own line.
  • The wizard-ask text is agent output and varies per run. The intro card centres on the temp dir name. The auth frame is a capture-timing substate (waiting for auth versus detecting).

Real runs through the socket route

Same route the workbench CI uses (scripts/tui-snapshots.no-jest.ts, project 228144, issued gateway bearer, throwaway fixture copies). posthog-integration is one run; self-driving chains two runs on one flow store, which is the case this refactor is for. Frames are compared against the P5 runs of the same programs after masking ANSI, MCP-created ids, the Program row, clock times, and the temp dir name.

summary.txt
posthog-integration rc=0 secs=222 frames=16 result=completed ['intro', 'auth', 'run', 'outro', 'mcp', 'slack-connect', 'keep-skills'] 20:36:26
self-driving rc=0 secs=614 frames=48 result=completed ['self-driving-intro', 'self-driving-integration-check', 'auth', 'self-driving-integration-detect', 'run', 'self-driving-handoff', 'self-driving-github', 'run', 'wizard-ask', 'run', 'wizard-ask', 'run', 'outro'] 20:46:40
smoke rc=0 20:51:51
DONE
compare.py: last frame per screen, P5 vs P6
## posthog-integration
  base: frames={'intro': 1, 'auth': 1, 'run': 10, 'outro': 1, 'mcp': 1, 'slack-connect': 1, 'keep-skills': 1} screenPath=['intro', 'auth', 'run', 'outro', 'mcp', 'slack-connect', 'keep-skills'] runPhase=completed
  p6: frames={'intro': 1, 'auth': 1, 'run': 10, 'outro': 1, 'mcp': 1, 'slack-connect': 1, 'keep-skills': 1} screenPath=['intro', 'auth', 'run', 'outro', 'mcp', 'slack-connect', 'keep-skills'] runPhase=completed
  screenPath identical: True
  auth: DIFFERS (14 lines)
    -                                Learn                                                      Tasks
    -                                                                                           ⠋ Analyzing project...
    -                               ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    -                                ◆ Using provided API key (CI mode - OAuth bypassed)
    -                                 Status   Tail logs   Visualizer   HN
    -                                ←→ switch tab  s toggle status
    +                               PostHog Setup Wizard
    +                               ✔ Framework: Node.js
  intro: DIFFERS (4 lines)
    -                                                                      Directory ✔ /wz-DIR
    -                                                                      Framework ✔ Node.js (detected)
    +                                                                       Directory ✔ /wz-DIR
    +                                                                       Framework ✔ Node.js (detected)
  keep-skills: identical
  mcp: identical
  outro: identical
  slack-connect: identical
## self-driving
  base: frames={'self-driving-intro': 1, 'self-driving-integration-check': 1, 'auth': 1, 'self-driving-integration-detect': 1, 'run': 32, 'self-driving-handoff': 1, 'self-driving-github': 1, 'wizard-ask': 2, 'outro': 1} screenPath=['self-driving-intro', 'self-driving-integration-check', 'auth', 'self-driving-integration-detect', 'run', 'self-driving-handoff', 'self-driving-github', 'run', 'wizard-ask', 'run', 'wizard-ask', 'run', 'outro'] runPhase=completed
  p6: frames={'self-driving-intro': 1, 'self-driving-integration-check': 1, 'auth': 1, 'self-driving-integration-detect': 1, 'run': 39, 'self-driving-handoff': 1, 'self-driving-github': 1, 'wizard-ask': 2, 'outro': 1} screenPath=['self-driving-intro', 'self-driving-integration-check', 'auth', 'self-driving-integration-detect', 'run', 'self-driving-handoff', 'self-driving-github', 'run', 'wizard-ask', 'run', 'wizard-ask', 'run', 'outro'] runPhase=completed
  screenPath identical: True
  auth: DIFFERS (10 lines)
    -                               PostHog Setup Wizard
    -                               Privacy & data
    -                               • Source files are read by Claude for AI context
    -                               • .env* and secrets stay on your machine
    -                               • Press [I] for full privacy & usage info
    -                               ⠦ Waiting for authentication...
    -                                I privacy & data
    +                               Detecting your project. Stick around, this will take a few seconds...
  outro: identical
  self-driving-github: DIFFERS (3 lines)
    -                               ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    -                                ┊ Confirming that every integration task is now completed.
    -                                ◆ PostHog integration complete
  self-driving-handoff: identical
  self-driving-integration-check: identical
  self-driving-integration-detect: identical
  self-driving-intro: DIFFERS (2 lines)
    -                                                                         Directory ✔ /wz-DIR
    +                                                                           Directory ✔ /wz-DIR
  wizard-ask: DIFFERS (16 lines)
    -                                                      │    for your inbox. Based on the product lifecycle in this project,   │
    -                                                      │    choose any focused checks to add.                                 │
    +                                                      │    for your inbox. Based on your project, this focused check can     │
    +                                                      │   cover the core habit lifecycle when its events begin arriving —    │
    +                                                      │   add it, or none.                                                   │
    -                                                      │     ◻ Watch habit creation and check-in health                       │
    -                                                      │         Watches the core habit loop for sustained drops in new       │
    -                                                      │         habits or completed check-ins, and for unusual surges in     │
p6-posthog-integration: 16 frames captured, 8 shown · runPhase completed

Screen path: intro → auth → run → outro → mcp → slack-connect → keep-skills

01-intro

p6-posthog-integration 01-intro

02-auth

p6-posthog-integration 02-auth

03-run

p6-posthog-integration 03-run

12-run

p6-posthog-integration 12-run

13-outro

p6-posthog-integration 13-outro

14-mcp

p6-posthog-integration 14-mcp

15-slack-connect

p6-posthog-integration 15-slack-connect

16-keep-skills

p6-posthog-integration 16-keep-skills

p6-self-driving: 48 frames captured, 10 shown · runPhase completed

Screen path: self-driving-intro → self-driving-integration-check → auth → self-driving-integration-detect → run → self-driving-handoff → self-driving-github → run → wizard-ask → run → wizard-ask → run → outro

01-self-driving-intro

p6-self-driving 01-self-driving-intro

02-self-driving-integration-check

p6-self-driving 02-self-driving-integration-check

03-auth

p6-self-driving 03-auth

04-self-driving-integration-detect

p6-self-driving 04-self-driving-integration-detect

05-run

p6-self-driving 05-run

21-self-driving-handoff

p6-self-driving 21-self-driving-handoff

22-self-driving-github

p6-self-driving 22-self-driving-github

35-wizard-ask

p6-self-driving 35-wizard-ask

47-run

p6-self-driving 47-run

48-outro

p6-self-driving 48-outro

Controlled headless surface

Detect, two POST /runs on fresh run stores (posthog-integration, then metrics with the detected context), GET /runs with both results, POST /shutdown.

controlled-headless-smoke transcript (secrets redacted)
$ npx tsx bin.ts --headless-DONOTUSE-EXPERIMENTAL --control-socket /var/folders/kz/r8kl49v5683fdf4prxcylqqh0000gn/T/wizard-ctl-fWIcjR/w.sock --install-dir /tmp/wz-p6-smoke-app --project-id 228144 --region us   # POSTHOG_WIZARD_API_KEY in env
GET /health -> {"ok":true,"version":"2.76.0","surface":"headless","pid":4139,"program":"posthog-integration"}
GET /state -> {"version":1,"screen":"intro","runPhase":"idle","integration":null,"detectionComplete":false,"hasCredentials":false,"tasks":[],"dashboardUrl":null,"notebookUrl":null,"outro":null}
POST /detect {} -> {"version":16,"screen":"intro","runPhase":"idle","integration":"javascript_node","detectionComplete":true,"hasCredentials":true,"tasks":[],"dashboardUrl":null,"notebookUrl":null,"outro":null}
POST /runs {"programId":"posthog-integration"} -> {"runId":"fc1ce1cc-6231-47bb-9326-988185b2ee21","programId":"posthog-integration","installDir":"/tmp/wz-p6-smoke-app","status":"running","error":null,"startedAt":"2026-09-20T00:46:51.793Z","finishedAt":null,"result":null}
  GET /state?wait=60000&since=19 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=23 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=24 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=25 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=26 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=27 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=28 -> screen=intro runPhase=running tasks=0/0
  GET /state?wait=60000&since=37 -> screen=intro runPhase=running tasks=0/9
  GET /state?wait=60000&since=38 -> screen=intro runPhase=running tasks=0/9
  GET /state?wait=60000&since=39 -> screen=intro runPhase=running tasks=0/9 last=in_progress:Analyze project
  GET /state?wait=60000&since=40 -> screen=intro runPhase=running tasks=0/9 last=in_progress:Analyze project
  GET /state?wait=60000&since=41 -> screen=intro runPhase=running tasks=0/9 last=in_progress:Analyze project
  GET /state?wait=60000&since=43 -> screen=intro runPhase=running tasks=1/9 last=in_progress:Plan events
  GET /state?wait=60000&since=44 -> screen=intro runPhase=running tasks=1/9 last=in_progress:Plan events
  GET /state?wait=60000&since=45 -> screen=intro runPhase=running tasks=1/9 last=in_progress:Plan events
  GET /state?wait=60000&since=46 -> screen=intro runPhase=running tasks=2/9 last=completed:Plan events
  GET /state?wait=60000&since=47 -> screen=intro runPhase=running tasks=2/9 last=completed:Plan events
  GET /state?wait=60000&since=48 -> screen=intro runPhase=running tasks=2/9 last=in_progress:Install SDK
  GET /state?wait=60000&since=49 -> screen=intro runPhase=running tasks=2/9 last=in_progress:Install SDK
  GET /state?wait=60000&since=50 -> screen=intro runPhase=running tasks=2/9 last=in_progress:Install SDK
  GET /state?wait=60000&since=52 -> screen=intro runPhase=running tasks=3/9 last=in_progress:Set env vars
  GET /state?wait=60000&since=53 -> screen=intro runPhase=running tasks=3/9 last=in_progress:Set env vars
  GET /state?wait=60000&since=54 -> screen=intro runPhase=running tasks=3/9 last=in_progress:Set env vars
  GET /state?wait=60000&since=56 -> screen=intro runPhase=running tasks=4/9 last=in_progress:Initialize PostHog
  GET /state?wait=60000&since=57 -> screen=intro runPhase=running tasks=4/9 last=in_progress:Initialize PostHog
  GET /state?wait=60000&since=58 -> screen=intro runPhase=running tasks=4/9 last=in_progress:Initialize PostHog
  GET /state?wait=60000&since=59 -> screen=intro runPhase=running tasks=4/9 last=in_progress:Initialize PostHog
  GET /state?wait=60000&since=60 -> screen=intro runPhase=running tasks=4/9 last=in_progress:Initialize PostHog
  GET /state?wait=60000&since=62 -> screen=intro runPhase=running tasks=5/9 last=in_progress:Instrument events
  GET /state?wait=60000&since=63 -> screen=intro runPhase=running tasks=5/9 last=in_progress:Instrument events
  GET /state?wait=60000&since=64 -> screen=intro runPhase=running tasks=5/9 last=in_progress:Instrument events
  GET /state?wait=60000&since=66 -> screen=intro runPhase=running tasks=6/9 last=in_progress:Verify integration
  GET /state?wait=60000&since=67 -> screen=intro runPhase=running tasks=6/9 last=in_progress:Verify integration
  GET /state?wait=60000&since=68 -> screen=intro runPhase=running tasks=6/9 last=in_progress:Verify integration
  GET /state?wait=60000&since=69 -> screen=intro runPhase=running tasks=6/9 last=in_progress:Verify integration
  GET /state?wait=60000&since=70 -> screen=intro runPhase=running tasks=7/9 last=completed:Verify integration
  GET /state?wait=60000&since=71 -> screen=intro runPhase=running tasks=7/9 last=completed:Verify integration
  GET /state?wait=60000&since=72 -> screen=intro runPhase=running tasks=7/9 last=completed:Verify integration
  GET /state?wait=60000&since=73 -> screen=intro runPhase=running tasks=7/9 last=completed:Verify integration
  GET /state?wait=60000&since=74 -> screen=intro runPhase=running tasks=7/9 last=completed:Verify integration
  GET /state?wait=60000&since=75 -> screen=intro runPhase=running tasks=7/9 last=completed:Verify integration
  GET /state?wait=60000&since=76 -> screen=intro runPhase=running tasks=7/9 last=in_progress:Create dashboard
  GET /state?wait=60000&since=77 -> screen=intro runPhase=running tasks=7/9 last=in_progress:Create dashboard
  GET /state?wait=60000&since=78 -> screen=intro runPhase=running tasks=7/9 last=in_progress:Create dashboard
  GET /state?wait=60000&since=80 -> screen=intro runPhase=running tasks=8/9 last=in_progress:Write setup report
  GET /state?wait=60000&since=81 -> screen=intro runPhase=running tasks=8/9 last=in_progress:Write setup report
  GET /state?wait=60000&since=82 -> screen=intro runPhase=running tasks=9/9 last=completed:Write setup report
  GET /state?wait=60000&since=83 -> screen=intro runPhase=running tasks=9/9 last=completed:Write setup report
  GET /state?wait=60000&since=84 -> screen=intro runPhase=running tasks=9/9 last=completed:Write setup report
  GET /state?wait=60000&since=85 -> screen=intro runPhase=running tasks=9/9 last=completed:Write setup report
  GET /state?wait=60000&since=86 -> screen=intro runPhase=running tasks=9/9 last=completed:Write setup report
  GET /state?wait=60000&since=87 -> screen=intro runPhase=running tasks=9/9 last=completed:Write setup report
  GET /state?wait=60000&since=88 -> screen=intro runPhase=completed tasks=9/9 last=completed:Write setup report
  final -> {"version":88,"screen":"intro","runPhase":"completed","integration":"javascript_node","detectionComplete":true,"hasCredentials":true,"tasks":["completed:Analyze project","completed:Plan events","completed:Install SDK","completed:Set env vars","completed:Initialize PostHog","completed:Instrument events","completed:Verify integration","completed:Create dashboard","completed:Write setup report"],"dashboardUrl":"https://us.posthog.com/project/228144/dashboard/2115090"
[...]
            "share": "boolean (optional)"
          }
        }
      ]
    }
  },
  {
    "runId": "bbed9669-e601-4a5f-b34d-b5701c3a35b9",
    "programId": "metrics",
    "installDir": "/tmp/wz-p6-smoke-app",
    "status": "done",
    "error": null,
    "startedAt": "2026-09-20T00:49:45.678Z",
    "finishedAt": "2026-09-20T00:51:49.555Z",
    "result": {
      "version": 114,
      "currentScreen": "intro",
      "session": {
        "installDir": "/tmp/wz-p6-smoke-app",
        "integration": "javascript_node",
        "detectedFrameworkLabel": null,
        "detectionComplete": true,
        "frameworkContext": {},
        "setupConfirmed": false,
        "integrate": null,
        "llmOptIn": false,
        "discoveredFeatures": [],
        "runRequested": false,
        "runPhase": "completed",
        "completedRuns": [],
        "pendingQuestion": null,
        "taskNotice": null,
        "outroData": null,
        "outroDismissed": false,
        "dashboardUrl": "https://us.posthog.com/project/228144/dashboard/2115090",
        "notebookUrl": "https://us.posthog.com/project/228144/notebooks/HLzBZhyy",
        "mcpComplete": false,
        "slackStepDismissed": false,
        "skillsComplete": false,
        "hasCredentials": true,
        "projectId": 228144
      },
      "tasks": [
        {
          "label": "Configure metrics on existing client",
          "activeForm": "Configuring metrics on existing client",
          "status": "completed",
          "done": true
        },
        {
          "label": "Add request middleware metrics",
          "activeForm": "Adding request middleware metrics",
          "status": "completed",
          "done": true
        },
        {
          "label": "Add business throughput counters",
          "activeForm": "Adding business throughput counters",
          "status": "completed",
          "done": true
        },
        {
          "label": "Verify build",
          "activeForm": "Verifying build",
          "status": "completed",
          "done": true
        },
        {
          "label": "Write setup report",
          "activeForm": "Writing setup report",
          "status": "completed",
          "done": true
        }
      ],
      "statusMessages": [
        "Initializing Wizard agent...",
        "Verbose logs: /tmp/posthog-wizard.log",
        "Agent initialized. Let's get cooking!",
        "Setting up application metrics...",
        "Adding metrics config to the existing PostHog client",
        "Application metrics configured! View the report at ./posthog-metrics-report.md"
      ],
      "eventPlan": [],
      "handoffText": "# PostHog Application Metrics — Setup Report\n\n## Summary\n\nAdded PostHog application metrics (`posthog.metrics`) to the existing Express todo API by reusing the existing `posthog-node` client already used for event capture and exception autocapture. No existing `capture()` calls, exception handling, or configuration were modified beyond adding the `metrics` block.\n\n- **SDK**: `posthog-node` `5.52.4` (already installed, exceeds the `>= 5.43.0` requirement — no version bump needed)\n- **Client**: the single existing `PostHog` instance in `index.js`, now configured with `metrics: { serviceName: 'express-todo' }`\n- **Env vars**: `POSTHOG_API_KEY` and `POSTHOG_HOST` were already present in `.env` — no changes needed\n\n## What was instrumented (`index.js`)\n\n### 1. Client configuration\nAdded `metrics: { serviceName: 'express-todo' }` to the existing `new PostHog(...)` call so all metric series are attributable to this service.\n\n### 2. Request/response layer (middleware — single choke point)\nA new `app.use` middleware, placed right after `express.json()` and before all routes, records on every request's `finish` event:\n- `http.requests` (count) — attributes: `method`, `route` (Express route pattern, e.g. `/api/todos/:id`), `status`\n- `http.request.duration` (histogram, `ms`) — same attributes\n\nUsing `req.route?.path` (not the raw URL) keeps `route` low-cardinality — it's the route pattern, not the interpolated todo ID.\n\n### 3. Business throughput counters\nAdded next to the existing `captureEvent(...)` calls for the same actions, so both the event and the metric are recorded at the single place each operation commits:\n- `todos.created` (count) — in `POST /api/todos`\n- `todos.updated` (count, attribute `completed`) — in `PATCH /api/todos/:id`\n- `todos.deleted` (count, attribute `was_completed`) — in `DELETE /api/todos/:id`\n- `todos.open` (gauge) — current count of incomplete todos, updated whenever the open count could have changed (create, completion toggle, delete)\n\n### 4. Error tracking\nAdded `http.errors` (count, attributes `method`, `route`) in the existing error-handling middleware, alongside the existing `posthog.captureException(...)` call.\n\n## Design notes\n\n- All metric calls are guarded by the same `if (posthog)` check already used for event capture, so a missing PostHog configuration still fails loudly outside production (existing behavior, untouched) and no-ops safely in production.\n- No `distinct_id`, session ID, todo ID, or other unbounded/PII value was used as a metric attribute — only bounded dimensions (`method`, `route` pattern, `status`, `completed`/`was_completed` booleans).\n- Metrics flush automatically on the existing interval and on the existing `shutdown()` call (`posthog.shutdown()` in the `SIGINT`/`SIGTERM` handler already flushes metrics along with events).\n- Metrics is in open alpha — enable it for this project under **Metrics → Enable metrics** in the PostHog sidebar if not already on, then trigger a few requests (`GET/POST/PATCH/DELETE /api/todos`) to see `http.requests`, `http.request.duration`, `todos.created`, `todos.updated`, `todos.deleted`, `todos.open`, and `http.errors` appear in the Metrics viewer within about a minute.\n\n## Files changed\n\n- `index.js` — metrics config on the existing client, request middleware, business counters/gauge, error counter (additive only)\n\n## Verification\n\n- No build/typecheck/lint script exists in `package.json` (`start`, `dev` only) — verified correctness by full-file review; all added code is syntactically consistent with the existing style and guarded identically to existing PostHog calls.\n- `posthog-node@5.52.4` (installed) exposes `posthog.metrics.count/gauge/histogram` — confirmed directly in `node_modules/posthog-node/dist/client.js`.\n",
      "setupQuestions": [],
      "actions": [
        {
          "id": "confirm_setup",
          "description": "Confirm the intro and continue. share: true grants and false declines sharing scan results; absent keeps the toggle (granted when undecided).",
          "params": {
            "share": "boolean (optional)"
          }
        }
      ]
    }
  }
]
POST /shutdown -> {"ok":true}
wizard exit code: 0
socket removed: true
wizard console:
┌  Welcome to the PostHog setup wizard
│  Running posthog-integration in Headless mode

How it was run

export WIZARD_CI_GATEWAY_TOKEN_FILE=<issued gateway bearer file> POSTHOG_KEY_FILE=<personal key file>
export PROJECT_ID=228144 POSTHOG_REGION=us E2E_ASK=true
PROGRAM=posthog-integration APP_DIR=/tmp/<copy of express-todo> SNAP_OUT=<dir> E2E_RESULT_JSON=<dir>/result.json \
  npx tsx scripts/tui-snapshots.no-jest.ts
PROGRAM=self-driving APP_DIR=/tmp/<copy of habit-tracker> SNAP_OUT=<dir> E2E_RESULT_JSON=<dir>/result.json \
  npx tsx scripts/tui-snapshots.no-jest.ts
APP_DIR=/tmp/<copy of express-todo> npx tsx scripts/controlled-headless-smoke.no-jest.ts posthog-integration metrics

@github-actions

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci ai-observability
  • /wizard-ci basic-integration
  • /wizard-ci mcp-analytics
  • /wizard-ci replay-vision
  • /wizard-ci revenue
  • /wizard-ci self-driving
  • /wizard-ci warehouse
  • /wizard-ci warehouse-seeded

Test an individual app:

  • /wizard-ci ai-observability/anthropic
  • /wizard-ci ai-observability/google-adk
  • /wizard-ci ai-observability/groq
Show more apps
  • /wizard-ci ai-observability/manual-capture
  • /wizard-ci ai-observability/openai
  • /wizard-ci ai-observability/openai-agents
  • /wizard-ci ai-observability/opentelemetry
  • /wizard-ci ai-observability/vercel-ai
  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/flutter
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci replay-vision/javascript-node
  • /wizard-ci replay-vision/next-js
  • /wizard-ci replay-vision/react-vite
  • /wizard-ci revenue/stripe
  • /wizard-ci self-driving/astro
  • /wizard-ci self-driving/fastapi
  • /wizard-ci self-driving/nuxt
  • /wizard-ci self-driving/react-router
  • /wizard-ci self-driving/sveltekit
  • /wizard-ci warehouse/monorepo-env
  • /wizard-ci warehouse/multi-source-next
  • /wizard-ci warehouse/stripe-node
  • /wizard-ci warehouse/zero-source
  • /wizard-ci warehouse-seeded/next-stripe
  • /wizard-ci warehouse-seeded/next-stripe-declined

Test against a Context Mill branch:

  • /wizard-ci all context-mill:my-branch

Add context-mill:<branch> to any command above to pin the Context Mill branch. It defaults to main.

Results will be posted here when complete.

@gewenyu99 gewenyu99 changed the title WIP refactor(store): one RunStore per agent run refactor(store): WIP one RunStore per agent run, FlowStore chains them Sep 21, 2026
The bundle audit compares these with the manifests CI builds; the new modules
and the regrouped chunks belong in them. A failed comparison now uploads its
manifests and reports both files.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
@gewenyu99 gewenyu99 closed this Sep 21, 2026
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