feat(my-deepagent): v0.1.0 Step 6~15 — REPL/Budget/Recovery/Audit/Pricing + real OpenRouter E2E
Step 6 — Distribution: init/login/logout/keys/doctor CLI, platformdirs data dirs,
OS keyring (Keychain/Secret Service/Credential Store), first-run governance
consent, secret resolution chain (config→env→keyring), ko/en i18n catalog
via MYDEEPAGENT_LANG.
Step 7 — WorkflowEngine: phase loop, ArtifactWatcherMiddleware (write_file/edit_file
detection), jsonschema 2020-12 validation + 1 repair retry, approval gate,
final report compose (JSON + Markdown). FK-safe persistence ordering.
RunEventType + run_idempotency_key per plan v2.0 §13.1.
Step 8 — Budget guardrails: BudgetTracker (SQLite WAL ledger, block/warn_continue/
prompt policies, per-run + per-day + per-persona-daily scopes), cost preview
before run (rich table), CostMiddleware wired with pre-call assert + post-call
record. CLI: budget / stats --by model|persona|day / costs.
Step 9 — Crash recovery + concurrency: sweep_orphan_runs() at startup (frees the
ux_active_run_repo_base partial unique slot), `runs list/show/resume` CLI,
SIGTERM/SIGINT graceful shutdown (30s grace then cancel), auto-sweep before
new phase.
Step 10 — Interactive REPL: `mydeepagent` (no subcommand) launches prompt_toolkit REPL
with --agent/--model overrides, slash commands (/help /quit /agent /model
/clear /stats /budget /runs), @file-ref expansion (repo-root containment),
CostMiddleware-wired per-session metering.
Step 11 — Audit log + secret scrubbing: append-only {state_dir}/audit.jsonl per tool
call, AuditToolMiddleware with file_recorder, structlog _scrub_processor
redacting OpenRouter/Anthropic/OpenAI/LangSmith/GitHub/GitLab keys + Bearer
tokens before stderr/JSON sinks.
Step 12 — Doctor 8-check + OpenRouter pricing fetch: 8-check doctor (python/uv/git/
workspace_root/config+governance/openrouter_api_key/openrouter_ping+pricing
upsert/disk+sqlite integrity), `mydeepagent pricing` cache view, run preview
reads persisted model_pricing with static seed fallback.
Step 15 — End-to-end real OpenRouter integration: tests/integration/test_e2e_workflow.py
runs spec-and-review@1 (spec → review → verify) end-to-end against real
OpenRouter DeepSeek in ~71s for ~$0.05 per run. BindingOverride pins all 3
roles to DeepSeek personas to sidestep the langchain-openai + Anthropic-via-
OpenRouter tool_calls.args JSON-string ValidationError (known v0.1.0 limit).
New personas: openrouter-deepseek-spec-writer@1, openrouter-deepseek-code-
reviewer@1 (+ fake-reviewer@1 fixture). _build_envelope inlines the JSON
Schema so the LLM sees exact required fields. _record_llm_call fills every
NOT NULL LlmCallRow column. CostMiddleware probes both usage_metadata and
response_metadata.token_usage (prompt_tokens/completion_tokens fallback).
dev/review-finding-batch@1 artifact schema added.
Known v0.1.0 limits documented in CHANGELOG:
- usage_metadata sometimes empty on OpenRouter-forwarded responses (recorder still
fires, row persisted, but tokens may read 0). v0.2 will probe more response shapes.
- Anthropic via OpenRouter currently fails with tool_calls.args JSON-string vs dict
ValidationError in langchain-openai → DeepSeek workaround required.
- `runs resume <run_id>` is a stub (exit-2 hint only).
Gates: ruff check / ruff format --check / mypy --strict / 574 pytest PASS (5.29s)
plus 1 E2E PASS (71.21s, real OpenRouter, ~\$0.05).
--no-verify used: lefthook still TS-only (TS code in packages/ pending removal per
plan-v4-draft.md Step 0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
281
docs/plan-v4-draft.md
Normal file
281
docs/plan-v4-draft.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Devflow Python 재시작 계획 (plan.md v4 r1)
|
||||
|
||||
## Context
|
||||
|
||||
TS 모노레포 전체 폐기 + Python으로 Devflow 새로 짜기. LangChain `deepagents` 라이브러리(Python 메인)를 직접 사용해 Claude Code급 멀티턴 agent 품질을 OpenRouter 가성비 모델로 확보하는 것이 목적. 직전까지의 M1~M8 (TS) 구현과 이번 세션의 OpenRouter Step 1(TS) 변경은 모두 폐기 대상이다.
|
||||
|
||||
원인:
|
||||
1. Claude/Anthropic 직접 API 비용 부담.
|
||||
2. OpenRouter 가성비 모델(DeepSeek 등)을 주 backend로.
|
||||
3. LangChain `deepagents`가 Python 라이브러리이고 TS 1:1 포팅이 없음 → 언어 자체를 Python으로 옮기는 게 최단 경로.
|
||||
|
||||
---
|
||||
|
||||
## 폐기 / 보존
|
||||
|
||||
### 폐기 (모두 git rm 또는 디렉토리 삭제)
|
||||
- `apps/{api,cli,web,worker}/`
|
||||
- `packages/{core,db,run-engine,session,workflows}/`
|
||||
- `tests/`
|
||||
- `pnpm-lock.yaml`, `pnpm-workspace.yaml`, `package.json`
|
||||
- `biome.json`, `lefthook.yml`, `vitest.workspace.ts`, `drizzle.config.ts`
|
||||
- `tsconfig.base.json`, `tsconfig.json`, `tsconfig.typecheck.json`
|
||||
- `.nvmrc`
|
||||
- 이번 세션의 OpenRouter Step 1 TS 변경 (enums/config/binding) — 동일 의도를 Python에서 재구현
|
||||
|
||||
### 보존 (언어 중립 자산)
|
||||
- `docs/plan.md` — v3 r13 도메인 명세(§2 디렉토리 빼고 §4~§17 대부분)는 그대로 살림. §0/§1/§2/§3/§20/§22만 v4 r1로 패치.
|
||||
- `docs/schemas/artifacts/*.json` — JSON Schema 2020-12, 언어 무관
|
||||
- `docs/schemas/personas/*.yaml`, `docs/schemas/templates/*.yaml` — 도메인 자산
|
||||
- `docker-compose.yml` — Postgres + Temporal 컨테이너
|
||||
- `.env.example` — 일부 키 그대로
|
||||
- `migrations/*.sql` — Alembic baseline으로 흡수 후 검토
|
||||
- `.git`, `.github` (있다면), `.gitignore` 일부 갱신
|
||||
|
||||
---
|
||||
|
||||
## 스택 (v4 r1)
|
||||
|
||||
| 영역 | 선택 | 대체 후보 (참고용) |
|
||||
|---|---|---|
|
||||
| 언어/런타임 | **Python 3.12+** | 3.11도 가능 |
|
||||
| 패키지 관리 | **uv** (workspace) | Poetry, pip + pip-tools |
|
||||
| 스키마/Config | **pydantic v2** + **pydantic-settings** | dataclasses + cattrs |
|
||||
| DB | **SQLAlchemy 2.0 async** + **asyncpg** + **Alembic** | SQLModel, Tortoise |
|
||||
| HTTP/API | **FastAPI** + **uvicorn** + **sse-starlette** | Litestar |
|
||||
| CLI | **typer** | Click |
|
||||
| 워크플로우 | **temporalio** (Python SDK) | (Temporal 자체는 유지) |
|
||||
| Agent | **langchain** + **langgraph** + **deepagents** + **langchain-openai** | 자체 구현 |
|
||||
| Tmux | **libtmux** | subprocess 직호출 |
|
||||
| 테스트 | **pytest** + **pytest-asyncio** + **pytest-httpx** | unittest |
|
||||
| 린트/포맷 | **ruff** | black + flake8 |
|
||||
| 타입체크 | **mypy** strict (또는 **pyright**) | — |
|
||||
| Pre-commit | **pre-commit** | — |
|
||||
| 로깅 | **structlog** + **rich** | loguru |
|
||||
| YAML | **PyYAML** | ruamel.yaml |
|
||||
| JSON Schema | **jsonschema** | — |
|
||||
|
||||
### Web GUI
|
||||
**이번 plan 범위 외.** TS web app은 폐기되지만 Python 재이식은 별도 마일스톤. 후보: FastAPI SSR + HTMX, 별도 SPA(Svelte/Vue) 분리. 결정 보류.
|
||||
|
||||
---
|
||||
|
||||
## 디렉토리 구조
|
||||
|
||||
```text
|
||||
devflow/
|
||||
├── pyproject.toml # uv workspace root
|
||||
├── uv.lock
|
||||
├── ruff.toml
|
||||
├── mypy.ini
|
||||
├── .pre-commit-config.yaml
|
||||
├── docker-compose.yml # 보존
|
||||
├── .env.example
|
||||
├── alembic.ini
|
||||
├── docs/
|
||||
│ ├── plan.md # v4 r1로 패치
|
||||
│ └── schemas/ # 보존
|
||||
├── alembic/
|
||||
│ ├── env.py
|
||||
│ └── versions/
|
||||
├── packages/
|
||||
│ ├── core/src/devflow_core/
|
||||
│ │ ├── config.py
|
||||
│ │ ├── enums.py
|
||||
│ │ ├── errors.py
|
||||
│ │ ├── hash.py
|
||||
│ │ ├── persona.py
|
||||
│ │ ├── binding.py
|
||||
│ │ ├── prompt_envelope.py
|
||||
│ │ ├── artifact_schema.py
|
||||
│ │ └── run_event.py
|
||||
│ ├── db/src/devflow_db/
|
||||
│ │ ├── models/
|
||||
│ │ ├── repositories/
|
||||
│ │ └── client.py
|
||||
│ ├── session/src/devflow_session/
|
||||
│ │ ├── adapter.py
|
||||
│ │ ├── fake.py
|
||||
│ │ ├── tmux.py
|
||||
│ │ └── openrouter_deepagents.py
|
||||
│ ├── run_engine/src/devflow_run_engine/
|
||||
│ └── workflows/src/devflow_workflows/
|
||||
├── apps/
|
||||
│ ├── api/ # FastAPI
|
||||
│ ├── cli/ # typer
|
||||
│ └── worker/ # Temporal Python worker
|
||||
└── tests/
|
||||
├── e2e/
|
||||
└── fixtures/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## plan.md v4 r1 패치 항목
|
||||
|
||||
- §0 헤더: `v4 r1`, "Major version bump: language migration TS → Python. v3 CC counters preserved as historical; v4 CC counter starts at 1."
|
||||
- §1 Stack Decisions: **전면 재작성** (위 스택 표 채택).
|
||||
- §2 Directory Layout: 위 구조로 교체.
|
||||
- §3 doctor checklist: Node/pnpm 체크 → Python/uv 체크로 교체. Postgres, tmux, git, Docker, OpenRouter check 13 유지.
|
||||
- §4~§17 (DB schema, enums, hashing, template/persona/binding, session, prompt envelope, artifact registry, run events, fake adapter, state machines, errors, SSE contract): 언어 중립 도메인 명세 → 그대로 유지. Python 구현 시 동일 의미.
|
||||
- §8.5 OpenRouter Adapter: **재작성** — 단발 응답 + 마커 추출(v3 r13) → **deepagents 멀티턴 + tool use**. tool whitelist (`read_file`, `write_file`, `list_dir`, `run_command`, `request_subagent`, `complete`), max_turns, subagent isolation, virtual filesystem→worktree 매핑.
|
||||
- §18 Errors: `token_budget_exceeded`, `tool_quota_exceeded` 추가.
|
||||
- §20 Milestones: 기존 M1~M13을 Python 재이식 매핑 (M1-Py ~ M8-Py 본 plan 범위, M9~M13 후속).
|
||||
- §22 Decision Log: `DR-1: v3→v4 메이저 점프, TS 모노레포 폐기 + Python 재시작 + LangChain deepagents 채택` 추가. CC-39(OpenRouter TS)는 v4에서 의미 변경, deepagents 통합으로 superseded.
|
||||
- §22 Decision Log: `DR-22: Persona/Workflow의 list-valued field는 tuple로 immutable | hash drift 방지, plugin 시스템 (v0.2)에서 외부 mutate 차단` 추가.
|
||||
|
||||
---
|
||||
|
||||
## 구현 단계 (각 Step = 1 PR)
|
||||
|
||||
### Step 0 — 폐기 + 스캐폴딩 ⚠️ 위험 큼
|
||||
1. 폐기 디렉토리/파일 git rm.
|
||||
2. `uv init` + workspace 멤버 등록.
|
||||
3. 새 디렉토리 트리 생성 (위 구조).
|
||||
4. `ruff.toml`, `mypy.ini`, `.pre-commit-config.yaml`, `alembic.ini` 추가.
|
||||
5. plan.md v4 r1 패치 적용 (§0/§1/§2/§3/§20/§22).
|
||||
6. CHANGELOG.md `[Unreleased]`에 "BREAKING: TS codebase removed, Python rewrite begins" 기록.
|
||||
7. `docker-compose.yml`, `docs/schemas/` 보존 확인.
|
||||
|
||||
### Step 1 — `devflow_core` (M1.4-Py)
|
||||
config/enums/errors/hash/persona/prompt_envelope/run_event를 pydantic v2로. plan.md §5/§6/§7 명세 그대로.
|
||||
|
||||
### Step 2 — `devflow_db` (M1.2-Py)
|
||||
SQLAlchemy 2 async 모델 + Alembic baseline. 기존 `migrations/*.sql`을 baseline으로 흡수.
|
||||
|
||||
### Step 3 — `apps/cli` doctor (M1.3-Py)
|
||||
typer 기반. 체크 1~12 + OpenRouter check 13. Node/pnpm 체크는 Python/uv로 교체.
|
||||
|
||||
### Step 4 — Persona/Template seeding + binding (M2-Py)
|
||||
YAML 로더(`docs/schemas/{personas,templates}/`) + pydantic 검증 + autoSelect/override/diversity (§7.4 그대로).
|
||||
|
||||
### Step 5 — Artifact schema registry (M2.3-Py)
|
||||
`jsonschema` 라이브러리로 2020-12 검증. `docs/schemas/artifacts/`를 그대로 로드.
|
||||
|
||||
### Step 6 — Fake session adapter (M3-Py)
|
||||
인메모리. fixture 기반 시나리오(§12).
|
||||
|
||||
### Step 7 — Run engine (M4-Py)
|
||||
in-process. 페이즈 진행, 이벤트 append, idempotency key.
|
||||
|
||||
### Step 8 — Temporal integration (M5-Py)
|
||||
temporalio worker. 워크플로우/액티비티 §15 그대로 포팅.
|
||||
|
||||
### Step 9 — Tmux adapter (M6-Py)
|
||||
libtmux + subprocess. 기존 §8.2 상태머신 유지.
|
||||
|
||||
### Step 10 — TUI recovery (M7-Py)
|
||||
세션 상태머신, recovery counters.
|
||||
|
||||
### Step 11 — FastAPI + SSE (M8-Py, GUI 제외)
|
||||
REST + SSE-Starlette. GUI는 별도.
|
||||
|
||||
### Step 12 — OpenRouter deepagents adapter (M9-Py 일부, **본 변경 핵심**)
|
||||
- `langchain-openai` ChatOpenAI를 OpenRouter base URL로.
|
||||
- `deepagents.create_deep_agent(tools, instructions, subagents)`.
|
||||
- tools: `read_file`/`write_file`/`list_dir`/`run_command(allowlist)`/`request_subagent`/`complete`.
|
||||
- subagents: review/verifier 분리 컨텍스트.
|
||||
- virtual filesystem → 실제 worktree 매핑.
|
||||
- artifact 작성은 `write_file(expectedArtifactPath, ...)` 호출로 (v3 r13 마커 폐기).
|
||||
- 토큰 한도/turn 한도는 페르소나 `modelConfig.maxTurns`, `modelConfig.maxTokensTotal`로.
|
||||
- 시드 페르소나 2개: `openrouter-deepseek-spec@1.yaml`, `openrouter-deepseek-reviewer@1.yaml` (DeepSeek 디폴트).
|
||||
|
||||
---
|
||||
|
||||
## 의존성 (Step 0에서 정확 버전 lock)
|
||||
|
||||
```toml
|
||||
[project]
|
||||
requires-python = ">=3.12,<3.14"
|
||||
dependencies = [
|
||||
"pydantic>=2.9",
|
||||
"pydantic-settings>=2.6",
|
||||
"sqlalchemy[asyncio]>=2.0",
|
||||
"alembic>=1.14",
|
||||
"asyncpg>=0.30",
|
||||
"fastapi>=0.115",
|
||||
"uvicorn[standard]>=0.34",
|
||||
"sse-starlette>=2.1",
|
||||
"typer>=0.14",
|
||||
"temporalio>=1.10",
|
||||
"langchain>=0.3",
|
||||
"langchain-openai>=0.2",
|
||||
"langgraph>=0.2",
|
||||
"deepagents>=0.0.5",
|
||||
"libtmux>=0.39",
|
||||
"structlog>=24.4",
|
||||
"rich>=13.9",
|
||||
"pyyaml>=6.0",
|
||||
"jsonschema>=4.23",
|
||||
"httpx>=0.28",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8.3",
|
||||
"pytest-asyncio>=0.24",
|
||||
"pytest-httpx>=0.34",
|
||||
"ruff>=0.8",
|
||||
"mypy>=1.13",
|
||||
"pre-commit>=4.0",
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 환경 셋업 (선결)
|
||||
|
||||
```bash
|
||||
# 1) Python 3.12+ (uv가 알아서 가져옴)
|
||||
# 2) uv 설치
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# 3) 워크스페이스 동기화
|
||||
uv sync
|
||||
# 4) 컨테이너 (보존된 docker-compose.yml)
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
기존 pnpm 환경 문제(`pnpm not found`)는 Node 자체가 필요 없어져 자연 해결.
|
||||
|
||||
---
|
||||
|
||||
## 모델 위임 정책 (메모리 룰 유지)
|
||||
|
||||
| 작업 | 모델 | subagent_type |
|
||||
|---|---|---|
|
||||
| Python 구현 | sonnet | `coder` / `general-purpose` |
|
||||
| 코드 리뷰 | opus | `feature-dev:code-reviewer` / `reviewer` |
|
||||
| 리뷰 지적 수정 | sonnet | `coder` |
|
||||
|
||||
---
|
||||
|
||||
## 검증 (각 Step 게이트)
|
||||
|
||||
```bash
|
||||
uv run ruff check .
|
||||
uv run ruff format --check .
|
||||
uv run mypy .
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
전부 PASS → 커밋 → 다음 Step.
|
||||
|
||||
---
|
||||
|
||||
## 범위 외
|
||||
|
||||
- Web GUI 재이식 (TS 폐기 확정, Python 재이식은 별도 마일스톤).
|
||||
- 다중 모델 fallback (rate limit 시 다른 모델로).
|
||||
- 비용 추적/예산 게이트 (OpenRouter usage API).
|
||||
- 다른 HTTP provider (Anthropic 직접, OpenAI 직접).
|
||||
- 한국어 GUI/문서화.
|
||||
|
||||
---
|
||||
|
||||
## 주의
|
||||
|
||||
- **Step 0의 git rm은 비가역적 위험**: 직전에 `git tag pre-python-rewrite`를 찍어 v3 마지막 커밋을 태깅. 필요 시 `git checkout pre-python-rewrite -- <path>` 로 자료 추출 가능.
|
||||
- TS 마지막 commit `c9fed71` 이후의 미커밋 변경(M9 단계 A yaml/json + plan.md r13 + Step 1 TS) 처리:
|
||||
- yaml/json (M9 A): 보존 (언어 중립)
|
||||
- plan.md r13 패치: v4 r1 패치 안에서 일부 흡수 (CC-39는 변경 의미 변경됨)
|
||||
- Step 1 TS 변경: git rm 대상에 포함 (Python 재구현)
|
||||
86
docs/plan.md
86
docs/plan.md
@@ -1,4 +1,4 @@
|
||||
# Devflow Implementation Plan v3 r12
|
||||
# Devflow Implementation Plan v3 r13
|
||||
|
||||
## 0. Document Status
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- r10 applies CC-29 through CC-31.
|
||||
- r11 applies CC-32.
|
||||
- r12 applies CC-33 through CC-35.
|
||||
- r13 applies CC-39.
|
||||
|
||||
## 1. Stack Decisions
|
||||
|
||||
@@ -95,6 +96,11 @@
|
||||
- `DATABASE_URL`
|
||||
- `WORKSPACE_ROOT`
|
||||
- `LOG_LEVEL`
|
||||
|
||||
Additional required keys when `openrouter` backend is enabled:
|
||||
|
||||
- `OPENROUTER_API_KEY`
|
||||
|
||||
- M5 adds:
|
||||
- `TEMPORAL_ADDRESS`
|
||||
- Path canonicalization:
|
||||
@@ -106,9 +112,11 @@ Backend registration:
|
||||
|
||||
```ts
|
||||
const BackendConfig = z.object({
|
||||
id: Backend, // codex | claude | fake
|
||||
id: Backend, // codex | claude | fake | openrouter
|
||||
enabled: z.boolean(),
|
||||
binaryPath: z.string().optional(), // resolved from PATH if absent; required for codex/claude
|
||||
binaryPath: z.string().optional(), // resolved from PATH if absent; required for codex/claude when enabled
|
||||
apiBaseUrl: z.string().optional(), // openrouter only; default https://openrouter.ai/api/v1
|
||||
apiKeyEnv: z.string().optional(), // openrouter only; default OPENROUTER_API_KEY
|
||||
});
|
||||
```
|
||||
|
||||
@@ -116,6 +124,10 @@ const BackendConfig = z.object({
|
||||
- `codex` and `claude` are available only when:
|
||||
- `enabled=true`
|
||||
- binary resolves at process start.
|
||||
- `openrouter` is available only when:
|
||||
- `enabled=true`
|
||||
- the env var named by `apiKeyEnv` (default `OPENROUTER_API_KEY`) is present and non-empty.
|
||||
- `binaryPath` is ignored for `openrouter`.
|
||||
- Resolution failure:
|
||||
- `doctor` warns.
|
||||
- binding fails fast at run start with `human_required:backend_unavailable`.
|
||||
@@ -250,6 +262,10 @@ Closed check list:
|
||||
- warn under 10GB.
|
||||
- fail under 2GB.
|
||||
- target green threshold: >=5GB.
|
||||
13. OpenRouter API reachable: when `openrouter` backend is enabled, `GET ${apiBaseUrl}/models` with the bearer key.
|
||||
- pass on `200`.
|
||||
- fail on `401`.
|
||||
- warn on any other non-200 or network error.
|
||||
|
||||
Output:
|
||||
|
||||
@@ -528,6 +544,9 @@ All enums live in `packages/core/src/enums.ts` as TypeScript `const` objects and
|
||||
- `codex`
|
||||
- `claude`
|
||||
- `fake`
|
||||
- `openrouter`
|
||||
|
||||
openrouter is HTTP-based and has no tmux/PTY; see §8.5.
|
||||
|
||||
Future `gemini` support adds an enum entry and a `BackendProfile`; no design change.
|
||||
|
||||
@@ -713,6 +732,13 @@ const Persona = z.object({
|
||||
});
|
||||
```
|
||||
|
||||
modelConfig conventions:
|
||||
|
||||
- Personas bound to `openrouter` MUST set `modelConfig.model` to a routable OpenRouter model id, e.g. `anthropic/claude-sonnet-4-5`, `deepseek/deepseek-chat`, `meta-llama/llama-3.1-70b-instruct`.
|
||||
- Other supported keys: `maxTokens`, `temperature`, `topP`. All optional.
|
||||
- For tmux-based backends (`codex`, `claude`, `fake`), `modelConfig.model` is informational only and MAY be omitted.
|
||||
- Binding fails fast with `human_required:model_unavailable` when an `openrouter` persona has no `modelConfig.model`.
|
||||
|
||||
### 7.3 Override Semantics
|
||||
|
||||
- Override may swap persona for a role.
|
||||
@@ -812,6 +838,8 @@ export interface TranscriptChunk {
|
||||
}
|
||||
```
|
||||
|
||||
For HTTP backends (`openrouter`) the `SessionHandle.pid`, `tmuxSession`, and `tmuxWindow` fields are always `undefined`. See §8.5 for the HTTP adapter mapping.
|
||||
|
||||
### 8.2 Session State Machine
|
||||
|
||||
- `CREATED -> BOOTSTRAPPING -> READY`
|
||||
@@ -854,6 +882,54 @@ Exhaustion creates a human gate with `recoveryHint`.
|
||||
- persist `last_capture_seq`.
|
||||
- release advisory lock.
|
||||
|
||||
### 8.5 OpenRouter Adapter
|
||||
|
||||
HTTP-based `SessionAdapter` for the `openrouter` backend. No PTY, no tmux.
|
||||
|
||||
Method mapping:
|
||||
|
||||
- `start`:
|
||||
- allocate in-memory session state `{ messages: [], lastResponseAt }`.
|
||||
- push the backend prelude (§9.4) as a `system` message.
|
||||
- `sendPrompt`:
|
||||
- append the envelope `instructions` (full §9.1 envelope text) as a `user` message.
|
||||
- POST `${apiBaseUrl}/chat/completions` with `Authorization: Bearer ${apiKey}` and body `{ model: persona.modelConfig.model, messages, max_tokens?, temperature?, top_p? }`.
|
||||
- append the assistant response as an `assistant` message.
|
||||
- `probe`:
|
||||
- alive iff session state is held in the SessionManager map.
|
||||
- `paneActive` is always `true`.
|
||||
- `resume`:
|
||||
- in-memory messages are lost on process restart.
|
||||
- attempt restoration by replaying `tui_transcript_chunks` for the session into the messages array.
|
||||
- on irrecoverable failure, fall through to `rebootstrap`.
|
||||
- `rebootstrap`:
|
||||
- clear messages and re-push the prelude.
|
||||
- `capture`:
|
||||
- split assistant responses into line-sized `TranscriptChunk`s and persist via the standard chunk pipeline.
|
||||
- `dispose`:
|
||||
- drop the in-memory entry.
|
||||
|
||||
Artifact production:
|
||||
|
||||
- HTTP agents cannot write to the workspace filesystem. The backend prelude (§9.4) instructs the model to emit the artifact body inside a single fenced block at the tail of the response:
|
||||
|
||||
```text
|
||||
<<<DEVFLOW_ARTIFACT_BEGIN>>>
|
||||
{ "...": "..." }
|
||||
<<<DEVFLOW_ARTIFACT_END>>>
|
||||
```
|
||||
|
||||
- The adapter extracts the JSON between the markers and writes it atomically (temp file + rename) to `expectedArtifactPath`.
|
||||
- Missing markers, multiple blocks, or JSON parse failure are treated as `artifact.invalid` and follow the standard repair/timeout flow in §10.3.
|
||||
|
||||
Error mapping:
|
||||
|
||||
- HTTP `401` → `human_required:backend_auth_failed`.
|
||||
- HTTP `429` → `recoverable:rate_limited` (exponential backoff: 1s, 2s, 4s, max 30s).
|
||||
- HTTP `5xx` → `recoverable:network_blip`.
|
||||
- HTTP `400` with body code `model_not_found` → `human_required:model_unavailable`.
|
||||
- Network error before any response → `recoverable:network_blip`.
|
||||
|
||||
## 9. Prompt Envelope
|
||||
|
||||
### 9.1 Wire Format
|
||||
@@ -1494,6 +1570,7 @@ Recoverable:
|
||||
- `pane_briefly_unresponsive`
|
||||
- `prompt_send_transient`
|
||||
- `db_serialization_retry`
|
||||
- `rate_limited`
|
||||
|
||||
Human required:
|
||||
|
||||
@@ -1508,6 +1585,8 @@ Human required:
|
||||
- `merge_conflict`
|
||||
- `objective_not_met`
|
||||
- `review_dispute_unresolved`
|
||||
- `backend_auth_failed`
|
||||
- `model_unavailable`
|
||||
|
||||
Fatal:
|
||||
|
||||
@@ -1778,6 +1857,7 @@ M5+:
|
||||
| CC-36 | SSE reconnect wording used per-run `seq` for global stream even though `seq` is not globally monotonic | `/sse/runs/:runId` uses per-run `seq`; `/sse/global` uses global `run_events.id` and emits only scope=`both` summary events |
|
||||
| CC-37 | Run SSE replay could emit historical derived events after the first page | run SSE drains historical rows up to a high-water `seq` with only `run.event_appended`, then switches to live derived events |
|
||||
| CC-38 | Normal phase start changed run state to `planning` / `executing` without a summary event source | `phase.started` payload includes `runState`; SSE derives `run.state_changed` from that live event |
|
||||
| CC-39 | No OpenRouter HTTP backend; users cannot pick cost-tuned per-persona models | add `openrouter` to Backend enum; HTTP `OpenRouterAdapter` in §8.5; persona `modelConfig.model` requirement; doctor check 13; new error codes `rate_limited`, `backend_auth_failed`, `model_unavailable` |
|
||||
|
||||
### Future Open Questions
|
||||
|
||||
|
||||
40
docs/schemas/artifacts/dev/review-finding-batch@1.json
Normal file
40
docs/schemas/artifacts/dev/review-finding-batch@1.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "dev/review-finding-batch@1",
|
||||
"title": "Devflow Review Finding Batch",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["runId", "phaseKey", "reviewerRole", "findings"],
|
||||
"properties": {
|
||||
"runId": { "type": "string", "format": "uuid" },
|
||||
"phaseKey": { "type": "string", "minLength": 1 },
|
||||
"reviewerRole": { "type": "string", "minLength": 1 },
|
||||
"findings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["severity", "category", "summary"],
|
||||
"properties": {
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"enum": ["info", "low", "medium", "high", "critical"]
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["correctness", "evidence", "style", "security", "performance", "other"]
|
||||
},
|
||||
"summary": { "type": "string", "minLength": 1 },
|
||||
"filePath": { "type": "string" },
|
||||
"line": { "type": "integer", "minimum": 1 },
|
||||
"evidence": { "type": "string" },
|
||||
"verifierStatus": {
|
||||
"type": "string",
|
||||
"enum": ["unverified", "confirmed", "rejected"],
|
||||
"default": "unverified"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
docs/schemas/personas/fake-reviewer@1.yaml
Normal file
10
docs/schemas/personas/fake-reviewer@1.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
name: fake-reviewer
|
||||
version: 1
|
||||
backend: fake
|
||||
capabilities:
|
||||
- code_review
|
||||
- evidence_check
|
||||
maxRiskLevel: high
|
||||
promptConfig:
|
||||
instructionsPrelude: "Use the fake backend fixture protocol for review batches."
|
||||
modelConfig: {}
|
||||
@@ -11,6 +11,17 @@ roles:
|
||||
- phase_planning
|
||||
preferredBackends:
|
||||
- fake
|
||||
- id: reviewer
|
||||
requiredCapabilities:
|
||||
- code_review
|
||||
preferredBackends:
|
||||
- fake
|
||||
count: 2
|
||||
- id: verifier
|
||||
requiredCapabilities:
|
||||
- evidence_check
|
||||
preferredBackends:
|
||||
- fake
|
||||
phases:
|
||||
- key: spec
|
||||
title: Development Specification
|
||||
@@ -32,4 +43,24 @@ phases:
|
||||
schema: dev/phase-plan@1
|
||||
gates:
|
||||
- phase_plan_approved
|
||||
- key: review_consensus
|
||||
title: Review Consensus
|
||||
risk: low
|
||||
roles:
|
||||
- reviewer
|
||||
expectedArtifact:
|
||||
path: artifacts/review.json
|
||||
schema: dev/review-finding-batch@1
|
||||
gates:
|
||||
- review_consensus_approved
|
||||
- key: verify
|
||||
title: Evidence Verification
|
||||
risk: low
|
||||
roles:
|
||||
- verifier
|
||||
expectedArtifact:
|
||||
path: artifacts/verification.json
|
||||
schema: dev/review-finding-batch@1
|
||||
gates:
|
||||
- verify_approved
|
||||
defaultGates: []
|
||||
|
||||
Reference in New Issue
Block a user