Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
acc5824
docs(programs): add non-TUI developer API reference
gewenyu99 Sep 22, 2026
e7b88ef
chore(docs): sync API reference with B3 implementation
gewenyu99 Sep 22, 2026
2902f31
docs(programs): require caller-owned inference authentication
gewenyu99 Sep 22, 2026
9f76b5e
Merge branch 'posthog/functional-b3-parity' into posthog/functional-b…
gewenyu99 Sep 22, 2026
161d366
Merge branch 'posthog/functional-b3-parity' into posthog/functional-b…
gewenyu99 Sep 22, 2026
855bcfd
Clarify callable Wizard API contracts
gewenyu99 Sep 23, 2026
aba2628
docs(agent): clarify B4 result and error ownership
gewenyu99 Sep 23, 2026
634bade
Merge reviewed A3 through B3 stack into B4
gewenyu99 Sep 23, 2026
0cc620d
docs: reconcile agent signal contract after stack merge
gewenyu99 Sep 23, 2026
94d3adf
Merge final B3 contract test correction
gewenyu99 Sep 23, 2026
ec58776
Merge standalone skill safety and final runner cleanup into B4
gewenyu99 Sep 23, 2026
c5b107f
Merge latest B4 error ownership docs
gewenyu99 Sep 23, 2026
ade12f5
Align runner comment with scan flush contract
gewenyu99 Sep 23, 2026
2a11a07
chore(programs): sync B4 with B3, the A3 fix and the e2e routes
gewenyu99 Sep 23, 2026
249a2a3
docs: align agent observer contract with A3
gewenyu99 Sep 23, 2026
a845d1f
docs: align B4 contracts with A3 terminal outcomes
gewenyu99 Sep 23, 2026
74dcd99
Merge updated B3 agent contracts into B4
gewenyu99 Sep 23, 2026
fa7f08b
Merge final B3 ancestry into B4
gewenyu99 Sep 23, 2026
d99a266
chore(docs): join the restacked B4 with the remote B4
gewenyu99 Sep 23, 2026
42c71bf
chore(docs): bring the joined B3, with the surface e2e routes, into B4
gewenyu99 Sep 23, 2026
98c3e78
docs: correct the callable program's rejection and signal notes, and …
gewenyu99 Sep 23, 2026
d50ab9e
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 23, 2026
632b881
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 23, 2026
133571e
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 23, 2026
ba93d81
docs: describe the post-WP3 agent and program contracts
gewenyu99 Sep 23, 2026
c3edf8e
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 24, 2026
dd37610
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 24, 2026
49d53ea
docs: describe the caller-built program run and the host-owned gates
gewenyu99 Sep 24, 2026
23a1edf
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 24, 2026
aa69825
Merge posthog/functional-b3-parity into posthog/functional-b4-api-ref…
gewenyu99 Sep 24, 2026
0d2c2c9
docs: list the restored jest e2e suite next to the live TUI run
gewenyu99 Sep 24, 2026
e74d36f
Merge posthog/functional-b-host-capabilities into posthog/functional-…
gewenyu99 Sep 24, 2026
bf66ab9
chore(programs): take the host-capabilities tree for every non-doc file
gewenyu99 Sep 24, 2026
a083676
docs(programs): document runProgram, its host and the agent contracts
gewenyu99 Sep 24, 2026
58b2d39
Merge posthog/functional-b-host-capabilities into posthog/functional-…
gewenyu99 Sep 25, 2026
e0db572
docs(programs): reshape developer interfaces around runProgram and ru…
gewenyu99 Sep 25, 2026
5651941
docs(programs): link the workbench repo, not a pull request
gewenyu99 Sep 25, 2026
784a84b
docs(programs): field definitions as tables
gewenyu99 Sep 25, 2026
e2668fe
docs(programs): name files, not paths
gewenyu99 Sep 25, 2026
7a8153a
Merge posthog/functional-b-host-capabilities into posthog/functional-…
gewenyu99 Sep 25, 2026
a0f1798
docs(programs): short programs and agent READMEs, runner not host
gewenyu99 Sep 25, 2026
c48f902
docs(programs): in and out as a short list
gewenyu99 Sep 25, 2026
256d31e
docs(agent): short runner README, bindings warning up front
gewenyu99 Sep 25, 2026
29758ce
docs(programs): future work as a warning up front
gewenyu99 Sep 25, 2026
ad4ac70
docs: AGENTS.md carries only the path, binding and runner-context cha…
gewenyu99 Sep 25, 2026
5554551
Merge posthog/functional-b-host-capabilities into posthog/functional-…
gewenyu99 Sep 25, 2026
5c92873
Merge posthog/functional-b-host-capabilities into posthog/functional-…
gewenyu99 Sep 25, 2026
09d91b9
docs: fix line anchors, the cancellation sentence, DEFAULT_BINDING an…
gewenyu99 Sep 25, 2026
3b9f540
docs(programs): say which flags skip the AI approval
gewenyu99 Sep 25, 2026
1134750
Merge posthog/functional-b-host-capabilities into posthog/functional-…
gewenyu99 Sep 25, 2026
574237b
docs(programs): fix the runAgent tools example, warn about tokens in …
gewenyu99 Sep 25, 2026
d1ed52c
docs(programs): quack examples for runProgram and runAgent
gewenyu99 Sep 25, 2026
3844eb8
docs(programs): quack examples log in with keys
gewenyu99 Sep 25, 2026
65c7d72
docs(programs): trim the quack sections to the command
gewenyu99 Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/wizard-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ infrastructure should consume those boundaries.
new Anthropic models.

