diff --git a/.opencode/PAI/SKILL.md b/.opencode/PAI/SKILL.md index 0977332d..8789fc7f 100644 --- a/.opencode/PAI/SKILL.md +++ b/.opencode/PAI/SKILL.md @@ -1,4 +1,3 @@ - --- name: PAI description: Personal AI Infrastructure core. The authoritative reference for how PAI works. diff --git a/.opencode/PAI/Tools/GenerateSkillIndex.ts b/.opencode/PAI/Tools/GenerateSkillIndex.ts index 115542be..64ed3afc 100644 --- a/.opencode/PAI/Tools/GenerateSkillIndex.ts +++ b/.opencode/PAI/Tools/GenerateSkillIndex.ts @@ -41,7 +41,9 @@ interface SkillIndex { } // Skills that should always be fully loaded (Tier 1) +// PAI = the core PAI skill (Algorithm, ISC, Capabilities) — MUST always be loaded const ALWAYS_LOADED_SKILLS = [ + 'PAI', 'CORE', 'Development', 'Research', diff --git a/.opencode/plugins/pai-unified.ts b/.opencode/plugins/pai-unified.ts index 47fd4455..4bd68ac2 100644 --- a/.opencode/plugins/pai-unified.ts +++ b/.opencode/plugins/pai-unified.ts @@ -53,7 +53,7 @@ import { fileURLToPath } from "node:url"; // ESM-equivalent of __dirname — .opencode/package.json has "type": "module" // so CommonJS globals (__dirname / __filename) are not defined. Used below -// by loadMinimalBootstrap() to resolve the PAI dir relative to this plugin +// by loadUserSystemContext() to resolve the PAI dir relative to this plugin // file regardless of the current working directory opencode is launched from. const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url)); import type { Hooks, Plugin } from "@opencode-ai/plugin"; @@ -248,49 +248,29 @@ async function readFileSafe(filePath: string): Promise { } /** - * Load minimal bootstrap context — WP2: Minimal Useful + * Load user context — Steering Rules + User Identity * - * Loads: - * 1. MINIMAL_BOOTSTRAP.md (core Algorithm + Steering Rules) - * 2. System AISTEERINGRULES.md (if exists) - * 3. User Identity files (ABOUTME, TELOS, DAIDENTITY) if exist + * The PAI Core Skill (Algorithm, ISC, Capabilities) is loaded via the + * skill system (tier: "always") — NOT here. This function loads only + * the dynamic, user-specific context that can't live in static skill files: * - * Target: ~15KB (not 2KB - must know the user!) + * 1. System AISTEERINGRULES.md (behavioral governance) + * 2. User Identity files (ABOUTME, TELOS, DAIDENTITY) if they exist */ -async function loadMinimalBootstrap(): Promise { +async function loadUserSystemContext(): Promise<{ context: string; filesLoaded: number } | null> { try { - // Resolve PAI directory relative to plugin location, not cwd. - // Plugin is at ~/.opencode/plugins/pai-unified.ts - // PAI is at ~/.opencode/PAI/ - // PLUGIN_DIR is resolved via fileURLToPath(import.meta.url) because - // .opencode/package.json declares "type": "module" — __filename is - // not defined in ESM contexts. const paiDir = path.join(PLUGIN_DIR, "..", "PAI"); - const bootstrapPath = path.join(paiDir, "MINIMAL_BOOTSTRAP.md"); - - // Check if bootstrap exists (async) - try { - await fs.promises.access(bootstrapPath); - } catch { - fileLog("MINIMAL_BOOTSTRAP.md not found, using fallback", "warn"); - return null; - } - const contextParts: string[] = []; - // 1. Core bootstrap (async) - const bootstrapContent = await fs.promises.readFile(bootstrapPath, "utf-8"); - contextParts.push(`--- PAI BOOTSTRAP ---\n${bootstrapContent}`); - - // 2. System Steering Rules (if exists) + // 1. System Steering Rules (if exists) const systemSteeringPath = path.join(paiDir, "AISTEERINGRULES.md"); const systemSteering = await readFileSafe(systemSteeringPath); if (systemSteering) { contextParts.push(`--- System Steering Rules ---\n${systemSteering}`); - fileLog("Loaded System AISTEERINGRULES.md"); + fileLog("Loaded PAI/AISTEERINGRULES.md"); } - // 3. User Identity Files (if exist) — CRITICAL: Must know the user! + // 2. User Identity Files (if exist) — CRITICAL: Must know the user! const userDir = path.join(paiDir, "USER"); const userFiles = [ { file: "ABOUTME.md", label: "User Profile" }, @@ -299,25 +279,31 @@ async function loadMinimalBootstrap(): Promise { { file: "AISTEERINGRULES.md", label: "User Steering Rules" }, ]; - let userContextLoaded = 0; for (const { file, label } of userFiles) { const filePath = path.join(userDir, file); const content = await readFileSafe(filePath); if (content) { contextParts.push(`--- ${label} ---\n${content}`); - fileLog(`Loaded USER/${file}`); - userContextLoaded++; + fileLog(`Loaded PAI/USER/${file}`); } } - // Combine all context + if (contextParts.length === 0) { + fileLog("No user context files found — PAI Core loaded via skill system"); + return null; + } + + // Combine all context — contextParts.length is the accurate total file count const fullContext = contextParts.join("\n\n"); const size = Buffer.byteLength(fullContext, "utf-8"); - fileLog(`Bootstrap loaded: ${size} bytes (${userContextLoaded} user files)`); + fileLog(`User context loaded: ${size} bytes (${contextParts.length} files)`); - return `\nPAI CONTEXT (Lazy Loading Bootstrap)\n\n${fullContext}\n\n---\nSkills load on-demand via OpenCode skill tool. User context auto-loaded if exists.\n`; + return { + context: `\nPAI CONTEXT (User Context)\n\n${fullContext}\n\n---\nPAI Core Skill loaded via skill system. Skills load on-demand via Skill tool.\n`, + filesLoaded: contextParts.length, + }; } catch (error) { - fileLogError("Failed to load minimal bootstrap", error); + fileLogError("Failed to load user system context", error); // Return null to signal failure - caller should handle return null; } @@ -385,8 +371,8 @@ export const PaiUnified: Plugin = async (_ctx) => { /** * CONTEXT INJECTION (SessionStart equivalent) * - * WP2: Injects minimal bootstrap (~7KB) instead of full 233KB context. - * Skills load on-demand via OpenCode native skill tool. + * Injects user system context (steering rules + identity files, ~7KB). + * PAI Core Skill loads via native skill system (tier:always). Other skills load on-demand. * * NOTE: Only loads when PAI_ENABLED=1 is set (via `pai` wrapper). * Plain `opencode` launches without PAI context by design. @@ -402,26 +388,27 @@ export const PaiUnified: Plugin = async (_ctx) => { return; } - fileLog("Injecting minimal bootstrap context (WP2 lazy loading)..."); + fileLog("Injecting user system context (steering rules + identity)..."); // Emit session start emitSessionStart({ model: (input as any).model }).catch(() => {}); - // WP2: Use minimal bootstrap instead of full context loader - const bootstrap = await loadMinimalBootstrap(); + // Load user-specific context (AISTEERINGRULES + USER/* identity files) + const userContextResult = await loadUserSystemContext(); - if (bootstrap && bootstrap.length > 0) { - output.system.push(bootstrap); - fileLog(`Context injected successfully (${bootstrap.length} chars)`); + if (userContextResult && userContextResult.context.length > 0) { + const { context: userContext, filesLoaded } = userContextResult; + output.system.push(userContext); + fileLog(`Context injected successfully (${userContext.length} chars)`); // Emit context loaded emitContextLoaded({ - files_loaded: 1, - total_size: bootstrap.length, + files_loaded: filesLoaded, + total_size: userContext.length, success: true, }).catch(() => {}); } else { - fileLog("Context injection skipped: empty bootstrap", "warn"); + fileLog("Context injection skipped: empty user context", "warn"); // Emit context load failure emitContextLoaded({ files_loaded: 0, diff --git a/.opencode/skills/PAI b/.opencode/skills/PAI new file mode 120000 index 00000000..46bcd35c --- /dev/null +++ b/.opencode/skills/PAI @@ -0,0 +1 @@ +../PAI \ No newline at end of file diff --git a/.opencode/skills/skill-index.json b/.opencode/skills/skill-index.json index b2fb7c4f..5d71364e 100644 --- a/.opencode/skills/skill-index.json +++ b/.opencode/skills/skill-index.json @@ -1,10 +1,10 @@ { - "generated": "2026-03-19T23:43:15.603Z", - "totalSkills": 52, + "generated": "2026-04-13T09:19:28.749Z", + "totalSkills": 53, "categories": 7, - "flatSkills": 11, + "flatSkills": 12, "hierarchicalSkills": 41, - "alwaysLoadedCount": 2, + "alwaysLoadedCount": 3, "deferredCount": 50, "skills": { "agents": { @@ -606,6 +606,16 @@ "tier": "deferred", "isHierarchical": true }, + "pai": { + "name": "PAI", + "path": "PAI/SKILL.md", + "category": null, + "fullDescription": "Personal AI Infrastructure core. The authoritative reference for how PAI works.", + "triggers": [], + "workflows": [], + "tier": "always", + "isHierarchical": false + }, "paiupgrade": { "name": "PAIUpgrade", "path": "Utilities/PAIUpgrade/SKILL.md", diff --git a/AGENTS.md b/AGENTS.md index 40cd6962..b270dcd6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,450 +1,16 @@ -# AGENTS.md — PAI-OpenCode +# AGENTS.md — PAI-OpenCode Repository -> Coding agent constitution for CI/CD compliance and branch protection workflows - -## Identity - -You are an AI coding assistant working on **PAI-OpenCode** — the community port of Daniel Miessler's PAI Infrastructure for OpenCode. - -**Your role:** Implement features, fix bugs, and maintain code quality while strictly adhering to CI/CD and branch protection workflows. - -**Tech Stack:** TypeScript, Bun (never npm), Biome (never ESLint/Prettier), GitHub Actions - -## OpenCode Bash Tool — CRITICAL Behavior - -**OpenCode's Bash tool is STATELESS. Every call spawns a fresh shell process.** - -| Behavior | OpenCode | Claude Code | -|----------|---------|------------| -| Working Directory | ❌ Does NOT persist | ✅ Persists | -| Environment Variables | ❌ Does NOT persist | ✅ Persists | -| `cd` commands | ❌ No effect on next call | ✅ Works | - -**ALWAYS use `workdir` parameter instead of `cd`:** +| Runtime | Linter | Lang | +|---------|--------|------| +| Bun (never npm/yarn/pnpm) | Biome (never ESLint/Prettier) | TypeScript ESM | ```bash -# ❌ WRONG — cd has no effect on next call -Bash({ command: "cd /some/path && ls" }) - -# ✅ CORRECT — use workdir parameter -Bash({ command: "ls", workdir: "/some/path" }) +bun install # Install dependencies +bun test # Run tests +biome check . # Lint + format check +biome check --write . # Auto-fix ``` ---- - -## CI/CD & Branch Protection Rules - -### Absolute Constraints (NEVER violate) - -| Constraint | Rule | Why | -|------------|------|-----| -| **Package Manager** | `bun` only — NEVER `npm install`, `npm ci`, `yarn`, `pnpm` | Per TECHSTACKPREFERENCES.md | -| **Linter/Formatter** | `biome` only — NEVER `eslint`, `prettier` | Per TECHSTACKPREFERENCES.md | -| **Commits to main** | NEVER commit directly to `main` | Branch protection enforced | -| **Force pushes** | NEVER `git push --force` to protected branches | Git history integrity | -| **Secrets** | NEVER hardcode API keys, passwords, tokens | Secret scan will fail | - -### Workflow: How to Make Changes - -```bash -# 1. Always branch from dev (not main) -git checkout -b feature/short-description origin/dev - -# 2. Make your changes -# ... edit files ... - -# 3. Verify locally (Bun only!) -bun install # Never npm install -bunx @biomejs/biome check . # If biome.json exists -bunx tsc --noEmit # If tsconfig.json exists - -# 4. Commit with conventional format -git commit -m "feat(scope): Description - -- Detail 1 -- Detail 2 - -Refs: #issue-number" - -# 5. Push to origin -git push origin feature/short-description - -# 6. Create PR targeting dev (not main!) -# - CI will run automatically -# - CodeRabbit will review automatically -# - Wait for green checks before requesting merge -``` - -### Branch Strategy - -| Branch | Purpose | Protection | Merge Policy | -|--------|---------|------------|--------------| -| `main` | Production releases | 1 approver required | PR from dev only | -| `dev` | Development/integration | CI must pass | PR from feature branches | -| `feature/*` | New features | No direct push | PR to dev | -| `fix/*` | Bug fixes | No direct push | PR to dev | - -### Conventional Commits (Required) - -``` -feat(skills): Add new Cloudflare skill -fix(tools): Resolve path issue in MigrationValidator -docs(readme): Update installation instructions -refactor(agents): Simplify agent registry logic -test(ci): Add secret scan workflow -ci(workflows): Update upstream sync schedule -``` - -**Format:** `type(scope): Description` - -**Types:** feat, fix, docs, refactor, test, ci, chore, perf - -**Scopes:** skills, agents, tools, workflows, docs, tests, core - ---- - -## CodeRabbit Integration - -**CodeRabbit reviews every PR automatically.** - -### When CodeRabbit Comments: - -**[BLOCKING] comments:** -- MUST be resolved before merge -- Reply with fix or justification - -**[SUGGESTION] comments:** -- Can be dismissed if you disagree -- But consider them — CodeRabbit catches real issues - -### How to Trigger Re-review - -``` -@coderabbit review -``` - -Or push new commits — CodeRabbit reviews automatically. - ---- - -## Secret Scanning - -**The CI scans for hardcoded secrets. If found, the build fails.** - -### Patterns that trigger failure: -- `sk-` followed by 20+ characters (OpenAI keys) -- `api_key` or `api-key` with values -- `client_secret`, `password`, `token` with values - -### Safe patterns: -- Environment variables: `process.env.API_KEY` -- Config files with placeholders: `"apiKey": "YOUR_KEY_HERE"` -- Documentation examples marked clearly - -**If you need to commit a secret pattern legitimately:** -Add comment: `# pragma: allowlist secret` on that line. - ---- - -## PR Checklist (Complete before requesting review) - -- [ ] Branched from `dev`, targeting `dev` -- [ ] Uses `bun` commands only (no npm) -- [ ] Follows conventional commit format -- [ ] No hardcoded secrets -- [ ] CodeRabbit review complete (no blocking issues) -- [ ] CI passes (green checkmark) -- [ ] Documentation updated if needed - ---- - -## File Structure Awareness - -### Critical Paths - -| Path | Purpose | Modification Rules | -|------|---------|-------------------| -| `.opencode/skills/**` | PAI Skills | Follow SKILL.md format, USE WHEN triggers | -| `.opencode/agents/**` | Agent definitions | Keep frontmatter valid, first-person voice | -| `.opencode/plugins/**` | Lifecycle plugins | No blocking operations in init hooks | -| `Tools/**` | CLI tools | Bun-only, pure execution (no LLM inside) | -| `docs/**` | Documentation | Obsidian format, ASCII + Mermaid diagrams | -| `.github/workflows/**` | CI/CD | NEVER use npm, ALWAYS use bun | - -### Never Modify (Unless Explicitly Asked) - -- `node_modules/**` — Auto-generated -- `bun.lock` — Auto-generated by bun install -- `.git/**` — Git internals -- `dist/**`, `coverage/**` — Build artifacts - ---- - -## Upstream Sync Awareness - -**PAI-OpenCode tracks two upstream repositories:** - -1. **danielmiessler/Personal_AI_Infrastructure** — Daily sync at 08:00 UTC -2. **anomalyco/opencode** — Daily sync at 08:30 UTC - -If an upstream sync Issue appears: -- Review the changes -- Assess impact on PAI-OpenCode -- Update code if necessary - -**Do NOT:** -- Modify upstream sync workflows unless fixing bugs -- Ignore upstream sync issues (they indicate drift) - ---- - -## OpenCode Action Commands - -When triggered via `/opencode` or `/oc` in a PR comment: - -1. Read the specific request -2. Check out the PR branch (not main/dev) -3. Make requested changes -4. Commit back to the PR branch -5. **NEVER merge — let humans decide** - -### Example Commands - -``` -/opencode Fix the Biome linting errors in this PR -/oc Add error handling to the migration tool -/opencode Review this PR for PAI v3.0 compliance -``` - ---- - -## Emergency Protocols - -### If CI fails on your PR: - -1. Read the CI logs (GitHub Actions tab) -2. Identify failure reason: - - **Secret scan:** Remove hardcoded secret or add `# pragma: allowlist secret` - - **Biome check:** Run `bunx @biomejs/biome check --write .` to auto-fix - - **Typecheck:** Fix TypeScript errors - - **Missing bun:** Ensure `bun install` not `npm install` -3. Push fixes to the same branch -4. CI re-runs automatically - -### If CodeRabbit flags something you disagree with: - -1. Reply to the comment explaining your reasoning -2. If it's a [SUGGESTION], you can dismiss after replying -3. If it's [BLOCKING], you MUST address it or escalate to Steffen - -### If you accidentally committed to main: - -**STOP.** Do not force push. - -1. Notify Steffen immediately -2. Main is protected — the push might have failed anyway -3. If it succeeded, a revert PR will be needed - ---- - -## OpenCode Session API - -After context compaction, subagent results are **NOT lost**. They are stored in OpenCode's SQLite database and accessible via custom tools. Use these tools to recover session context after compaction or to resume subagent work. - -### Custom Tools - -**`session_registry`** — List all subagent sessions spawned in this session. - -- **When to use:** After context compaction, or when you need to check what subagents were spawned -- **Returns:** Markdown table with session IDs, agent types, descriptions, and spawn times -- **Example output:** - ```text - ## Subagent Registry (2 sessions) - - | # | Agent Type | Session ID | Description | Spawned At | - |---|-----------|-----------|-------------|------------| - | 1 | Engineer | ses_abc123 | Refactor auth middleware | 2026-03-10T10:30:00Z | - | 2 | Research | ses_def456 | Investigate OpenCode API | 2026-03-10T10:35:00Z | - ``` - -**`session_results`** — Get registry metadata for a specific subagent session. - -- **When to use:** When you need details about a specific subagent's work -- **Args:** `{ session_id: string }` -- **Returns:** Agent type, full description, model tier, status, and resume instructions -- **Note:** The full conversation history is in OpenCode's database. Use Task tool with `session_id` to retrieve it. - -### Post-Compaction Recovery Pattern - -When the Algorithm says "subagent results are lost after compaction": - -1. **Call `session_registry`** to see what subagents exist - ```json - session_registry: {} - ``` - -2. **Call `session_results`** for any sessions you need context on - ```json - session_results: { "session_id": "ses_abc123" } - ``` - -3. **Resume the session** using Task tool if you need full conversation: - ```javascript - Task({ session_id: "ses_abc123", prompt: "Continue where you left off and summarize what you did" }) - ``` - -### Key Facts - -- Subagent data survives compaction — it's stored in OpenCode's SQLite with indexed `parent_id` -- The registry file lives in `.opencode/MEMORY/STATE/subagent-registry-{parentSessionId}.json` -- Registry is human-readable JSON for debugging -- Session data persists across restarts, not just compaction - ---- - -## Code Navigation (LSP Integration) - -OpenCode has 35+ Language Server Protocol (LSP) servers built-in. When enabled, they provide **type-aware code navigation** that goes beyond simple text matching. - -### Available LSP Tools - -| Tool | What It Does | When to Use | -|------|-------------|-------------| -| `goToDefinition` | Jump to symbol definition (type-aware, follows imports) | Find where a function/type is defined | -| `findReferences` | All usages of a function (semantic, not text-match) | Understand impact before refactoring | -| `hover` | Show type info and docs for a symbol | Quickly inspect unfamiliar APIs | -| `callHierarchy` | Incoming/outgoing call chains | Trace execution paths | - -### LSP vs. Grep — When to Use Which - -| Use Case | LSP | Grep | -|----------|-----|------| -| Find all callers of `myFunction()` | ✅ `findReferences` — semantic, exact | ⚠️ Misses renamed imports, aliases | -| Jump to type definition across files | ✅ `goToDefinition` — follows imports | ❌ Can't follow re-exports | -| Check TypeScript type of a variable | ✅ `hover` — live type info | ❌ Not possible | -| Find all files containing "TODO" | ❌ LSP can't do text search | ✅ Grep is correct tool | -| Find all uses of a string literal | ❌ LSP is symbol-only | ✅ Grep is correct tool | -| Quick pattern match in one file | ❌ Overhead not worth it | ✅ Grep is faster | - -**Rule of thumb:** Use LSP for **symbols** (functions, types, variables). Use Grep for **text** (strings, comments, patterns). - -### Activation - -LSP tools are **experimental** and must be explicitly enabled: - -```bash -# Enable LSP tools for the current session -export OPENCODE_EXPERIMENTAL_LSP_TOOL=true -opencode -``` - -Or add to your shell profile for permanent activation: - -```bash -echo 'export OPENCODE_EXPERIMENTAL_LSP_TOOL=true' >> ~/.zshrc -``` - -> [!NOTE] -> LSP tools are only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` is set. Without this flag, the tools are not registered and will not appear in the tool list. - ---- - -## Safe Experiments (Session Fork) - -> [!NOTE] -> Plan Mode is **not available** in OpenCode. Session Fork is the native equivalent — a checkpoint system for safe experimentation. - -OpenCode's Session Fork creates an **exact copy** of the current session up to a specific message. The original session is untouched. If the experiment fails, discard the fork and return to the original. - -### When to Fork - -| Situation | Action | -|-----------|--------| -| About to do a risky refactoring | Fork first, then refactor in the fork | -| Exploring multiple solution approaches | Fork once per approach, compare results | -| About to run destructive operations (delete, overwrite) | Fork → verify in fork → apply to original | -| Algorithm needs to "try something" without commitment | Fork, try, decide | -| Pre-BUILD checkpoint in the PAI Algorithm | Fork at end of PLAN phase | - -### API Reference - -```http -POST /session/{sessionID}/fork -Content-Type: application/json - -{ - "messageID": "msg_..." -} -``` - -**Response:** A new session ID pointing to an exact copy of the session at the specified message. - -**How to get the current messageID:** Available via the OpenCode Session API (same endpoint used by `session_registry`). - -### Fork Workflow - -```text -PLAN phase complete → identify last messageID - → POST /session/{id}/fork - → get forked_session_id - → work in forked session (BUILD / EXECUTE) - → if success: apply changes to original - → if failure: discard fork, original is safe -``` - -### Key Properties - -- **Atomic:** Fork creates a complete snapshot — no partial state -- **Non-destructive:** Original session is never modified by fork operations -- **Persistent:** Forked sessions survive restarts (stored in OpenCode SQLite) -- **Discardable:** Failed experiments leave no traces in the original session - ---- - -## Quick Reference - -### Commands - -```bash -# Install dependencies (BUN ONLY!) -bun install - -# Run Biome check -bunx @biomejs/biome check . - -# Auto-fix Biome issues -bunx @biomejs/biome check --write . - -# TypeScript check -bunx tsc --noEmit - -# Run tests -bun test -``` - -### Git Workflow - -```bash -# Start feature -git checkout -b feature/description origin/dev - -# Work... - -# Commit -git commit -m "feat(scope): Description" - -# Push -git push origin feature/description - -# Create PR via GitHub UI (target: dev) -``` - -### Links - -- **CI Status:** https://github.com/Steffen025/pai-opencode/actions -- **CodeRabbit:** https://app.coderabbit.ai -- **Setup Guide:** docs/CI-CD-SETUP.md - ---- +**Commits:** `feat(scope):`, `fix(scope):`, `docs:`, `chore:` — branch from `dev`, PR to `dev`, never commit to `main`. -**Remember:** Professionalism = following the workflow, not hacking around it. When in doubt, ask in the PR comments. +**PAI System:** Loaded via skill system (`PAI/SKILL.md`, tier: always). Not in this file. diff --git a/INSTALL.md b/INSTALL.md index 38f8e2af..d1fe35e0 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -49,14 +49,9 @@ opencode The installer will: 1. ✅ Check prerequisites (git, bun 1.3.9+) 2. ✅ **Install vanilla OpenCode** from opencode.ai -3. ✅ Ask you to choose a preset: - - **Anthropic Max** (recommended) — Best quality, full PAI experience - - **ZEN PAID** — Budget-friendly, paid tier models - - **OpenRouter** — Provider diversity, 100+ models - - **OpenAI** — GPT-4 models via OpenAI directly -4. ✅ Configure research agents (optional) -5. ✅ Set up your identity (name, AI assistant name, timezone) -6. ✅ Generate all configuration files +3. ✅ Uses OpenCode Zen free models by default — no API key required. To connect premium providers after install, run `/connect` inside OpenCode. +4. ✅ Set up your identity (name, AI assistant name, timezone) +5. ✅ Generate all configuration files **Takes ~2-3 minutes.** @@ -232,7 +227,7 @@ If you already have a PAI installation on Claude Code, see our [Migration Guide] After installation, verify everything works: 1. **Check Skills Loading** - - On first message, CORE skill should auto-load + - On first message, PAI skill auto-loads (tier: always) - Ask: "What skills do I have?" 2. **Test an Agent** @@ -322,27 +317,80 @@ Edit `.opencode/settings.json`: ## Provider Configuration -### The Four Presets +### Default: Zen Free (No Setup Required) -PAI-OpenCode uses a **preset system** for simplicity: +PAI-OpenCode ships with **OpenCode Zen free** as the default provider. Big Pickle — Zen's free flagship model — works out of the box with no API key or account required. Just install and start. -| Preset | Best For | Models | Cost | -|--------|----------|--------|------| -| **Anthropic Max** | Best quality | Claude Opus 4.6, Sonnet 4.5 | ~$75/1M tokens | -| **ZEN PAID** | Budget-friendly | GLM 4.7, Kimi K2.5, Gemini Flash | ~$1-15/1M tokens | -| **OpenRouter** | Provider diversity | 100+ models via OpenRouter | Varies by model | -| **OpenAI** | GPT-4 models | GPT-4o, GPT-4.1 | ~$10-30/1M tokens | +### Connecting Premium Providers -### Switching Presets +Provider setup is a **post-install step** — PAI-OpenCode ships with Zen free so you can start immediately, then connect additional providers whenever you're ready. + +Connecting a provider is a two-step process: + +#### Step 1 — Establish credentials via `/connect` + +Run `/connect` inside a running OpenCode session and follow the prompts: + +```text +/connect +``` + +OpenCode stores credentials securely in `~/.opencode/`. Supported providers: + +| Provider | Best For | Cost | +|----------|----------|------| +| **Zen paid** | Budget-friendly paid models (GLM 4.7, Kimi K2.5, Gemini Flash) | ~$1-15/1M tokens | +| **Anthropic (API key)** | Best quality, Claude Opus 4.6 / Sonnet 4.5 | Pay-per-token | +| **OpenRouter** | Provider diversity, 100+ models | Varies by model | +| **OpenAI** | GPT-4o, GPT-4.1 | ~$10-30/1M tokens | +| **Ollama** | Local, private, offline | Free (your hardware) | + +#### Step 2 — Update agent model assignments in `opencode.json` + +`/connect` stores your credentials, but the agent model strings in `opencode.json` still point to the Zen free defaults. Switch them to your newly connected provider: ```bash -# Re-run the installer in headless mode to force a specific preset +# Switch all agents to Anthropic models +bun run .opencode/tools/switch-provider.ts anthropic + +# Switch to paid Zen models +bun run .opencode/tools/switch-provider.ts zen-paid + +# Switch to OpenAI models +bun run .opencode/tools/switch-provider.ts openai + +# List all available profiles +bun run .opencode/tools/switch-provider.ts --list +``` + +See [ADVANCED-SETUP.md](docs/ADVANCED-SETUP.md) for manual `opencode.json` editing and custom profiles. + +--- + +### ⚠️ Anthropic Claude Max — Important Note + +Anthropic's Claude Max subscription (the one used at claude.ai) is designed for direct human use. Routing it through a third-party tool like OpenCode via OAuth is a **grey area in Anthropic's Terms of Service** and carries a risk of account suspension. + +**How the connection works technically:** +OpenCode connects to Claude Max via a community plugin — [`cemalturkcan/opencode-anthropic-login-via-cli`](https://github.com/cemalturkcan/opencode-anthropic-login-via-cli) — that authenticates your Claude Max account and proxies requests through it. + +**Why PAI-OpenCode does not ship this plugin:** +We intentionally exclude it from the distribution. Installing and using it is your own decision and responsibility — we do not want to expose users to ToS risk without their explicit informed consent. + +**Alternatives that are clearly within Anthropic's ToS:** +- **Anthropic API key** — Pay-per-token access via [console.anthropic.com](https://console.anthropic.com/). Add `ANTHROPIC_API_KEY=sk-ant-...` to `~/.opencode/.env` and run `/connect` → Anthropic. +- **OpenCode Zen paid** — High-quality models at competitive rates, no ToS concerns. Includes access to models equivalent in capability to Sonnet/Opus tiers. + +### Changing Providers After Install + +```bash +# Re-run the installer in headless mode to reconfigure a specific preset bash PAI-Install/install.sh --headless --preset openai --name "Your Name" --ai-name "Jeremy" ``` ### Advanced Provider Setup -For custom provider configuration beyond the 4 presets, see [ADVANCED-SETUP.md](docs/ADVANCED-SETUP.md). +For custom provider configuration, see [ADVANCED-SETUP.md](docs/ADVANCED-SETUP.md). ### Multi-Provider Research (Optional) @@ -365,20 +413,11 @@ Check which keys you have: bun run .opencode/tools/switch-provider.ts --researchers ``` -### Authentication Options - -| Method | How | Best For | -|--------|-----|----------| -| **Subscription login** | Run `/login` in OpenCode | Claude Pro/Max, ChatGPT Plus users | -| **API key** | Add to `~/.opencode/.env` | Pay-per-use, multiple providers | - ### API Keys for Multi-Provider Research (Optional) -The 4-preset system covers most use cases. For multi-provider research or custom providers, see [ADVANCED-SETUP.md](docs/ADVANCED-SETUP.md). - | Provider | Where to Get Key | For | |----------|-----------------|-----| -| Anthropic | https://console.anthropic.com/ | Claude models (via Anthropic Max) | +| Anthropic | https://console.anthropic.com/ | Claude models | | Google | https://aistudio.google.com/apikey | GeminiResearcher | | xAI | https://console.x.ai/ | GrokResearcher | | Perplexity | https://perplexity.ai/settings/api | PerplexityResearcher | diff --git a/PAI-Install/cli/install-wizard.ts b/PAI-Install/cli/install-wizard.ts index 4e69b426..95dc87a8 100644 --- a/PAI-Install/cli/install-wizard.ts +++ b/PAI-Install/cli/install-wizard.ts @@ -21,8 +21,7 @@ import { stepProviderConfig, stepVoice, } from "../engine/steps-fresh.ts"; -import { PROVIDER_LABELS, PROVIDER_MODELS } from "../engine/provider-models.ts"; -import type { ProviderName } from "../engine/provider-models.ts"; + import { stepCreateBackup, stepDetectMigration, @@ -48,15 +47,6 @@ const COLOR = { const PROGRESS_BAR_WIDTH = 24; -const API_KEY_REQUIRED = new Set(["openai", "anthropic", "openrouter"]); - -const PROVIDER_ENV_VARS: Record = { - anthropic: ["ANTHROPIC_API_KEY", "API_KEY"], - openai: ["OPENAI_API_KEY", "API_KEY"], - openrouter: ["OPENROUTER_API_KEY", "API_KEY"], - zen: ["ZEN_API_KEY", "API_KEY"], -}; - type Mode = "fresh" | "migrate" | "update"; const { values } = parseArgs({ @@ -362,39 +352,14 @@ async function runFreshWizard(): Promise { printInfo("OpenCode install is managed by the vanilla opencode.ai installer."); printInfo("If opencode is not on your PATH, run: curl -fsSL https://opencode.ai/install | bash"); - const providerChoices = (Object.keys(PROVIDER_MODELS) as ProviderName[]).map((provider) => ({ - label: PROVIDER_LABELS[provider].label, - value: provider, - description: PROVIDER_LABELS[provider].description, - })); - - const provider = (await askChoice("Choose AI provider:", providerChoices, 1)) as ProviderName; - const envCandidates = PROVIDER_ENV_VARS[provider]; - const envKey = envCandidates.map((key) => process.env[key]).find(Boolean)?.trim() || ""; - let apiKey = ""; - - if (API_KEY_REQUIRED.has(provider)) { - const hasEnvKey = envKey.length > 0; - if (hasEnvKey) { - printInfo(`Detected ${envCandidates.join("/")} in environment.`); - } - - apiKey = await askHidden( - `Enter ${provider} API key${hasEnvKey ? " (press Enter to use env key)" : ""}`, - "", - ); - } - - const chosenApiKey = apiKey.trim() || envKey; - if (API_KEY_REQUIRED.has(provider) && !chosenApiKey) { - printError(`Provider ${provider} requires API key.`); - printInfo(`Set one of: ${envCandidates.join(", ")} or enter it in wizard.`); - process.exit(1); - } + // Provider defaults to Zen free — no API key needed for out-of-box experience. + // Users configure premium providers AFTER install via `/connect` in OpenCode. + printInfo("Using OpenCode Zen (free models) — no API key required."); + printInfo("To connect premium providers later, run /connect inside OpenCode.\n"); await stepProviderConfig( state, - { provider, apiKey: chosenApiKey }, + { provider: "zen", apiKey: "" }, progress, ); @@ -455,7 +420,16 @@ async function runFreshWizard(): Promise { print("Next steps:"); print(" 1. Open a new terminal or source your shell config file"); print(" 2. Run: pai"); - print(" 3. Optional: switch provider profiles via switch-provider tool"); + print(""); + print("To use a different model provider (optional):"); + print(" Step 1 — Connect the provider inside a running OpenCode session:"); + print(" /connect"); + print(" Step 2 — Update agent model assignments in opencode.json:"); + print(" bun run .opencode/tools/switch-provider.ts "); + print(" bun run .opencode/tools/switch-provider.ts --list (see available profiles)"); + print(""); + printInfo("Default: all agents use opencode/big-pickle (Zen free, no API key needed)."); + printInfo("Free model list: https://opencode.ai/docs/zen/"); } async function runMigrationWizard(): Promise { diff --git a/PAI-Install/engine/provider-models.ts b/PAI-Install/engine/provider-models.ts index 69362643..c83be17b 100644 --- a/PAI-Install/engine/provider-models.ts +++ b/PAI-Install/engine/provider-models.ts @@ -26,10 +26,21 @@ export const PROVIDER_MODELS: Record = { advanced: "anthropic/claude-opus-4-6", }, zen: { - // OpenCode Zen — cost-optimised tiers (IDs verified against opencode.ai/docs/zen/) - quick: "zen/minimax-m2.5-free", // FREE - standard: "zen/gpt-5.1-codex-mini", // $0.25/M in+out - advanced: "zen/claude-3-5-haiku", // $0.80/M — catalog ID for Claude Haiku 3.5 + // OpenCode Zen — big-pickle for ALL tiers at install time. + // + // Why big-pickle everywhere? Free model availability on Zen changes + // periodically (e.g. qwen3.6-plus-free and gpt-5-nano were removed in + // a past rotation). big-pickle is the permanent Zen flagship — always + // available, always free. Shipping a single model removes the risk of + // users hitting "model not found" errors out of the box. + // + // After install, users can optimise per-agent routing via: + // bun run .opencode/tools/switch-provider.ts zen-free + // which applies the zen-free profile with current model assignments. + // Check https://opencode.ai/docs/zen/ for the up-to-date free model list. + quick: "opencode/big-pickle", + standard: "opencode/big-pickle", + advanced: "opencode/big-pickle", }, openrouter: { quick: "openrouter/google/gemini-flash-1.5", diff --git a/PAI-Install/engine/steps-fresh.ts b/PAI-Install/engine/steps-fresh.ts index 9ef8ec77..7bdd3b0f 100644 --- a/PAI-Install/engine/steps-fresh.ts +++ b/PAI-Install/engine/steps-fresh.ts @@ -255,20 +255,30 @@ export async function stepInstallPAI( onProgress(94, "Generated settings.json..."); // Create .env file with API keys (restricted permissions) - const providerEnvVar = `${(state.collected.provider || "zen").toUpperCase()}_API_KEY`; const voiceEnvVar = state.collected.voiceProvider === "google" ? "GOOGLE_API_KEY" : state.collected.voiceProvider === "elevenlabs" ? "ELEVENLABS_API_KEY" : state.collected.voiceProvider === "macos" ? "" : ""; let envContent = `# PAI-OpenCode Environment Variables # Generated by installer - DO NOT COMMIT THIS FILE -${providerEnvVar}=${state.collected.apiKey || ""} +# +# Model provider authentication (Anthropic, Zen paid, OpenRouter, etc.) is +# managed by OpenCode via /connect — NOT via this file. OpenCode stores +# provider credentials in its own config, separate from this repository. +# +# This file is for service keys that PAI tools read directly (e.g. voice). # Optional: Enable experimental LSP code navigation tools # Uncomment to activate goToDefinition, findReferences, hover, callHierarchy # OPENCODE_EXPERIMENTAL_LSP_TOOL=true `; - + + // NOTE: Model provider API keys are NOT written to .env. + // Provider authentication is managed by OpenCode via /connect (stored in + // OpenCode's own credential store, outside this repository). + // Only voice service keys are written here — those are read directly by + // PAI tools, not by OpenCode's provider routing. + if (voiceEnvVar && state.collected.voiceApiKey) { envContent += `${voiceEnvVar}=${state.collected.voiceApiKey}\n`; if (state.collected.voiceProvider === "google") { @@ -515,43 +525,38 @@ export async function runFreshInstall( } await emit({ event: "step_complete", step: "prerequisites" }); - // Step 3: Provider Configuration (API Keys) + // Step 3: Provider — default to Zen free (no API key needed) + // + // Provider configuration is intentionally skipped during install. + // OpenCode ships pre-connected to OpenCode Zen with free models. + // Users configure premium providers AFTER install via `/connect` + // inside a running OpenCode session. await emit({ event: "step_start", step: "api-keys" }); - // Collect provider config via interactive callbacks - const providerChoices = Object.entries(PROVIDER_LABELS).map(([value, { label, description }]) => ({ - label, - value, - description, - })); - const provider = (await requestChoice("provider", "Choose your AI provider:", providerChoices)) as ProviderName || "zen"; - - const providerEnvMap: Record = { - anthropic: ["ANTHROPIC_API_KEY", "API_KEY"], - openai: ["OPENAI_API_KEY", "API_KEY"], - openrouter: ["OPENROUTER_API_KEY", "API_KEY"], - zen: ["ZEN_API_KEY", "API_KEY"], - }; - const requiredProviders = new Set(["openai", "anthropic", "openrouter"]); - - let apiKey = ""; - if (requiredProviders.has(provider)) { - apiKey = await requestInput("api-key", `Enter your ${provider} API key:`, "key", "sk-..."); - } - - const envCandidates = providerEnvMap[provider] || ["API_KEY"]; - const envKey = envCandidates.map((name) => process.env[name]).find(Boolean) || ""; - const selectedApiKey = apiKey.trim() || envKey; - - if (requiredProviders.has(provider) && !selectedApiKey) { - throw new Error(`Provider ${provider} requires an API key. Provide --api-key or set ${envCandidates.join("/")}.`); - } - await stepProviderConfig(state, { - provider, - apiKey: selectedApiKey, + provider: "zen", + apiKey: "", }, (percent, message) => { emit({ event: "progress", step: "api-keys", percent, detail: message }); }); + await emit({ + event: "message", + content: [ + "Using OpenCode Zen (free models) — no API key required.", + "", + "All agents default to opencode/big-pickle, which is always available on Zen free.", + "", + "To switch to a premium provider after install (two steps required):", + " Step 1 — Connect the provider inside a running OpenCode session:", + " /connect", + " (This stores credentials in OpenCode's own config — not in .env)", + " Step 2 — Update agent model assignments in opencode.json:", + " bun run .opencode/tools/switch-provider.ts ", + " bun run .opencode/tools/switch-provider.ts --list", + "", + "The default remains opencode/big-pickle (Zen free) until Step 2 is performed.", + "Free model list: https://opencode.ai/docs/zen/", + ].join("\n"), + }); await emit({ event: "step_complete", step: "api-keys" }); // Step 4: Identity diff --git a/README.md b/README.md index ef17b020..45dbcfb5 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) > [!note] -> **v3.0 Release** — Plugin event bus, security hardening (prompt injection protection), terminal installer wizard + headless mode, DB health tooling, hierarchical skills structure, and 52 skills. See [CHANGELOG.md](CHANGELOG.md) and [UPGRADE.md](UPGRADE.md). +> **v3.0 Release** — Zero-bootstrap context loading, Zen free out-of-box (no API key required), PAI Core Skill always-loaded via OpenCode's native skill system, plugin event bus, security hardening (prompt injection protection), terminal installer wizard + headless mode, DB health tooling, hierarchical skills structure, and 52 skills. See [CHANGELOG.md](CHANGELOG.md) and [UPGRADE.md](UPGRADE.md). > **🎯 Scope Note:** PAI-OpenCode is a **community port** of PAI to OpenCode. For the future vision (Voice-to-Voice, Ambient AI, OMI integration), see **[Open Arc](https://github.com/jeremaiah-ai/openark)**. @@ -100,7 +100,7 @@ PAI-OpenCode is the complete port of **Daniel Miessler's Personal AI Infrastruct bash PAI-Install/install.sh # Headless example (optional) -bash PAI-Install/install.sh --headless --preset zen --name "Your Name" --ai-name "Jeremy" +bash PAI-Install/install.sh --headless --name "Your Name" --ai-name "Jeremy" ``` ### Manual Setup @@ -126,17 +126,19 @@ opencode The installer wizard will: - **Install vanilla OpenCode** from opencode.ai -- **Choose your preset** — zen (recommended), anthropic, openrouter, or openai +- **Uses Zen free models by default** — no API key required to start - Set your name and timezone - Name your AI assistant +To add premium providers (Anthropic, OpenAI, OpenRouter), run `/connect` inside a running OpenCode session after install. + **Takes ~2-3 minutes** and creates all necessary configuration files. --- ## Switch Providers Anytime -The installer configures one preset, but you can switch to **any provider** after installation: +After installation, you can switch to **any provider** at any time: ```bash # See all available provider profiles @@ -186,7 +188,7 @@ This **10-15 minute** interactive session will configure your complete TELOS fra ### 🎯 Skills System (52 Skills) Modular, reusable capabilities invoked by name: -- **CORE** — Identity, preferences, auto-loaded at session start (Algorithm v1.8.0) +- **PAI** — Core Algorithm, ISC, Capabilities — always-loaded via skill system (`tier:always`) - **Art** — Excalidraw-style visual diagrams - **Browser** — Code-first browser automation - **Security** — Pentesting, secret scanning @@ -231,7 +233,7 @@ Persistent context across sessions: ### 🔧 Plugin System (20 Handlers) TypeScript lifecycle plugins with comprehensive coverage: -- **Context injection** at session start +- **User identity context** loaded at session start (ABOUTME, TELOS, DAIDENTITY, Steering Rules) - **Security validation** before commands - **Voice notifications** (ElevenLabs + Google TTS + macOS say) - **Implicit sentiment** detection from user messages @@ -245,6 +247,9 @@ TypeScript lifecycle plugins with comprehensive coverage: - **Version check** — Algorithm version compatibility - **Integrity check** — Session-end validation +> [!NOTE] +> PAI Core (Algorithm, ISC, Capabilities) is loaded automatically by OpenCode's native skill system via `tier:always`. The plugin handles user identity context only. + ### 🌐 75+ AI Providers Use any AI provider: - Anthropic (Claude) @@ -255,15 +260,37 @@ Use any AI provider: --- -## Provider Preset System +## Provider Configuration + +PAI-OpenCode ships with **Zen free as the default** — OpenCode Zen is pre-connected with free models, so you can start immediately with no API key. + +| Provider | How to Enable | Cost | +|----------|---------------|------| +| **Zen free** (Default) | Works out of the box — no setup required | **FREE** | +| **Zen paid** | Run `/connect` in OpenCode → select Zen AI Gateway | ~$1-75/1M tokens depending on tier | +| **Anthropic API** | Add `ANTHROPIC_API_KEY` to `~/.opencode/.env`, then `/connect` | Pay-per-token | +| **OpenRouter** | Run `/connect` in OpenCode → select OpenRouter | Varies by model | +| **Ollama (local)** | Run `/connect` in OpenCode → select Ollama | **FREE** (your hardware) | -PAI-OpenCode offers **three presets** — each gives you access to 75+ providers with different routing strategies: +> **Anthropic Claude Max:** Using a Claude Max subscription (claude.ai) with OpenCode requires a community plugin not shipped with PAI-OpenCode due to Anthropic ToS concerns. See [INSTALL.md](INSTALL.md#️-anthropic-claude-max--important-note) for details. -| Preset | Best For | Providers | Cost | -|--------|----------|-----------|------| -| **`zen-paid`** (Recommended) | Production use, privacy-conscious | 75+ providers via Zen AI Gateway | ~$1-75/1M tokens depending on tier | -| **`openrouter`** | Provider diversity, experimental models | OpenRouter routing to 100+ models | Varies by model | -| **`local-ollama`** | Full privacy, offline operation | Local Ollama instance | **FREE** (your hardware) | +### Connecting Providers After Install + +Provider setup is a **two-step process** — no reinstall needed: + +1. **Run `/connect`** inside OpenCode to store credentials: + +```text +/connect +``` + +2. **Update agent model assignments** in `opencode.json` to point at your new provider: + +```bash +bun run .opencode/tools/switch-provider.ts anthropic # or zen-paid, openai, etc. +``` + +See [INSTALL.md](INSTALL.md#connecting-premium-providers) for the full walkthrough. ### Why This Design? @@ -278,9 +305,9 @@ This is what PAI on OpenCode can do that PAI on Claude Code cannot — Claude Co **Easy to customize** later via [ADVANCED-SETUP.md](docs/ADVANCED-SETUP.md) -### Switching Presets +### Switching Provider Profiles -Use the provider switcher tool after install: +Use the provider switcher tool anytime after install: ```bash bun run .opencode/tools/switch-provider.ts --list @@ -367,6 +394,7 @@ PAI-OpenCode's design is documented through **Architecture Decision Records (ADR | [ADR-006](docs/architecture/adr/ADR-006-security-validation-preservation.md) | Security Patterns Preserved | Critical security validation unchanged | | [ADR-007](docs/architecture/adr/ADR-007-memory-system-structure-preserved.md) | Memory Structure Preserved | File-based MEMORY/ system unchanged | | [ADR-008](docs/architecture/adr/ADR-008-opencode-bash-workdir-parameter.md) | Bash workdir Parameter | Critical platform difference for multi-repo workflows | +| [ADR-020](docs/architecture/adr/ADR-020-native-opencode-context-loading.md) | Zero Bootstrap / tier:always | PAI Core loads via native skill system, no custom bootstrap | **Key Principles:** - **Preserve PAI's design** where possible @@ -390,11 +418,6 @@ PAI-OpenCode's design is documented through **Architecture Decision Records (ADR | [docs/MIGRATION.md](docs/MIGRATION.md) | Migration from Claude Code PAI | | [CONTRIBUTING.md](CONTRIBUTING.md) | Contribution guidelines | -**For Contributors:** -| Document | Description | -|----------|-------------| -| [PAI-to-OpenCode Mapping](.opencode/PAISYSTEM/PAI-TO-OPENCODE-MAPPING.md) | How to correctly import PAI components | - **Upstream Resources:** - [Daniel Miessler's PAI](https://github.com/danielmiessler/Personal_AI_Infrastructure) — Original PAI documentation - [OpenCode Documentation](https://docs.opencode.ai) — OpenCode official docs @@ -437,7 +460,7 @@ opencode For scripted installs, use `--headless`: ```bash -bash PAI-Install/install.sh --headless --preset zen --name "Your Name" --ai-name "Jeremy" +bash PAI-Install/install.sh --headless --name "Your Name" --ai-name "Jeremy" ``` **Welcome to Personal AI Infrastructure, your way.** diff --git a/docs/ADVANCED-SETUP.md b/docs/ADVANCED-SETUP.md index babd8515..1393470b 100644 --- a/docs/ADVANCED-SETUP.md +++ b/docs/ADVANCED-SETUP.md @@ -3,7 +3,11 @@ ## Overview -The installer configures a preset for your initial provider routing (`zen`, `anthropic`, `openrouter`, `openai`). See [INSTALL.md](../INSTALL.md) for installation details. + +The installer configures Zen free as the default provider. Connecting additional providers and updating agent model assignments are **post-install steps** — see [INSTALL.md — Connecting Premium Providers](../INSTALL.md#connecting-premium-providers) for the two-step process (`/connect` → `switch-provider`). + +> [!important] +> **Anthropic Claude Max:** Routing a Claude Max subscription through OpenCode via OAuth requires a community plugin that PAI-OpenCode intentionally does not ship ([details in INSTALL.md](../INSTALL.md#️-anthropic-claude-max--important-note)). The ToS-safe alternative is an Anthropic API key or OpenCode Zen paid. ## Custom Model Configuration diff --git a/docs/PAI-ADAPTATIONS.md b/docs/PAI-ADAPTATIONS.md index 66b32b95..0b8fa4f2 100644 --- a/docs/PAI-ADAPTATIONS.md +++ b/docs/PAI-ADAPTATIONS.md @@ -20,6 +20,7 @@ This document explains **what we changed** and **why**. | v1.1.0 | PAI 2.5 | v0.2.25 | 13 handlers, voice/sentiment | | v1.3.0 | PAI 2.5 | v0.2.25 | 16 agents, agent-based routing, 3 presets | | **v2.0.0** | **PAI 3.0** | **v1.8.0** | **8 effort levels, Verify Completion Gate, Wisdom Frames, 20 handlers, 39 skills** | +| **v3.0.0** | **PAI 3.0** | **v1.8.0** | **Vanilla OpenCode, zero bootstrap, Zen free default, PAI Core tier:always** | --- @@ -135,11 +136,13 @@ export function fileLog(message: string, level = "info") { ### Skills System -**100% unchanged:** -- All 29 skills work identically +**Skill content unchanged** — SKILL.md format, workflow files, and trigger logic are identical to v2.x. What changed is the *discovery path*: PAI now uses the native OpenCode scanner via the symlink `skills/PAI → ../PAI`, so OpenCode discovers `PAI/SKILL.md` automatically without any plugin intervention. + +- All skills work identically (invoke, trigger, and output behaviour unchanged) - SKILL.md format unchanged - Workflow files unchanged - Skill trigger logic unchanged +- Discovery path changed: bootstrap → native scanner via symlink ### Memory Structure @@ -192,6 +195,162 @@ export function fileLog(message: string, level = "info") { --- +## v3.0 Native OpenCode Architecture + +> [!note] **Architecture Decision:** ADR-020 — Native OpenCode Context Loading (Bootstrap Removal) + +The v3.0 release represents a fundamental shift in how PAI Core loads into context. The custom bootstrap mechanism built for earlier versions became redundant once OpenCode's native skill system matured — so we removed it entirely. + +### What Changed + +**Context Loading: Bootstrap → Skill System (tier:always)** + +| Before (v2.x) | After (v3.0) | +|---------------|--------------| +| `MINIMAL_BOOTSTRAP.md` loaded explicitly by plugin | PAI Core Skill loads via native OpenCode `tier: always` | +| `pai-unified.ts` handled bootstrap + user context | `pai-unified.ts` handles user context only | +| `AGENTS.md` contained ~450 lines of PAI behavior | `AGENTS.md` contains 14 lines (repo build commands only) | +| Manual Skill Discovery Index in bootstrap | OpenCode natively provides `` XML listing | + +**Why:** +- OpenCode's native skill system (`tier: always` in `GenerateSkillIndex.ts`) reliably loads skills into every session context +- The `ALWAYS_LOADED_SKILLS` list now includes `'PAI'`, ensuring the full 479-line PAI Core Skill is always in context +- OpenCode natively provides `` XML with name + description + location for every skill — the bootstrap's Skill Discovery Index was a manual duplicate of this native capability +- Removing bootstrap reduces the attack surface, eliminates a maintenance burden, and makes the architecture easier to reason about + +### Plugin Simplification + +**`pai-unified.ts` — What it does now:** + +```text +Before: Bootstrap loading (Algorithm + ISC + Capabilities) + behavioral governance + user identity +After: Behavioral governance + user identity context only (no Algorithm content) +``` + +The plugin now loads only the pieces OpenCode's native system cannot provide — files that are user-specific or cannot live in a committed skill file: +- `PAI/AISTEERINGRULES.md` — behavioral governance (system-level, applies to all sessions) +- `PAI/USER/ABOUTME.md` — personal background +- `PAI/USER/TELOS/TELOS.md` — goals and mission +- `PAI/USER/DAIDENTITY.md` — DA name and personality +- `PAI/USER/AISTEERINGRULES.md` — personal behavioral steering rules + +PAI Core (Algorithm, ISC, Capabilities) loads automatically via the skill system. + +### v3.0 Architecture — Context Flow + +```text +OpenCode session start + │ + ├─► GenerateSkillIndex.ts (ALWAYS_LOADED_SKILLS) + │ └─► skills/PAI symlink → PAI/SKILL.md [tier:always] + │ Algorithm, ISC, Capabilities — always in context + │ + └─► pai-unified.ts plugin (loadUserSystemContext) + └─► PAI/AISTEERINGRULES.md [behavioral governance] + └─► PAI/USER/ABOUTME.md [personal background] + └─► PAI/USER/TELOS/TELOS.md [goals & mission] + └─► PAI/USER/DAIDENTITY.md [DA name & personality] + └─► PAI/USER/AISTEERINGRULES.md [personal steering rules] +``` + +
+Detailed Mermaid Diagram + +```mermaid +flowchart TD + A[OpenCode session start] --> B[GenerateSkillIndex.ts] + A --> C[pai-unified.ts] + + B --> D["ALWAYS_LOADED_SKILLS\n['PAI','CORE','Development',...]"] + D --> E["skills/PAI symlink\n→ PAI/SKILL.md"] + E --> F["PAI Core Skill\ntier: always\nAlgorithm · ISC · Capabilities"] + + C --> G["loadUserSystemContext()"] + G --> H["PAI/AISTEERINGRULES.md\nBehavioral governance"] + G --> I["PAI/USER/ABOUTME.md\nPersonal background"] + G --> J["PAI/USER/TELOS/TELOS.md\nGoals & mission"] + G --> K["PAI/USER/DAIDENTITY.md\nDA name & personality"] + G --> L["PAI/USER/AISTEERINGRULES.md\nPersonal steering rules"] + + F --> M[Session context] + H & I & J & K & L --> M + + N["opencode.json\nagent model assignments"] --> O[Agent routing per task] +``` + +
+ +### AGENTS.md Simplification + +**From 450 LOC → 14 LOC** + +The old `AGENTS.md` contained the full PAI behavior specification — effectively duplicating what `PAI/SKILL.md` already held. This created a maintenance hazard: two sources of truth that could drift apart. + +The new `AGENTS.md` contains only what belongs there — repository-specific build commands: + +```markdown +# AGENTS.md +## Build, Test & Lint Commands +| Command | Purpose | +|---------|---------| +| `bun test` | Run all tests | +... +``` + +All PAI behavior lives in `PAI/SKILL.md` — the single source of truth. + +### PAI/SKILL.md Now tier:always + +**`GenerateSkillIndex.ts` — ALWAYS_LOADED_SKILLS:** + +```typescript +// Before +const ALWAYS_LOADED_SKILLS = []; + +// After +const ALWAYS_LOADED_SKILLS = [ + 'PAI', // ← PAI Core Skill (Algorithm, ISC, Capabilities) + 'CORE', + 'Development', + 'Research', + 'Blogging', + 'Art', +]; +``` + +This ensures the full 479-line PAI Core Skill (Algorithm, ISC, Capabilities) is always available in session context without any plugin intervention. + +### skills/PAI Symlink + +A new symlink `skills/PAI → ../PAI` enables the native OpenCode skill scanner to discover `PAI/SKILL.md`: + +```text +.opencode/ +├── skills/ +│ ├── PAI -> ../PAI ← symlink +│ ├── Blog/ +│ ├── Research/ +│ └── ... +└── PAI/ + └── SKILL.md ← the actual file +``` + +The skill scanner follows symlinks (supported since `upstream #620`), so PAI is discovered and loaded exactly like any other skill — no special handling required. + +### Default Model: Zen Free + +**`opencode.json` default model:** + +```json +{ + "model": "opencode/big-pickle" +} +``` + +Previously the default required configuring an API key. With `opencode/big-pickle` as default, new users get a working PAI installation out of the box — zero API key required. + +--- + ## v3.0 Adaptations ### Platform-Specific Adaptations @@ -432,19 +591,22 @@ See **ROADMAP.md** for detailed timeline. ## Version Compatibility -| Component | PAI v3.0 | PAI-OpenCode v2.0 | -|-----------|----------|-------------------| -| Skills | ✅ 39 skills | ✅ 39 skills (identical) | -| Agents | ✅ Content identical | ⚠️ Filename casing changed (PascalCase) | -| MEMORY | ✅ Identical | ✅ Identical (+ WISDOM/ directory) | -| Security Patterns | ✅ Identical | ✅ Identical | -| Hooks/Plugins | ❌ Different architecture | ✅ Plugin system (20 handlers) | -| Algorithm v1.8.0 | ✅ Full | ✅ Full | -| Wisdom Frames | ✅ Available | ✅ Available (5 seed domains) | -| Voice Server | ✅ Available | ✅ Available (3 backends) | -| Sentiment Detection | ✅ Available | ✅ Available | -| Verify Completion Gate | ✅ Available | ✅ Available | -| Observability Dashboard | ✅ Available | ✅ Available | +| Component | PAI v3.0 | PAI-OpenCode v2.0 | PAI-OpenCode v3.0 | +|-----------|----------|-------------------|-------------------| +| Skills | ✅ 39 skills | ✅ 39 skills (identical) | ✅ 39 skills (identical) | +| Agents | ✅ Content identical | ⚠️ Filename casing changed (PascalCase) | ⚠️ Filename casing changed (PascalCase) | +| MEMORY | ✅ Identical | ✅ Identical (+ WISDOM/ directory) | ✅ Identical (+ WISDOM/ directory) | +| Security Patterns | ✅ Identical | ✅ Identical | ✅ Identical | +| Hooks/Plugins | ❌ Different architecture | ✅ Plugin system (20 handlers) | ✅ Plugin system, simplified (user context only) | +| Algorithm v1.8.0 | ✅ Full | ✅ Full | ✅ Full | +| Wisdom Frames | ✅ Available | ✅ Available (5 seed domains) | ✅ Available (5 seed domains) | +| Voice Server | ✅ Available | ✅ Available (3 backends) | ✅ Available (3 backends) | +| Sentiment Detection | ✅ Available | ✅ Available | ✅ Available | +| Verify Completion Gate | ✅ Available | ✅ Available | ✅ Available | +| Observability Dashboard | ✅ Available | ✅ Available | ✅ Available | +| Bootstrap | N/A | ✅ MINIMAL_BOOTSTRAP.md | ❌ Removed — native skill system | +| PAI Core Loading | SKILL.md | Plugin-injected | Native tier:always | +| Default Model | N/A | Requires API key | `opencode/big-pickle` (free) | --- @@ -485,6 +647,7 @@ See **MIGRATION.md** for full guide. | **Logging** | stdout → file logging | TUI integrity | Debug workflow change | | **Deferred** | Voice/Observability | Focus on core first | Available in v1.x | | **Model Routing** | `.md` frontmatter → `opencode.json` exclusively (one model per agent) | Centralized configuration | Easier provider switching | +| **Context Loading** | Bootstrap → Native skill system (`tier:always`) | OpenCode native `` XML made bootstrap redundant | Zero bootstrap overhead, single source of truth | --- @@ -499,6 +662,6 @@ See **MIGRATION.md** for full guide. --- -*Last updated: 2026-03-06 (PR #42 — WP-A complete, shell.env hook, 6 new Bus events, 5 new handlers)* +*Last updated: 2026-04-13 (v3.0 native OpenCode architecture — bootstrap removed, PAI Core tier:always, AGENTS.md 450→14 LOC, Zen free default)* -**PAI-OpenCode v3.0-dev** — Full PAI v3.0, Algorithm v1.8.0, 39 Skills, 25 Handlers, Wisdom Frames, shell.env Hook +**PAI-OpenCode v3.0** — Full PAI v3.0, Algorithm v1.8.0, 39 Skills, Native OpenCode Skill Loading, Zero Bootstrap, Zen Free Default diff --git a/docs/architecture/adr/ADR-020-native-opencode-context-loading.md b/docs/architecture/adr/ADR-020-native-opencode-context-loading.md new file mode 100644 index 00000000..73ff54d7 --- /dev/null +++ b/docs/architecture/adr/ADR-020-native-opencode-context-loading.md @@ -0,0 +1,111 @@ +# ADR-020: Native OpenCode Context Loading (Bootstrap Removal) + +**Status:** Accepted +**Date:** 2026-04-13 +**Deciders:** Steffen Zellmer + +--- + +## Context + +PAI-OpenCode v2.x used a custom bootstrap mechanism (`MINIMAL_BOOTSTRAP.md`) loaded by the `pai-unified.ts` plugin via the `experimental.chat.system.transform` hook. This bootstrap served three purposes: + +1. **Algorithm Essence** — A condensed summary of Algorithm v3.7.0 phases and ISC rules +2. **Skill Discovery Index** — A manual table listing all 50+ skills with their trigger phrases and paths +3. **User Identity Routing** — Instructions for loading ABOUTME, TELOS, DAIDENTITY files + +The bootstrap was introduced in early v3.0 development as an interim solution while we were porting PAI's Claude Code `contextFiles` mechanism to OpenCode. The intent was "minimal useful context" — provide just enough for the AI to understand the system without loading everything upfront. + +## Problem + +Three issues emerged with this approach: + +1. **Algorithm works poorly from the bootstrap alone.** The bootstrap contained only an "essence" (20-30 lines) of the full 479-line Algorithm. Models produced a degraded experience — following the Algorithm's structure but missing critical details like exact ISC minimums, capability audit format, Red Lines, and verification phase requirements. + +2. **Skill Discovery Index was redundant.** OpenCode natively generates an `` XML block for every session, listing all discovered skills with name, description, and location. The bootstrap's 90-line manual table was duplicating what OpenCode already provided — and falling out of sync as new skills were added. + +3. **Plugin used an experimental hook.** `experimental.chat.system.transform` is marked experimental. The PAI Core (Algorithm) is the most critical piece of PAI — anchoring it to an experimental hook was a reliability risk. + +## Decision + +Remove the bootstrap mechanism entirely. Shift PAI Core loading to OpenCode's native skill system. + +**Three changes:** + +### 1. PAI/SKILL.md → tier:always + +Add `'PAI'` to the `ALWAYS_LOADED_SKILLS` array in `GenerateSkillIndex.ts`. The PAI Core Skill (479 lines, full Algorithm + ISC + Capabilities) is now loaded by the native OpenCode skill system at session start — not by the plugin. + +```typescript +// PAI/Tools/GenerateSkillIndex.ts +const ALWAYS_LOADED_SKILLS = [ + 'PAI', // ← added + 'CORE', + 'Development', + // ... +]; +``` + +### 2. skills/PAI symlink + +Create `skills/PAI → ../PAI` symlink so OpenCode's native skill scanner discovers `PAI/SKILL.md`. The scanner already follows symlinks (established in upstream #620). No special handling required. + +```text +.opencode/ +├── skills/ +│ └── PAI -> ../PAI ← new symlink +└── PAI/ + └── SKILL.md +``` + +### 3. Plugin simplified to user context only + +`pai-unified.ts` no longer loads `MINIMAL_BOOTSTRAP.md`. It loads only: +- `PAI/AISTEERINGRULES.md` — behavioral governance (cannot be in a static skill file) +- `USER/ABOUTME.md` — personal background (user-specific, not in repo) +- `USER/TELOS/TELOS.md` — life goals and mission (user-specific) +- `USER/DAIDENTITY.md` — DA name and personality (user-specific) +- `USER/AISTEERINGRULES.md` — personal behavioral rules (user-specific) + +These files are genuinely dynamic — they vary per user installation and cannot live in a static committed skill file. Everything else (the Algorithm, ISC rules, Capabilities) belongs in `PAI/SKILL.md`. + +## Consequences + +### Positive + +- **Full Algorithm always available.** The complete 479-line PAI Core Skill is in context — not a condensed essence. Models follow the Algorithm correctly. +- **No redundant Skill Discovery Index.** OpenCode's native `` XML handles skill discovery. One source of truth. +- **Stable loading mechanism.** Native skill system (`tier:always`) is production-stable, not experimental. +- **Simpler plugin.** `pai-unified.ts` has a single clear responsibility: user identity context. +- **AGENTS.md simplified.** Reduced from 450 to 14 lines. PAI behavior has one home: `PAI/SKILL.md`. + +### Negative / Tradeoffs + +- **Larger always-loaded context.** PAI/SKILL.md (479 lines, ~18KB) vs. MINIMAL_BOOTSTRAP.md (203 lines, ~7KB). This is accepted — the degraded Algorithm experience from the thin bootstrap was worse than the token cost of loading it fully. +- **All context reaches model via user message (not system prompt).** The `cemalturkcan/opencode-anthropic-login-via-cli` plugin (used by most PAI-OpenCode users with Anthropic Max) relocates all system prompt blocks except the identity block to the first user message. This applies to both the old bootstrap and the new skill content equally — so the tradeoff is identical. + +### No Impact + +- Skill on-demand loading — unchanged. All 50+ non-PAI skills remain deferred, loaded via `Skill()` tool. +- Memory system, agents, security validation — unchanged. +- User identity loading — unchanged (still handled by plugin). + +## Alternatives Considered + +### Alternative A: Expand MINIMAL_BOOTSTRAP.md + +Expand the bootstrap to contain the full Algorithm. Rejected: still uses the experimental hook, still a second copy of content that belongs in SKILL.md. + +### Alternative B: Use opencode.json `instructions` field + +Load Algorithm via `opencode.json → instructions: [".opencode/PAI/Algorithm/v3.7.0.md"]`. Rejected: the Anthropic auth plugin relocates `instructions` content to user messages exactly like the existing bootstrap — same net result, but adds complexity without benefit for the target user base. + +### Alternative C: Inline Algorithm in AGENTS.md + +Add Algorithm content to AGENTS.md. Rejected: AGENTS.md is loaded on every message by every agent working in the repo. Putting PAI Core there would load it for Contributors fixing CI issues — work that has nothing to do with PAI. Wrong layer of concern. + +## Related ADRs + +- [ADR-001](ADR-001-hooks-to-plugins-architecture.md) — Hooks → Plugins (original plugin architecture) +- [ADR-003](ADR-003-skills-system-unchanged.md) — Skills system preservation +- [ADR-019](ADR-019-vanilla-opencode-migration.md) — Vanilla OpenCode migration (prerequisite for this ADR) diff --git a/opencode.json b/opencode.json index 33fdb826..56037a99 100644 --- a/opencode.json +++ b/opencode.json @@ -1,7 +1,7 @@ { "$schema": "https://opencode.ai/config.json", "theme": "dark", - "model": "anthropic/claude-sonnet-4-5", + "model": "opencode/big-pickle", "snapshot": true, "username": "User", "permission": { @@ -22,52 +22,52 @@ }, "agent": { "Algorithm": { - "model": "anthropic/claude-opus-4-6" + "model": "opencode/big-pickle" }, "Architect": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "Engineer": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "general": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "explore": { - "model": "anthropic/claude-haiku-4-5" + "model": "opencode/big-pickle" }, "Intern": { - "model": "anthropic/claude-haiku-4-5" + "model": "opencode/big-pickle" }, "Writer": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "DeepResearcher": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "GeminiResearcher": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "GrokResearcher": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "PerplexityResearcher": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "CodexResearcher": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "QATester": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "Pentester": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "Designer": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" }, "Artist": { - "model": "anthropic/claude-sonnet-4-5" + "model": "opencode/big-pickle" } } }