Skip to content

feat: Pi coding-agent as third AI assistant provider #965

@Wirasm

Description

@Wirasm

Feature: Pi Coding Agent Provider Integration

Summary

Add @mariozechner/pi-coding-agent (v0.65.2) as a third AI provider alongside Claude and Codex. Pi is a coding agent harness (like Claude Code SDK and Codex SDK) that wraps 15+ LLM providers — adding it unlocks Google Gemini, Mistral, Groq, xAI, OpenRouter, and any OpenAI-compatible endpoint through a single integration. The core challenge is bridging Pi's callback-based subscribe() event system into Archon's AsyncGenerator<MessageChunk> contract.

User Story

As a user of Archon
I want to use Pi as an AI provider in workflows and conversations
So that I can access models from Google, Mistral, Groq, xAI, OpenRouter, and other providers without individual integrations

Problem Statement

Archon currently supports only two AI providers: Claude (via @anthropic-ai/claude-agent-sdk) and Codex (via @openai/codex-sdk). Users wanting to use other models (Gemini, Mistral, Grok, local models) have no path. Pi coding agent wraps 15+ providers with built-in coding tools (read, write, edit, bash) and a TypeScript SDK suitable for embedding.

Solution Statement

Implement PiProvider as a new IAgentProvider that:

  1. Creates a pi-coding-agent AgentSession per sendQuery call
  2. Bridges Pi's callback-based subscribe() events to an AsyncGenerator<MessageChunk> using an async queue
  3. Maps Pi's event types (text_delta, tool_execution_start/end, thinking_delta, agent_end, turn_end) to Archon's MessageChunk variants
  4. Uses SessionManager.inMemory() (no file persistence — session resume not supported in v1)
  5. Propagates cwd, model selection, system prompt, and abort signal

Metadata

Field Value
Type NEW_CAPABILITY
Complexity HIGH
Systems Affected @archon/core (clients, config), @archon/workflows (schemas, validation, executor, dag-executor, deps), @archon/server (config schemas), @archon/web (provider selects)
Dependencies @mariozechner/pi-coding-agent@^0.65.2, @mariozechner/pi-ai@^0.65.2
Estimated Tasks 14

UX Design

Before State

╔═══════════════════════════════════════════════════════════════╗
║                        BEFORE STATE                          ║
╠═══════════════════════════════════════════════════════════════╣
║                                                               ║
║  ┌──────────┐     ┌──────────────┐     ┌──────────────┐      ║
║  │ Workflow  │ ──► │  provider:   │ ──► │ Claude Code  │      ║
║  │   YAML    │     │ claude|codex │     │ or Codex SDK │      ║
║  └──────────┘     └──────────────┘     └──────────────┘      ║
║                                                               ║
║  Only 2 providers: Claude (Anthropic) and Codex (OpenAI)      ║
║  Want Gemini/Mistral/Groq? Out of luck.                       ║
║                                                               ║
╚═══════════════════════════════════════════════════════════════╝

After State

╔═══════════════════════════════════════════════════════════════╗
║                        AFTER STATE                           ║
╠═══════════════════════════════════════════════════════════════╣
║                                                               ║
║  ┌──────────┐     ┌────────────────────┐                      ║
║  │ Workflow  │ ──► │     provider:      │                      ║
║  │   YAML    │     │ claude|codex|pi    │                      ║
║  └──────────┘     └────────┬───────────┘                      ║
║                            │                                  ║
║            ┌───────────────┼───────────────┐                  ║
║            ▼               ▼               ▼                  ║
║     ┌────────────┐  ┌────────────┐  ┌────────────┐           ║
║     │ Claude SDK │  │ Codex SDK  │  │  Pi Agent   │           ║
║     │ (Anthropic)│  │  (OpenAI)  │  │  (15+ LLMs) │          ║
║     └────────────┘  └────────────┘  └─────┬──────┘           ║
║                                           │                   ║
║                        ┌──────────────────┼────────────┐      ║
║                        ▼         ▼        ▼       ▼    ▼      ║
║                     Gemini   Mistral    Groq    xAI  ...     ║
║                                                               ║
╚═══════════════════════════════════════════════════════════════╝

Interaction Changes

