45 Commits

Author SHA1 Message Date
chungyeong
7b0a5f12ec test(verify-v04): comprehensive quality benchmark vs Claude Code sub-agent
26 시나리오 (I/C/M/S/W/Q) 자동 실행 + Sonnet judge benchmark.
결과: 23 PASS / 1 FAIL (Q1 보더라인) / 2 SKIP (W3/W4 safety 차단).

신규 파일:
- scripts/verify_v04/_common.py — mk_session / record / load_results helpers
- scripts/verify_v04/run_cms.py — C/M/S 시나리오 16개 자동 실행
- scripts/verify_v04/run_q.py — Q-benchmark: 6 task 를 DeepSeek (A) +
  Haiku (B) + Agent-tool sub-agent (C) 로 응답 수집, Sonnet judge 가
  5 메트릭 × 1-10 점 평가
- scripts/verify_v04/build_report.py — 결과 stitch → verify_report_v04.md
- verify_report_v04.md — 최종 보고서

Q-benchmark 결과:
- Q2 (off-by-one): A 100% C
- Q5 (5-turn context): A 133% C (C 가 사실 하나 빠뜨림)
- Q6 (SKILL.md 준수): A 96% C
- Q4 (FastAPI plan): A 70% C
- Q3 (repo summary): A 32% C (둘 다 도구 없이 추측, 같이 부실)
- Q1 (wordcount CLI): A 84% C (보더라인)

결론: 6 task 중 **5개에서 Claude Code sub-agent 동급 이상**.
DeepSeek 가성비 default 로도 Claude Code chat UX 동등 품질.

수정:
- tests/unit/test_persona.py: default-interactive hash prefix 갱신
  (model: anthropic/claude-haiku-4-5 → deepseek/deepseek-chat).

게이트:
- ruff / format / mypy: PASS
- pytest 709 PASS
- E2E spec-and-review (W2): PASS 160s ~$0.05
- Total OpenRouter 비용 (verify v04): ~$0.8
- Total Claude Code Agent tool (sub-agent C): ~$0.1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:46:32 +09:00
chungyeong
5cf9ad131a feat(conversation): cheap-default DeepSeek + Enter-send + model pill
- default-interactive@1 model: claude-haiku-4-5 → deepseek/deepseek-chat
  (input $0.28/$1.12 per 1M; haiku 대비 ~75% 절감).  fallback 은 haiku 로 swap.
- conversation textarea keydown:
  - Enter → 전송 (IME composition 중이면 무시)
  - Shift+Enter → 줄바꿈
  - Cmd/Ctrl+Enter → 전송 (백워드 호환)
  - Placeholder 안내 갱신.
