Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ uv run syncmill room export <room-id> --include patch:<cand> [--include output:|
# room rounds also record each member's conversation (session cold/resume/fork, checkpoint candidate ids + omitted, memory delivery; v2 adds lessons.selected ids + per-member lessons delivery, readers accept v1+v2) in rounds.conversation_json, written once under the lease before the fan-out (rooms.round_conversation_status binds it to SQLite rows); compare shows per-candidate facts and round counts only (plus the round's selected lesson ids)
# room rounds record their effective starting tree + gate settings (v2 adds the gate's env_allowlist: null = inherited, [] distinct) in rounds.provenance_json (rooms.round_provenance_status, writer and readers share it, readers accept v1+v2; NULL = legacy); compare/export/TUI show them as starting tree / gate settings beside the report-derived gate profile, whose JSON fields are unchanged; a v1 round's environment reads not recorded and never groups as same settings; `room apply --verify` still runs the current `[gate]` and prints a `verify:` notice (`rooms.verify_settings_notice`) unless the round's v2 record equals it
uv run syncmill room repair <cand> --prompt TEXT (--preview | --confirm TOKEN) [--cold] # #173 PR 1: one member runs from its newest completed candidate; the token is `RepairPlan.token` (rooms.py) over a plan rebuilt at confirm; `start_round(repair_guard=)` re-derives revision, parent rules and session in the claim's transaction; provenance v3 (repair rounds only, all-member rounds keep v2; `round_expected_members` narrows conversation records and `_missing_members`); the checkout is rebuilt only when it differs from the parent's patch; foreground only; design: docs/room-targeted-repair-plan.md
uv run syncmill room feedback add <cand> --kind change|question [--file PATH] --text TEXT # #173 PR 2: append-only room_feedback rows; `list <room-id>` / `resolve <id> addressed|not-addressed|withdrawn [--on NEW]` / `carry <id> --to NEW`; status derived by rooms.feedback_status (open/sent/addressed/not_addressed/withdrawn/unknown), never stored; `room repair` sends the open requests (or `--feedback ID`), `--prompt` optional; feedback text stays out of rounds.prompt, room_events and memory
uv run syncmill room select <room-id> <cand> [--expected-revision N --expected-digest SHA256 --rationale TEXT] # verifies the artifact, records a bound selection (rooms.selection_json); apply refuses an inconsistent record
uv run syncmill room judge <room-id> accepted|rejected [--expected-digest SHA256 --rationale TEXT] # #174: append-only human verdict on the APPLIED candidate (room_judgments, bound to candidate + patch digest + applied run; never inferred from select/apply, never touches memory, no state/revision change); rooms.room_outcome_status is the one read model (not_selected / selected_not_applied / applied_unreviewed / accepted / rejected / unknown — a damaged latest record is unknown, never an older verdict) shown by compare/export as room.outcome
uv run syncmill room promote <room-id> --lesson TEXT [--scope GOAL --supersedes ID] # #174 PR 2: needs the board; only a judged room (accepted/rejected); a board decision bound to project_id with provenance_json (room/judgment/verdict/candidate/digest/run) and a unique source_key (retry returns the existing decision, never reactivates a superseded one); `run` shows then injects Board.project_lessons (project-wide scope; a room created with `room create --goal G` (rooms.goal, verbatim, fixed) also gets scope==G lessons via project_lessons(goal=), marked `(goal G)`; board.select_lessons budget; --skip-lesson/--no-lessons, forwarded in _inner_argv) via Supervisor.run(lessons=) AFTER routing classified the raw prompt; board items get unbound + own-project decisions; room rounds: `room round`/`start` show them (cli._show_round_lessons, rooms.round_lessons_fit) and pass RoundLesson pairs to run_round(lessons=) (detached: a second anonymous fd, --lessons-fd); the coordinator never opens the board; lessons sit after the checkpoint, before the task, excerpts budgeted around them; TUI (Rooms `j` verdict / `p` promote, `tui.py` `_confirm_room_judge`/`_confirm_lesson_promote`): the round dialog reads `project_lessons` once on the Textual thread (`RoundLessonsView`), recomputes `select_lessons(skip=unchecked)` live, refuses (never drops) when `round_lessons_fit` fails on `round_prompt`'s fallback rule, and launches with exactly those pairs; promote passes `expected_judgment_id` (checked on the same outcome read as the provenance); the round/verdict/promote dialog callbacks act on the captured room id + store root
Expand Down
2 changes: 1 addition & 1 deletion docs/room-targeted-repair-plan.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Targeted room repair: design for #173

Status: **APPROVED** 2026-11-09. PR 1 (section 9) is implemented; the rest is not. Baseline:
Status: **APPROVED** 2026-11-09. PR 1 and PR 2 (section 9) are implemented; the rest is not. Baseline:
main @ `132e6c4`, before #273 changed what a retained worktree holds. Where that moved a
decision, the section says so. The proposed answers in section 14 are the approved ones, except
where decision 5 says otherwise.
Expand Down
37 changes: 36 additions & 1 deletion docs/user-guide.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,11 @@ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room cand
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room compare <room-id> [--round N | --all-rounds] [--json]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room export <room-id> [--round N | --all-rounds] [--format markdown|json] [--include KIND:TARGET ... (--preview | --confirm TOKEN)] [--output FILE]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room diff <후보-id>
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room repair <후보-id> --prompt "고칠 내용" (--preview | --confirm TOKEN) [--cold]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback add <후보-id> --kind change|question [--file PATH] (--text TEXT | --text-file PATH)
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback list <room-id> [--candidate <후보-id>]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room repair <후보-id> [--prompt "고칠 내용"] [--feedback ID ...] (--preview | --confirm TOKEN) [--cold]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback resolve <피드백-id> addressed|not-addressed|withdrawn [--on <후보-id>]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback carry <피드백-id> --to <후보-id>
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room select <room-id> <후보-id> [--expected-revision N] [--expected-digest SHA256] [--rationale TEXT | --rationale-file PATH]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room apply <room-id> --verify [--expected-revision N]
# 적용한 변경을 써 본 뒤: 받아들이는지 기록
Expand Down Expand Up @@ -1012,6 +1016,37 @@ syncmill room repair <후보-id> --prompt "고칠 내용" --confirm <토큰>
에이전트가 다시 설치할 때까지 실패할 수 있습니다.
- `--detach`와 TUI에서는 아직 수리를 쓸 수 없어요.

프롬프트 하나 대신 후보에 리뷰 피드백을 먼저 남길 수도 있어요. 고칠 요청(change)과
답을 듣고 싶은 질문(question)이고, 각각 후보가 바꾼 파일 하나에 묶을 수 있습니다.
그러면 수리는 그 후보에 열려 있는 요청을 모두 보내고, `--prompt`를 주면 그것도 함께
보내요. `--feedback ID`를 주면 지정한 요청만 보냅니다.

```bash
syncmill room feedback add <후보-id> --kind change --file calc.py --text "예전 이름 유지"
syncmill room feedback add <후보-id> --kind question --text "캐시를 하나 더 둔 이유는?"
syncmill room feedback list <room-id>
syncmill room repair <후보-id> --preview
```

피드백을 쓰는 일은 provider를 부르지 않고, 방의 상태도 revision도 바꾸지 않아요. 그
텍스트는 수리되는 멤버의 프롬프트에만 들어가고, 라운드에 저장되는 프롬프트, 방의 이벤트
로그, 공유 메모리에는 들어가지 않습니다(에이전트가 자기 출력에서 인용할 수는 있어요).
`feedback list`는 요청마다 상태를 보여 주는데, 매번 기록에서 다시 계산합니다.

- `open`: 아직 보내지 않았거나, 실패한 수리로만 보냈어요. 다음 수리가 다시 보냅니다.
- `sent`: 이 후보의 수리가 완료되면서 받았어요.
- `addressed`, `not-addressed`, `withdrawn`: `feedback resolve`로 직접 기록한 결과예요.
요청을 해결 처리하는 건 사람뿐이고, 에이전트 출력에서 해결했다는 주장을 읽지
않습니다. `addressed`와 `not-addressed`에는 확인한, 같은 멤버의 이후 후보를
`--on`으로 지정해요.
- `unknown`: 믿을 수 없는 기록이에요. 예전 상태로 대신 읽지 않습니다. 방의 피드백
기록끼리의 연결이 깨지면(중간 기록이 빠졌거나, resolution이 엉뚱한 요청을 가리키면)
그 방의 요청이 모두 `unknown`이 되고, 더는 피드백을 쓸 수 없어요.

요청은 처음 쓴 후보에 머물러요. 수리로 더 새로운 후보가 생기면 예전 후보는 다시 고칠
수 없으니, `feedback carry <id> --to <새-id>`로 새 후보에 열린 요청으로 다시 씁니다.
파일 지정은 새 후보도 그 파일을 바꿨을 때만 유지돼요.

`room candidates`와 TUI 후보 화면에 `cache 85% (34.2k/40.1k)`처럼 캐시 적중률이
표시됩니다.

Expand Down
39 changes: 38 additions & 1 deletion docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,11 @@ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room cand
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room compare <room-id> [--round N | --all-rounds] [--json]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room export <room-id> [--round N | --all-rounds] [--format markdown|json] [--include KIND:TARGET ... (--preview | --confirm TOKEN)] [--output FILE]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room diff <candidate-id>
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room repair <candidate-id> --prompt "what to change" (--preview | --confirm TOKEN) [--cold]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback add <candidate-id> --kind change|question [--file PATH] (--text TEXT | --text-file PATH)
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback list <room-id> [--candidate <candidate-id>]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room repair <candidate-id> [--prompt "what to change"] [--feedback ID ...] (--preview | --confirm TOKEN) [--cold]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback resolve <feedback-id> addressed|not-addressed|withdrawn [--on <candidate-id>]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room feedback carry <feedback-id> --to <candidate-id>
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room select <room-id> <candidate-id> [--expected-revision N] [--expected-digest SHA256] [--rationale TEXT | --rationale-file PATH]
SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room apply <room-id> --verify [--expected-revision N]
# after you have lived with the applied change: record whether you accept it
Expand Down Expand Up @@ -1110,6 +1114,39 @@ their candidates stay as they were, and your source is not touched.
dependencies. A gate that relies on them can fail until the agent reinstalls them.
- `--detach` and the TUI do not offer repair yet.

Instead of one prompt, you can write review feedback on the candidate first: change
requests, and questions you want answered, each optionally tied to a file the candidate
changed. A repair then sends every open request on that candidate, plus `--prompt` if you
give one; `--feedback ID` sends only the requests you name.

```bash
syncmill room feedback add <candidate-id> --kind change --file calc.py --text "keep the old name"
syncmill room feedback add <candidate-id> --kind question --text "why a second cache?"
syncmill room feedback list <room-id>
syncmill room repair <candidate-id> --preview
```

Writing feedback calls no provider and changes neither the room's state nor its revision.
Its text goes only to the repaired member's prompt: not to the round's stored prompt, the
room's event log or shared memory (an agent may still quote it in its own output).
`feedback list` shows each request's status, worked out from the records each time:

- `open`: not sent yet, or only sent by repairs that failed. The next repair sends it again.
- `sent`: a completed repair of this candidate received it.
- `addressed`, `not-addressed`, `withdrawn`: what you recorded with `feedback resolve`.
Only you resolve a request; nothing reads an agent's output for a claim that it did.
`addressed` and `not-addressed` name the later candidate of the same member you
checked (`--on`).
- `unknown`: a record that cannot be trusted. It is never read as an older status. If
the links between the room's feedback records are damaged (a record missing from the
middle, a resolution pointing at the wrong request), every request in the room reads
`unknown` and no more feedback can be written there.

A request stays on the candidate it was written for. Once a repair produced a newer
candidate, the old one cannot be repaired again: `feedback carry <id> --to <new-id>`
writes the request again on the new candidate as an open request, and keeps the file only
if the new candidate still changes it.

When a provider reports token usage, `room candidates` and the TUI candidate
views show it — e.g. `cache 85% (34.2k/40.1k)` — so you can see the cache
hit-rate per member per round.
Expand Down
Loading
Loading