Location Before After User Impact
Workflow YAML provider: claude | codex claude | codex | pi Can use Pi in workflows
Web UI BuilderToolbar 2 provider options 3 provider options Can select Pi in builder
Web UI NodeInspector 2 provider options 3 provider options Can set Pi per-node
.archon/config.yaml assistant: claude | codex assistant: claude | codex | pi Can set Pi as default
DEFAULT_AI_ASSISTANT env claude | codex claude | codex | pi Env-level Pi default

Mandatory Reading

CRITICAL: Implementation agent MUST read these files before starting any task:

Priority File Lines Why Read This
P0 packages/core/src/providers/codex.ts all Primary pattern to MIRROR — simpler than Claude, same harness model
P0 packages/core/src/types/index.ts 188-386 MessageChunk, TokenUsage, IAgentProvider, AgentRequestOptions — the contract
P0 packages/core/src/providers/factory.ts all Registration point — add case 'pi'
P1 packages/workflows/src/deps.ts 224-262 AgentProviderFactory type union + WorkflowConfig.assistants shape
P1 packages/workflows/src/model-validation.ts all isClaudeModel, isModelCompatible — add isPiModel
P1 packages/workflows/src/dag-executor.ts 346-608 resolveNodeProviderAndModel — add Pi branch
P1 packages/workflows/src/executor.ts 278-312 Workflow-level provider resolution — widen type
P1 packages/core/src/config/config-types.ts all Config type definitions — add Pi everywhere
P2 packages/core/src/providers/codex.test.ts all Test pattern to FOLLOW
P2 packages/workflows/src/schemas/workflow.ts 29-43 workflowBaseSchema.provider enum
P2 packages/workflows/src/schemas/dag-node.ts 113-139 dagNodeBaseSchema.provider enum

External Documentation:

Source Section Why Needed
Pi SDK docs createAgentSession, subscribe, tools Core embedding API
Pi agent-core types AgentEvent, AgentTool Event types to map
Pi AI types AssistantMessageEvent, Model, KnownProvider, Usage Model and streaming types

Patterns to Mirror

LAZY_LOGGER:

// SOURCE: packages/core/src/providers/codex.ts:17-21
let cachedLog: ReturnType<typeof createLogger> | undefined;
function getLog(): ReturnType<typeof createLogger> {
  if (!cachedLog) cachedLog = createLogger('provider.codex');
  return cachedLog;
}

ERROR_CLASSIFICATION:

// SOURCE: packages/core/src/providers/codex.ts:112-121
function classifyCodexError(
  errorMessage: string,
  stderrOutput: string
): 'rate_limit' | 'auth' | 'model_access' | 'crash' | 'unknown' {
  const combined = `${errorMessage} ${stderrOutput}`.toLowerCase();
  if (RATE_LIMIT_PATTERNS.some(p => combined.includes(p))) return 'rate_limit';
  if (AUTH_PATTERNS.some(p => combined.includes(p))) return 'auth';
  if (MODEL_ACCESS_PATTERNS.some(p => combined.includes(p))) return 'model_access';
  if (SUBPROCESS_CRASH_PATTERNS.some(p => combined.includes(p))) return 'crash';
  return 'unknown';
}

RETRY_LOOP:

// SOURCE: packages/core/src/providers/codex.ts (same pattern as claude.ts)
const MAX_SUBPROCESS_RETRIES = 3;
const RETRY_BASE_DELAY_MS = 2000;
for (let attempt = 0; attempt <= MAX_SUBPROCESS_RETRIES; attempt++) {
  try {
    // ... execute ...
    return;
  } catch (error) {
    // exponential backoff: RETRY_BASE_DELAY_MS * 2^attempt
  }
}

FACTORY_CASE:

// SOURCE: packages/core/src/providers/factory.ts:26-37
case 'codex':
  getLog().debug({ provider: 'codex' }, 'provider_selected');
  return new CodexProvider();

PROVIDER_ENUM (Zod):

// SOURCE: packages/workflows/src/schemas/workflow.ts:32
provider: z.enum(['claude', 'codex']).optional(),

MODEL_VALIDATION:

// SOURCE: packages/workflows/src/model-validation.ts:1-16
export function isClaudeModel(model: string): boolean { ... }
export function isModelCompatible(provider: 'claude' | 'codex', model?: string): boolean { ... }