Existing routing has not all migrated:
[DEFAULT_BINDING](../../../src/agent/runner/switchboard/index.ts) still
selects Anthropic + linear, with per-program and flag overrides. Set new
[DEFAULT_BINDING](../../../src/agent/runner/switchboard/index.ts) selects
Pi + linear, with per-program and flag overrides. Set new
bindings explicitly. Migrating an existing program requires checking its flow,
tasks, and lifecycle hooks; changing the default constant alone is insufficient.
Both harnesses implement `run` and `runTask`.
Expand Down
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ Each domain has a dedicated boundary:
`@agent/types` (types); see [src/agent/README.md](src/agent/README.md)
- **Shared** → `src/shared/`, stateless library code with no upward imports;
see [src/shared/README.md](src/shared/README.md)
- **Programs** → configs, detection, framework registry and task stream in
`src/programs/`; runtime and type entries are `@programs` and
`@programs/types`
- **Programs** → program configs and `runProgram` in `src/programs/`; see
[src/programs/README.md](src/programs/README.md) and the
[developer interfaces](docs/developer-interfaces.md)
- **TUI** → screens, primitives and content decks in `src/ui/tui/`

Adding a new concern means finding the narrowest existing surface, not adding
Expand Down Expand Up @@ -76,7 +76,7 @@ Agent SDK is a supported legacy fallback, deprecated as the default; retain it
for major Pi vulnerabilities or gaps in support for new Anthropic models.

