adding-skill-program · v3.0 · 2026-09-09 · sha256 e6c2345f9df627d3

adding-skill-program v3.0B

Immutable. This exact content is served forever at /api/v1/blob/e6c2345f9df627d3.

---
name: adding-skill-program
description:
  Add a PostHog wizard capability backed by context-mill content. Choose a skill
  command or native program, configure an orchestrator flow, and wire any
  required CLI, screens, or prerequisites.
compatibility:
  Designed for coding agents working on the PostHog wizard codebase.
metadata:
  author: posthog
  version: '3.0'
---

# Adding a Skill-Based Program

Read [wizard-development](../wizard-development/SKILL.md) for the shared design
policy. Use Pi for new agent work and prefer an orchestrator flow. Linear runs
are for very simple work and existing flows. The Anthropic SDK remains a
supported legacy fallback, deprecated as the default for new work; the shared
guide owns the fallback criteria and gateway model/effort/system-prompt
contract.

These are contribution defaults. Current runtime
[bindings](../../../src/lib/agent/runner/switchboard/index.ts) still default
many programs to Anthropic plus linear; documentation changes do not migrate
them.

## Choose the contribution surface

- **Content-only capability:** use the existing skill command machinery when it
  can express the workflow.
  [Context-mill](https://github.com/PostHog/context-mill) owns skill content and
  `cliEntries`. A new skill-backed child of an existing family ships through
  context-mill; inspect
  [family dispatch](../../../src/lib/programs/dispatch-family.ts). Unpromoted
  skills run through [the skill command](../../../src/commands/skill.ts).
- **Native program:** use a
  [ProgramConfig](../../../src/lib/programs/program-step.ts) when the wizard
  needs its own flow, screens, detection, composition, or other native behavior.
  Keep product instructions in context-mill.

Command names, program `id`, content-mill `agentFlow`, and `skillId` have
different roles. Use the full product name for public commands. The config field
is `id`, not the retired `flowKey`.

## Build a native orchestrator program

Use [metrics](../../../src/lib/programs/metrics/) as the current Pi/orchestrator
example and read the
[runner architecture](../wizard-development/references/ARCHITECTURE.md) when
changing execution behavior.

1. Add the program config under `src/lib/programs/<name>/`. Set `agentFlow` when
   its content-mill flow differs from `id`; setting it explicitly also documents
   the content dependency. Keep a `run` definition so the outer runner executes
   agent work.
2. Supply the flow's seed and task prompts in context-mill, including the task
   dependencies and applicable skill variants. The
   [orchestrator](../../../src/lib/agent/runner/sequence/orchestrator/orchestrator-runner.ts)
   loads `agentFlow ?? id`, requires a seed prompt, and checks task-skill
   variants before running. `run.skillId` alone does not define this flow.
3. Register the config in
   [PROGRAM_REGISTRY](../../../src/lib/programs/program-registry.ts) and add its
   Pi/orchestrator entry to
   [PROGRAM_BINDINGS](../../../src/lib/agent/runner/switchboard/index.ts).
   [Existing binding checks](../../../src/lib/agent/runner/__tests__/switchboard.test.ts)
   enforce coverage; `ProgramId` currently widens to `string`.
4. For a standalone native command, create a command module with
   [nativeCommandFactory](../../../src/commands/factories/native-command-factory.ts)
   and register it in [bin.ts](../../../bin.ts). A native family child uses the
   handlers in family dispatch. Program registration derives screen sequences
   and store lookup, not the top-level CLI `.use()` chain.
5. Check [program OAuth scopes](../../../src/lib/oauth/program-scopes.ts)
   against the tools the program needs; add scopes only when the base set is
   insufficient.

Model and effort selections in flow frontmatter must be supported by the wizard
and gateway. Follow the cross-repo procedure in
[wizard-development](../wizard-development/SKILL.md) before introducing a model
or changing gateway-required prompt material.

## Simple linear programs and existing flows

For a very simple linear flow, use
[createSkillProgram](../../../src/lib/programs/agent-skill/index.ts) to
configure installation of one skill. Register the native program as above with
an explicit Pi/linear binding; the factory does not select a sequence. Read
`SkillProgramOptions` for required fields;
[audit](../../../src/lib/programs/audit/) demonstrates factory customization and
a dynamic `run(session)` that seeds a ledger.
[Revenue analytics](../../../src/lib/programs/revenue-analytics/) builds its
config directly and adds prerequisite detection.

`ProgramRun.customPrompt`, `abortCases`, `postRun`, and `buildOutroData` are
consumed by the
[linear sequence](../../../src/lib/agent/runner/sequence/linear.ts). `postRun`
runs after success; `buildOutroData` receives session and credentials, with host
information inside credentials. The orchestrator currently uses its own task
prompts, failure handling, and outro, and does not invoke those hooks. Check
this limitation before migrating a linear flow; setting an orchestrator binding
does not preserve these behaviors automatically.

## Screens, prerequisites, and composition

Reuse [AGENT_SKILL_STEPS](../../../src/lib/programs/agent-skill/steps.ts):
intro, health check, auth, run, outro, and keep-skills. Auth also applies the
shared [AI opt-in gate](../../../src/lib/programs/ai-opt-in-gate.ts) for agent
programs. Override `screenId`, not `screen`, when adapting a step. New screens
need an entry in [ScreenId](../../../src/ui/tui/screen-sequences.ts), a
component, and registration in
[screen-registry](../../../src/ui/tui/screen-registry.tsx). Follow
[ink-tui](../ink-tui/SKILL.md) for rendering and store usage.

Use a headless step's `onReady` for session-dependent detection, then render
structured `frameworkContext.detectError` data in the intro. `onInit` runs when
the TUI starts rendering with its initial session; `onReady` runs after the real
session is assigned. See [store hooks](../../../src/ui/tui/store.ts) and
[run-wizard](../../../src/lib/runners/run-wizard.ts). The
[noninteractive runner](../../../src/lib/runners/run-non-interactive.ts) also
walks `onReady` by default; set `ciPreRun` only when it needs a different
prerequisite strategy.

`requires` currently records metadata; it does not execute or enforce prior
programs. Compose real work through `ProgramStep.run`, with `onRunPrep` and
`targetDir` when needed. The
[integration run step](../../../src/lib/programs/posthog-integration/index.ts)
and [self-driving](../../../src/lib/programs/self-driving/) demonstrate this.
Composed sub-runs are structurally linear; orchestrators cannot nest.

## Validate the affected path

Reuse the relevant registry, binding, detection, or routing checks. Add a
focused behavioral test only for a meaningful gap; avoid tests that repeat
configuration fields. Check that the selected CLI route resolves the intended
program and that its content-mill flow is available. Use the
[exploration guide](../exploring-the-wizard/SKILL.md) for a warranted end-to-end
run against a disposable app. Follow
[wizard-development](../wizard-development/SKILL.md) for proportionate checks;
documentation-only changes need source/link verification.