CONFIG_ASSISTANTS_SHAPE:

// SOURCE: packages/core/src/config/config-types.ts:196-199
assistants: {
  claude: ClaudeProviderDefaults;
  codex: ProviderDefaults;
};

DAG_PROVIDER_OPTIONS:

// SOURCE: packages/workflows/src/dag-executor.ts:476-485
if (provider === 'codex') {
  options = {
    model,
    modelReasoningEffort: config.assistants.codex.modelReasoningEffort,
    webSearchMode: config.assistants.codex.webSearchMode,
    additionalDirectories: config.assistants.codex.additionalDirectories,
  };
}

TEST_MOCK_PATTERN:

// SOURCE: packages/core/src/providers/codex.test.ts
mock.module('@openai/codex-sdk', () => ({ Codex: MockCodex }));
import { CodexProvider } from './codex';

Files to Change

File Action Justification
packages/core/src/providers/pi.ts CREATE PiProvider class implementing IAgentProvider
packages/core/src/providers/factory.ts UPDATE Add case 'pi' to switch
packages/core/src/providers/index.ts UPDATE Export PiProvider
packages/core/src/config/config-types.ts UPDATE Add 'pi' to all provider unions + PiProviderDefaults
packages/core/src/config/config-loader.ts UPDATE Add 'pi' to env var check (line 216) + defaults
packages/workflows/src/model-validation.ts UPDATE Add isPiModel(), widen isModelCompatible()
packages/workflows/src/schemas/workflow.ts UPDATE z.enum(['claude', 'codex', 'pi'])
packages/workflows/src/schemas/dag-node.ts UPDATE z.enum(['claude', 'codex', 'pi'])
packages/workflows/src/deps.ts UPDATE Widen AgentProviderFactory, add pi to WorkflowConfig.assistants
packages/workflows/src/executor.ts UPDATE Widen resolvedProvider type, add Pi model inference
packages/workflows/src/dag-executor.ts UPDATE Add Pi branch in resolveNodeProviderAndModel + buildLoopNodeOptions
packages/workflows/src/loader.ts UPDATE Add 'pi' to provider literal check (line 272)
packages/server/src/routes/schemas/config.schemas.ts UPDATE Add 'pi' to Zod enums and assistants object
packages/web/src/components/workflows/BuilderToolbar.tsx UPDATE Add Pi option to provider select
packages/web/src/components/workflows/NodeInspector.tsx UPDATE Add Pi option to provider select
packages/core/src/providers/pi.test.ts CREATE Unit tests for PiProvider
packages/core/src/providers/factory.test.ts UPDATE Add Pi test case, update error string assertion

NOT Building (Scope Limits)

  • Session resume — Pi sessions are file-based (.jsonl). Resume support requires persisting file paths and reopening sessions. Deferred to v2 — resumeSessionId will be ignored, each turn starts fresh.
  • Pi-specific config options — Pi supports thinkingLevel, custom tools, extensions. V1 only exposes model in config (assistants.pi.model). Advanced options deferred.
  • OAuth providers — Pi supports OAuth-based model access (Claude Pro, ChatGPT Plus). Not wiring this in v1 — API keys only.
  • Structured output — Pi may support structured output via model-level features. Not mapped to outputFormat in v1.
  • Token cost tracking — Pi's Usage type includes cost breakdown. Not surfacing in v1 beyond basic input/output token counts.

Step-by-Step Tasks

Task 1: Install Pi dependencies

  • ACTION: Add @mariozechner/pi-coding-agent and @mariozechner/pi-ai to packages/core/package.json
  • IMPLEMENT: bun add @mariozechner/pi-coding-agent@^0.65.2 @mariozechner/pi-ai@^0.65.2 --cwd packages/core
  • GOTCHA: Pi pulls @anthropic-ai/sdk and openai as transitive deps — check for version conflicts with existing deps. Bun workspaces should deduplicate compatible ranges.
  • VALIDATE: bun install succeeds, bun run type-check still passes