This is the contribution policy, not a claim that every existing binding has
migrated: `DEFAULT_BINDING` is still Anthropic + linear. Set new bindings
migrated: `DEFAULT_BINDING` is Pi + linear. Set new bindings
explicitly and check sequence-specific hooks before migrating existing flows.
See
[execution policy and model admission](.claude/skills/wizard-development/SKILL.md#execution-policy-and-model-admission)
Expand Down Expand Up @@ -202,7 +202,8 @@ wizard run points. Full catalog: [`docs/local-dev.md`](docs/local-dev.md).
- TypeScript everywhere. Use `type` (not `interface`) for framework context
types so they satisfy `Record<string, unknown>`.
- All UI calls go through `getUI()` (returns `WizardUI` interface). Never import
the store directly from business logic.
the store directly from business logic. A program's `run` and `ciPreRun`
Comment thread
johncwaters marked this conversation as resolved.
use the runner context they receive, not `getUI()`.
- Shared helpers never call `getUI()`; they take a sink or return data. `debug()`
reaches the UI through the sink `src/ui/index.ts` installs.
- Outside `src/agent`, import the agent through `@agent` or `@agent/types`. Add
Expand Down
193 changes: 193 additions & 0 deletions docs/developer-interfaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Developer interfaces

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

personal opinion/experience: having code/interfaces in docs drifts quickly compared to linking to the code itself. As a human my eyes glaze over and as an agent I want them reading the actual code not the potential code in docs.

The actual interface and everything is sick!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

NotVincent here. Agreed. The field tables now link straight to the types, and each field has its comment next to it in the code. Each surface keeps one short example for the call shape.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Human vincent here. Yeah this is a good point. We can deal with this later. Since we're rippping things apart, this just makes the transition easy. Good catch @johncwaters


There are four ways to run the wizard. Most end users run it from the TUI. The
headless runner runs the same flow without a terminal UI. `runProgram` runs just
one program, and `runAgent` runs only the agent.

| Way | Entry | Use it for | Reference |
| ------------ | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------- |
| TUI | `npx @posthog/wizard`, through [`run-wizard.ts`](../src/lib/runners/run-wizard.ts) | End users setting up PostHog in a terminal | [README](../README.md) |
| Headless | `npx @posthog/wizard --ci`, through [`run-non-interactive.ts`](../src/lib/runners/run-non-interactive.ts) | CI and scripts, with no prompts | [Local credentials](local-dev.md#credentials-for-local-ci-and-headless-runs) |
| `runProgram` | `runProgram(programId, input, options)` from `@programs` | One program, from code, with its policy and telemetry | [`runProgram`](#runprogram), [programs reference](../src/programs/README.md) |
| `runAgent` | `runAgent(config, input, options)` from `@agent` | Only the agent, from a resolved config | [`runAgent`](#runagent), [agent reference](../src/agent/README.md) |

![The TUI and the headless runner go through their own state to runProgram. The workbench calls runProgram directly. runProgram calls runAgent, and detection calls runAgent directly](images/wizard-run-paths.svg)

The dashed boxes are the state the TUI and the headless runner keep above
`runProgram`. The workbench and other integrations call `runProgram` directly.

`runProgram` and `runAgent` live in this repository and import through its path
aliases. The `@posthog/wizard` npm package publishes the CLI, not these
functions.

## `runProgram`

### What `runProgram` is for

`runProgram` is a clean interface between the TUI, which is the interface, and
the programmatic parts of what a wizard program does.

> ⚠️ **Temporary adapter.** The TUI and the headless runner reach `runProgram`
> through `runProgramAgent` in
> [`run-agent-legacy.ts`](../src/programs/run-agent-legacy.ts). This is a
> temporary adapter, and we will remove it in the full program.

Three things use it:

1. **The TUI and the headless runner.** Both call `runProgram` to run a full
wizard program.
2. **Testing.** The test workbench calls `runProgram` directly, with no TUI,
from the `wizard-program` service in
[wizard-workbench](https://github.com/PostHog/wizard-workbench).
3. **Other integrations.** If you want to integrate with the wizard more
directly, without everything on top, call `runProgram` yourself.

### Signatures

```ts
import { runProgram } from '@programs';
import type {
ProgramInput,
ProgramOptions,
ProgramRunOutcome,
} from '@programs/types';

// The shape `@programs` exports.
export const signature: (
programId: string,
input: ProgramInput,
options?: ProgramOptions,
) => Promise<ProgramRunOutcome> = runProgram;
```

- **`programId`** says which program runs.
- **`input`** says what to run and where: the program's run definition built
from its `ProgramConfig`, the install directory and the login.
- **`options`** is how the caller plugs in: credentials, questions, progress,
the approval and gate waits, flags and cancellation.

`runProgram` resolves to one `ProgramRunOutcome`.

### Field definitions

| Field | Type | What it's for |
| ------------------------------------------------------------ | ------------------- | -------------------------------------------------------------------------------- |
| `programId` | `string` | Which program runs. It names analytics, the route and the gateway spend. |
| [`input`](../src/programs/run-program.ts#L67) | `ProgramInput` | What to run and where. `installDir` and `run` are required. |
| [`input.program`](../src/programs/run-program.ts#L57) | `ProgramSettings` | The program's settings from its `ProgramConfig`. |
| [`options`](../src/programs/run-program.ts#L90) | `ProgramOptions` | The login, questions, approval and gate waits, flags, progress and cancellation. |
| [`options.onProgress`](../src/programs/program-store.ts#L17) | `ProgramProgress` | Agent events and program data snapshots. Never awaited. |
| [Outcome](../src/programs/run-program.ts#L107) | `ProgramRunOutcome` | How the run ended, the agent's result, the final data and the report path. |

The outcome's `data` and the `kind: 'program'` progress snapshots hold PostHog
tokens, including the refresh token. Don't log or serialize them.

`flags.ci` and `flags.signup` skip the AI-processing approval. Set them only
when consent is already settled.

### Do a quack

[`run-program-quack.ts`](examples/run-program-quack.ts) is the smallest
`runProgram` call. It runs one prompt that replies `quack`, logs status lines
and prints the outcome. Each step has a comment.

Run it from the repository root against the [local stack](local-dev.md):

```bash
npx tsx --tsconfig tsconfig.json docs/examples/run-program-quack.ts
```

It prints `reply: quack` and `outcome: success`.

### Cancellation

Pass a `signal` to cancel the run. The credentials, approval and gate waits
receive it, and each must settle when it aborts. A cancelled run resolves to
`aborted`, not a rejection.

### Failures

Most endings resolve to an outcome instead of throwing. Check `outcome` and read
`failure`. The promise rejects only when the call itself can't run, such as an
input field that can't be copied. The cases are in
[`run-program.ts`](../src/programs/run-program.ts#L164).

### Program callbacks

A program's `run` and `ciPreRun` receive a runner context, `RunnerContext` or
`CiRunnerContext`, instead of calling `getUI()`. Both types come from
`@programs/types` and are defined in
[`runner-context.ts`](../src/programs/runner-context.ts). Build it when you
build the run from a `ProgramConfig`, before you call `runProgram`.
Comment thread
johncwaters marked this conversation as resolved.

## `runAgent`

### What `runAgent` is for

`runAgent` runs only the agent. Call it when you have a resolved route and no
program policy to apply. It doesn't log in, ask for consent, load flags or
resolve a route. The caller does those.

### Signatures

```ts
import { runAgent } from '@agent';
import type {
AgentInteraction,
AgentProgress,
RunConfig,
RunInput,
RunResult,
} from '@agent/types';

// The shape `@agent` exports.
export const signature: (
config: RunConfig,
input: RunInput,
options?: {
onProgress?: (event: AgentProgress) => unknown;
interaction?: AgentInteraction;
signal?: AbortSignal;
},
) => Promise<RunResult> = runAgent;
```

- **`config`** says what the agent runs: the run definition, the route and the
tools.
- **`input`** says where and as whom: the project, the login and the flags.
- **`options`** carries progress, questions and cancellation.

`runAgent` resolves to one `RunResult`. It mints its own gateway token from
`input.credentials`.

### Field definitions

| Field | Type | What it's for |
| -------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------- |
| [`config`](../src/agent/runner/shared/types.ts#L151) | `RunConfig` | What the agent runs, with its route and tools. |
| [`config.run`](../src/agent/runner/shared/types.ts#L48) | `AgentRunDefinition` | The prompt and run options, such as `collectTranscript`. |
| [`config.allowedTools`](../src/agent/runner/shared/types.ts#L174) | `readonly string[]` | Tools added to the base tools. |
| [`config.disallowedTools`](../src/agent/runner/shared/types.ts#L176) | `readonly string[]` | Tools removed from the base tools. |
| [`input`](../src/agent/runner/shared/types.ts#L205) | `RunInput` | Where and as whom: the project, the login and the flags. |
| `options` | | `onProgress` for agent events, `interaction` for questions, `signal` to cancel. |
| [Result](../src/agent/runner/shared/types.ts#L307) | `RunResult` | How the run ended, with a snapshot of its tasks and transcript. |

### Callers

| Caller | What it runs |
| --------------------------------------------------------------------------------- | ----------------------------------------------- |
| `runProgram` | One program's agent run. |
| `detectProjectsWithAgent` in [`agentic.ts`](../src/programs/detection/agentic.ts) | The agentic project scan, one call per attempt. |
| [`a3-fault-probe.no-jest.ts`](../scripts/a3-fault-probe.no-jest.ts) | A fault probe against a local gateway. |

### Do a quack

[`run-agent-quack.ts`](examples/run-agent-quack.ts) is the smallest `runAgent`
call. It builds a `RunConfig` with one prompt and no Write, Edit or Bash, and
prints the transcript tail and the outcome. Each step has a comment.

```bash
npx tsx --tsconfig tsconfig.json docs/examples/run-agent-quack.ts
```

It prints `transcriptTail: quack` and `outcome: success`.
110 changes: 110 additions & 0 deletions docs/examples/run-agent-quack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable no-console -- the example prints to the terminal */
// Run only the agent with runAgent, against a local PostHog stack.
//
// npx tsx --tsconfig tsconfig.json docs/examples/run-agent-quack.ts
//
// Needs local PostHog on :8010 (with its ai-gateway) and context-mill on :8765.
// POSTHOG_PERSONAL_API_KEY logs in. WIZARD_CI_GATEWAY_TOKEN_FILE holds the gateway token.
// QUACK_INSTALL_DIR sets the project the agent runs in (default: the current directory).
import {
configureGatewayFromCIEnvironment,
runAgent,
RunOutcome,
} from '@agent';
import type { RunConfig, RunInput } from '@agent/types';
import {
Harness,
HAIKU_MODEL,
Sequence,
getSkillsBaseUrl,
} from '@shared/constants';
import { initLocalDev, POSTHOG_LOCAL_URL } from '@shared/local-dev';
import { getOrAskForProjectData } from '@utils/setup-utils';

// Point PostHog, skills and MCP at the local stack, like --local-posthog --local-context-mill --local-mcp.
initLocalDev({ localPosthog: true, localContextMill: true, localMcp: true });

// Log in with keys instead of the browser, the same way --ci does.
const apiKey = process.env.POSTHOG_PERSONAL_API_KEY;
if (!apiKey) throw new Error('Set POSTHOG_PERSONAL_API_KEY');
const programId = 'posthog-integration'; // a program the local gateway admits
const login = await getOrAskForProjectData({
signup: false,
ci: true, // with apiKey, this skips OAuth
apiKey,
baseUrl: POSTHOG_LOCAL_URL,
localMcp: true,
programId,
});
// Use the token in WIZARD_CI_GATEWAY_TOKEN_FILE at WIZARD_CI_GATEWAY_URL instead of minting one.
configureGatewayFromCIEnvironment(login.projectId, 'us');

// What the agent runs: one prompt, a small model, no Write, Edit or Bash.
const config: RunConfig = {
programId, // pins the gateway spend
run: {
integrationLabel: 'quack',
prompt: () => 'Reply with the single word quack. Use no tools.',
collectTranscript: true, // keep the agent's output for snapshot.transcriptTail
requestRemark: false, // no closing remark
spinnerMessage: 'Quacking...',
successMessage: 'Quacked',
estimatedDurationMinutes: 1,
reportFile: '',
docsUrl: 'https://posthog.com/docs',
},
composed: true, // a sub-run: no terminal outro
// runAgent doesn't resolve a route. Linear on the Anthropic harness keeps the transcript.
binding: {
sequence: Sequence.linear,
harness: Harness.anthropic,
model: HAIKU_MODEL,
},
switchboard: { program: programId, composed: true, flags: {} },
skillsBaseUrl: getSkillsBaseUrl(),
wizardFlags: {},
wizardFlagPayloads: {},
wizardMetadata: {},
disallowedTools: ['Write', 'Edit', 'Bash'],
};

// Where and as whom: the project, the login and the flags.
const input: RunInput = {
installDir: process.env.QUACK_INSTALL_DIR ?? process.cwd(),
credentials: {
accessToken: login.accessToken,
refreshToken: login.refreshToken,
expiresAt: login.expiresAt,
projectApiKey: login.projectApiKey,
host: login.host,
projectId: login.projectId,
missingScopes: login.missingScopes,
},
project: login.project,
apiUser: login.user,
flags: {
ci: false,
signup: false,
debug: false,
e2eAsk: false,
localMcp: true,
captureAio: false,
benchmark: false,
yaraReport: false,
},
host: { baseUrl: POSTHOG_LOCAL_URL },
};

// Run it. Each step the agent takes arrives as one activity line.
const result = await runAgent(config, input, {
onProgress: (event) => {
if (event.kind === 'activity') console.log(`activity: ${event.line}`);
},
});

// Every ending resolves to a result. Print the reply and the outcome.
console.log(`transcriptTail: ${result.snapshot.transcriptTail ?? ''}`);
console.log(`outcome: ${result.outcome}`);
if (result.outcome !== RunOutcome.Success)
console.log(`failure: ${result.failure.message}`);
process.exit(result.outcome === RunOutcome.Success ? 0 : 1);
Loading
Loading