You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π Re-spec β 2026-06-26 (config map + insertion points; each flag its own middleware)
Supersedes the earlier AgentBackend + selectBackend framing. The selection surface becomes a central
config map (the base) + named insertion points (middleware): two registries (ROUTERS, RUNNERS) and a ROUTES map define per-program defaults ({ router, runner, model }); each decision then flows through an
ordered middleware chain at a named point (resolveRouter, resolvePair) that terminates in the map β
no default-resolver functions. Each existing flag is its own middleware, named for it
(wizard-orchestrator β wizardOrchestrator, wizard-runner β wizardRunner) β nothing renamed, replaced,
or created. Two stacked PRs: the map + empty points first, then the per-flag middleware.
Replace the two switch points and the 4 hardcoded model constants with a config map + insertion points.
The base is just the map; control is asserted by middleware, one per flag. New capability: per-program
model selection (today model is hardcoded sonnet). Existing flags keep exact behavior, now as middleware.
RUNNERS: Record<RunnerName, AgentRunner> β leaf engines (anthropic now; pi in 04 β pi.dev runner (pi)Β #524). AgentRunner.run(inputs & { model: string }) β drop-in for today's linear body, consuming BootstrapResult.
MODELS aliasβgateway-id map (sonnet/opus). Replaces the hardcoded model in agent-interface.ts, agent-prompt-loader.ts, mcp-prompt-streaming.ts (+ 04 β pi.dev runner (pi)Β #524 pi).
ROUTERS β linearRouter now; orchestratorRouter is a separate stack.
ROUTES: Partial<Record<ProgramId, Route>> where Route = { router, runner, model, roles? }; DEFAULT_ROUTE = { linear, anthropic, sonnet }. This map is the base β no defaultRouter/defaultPair
functions.
Rewire runner/index.ts to ROUTERS[resolveRouter(ctx)].run(...); routers call resolvePair(ctx, role)
per leaf. Empty chains β every program resolves to linear / anthropic / sonnet β byte-identical to today.
Extract today's linear body into RUNNERS.anthropic verbatim.
Log the resolved router + runner + model every run.
Scope β PR #692b (per-flag middleware for the existing flags β adds no flags)
Add one middleware per existing flag, named for the flag:
wizardRunner (PAIR_MIDDLEWARE): defers-then-modifies β ALWAYS calls next() first to get the
base pair, then overlays only the runner field iff wizard-runner β {anthropic, pi}. The model
always comes from the config map, never the flag. (Note: this is not the assert-or-defer shape of wizardOrchestrator β it never short-circuits; pattern: const p = next(); const r = flags['wizard-runner']; return (r==='anthropic'||r==='pi') ? {...p, runner:r} : p.)
Construct ResolveCtx (flags + env) once after bootstrap; hand resolvePair into the RouterContext so
every leaf (linear's one, orchestrator's many) flows through the same point.
No PostHog flag changes.wizard-orchestrator / wizard-runner keep their current definitions.
(No standalone guard middleware β dropped.)
Acceptance criteria
pnpm build && pnpm test && pnpm fix pass; existing e2e unchanged; with the flag middleware and no flag
set, every program resolves to linear / anthropic / sonnet (behavior identical).
Chains unit-tested: empty chains β map defaults; wizardOrchestrator / wizardRunner each in
isolation (assert-vs-defer).
wizard-orchestrator / wizard-runner behave exactly as before β asserted by test.
Resolved router + runner + model log line present.
Files
src/lib/agent/runner/runner-plan.ts (registries, MODELS, ROUTES/DEFAULT_ROUTE, run/chains, resolveRouter/resolvePair, linearRouter, the per-flag middleware), runner/index.ts, runner/backends/anthropic.ts, agent-interface.ts, agent-prompt-loader.ts, mcp-prompt-streaming.ts.
(src/lib/constants.ts flag keys unchanged.)
Notes
The orchestrator is not a runner β it's a Router (separate stack on this core). #692a is the config map
empty points (the "clean base"); #692b adds one named middleware per existing flag. Future controls (cost
caps, per-org policy, A/B, a model-override flag) are each their own new middleware at these same points β no
rewrite.
Landed as (2026-06-27)
Implemented across #692 (pi/01-seam-flag) and #693 (pi/02-pi-backend), not a separate #692a/#692b:
feat(runner): central runner plan + anthropic runner seam (#521)Β #692 β runner-plan.ts (config map + resolvePair over an empty PAIR_MIDDLEWARE chain, RUNNERS/MODELS/ROUTES), the anthropic runner seam, the 4 model literals β DEFAULT_AGENT_MODEL/MODELS, WIZARD_RUNNER_FLAG_KEY. Behavior identical.
The router side (resolveRouter + wizardOrchestrator middleware) was not added; the existing isOrchestratorEnabled fork in runner/index.ts is untouched. That stays the future additive step.
π Re-spec β 2026-06-26 (config map + insertion points; each flag its own middleware)
Supersedes the earlier
AgentBackend+selectBackendframing. The selection surface becomes a centralconfig map (the base) + named insertion points (middleware): two registries (
ROUTERS,RUNNERS) and aROUTESmap define per-program defaults ({ router, runner, model }); each decision then flows through anordered middleware chain at a named point (
resolveRouter,resolvePair) that terminates in the map βno default-resolver functions. Each existing flag is its own middleware, named for it
(
wizard-orchestratorβwizardOrchestrator,wizard-runnerβwizardRunner) β nothing renamed, replaced,or created. Two stacked PRs: the map + empty points first, then the per-flag middleware.
Epic: #520 Β· Behavior change: none (defaults reproduce today exactly) Β· Depends on: #688
Summary
Replace the two switch points and the 4 hardcoded model constants with a config map + insertion points.
The base is just the map; control is asserted by middleware, one per flag. New capability: per-program
model selection (today model is hardcoded sonnet). Existing flags keep exact behavior, now as middleware.
Scope β PR #692a (config map + empty insertion points, NO flags)
runner-plan.ts:RUNNERS: Record<RunnerName, AgentRunner>β leaf engines (anthropicnow;piin 04 β pi.dev runner (pi)Β #524).AgentRunner.run(inputs & { model: string })β drop-in for today's linear body, consumingBootstrapResult.MODELSaliasβgateway-id map (sonnet/opus). Replaces the hardcoded model inagent-interface.ts,agent-prompt-loader.ts,mcp-prompt-streaming.ts(+ 04 β pi.dev runner (pi)Β #524 pi).ROUTERSβlinearRouternow;orchestratorRouteris a separate stack.ROUTES: Partial<Record<ProgramId, Route>>whereRoute = { router, runner, model, roles? };DEFAULT_ROUTE = { linear, anthropic, sonnet }. This map is the base β nodefaultRouter/defaultPairfunctions.
Mw<D> = (ctx, next) => D,run(chain, ctx, base), two chainsROUTER_MIDDLEWARE/PAIR_MIDDLEWAREinitialized empty, exposed asresolveRouter/resolvePairwhose terminal reads
ROUTES[program] ?? DEFAULT_ROUTE(pair ={ runner, model, ...roles?.[role] }).runner/index.tstoROUTERS[resolveRouter(ctx)].run(...); routers callresolvePair(ctx, role)per leaf. Empty chains β every program resolves to
linear / anthropic / sonnetβ byte-identical to today.Extract today's linear body into
RUNNERS.anthropicverbatim.Scope β PR #692b (per-flag middleware for the existing flags β adds no flags)
wizardOrchestrator(ROUTER_MIDDLEWARE):wizard-orchestrator === 'true'β assertorchestrator,else
next().wizardRunner(PAIR_MIDDLEWARE): defers-then-modifies β ALWAYS callsnext()first to get thebase pair, then overlays only the
runnerfield iffwizard-runner β {anthropic, pi}. Themodelalways comes from the config map, never the flag. (Note: this is not the assert-or-defer shape of
wizardOrchestratorβ it never short-circuits; pattern:const p = next(); const r = flags['wizard-runner']; return (r==='anthropic'||r==='pi') ? {...p, runner:r} : p.)ResolveCtx(flags + env) once after bootstrap; handresolvePairinto theRouterContextsoevery leaf (linear's one, orchestrator's many) flows through the same point.
wizard-orchestrator/wizard-runnerkeep their current definitions.Acceptance criteria
pnpm build && pnpm test && pnpm fixpass; existing e2e unchanged; with the flag middleware and no flagset, every program resolves to
linear / anthropic / sonnet(behavior identical).wizardOrchestrator/wizardRunnereach inisolation (assert-vs-defer).
wizard-orchestrator/wizard-runnerbehave exactly as before β asserted by test.Files
src/lib/agent/runner/runner-plan.ts(registries,MODELS,ROUTES/DEFAULT_ROUTE,run/chains,resolveRouter/resolvePair,linearRouter, the per-flag middleware),runner/index.ts,runner/backends/anthropic.ts,agent-interface.ts,agent-prompt-loader.ts,mcp-prompt-streaming.ts.(
src/lib/constants.tsflag keys unchanged.)Notes
The orchestrator is not a runner β it's a
Router(separate stack on this core). #692a is the config mapcaps, per-org policy, A/B, a model-override flag) are each their own new middleware at these same points β no
rewrite.
Landed as (2026-06-27)
Implemented across #692 (
pi/01-seam-flag) and #693 (pi/02-pi-backend), not a separate #692a/#692b:runner-plan.ts(config map +resolvePairover an emptyPAIR_MIDDLEWAREchain,RUNNERS/MODELS/ROUTES), the anthropic runner seam, the 4 model literals βDEFAULT_AGENT_MODEL/MODELS,WIZARD_RUNNER_FLAG_KEY. Behavior identical.wizardRunnerpair-middleware (reads the existingwizard-runnerflag) alongside the pi runner it selects.resolveRouter+wizardOrchestratormiddleware) was not added; the existingisOrchestratorEnabledfork inrunner/index.tsis untouched. That stays the future additive step.