Task 2: Define provider type alias

  • ACTION: Create a shared ProviderType literal union to avoid repeating 'claude' | 'codex' | 'pi' in 20+ places
  • IMPLEMENT: In packages/core/src/types/index.ts, add: export type ProviderType = 'claude' | 'codex' | 'pi';
  • UPDATE: Refactor config-types.ts, factory.ts, conversations.ts to use ProviderType where the literal union is currently hardcoded
  • NOTE: @archon/workflows cannot import from @archon/core (circular dep). The deps.ts type union must be updated independently (or use string with runtime validation).
  • VALIDATE: bun run type-check

Task 3: CREATE packages/core/src/providers/pi.ts

  • ACTION: Implement PiProvider class implementing IAgentProvider

  • IMPLEMENT: The core of the integration. Key design decisions:

    Callback-to-AsyncGenerator bridge:

    // Create an async queue that bridges subscribe() callbacks to yield
    interface QueueItem {
      value: MessageChunk;
      done: false;
    } | { done: true }
    
    function createAsyncQueue<T>(): {
      push: (item: T) => void;
      finish: () => void;
      error: (err: Error) => void;
      [Symbol.asyncIterator]: () => AsyncIterator<T>;
    }

    Session lifecycle per sendQuery call:

    1. Create AuthStorage, set API key from env vars based on resolved provider
    2. Get model via getModel(piProvider, piModelId) — requires mapping Archon's flat model string to Pi's (provider, modelId) tuple
    3. Create DefaultResourceLoader with systemPromptOverride if options.systemPrompt is set
    4. Call createAgentSession({ cwd, model, tools: createCodingTools(cwd), sessionManager: SessionManager.inMemory(), ... })
    5. Subscribe to events, map to MessageChunk, push to async queue
    6. Call session.prompt(prompt)
    7. On agent_end, push { type: 'result' } with token usage from accumulated turn_end events, then finish the queue
    8. Yield from the async queue

    Event mapping:

    Pi Event MessageChunk
    message_update + text_delta { type: 'assistant', content: delta }
    message_update + thinking_delta { type: 'thinking', content: delta }
    tool_execution_start { type: 'tool', toolName, toolInput: args }
    tool_execution_end { type: 'tool_result', toolName, toolOutput }
    message_update + error { type: 'system', content: errorMessage }
    agent_end { type: 'result', tokens }

    Model string mapping:
    Pi uses (provider, modelId) tuples (e.g., ('anthropic', 'claude-opus-4-5')). Archon uses flat strings. The PiProvider needs a parser:

    • Format: "pi:<provider>/<model>" e.g., "pi:anthropic/claude-opus-4-5", "pi:google/gemini-2.5-pro", "pi:openai/gpt-5.1"
    • If no prefix, treat as a Pi model alias (future: add common aliases)
    • Store mapping logic in a parsePiModel(modelString: string): { provider: KnownProvider, modelId: string } helper

    Abort handling:
    Wire options.abortSignalsession.abort(). Subscribe to the signal's abort event.

    Error classification:
    Define classifyPiError() following the same pattern as Codex. Pi throws plain Error from prompt() for auth issues and missing models. LLM failures arrive as events with stopReason: 'error'.

  • MIRROR: packages/core/src/providers/codex.ts — same class structure, lazy logger, retry loop, error classification

  • IMPORTS: createAgentSession, createCodingTools, SessionManager, AuthStorage, ModelRegistry, DefaultResourceLoader from @mariozechner/pi-coding-agent; getModel from @mariozechner/pi-ai

  • GOTCHA: session.prompt() returns Promise<void> and does NOT await agent completion — completion is signaled via the agent_end event through subscribe(). The async generator must not return until agent_end fires.

  • GOTCHA: session.dispose() must be called in a finally block to clean up resources.

  • GOTCHA: Pi's @anthropic-ai/sdk transitive dep may conflict with the project's @anthropic-ai/claude-agent-sdk. Verify at runtime.

  • VALIDATE: bun run type-check

Task 4: UPDATE packages/core/src/providers/factory.ts

  • ACTION: Add case 'pi' to the switch
  • IMPLEMENT:
    case 'pi':
      getLog().debug({ provider: 'pi' }, 'provider_selected');
      return new PiProvider();
    Update error message: "Supported types: 'claude', 'codex', 'pi'"
  • MIRROR: factory.ts:28-33
  • VALIDATE: bun run type-check

