Skip to content

feat(logs): add a wizard logs program - #1043

Open
ThaliaBarrera wants to merge 4 commits into
PostHog:mainfrom
ThaliaBarrera:thalia/logs-program
Open

feat(logs): add a wizard logs program#1043
ThaliaBarrera wants to merge 4 commits into
PostHog:mainfrom
ThaliaBarrera:thalia/logs-program

Conversation

@ThaliaBarrera

Copy link
Copy Markdown

Problem

There's no logs command. PostHog Logs is reachable only through the generic wizard 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 posthogDistinctId and sessionId to 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 new logs-setup skill in context-mill (PostHog/context-mill#302).

Follows the ai-observability shape — a flat native command via nativeCommandFactory, with no run.skillId so 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 via wizard_ask when 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-analytics and ai-observability.

File
src/lib/programs/logs/index.ts program config, custom prompt, abort case
src/lib/programs/logs/content/index.tsx Learn deck
src/lib/programs/logs/content/correlation-diagram.tsx ASCII diagram of the correlation chain
src/lib/programs/logs/test/e2e.json e2e profile + documented path
src/commands/logs.ts nativeCommandFactory
src/ui/tui/screens/LogsIntroScreen.tsx intro screen
bin.ts, program-registry.ts, screen-sequences.ts, screen-registry.tsx, switchboard/index.ts, e2e-harness/profiles.ts registration

I worked from #922 as a checklist rather than trusting the registry docblock, which says bin.ts derives its wiring automatically — it doesn't.

PROGRAM_BINDINGS is 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, and switchboard.test.ts failed loudly and caught it. That test is doing real work. Bound explicitly to DEFAULT_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 found string 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:

  a log line
      "checkout failed: card declined"
  ↓ posthogDistinctId
  the person it happened to
      every log they ever caused
  ↓ sessionId
  the replay of them causing it
      watch the click that broke it

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 passed
  • pnpm lint — 0 errors; no new warnings in the added files
  • pnpm typecheck — 25 errors, identical to the count on main in this checkout, none in the added files
  • pnpm screens:check — all screen checks pass
  • pnpm try --help lists logs; pnpm try logs --help renders
  • Ran the local loop against context-mill's dev server (pnpm dev + --local-mcp) and confirmed the wizard resolves and downloads logs-setup-nextjs.zip from localhost:8765

LLM 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

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 gewenyu99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wiring looks clean! I have more feedback in the context mill PR

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this direction

@sarahxsanders sarahxsanders left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +22 to +30
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.';
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is lovely!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +78 to +87
},

{
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 },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

ThaliaBarrera and others added 2 commits July 30, 2026 21:39
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>
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.

3 participants