From b4dcfcf29f95535414b554d090d81f0b6524b27f Mon Sep 17 00:00:00 2001 From: Tran Quang Dang Date: Tue, 30 Jun 2026 23:32:35 +0700 Subject: [PATCH 1/3] docs: add goal-driven feature implementation backlog Add consolidated PR backlog from 13 reference repos (A-J, ~80 features) and supporting docs (MASTER_GOAL_PROMPT, GOAL_DRIVEN_PROMPT, CONSOLIDATED_FINDINGS). --- docs/CONSOLIDATED_FINDINGS.md | 79 +++++++ docs/GOAL_DRIVEN_PROMPT.md | 329 +++++++++++++++++++++++++++++ docs/MASTER_GOAL_PROMPT.md | 379 ++++++++++++++++++++++++++++++++++ docs/PR_BACKLOG.md | 225 ++++++++++++++++++++ 4 files changed, 1012 insertions(+) create mode 100644 docs/CONSOLIDATED_FINDINGS.md create mode 100644 docs/GOAL_DRIVEN_PROMPT.md create mode 100644 docs/MASTER_GOAL_PROMPT.md create mode 100644 docs/PR_BACKLOG.md diff --git a/docs/CONSOLIDATED_FINDINGS.md b/docs/CONSOLIDATED_FINDINGS.md new file mode 100644 index 000000000..8c6d6de6e --- /dev/null +++ b/docs/CONSOLIDATED_FINDINGS.md @@ -0,0 +1,79 @@ +# Consolidated Research Findings — 13 Reference Repos vs jcode + +> **Generated from**: PARITY.md, MASTER_UI.md, .agents/skills/feature-planning/, and 12 cloned reference repos in /tmp/feature-research/ +> **Date**: 2026-06-30 +> **Status**: Initial consolidation; will be refined as research subagents report back + +## Executive Summary + +**jcode is at 91% parity** with reference repos (281/310 features marked ✅), but has 13 ❌ missing + 16 ⚠️ partial features. The biggest gaps are: +1. **Provider System** (Section A) — needs 4-axis Route architecture +2. **Plugin System hardening** (Section B) — needs V2 capability chain +3. **Tools** (Section C) — DAP, tree-sitter code-map, prompt variants +4. **Multi-agent orchestration** (Section D) — Agent Arena, Ferment plans +5. **TUI features** (Section G) — file browser, MCP/LSP status panels + +## Reference Repos Cloned + +All 13 repos successfully cloned to `/tmp/feature-research/`: + +| # | Repo | Files | Key Feature | +|---|------|-------|-------------| +| 1 | claude-code (CCB) | 1106 | Pipe IPC, ACP, Langfuse, Computer Use, Voice | +| 2 | codebuff | 252 | 4-agent pipeline, tree-sitter code-map | +| 3 | codex | 520 | Sandboxed execution, hardened tool use | +| 4 | crush | 357 | Bubble Tea TUI, Agent Skills standard | +| 5 | gajae-code | 338 | deep-interview→ralplan→ultragoal pipeline | +| 6 | kimchi | 444 | Multi-model orchestration, Ferment, RTK | +| 7 | oh-my-Codex (oh-my-codex) | 720 | Codex plugin, hooks, guards | +| 8 | oh-my-openagent | 365 | Agent factory, per-model prompts, tmux | +| 9 | oh-my-pi | 358 | 40+ providers, 32 tools, 13 LSP, 27 DAP | +| 10 | opencode | 372 | 4-axis Route, monorepo, models.dev | +| 11 | pi-agent-rust | 1041 | SQLite sessions, WASM, SSE parser | +| 12 | qwen-code | 412 | Multi-protocol, IM bots, SDK | + +## Confirmed Missing Features (PARITY.md §XIV) + +| Feature | Source | Status | Notes | +|---------|--------|--------|-------| +| WASM extension security | pi-agent-rust | ❌ | | +| SSE streaming | pi-agent-rust | ⚠️ | | +| ACP / Remote control | claude-code | ⚠️ | | +| Sandbox execution | codex | ❌ (skipped) | | +| 40+ providers | oh-my-pi | ⚠️ | | +| IDE wiring (VS Code) | oh-my-pi | ❌ | | +| DAP operations (27) | oh-my-pi | ⚠️ | | +| Computer Use (full) | CCB | ⚠️ (macOS only) | | +| Chrome Use | CCB | ❌ | | +| Voice Mode | CCB | ❌ | | +| Pipe IPC multi-instance | CCB | ❌ | | +| Langfuse monitoring | CCB | ❌ | | +| Remote Control Docker | CCB | ❌ | | +| Tmux integration | oh-my-openagent | ⚠️ | | +| Prompt variants per model | oh-my-openagent | ❌ | | +| Tree-sitter code map | codebuff | ⚠️ | | +| io_uring | pi-agent-rust | ❌ (skipped) | | +| Shadow dual execution | pi-agent-rust | ❌ | | + +## Per-PR Plan Files Created (in docs/pr-plans/) + +Total backlog: **~80 features** across 10 sections (A-J). +Plan files to be created: `docs/pr-plans/-.md` + +## Next Steps (Implementation Phase) + +Phase 1 - Foundation (P0, 6 features): +- A1: Auth trait combinators +- A2: 4-axis Route +- A3: Canonical schema +- A4: OpenAI Responses protocol +- A5: Anthropic Messages protocol +- B1: ToolTier + ApprovalGate + +Phase 2 - Core Ecosystem (P1, 16 features): +- A6-A10, B2-B3, C2-C3, C14, D3-D4, D6, E1-E2, F1 + +Phase 3 - Polish (P1-P2, 20+ features) + +Phase 4 - Long Tail (P2-P3, 18+ features) + diff --git a/docs/GOAL_DRIVEN_PROMPT.md b/docs/GOAL_DRIVEN_PROMPT.md new file mode 100644 index 000000000..37a4e2ff5 --- /dev/null +++ b/docs/GOAL_DRIVEN_PROMPT.md @@ -0,0 +1,329 @@ +# Goal-Driven(jcode Feature Implementation) System + +## 🎯 Goal + +**Implement all missing features from 13 reference AI coding agent repos as individual PRs against `master`, each accompanied by a detailed planning markdown file.** + +Each PR must: +1. Have base branch = `master` +2. Include a plan markdown file (`docs/pr-plans/-.md`) with: research findings, reasoning, alternatives compared, chosen approach +3. Pass `cargo build` and `cargo test` +4. Update PARITY.md to mark the feature as implemented + +--- + +## ✅ Criteria for Success + +**The system is complete when:** +1. All P0 features are implemented and merged +2. All P1 features are implemented (or explicitly deferred with rationale) +3. PARITY.md §XIV (Reference Repo Gaps) shows all P0/P1 items marked ✅ or ❌(skipped) +4. The PR backlog (`docs/PR_BACKLOG.md`) is updated with actual status per feature +5. Each implemented feature has a plan file at `docs/pr-plans/-.md` + +--- + +## 🏗️ System Architecture + +### Master Agent (this session) + +The master agent is responsible for: +1. **Supervising** the implementation subagents +2. **Checking progress** every 5 minutes +3. **Restarting inactive** subagents +4. **Evaluating** whether success criteria are met +5. **NOT stopping** until user manually stops + +### Implementation Subagents + +Each implementation subagent handles ONE feature PR: +- Reads the plan file template at `docs/pr-plans/-.md` +- Clones/checkouts the relevant reference repo at `/tmp/feature-research/` +- Compares against jcode's actual implementation +- Writes the plan markdown (research, reasoning, alternatives, chosen approach) +- Implements the feature +- Runs tests +- Opens a PR with proper description +- Updates the backlog + +--- + +## 📋 Workflow + +### Step 1 — Prioritized Queue + +Features are processed in this order (from `docs/PR_BACKLOG.md`): + +``` +Phase 1 (Foundation - P0): + A1 → A2 → A3 → A4 → A5 → B1 + +Phase 2 (Core Ecosystem - P1): + A6 → A7 → A8 → A9 → A10 → B2 → B3 → C2 → C3 → C14 → D3 → D4 → D6 → E1 → E2 → F1 + +Phase 3 (Polish - P1-P2): + A11 → A12 → A16 → A17 → B4 → B7 → C4 → C6 → C15 → C16 → C20 → D5 → G1 → G2 → G3 → G6 → G7 → G8 + +Phase 4 (Long Tail - P2-P3): + Remaining P2/P3 items +``` + +### Step 2 — Implementation Subagent Task + +For each feature, spawn an implementation subagent with: + +``` +## Task for Feature: () + +### Context +- Feature description: +- Source repos: +- Priority: +- Effort: +- Plan file: docs/pr-plans/-.md +- Branch name: feat/- + +### Research Phase +1. Check /tmp/feature-research// for cloned reference code +2. If not cloned: git clone --depth=1 /tmp/feature-research/ +3. Read the actual reference implementation code +4. Read jcode's current implementation +5. Compare and identify gaps + +### Plan Phase +Write docs/pr-plans/-.md with: +- Research summary (source files, direct links) +- Why this feature is missing in jcode +- Alternatives considered (table format) +- Chosen approach with rationale +- Implementation plan (file-by-file) +- Risk analysis +- Success criteria checklist + +### Implementation Phase +1. git checkout -b feat/- +2. Implement the feature following the plan +3. cargo build (must pass) +4. cargo test (must pass) +5. Update PARITY.md status to ✅ +6. git add + commit + +### PR Phase +1. Create PR with: + - Base: master + - Title: feat(): + - Body: Reference the plan file + summary of changes + - Labels: feature, +2. Push branch +3. Update docs/PR_BACKLOG.md row status to "PR #" + +### Cleanup +- Delete /tmp/feature-research// if you cloned it +``` + +### Step 3 — Master Loop + +``` +WHILE criteria not met: + 1. Check PR backlog status + 2. Identify next unstarted feature from Phase 1-4 + 3. Spawn implementation subagent for that feature + 4. Wait 5 minutes (or until agent completes) + 5. IF agent completed: + - Verify PR opened + - Update backlog + - Mark criteria check + 6. IF agent inactive: + - Restart new agent with same task + 7. IF all Phase 1+2 features done: + - Final evaluation + - Report summary +``` + +--- + +## 🔧 Per-Feature Implementation Pattern + +### Creating the Plan File + +Each `docs/pr-plans/-.md` follows this template: + +```markdown +# PR Plan: + +## Research Summary +- Source repo(s): +- Key files inspected: +- Direct code links: + - https://github.com///blob/main/#L + - ... + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md §XIV +- Code path that should exist but doesn't +- Architectural reason for absence + +## Alternatives Considered + +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| Alternative A | oh-my-pi | ... | ... | Rejected because... | +| Alternative B | opencode | ... | ... | Selected ✓ | + +## Chosen Approach +- What we're building +- Why this approach fits jcode's architecture +- Key architectural decisions + +## Implementation Plan + +### Phase 1: Scaffold +- [ ] Add new types to `crates/jcode-/src/` +- [ ] Add tests + +### Phase 2: Integrate +- [ ] Wire into existing systems +- [ ] Add CLI/TUI integration + +### Phase 3: Test +- [ ] Unit tests +- [ ] Integration tests +- [ ] Manual verification + +## File Changes + +| File | Change | +|------|--------| +| `crates/jcode-xxx/src/yyy.rs` | New: Z struct, impl Trait | +| `crates/jcode-app-core/src/agent.rs` | Modified: added trait impl | +| `PARITY.md` | Updated: feature row → ✅ | + +## Risk Analysis +- **Performance**: +- **Compatibility**: +- **Security**: + +## Success Criteria +- [ ] `cargo build` exits 0 +- [ ] `cargo test` exits 0 +- [ ] PARITY.md §XIV updated +- [ ] Manual test: +- [ ] PR opened against master +``` + +### Branch Naming + +``` +feat/A1-auth-trait-combinators +feat/B1-tool-tier-approval-gate +feat/C2-tree-sitter-codemap +feat/D1-agent-arena +etc. +``` + +### PR Description Template + +```markdown +## Summary +Brief description of what this PR implements. + +## Plan +See [docs/pr-plans/-.md](docs/pr-plans/-.md) for full research, alternatives, and implementation details. + +## Changes +- Added: ... +- Modified: ... +- Removed: ... + +## Testing +- [ ] `cargo build` passes +- [ ] `cargo test` passes +- [ ] Manual verification: + +## References +- Source: +- PARITY.md: §
row +``` + +--- + +## 🎛️ Control Panel + +### Start from Specific Phase +To start from Phase 2 (skip completed Phase 1 features): +``` +Skip Phase 1 implementation. Start with Phase 2 feature A6. +``` + +### Skip Specific Feature +``` +Skip feature . Mark as deferred in backlog with reason: . +``` + +### Change Order +``` +Move feature before in the queue. +``` + +### Emergency Stop +``` +STOP: Do not spawn any more agents. Report current status. +``` + +--- + +## 📊 Progress Tracking + +Track in `docs/PR_BACKLOG.md`: + +| Status | Meaning | +|--------|---------| +| 🔜 Pending | Not started | +| 🏗️ In Progress | Agent working on it | +| ✅ Done | Merged to master | +| ⏸️ Deferred | Explicitly deferred with reason | +| ❌ Skipped | Not applicable (sandboxed, etc.) | +| 🔀 PR #N | Open PR | +| ⚠️ Partial | Partially implemented | + +--- + +## 🚨 Error Handling + +If an implementation subagent fails: +1. Log the error +2. Restart with same task (max 3 retries) +3. If 3 retries fail, mark as `deferred` with error summary +4. Move to next feature + +If `cargo build` fails: +1. Capture error output +2. Add fix commits to the branch +3. Retry build +4. If cannot fix, defer with error summary + +If `cargo test` fails: +1. Run specific failing test with output +2. Fix test or update test expectations +3. If test is flaky, add retry logic +4. If cannot fix, defer with error summary + +--- + +## 🏁 Success Conditions + +The goal is **COMPLETE** when: + +1. **P0 Complete**: All 6 Phase 1 features (A1-A5, B1) are merged +2. **P1 Mostly Done**: ≥80% of Phase 2 features are merged or deferred +3. **Backlog Updated**: Every row in `docs/PR_BACKLOG.md` has a status +4. **PARITY.md Current**: §XIV accurately reflects implemented vs missing + +The goal is **PARTIAL** if: +- Some features remain unimplemented +- Report which features remain and why + +The goal is **STUCK** if: +- Agent repeatedly fails on same feature +- Network/build issues persist +- Requires human intervention diff --git a/docs/MASTER_GOAL_PROMPT.md b/docs/MASTER_GOAL_PROMPT.md new file mode 100644 index 000000000..52bec0b10 --- /dev/null +++ b/docs/MASTER_GOAL_PROMPT.md @@ -0,0 +1,379 @@ +# Goal-Driven(jcode Feature Implementation) System — MASTER PROMPT + +> 🎯 **Goal**: Implement tất cả features còn thiếu so với 13 reference repos dưới dạng các PR riêng biệt vào branch `master`, mỗi PR kèm theo file planning markdown chi tiết (research, lý do, alternatives, chosen approach). + +--- + +## Goal Statement + +**Implement all missing features from 13 reference AI coding agent repos as individual PRs against `master`, each accompanied by a detailed planning markdown file.** + +## Criteria for Success + +1. All P0 features (Foundation, ~6 features) are implemented and merged +2. ≥80% of P1 features (Core Ecosystem, ~25 features) are merged or explicitly deferred with rationale +3. `PARITY.md` §XIV (Reference Repo Gaps) accurately reflects current state +4. `docs/PR_BACKLOG.md` updated with status per feature +5. Each implemented feature has a plan file at `docs/pr-plans/-.md` + +--- + +## Reference Repositories (13 total, all cloned to `/tmp/feature-research/`) + +| Alias | Repo URL | Stack | +|-------|----------|-------| +| `oh-my-openagent` | https://github.com/code-yeongyu/oh-my-openagent | TypeScript | +| `opencode` | https://github.com/anomalyco/opencode | TypeScript | +| `oh-my-pi` | https://github.com/can1357/oh-my-pi | TS + Rust | +| `codebuff` | https://github.com/CodebuffAI/codebuff | TypeScript | +| `codex` | https://github.com/openai/codex | TypeScript | +| `claude-code` | https://github.com/claude-code-best/claude-code | TypeScript | +| `pi-agent-rust` | https://github.com/Dicklesworthstone/pi_agent_rust | Rust | +| `oh-my-Codex` | https://github.com/Yeachan-Heo/oh-my-Codex | TypeScript | +| `oh-my-codex` | https://github.com/Yeachan-Heo/oh-my-codex | TypeScript | +| `gajae-code` | https://github.com/Yeachan-Heo/gajae-code | TS + Rust | +| `kimchi` | https://github.com/getkimchi/kimchi | TypeScript | +| `qwen-code` | https://github.com/QwenLM/qwen-code | TS + Rust | +| `crush` | https://github.com/charmbracelet/crush | Go | + +--- + +## jcode Project Structure + +- **Repo root**: `/Users/tranquangdang21/Projects/jcode` +- **Workspace**: 100+ crates in `crates/` +- **Main crates**: + - `jcode-app-core` — agent runtime + - `jcode-agent-runtime` — agent definitions/registry + - `jcode-plugin-core` + `jcode-plugin-runtime` — plugin system + - `jcode-provider-*` — 10 provider crates + - `jcode-tui*` — TUI modules + - `jcode-llm-*` — LLM layer +- **PARITY.md**: 310 features tracked, 91% complete +- **MASTER_UI.md**: 110 TUI section specs +- **Source binary**: `~/.local/bin/jcode` + +--- + +## The System: 1 Master + N Subagents + +### Master Agent + +You are the master agent. Your ONLY responsibilities are: + +1. **Spawn implementation subagents** for missing features (one per feature/PR) +2. **Check every 5 minutes** if subagents are still active +3. **Evaluate progress** against success criteria +4. **Restart inactive** subagents (max 3 retries per feature) +5. **Report status** without stopping until user intervenes + +### Implementation Subagent (one per feature) + +For each feature, spawn a subagent with this task: + +``` +## Task: Implement Feature - + +### Step 1: Research +- Check /tmp/feature-research// for the reference code +- Read the actual implementation +- Read jcode's current implementation in crates/ +- Identify the gap + +### Step 2: Plan +Write docs/pr-plans/-.md with this structure: +# PR Plan: + +## Research Summary +- Source repo(s): +- Key files inspected: +- Direct code links: + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md §XIV +- Code path that should exist but doesn't + +## Alternatives Considered +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| ... | ... | ... | ... | ... | + +## Chosen Approach +- What we're building +- Why this approach fits jcode + +## Implementation Plan +- File-by-file changes +- New types/structs +- Test cases + +## Risk Analysis +- Performance, compatibility, security + +## Success Criteria +- [ ] cargo build passes +- [ ] cargo test passes +- [ ] PARITY.md updated +- [ ] Manual verification works + +### Step 3: Implement +1. git checkout -b feat/- +2. Make changes per the plan +3. cargo build (must pass) +4. cargo test (must pass) +5. Update PARITY.md to mark feature as ✅ +6. git commit with conventional commit message + +### Step 4: PR +1. Open PR with: + - Base: master + - Title: feat(): + - Body: Reference the plan file + summary +2. Update docs/PR_BACKLOG.md with PR number + +### Step 5: Cleanup +- Mark task complete in /Users/tranquangdang21/Projects/jcode/docs/PR_BACKLOG.md +- Move to next feature +``` + +--- + +## Pseudocode for Master Loop + +``` +create_subagent_for_each_feature(features_to_implement) +completed_prs = [] + +while (criteria_not_met): + for feature in priority_order: + if feature not started: + spawn_implementation_subagent(feature) + elif feature agent inactive > 5min: + if retry_count < 3: + restart_subagent(feature) + else: + mark_feature_as_deferred(feature, "Build/test failures") + elif feature pr_merged: + completed_prs.append(feature) + + if all_p0_done AND p1_progress >= 80%: + evaluate_success_criteria() + if success: + announce_completion() + + sleep 5 minutes +``` + +--- + +## Feature Priority Queue (from docs/PR_BACKLOG.md) + +**Phase 1 — Foundation (P0, weeks 1-2)**: +A1 (auth trait) → A2 (4-axis route) → A3 (schema) → A4 (OpenAI Responses) → A5 (Anthropic Messages) → B1 (ToolTier) + +**Phase 2 — Core Ecosystem (P1, weeks 3-6)**: +A6 (inband dialects) → A7 (VCR) → A8 (failover) → A9 (catalog) → A10 (integration) → B2 (capability V2) → B3 (PluginManager) → C2 (tree-sitter) → C3 (prompt variants) → C14 (RTK) → D3 (4-agent pipeline) → D4 (multi-model) → D6 (team DAG) → E1 (SQLite) → E2 (SSE) → F1 (workflow pipeline) + +**Phase 3 — Polish (P1-P2, weeks 7-10)**: +A11-A18 (more providers) → B4-B9 (plugin features) → C4-C20 (tools) → D5 (best-of-N) → G1-G8 (TUI) + +**Phase 4 — Long Tail (P2-P3, weeks 11+)**: +All P2/P3 items + +--- + +## Per-PR Plan File Template + +`docs/pr-plans/-.md` must contain: + +```markdown +# PR Plan: + +## Research Summary +- **Source repo(s)**: +- **Key files inspected**: + - `/tmp/feature-research//:` +- **Direct code links**: + - https://github.com///blob/main/#L + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md §XIV +- Code path that should exist but doesn't + +## Alternatives Considered + +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| Pattern A | oh-my-pi | Simple | Limited scope | Rejected | +| Pattern B | opencode | Full-featured | Complex | **Selected** | + +## Chosen Approach +- **What we're building**: +- **Why this approach fits jcode**: +- **Key architectural decisions**: + +## Implementation Plan + +### Phase 1: Scaffold +- [ ] New file: `crates/jcode-/src/.rs` +- [ ] Add new type: `` +- [ ] Add trait impl + +### Phase 2: Integrate +- [ ] Wire into existing systems +- [ ] Add CLI/TUI integration + +### Phase 3: Test +- [ ] Unit tests +- [ ] Integration tests +- [ ] Manual verification command + +## File Changes + +| File | Change | +|------|--------| +| `crates/.../src/...` | New: | +| `crates/.../src/...` | Modified: | + +## Risk Analysis +- **Performance**: +- **Compatibility**: +- **Security**: + +## Success Criteria +- [ ] `cargo build` exits 0 +- [ ] `cargo test` exits 0 +- [ ] `PARITY.md` §XIV updated +- [ ] Manual verification: `` +- [ ] PR opened against `master` +``` + +--- + +## Branch & PR Conventions + +### Branch Naming +``` +feat/- +fix/- (for bug fixes found during implementation) +docs/- (for doc-only PRs) +``` + +### Commit Message +``` +feat(): + +- +- + +Closes # (if applicable) +Refs: docs/pr-plans/-.md +``` + +### PR Title +``` +feat(): +``` + +### PR Body +```markdown +## Summary +<1-2 sentence description> + +## Plan +See [docs/pr-plans/-.md](docs/pr-plans/-.md) for full research, alternatives, and implementation details. + +## Changes +- Added: ... +- Modified: ... + +## Testing +- [ ] `cargo build` passes +- [ ] `cargo test` passes +- [ ] Manual verification: + +Closes # (if applicable) +``` + +--- + +## Spawning Subagents — Detailed Pattern + +For each feature, the master agent should use the Agent tool with: + +```python +Agent( + description=f"Implement feature {feature_id}: {feature_name}", + prompt=f""" +You are implementing feature {feature_id} for jcode. + +## Context +- jcode is at: /Users/tranquangdang21/Projects/jcode +- Reference repos at: /tmp/feature-research/ +- Feature: {feature_name} +- Source: {source_repo} +- Priority: {priority} +- Effort: {effort} +- Plan file: docs/pr-plans/{feature_id}-{feature_name_kebab}.md +- Branch: feat/{feature_id}-{feature_name_kebab} + +## Your Task +1. Research: Read /tmp/feature-research/{source_repo}/ for the reference implementation +2. Plan: Write the plan file at docs/pr-plans/{feature_id}-{feature_name_kebab}.md +3. Implement: Create branch feat/{feature_id}-{feature_name_kebab}, implement, test +4. PR: Open PR against master with the plan file referenced +5. Update: Update docs/PR_BACKLOG.md status + +## Critical Rules +- Always read actual code in /tmp/feature-research/ before writing the plan +- Use real file:line references in the plan +- cargo build and cargo test MUST pass before opening PR +- If you cannot make it work, update the plan with what's blocking and mark as deferred +- Update PARITY.md in the same PR + +Work autonomously. Do not stop until you have either: +(a) Opened the PR with all checks passing +(b) Documented the blocker in the plan file +""", + subagent_type="general-purpose", + run_in_background=True, + name=f"impl-{feature_id}" +) +``` + +--- + +## Tracking Progress + +### In `docs/PR_BACKLOG.md` + +Update each row's status: +- 🔜 Pending → 🏗️ In Progress → ✅ Done / 🔀 PR #N / ⏸️ Deferred / ❌ Skipped + +### In `PARITY.md` §XIV + +Each implemented feature gets updated from `❌ Not implemented` to `✅ Implemented in PR #N`. + +--- + +## Control Commands + +| Command | Effect | +|---------|--------| +| "Start from Phase 2" | Skip completed Phase 1 features | +| "Skip feature X" | Mark as deferred with reason | +| "Prioritize X over Y" | Reorder queue | +| "STOP" | Pause all agents, report status | +| "Continue" | Resume from current position | + +--- + +## DO NOT STOP + +The master agent must continue: +- Spawning subagents +- Checking status +- Restarting inactive agents +- Reporting progress + +Until the user explicitly says "STOP" or all success criteria are met. diff --git a/docs/PR_BACKLOG.md b/docs/PR_BACKLOG.md new file mode 100644 index 000000000..a9e3a77b4 --- /dev/null +++ b/docs/PR_BACKLOG.md @@ -0,0 +1,225 @@ +# jcode Feature PR Backlog — From 13 Reference Repos + +> Goal-driven implementation backlog. Each row = 1 PR against `master`. +> For each missing feature, the implementation subagent must: +> 1. Spawn a research subagent to verify the actual code in `/tmp/feature-research/` +> 2. Compare against jcode implementation +> 3. Produce a plan markdown: research findings, reasoning, alternatives considered, chosen approach +> 4. Implement, test, and open the PR +> 5. Attach the plan markdown to the PR description + +## Priority Legend +- **P0** — Critical: Blocks core workflows or closes major user-visible gaps +- **P1** — High: Significant value, matches established patterns in multiple reference repos +- **P2** — Medium: Nice-to-have, ecosystem parity +- **P3** — Low: Experimental, niche use cases + +## Effort Legend +- **S** — Small (<1 day) +- **M** — Medium (1-3 days) +- **L** — Large (3-7 days) +- **XL** — Extra Large (>1 week, may need to split) + +--- + +## Section A — Provider System (from opencode, oh-my-pi, pi-agent-rust, crush) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| A1 | Auth trait with combinators (Bearer/Header/Remove/Custom/Optional/Config/OrElse) | opencode | 🔜 Pending | P0 | M | docs/pr-plans/A1-auth-trait-combinators.md | feat/A1-auth-trait-combinators | +| A2 | 4-axis Route (Protocol × Endpoint × Auth × Framing) | opencode | 🔜 Pending | P0 | L | docs/pr-plans/A2-route-4-axis.md | feat/A2-route-4-axis | +| A3 | Canonical LlmRequest/LlmEvent/LlmError schema | opencode | 🔜 Pending | P0 | M | docs/pr-plans/A3-canonical-schema.md | feat/A3-canonical-schema | +| A4 | OpenAI Responses protocol | opencode | 🔜 Pending | P0 | M | docs/pr-plans/A4-openai-responses.md | feat/A4-openai-responses | +| A5 | Anthropic Messages protocol | opencode | 🔜 Pending | P0 | M | docs/pr-plans/A5-anthropic-messages.md | feat/A5-anthropic-messages | +| A6 | 13 inband dialect layer (anthropic/deepseek/gemini/glm/harmony/kimi/qwen3/xml/etc) | oh-my-pi | 🔜 Pending | P1 | L | docs/pr-plans/A6-inband-dialects.md | feat/A6-inband-dialects | +| A7 | VCR test infrastructure (recorded-replay cassettes) | pi-agent-rust, opencode | 🔜 Pending | P1 | L | docs/pr-plans/A7-vcr-recorder.md | feat/A7-vcr-recorder | +| A8 | Reactive failover walker | oh-my-openagent, oh-my-pi | ⚠️ Partial | P1 | M | docs/pr-plans/A8-failover-walker.md | feat/A8-failover-walker | +| A9 | Catalog service (in-memory Map) | opencode | 🔜 Pending | P1 | M | docs/pr-plans/A9-catalog-service.md | feat/A9-catalog-service | +| A10 | Integration/Credential service (OAuth PKCE + device code + API key) | opencode | 🔜 Pending | P1 | M | docs/pr-plans/A10-integration-credential.md | feat/A10-integration-credential | +| A11 | Provider: Azure OpenAI Responses | codex | 🔜 Pending | P1 | S | docs/pr-plans/A11-provider-azure.md | feat/A11-provider-azure | +| A12 | Provider: Vertex AI (Claude + Gemini) | opencode, pi-agent-rust | 🔜 Pending | P1 | S | docs/pr-plans/A12-provider-vertex.md | feat/A12-provider-vertex | +| A13 | Provider: Groq | opencode | 🔜 Pending | P2 | S | docs/pr-plans/A13-provider-groq.md | feat/A13-provider-groq | +| A14 | Provider: Mistral | opencode | 🔜 Pending | P2 | S | docs/pr-plans/A14-provider-mistral.md | feat/A14-provider-mistral | +| A15 | Provider: Cohere v2 | pi-agent-rust | 🔜 Pending | P2 | S | docs/pr-plans/A15-provider-cohere.md | feat/A15-provider-cohere | +| A16 | TUI /provider command (list/login/logout/set default) | opencode, oh-my-pi | 🔜 Pending | P1 | M | docs/pr-plans/A16-tui-provider.md | feat/A16-tui-provider | +| A17 | TUI /model command (browse/filter/pick model) | opencode | 🔜 Pending | P1 | M | docs/pr-plans/A17-tui-model.md | feat/A17-tui-model | +| A18 | Models.dev auto-bootstrap with cache + fingerprint | opencode | 🔜 Pending | P1 | S | docs/pr-plans/A18-models-dev-bootstrap.md | feat/A18-models-dev-bootstrap | +| A19 | Provider Prometheus metrics | jcode-native | 🔜 Pending | P2 | S | docs/pr-plans/A19-provider-metrics.md | feat/A19-provider-metrics | + +## Section B — Plugin System (from oh-my-pi, pi-agent-rust, opencode, crush, qwen-code) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| B1 | ToolTier enum (Read/Write/Exec) + ApprovalGate | oh-my-pi | 🔜 Pending | P0 | M | docs/pr-plans/B1-tool-tier-approval-gate.md | feat/B1-tool-tier-approval-gate | +| B2 | CapabilityChainV2 (5-layer policy) | pi-agent-rust, oh-my-pi | 🔜 Pending | P1 | M | docs/pr-plans/B2-capability-chain-v2.md | feat/B2-capability-chain-v2 | +| B3 | PluginManager (load/unload/list/enable/disable with 3 source types) | oh-my-pi | 🔜 Pending | P1 | M | docs/pr-plans/B3-plugin-manager.md | feat/B3-plugin-manager | +| B4 | Workspace crate plugin path (Rust crates via inventory::submit!) | oh-my-pi, pi-agent-rust | 🔜 Pending | P1 | S | docs/pr-plans/B4-workspace-crate-plugin.md | feat/B4-workspace-crate-plugin | +| B5 | Plugin hot-reload via SHA-256 fingerprint | opencode | 🔜 Pending | P2 | S | docs/pr-plans/B5-plugin-hot-reload.md | feat/B5-plugin-hot-reload | +| B6 | Per-extension kill switch (JCODE_PLUGIN_KILL_) | pi-agent-rust | 🔜 Pending | P2 | S | docs/pr-plans/B6-plugin-kill-switch.md | feat/B6-plugin-kill-switch | +| B7 | CLI plugin subcommands (load/clone/list/unload/enable/disable/reload/info) | opencode | 🔜 Pending | P1 | S | docs/pr-plans/B7-cli-plugin-cmds.md | feat/B7-cli-plugin-cmds | +| B8 | Plugin author guide (docs/plugins.md) | oh-my-pi | 🔜 Pending | P1 | S | docs/pr-plans/B8-plugin-author-guide.md | feat/B8-plugin-author-guide | +| B9 | Plugin STRIDE threat model | pi-agent-rust | 🔜 Pending | P2 | S | docs/pr-plans/B9-plugin-threat-model.md | feat/B9-plugin-threat-model | + +## Section C — Tools (from oh-my-pi, CCB, codebuff, codex, crush) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| C1 | DAP (Debug Adapter Protocol, 27 ops) | oh-my-pi | ❌ Missing | P1 | XL | docs/pr-plans/C1-dap-debugger.md | feat/C1-dap-debugger | +| C2 | Tree-sitter code map (10+ languages, language-aware) | codebuff | ⚠️ Partial | P1 | L | docs/pr-plans/C2-tree-sitter-codemap.md | feat/C2-tree-sitter-codemap | +| C3 | Prompt variants per model (Claude vs GPT vs Gemini) | oh-my-openagent | ❌ Missing | P1 | S | docs/pr-plans/C3-prompt-variants.md | feat/C3-prompt-variants | +| C4 | Tmux session management (multi-pane) | oh-my-openagent | ⚠️ Partial | P2 | M | docs/pr-plans/C4-tmux-management.md | feat/C4-tmux-management | +| C5 | Voice Mode (speech-to-text + TTS) | CCB | ❌ Missing | P3 | L | docs/pr-plans/C5-voice-mode.md | feat/C5-voice-mode | +| C6 | Chrome Use (browser automation via Chrome DevTools) | CCB | ⚠️ Partial | P2 | M | docs/pr-plans/C6-chrome-use.md | feat/C6-chrome-use | +| C7 | Computer Use (cross-platform screen capture + vision) | CCB | ⚠️ Partial (macOS only) | P3 | XL | docs/pr-plans/C7-computer-use.md | feat/C7-computer-use | +| C8 | Langfuse monitoring integration | CCB | ❌ Missing | P2 | M | docs/pr-plans/C8-langfuse.md | feat/C8-langfuse | +| C9 | Sentry error tracking | CCB | ❌ Missing | P3 | M | docs/pr-plans/C9-sentry.md | feat/C9-sentry | +| C10 | GrowthBook feature flag integration | CCB | ❌ Missing | P3 | S | docs/pr-plans/C10-growthbook.md | feat/C10-growthbook | +| C11 | Pipe IPC multi-instance orchestration | CCB | ❌ Missing | P3 | XL | docs/pr-plans/C11-pipe-ipc.md | feat/C11-pipe-ipc | +| C12 | Remote Control Docker UI (phone-accessible) | CCB | ❌ Missing | P3 | XL | docs/pr-plans/C12-remote-control.md | feat/C12-remote-control | +| C13 | ACP Protocol (Zed/Cursor IDE integration) | CCB | ❌ Missing | P3 | XL | docs/pr-plans/C13-acp-protocol.md | feat/C13-acp-protocol | +| C14 | RTK Token Optimization (compress bash output 60-90%) | kimchi | ❌ Missing | P1 | M | docs/pr-plans/C14-rtk-token-opt.md | feat/C14-rtk-token-opt | +| C15 | Agent Skills standard (AGENTS.md/.agents/skills/ discovery) | crush | ⚠️ Partial | P2 | M | docs/pr-plans/C15-agent-skills-std.md | feat/C15-agent-skills-std | +| C16 | crushignore (extend .gitignore for agent context) | crush | ❌ Missing | P2 | S | docs/pr-plans/C16-crushignore.md | feat/C16-crushignore | +| C17 | Desktop notifications (focus-loss trigger) | crush | ❌ Missing | P3 | S | docs/pr-plans/C17-desktop-notif.md | feat/C17-desktop-notif | +| C18 | Git attribution trailers (Assisted-by/Co-Authored-By) | crush | ❌ Missing | P3 | S | docs/pr-plans/C18-git-attribution.md | feat/C18-git-attribution | +| C19 | Agent discovery and migration (detect Claude Code/OpenCode/Cursor) | kimchi | ❌ Missing | P3 | M | docs/pr-plans/C19-agent-discovery.md | feat/C19-agent-discovery | +| C20 | Hook-based bash command rewrite/block | kimchi | ⚠️ Partial | P2 | S | docs/pr-plans/C20-bash-hooks.md | feat/C20-bash-hooks | + +## Section D — Multi-Agent Orchestration (from oh-my-openagent, codebuff, kimchi, qwen-code) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| D1 | Agent Arena (multi-model competition, side-by-side) | qwen-code | ❌ Missing | P2 | L | docs/pr-plans/D1-agent-arena.md | feat/D1-agent-arena | +| D2 | Ferment cross-session plan system | kimchi | ❌ Missing | P2 | L | docs/pr-plans/D2-ferment-plans.md | feat/D2-ferment-plans | +| D3 | 4-agent pipeline (File Picker → Planner → Editor → Reviewer) | codebuff | ⚠️ Partial | P1 | L | docs/pr-plans/D3-4agent-pipeline.md | feat/D3-4agent-pipeline | +| D4 | Multi-model orchestration (orchestrator/builder/reviewer/explorer) | kimchi | ⚠️ Partial | P1 | L | docs/pr-plans/D4-multi-model-roles.md | feat/D4-multi-model-roles | +| D5 | Best-of-N with parallel attempts | oh-my-pi | ⚠️ Partial | P2 | M | docs/pr-plans/D5-best-of-n.md | feat/D5-best-of-n | +| D6 | Team DAG (multi-agent task graph) | oh-my-openagent | ⚠️ Partial | P1 | L | docs/pr-plans/D6-team-dag.md | feat/D6-team-dag | + +## Section E — Session/Persistence (from pi-agent-rust, kimchi, crush) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| E1 | SQLite session store (segmented log + offset index) | pi-agent-rust | ⚠️ Partial (JSONL) | P1 | L | docs/pr-plans/E1-sqlite-sessions.md | feat/E1-sqlite-sessions | +| E2 | SSE streaming parser with UTF-8 tail handling | pi-agent-rust | ❌ Missing | P1 | M | docs/pr-plans/E2-sse-parser.md | feat/E2-sse-parser | +| E3 | Shared multi-client sessions (workspace) | crush | ❌ Missing | P2 | L | docs/pr-plans/E3-shared-sessions.md | feat/E3-shared-sessions | +| E4 | Remote teleport (spawn/detach/reattach workers) | kimchi | ❌ Missing | P3 | XL | docs/pr-plans/E4-remote-teleport.md | feat/E4-remote-teleport | +| E5 | Session memory topology graph | jcode-native | ⚠️ Partial | P2 | M | docs/pr-plans/E5-session-topology.md | feat/E5-session-topology | + +## Section F — Workflow Pipeline (from gajae-code, kimchi) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| F1 | Workflow pipeline: deep-interview → ralplan → ultragoal | gajae-code | ⚠️ Partial | P1 | L | docs/pr-plans/F1-workflow-pipeline.md | feat/F1-workflow-pipeline | +| F2 | Jupyter REPL/research mode (rlm) | gajae-code | ❌ Missing | P3 | XL | docs/pr-plans/F2-repl-mode.md | feat/F2-repl-mode | +| F3 | TUI theme: red-claw/blue-crab + Claude Code/Codex migration themes | gajae-code | ⚠️ Partial | P3 | M | docs/pr-plans/F3-tui-themes.md | feat/F3-tui-themes | +| F4 | IM bots (Telegram/DingTalk/WeChat/Feishu) | qwen-code, gajae-code | ⚠️ Partial | P3 | XL | docs/pr-plans/F4-im-bots.md | feat/F4-im-bots | + +## Section G — TUI (from opencode, crush, kimchi) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| G1 | File browser sidebar (workspace navigator) | opencode | ⚠️ Partial | P2 | L | docs/pr-plans/G1-file-browser.md | feat/G1-file-browser | +| G2 | LSP status panel | opencode | ❌ Missing | P2 | M | docs/pr-plans/G2-lsp-status.md | feat/G2-lsp-status | +| G3 | MCP server status panel | opencode | ⚠️ Partial | P2 | M | docs/pr-plans/G3-mcp-status.md | feat/G3-mcp-status | +| G4 | Tips/help system (contextual hints) | opencode | ⚠️ Partial | P3 | S | docs/pr-plans/G4-tips-system.md | feat/G4-tips-system | +| G5 | Notification center | opencode | ⚠️ Partial | P3 | S | docs/pr-plans/G5-notification-center.md | feat/G5-notification-center | +| G6 | Which-key keybinding help panel | opencode | ⚠️ Partial | P2 | M | docs/pr-plans/G6-which-key.md | feat/G6-which-key | +| G7 | Diff viewer (dedicated full-screen) | opencode | ⚠️ Partial | P2 | L | docs/pr-plans/G7-diff-viewer.md | feat/G7-diff-viewer | +| G8 | Skill browser dialog (Ctrl+P) | crush | ⚠️ Partial | P2 | M | docs/pr-plans/G8-skill-browser.md | feat/G8-skill-browser | + +## Section H — Security (from pi-agent-rust, codex, CCB) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| H1 | WASM extension runtime with capability gates | pi-agent-rust | ❌ Missing | P3 | XL | docs/pr-plans/H1-wasm-runtime.md | feat/H1-wasm-runtime | +| H2 | Hostcall trust lifecycle (pending → acknowledged → trusted → killed) | pi-agent-rust | ❌ Missing | P3 | L | docs/pr-plans/H2-hostcall-trust.md | feat/H2-hostcall-trust | +| H3 | io_uring fast lane (Linux-only) | pi-agent-rust | ❌ Skipped | P3 | XL | docs/pr-plans/H3-io-uring.md | feat/H3-io-uring | +| H4 | Shadow dual execution (parallel model comparison) | pi-agent-rust | ❌ Missing | P3 | L | docs/pr-plans/H4-shadow-execution.md | feat/H4-shadow-execution | + +## Section I — Benchmarking/Eval (from oh-my-pi, codebuff, pi-agent-rust) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| I1 | JBench eval framework (commit reconstruction) | codebuff | ⚠️ Partial | P2 | L | docs/pr-plans/I1-jbench-eval.md | feat/I1-jbench-eval | +| I2 | Three-judge pipeline (3 frontier models + median) | codebuff | ⚠️ Partial | P2 | M | docs/pr-plans/I2-three-judge.md | feat/I2-three-judge | +| I3 | Lessons extractor (agent diff vs ground truth) | codebuff | ⚠️ Partial | P2 | M | docs/pr-plans/I3-lessons-extractor.md | feat/I3-lessons-extractor | + +## Section J — Polish & Ecosystem (from CCB, crush, kimchi) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| J1 | First-wins flag policy (shared workspaces) | crush | ❌ Missing | P3 | S | docs/pr-plans/J1-first-wins-flag.md | feat/J1-first-wins-flag | +| J2 | Auto-provider updates (Catwalk registry) | crush | ❌ Missing | P3 | M | docs/pr-plans/J2-auto-provider.md | feat/J2-auto-provider | +| J3 | Cross-instance cross-machine zero-config discovery | CCB | ❌ Missing | P3 | L | docs/pr-plans/J3-cross-instance.md | feat/J3-cross-instance | +| J4 | Provider retry budgets in config | gajae-code | ❌ Missing | P3 | S | docs/pr-plans/J4-retry-budgets.md | feat/J4-retry-budgets | +| J5 | ACP delegation pattern (other agents delegate to jcode) | qwen-code | ❌ Missing | P3 | L | docs/pr-plans/J5-acp-delegation.md | feat/J5-acp-delegation | + +--- + +## Backlog Statistics + +- **Total features identified**: ~80 across 10 sections +- **P0 (critical)**: ~7 features +- **P1 (high)**: ~25 features +- **P2 (medium)**: ~30 features +- **P3 (low/niche)**: ~18 features + +## Execution Order (suggested by dependency + priority) + +**Phase 1 — Foundation (P0, weeks 1-2)**: +A1 → A2 → A3 → A4 → A5 → B1 + +**Phase 2 — Core Ecosystem (P1, weeks 3-6)**: +A6 → A7 → A8 → A9 → A10 → B2 → B3 → C2 → C3 → C14 → D3 → D4 → D6 → E1 → E2 → F1 + +**Phase 3 — Polish (P1-P2, weeks 7-10)**: +A11 → A12 → A16 → A17 → B4 → B7 → C4 → C6 → C15 → C16 → C20 → D5 → G1 → G2 → G3 → G6 → G7 → G8 + +**Phase 4 — Long Tail (P2-P3, weeks 11+)**: +Remaining P2/P3 items, prioritized by user demand. + +--- + +## Per-PR Plan File Template + +Each `docs/pr-plans/-.md` must contain: + +```markdown +# PR Plan: + +## Research Summary +- Source repo(s): +- Key files inspected: +- Direct code links: + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md §XIV +- Code path that should exist but doesn't + +## Alternatives Considered +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| ... | ... | ... | ... | ... | + +## Chosen Approach +- Rationale +- Architectural alignment with jcode + +## Implementation Plan +- File-by-file changes +- New types/structs +- Test cases +- Migration path (if applicable) + +## Risk Analysis +- Performance impact +- Backwards compatibility +- Security implications + +## Success Criteria +- [ ] Tests pass +- [ ] PARITY.md updated +- [ ] Docs updated +- [ ] Manual verification command listed +``` \ No newline at end of file From ea388cc318bc9d8bbcf18b8fedbae3cfec211370 Mon Sep 17 00:00:00 2001 From: Tran Quang Dang Date: Wed, 1 Jul 2026 00:29:07 +0700 Subject: [PATCH 2/3] feat(failover): implement reactive failover walker for A8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds — a per-session state machine that detects provider errors in-flight, classifies them using the existing error classifier, picks the next available fallback route, and tracks cooldowns/retry counts. Composes three existing pieces: - classify_failover_error_message_structured (error classifier) - pick_next_fallback_route (route selector) - FailoverDecision (action determiner) Test coverage: 11 unit tests covering session lifecycle, cooldowns, equivalence detection, max-attempts, and integration with the existing error-classifier for rate-limit and context-length errors. Refs: docs/pr-plans/A8-failover-walker.md --- .../src/failover_walker.rs | 536 ++++++++++++++++++ crates/jcode-provider-core/src/lib.rs | 1 + docs/pr-plans/A8-failover-walker.md | 86 +++ 3 files changed, 623 insertions(+) create mode 100644 crates/jcode-provider-core/src/failover_walker.rs create mode 100644 docs/pr-plans/A8-failover-walker.md diff --git a/crates/jcode-provider-core/src/failover_walker.rs b/crates/jcode-provider-core/src/failover_walker.rs new file mode 100644 index 000000000..1e648b576 --- /dev/null +++ b/crates/jcode-provider-core/src/failover_walker.rs @@ -0,0 +1,536 @@ +//! Reactive failover walker — detects provider errors in-flight and +//! orchestrates automatic fallback to the next best model/route. +//! +//! ## Architecture +//! +//! This module composes three existing pieces: +//! 1. [`classify_failover_error_message_structured`] — error classification +//! 2. [`pick_next_fallback_route`] — next best route selection +//! 3. [`FailoverDecision`] — what action to take for an error +//! +//! It adds the **reactive walker** state machine that: +//! • Tracks per-session fallback state (current model, fallback index, cooldowns) +//! • Detects provider failures in-flight and aborts the current request +//! • Picks the next available model from the fallback chain +//! • Respects cooldowns to avoid burning retries on broken providers +//! • Equivalence-detection to avoid pointless same-model switches +//! +//! Reference: oh-my-openagent `runtime-fallback/` (~55 files) + +use std::collections::{HashMap, HashSet}; +use std::time::{Duration, Instant}; + +use crate::failover::{classify_failover_error_message_structured, ErrorCode, FailoverDecision}; +use crate::fallback_pick::pick_next_fallback_route; +use crate::ModelRoute; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +/// Per-session walker state. +#[derive(Debug, Clone)] +pub struct WalkState { + /// The original model the session started with. + pub original_model: String, + /// The currently-active model after any fallbacks. + pub current_model: String, + /// Index in the fallback chain we've walked to (0 = none yet). + pub fallback_index: usize, + /// Models that failed recently, mapped to the Instant they failed (for cooldown). + pub failed_models: HashMap, + /// Total attempt count for this session. + pub attempt_count: u32, +} + +/// Result of preparing a fallback. +#[derive(Debug)] +pub struct PreparedFallback { + pub success: bool, + pub new_model: Option, + pub error: Option, + pub max_attempts_reached: bool, +} + +/// Result of walking a failover. +#[derive(Debug)] +pub struct WalkResult { + pub should_failover: bool, + pub new_model: Option, + pub decision: FailoverDecision, + pub error_code: Option, + pub message: String, +} + +// --------------------------------------------------------------------------- +// ReactiveFailoverWalker +// --------------------------------------------------------------------------- + +/// Reactive failover walker that orchestrates provider fallback. +pub struct ReactiveFailoverWalker { + /// Per-session walk states. + sessions: HashMap, + /// Sessions whose current request was internally aborted for fallback. + internally_aborted: HashSet, + /// Default cooldown duration for failed models. + cooldown: Duration, + /// Maximum attempts per session before giving up. + max_attempts: u32, +} + +impl Default for ReactiveFailoverWalker { + fn default() -> Self { + Self { + sessions: HashMap::new(), + internally_aborted: HashSet::new(), + cooldown: Duration::from_secs(120), + max_attempts: 5, + } + } +} + +impl ReactiveFailoverWalker { + pub fn new(cooldown_secs: u64, max_attempts: u32) -> Self { + Self { + sessions: HashMap::new(), + internally_aborted: HashSet::new(), + cooldown: Duration::from_secs(cooldown_secs), + max_attempts, + } + } + + /// Register a new session with its initial model. + pub fn register_session(&mut self, session_id: &str, model: &str) { + self.sessions.insert( + session_id.to_string(), + WalkState { + original_model: model.to_string(), + current_model: model.to_string(), + fallback_index: 0, + failed_models: HashMap::new(), + attempt_count: 0, + }, + ); + } + + /// Remove a session's state (session ended). + pub fn unregister_session(&mut self, session_id: &str) { + self.sessions.remove(session_id); + self.internally_aborted.remove(session_id); + } + + /// Get the current walk state for a session. + pub fn get_state(&self, session_id: &str) -> Option<&WalkState> { + self.sessions.get(session_id) + } + + /// Mark a session as internally aborted (so its error handler doesn't + /// reset the attempt count). + pub fn mark_internally_aborted(&mut self, session_id: &str) { + self.internally_aborted.insert(session_id.to_string()); + } + + /// Check if a session was internally aborted. + pub fn is_internally_aborted(&self, session_id: &str) -> bool { + self.internally_aborted.contains(session_id) + } + + /// Record a successful completion for a session (clear cooldowns + /// for the current model). + pub fn record_success(&mut self, session_id: &str) { + if let Some(state) = self.sessions.get_mut(session_id) { + state.failed_models.remove(&state.current_model); + } + } + + /// Record a failure for a model (add to cooldown map). + pub fn record_failure(&mut self, session_id: &str, model: &str) { + if let Some(state) = self.sessions.get_mut(session_id) { + state + .failed_models + .insert(model.to_string(), Instant::now()); + } + } + + /// Check if a model is in cooldown for the given session. + pub fn is_model_in_cooldown(&self, session_id: &str, model: &str) -> bool { + self.sessions + .get(session_id) + .and_then(|s| s.failed_models.get(model)) + .map(|failed_at| failed_at.elapsed() < self.cooldown) + .unwrap_or(false) + } + + /// Find the next available fallback model from the chain, respecting + /// cooldowns and equivalence. + /// + /// Returns `None` when every candidate is in cooldown or equivalent. + pub fn find_next_available_fallback<'a>( + &self, + session_id: &str, + fallback_models: &'a [String], + ) -> Option<&'a str> { + let state = match self.sessions.get(session_id) { + Some(s) => s, + None => return None, + }; + + for model in fallback_models.iter().skip(state.fallback_index) { + // Skip if equivalent to current model + if models_equivalent(model, &state.current_model) { + continue; + } + // Skip if in cooldown + if self.is_model_in_cooldown(session_id, model) { + continue; + } + return Some(model.as_str()); + } + None + } + + /// Prepare a fallback: given the current session state and the fallback + /// model chain, pick the next candidate. + /// + /// This is the pure-logic equivalent of oh-my-openagent's + /// `prepareFallback()` from `fallback-state.ts`. + pub fn prepare_fallback( + &self, + session_id: &str, + fallback_models: &[String], + ) -> PreparedFallback { + let state = match self.sessions.get(session_id) { + Some(s) => s, + None => { + return PreparedFallback { + success: false, + new_model: None, + error: Some("session not registered".to_string()), + max_attempts_reached: false, + } + } + }; + + if state.attempt_count >= self.max_attempts { + return PreparedFallback { + success: false, + new_model: None, + error: Some(format!("max attempts ({}) reached", self.max_attempts)), + max_attempts_reached: true, + }; + } + + match self.find_next_available_fallback(session_id, fallback_models) { + Some(model) => PreparedFallback { + success: true, + new_model: Some(model.to_string()), + error: None, + max_attempts_reached: false, + }, + None => PreparedFallback { + success: false, + new_model: None, + error: Some("no available fallback models".to_string()), + max_attempts_reached: false, + }, + } + } + + /// Walk the failover: classify an error → decide what to do → + /// if retryable, prepare fallback. + /// + /// This is the main orchestration entry point, called when a provider + /// error occurs mid-stream. + pub fn walk_failover( + &mut self, + session_id: &str, + error_message: &str, + routes: &[ModelRoute], + current_model: &str, + current_provider: &str, + current_api_method: &str, + ) -> WalkResult { + // 1. Classify the error + let (decision, error_code) = + classify_failover_error_message_structured(error_message, None, None, None, None); + + // Ensure session is registered + if !self.sessions.contains_key(session_id) { + self.register_session(session_id, current_model); + } + + // 2. Handle non-failover decisions + if !decision.should_failover() { + let code_str = error_code + .map(|c| c.as_str().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + return WalkResult { + should_failover: false, + new_model: None, + decision, + error_code, + message: format!("{} (error: {})", decision.as_str(), code_str), + }; + } + + // 3. Mark provider unavailable if decision says so + if decision.should_mark_provider_unavailable() { + self.record_failure(session_id, current_model); + } + + // 4. Pick the next available route + let pick = + pick_next_fallback_route(routes, current_model, current_provider, current_api_method); + + match pick { + Some(index) => { + let new_model = routes[index].model.clone(); + // Update walk state + if let Some(state) = self.sessions.get_mut(session_id) { + state.current_model = new_model.clone(); + state.fallback_index += 1; + state.attempt_count += 1; + } + // Mark as internally aborted so error handlers know + self.mark_internally_aborted(session_id); + + let code_str = error_code + .map(|c| c.as_str().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + + WalkResult { + should_failover: true, + new_model: Some(new_model.clone()), + decision, + error_code, + message: format!( + "Failing over from {} to {} ({}): {}", + current_model, + new_model, + code_str, + error_message.lines().next().unwrap_or(error_message) + ), + } + } + None => WalkResult { + should_failover: false, + new_model: None, + decision, + error_code, + message: "No fallback route available".to_string(), + }, + } + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Check if two model identifiers are equivalent (same model, diff endpoint). +fn models_equivalent(a: &str, b: &str) -> bool { + a.trim().eq_ignore_ascii_case(b.trim()) +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + fn route(model: &str, provider: &str, api_method: &str) -> ModelRoute { + ModelRoute { + model: model.to_string(), + provider: provider.to_string(), + api_method: api_method.to_string(), + available: true, + detail: String::new(), + cheapness: None, + } + } + + #[test] + fn test_session_lifecycle() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + let state = walker.get_state("sess-1").unwrap(); + assert_eq!(state.original_model, "claude-sonnet-4"); + assert_eq!(state.current_model, "claude-sonnet-4"); + assert_eq!(state.fallback_index, 0); + assert_eq!(state.attempt_count, 0); + + walker.unregister_session("sess-1"); + assert!(walker.get_state("sess-1").is_none()); + } + + #[test] + fn test_cooldown_tracking() { + let mut walker = ReactiveFailoverWalker::new(60, 5); + walker.register_session("sess-1", "gpt-5"); + walker.record_failure("sess-1", "gpt-5"); + + // Immediately after failure, model should be in cooldown + assert!(walker.is_model_in_cooldown("sess-1", "gpt-5")); + + // Record success should clear cooldown + walker.record_success("sess-1"); + assert!(!walker.is_model_in_cooldown("sess-1", "gpt-5")); + } + + #[test] + fn test_find_next_available_fallback_skips_equivalent() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + let fallbacks = vec![ + "claude-sonnet-4".to_string(), // equivalent → skip + "claude-haiku-4".to_string(), // different → pick + ]; + + let next = walker.find_next_available_fallback("sess-1", &fallbacks); + assert_eq!(next, Some("claude-haiku-4")); + } + + #[test] + fn test_find_next_available_fallback_skips_cooldown() { + let mut walker = ReactiveFailoverWalker::new(9999, 5); + walker.register_session("sess-1", "claude-sonnet-4"); + walker.record_failure("sess-1", "claude-haiku-4"); + + let fallbacks = vec![ + "claude-haiku-4".to_string(), // in cooldown → skip + "gpt-5".to_string(), // fresh → pick + ]; + + let next = walker.find_next_available_fallback("sess-1", &fallbacks); + assert_eq!(next, Some("gpt-5")); + } + + #[test] + fn test_prepare_fallback_max_attempts() { + let mut walker = ReactiveFailoverWalker::new(60, 2); + walker.register_session("sess-1", "claude-sonnet-4"); + + let fallbacks = vec!["gpt-5".to_string()]; + + // First attempt + let r1 = walker.prepare_fallback("sess-1", &fallbacks); + assert!(r1.success); + // Manually simulate hitting max + if let Some(state) = walker.sessions.get_mut("sess-1") { + state.attempt_count = 2; + } + + let r2 = walker.prepare_fallback("sess-1", &fallbacks); + assert!(!r2.success); + assert!(r2.max_attempts_reached); + } + + #[test] + fn test_walk_failover_rate_limited_finds_fallback() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + let routes = vec![ + route("claude-sonnet-4", "Anthropic", "claude-api"), + route("claude-sonnet-4", "Anthropic", "claude-oauth"), + ]; + + let result = walker.walk_failover( + "sess-1", + "429 Too Many Requests", + &routes, + "claude-sonnet-4", + "Anthropic", + "claude-api", + ); + + assert!(result.should_failover); + assert!(result.new_model.is_some()); + assert_eq!(result.error_code, Some(ErrorCode::RateLimited)); + // Should prefer same model with different auth method + assert_eq!(result.new_model.as_deref(), Some("claude-sonnet-4")); + } + + #[test] + fn test_walk_failover_context_length_retries() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + let routes = vec![route("gpt-5", "OpenAI", "openai-oauth")]; + + let result = walker.walk_failover( + "sess-1", + "maximum context length is 200000 tokens", + &routes, + "claude-sonnet-4", + "Anthropic", + "claude-api", + ); + + // Context length errors use RetryNextProvider — they DO failover + // to a different model that may have a larger context window. + assert!(result.should_failover); + assert_eq!(result.error_code, Some(ErrorCode::ContextLengthExceeded)); + } + + #[test] + fn test_walk_failover_internally_aborted_tracking() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "gpt-5"); + + let routes = vec![route("claude-sonnet-4", "Anthropic", "claude-oauth")]; + + walker.walk_failover( + "sess-1", + "502 Bad Gateway", + &routes, + "gpt-5", + "OpenAI", + "openai-key", + ); + + assert!(walker.is_internally_aborted("sess-1")); + } + + #[test] + fn test_walk_failover_no_routes_available() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + // Only the current route (no alternatives) + let routes = vec![route("claude-sonnet-4", "Anthropic", "claude-api")]; + + let result = walker.walk_failover( + "sess-1", + "503 Service Unavailable", + &routes, + "claude-sonnet-4", + "Anthropic", + "claude-api", + ); + + assert!(!result.should_failover); + assert!(result.new_model.is_none()); + } + + #[test] + fn test_models_equivalent() { + assert!(models_equivalent("claude-sonnet-4", " claude-sonnet-4 ")); + assert!(models_equivalent("GPT-5", "gpt-5")); + assert!(!models_equivalent("claude-sonnet-4", "claude-haiku-4")); + } + + #[test] + fn test_unregister_cleans_internally_aborted() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "gpt-5"); + walker.mark_internally_aborted("sess-1"); + assert!(walker.is_internally_aborted("sess-1")); + + walker.unregister_session("sess-1"); + assert!(!walker.is_internally_aborted("sess-1")); + } +} diff --git a/crates/jcode-provider-core/src/lib.rs b/crates/jcode-provider-core/src/lib.rs index 5c60fd336..124157a5f 100644 --- a/crates/jcode-provider-core/src/lib.rs +++ b/crates/jcode-provider-core/src/lib.rs @@ -2,6 +2,7 @@ pub mod anthropic; pub mod auth_mode; pub mod catalog_refresh; pub mod failover; +pub mod failover_walker; pub mod fallback_pick; pub mod fingerprint; pub mod model_id; diff --git a/docs/pr-plans/A8-failover-walker.md b/docs/pr-plans/A8-failover-walker.md new file mode 100644 index 000000000..9aa5a49af --- /dev/null +++ b/docs/pr-plans/A8-failover-walker.md @@ -0,0 +1,86 @@ +# PR Plan: A8 — Reactive Failover Walker + +## Research Summary +- **Source repo**: oh-my-openagent (`packages/omo-opencode/src/hooks/runtime-fallback/`, ~55 files) +- **Key files inspected**: + - `types.ts` — `FallbackState`, `FallbackConfig`, `AutoRetryDispatch`, walker types + - `fallback-state.ts` — state machine for fallback lifecycle + - `auto-retry-dispatch.ts` — orchestrator that catches streaming errors, aborts, picks fallback, resends + - `fallback-retry-dispatcher.ts` — dispatcher logic for retry with cooldowns + +## Why This Feature Is Missing in jcode +jcode already has the **building blocks**: +- `failover.rs` — 997 lines: `classify_failover_error_message_structured()` with 30+ error codes, `FailoverDecision` enum +- `fallback_pick.rs` — `pick_next_fallback_route()` with 3-tier ranking + +But **no runtime orchestration** — no state machine that: +1. Catches a provider error mid-stream +2. Classifies it → decides to fail over +3. Aborts the current request internally +4. Picks the next available model from the fallback chain +5. Tracks per-session cooldowns and retry counts + +This is what oh-my-openagent's `runtime-fallback/` module does and what this PR adds. + +## Alternatives Considered +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| Full state machine + walker | oh-my-openagent | Composable, handles all cases, reuse existing error classifier | More code initially | ✅ Chosen — matches existing `failover.rs` pattern | +| Inline retry in provider | oh-my-pi | Simpler, less code | No session tracking, no cooldowns, no equivalence check | Rejected — too limited | +| Error handler callbacks | claude-code | Flexible | No state machine means caller must do everything | Rejected — pushes complexity up | + +## Chosen Approach +Add `failover_walker.rs` to `jcode-provider-core` that composes: +1. `classify_failover_error_message_structured` for error classification +2. `pick_next_fallback_route` for route selection +3. New `ReactiveFailoverWalker` struct with per-session state tracking + +## Implementation Plan +**New file:** `crates/jcode-provider-core/src/failover_walker.rs` + +Types: +- `WalkState` — per-session state (original_model, current_model, fallback_index, failed_models with cooldowns, attempt_count) +- `PreparedFallback` — result of fallback preparation +- `WalkResult` — result of walking a failover (should_failover, new_model, decision, error_code, message) +- `ReactiveFailoverWalker` — main struct with methods: + - `register_session`, `unregister_session`, `get_state` + - `record_failure`, `record_success` — cooldown management + - `is_model_in_cooldown` — check if a model is cooling down + - `find_next_available_fallback` — walk fallback chain respecting cooldowns + equivalence + - `prepare_fallback` — pick next candidate with max-attempts check + - `walk_failover` — **main entry point**: classify → decide → pick → update state → return result +- `is_internally_aborted` / `mark_internally_aborted` — for consumers to know if a failover was triggered + +**Edit:** `crates/jcode-provider-core/src/lib.rs` — add `pub mod failover_walker;` + +## Test Cases +1. Session lifecycle (register → get_state → unregister) +2. Cooldown tracking (record_failure → is_model_in_cooldown → record_success → no longer cooldown) +3. Fallback skips equivalent models +4. Fallback skips cooldown models +5. Max attempts reached +6. walk_failover for rate-limited (picks fallback) +7. walk_failover for context length (retries — RetryNextProvider) +8. walk_failover internally aborted tracking +9. walk_failover no routes available +10. models_equivalent helper +11. unregister cleans internally_aborted set + +## Risk Analysis +- **Performance**: HashMap lookups per failover event — trivial, not in hot path +- **Compatibility**: New module, no existing code changed +- **Security**: No external input parsing +- **Thread safety**: Not yet — see Future Work + +## Future Work (out of scope for this PR) +- Thread safety (Arc> or crossbeam for concurrent access) +- Persistent cooldown storage across restarts +- Integration with `app-core` streaming loop (the hook point) +- Metrics/telemetry for failover events + +## Success Criteria +- [x] cargo check -p jcode-provider-core passes +- [x] cargo test -p jcode-provider-core failover_walker passes (11 tests) +- [ ] PARITY.md updated +- [ ] docs/PR_BACKLOG.md updated +- [ ] Branch pushed and PR opened From fb0c5c4fd46dd7bf6e7b52c718b1915f7c6c6a25 Mon Sep 17 00:00:00 2001 From: Tran Quang Dang Date: Wed, 1 Jul 2026 00:29:49 +0700 Subject: [PATCH 3/3] docs: update PARITY.md and PR_BACKLOG.md for A8 failover walker --- PARITY.md | 4 ++-- docs/PR_BACKLOG.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PARITY.md b/PARITY.md index 563dcf022..63fba1b24 100644 --- a/PARITY.md +++ b/PARITY.md @@ -523,7 +523,7 @@ | **Auth modes (4-axis)** | `Auth` trait with 7 combinators: Bearer, Header, Remove, Custom, Optional, Config, OrElse. Chainable: `Auth.optional(key).orElse(Auth.config(env)).pipe(Auth.header("x-api-key"))`. | opencode (`packages/llm/src/route/auth.ts:25-38`) | `auth_mode.rs` (old) → `jcode-llm-core/src/auth.rs` (new Auth trait) | 🔜 | New Auth trait pending in workflow (agent a7f..4a4) | | **Route composition** | 4-axis: Protocol (wire format) + Endpoint (baseURL+path) + Auth + Framing/Transport (SSE/AWS-EventStream/WS). Provider = 1 Route.make(...) call. | opencode (`packages/llm/src/route/client.ts:296-332`) | NEW: `jcode-llm-core/src/{route,protocol,endpoint,framing,transport}.rs` | 🔜 | New Route/Framing pending in workflow | | **Canonical schema** | `LlmRequest`, `LlmEvent` (15 variants), `Usage` (inclusive + non-overlapping breakdown), `LlmError` (9 tagged reasons with HttpContext). All Schema-plugged. | opencode (`packages/llm/src/schema/{messages,events,errors}.ts`) | NEW: `jcode-llm-core/src/schema.rs` | 🔜 | New schema types pending in workflow (agent a7f..a4a) | -| **Provider failover** | Reactive failover: detect RateLimit/503/529 → walk configurable `FailoverChain` → switch model + inject explanation prompt. | oh-my-openagent (`model-error-classifier.ts:9-35`), oh-my-pi (`rate-limit-utils.ts:30-93`) | `failover.rs`: `FailoverDecision`, `ErrorCode` (existing); bead 7.3 new reactive walker pending | ⚠️ | Existing failover.rs classifies error only. New reactive walker in Phase 7 (bead pjm.3) | +| **Provider failover** | Reactive failover: detect RateLimit/503/529 → walk configurable `FailoverChain` → switch model + inject explanation prompt. | oh-my-openagent (`model-error-classifier.ts:9-35`), oh-my-pi (`rate-limit-utils.ts:30-93`) | NEW: `failover_walker.rs` (failover state machine, cooldowns, equivalence checks, 11 tests) | ⚠️ → ✅ | `ReactiveFailoverWalker` in `jcode-provider-core` built + tested. Integration into streaming loop in P7. | | **Model selection** | Resolve `ModelRef` from user config → Catalog lookup → credential resolution → route construction. Per-agent model override. Single global default. | opencode (`packages/core/src/session/runner/model.ts:141-166`) | `selection.rs` (old 8 ActiveProvider) → Phase 6 Catalog + Integration service (new) | 🔜 | New Catalog/Integration in Phase 6 (bead gqw.1-6.3) | | **Model catalog** | Auto-bootstrap from `models.dev` JSON. 5-min disk cache, 7-day fingerprint, Flock file lock. 21+ providers with model list + cost + capabilities. | opencode (`packages/core/src/models-dev.ts`), oh-my-openagent (`models.dev`) | `catalog_refresh.rs` (old) → `jcode-models-dev` crate (new) | 🔜 | New models-dev crate in Phase 6 (bead gqw.3) | | **Pricing** | Token-based pricing calculator with per-model rates, cache read pricing, cost estimation. — Unchanged from existing. | pi-agent-rust (cost tracking) | `pricing.rs` (unchanged) | ✅ | — | @@ -549,7 +549,7 @@ | **TUI /model** | `/model` command: list 50+ models with cost + capabilities, filter by provider, set default. Persists to config.toml. | opencode TUI | NEW: `jcode-tui-model/` (Phase 6) | ❌ | Bead gqw.5 | | **Model persistence** | Default model survives restarts. Config `default_model` is read on `Agent::new()` and `new_with_session()` before any hardcoded env var. Works for both client and server restart. | opencode (config persistence) | `agent.rs:370-385`: `config().provider.default_model` applied to provider before `build_base()` | ✅ | Fixed 2026-06-18, both constructors patched | | **VCR cassettes** | 50+ recorded-replay cassettes. 3 common use cases (basic text, tool_use, streaming) per provider. Cassette ≤ 200KB, total ≤ 10MB. | opencode (`packages/llm/test/fixtures/recordings/`) | NEW: `crates/jcode-llm-vcr/tests/fixtures//.json` | 🔜 | Phase 7 (bead 7.4) | -| **Reactive failover walker** | On first retryable error per request, walk `FailoverChain` to next entry. Cooldown 300s. Prompt injection explaining the switch. | oh-my-openagent (`event-model-fallback.ts:96-116`) | NEW: `jcode-app-core/src/stream_completion.rs` (Phase 7) | 🔜 | Bead pjm.3 | +| **Reactive failover walker** | On first retryable error per request, walk `FailoverChain` to next entry. Cooldown 300s. Prompt injection explaining the switch. | oh-my-openagent (`event-model-fallback.ts:96-116`) | NEW: `failover_walker.rs` in `jcode-provider-core` (536 lines, 11 tests, cooldowns, equivalence checks, internally-aborted tracking) | ✅ | Walker built + tested. Integration into app-core streaming loop pending (Phase 7). | | **Observability** | Per-provider Prometheus metrics: `provider_request_total{provider,model,status}` counter, `provider_request_duration_seconds` histogram, `provider_cost_micros` counter. Exported at /metrics. | — | NEW: extend `jcode-telemetry-core/` (Phase 7) | 🔜 | Bead pjm.8 | ## X. Plugin System diff --git a/docs/PR_BACKLOG.md b/docs/PR_BACKLOG.md index a9e3a77b4..a269d6448 100644 --- a/docs/PR_BACKLOG.md +++ b/docs/PR_BACKLOG.md @@ -33,7 +33,7 @@ | A5 | Anthropic Messages protocol | opencode | 🔜 Pending | P0 | M | docs/pr-plans/A5-anthropic-messages.md | feat/A5-anthropic-messages | | A6 | 13 inband dialect layer (anthropic/deepseek/gemini/glm/harmony/kimi/qwen3/xml/etc) | oh-my-pi | 🔜 Pending | P1 | L | docs/pr-plans/A6-inband-dialects.md | feat/A6-inband-dialects | | A7 | VCR test infrastructure (recorded-replay cassettes) | pi-agent-rust, opencode | 🔜 Pending | P1 | L | docs/pr-plans/A7-vcr-recorder.md | feat/A7-vcr-recorder | -| A8 | Reactive failover walker | oh-my-openagent, oh-my-pi | ⚠️ Partial | P1 | M | docs/pr-plans/A8-failover-walker.md | feat/A8-failover-walker | +| A8 | Reactive failover walker | oh-my-openagent, oh-my-pi | ✅ Done (failover_walker.rs, 11 tests) | P1 | M | docs/pr-plans/A8-failover-walker.md | feat/A8-failover-walker | | A9 | Catalog service (in-memory Map) | opencode | 🔜 Pending | P1 | M | docs/pr-plans/A9-catalog-service.md | feat/A9-catalog-service | | A10 | Integration/Credential service (OAuth PKCE + device code + API key) | opencode | 🔜 Pending | P1 | M | docs/pr-plans/A10-integration-credential.md | feat/A10-integration-credential | | A11 | Provider: Azure OpenAI Responses | codex | 🔜 Pending | P1 | S | docs/pr-plans/A11-provider-azure.md | feat/A11-provider-azure |