Task 5: UPDATE packages/core/src/providers/index.ts

  • ACTION: Export PiProvider
  • IMPLEMENT: Add export { PiProvider } from './pi';
  • MIRROR: index.ts:11-12
  • VALIDATE: bun run type-check

Task 6: UPDATE packages/core/src/config/config-types.ts

  • ACTION: Add Pi to all provider unions and assistants objects
  • IMPLEMENT:
    1. Create PiProviderDefaults interface: { model?: string; } (minimal for v1)
    2. GlobalConfig.defaultAssistant: 'claude' | 'codex' | 'pi' (line 41)
    3. GlobalConfig.assistants: add pi?: PiProviderDefaults (line 48)
    4. RepoConfig.assistant: 'claude' | 'codex' | 'pi' (line 98)
    5. RepoConfig.assistants: add pi?: PiProviderDefaults (line 103)
    6. MergedConfig.assistant: 'claude' | 'codex' | 'pi' (line 195)
    7. MergedConfig.assistants: add pi: PiProviderDefaults (line 196-199)
    8. SafeConfig.assistant: 'claude' | 'codex' | 'pi' (line 251)
    9. SafeConfig.assistants: add pi: Pick<PiProviderDefaults, 'model'> (line 253)
  • VALIDATE: bun run type-check

Task 7: UPDATE packages/core/src/config/config-loader.ts

  • ACTION: Add 'pi' to env var validation and config defaults
  • IMPLEMENT:
    1. Line 216: if (envAssistant === 'claude' || envAssistant === 'codex' || envAssistant === 'pi')
    2. Add pi: {} to the defaults merge in getDefaults() under assistants
    3. Add pi: {} to the merged config construction
  • VALIDATE: bun run type-check

Task 8: UPDATE packages/workflows/src/model-validation.ts

  • ACTION: Add isPiModel() function and widen isModelCompatible()
  • IMPLEMENT:
    export function isPiModel(model: string): boolean {
      return model.startsWith('pi:');
    }
    
    export function isModelCompatible(
      provider: 'claude' | 'codex' | 'pi',
      model?: string
    ): boolean {
      if (!model) return true;
      if (provider === 'claude') return isClaudeModel(model);
      if (provider === 'pi') return isPiModel(model) || (!isClaudeModel(model));
      // Codex: reject Claude aliases and Pi-prefixed models
      return !isClaudeModel(model) && !isPiModel(model);
    }
    Pi accepts pi:provider/model strings AND generic model strings (not Claude aliases). Codex rejects Pi-prefixed models.
  • VALIDATE: bun run type-check

Task 9: UPDATE Zod schemas (workflow + dag-node)

  • ACTION: Add 'pi' to provider enums in both schemas
  • IMPLEMENT:
    • packages/workflows/src/schemas/workflow.ts:32: z.enum(['claude', 'codex', 'pi'])
    • packages/workflows/src/schemas/dag-node.ts:119: z.enum(['claude', 'codex', 'pi'])
  • VALIDATE: bun run type-check

Task 10: UPDATE packages/workflows/src/deps.ts

  • ACTION: Widen AgentProviderFactory type and add pi to WorkflowConfig.assistants
  • IMPLEMENT:
    1. Line 224: export type AgentProviderFactory = (provider: 'claude' | 'codex' | 'pi') => IWorkflowAgentProvider;
    2. Lines 249-261: Add to WorkflowConfig.assistants:
      pi: {
        model?: string;
      };
    3. Line 236: assistant: 'claude' | 'codex' | 'pi';
  • GOTCHA: The compile-time assertion in store-adapter.ts:19 (const assertConfigCompat: WorkflowConfig = {} as MergedConfig) will fail until BOTH MergedConfig and WorkflowConfig have the pi field. Do Tasks 6 and 10 together.
  • VALIDATE: bun run type-check