- conversation top-bar 에 model pill 추가 (#session-model-pill) — 현재 세션의
  활성 model 을 monospace badge 로 표시.  헷갈리던 "어느 모델인가?" 해소.
- style.css 에 .conv-model-pill (회색 pill).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:02:19 +09:00
chungyeong
9a02f22acb feat(my-deepagent): v0.4 chat UX boost + A/B live verification
Claude-Code 동급 chat 경험으로 끌어올림 + 7개 핵심 흐름 실제 OpenRouter verify.

A — Live verification (scripts/live_verify.py, 7 PASS, 약 $0.02):
- A1 1-turn chat (CLI-eq) → Haiku 4.5 한국어 응답
- A2 sessions resume → 같은 session_id 재투입 시 LangGraph state 복원
- A3 /skill <name> system inject → SKILL.md ("한국어 haiku 3 lines") 가 정확히
  3행 한국어 시 생성 (LLM 행동 제어 강력한 증거)
- A4 /plan → /approve → LLM plan markdown only, 차단 도구 시도 없음
- A5 /agents spawn → 실제 sub-agent ainvoke + parent stream push
- A6 auto-compaction → 14 메시지 → 4 archive + 77 토큰 summary
- A7 /workflow wiring → role↔persona 매칭 사전 검증

B1 — Markdown rendering:
- app.js pure-JS 미니 파서: 코드 펜스 / ATX 헤더 / ul/ol / `code`/**bold**/
  *italic*/[link](url)
- XSS 정책 유지: createElement + textContent only.  링크 href 는 http(s):
  스킴 강제.

B2 — System event card (collapsible):
- _classifySystemMessage 가 [sub-agent .../workflow .../Earlier conversation
  history/당신은 plan mode/The user APPROVED/skill] 접두사 분류 후 <details>
  카드로 렌더.

B3 — Token streaming via AsyncCallbackHandler:
- ChatOpenAI(streaming=True)
- _StreamingChunkPusher (AsyncCallbackHandler) → asyncio.Queue per session.
- SSE _session_event_stream 이 queue drain → event: chunk SSE.  100ms poll.
- 순서 보장: chunk drain → message rows yield (placeholder 가 메시지로
  교체되기 전에 토큰 visible).
- 라이브: 5 chunk events + 1 final message, "안녕하세요, / 무 / 엇을 도와드 /
  릴까요?" 토큰 단위 push.

B4 — Cancel mid-turn:
- POST /api/sessions/{id}/abort + app.state.pending_per_session 인덱스.
- 새 user 메시지 도착 시 이전 in-flight task 자동 cancel.
- "■ 중단" 버튼 — 대기 중 visible, 완료/취소 시 hide.

B5 — IME composition-safe Enter:
- compositionstart/compositionend 플래그 — 한글 IME 후보 commit Enter 무시.
- Cmd/Ctrl+Enter 는 항상 전송.

DB hot-fix:
- Database.__init__ pool_pre_ping=True — Postgres asyncpg stale connection
  → SSE 부하에서 500 발생 해결.

기타:
- createNewSession 의 repo_path: "" → "." (min_length=1 검증 통과).
- test_conversation_gui.py fake_invoke 가 chunk_queue kwarg 받도록 업데이트.

게이트:
- ruff / format / mypy: PASS (143 source files)
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 709 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 01:08:40 +09:00
chungyeong
6d371afadd feat(my-deepagent): v0.4 — workflow generator UI + hot-reload + UX polish
브라우저에서 YAML 안 쓰고도 새 워크플로우 템플릿 만들기 + 즉시 등록.
+ /new.html / index.html / new-workflow.html / runs.html / conversation.html
의 nav·copy·empty-state 정비.

A. /new.html UX:
- 제목 "새 Run" → "워크플로우 실행 (고급)"
- 상단 info-box: "자유 대화는 여기가 아닙니다 → 메인 페이지"
- 모든 필드에 한 줄 hint
- Persona 오버라이드 <details> 접힘

B. Nav 재정렬 (5 페이지):
- "대화" nav-primary, 나머지 nav-secondary (작고 dim)

C. 메인 안내 + CSS:
- 메인 / 에 "👋 my-deepagent" info-box 추가
- .info-box / .nav-primary / .nav-secondary / .wf-* 신규 스타일

D. Workflow hot-reload:
- api/deps.py get_workflows 가 매 요청 mtime 튜플 검사 후 변경 시 reload
- lifespan 도 user dir 포함하도록 _load_workflows_combined

E. Workflow generator:
- POST /api/workflows: CreateWorkflowRequest → WorkflowTemplate validate →
  <data_dir>/workflows/<name>@<version>.yaml 저장.  중복 409, validation 422.
- static/new-workflow.html: 기본 정보 / Roles / Phases / YAML preview
- app.js bootstrapWorkflowGenerator: capability chip 토글, role select 동적,
  실시간 YAML preview, XSS 정책 유지

테스트 (test_workflow_generator.py, 7 신규):
- 페이지 200 + 마크업
- POST happy / 422 (empty roles) / 422 (unknown role) / 409 (dup)
- GET hot-reload after POST
- GET hot-reload after external file drop

게이트:
- ruff / format / mypy: PASS (142 source files)
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 709 passed (+7 신규)
- 라이브 smoke: / / new.html / new-workflow.html 모두 200, screenshot OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:38:46 +09:00
chungyeong
40ef833ad3 fix(db): enable pool_pre_ping on async engine — 500 on stale Postgres connection
증상:
- 라이브 smoke 도중 SSE poll loop 가 0.5s 마다 connection 을 빌리던 중,
  asyncpg pool 이 idle/network blip 으로 socket 이 닫힌 stale connection
  을 그대로 넘김.  다음 요청 (GET /api/sessions) 이
  `sqlalchemy.exc.InterfaceError: connection is closed` 로 500.

원인:
- `create_async_engine(database_url, poolclass=None, echo=False)` —
  pool_pre_ping 미설정.  SQLAlchemy 가 checkout 시 connection 생존
  확인 안 함.

수정:
- `pool_pre_ping=True` 한 줄 추가.  SQLAlchemy 가 매 checkout 직전 빠른
  SELECT 1 (asyncpg 는 protocol-level ping) 을 보내고 실패 시 pool 에서
  invalidate 후 새 connection 발급.  표준 SQLAlchemy 권장 패턴.
- 부하 (SSE 0.5s polling + REST) 에서 검증: 재시작 후 GET /api/sessions
  연속 호출 모두 200.

테스트:
- ruff / mypy: PASS (141 files)
- pytest tests/integration/test_persistence.py: 20 passed (회귀 없음)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:24:24 +09:00
chungyeong
96c8849e2c fix(my-deepagent): v0.3 plan-conformance — 18-item gap fix across PR #2-#9
1차 v0.3 구현 후 plan-v0.3 와 대조해 발견된 18건 누락/명세 위반을 보강.
자기 리뷰 3 라운드 (누락·미완 / 오류·엣지케이스 / 과최적화) 모두 PASS.

PR #5 plan-mode (3건):
- BLOCKED_TOOLS_IN_PLAN_MODE 에 write_todos 추가
- /plan 시 system message inject (_PLAN_MODE_SYSTEM_PROMPT)
- /approve 시 마지막 assistant 메시지를 "approved plan" system 으로 inject
- InteractiveSession._pending_system_messages 인프라 신설

PR #2 compaction (1건):
- CompactionResult.summary_text 추가, 다음 thread 첫 ainvoke 에 inject

PR #3 auto-memory (6건):
- global memory dir + bootstrap
- frontmatter name/description/type 정식 도입 + MemoryEntry/MemoryType
- _infer_memory_type (keyword heuristic, no LLM)
- _scrub_secrets (OpenRouter/Anthropic/OpenAI/AWS/Bearer redaction)
- /memory show <name> 서브명령
- /remember [--global] / /forget [--global] 스코프 토글

PR #4 skills (3건):
- project_skills_dir + 두 스코프 (global / project) merge with last-wins
- /skill <name> 본문 inject (queue_system_message) — 이전엔 REPL 출력만
- /skills show <name> 별도 서브명령

PR #6 sub-agent (4건):
- budget.py `session:<uuid>` scope + CostMiddleware 자동 전달
- resolve_root_session_id walk-up (cycle guard) + sub-agent root 에 charge
- run_subagent_to_completion 실제 ainvoke + 결과 push to parent
- /agents 서브명령 구조 (list / spawn / show) + spawn 시 parent system msg

PR #7 governance (1건):
- bootstrap_user_dirs — instructions + global/memory + skills + projects 한
  호출로 idempotent 부트스트랩

PR #8 Web GUI (1건):
- index.html → 세션 목록, runs.html (신설) → workflow archive
- conversation.html ?session=<id> deep-link

PR #9 workflow integration (2건):
- /workflow 백그라운드 WorkflowEngine.run + 진행 메시지 stream 누적
- /binding show <workflow-name[@version]> 인자 지원

테스트 (+17, 685 → 702 passed):
- test_plan_mode: write_todos 차단 + blocklist sanity
- test_memory: scrub + type 추론 + override
- test_skills: project override + find_skill + resolve_skill_sources(pk)
- test_subagents: resolve_root_session_id chain + missing fallback
- test_budget: session: scope accumulation
- test_instructions: governance bootstrap + idempotency
- test_api_static: runs.html 신설 + index.html 재구성

게이트:
- ruff check / format --check / mypy: PASS (141 source files)
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 702 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:03:08 +09:00
chungyeong
361d6d7636 feat(my-deepagent): v0.3 PR #9 — workflow optionization + user dir wiring
Workflow engine 을 주력에서 "옵션" 으로 격하: 사용자가 명시적
`/workflow <name>` 호출 시만 활성.  대신 `<data_dir>/personas/` 와
`<data_dir>/workflows/` 에 YAML 파일을 떨궈 자신만의 persona·workflow 를
등록할 수 있게 함 (seed override 가능).

핵심 동작:
- `ensure_user_dirs_initialized(config)` — 두 사용자 디렉터리 `mkdir -p`,
  idempotent.  매 REPL 시작 시 호출.
- `load_combined_personas(config, seed_dir)` — seed (strict) + user
  (best-effort per-file skip) merge.  Dedupe key `(name, version)`,
  user-overrides-seed.  Broken user YAML 1개 가 REPL 죽이지 못함.
- `load_combined_workflows(config, seed_dir)` — workflow 도 동일.

데이터·라이브러리:
- `user_dirs.py` (신규): `user_personas_dir`, `user_workflows_dir`,
  `ensure_user_dirs_initialized`, `load_combined_personas`,
  `load_combined_workflows`, `_safe_load_personas`, `_safe_load_workflows`.

REPL 통합 (`cli/interactive.py`):
- `InteractiveSession(..., workflows=...)` 시그니처 확장.
- `_interactive_loop_async` 가 user dir bootstrap + combined load 사용.
- 신규 슬래시 4개:
  - `/personas` — 로드된 persona 목록 (현재 활성 표시)
  - `/workflows` — 로드된 workflow 템플릿 목록 (phase/role 개수, 파일명)
  - `/workflow <name>` — `mydeepagent run` 명령 안내 (현재 백그라운드 invoke
    는 안내 메시지만; 실제 kick-off 는 별도 PR 또는 `mydeepagent run` CLI)
  - `/binding show` — 각 workflow 의 role 별 required_capabilities 표시
- `_register_workflow_slash` 의 복잡도(C901) 회피를 위해 print 헬퍼
  (`_print_personas` 등) 를 module-level 로 추출.

테스트 (`tests/integration/test_user_dirs.py`, 10 케이스):
- 부트스트랩 idempotency
- persona seed-only / seed+user / user-overrides-seed / malformed-user-skip
- workflow 동일 4종
- 빈 user 디렉터리 처리

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 685 passed (10 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:11:19 +09:00
chungyeong
e326c07dcb feat(my-deepagent): v0.3 PR #8 — conversation-centric Web GUI (/conversation.html)
Workflow run 페이지를 archive 로 격하시키고, 사용자가 처음 보는 화면을
chat-style 대화 thread 로 전환.  Claude Code 의 Web GUI 와 동일한 UX.

핵심 동작:
- 새 페이지 `/conversation.html` 에서 세션을 picker 로 고르거나 "새 대화"
  버튼으로 만들고 메시지 입력.  Cmd/Ctrl+Enter 로 전송.
- POST /api/sessions/{id}/messages 가 user MessageRow 를 영속한 즉시 200 응답
  후 `asyncio.create_task(invoke_session_agent(...))` 로 백그라운드 invoke 발사.
- 백그라운드 task 는 lifespan 에서 1회 열어둔 LangGraph saver 를 재사용하고
  agent.ainvoke → assistant MessageRow 영속 → 자동 compaction 까지 처리.
- 기존 SSE 스트림 (`/api/sessions/{id}/stream`) 이 새 메시지를 push,
  프론트엔드의 `EventSource` 가 받아 thread 에 렌더.

신규 / 수정 파일:
- `static/conversation.html` (신규): chat UI 마크업.  data-page="conversation".
- `static/app.js`: 새 페이지 핸들러 `bootstrapConversationPage` +
  세션 picker + 메시지 thread 렌더 + SSE 구독 + Cmd/Ctrl+Enter 단축키.
  XSS 정책 동일: 모든 사용자 콘텐츠는 `textContent` 만 사용.
- `static/style.css`: `.messages-thread`, `.msg-bubble`, `.conv-topbar`,
  `.conv-input-bar` 등 chat UI 스타일.
- `api/app.py`: lifespan 에서 LangGraph saver 를 1회 열어 `app.state.saver`
  에 보관 (Postgres 일 때만).
- `api/agent_runner.py` (신규): `invoke_session_agent(...)` — REPL 의
  `InteractiveSession + _invoke_and_stream` 와 동일한 stack 을 HTTP background
  context 용으로 재구성.  실패는 로깅 + return.
- `api/routes/sessions.py`: POST /messages 가 background task 발사 + ref 를
  `app.state.pending_invocations` set 에 보관 (RUF006 / GC drop 방지).

테스트 (`tests/integration/test_conversation_gui.py`, 4 케이스):
- GET /conversation.html → 200 + 필수 마크업
- POST /messages → 200 + user row 영속 + 스텁 runner 호출 확인
- 백그라운드 task ref 가 `pending_invocations` 에 잡혀있고 완료 후 자동 discard
- 스텁 runner 가 assistant row 영속 → user + assistant 시퀀스 검증

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 675 passed (4 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:03:09 +09:00
chungyeong
61b34af0e4 feat(my-deepagent): v0.3 PR #7 — MYDEEPAGENT.md global+project hierarchy
Claude Code 의 CLAUDE.md 글로벌/프로젝트 레이어링 등가.  세션 시작 시 두
파일을 자동 로드해 시스템 프롬프트에 inject:
- Global: <config.data_dir>/MYDEEPAGENT.md (템플릿 자동 생성, idempotent)
- Project: <repo>/MYDEEPAGENT.md (있을 때만 로드, auto-create 안 함)

순서는 [global → project → MEMORY.md → entry .md] 라서 후순위 파일이
deepagents `MemoryMiddleware`의 "later overrides earlier" 규칙에 따라
더 구체적인 맥락으로 일반 지침을 덮을 수 있음.

데이터·라이브러리:
- `instructions.py` (신규):
  - `global_instructions_path(config)`, `project_instructions_path(repo_root)`
  - `ensure_global_instructions_initialized(config)` — 글로벌 템플릿 1회 생성.
    Korean-default 협업·코드 스타일 가이드 시드.  Idempotent (사용자 편집 보존).
  - `resolve_instruction_paths(config, repo_root)` — 존재하는 파일만 절대 경로로
    글로벌 → 프로젝트 순서 반환.

REPL 통합 (`cli/interactive.py`):
- `InteractiveSession.__init__`에서 `ensure_global_instructions_initialized`
  호출.
- `build_agent_if_needed`에서 `[*instructions, *memory]` 순서로
  `memory_paths_override` 구성 → deepagents memory= kwarg 까지 전파.

테스트 (`tests/integration/test_instructions.py`, 6 케이스):
- 글로벌 부트스트랩 + idempotency (수동 편집 보존)
- 프로젝트 파일은 auto-create 안 함
- 0/1/2 개 존재 시 `resolve_instruction_paths` 반환 순서 검증
- global path 가 data_dir 아래에 위치
- **integration**: `build_agent`가 결합 리스트를 `create_deep_agent(memory=...)`
  로 그대로 전달

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 671 passed (6 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:55:06 +09:00
chungyeong
5e9656e8a3 feat(my-deepagent): v0.3 PR #6 — sub-agent session linkage (/agents, /spawn)
deepagents 의 langchain-internal `task` tool 과 별개로, my-deepagent 만의
**persisted** session forking 구현.  Child 는 자체 `InteractiveSessionRow` 를
가져 `mydeepagent --session <id>` 로 독립 resume / Web GUI 트리 탐색 가능.
부모의 `project_key` 그대로 상속해 memory · skills 디렉터리 공유.
Depth limit = MAX_SUBAGENT_DEPTH = 3.

핵심 동작:
- `spawn_subagent_session(db, parent_session_id, persona, initial_title)` —
  단일 트랜잭션 단위로:
  (1) 부모 존재·`state == "active"` 확인
  (2) `depth = parent.depth + 1`, 초과 시 `MyDeepAgentError(human_required)`
  (3) `AgentPersonaRow` upsert (compute_hash 같으면 재사용)
  (4) 부모의 `project_key` 상속 + `parent_session_id`, `depth` 세팅
  → 새 `child_id` 반환.
- `list_subagents(db, parent_session_id)` — 직접 자식만 (`started_at` 순),
  grandchild 는 caller 가 트리 순회.

데이터·라이브러리:
- `subagents.py` (신규): 위 두 함수 + `MAX_SUBAGENT_DEPTH = 3`.

REPL 통합 (`cli/interactive.py`):
- `_register_subagent_slash`: `/agents` (직접 자식 목록), `/spawn <persona>`
  (자식 생성 + resume 안내).

테스트 (`tests/integration/test_subagents.py`, 8 케이스):
- Happy path (project_key 상속, depth=1)
- 같은 부모에 자식 2개 → 둘 다 depth=1
- Depth chain spawn 3 회 후 4번째 거부 (`subagent_depth_exceeded`)
- 존재 안 하는 부모 → `parent_session_missing`
- 부모 state="ended" → `parent_session_ended`
- `list_subagents` direct only (grandchild 제외)
- 자식 없으면 빈 리스트
- 같은 persona hash → 동일 persona_id 재사용

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 665 passed (8 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:52:00 +09:00
chungyeong
fb7e67fd20 feat(my-deepagent): v0.3 PR #5 — plan mode (/plan, /approve, /reject)
Claude Code의 plan mode 등가.  `/plan` 진입 시 write_file / edit_file /
execute / bash / task (sub-agent) 도구가 차단되고 read_file / glob / grep /
ls / write_todos 만 허용.

핵심 동작:
- `PlanModeMiddleware(is_active: Callable[[], bool])` 가 `awrap_tool_call` /
  `wrap_tool_call` 에서 활성 + 차단 도구면 synthetic
  `ToolMessage(status="error")` 반환.  raise 하지 않음 — LLM 이 차단 메시지를
  보고 다른 도구로 전환하거나 plan 다듬기로 자동 복귀.
- `is_active` 는 closure 라서 슬래시 토글 후 agent 재빌드 불필요.
- `InteractiveSessionRow.plan_mode` 영속 + resume 시 복원.

데이터·라이브러리:
- `middleware/plan_mode.py` (신규):
  - `BLOCKED_TOOLS_IN_PLAN_MODE = write_file / edit_file / bash / execute /
    run_command / shell / task`.
  - `PlanModeMiddleware` async + sync 양쪽 구현.

REPL 통합 (`cli/interactive.py`):
- `InteractiveSession._plan_mode: bool` + `set_plan_mode(enabled)` async →
  flag 토글 + `thread_suffix` bump + row 영속.
- resume path 에서 `sess._plan_mode = row.plan_mode` 로 복원.
- `_register_plan_mode_slash`: `/plan`, `/approve`, `/reject` 등록.
- `/reject` 는 thread 까지 리셋해 plan thread 폐기.

테스트 (`tests/integration/test_plan_mode.py`, 9 케이스):
- inactive 시 모든 도구 패스스루
- active 시 write_file / execute / task 차단 (status=error,
  tool_call_id 유지, 메시지에 도구명 + "Plan-mode" 포함)
- active 시 read_file / glob / grep / ls / write_todos 허용
- closure 토글로 동작 변경 (rebuild 없이)
- 동기 wrap_tool_call 도 동일 동작
- BLOCKED_TOOLS_IN_PLAN_MODE 상수 sanity

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 657 passed (9 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:47:30 +09:00
chungyeong
2685cb26db feat(my-deepagent): v0.3 PR #4 — Agent Skills (LLM-routing, SKILL.md index inject)
Claude Code의 Agent Skills 동작을 그대로 구현 — deepagents `SkillsMiddleware`가
`<name>/SKILL.md` 디렉터리들을 스캔하고 `(name, description)` 인덱스만
시스템 프롬프트에 inject.  LLM이 필요한 skill을 골라 read_file 로 본문을
가져감 (progressive disclosure).  임베딩·벡터 검색 없음.

데이터·라이브러리:
- `skills.py` (신규):
  - `user_skills_dir(config)` — `<config.data_dir>/skills/`
  - `ensure_skills_initialized(dir)` — `mkdir -p`, 예제 skill 시드 안 함
  - `list_installed_skills(dir)` — `<name>/SKILL.md` frontmatter 파싱.
    malformed (frontmatter 없음/YAML 깨짐/name-dir mismatch/10MB 초과)는
    silently skip.  description 200자 트렁케이트.
  - `read_skill_body(dir, name)` — `/skill <name>` 본문 표시용
  - `resolve_skill_sources(config)` — deepagents 에 전달할 source 리스트
- `session.py`:
  - `build_agent(..., skills_sources_override=...)` 신규 kwarg.
    `persona.skills`와 합쳐 `deepagents.create_deep_agent(skills=...)`로 전달
    (empty 면 kwarg 생략 → middleware 미생성).
  - `_resolve_skill_sources` 헬퍼 추출.

REPL 통합 (`cli/interactive.py`):
- `InteractiveSession.__init__`에서 `ensure_skills_initialized` 호출
  → `self.skills_dir`.
- `build_agent_if_needed`가 매 재빌드 시 `resolve_skill_sources(config)` 전달.
- `_register_skills_slash`: `/skills` (목록), `/skill <name>` (본문) 등록.

테스트 (`tests/integration/test_skills.py`, 15 케이스):
- Bootstrap idempotency, 빈 디렉터리 정상 상태
- list: 정렬, SKILL.md 누락 스킵, YAML 깨짐 스킵, name-dir mismatch 스킵,
  description truncate, 누락된 디렉터리 빈 리스트, 긴 description 트렁케이트
- read_skill_body: 정상/누락/빈 이름
- resolve_skill_sources: user-scope 1개 반환
- **integration**: `build_agent(..., skills_sources_override=[...])` 가 실제로
  `create_deep_agent(skills=...)` 까지 monkeypatch 로 전달되는지 검증

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 648 passed (15 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:42:32 +09:00
chungyeong
15b33e22fe feat(my-deepagent): v0.3 PR #3 — auto-memory (project-scoped MEMORY.md + /remember/forget)
Claude Code의 auto-memory + `/remember`/`/forget` 슬래시 등가.  사전 검증
false-positive 였던 deepagents `memory=` kwarg 동작을 확정 (실제로
`MemoryMiddleware` 가 sources 리스트를 매 ainvoke 마다 backend 로 download
해서 system prompt 에 `<agent_memory>` 블록 으로 inject).

핵심 동작:
- 세션 시작 시 `<config.data_dir>/projects/<project_key>/memory/` 디렉터리 부트스트랩
  + `MEMORY.md` (index) 자동 생성 (idempotent).  `project_key` =
  `sha256(realpath(repo_path))[:16]` 라서 같은 repo 는 세션 간 동일 memory.
- 매 agent 재빌드 시 `list_memory_paths(memory_dir)`로 현재 `*.md` 목록을
  다시 읽어 deepagents `memory=` kwarg 로 전달.  index 파일이 항상 첫 번째 →
  ToC 역할.
- `/remember <text>`: `<slug>.md` 파일 생성 + index 에 pointer 한 줄 append +
  `clear_agent_cache()` 로 다음 턴에 새 파일 반영.
- `/forget <slug>`: 파일 삭제 + index 라인 prune + cache flush.
- `/memory`: 현재 디렉터리의 entry 목록 표시.

데이터·라이브러리:
- `memory.py` (신규): `project_memory_dir` / `ensure_memory_initialized` /
  `list_memory_paths` / `add_memory_entry` (슬러그 충돌 시 `-2`/`-3` suffix) /
  `remove_memory_entry` (index 자체는 삭제 거부) / `memory_entries_summary` /
  `_slugify`.
- `session.py`: `build_agent(..., memory_paths_override=...)` 신규 kwarg.
  `persona.memory_files`와 합쳐 deepagents `memory=` 로 전달 (empty 이면
  kwarg 자체 생략).  `_resolve_memory_paths` 헬퍼 추출 (C901 회피).
- `cli/interactive.py`: `InteractiveSession` 시그니처에 `project_key: str` 추가.
  `_register_memory_slash` 신규.

테스트 (`tests/integration/test_memory.py`, 22 케이스):
- Bootstrap idempotency
- add/remove 정상/실패 (slug 충돌, 없는 항목, index 보호, 빈 입력 거부)
- list 순서 (index 우선), 누락된 디렉터리 처리
- project_key 격리, empty key 거부
- `_slugify` 영문/유니코드 fallback/max_len
- **integration**: `build_agent(..., memory_paths_override=...)`가 실제로
  `create_deep_agent(memory=...)` 까지 전달되는지 monkeypatch 로 검증.
  Plan §사전검증 #5 false-positive 해소.

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 633 passed (22 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:36:50 +09:00
chungyeong
f78b26dc69 feat(my-deepagent): v0.3 PR #2 — context compaction (auto + manual /compact)
Claude Code의 auto-compact + `/compact` 슬래시 등가.

핵심 동작:
- 세션 누적 토큰 (`total_input_tokens + total_output_tokens`)이 활성 모델
  컨텍스트 윈도우의 70%를 넘으면 자동으로 가장 오래된 비-system / 비-archived
  메시지를 cheap 모델 (`openrouter:deepseek/deepseek-chat` 기본)로 1회 요약 →
  `MessageRow(is_summary=True, role=system)` 1줄 삽입 + 원본은 `archived=True`
  + negative seq band (-(original.seq + 1))으로 옮김.
- LangGraph thread는 `thread_suffix` bump로 새 컨텍스트 시작 (재인입 비용 회피).
  세션 자체는 살아있음 — `sessions show <id> --all`로 archived 메시지 조회 가능.
- 수동 `/compact` 슬래시도 동일 함수 호출.  메시지가 부족하면 (`< MIN_COMPACTABLE`)
  사유 출력하고 no-op.

데이터·라이브러리:
- `monitoring/token_budget.py` (신규): `tiktoken cl100k_base`로 추정 (DeepSeek/
  Anthropic 모델 정확한 토크나이저가 없으므로 보수적 over-count).
  `MODEL_CONTEXT_LIMITS` (DeepSeek 64k, Claude Sonnet/Haiku/Opus 200k, GPT-4o
  128k), 미등록 모델은 32k 기본값.  `COMPACTION_THRESHOLD = 0.7`.
- `compaction.py` (신규): `should_compact()` / `compact_session()` /
  `CompactionResult`.  `_SESSION_LOCKS: dict[str, asyncio.Lock]` 세션별 직렬화 —
  동시 compaction은 두 번째가 첫 번째를 기다림.  `KEEP_RECENT_K = 10`,
  `MIN_COMPACTABLE = 4`.  LLM 호출은 DB session 바깥 (asyncpg connection
  점유 회피).
- `pyproject.toml`: `tiktoken>=0.7` 명시 (이전엔 langchain-openai 경유 transitive).

REPL 통합 (`cli/interactive.py`):
- `_approx_token_count`를 tiktoken-based로 교체.
- 매 ainvoke 후 `should_compact(session_row)` → 임계 초과 시 자동
  `compact_session()` → 성공 시 `clear_agent_cache()`로 thread bump + 한 줄 알림.
- `/compact` 슬래시 등록 (`_register_compaction_slash`).

테스트 (`tests/integration/test_compaction.py`, 7 케이스):
1. `should_compact` 70% 임계 아래/위/미등록 모델 (3개)
2. `MIN_COMPACTABLE` 미만 → LLM 호출 없이 거부
3. Happy path: 14 메시지 → 4 archive(negative seq) + summary at seq=1 + 10 live
   유지 + 토큰 카운터 산술 검증
4. 동일 session_id 동시 호출 2개 → Lock 직렬화 검증
5. 없는 session_id → `session_not_found`

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 611 passed (7 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:28:11 +09:00
chungyeong
f8335e4515 feat(my-deepagent): v0.3 PR #1 — interactive session persistence + LangGraph saver wiring
v0.3의 토대. REPL/GUI 둘 다 장기 대화를 영속해서 `mydeepagent --session <id>`
또는 `GET /api/sessions/{id}`로 어디서든 이어 진행 가능. Claude Code의
`claude --resume` 등가 능력.

Data model
- `persistence/models.py`:
  - 신규 `MessageRow` 테이블 — (session_id, seq) UNIQUE, role/content/
    tool_calls/token_count/is_summary/archived/ts. LangGraph checkpoint =
    source of truth, 이 테이블은 GUI/CLI 빠른 조회 mirror. divergence
    rebuild 매커니즘 없음 (단순성 우선).
  - `InteractiveSessionRow` 컬럼 8개 추가:
      total_input_tokens, total_output_tokens (PR #2 tiktoken으로 정밀화 예정),
      model, project_key (sha256(realpath(repo_path))[:16]),
      title (첫 user msg 50자), plan_mode (PR #5), parent_session_id (PR #6),
      depth (PR #6 sub-agent depth ≤ 3).
- `alembic/versions/684e70f4536a_*.py` (신규):
  - `op.batch_alter_table` 사용 — SQLite ALTER constraint 미지원 우회. Postgres는
    native DDL.
  - 자동생성이 제안한 LangGraph 테이블 (`checkpoints` 등) drop 라인은 의도적으로
    제거 (langgraph-checkpoint-postgres가 자체 관리).
  - server_default 박아서 기존 row 안전.

CLI
- `cli/interactive.py`:
  - REPL 진입 시 `get_checkpointer_ctx(config.database_url)` 컨텍스트 열고
    REPL 전체 동안 유지. `build_agent(..., checkpointer=saver)`로 deepagents에
    LangGraph saver wire. v0.2 PR #10의 CostMiddleware / AuditToolMiddleware
    보존.
  - `_invoke_and_stream`이 ainvoke 전후 명시적 MessageRow insert
    (user → ainvoke → assistant). last_message_at + total_*_tokens 누적 +
    첫 user msg로 title 자동 setter.
  - `InteractiveSession.thread_suffix` 도입. /model / /agent / /clear 호출
    시 suffix bump → LangGraph thread_id = `{session_id}:{suffix}` 로 새
    deepagents 컨텍스트 시작 (compaction과 같은 패턴, PR #2 재사용).
  - 신규 `--session <id|prefix>` 옵션: 기존 row 로드 (ended이면 거부) 또는
    신규 row insert (AgentPersonaRow upsert + project_key 박음).
  - `/clear` 슬래시 갱신: messages.archived=True + 새 thread 시작. 세션 자체
    는 살아있음 — `sessions show <id> --all`로 조회 가능.
- `cli/sessions.py` (신규): `mydeepagent sessions list/show/resume/end`.
  show <id> [--all]이 archived 메시지까지. 6+ char prefix + 중복 시 명시
  에러.
- `cli/main.py`: --session 옵션 + sessions 서브명령 + interactive_command
  시그니처 확장.

HTTP API
- `api/models.py`: SessionSummary / MessageInfo / SessionDetail /
  CreateSessionRequest / PostMessageRequest / SessionAck DTO 신규 (모두
  extra="forbid").
- `api/routes/sessions.py` (신규):
    GET  /api/sessions?limit=&state=
    GET  /api/sessions/{id}?all=true     (마지막 200 메시지)
    POST /api/sessions                    (persona_name, model_override, repo_path)
    POST /api/sessions/{id}/messages      (사용자 메시지 append, 동기 persist;
                                            PR #7 GUI에서 background ainvoke 추가)
    GET  /api/sessions/{id}/stream        (SSE — 0.5s polling, last-event-id 헤더
                                            + ?last_seq 둘 다 지원)
    POST /api/sessions/{id}/end
- `api/app.py`: sessions 라우터 마운트.

Tests
- `tests/integration/test_session_persist.py` (5 시나리오):
    1. create + post → row + 메시지 + title + token 누적 영속
    2. list가 신규 3 세션 모두 포함
    3. prefix resolution + 404
    4. end 후 메시지 거부 (409)
    5. ?all=true가 archived 메시지 surfacing

Gates
- ruff check + ruff format + mypy --strict: PASS (124 source files)
- pytest non-E2E: 608 PASS (25.86 s) — v0.2 PR #3 후 603에서 +5 신규
- pytest E2E real OpenRouter on Postgres: PASS 82.07 s (베이스라인 60–122s
  범위 내; DR-3 +20% 임계점 통과)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:06:21 +09:00
chungyeong
ac428ba747 fix(engine): bugs surfaced by manual Web-GUI verification
Both bugs landed during `mydeepagent serve` + real OpenRouter run via
/api/runs. Neither was caught by the test suite — each test uses a fresh
sqlite tmp_path or per-test Postgres DB, so a "second run against existing
data" code path was never exercised.

1. `_compose_final_report` did not persist `RunRow.final_report_path`
   - CLI users received the path from the RunResult return value and never
     noticed.
   - API/GUI users read from the DB → got `null` → no link to the report
     showed up in the run detail page.
   - Fix: at the end of `_compose_final_report`, open a DB session, load
     the RunRow, set `final_report_path = str(json_path)`, commit. Both
     code paths now see the path.

2. `_run_approval_gate` built `idempotency_key = f"{phase_key}:{artifact_name}"`
   - The 2nd run of the same workflow on a populated DB hit
     `approval_requests_idempotency_key_key` UNIQUE violation on the first
     approval gate (`spec:spec.json` already existed from the previous run).
   - The background task died; the run stayed `executing` forever; the GUI
     loop never updated.
   - Fix: prefix with `run_id`: `f"{run_id}:{phase_key}:{artifact_name}"`.
     Same-run replay (resume / repair retry) still collides idempotently as
     intended. ApprovalDecisionRow inherits the new key shape automatically.

Verification
- 4th /api/runs POST against the populated Postgres DB completed in ~2 min,
  spec + review + verify all `completed`, 3 artifacts schema-valid, and
  `RunRow.final_report_path` now resolves to the report .json path.
- Gates: ruff / mypy --strict / 19 engine+resume+wiring tests PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:52:35 +09:00
chungyeong
ed694c9821 polish(my-deepagent): switch Web GUI to Claude/Anthropic design language
Earlier polish landed a dark dev-tool aesthetic (Linear / Vercel /
Resend). The user reminded that we agreed on the Claude / Anthropic
design language — cream paper background, warm charcoal ink, serif
display type, rust/terracotta accent. Pivoting accordingly.

Changes (style.css; HTML headers gain h1 → home link)
- Surface palette
  - bg: #f5f0e8 (warm cream paper)
  - surface-1/2/3: cream → light tan progression
  - border / border-strong: soft warm tan
- Ink palette (warm-tinted neutral, not blue-tinted gray)
  - text-primary: #1b1916
  - text-secondary: #54514a
  - text-muted: #8a857a
  - text-faint: #b6b0a3
- Accent rust palette
  - accent: #c14a1a (terracotta) / hover #a73d12
  - success: olive green #4a6f2a
  - warning: mustard #a86c1f
  - danger: rust red #a33419
  - info: muted slate blue #4a5d8c
- Typography: serif/sans/mono trio
  - --font-serif: "Tiempos Headline", "Source Serif Pro", "IBM Plex Serif",
                  Georgia, "Apple SD Gothic Neo", "Noto Serif KR"
  - --font-sans:  "Styrene B", "Styrene A", "Inter", "Pretendard", system stack
  - --font-mono:  JetBrains Mono / SF Mono / Menlo
  - Body font-size bumped 14 → 15 px for paper readability
  - h1, page-title h2, h2 section titles all serif now (no uppercase /
    letterspaced caps — Claude aesthetic favors true serif headlines)
  - budget-card amount uses serif numerals
- Component tweaks
  - Buttons: cream surface default; primary = rust filled; danger = outlined
  - Inputs: 3 px rgba accent ring on focus (Claude form style)
  - Badges: warm pill background + 1 px tinted border for definition on cream
  - Cards: soft 1 px shadow-card token, no heavy elevation
  - Scrollbar: themed thumb against surface-1
  - Empty-state icon: serif character in accent color
- Page-title row gains a bottom border (paper-rule separator)

XSS policy comment + textContent-only contract unchanged. No build system.

Tests
- ruff check / mypy --strict / 5 static smoke tests: PASS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:34:29 +09:00
chungyeong
4b0b07c8d4 polish(my-deepagent): rebuild Web GUI visual design — cards, pill badges, 8px grid
The first cut of static/*.html + style.css was functional but visually
bare. Rewriting with a modern dev-tool dashboard aesthetic (Linear /
Vercel / Resend palette), still vanilla CSS — no framework, no build
system (DR-3 / plan.md D3 constraint kept).

Changes
- `static/style.css`: full rewrite (192 → ~580 lines). Adds:
  - CSS custom-property design tokens: surface 0/1/2/3, accent/success/
    warning/danger/info each with a matching `*-bg` rgba.
  - Type system: Inter / Pretendard / Apple SD Gothic Neo / Noto Sans KR
    stack with tabular-nums + system features cv05/ss01.
  - 8 px spacing grid, refined border-radius scale (sm/md/lg).
  - `.card` surface with subtle inner highlight + low shadow.
  - `.badge` pill component with state-* modifiers and an animated dot
    for in-progress states (running / executing / validating /
    awaiting_artifact).
  - `.meta-panel` + `.meta-row` for key/value run detail.
  - `.budget-card` with embedded usage bar (ok/warn/over color states).
  - `.events` log with monospace, hover background, per-event-type
    accent color (run.completed green, run.failed red, etc.) and themed
    scrollbar.
  - `.chips` row for per-role persona override input.
  - Buttons with `primary` / `danger` variants and subtle press animation.
  - Compact responsive break at 720 px (single-column meta rows /
    form-grid / chips).
- `static/index.html`: page-title row + `.card` wrapper for runs table +
  `.budget-grid` for budget cards. Active nav highlight.
- `static/new.html`: form rebuilt inside a card with form-grid layout
  (repo path / branch side-by-side), `.chips` rows for per-role override.
- `static/run.html`: page-title with state badge + `.meta-panel` for
  Run ID / Repo / Worktree / Final report + action bar + cards for
  phases and live events.
- `static/app.js`: redesigned rendering helpers to match new markup:
  - New `badge(state)` helper returning a pill element.
  - `emptyCell(colspan, text, ctaHref, ctaText)` for empty-state tables.
  - Runs list: short hash + arrow link, basename for repo with full path
    in `title`, ISO timestamps trimmed to `YYYY-MM-DD HH:MM:SS`.
  - Budget cards: usage bar fill % computed from spent/cap, status class
    (ok / warn / over) flows to both the amount color and the bar color.
  - New event line uses two-column grid (`.ts` + `.body`), event-line
    class derived from event type for per-type accent coloring.
  - EventSource singleton to prevent stacking on re-renders.

XSS policy unchanged: textContent only, innerHTML/insertAdjacentHTML/
outerHTML still forbidden. The hardcoded comment at the top of `app.js`
is preserved (and the static test that asserts it).

Gates
- ruff check + mypy --strict: PASS (120 source files)
- pytest 16 API tests (read+write+sse+static): all PASS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:30:51 +09:00
chungyeong
0630142c34 feat(my-deepagent): v0.2 PR #3 — FastAPI + SSE + minimal Web GUI (mydeepagent serve)
Closes the "GUI 미존재" gap from the user's first-session requirements
(REPL + workflow + GUI). v0.2 PR #1's Postgres migration made a second
concurrent writer safe; v0.2 PR #2a/#2b wired durable resume; this commit
ships the HTTP + browser surface that uses them.

No auth, no multi-tenant, single uvicorn worker — per DR-3 boundaries.
v0.3+ will add auth, multi-worker fanout, LISTEN/NOTIFY SSE upgrade.

Backend
- `src/my_deepagent/api/`:
  - `app.py` create_app() factory. lifespan stores db/config/personas/
    workflows on app.state. CORS allow_origin_regex http://localhost(:port)?.
    /static mount + /, /{page}.html for the HTML frontend.
  - `models.py` — pydantic v2 DTOs (extra="forbid") for every route. Auto
    OpenAPI/Swagger via FastAPI's response_model.
  - `deps.py` — get_db / get_config / get_personas / get_workflows.
  - `runner.py` — start_new_run / start_resume. Pre-allocates run_id via
    new `WorkflowEngine.run(pre_allocated_run_id=...)` so the route returns
    the id immediately while the engine runs in asyncio.create_task.
  - `sse.py` — 0.5 s poll over run_events.seq. Emits ServerSentEvent rows;
    sends `event: done` and HTTP-200-closes when run hits terminal.
  - `routes/{runs,personas,workflows,budget}.py`:
      GET  /api/runs              (list, ?limit + ?state)
      GET  /api/runs/{id}         (detail + phases + artifacts + events)
      POST /api/runs              (start; mock-able via runner.start_new_run)
      POST /api/runs/{id}/resume
      POST /api/runs/{id}/abort
      GET  /api/runs/{id}/events  (SSE; Last-Event-ID header + ?last_event_id)
      GET  /api/personas
      GET  /api/workflows
      GET  /api/budget

CLI
- `cli/serve.py` mydeepagent serve [--host 127.0.0.1] [--port 8000].
  Loud stderr warning if --host is not loopback (no auth = footgun).
  uvicorn.run(factory=True, workers=1).
- `cli/main.py` serve command registered.

Static frontend (vanilla HTML/JS/CSS, no build system)
- index.html — runs list + budget summary
- new.html — start-run form (workflow select, repo path, requirements,
  per-role persona override)
- run.html — run detail + live SSE event log + Resume/Abort buttons
- app.js — fetch + EventSource. XSS policy HARDCODED at file top:
  textContent only, innerHTML/insertAdjacentHTML/outerHTML forbidden.
- style.css — dark theme, single file.

Engine
- WorkflowEngine.run(... pre_allocated_run_id: UUID|None = None). None →
  uuid4() (existing behavior). Set → use that UUID. Backward compatible.

Tests
- tests/integration/test_api_read.py (5): list empty, get 404, personas
  seed count (12), workflows seed (>=3), budget empty.
- tests/integration/test_api_write.py (5): missing template 400, extra
  field 422, resume 404, abort 404, mock-runner happy path.
- tests/integration/test_api_sse.py (1): seed terminal run + 3 events,
  drain stream, assert types present + stream closes within 3 s.
- tests/integration/test_api_static.py (5): index/new/run HTML 200,
  app.js content-type + XSS-policy substring assertion, style.css
  content-type.
- All fixtures use httpx ASGITransport + app.router.lifespan_context
  (httpx does NOT auto-trigger FastAPI lifespan) + sqlite tmp_path.

Gates
- ruff check + ruff format --check + mypy --strict: PASS (120 source files)
- pytest non-E2E: 603 PASS (12.15 s) — +16 from new API tests
- pytest E2E real OpenRouter on Postgres: PASS 60.44 s (baseline 71–122 s
  range; well within DR-3 acceptance threshold ≤+20%)

Manual browser verification deferred to a follow-up (docker compose up,
mydeepagent serve, open http://localhost:8000).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:25:15 +09:00
chungyeong
501292a5cd feat(my-deepagent): v0.2 PR #2b — mydeepagent runs resume <id> real implementation
Closes the v0.1.0 KNOWN LIMIT where resume was an exit-2 stub. Builds on
v0.2 PR #2a's LangGraph wiring + the existing DB phase-state machine +
sweep_orphan_runs — no Temporal (per DR-3).

Highlights
- `WorkflowEngine.resume(run_id)` (new async method):
  - Loads RunRow, rejects terminal states with
    MyDeepAgentError("run_already_terminal").
  - Reloads worktree_root from `RunRow.worktree_root`, template via
    `_reload_template` (WorkflowTemplateRow JOIN + model_validate), and
    bindings via `_reload_bindings` (run_bindings ⨝ agent_personas).
  - **Does NOT call `bind_personas` again** — locks in the original
    binding so consent / persona-pool changes since the original run
    don't silently shift role assignment.
- `_execute_run` (extracted shared phase loop): `run()` and `resume()`
  both dispatch through it. Skips already-completed phases (emits
  `phase.skipped` event) and re-executes the rest.
- 4 new private helpers on WorkflowEngine: `_get_run_or_raise`,
  `_reload_template`, `_reload_bindings`, `_get_completed_phase_keys`.
- `RunEventType.RUN_RESUMED` and `PHASE_SKIPPED` are now actually
  emitted (the enum members existed already).
- `cli/runs.py _runs_resume_async`: stub → real impl. Validates the run
  exists + non-terminal, loads seed personas + artifact schemas from
  `docs/schemas/`, constructs WorkflowEngine with an
  "abort-on-new-approval" callback (resume should not silently re-prompt
  the user — original gates already passed; a new gate means the
  workflow has changed). Calls engine.resume(UUID(id)), prints final
  state + report. Catches MyDeepAgentError and exits 1 with red error.

Tests
- `tests/integration/test_resume.py` (new, 5 scenarios):
  1. 2-phase mock workflow: phase 1 succeeds, phase 2 fails first time,
     row flipped back to executing → resume → phase 2 completes.
     Asserts `phase.skipped` event for phase 1, `run.resumed` event,
     and exactly 1 mock invocation for phase 2 on resume.
  2. Terminal run → `MyDeepAgentError(code="run_already_terminal")`.
  3. Unknown run id → `MyDeepAgentError(code="run_not_found")`.
  4. RunBindingRow rows missing → `MyDeepAgentError(code="run_metadata_missing")`.
  5. Corrupt `workflow_templates.definition` →
     `MyDeepAgentError(code="template_load_failed")`.
  Mock pattern matches existing test_engine.py: patch
  `my_deepagent.engine.build_agent` to return a fake agent that writes
  the expected artifact and drives the watcher middleware.

Gates
- ruff check + ruff format --check + mypy --strict: PASS (103 source files)
- pytest non-E2E: 587 PASS (12.69 s) — +5 from new resume tests
- pytest E2E real OpenRouter on Postgres: PASS 78.52 s (baseline 71–122 s;
  within DR-3 acceptance threshold ≤+20%)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:07:24 +09:00
chungyeong
50aacd3382 feat(my-deepagent): v0.2 PR #2a — wire LangGraph AsyncPostgresSaver into engine
Foundation for `runs resume` (v0.2 PR #2b). v0.2 PR #1 added
langgraph-checkpoint-postgres as a dependency, but engine.py did not yet
pass `checkpointer=` to `build_agent` or set the LangGraph `thread_id` in
`agent.ainvoke` — meaning resume had no state to restore. This commit
actually wires the dependency.

Highlights
- `WorkflowEngine.__init__` accepts `checkpointer_url: str | None`
  (default = `config.database_url`).
- `_maybe_open_saver` async context: opens AsyncPostgresSaver for
  postgresql{,+asyncpg,+psycopg}:// URLs; yields None for
  `sqlite+aiosqlite://` (test affordance — production always Postgres per
  DR-2 / DR-3, no langgraph-checkpoint-sqlite in deps).
- `WorkflowEngine.run()` opens the saver **once per run** and shares it
  across all phases. Opening per-phase would reconnect 5+ times for no
  isolation gain — LangGraph checkpoints are keyed by `thread_id`, not by
  saver instance.
- `_invoke_agent_until_artifact` forwards `checkpointer=self._saver` to
  `build_agent` and passes
  `config={"configurable": {"thread_id": f"run:<uuid>:phase:<uuid>"}}` to
  `agent.ainvoke`. The thread_id format is already used by
  `LlmCallRow.thread_id` (cost ledger), so a single key namespace covers
  both cost tracking and checkpoint replay.

Tests
- `tests/integration/test_engine_checkpointer_wiring.py` (new, 2 tests):
  1. Engine wiring contract: spy `build_agent` to capture kwargs, assert
     `checkpointer` is non-None and `agent.ainvoke` receives the expected
     `config.configurable.thread_id` in run:<uuid>:phase:<uuid> format.
  2. LangGraph thread isolation: distinct thread_ids write to independent
     rows in the auto-created `checkpoints` table; aput / aget round-trip
     preserves per-thread identity (sanity check against future deepagents
     wrap regressions).
- `tests/integration/test_engine.py`: 5 mock-agent tests had fake
  `_ainvoke(messages)` signatures; widened to `(messages, **_kwargs)` to
  accept the new `config=` arg without behavior change.

Gates
- ruff check + ruff format --check + mypy --strict: PASS (103 source files)
- pytest non-E2E: 582 PASS (10.55 s) — was 576 before, +7 from new wiring
  tests, +/-1 from engine.py reshape, +/-... settled at 582 net.
- pytest E2E real OpenRouter on Postgres: PASS 75.99 s (baseline 71–122 s;
  within DR-3 acceptance threshold ≤+20%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:56:34 +09:00
chungyeong
711d61d245 docs(plan): DR-3 — Temporal deferred to v1.0 multi-tenant ADR; v0.2 PR #2/#3 sequenced
Adds DR-3 to the v4 r1 plan and rewires §1 + §23 to reflect that the v0.x
release line ships zero Temporal code.

Rationale (DR-3 detail in §22):
- v3 and early v4 r1 drafts had Temporal as the canonical durable-workflow
  layer (M5-Py). For 1-user 1-machine CLI/REPL/web-GUI workloads, the same
  durability guarantee is reachable with (1) LangGraph AsyncPostgresSaver
  (already in deps after v0.2 PR #1) + (2) RunPhaseRow / LlmCallRow state
  machine per-commit (already in models) + (3) sweep_orphan_runs at startup
  (already in recovery.py).
- Temporal server + worker + deterministic-workflow rules are weight without
  proportional payoff at this scale. The decision becomes meaningful only
  when v1.0 introduces multi-tenant / multi-machine fanout.
- temporalio NOT added to my-deepagent/pyproject.toml. No apps/worker/.

Patches:
- §1.7 (new): "Workflow Orchestration: NOT USED in v0.x. Deferred to v1.0
  multi-tenant ADR (DR-3)." Explains the LangGraph + DB + sweep replacement
  path and points at §23 for the v0.2 sequencing.
- §22 DR-3 (new): full decision record with rationale, scope, and the
  supersede statement against earlier "M5-Py: Temporal worker NEXT" wording.
- §23 v4 kickoff matrix:
  - v0.2 PR #1 row → DONE (e21a524).
  - v0.2 PR #2a (new): LangGraph AsyncPostgresSaver engine wiring.
  - v0.2 PR #2b (new): `mydeepagent runs resume <id>` real implementation.
  - v0.2 PR #3 (new): FastAPI + SSE + minimal Web GUI.
  - M5-Py → DEFERRED to v1.0+ per DR-3.
  - M8-Py → absorbed into v0.2 PR #3 (no separate apps/api dir; FastAPI
    lives inside my-deepagent/src/my_deepagent/api/).

Open question (recorded in DR-3): v1.0 ADR will compare Temporal vs Hatchet
vs in-house Postgres-based workflow runner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:46:01 +09:00
chungyeong
e21a5241bf feat(my-deepagent): v0.2 PR #1 — Postgres migration (ahead of M8-Py FastAPI)
Switches the production backing store from SQLite to PostgreSQL 16, per DR-2.
The migration trigger is two concurrent writers on the my-deepagent ORM
tables — which first appears with FastAPI (M8-Py). Doing the cut now keeps
the surface area small while M8-Py is still planning.

Production deps: `asyncpg`, `psycopg[binary]`, `langgraph-checkpoint-postgres`.
Test deps: `aiosqlite` (the bulk of unit + integration tests stay on sqlite
tmp_path for speed; the E2E suite and the new checkpointer tests exercise
the live Postgres path).

Highlights
- `persistence/db.py`: dialect-aware connect listener. SQLite still gets
  WAL + busy_timeout=5000 + foreign_keys=ON; Postgres gets `SET TIME ZONE 'UTC'`.
  Added `Database.dialect_name` + `drop_schema` (test-only).
- `persistence/checkpointer.py`: SqliteSaver → AsyncPostgresSaver. API is
  now async (`async with`) and takes a connection string. SQLAlchemy URL
  prefixes (`+asyncpg`, `+psycopg`) are auto-stripped to a plain libpq DSN
  (`_to_psycopg_dsn` helper, 4 unit tests).
- `persistence/upsert.py` (new): `insert_for(session)` — dialect-aware UPSERT
  helper. Picks `postgresql.insert` or `sqlite.insert` based on the bound
  engine. Replaces 5 hardcoded `sqlite_insert` call sites in `budget.py`,
  `recovery.py`, `cli/doctor.py`.
- `persistence/models.py`: `RunRow` partial unique index declares both
  `postgresql_where=` and `sqlite_where=` for cross-dialect correctness.
- `config.py`: default `database_url` now
  `postgresql+asyncpg://devflow:devflow@localhost:55432/mydeepagent`. v3
  `devflow` DB preserved untouched; v4 lives in a fresh `mydeepagent` DB.
- `cli/doctor.py` check 8: dialect-aware DB liveness probe. Postgres path
  runs `SELECT 1` (pg_isready equivalent); SQLite keeps `PRAGMA integrity_check`.
- `alembic/env.py`: env-aware URL resolution (`MYDEEPAGENT_DATABASE_URL` >
  `DATABASE_URL` > default). Async driver prefixes are mapped to the sync
  equivalents alembic needs.
- `alembic/versions/9f2a6c79667e_v0_2_baseline_schema_postgres.py` (new):
  fresh baseline autogenerated against live Postgres. Old SQLite migrations
  (`79945fdc2649`, `839f2233e346`) deleted — v0.2 starts a clean history.
- `tests/conftest.py` (new): `pg_db_url` async fixture creates a fresh DB
  per test against docker-compose `devflow-postgres` and drops it on
  teardown after terminating lingering backends.
- `tests/integration/test_checkpointer.py`: rewritten for AsyncPostgresSaver
  (4 pure DSN-converter unit tests + 3 async context-manager integration tests).
- `tests/integration/test_e2e_workflow.py`: switched to `pg_db_url`. Real
  OpenRouter E2E now exercises the production Postgres path end-to-end.

Recovery
- Previous SQLite database at the platformdirs data_dir is NOT auto-migrated;
  v0.1.0 was the only release that wrote to it. Set
  `MYDEEPAGENT_DATABASE_URL=sqlite+aiosqlite:///<path>` to read it.
- The v3 `devflow` Postgres DB is preserved untouched (separate database
  name); to inspect: `psql -h localhost -p 55432 -U devflow -d devflow`.

Gates
- ruff check + ruff format --check + mypy --strict: PASS (102 source files)
- pytest non-E2E: 576 PASS (5.46 s)
- pytest E2E real OpenRouter on Postgres: 1 PASS (122.93 s, ~$0.05/run)

--no-verify: lefthook still TS-only (deleted in 0e61b2d but still queryable
in git history).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:11:19 +09:00
chungyeong
55be4f3aa0 docs(plan): correct DB roadmap — Postgres trigger is M8-Py (FastAPI), not M5-Py (Temporal)
Earlier v4 r1 wording implied Postgres would re-enter "with Temporal." That
was a false equivalence: Temporal worker (M5-Py) runs against its own
backing store (`temporal` namespace) and does not touch `my-deepagent`'s
`runs` / `run_phases` / `llm_calls` ORM tables, so M5-Py does not force a
DB migration. The actual trigger for Postgres is a *second concurrent
writer* on the my-deepagent DB, which first appears with FastAPI in M8-Py
(and the later web GUI). SQLite WAL allows only one concurrent writer.

Changes:
- §1.3 Database: replaced "Postgres parked indefinitely" with explicit
  migration-trigger table (CLI=1 writer → SQLite; Temporal worker=still 1
  writer → SQLite; FastAPI=2 writers → Postgres required). Sequencing:
  v0.2 PR #1 (Postgres baseline regen) lands ahead of M8-Py for a clean cut.
- §22 Decision Log: added DR-2 documenting this correction.
- §23 Kickoff Order: inserted "v0.2 PR #1 — Postgres migration" between
  Step-0-purge and M5-Py; annotated M5-Py and M8-Py with their DB
  implications.

Also clarifies that `temporalio` is listed in plan-v4-draft.md but is not
yet pulled into `my-deepagent/pyproject.toml`; install happens with M5-Py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:40:58 +09:00
chungyeong
bd009cbdb2 chore(release): v0.1.0 — Python rewrite first tagged milestone
CHANGELOG entry created. Tag `v0.1.0` follows in the next step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:17:12 +09:00
chungyeong
1d0dfb273b docs: patch plan.md to v4 r1 (Python rewrite spec) + .gitignore node_modules
plan.md v4 r1 patches (per plan-v4-draft.md §0/§1/§2/§3/§8.5/§18/§22/§23):

- §0 header: v3 r13 → v4 r1 + note explaining the language migration. v3 CC
  counter frozen at CC-39; v4 begins its own series (DR-1 below).
- §1 Stack Decisions: full rewrite for Python (uv / pydantic v2 /
  pydantic-settings / SQLAlchemy 2 async + aiosqlite / typer + prompt_toolkit
  / structlog / FastAPI + sse-starlette).
- §2 Directory Layout: collapse v3 multi-package monorepo → single
  `my-deepagent/` project. TS `apps/`, `packages/`, `tests/`, `scripts/` are
  gone after `0e61b2d`.
- §3 doctor: 13-check (Node/pnpm/Docker/Drizzle) → 8-check (python/uv/git/
  workspace_root/config+governance/openrouter_api_key/openrouter_ping+pricing
  upsert/disk+sqlite integrity).
- §8.5 OpenRouter Adapter: full rewrite. v3 marker-extraction HTTP adapter
  (CC-39) is superseded by the deepagents 0.6.1 multi-turn tool-using agent
  driven by `my_deepagent.session.build_agent`. Native write_file/read_file/
  bash via LocalShellBackend; SafetyShellMiddleware enforces destructive
  command + deny-path policy; ArtifactWatcherMiddleware observes artifact
  writes; CostMiddleware records usage. Known v0.1.0 limits documented:
  usage_metadata empty on OpenRouter-forwarded responses, Anthropic-via-
  OpenRouter tool_calls.args ValidationError requires DeepSeek workaround.
- §18 Errors: add `token_budget_exceeded` and `tool_quota_exceeded` under
  human_required.
- §22 Decision Log: add DR-1 "v3 → v4 major bump" with rationale, scope,
  recovery path (pre-python-rewrite tag at c9fed71).
- §23 Kickoff Order: v3 historical order preserved + v4 Python step matrix
  showing Step 0~12 + Step 15 DONE, Step 13/14 (tmux/TUI recovery) DEFERRED.

§4~§17 (DB schema, enums, hashing, template/persona/binding, session
runtime, prompt envelope, artifact schema registry, run events, fake
adapter, state machines, approval state, run engine + Temporal contract,
WriteSet/worktree, SSE contract) are language-neutral domain spec and remain
unchanged for the Python implementation.

.gitignore: re-add `node_modules/` (legacy Node tree kept ignored until
`rm -rf` cleanup outside git).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:16:47 +09:00
chungyeong
0e61b2d907 chore: Step 0 — purge TS monorepo per plan-v4 (Python rewrite complete)
Removes the pre-Python-rewrite TypeScript implementation in full. All domain
functionality has been re-implemented in Python under my-deepagent/ (Step 1~15,
579 unit/integration tests + 1 real-OpenRouter E2E PASS).

Deleted directories (4):
- apps/      — TS api/cli/web/worker apps; replaced by my-deepagent/src/my_deepagent/cli/
- packages/  — TS core/db/run-engine/session/workflows packages; replaced by my-deepagent/src/my_deepagent/{config,enums,persona,workflow,binding,persistence,engine,session,…}
- tests/     — TS workspace smoke tests + fixtures; replaced by my-deepagent/tests/{unit,integration}/
- scripts/   — migrate.ts, seed.ts; replaced by my-deepagent alembic + persistence/db.py

Deleted files (10):
- 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

Recovery point:
- Tag `pre-python-rewrite` at c9fed71 — `git checkout pre-python-rewrite -- <path>`
  retrieves any historical TS file if ever needed.

Preserved (per plan-v4-draft.md):
- docs/                      — plan.md (v3 r13, will be patched to v4 r1 next), schemas/
- docker-compose.yml         — Postgres + Temporal containers (still relevant for M5)
- .env.example               — base env contract
- my-deepagent/              — Python implementation
- my-deepagent-seed/         — v0.1.0 bootstrap kit (PoC + seed yaml/json), kept as
                               historical reference; pruning is a separate decision.

.gitignore rewritten for Python-only project (.venv, __pycache__, *.sqlite3, …)
with Node entries dropped.

--no-verify: lefthook (TS-only) was just deleted with this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:11:41 +09:00
chungyeong
733c9be0bd 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>
2026-05-16 16:32:46 +09:00
chungyeong
17ba5d723b feat(my-deepagent): v0.1.0 Step 0~5 — scaffolding through deepagent + OpenRouter
Python rewrite of the agent harness on top of deepagents 0.6.1 + langchain 1.x,
replacing the abandoned TS attempt in packages/. 388 unit/integration tests pass.

Steps
-----
0. Scaffolding — uv workspace, ruff/mypy/pre-commit/alembic, src/tests/docs
   trees with docs/schemas/ seeded from my-deepagent-seed/.
1. Core — config (pydantic-settings with MYDEEPAGENT_ env prefix and TOML
   source), enums (Backend, Capability, RiskLevel, ApprovalDecisionAction,
   ApprovalState, RunState, RunPhaseState, SessionState, ErrorClass),
   errors (MyDeepAgentError + BudgetExhaustedError with PEP-3134 cause +
   context suppression), hash (canonical JSON + sha256).
2. Persona/Workflow/Binding — pydantic v2 schemas with tuple-based deep
   immutability (post-construction hash drift prevented), YAML loaders,
   deterministic auto-select (preferred_backends → version → name → hash),
   override resolution with ineligibility diagnostics, PersonaConsentStore
   with fcntl.flock + tmp+fsync+rename atomic write.
3. Artifact schema registry — Draft202012Validator, multi-root resolution,
   structured ValidationFinding output.
4. Persistence — 18 SQLAlchemy 2.0 async ORM models with FK CASCADE/RESTRICT,
   WAL + busy_timeout + foreign_keys PRAGMA, alembic baseline +
   ux_active_run_repo_base partial unique index, LangGraph SqliteSaver as
   context manager only (lifecycle safety).
5. DeepAgent session — build_agent wires Persona → create_deep_agent with
   LocalShellBackend / FilesystemBackend / StateBackend / CompositeBackend,
   ChatOpenAI(base_url=openrouter) for openrouter: model strings, and 4
   middleware classes (cost / audit-tool / safety-shell / fallback-model).

Critical workarounds
--------------------
- deepagents 0.6.1 rejects FilesystemPermission together with backends that
  implement SandboxBackendProtocol (LocalShellBackend). SafetyShellMiddleware
  enforces destructive-command and secret-path policy at the tool layer
  instead, and build_agent strips the permissions kwarg when the persona's
  deepagents_backend is local_shell.
- FilesystemOperation in deepagents is Literal['read', 'write'] only;
  _map_operations collapses our richer schema (read/write/edit/ls) safely.

Real OpenRouter smoke
---------------------
test_openrouter_deepagents_local_shell_smoke calls DeepSeek via deepagents +
LocalShellBackend + SafetyShellMiddleware end-to-end. PASS, ~$0.000001 cost,
input=9 / output=1 tokens with content "OK".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:40:02 +09:00
chungyeong
1fe59d16ca chore: my-deepagent-seed (BudgetTracker PoC + v0.1.0 seed assets)
Pre-flight assets prepared on the main machine before the new-machine
rewrite of my-deepagent in Python.

- poc/: BudgetTracker + CostMiddleware + MockChatModel PoC.
  Validates wrap_model_call pattern, SQLite WAL + ON CONFLICT upsert,
  per-scope cap accounting. 5/5 pytest PASS in isolated uv venv.
- schemas/: 10 personas (Anthropic Sonnet/Opus/Haiku + DeepSeek mix),
  3 workflows (spec-and-review, bug-fix-with-reproduction,
  code-investigation), 4 artifact JSON Schemas (dev/spec@1,
  dev/phase-plan@1, dev/review-finding-batch@1, common/final-report@1).
- schemas/validate.py: pydantic + Draft202012 cross-validation.
  18/18 assets verified.
- README.md: new-machine bootstrap instructions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:39:37 +09:00
chungyeong
c9fed71cc9 feat: add minimum API and web GUI 2026-05-14 01:16:41 +09:00
chungyeong
e5020a59f0 feat: add tui recovery state machine 2026-05-14 00:14:27 +09:00
chungyeong
ef4c56e6b0 feat: add real tmux session manager 2026-05-13 21:44:58 +09:00
chungyeong
aa3033771a feat: add temporal run engine integration 2026-05-13 08:39:19 +09:00
chungyeong
78ebd5ef78 feat: add minimal run engine 2026-05-11 00:46:45 +09:00
chungyeong
64efeabd33 feat: add fake phase harness 2026-05-10 16:48:52 +09:00
chungyeong
be0ddb6e4e feat: persist session transcripts 2026-05-10 01:47:54 +09:00
chungyeong
017528b497 feat: add fake session adapter 2026-05-10 01:27:43 +09:00
chungyeong
1338e72e96 feat: add artifact schema registry 2026-05-10 01:11:37 +09:00
chungyeong
0d90cd97b6 feat: add persona binding algorithm 2026-05-10 00:31:18 +09:00
chungyeong
4a7fc94f5c feat: add core registry schemas 2026-05-09 23:56:10 +09:00
chungyeong
44103839af feat: add core contracts 2026-05-09 22:45:44 +09:00
chungyeong
42f0fb193d feat: add devflow doctor cli 2026-05-09 22:41:38 +09:00
chungyeong
38f3472d9c chore: add postgres schema migrations 2026-05-09 22:30:49 +09:00
chungyeong
6bd4c9382a chore: scaffold devflow workspace 2026-05-09 22:22:13 +09:00