--- 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: '4.0.1' --- # 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. A program whose config sets no `binding` runs on [DEFAULT_BINDING](../../../src/agent/runner/switchboard/index.ts), Pi and linear. ## 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/cli/commands/dispatch-family.ts). Unpromoted skills run through [the skill command](../../../src/cli/commands/skill.ts). - **Native program:** use a [ProgramConfig](../../../src/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`, context-mill `agentFlow`, and `skillId` have different roles. Use the full product name for public commands. The config field is `id`. ## Build a native orchestrator program Use [metrics](../../../src/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 folder `src/programs//`. Its `index.ts` is the only entry: it exports the `ProgramConfig` as `config`, and any detectors or keys the TUI and CLI need. No other file in the folder is named `index.ts`. Copy a sibling's `tsconfig.json` into the folder, unchanged, and add `{ "path": "" }` to `references` in [`src/programs/tsconfig.json`](../../../src/programs/tsconfig.json). The folder is one project, its tests included, and `pnpm typecheck` (`tsc -b`) builds it. Without the reference, the registry's import fails with TS6307. Keep tests in `/__tests__`; they reach other layers through their entries only. Shared program code goes in `src/programs/shared/`, `detection/`, `warehouse-sources/`, `frameworks/` or `oauth/`, never in another program's folder, and has no `index.ts`. A program imports its own files and the shared program code by relative path, such as `../shared/skill-program`. Set `agentFlow` when its context-mill flow differs from `id`; setting it explicitly also documents the content dependency. Keep a `run` definition so `runProgram` 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/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: import it as `import { config as } from '@programs/'` in [program-registry.ts](../../../src/programs/program-registry.ts) and add it to `PROGRAM_REGISTRY`. Set the config's `binding` to Pi and the orchestrator. `ProgramId` is a `string`. 4. For a standalone native command, create a command module in [src/cli/commands](../../../src/cli/commands) with [nativeCommandFactory](../../../src/cli/commands/factories/native-command-factory.ts) and register it in `runCli` in [src/cli/index.ts](../../../src/cli/index.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 the program's OAuth scopes against the tools it needs. Add `oauthScopeAdditions` to the config only when the base set is insufficient. Keep a program's own set in its folder, as [replay-vision](../../../src/programs/replay-vision/scopes.ts) does; sets several programs share live in [`program-scopes.ts`](../../../src/programs/oauth/program-scopes.ts). 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/programs/shared/skill-program.ts) to configure installation of one skill. Register the native program as above. The factory sets no `binding`, so the program runs on `DEFAULT_BINDING` unless you add one to the config it returns. Read `SkillProgramOptions` for required fields; [audit](../../../src/programs/audit/) demonstrates factory customization and a dynamic `run(session)` that seeds a ledger. [Revenue analytics](../../../src/programs/revenue-analytics/) builds its config directly and adds prerequisite detection. `ProgramRun.customPrompt`, `abortCases`, `postRun`, and `buildOutroData` are consumed by the [linear sequence](../../../src/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 The program's UI lives in `src/tui/programs//`: `flow.ts`, `deck/`, `screens/`, `screen-ids.ts`, and an `index.ts(x)` entry whose `TUI_PROGRAMS` maps each program id it serves to a [TuiProgram](../../../src/tui/programs/types.ts) (flow, deck, tips and screens). Outside the folder, the [TUI program registry](../../../src/tui/programs/index.ts) imports and spreads the entry. A program with no entry runs [AGENT_SKILL_STEPS](../../../src/tui/programs/shared/skill-flow.ts): intro, health check, auth, run, outro, and keep-skills, with the skill deck. Map its steps to swap in your own intro screen, as [audit's flow](../../../src/tui/programs/audit/flow.ts) does. Auth also applies the shared [AI opt-in gate](../../../src/tui/ai-opt-in-gate.ts), `withAiOptInGate(config, flow)`, for agent programs. Set `healthCheck: false` on the config when the run skips the PostHog readiness check, and leave the health-check step out of its flow. Flow steps are [FlowStep](../../../src/tui/flow.ts)s. Override `screenId` when adapting one. A new screen needs an id in the folder's `screen-ids.ts`, a component, and an entry in its entry's `screens`; the [screen registry](../../../src/tui/screen-registry.tsx) mounts every program's screens. State only the TUI reads, such as a screen's answer, lives in [`TuiState`](../../../src/tui/tui-state.ts) and is written through a store setter, such as `store.setIntegrate`. The folder is its own tsconfig project: it may import the TUI core, through `@tui/*` and never by relative path, `@programs`, `@programs/types` and its own `@programs/`, never another program's folder or the registry. Besides its line in `src/tui/programs/index.ts`, a new folder needs a `tsconfig.json` copied from a sibling, unchanged, and `{ "path": "programs/" }` in `references` in [`src/tui/tsconfig.json`](../../../src/tui/tsconfig.json). Then run `pnpm typecheck`. Without both, the registry's `@tui/programs/` import fails with TS6307. Follow [ink-tui](../ink-tui/SKILL.md) for rendering and store usage. If the program is team-owned, add both folders to `CODEOWNERS`. Use the config's `onReady(ctx)` for session-dependent detection, then render structured `frameworkContext.detectError` data in the intro. A step's `onInit` runs when the TUI starts rendering with its initial session. `onReady` runs after the real session is assigned: `runTui` calls `runReadyHooks` in [store.ts](../../../src/tui/store.ts) before the intro, and [`detectProgram`](../../../src/programs/detect-program.ts) runs it as the detection of a run whose store has none, as in headless. Set `ciPreRun` only when a CI session needs a different prerequisite strategy: `detectProgram` runs it in place of `onReady` for a CI session. Programs read a `ProgramSession` and never use the UI: `run` gets a `RunnerContext`, `ciPreRun` gets a `CiRunnerContext` and a run step's `onRunPrep` gets the runner's `log` ([runner-context.ts](../../../src/programs/runner-context.ts)). `requires` currently records metadata; it does not execute or enforce prior programs. Compose real work through `runSteps`, keyed by the flow step id, with `runProgramId`, `onRunPrep` and `targetDir` when needed. [Self-driving](../../../src/programs/self-driving/index.ts) runs `posthog-integration` composed at its `integrate-run` step. Composed sub-runs are structurally linear; orchestrators cannot nest. A run step without `runProgramId` can set `targetDir` and `onRunPrep` to scope the program's own agent to a picked project and keep its sequence, as [error-tracking](../../../src/programs/error-tracking/index.ts) does for its `run` step. ## 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 context-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.