Task 11: UPDATE workflow executor + dag-executor + loader

  • ACTION: Add Pi to provider resolution logic
  • IMPLEMENT:
    1. executor.ts:281: Widen type to 'claude' | 'codex' | 'pi'. Add model inference:
      } else if (workflow.model && isPiModel(workflow.model)) {
        resolvedProvider = 'pi';
        providerSource = 'inferred from workflow model';
      }
      Insert BEFORE the else if (workflow.model) codex fallback (line 289).
    2. dag-executor.ts:350-608: Widen all 'claude' | 'codex' types. Add Pi inference in resolveNodeProviderAndModel:
      } else if (node.model && isPiModel(node.model)) {
        provider = 'pi';
      }
      Add Pi options branch after the codex branch (line 476):
      else if (provider === 'pi') {
        options = { model };
      }
      Add Pi warnings for Claude-only fields (same pattern as Codex warnings at lines 387-472).
    3. loader.ts:272: Add || raw.provider === 'pi'
    4. dag-executor.ts buildLoopNodeOptions: Add Pi branch (same pattern).
    5. Update ALL 'claude' | 'codex' type annotations in parameters and variables across both files.
  • GOTCHA: There are ~15 locations where 'claude' | 'codex' is used as a type annotation in dag-executor.ts. Use search to find all of them.
  • VALIDATE: bun run type-check

Task 12: UPDATE server config schemas + API generated types

  • ACTION: Add Pi to server-side Zod schemas
  • IMPLEMENT:
    1. packages/server/src/routes/schemas/config.schemas.ts:10: z.enum(['claude', 'codex', 'pi'])
    2. Same file line 37: z.enum(['claude', 'codex', 'pi'])
    3. Add pi to safeConfigSchema.assistants:
      pi: z.object({ model: z.string().optional() }),
    4. Add pi to updateAssistantConfigBodySchema:
      pi: z.object({ model: z.string() }).optional(),
  • POST-STEP: Regenerate frontend types: bun run dev:server then bun --filter @archon/web generate:types
  • VALIDATE: bun run type-check

Task 13: UPDATE Web UI provider selects

  • ACTION: Add Pi option to BuilderToolbar and NodeInspector
  • IMPLEMENT:
    1. packages/web/src/components/workflows/BuilderToolbar.tsx:14: Update type to 'claude' | 'codex' | 'pi' | undefined
    2. Line 21: Same type update for onProviderChange
    3. Line 161: Update cast to include 'pi'
    4. After line 167: Add <option value="pi">Pi</option>
    5. packages/web/src/components/workflows/NodeInspector.tsx:324: Update cast to include 'pi'
    6. After line 331: Add <option value="pi">Pi</option>
  • VALIDATE: bun run type-check && bun run lint

