feat(logs): add a wizard logs program - #1043
Conversation
Sends a project's logs to PostHog and then does the part the docs can't: finds where the app already knows who a request belongs to and attaches `posthogDistinctId` and `sessionId` to every log record, so a log line resolves to a person and opens their session replay. Backed by the new `logs-setup` skill in context-mill. Follows the `ai-observability` shape: a flat native command via `nativeCommandFactory`, no `run.skillId`, so the agent loads the menu and installs the variant that matches the project's runtime. A repo holding both a Next.js frontend and a Python backend is common, and server-emitted records are where correlation is missing, so the prompt tells the agent to ask via `wizard_ask` when it's ambiguous which runtime emits them. Registration points, from the ai-observability PR (PostHog#922) used as a checklist: program config, PROGRAM_REGISTRY + Program enum, src/commands/logs.ts, bin.ts, ScreenId + screen-registry, an intro screen, PROGRAM_BINDINGS, and an e2e profile. PROGRAM_BINDINGS is worth a note: it's optional at the type level, so omitting it compiles and silently falls back to the default. The switchboard test catches that, which is how this one got caught. Bound explicitly to DEFAULT_BINDING. The Learn deck draws the correlation chain — log line, person, replay — rather than asserting it, since that chain is the whole argument for logs living here rather than in a standalone log vendor. Co-authored-by: Cursor <cursoragent@cursor.com>
Whether session replay is enabled decides whether log records can link to recordings at all, so it decides which correlation tier the skill should aim for. It's project-level state and the codebase can't answer it — replay is routinely enabled from the snippet or from a separate frontend repo, so its absence in this repo proves nothing. `teamProductOptIns.sessionReplay` is already on PromptContext from the `/api/projects/:id/` payload read at auth time, so surface it. Unknown is kept distinct from off: the agent is told to let the verify step establish the tier rather than assuming either way. Co-authored-by: Cursor <cursoragent@cursor.com>
gewenyu99
left a comment
There was a problem hiding this comment.
The wiring looks clean! I have more feedback in the context mill PR
There was a problem hiding this comment.
Super interesting that you opted to include one of these! Were you able to figure out how these test manifests work? Still something experimental we're working on
| }, | ||
|
|
||
| { | ||
| content: 'So here’s what’s different about putting them here.', |
There was a problem hiding this comment.
I really like this direction
sarahxsanders
left a comment
There was a problem hiding this comment.
awesome work! I think there's some clear product thinking going on here that I enjoy. I like that you've approached this by splitting installation from correlation. left some general high level feedback :)
| // logs picks its skill variant at run time (logs-setup-nextjs, | ||
| // logs-setup-python), so there's no pre-seeded skillId here. Fall back to | ||
| // the default variant for the "more info" lookup. | ||
| const skillId = session.skillId ?? 'logs-setup-nextjs'; |
There was a problem hiding this comment.
I think for a python app this might mean they see Next.js skill metadata on the "more info" panel. veryyy minor papercut just wanted to flag
we do have a way to handle null skillId by falling back to the program label, might be worth re-using instead of hardcoding
| function sessionReplayNote(enabled: boolean | null | undefined): string { | ||
| if (enabled === true) { | ||
| return 'Session replay is enabled on this PostHog project, so linking log records to recordings is achievable — aim for the `session` correlation tier.'; | ||
| } | ||
| if (enabled === false) { | ||
| return 'Session replay is disabled on this PostHog project, so log records cannot link to recordings however they are wired. Aim for the `person` tier and say in the report that enabling session replay is what would unlock replay linking.'; | ||
| } | ||
| return 'This run could not read whether session replay is enabled on this PostHog project. Wire correlation as normal and let the verify step establish which tier was actually reached, rather than assuming either way.'; | ||
| } |
There was a problem hiding this comment.
I love this. the visual is really nice. and it looks like you tested it at different sizes which is 👍
|
|
||
| { content: 'Two attributes fix that.', pause: 3000 }, | ||
|
|
||
| CORRELATION_BLOCK, |
There was a problem hiding this comment.
I feel like the transition here was a little abrupt for me. the deck is designed to promise session replay no matter what, and ends on "the replay of them causing it"
but if replay is turned off, they wouldn't have a replay to open. might be worth hiding this card or softening it
| }, | ||
|
|
||
| { | ||
| content: | ||
| 'A log line on its own tells you what broke. It rarely tells you who it broke for, or what they did to break it.', | ||
| pause: 7000, | ||
| }, | ||
|
|
||
| { content: 'Two attributes fix that.', pause: 3000 }, | ||
|
|
There was a problem hiding this comment.
I think it would help to really explain what a distinct ID actually is, where it comes from, or what makes this whole program necessary. the diagram is carrying the explanation on its own
The first live run took 18 minutes against the Next.js SaaS starter while the TUI promised 5, which is the kind of gap that makes someone assume the run has hung. Skill fixes for the two clusters that dominated that run are going out alongside this, so 12 is the honest number to quote now. Co-authored-by: Cursor <cursoragent@cursor.com>
Teach the --benchmark phase detector the logs skill's [STATUS] phrases (LOGS_PHASES) and thread the program id through createBenchmarkPipeline and the linear runner, so `wizard logs --benchmark` reports duration, turns, tokens and cost per step (detect/install/plan/context/attach/verify/report) instead of collapsing everything into one bucket. Adds a phase-detector unit test covering the logs mapping and the integration fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
Problem
There's no
logscommand. PostHog Logs is reachable only through the genericwizard skill logs-<variant>, and the skill behind it is docs-only — it points at installation docs and stops there.Installation is the half the docs already cover well. The half they don't is correlation: attaching
posthogDistinctIdandsessionIdto server-emitted records so a log line resolves to a person and opens their session replay. That's the reason to put logs in PostHog rather than in Better Stack or Datadog, and today it's left to the reader to implement by hand.It's also a bad fit for a doc and a good fit for an agent. "Where does this codebase already know who this request belongs to" has a different answer in every project — an auth middleware here, a session callback there.
The moment this is built around: you run one command, then click a log line in PostHog and land in the session replay of the user who caused it.
Changes
wizard logs, backed by the newlogs-setupskill in context-mill (PostHog/context-mill#302).Follows the
ai-observabilityshape — a flat native command vianativeCommandFactory, with norun.skillIdso the agent loads the menu and installs the variant matching the project's runtime rather than the wizard doing detection. The prompt tells it to ask viawizard_askwhen a repo holds both a Next.js frontend and a Python backend, since server-emitted records are where correlation is missing and it isn't always obvious which runtime emits them.Stays flat while "set up logs for this project" is the only action. Named for the product, per context-mill's CLI naming convention — the verb is implied by the wizard, as in
revenue-analyticsandai-observability.src/lib/programs/logs/index.tssrc/lib/programs/logs/content/index.tsxsrc/lib/programs/logs/content/correlation-diagram.tsxsrc/lib/programs/logs/test/e2e.jsonsrc/commands/logs.tsnativeCommandFactorysrc/ui/tui/screens/LogsIntroScreen.tsxbin.ts,program-registry.ts,screen-sequences.ts,screen-registry.tsx,switchboard/index.ts,e2e-harness/profiles.tsI worked from #922 as a checklist rather than trusting the registry docblock, which says
bin.tsderives its wiring automatically — it doesn't.PROGRAM_BINDINGSis worth flagging. It's optional at the type level, so omitting it compiles cleanly and silently falls back to the default model and harness. I did omit it, andswitchboard.test.tsfailed loudly and caught it. That test is doing real work. Bound explicitly toDEFAULT_BINDING— this is an ordinary linear agent-skill flow with no reason to deviate.The abort case is wired to the
[ABORT] No supported runtime foundstring the skill emits, and its body points at the full installation doc list, since Logs supports many more runtimes than the two the wizard automates today.On the Learn card
The deck draws the correlation chain rather than describing it, because that chain is the entire argument for logs living here:
It also sets expectations for the run itself — existing logging keeps working, identity attaches in one place, background jobs legitimately stay uncorrelated and the report says so — and closes on a few logging practices from the public docs.
Test plan
pnpm test— 118 files, 1675 passedpnpm lint— 0 errors; no new warnings in the added filespnpm typecheck— 25 errors, identical to the count onmainin this checkout, none in the added filespnpm screens:check— all screen checks passpnpm try --helplistslogs;pnpm try logs --helprenderspnpm dev+--local-mcp) and confirmed the wizard resolves and downloadslogs-setup-nextjs.zipfromlocalhost:8765LLM context
Written with Cursor. The companion context-mill PR carries the bulk of the thinking — the program deliberately stays thin, since the factory means nearly all the real logic belongs in the skill.
Made with Cursor