Task 14: CREATE tests + UPDATE factory test

  • ACTION: Create pi.test.ts and update factory.test.ts
  • IMPLEMENT:
    1. Create packages/core/src/providers/pi.test.ts:
      • Mock @mariozechner/pi-coding-agent and @mariozechner/pi-ai with mock.module()
      • Mock createAgentSession to return a controllable session object
      • Mock subscribe to call the callback with test events
      • Mock prompt to resolve immediately
      • Test cases:
        • Yields assistant text from text_delta events
        • Yields thinking from thinking_delta events
        • Yields tool from tool_execution_start
        • Yields tool_result from tool_execution_end
        • Yields result with token usage from agent_end
        • Handles abort signal (calls session.abort())
        • Retries on transient errors
        • Throws on auth errors (no retry)
        • Calls session.dispose() in finally
    2. Update packages/core/src/providers/factory.test.ts:
      • Add mock for pi module
      • Add test: 'creates PiProvider for pi type'
      • Update error message assertion: "Supported types: 'claude', 'codex', 'pi'"
    3. Ensure pi.test.ts runs in the same bun test src/providers/ batch (no new test split needed since Pi mocks don't conflict with Claude/Codex mocks — they mock different module paths)
  • VALIDATE: bun run test

Testing Strategy

Unit Tests to Write

Test File Test Cases Validates
packages/core/src/providers/pi.test.ts 9 cases: text, thinking, tool, tool_result, result, abort, retry, auth error, dispose PiProvider event mapping and lifecycle
packages/core/src/providers/factory.test.ts +2 cases: pi creation, updated error msg Factory registration

Edge Cases Checklist

  • Pi session creation fails (missing API key) — should throw auth error, no retry
  • Pi model string parsing — pi:google/gemini-2.5-pro vs bare gemini-2.5-pro vs invalid format
  • Agent ends with error (stopReason: 'error') — should yield result with isError: true
  • Agent aborted mid-stream — should clean up session and not hang
  • Abort signal fires before prompt() is called — should not start session
  • Multiple text_delta events — should yield each as separate assistant chunk (not buffer)
  • Empty text_delta — should skip (not yield empty assistant chunk)
  • Tool execution with isError: true — should still yield tool_result with error info
  • Pi model string validation in isModelCompatible — pi:anthropic/opus should be valid for Pi, invalid for Claude/Codex

Validation Commands

Level 1: STATIC_ANALYSIS

bun run type-check && bun run lint

EXPECT: Exit 0, no errors or warnings

Level 2: UNIT_TESTS

bun run test

EXPECT: All tests pass across all packages

Level 3: FULL_SUITE

bun run validate

EXPECT: Type-check + lint + format + tests all pass

Level 4: MANUAL_VALIDATION

  1. Start dev server: bun run dev:server
  2. Open Web UI workflow builder — verify Pi appears in provider dropdowns
  3. Create a test workflow YAML with provider: pi
  4. Validate the workflow via API: POST /api/workflows/validate
  5. Verify GET /api/config returns Pi in the assistants object
  6. (If Pi API key available) Run a simple workflow with Pi provider end-to-end

Acceptance Criteria

  • PiProvider implements IAgentProvider and yields correct MessageChunk variants
  • provider: pi works in workflow YAML and is validated by Zod schemas
  • Web UI shows Pi as a provider option in both BuilderToolbar and NodeInspector
  • .archon/config.yaml accepts assistant: pi and assistants.pi.model
  • DEFAULT_AI_ASSISTANT=pi env var works
  • Model validation: pi:google/gemini-2.5-pro valid for Pi, rejected for Claude/Codex
  • All existing tests pass (no regressions)
  • New PiProvider unit tests pass
  • bun run validate succeeds

Completion Checklist

  • All 14 tasks completed in dependency order
  • Each task validated immediately after completion
  • Level 1: Static analysis (type-check + lint) passes
  • Level 2: Unit tests pass
  • Level 3: Full validation suite passes
  • All acceptance criteria met

Risks and Mitigations

Risk Likelihood Impact Mitigation
Pi's @anthropic-ai/sdk transitive dep conflicts with existing @anthropic-ai/claude-agent-sdk MEDIUM HIGH Bun workspace dedup should handle it; if not, check if Pi supports tree-shaking or selective provider loading
Pi SDK API changes (pre-1.0) MEDIUM MEDIUM Pin to ^0.65.2; monitor releases. The createAgentSession + subscribe API has been stable since 0.50+
Callback→AsyncGenerator bridge has edge cases (backpressure, error propagation) LOW MEDIUM Use proven pattern with Promise-based queue; test thoroughly with mock events
Large dependency footprint (Pi pulls in all provider SDKs) HIGH LOW Accept for now — tree-shaking or conditional imports could be optimized later. Pi's @mariozechner/pi-ai imports all providers unconditionally
Pi's bash tool runs with detached: true (can orphan processes) LOW MEDIUM Archon already manages this via worktree isolation; Pi sessions are short-lived per sendQuery call

Notes

Model String Convention

The pi:<provider>/<model> convention is chosen to:

  1. Avoid ambiguity with Claude and Codex model strings
  2. Map cleanly to Pi's getModel(provider, modelId) API
  3. Be self-documenting in workflow YAML: model: pi:google/gemini-2.5-pro

Session Resume (Future v2)

Pi supports file-based session persistence. A future version could:

  1. Store session file paths (not just IDs) in the sessions table
  2. Use SessionManager.open(filePath) to resume
  3. This would give full conversation continuity across turns

Pi's Multi-Provider Value

The key strategic value of Pi integration is that it's a "provider multiplier" — one integration gives access to:

  • Google Gemini (1M context, multimodal)
  • Mistral models (fast, good for coding)
  • Groq/Cerebras (ultra-fast inference)
  • xAI Grok
  • OpenRouter (hundreds of models)
  • Any OpenAI-compatible endpoint (Ollama, vLLM for local models)
  • OAuth-based access to Claude Pro, ChatGPT Plus, GitHub Copilot (no API keys)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low priority - Nice to have, consider closing if stalearea: orchestratorMain conversation orchestrationeffort/highCross-cutting changes, multiple domains, requires design decisionsfeatureNew functionality (planned)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions