syncmill 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. syncmill/__init__.py +11 -0
  2. syncmill/agents.py +2064 -0
  3. syncmill/artifact_io.py +201 -0
  4. syncmill/artifacts.py +1946 -0
  5. syncmill/board.py +2060 -0
  6. syncmill/breaker.py +327 -0
  7. syncmill/cli.py +4986 -0
  8. syncmill/command_errors.py +32 -0
  9. syncmill/config.py +1118 -0
  10. syncmill/dashboard.py +935 -0
  11. syncmill/gates.py +179 -0
  12. syncmill/gateway.py +234 -0
  13. syncmill/git_env.py +99 -0
  14. syncmill/isolated_session.py +431 -0
  15. syncmill/matcher.py +127 -0
  16. syncmill/memory.py +1067 -0
  17. syncmill/memory_backend.py +1907 -0
  18. syncmill/memory_migration.py +962 -0
  19. syncmill/model_routing.py +482 -0
  20. syncmill/models.py +244 -0
  21. syncmill/monitor.py +775 -0
  22. syncmill/phoenix_exporter.py +339 -0
  23. syncmill/planning.py +512 -0
  24. syncmill/platform_support.py +14 -0
  25. syncmill/preflight.py +1129 -0
  26. syncmill/project.py +1067 -0
  27. syncmill/report.py +502 -0
  28. syncmill/review_candidates.py +107 -0
  29. syncmill/room_compare.py +2380 -0
  30. syncmill/room_raw.py +484 -0
  31. syncmill/room_viewer.py +604 -0
  32. syncmill/rooms.py +4044 -0
  33. syncmill/session.py +444 -0
  34. syncmill/strategies/__init__.py +82 -0
  35. syncmill/strategies/base.py +910 -0
  36. syncmill/strategies/compete.py +233 -0
  37. syncmill/strategies/council.py +429 -0
  38. syncmill/strategies/decompose.py +570 -0
  39. syncmill/strategies/pipeline.py +221 -0
  40. syncmill/strategies/route.py +130 -0
  41. syncmill/supervisor.py +1423 -0
  42. syncmill/teamfile.py +571 -0
  43. syncmill/telemetry_hook.py +231 -0
  44. syncmill/terminal.py +245 -0
  45. syncmill/tracer.py +548 -0
  46. syncmill/tui.py +3537 -0
  47. syncmill/tui_collaboration.py +499 -0
  48. syncmill/tui_jobs.py +1407 -0
  49. syncmill/tui_render.py +156 -0
  50. syncmill/workflow.py +3746 -0
  51. syncmill/worktree.py +1045 -0
  52. syncmill-0.2.0.dist-info/METADATA +853 -0
  53. syncmill-0.2.0.dist-info/RECORD +56 -0
  54. syncmill-0.2.0.dist-info/WHEEL +4 -0
  55. syncmill-0.2.0.dist-info/entry_points.txt +2 -0
  56. syncmill-0.2.0.dist-info/licenses/LICENSE +191 -0
@@ -0,0 +1,853 @@
1
+ Metadata-Version: 2.5
2
+ Name: syncmill
3
+ Version: 0.2.0
4
+ Summary: Multi-agent orchestration with shared memory — grind tasks through competing and collaborating AI agents
5
+ Project-URL: Homepage, https://github.com/memtomem/syncmill
6
+ Project-URL: Repository, https://github.com/memtomem/syncmill
7
+ Project-URL: Issues, https://github.com/memtomem/syncmill/issues
8
+ Project-URL: Changelog, https://github.com/memtomem/syncmill/blob/main/CHANGELOG.md
9
+ Author-email: "DAPADA Inc. and syncmill contributors" <contact@dapada.co.kr>
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: agent,ai,cli,mcp,memory,multi-agent,orchestration
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: click>=8.1
22
+ Requires-Dist: mcp<3,>=2.0.0
23
+ Requires-Dist: memtomem[korean,onnx]<0.7,>=0.6.8
24
+ Requires-Dist: pydantic-settings>=2.7
25
+ Requires-Dist: pydantic>=2.10
26
+ Requires-Dist: textual>=8.2.8
27
+ Requires-Dist: tomlkit>=0.15.0
28
+ Provides-Extra: langgraph
29
+ Requires-Dist: fastembed<0.9,>=0.8; extra == 'langgraph'
30
+ Requires-Dist: langgraph-checkpoint-sqlite<4,>=3.1; extra == 'langgraph'
31
+ Provides-Extra: mem0
32
+ Requires-Dist: fastembed<0.9,>=0.8; extra == 'mem0'
33
+ Requires-Dist: mem0ai<2.1,>=2.0.11; extra == 'mem0'
34
+ Provides-Extra: phoenix
35
+ Requires-Dist: arize-phoenix-otel<0.17,>=0.16.1; extra == 'phoenix'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # syncmill
39
+
40
+ Multi-agent orchestration with shared memory — grind tasks through competing and
41
+ collaborating AI coding agents (Claude Code, Codex CLI, Kimi Code).
42
+
43
+ syncmill hands a task to CLI coding agents, gives them a pluggable shared
44
+ long-term memory ([memtomem](https://pypi.org/project/memtomem/) by default,
45
+ optional mem0 or LangGraph SQLite) for context continuity, and
46
+ captures the complete Git state (including dirty tracked and untracked files) in
47
+ a private control clone, then isolates each agent in its own worktree. A central **Supervisor** is the only
48
+ mediator: agents are pure functions (prompt in → result out) and know nothing
49
+ about the selected backend, each other, or the orchestrator.
50
+
51
+ > **Status:** All five orchestration strategies have shipped: **route**
52
+ > (priority fallback), **pipeline** (sequential chain, each agent building on the
53
+ > previous), **compete** (concurrent head-to-head — every agent runs at once in its
54
+ > own worktree and the selection policy keeps a winner), **council** (multi-round propose →
55
+ > critique → synthesize deliberation through shared memory), and **decompose**
56
+ > (split the task into subtasks, run them across agents concurrently, then
57
+ > synthesize the results into one outcome). The architecture is built to grow
58
+ > more. See [CLAUDE.md](https://github.com/memtomem/syncmill/blob/main/CLAUDE.md) for the design invariants.
59
+
60
+ > **New to syncmill?** Start with the **[User Guide](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.md)** — a
61
+ > friendly, task-oriented walkthrough (install → first run → strategies →
62
+ > configuration → troubleshooting). 한국어 안내서:
63
+ > **[처음 시작하는 분들을 위한 가이드](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.ko.md)**. TUI의 7개 탭과
64
+ > 11개 화면은 **[한국어 TUI 화면 안내서](https://github.com/memtomem/syncmill/blob/main/docs/tui-guide.ko.md)**에서 볼 수 있습니다.
65
+ > 실제 프로젝트 대신 버려도 되는 저장소에서 검증하려면
66
+ > **[`/tmp` 안전 테스트 가이드](https://github.com/memtomem/syncmill/blob/main/docs/tmp-test-guide.ko.md)**를 사용하세요.
67
+
68
+ Supported execution platforms: **macOS and Linux**. Repositories containing Git
69
+ submodules are currently rejected before agent execution.
70
+
71
+ ## Install
72
+
73
+ syncmill needs Python 3.12+ and the `claude`, `codex`, or `kimi` CLIs you intend
74
+ to orchestrate, installed and authenticated.
75
+
76
+ ```bash
77
+ uv tool install syncmill # or: pip install syncmill
78
+ syncmill --version
79
+ ```
80
+
81
+ Optional backends are extras: `syncmill[mem0]`, `syncmill[langgraph]`,
82
+ `syncmill[phoenix]`.
83
+
84
+ The supported beginner path is still a source checkout, which is what the guides
85
+ assume — it keeps the repository's own examples and test suite to hand, and lets
86
+ you run against an unreleased change:
87
+
88
+ ```bash
89
+ git clone https://github.com/memtomem/syncmill.git
90
+ cd syncmill
91
+ uv sync
92
+ export SYNCMILL_SRC="$PWD"
93
+ ```
94
+
95
+ Run commands from the **target Git repository**, while `--project` points back
96
+ to this checkout:
97
+
98
+ ```bash
99
+ cd /path/to/my-project
100
+ uv run --project "$SYNCMILL_SRC" syncmill init
101
+ uv run --project "$SYNCMILL_SRC" syncmill doctor
102
+ uv run --project "$SYNCMILL_SRC" syncmill run --dry-run "Add type hints to main.py"
103
+ ```
104
+
105
+ The default memory backend is memtomem. Optional alternatives are installed
106
+ separately:
107
+
108
+ ```bash
109
+ uv sync --project "$SYNCMILL_SRC" --extra mem0
110
+ uv sync --project "$SYNCMILL_SRC" --extra langgraph
111
+ uv sync --project "$SYNCMILL_SRC" --extra phoenix
112
+ ```
113
+
114
+ ### Prerequisites
115
+
116
+ - **Agent CLIs** — install and authenticate whichever you want to use; they are
117
+ invoked as subprocesses:
118
+ - `claude` ([Claude Code](https://claude.com/claude-code))
119
+ - `codex` (Codex CLI)
120
+ - `kimi` ([Kimi Code](https://moonshotai.github.io/kimi-code/) — agent id `kimi-code`)
121
+ A missing or unauthenticated agent simply fails and the next one in priority
122
+ order runs (see [fallback](#how-it-works)).
123
+ - **memtomem embedding backend** (optional but recommended) — memtomem needs a
124
+ working embedding backend for shared memory. syncmill installs
125
+ `memtomem[onnx,korean]`, so the local **ONNX** provider (fastembed, e.g.
126
+ `bge-m3`) and the Korean tokenizer work out of the box; a local **ollama**
127
+ (`ollama pull nomic-embed-text`) or an **OpenAI** key work too. The active
128
+ provider is governed by `~/.memtomem/config.json`.
129
+ If memory is unavailable or misconfigured, syncmill **degrades gracefully** —
130
+ it logs a warning and runs the agents anyway, just without shared context.
131
+ Projects created by `syncmill init` use `.syncmill/memory/`; legacy uninitialized
132
+ use retains the global `~/.memtomem/memtomem.db` default. Explicit deployments
133
+ can set both `memory.db_path` and `memory.memory_dir`.
134
+ - **mem0** (optional alternative) — select `backend = "mem0"` after installing
135
+ the `mem0` extra. It stores verbatim text with `infer=False`, defaults to local
136
+ Qdrant plus multilingual FastEmbed under `~/.syncmill/mem0`, and makes no LLM
137
+ calls. First use downloads the embedding model. Anonymous mem0 telemetry is
138
+ disabled by default. Embedded Qdrant holds an exclusive lock on its configured
139
+ path, so only one process can use the default local store at a time. For
140
+ concurrent runs, `serve`, or `status`, configure a Qdrant server through
141
+ `memory.mem0.config.vector_store.config.url` as shown in
142
+ [`syncmill.toml.example`](https://github.com/memtomem/syncmill/blob/main/syncmill.toml.example); Mem0 history remains local
143
+ under `data_dir`. Mem0 determines telemetry on the process's first `mem0`
144
+ import, so set it before launch and restart the process to change it.
145
+ - **LangGraph BaseStore** (optional alternative) — install the `langgraph` extra
146
+ and select `backend = "langgraph"`. It uses persistent
147
+ `AsyncSqliteStore` plus local multilingual FastEmbed at
148
+ `~/.syncmill/langgraph.db`, stores text verbatim, and makes no network or LLM
149
+ calls after the embedding model is available. Unlike embedded Qdrant, SQLite
150
+ does not impose Mem0's single-client directory lock.
151
+
152
+ ## Usage
153
+
154
+ ```bash
155
+ # Run these examples from the target repository. --project selects the
156
+ # Syncmill source checkout without changing the target repository.
157
+
158
+ # One-time project setup and readiness check
159
+ uv run --project "$SYNCMILL_SRC" syncmill init
160
+ uv run --project "$SYNCMILL_SRC" syncmill doctor # one ready provider is enough
161
+ uv run --project "$SYNCMILL_SRC" syncmill doctor --strict # optional: require every enabled provider
162
+
163
+ # Inspect the exact bounded plan without starting an agent
164
+ uv run --project "$SYNCMILL_SRC" syncmill run --dry-run "Add type hints to main.py"
165
+
166
+ # Run a task through the orchestration pipeline (priority fallback)
167
+ uv run --project "$SYNCMILL_SRC" syncmill run "Add type hints to main.py"
168
+
169
+ # Force a single agent (no fallback)
170
+ uv run --project "$SYNCMILL_SRC" syncmill run "Refactor the auth module" --agent claude
171
+
172
+ # Every winner is retained. Review and explicitly apply it by run id.
173
+ uv run --project "$SYNCMILL_SRC" syncmill runs list
174
+ uv run --project "$SYNCMILL_SRC" syncmill diff <run-id>
175
+ uv run --project "$SYNCMILL_SRC" syncmill apply <run-id>
176
+ uv run --project "$SYNCMILL_SRC" syncmill apply <run-id> --detach # durable detached apply worker (survives this process)
177
+ uv run --project "$SYNCMILL_SRC" syncmill recover [<run-id>] # reconcile a standalone apply interrupted by a crash
178
+
179
+ # Continue the winning Claude/Codex/Kimi provider session in a later invocation.
180
+ # A pending parent patch is restored in isolation and the child patch is cumulative.
181
+ uv run --project "$SYNCMILL_SRC" syncmill continue <run-id> "Now add regression tests"
182
+
183
+ # Optional compatibility export (the durable artifact is still written)
184
+ uv run --project "$SYNCMILL_SRC" syncmill run "Add type hints to main.py" --emit-patch winner.patch
185
+
186
+ # Chain agents as a pipeline — each stage builds on the previous via shared memory
187
+ uv run --project "$SYNCMILL_SRC" syncmill run "Implement the parser, then add tests" --strategy pipeline
188
+
189
+ # Convene a council — agents propose, critique over rounds, then synthesize a winner
190
+ uv run --project "$SYNCMILL_SRC" syncmill run "Design the caching layer" --strategy council
191
+
192
+ # Decompose — split into subtasks, run them across agents concurrently, then synthesize
193
+ uv run --project "$SYNCMILL_SRC" syncmill run "Add a config loader with validation and tests" --strategy decompose
194
+
195
+ # Native workflow — Claude/Codex proposes a validated DAG, but nothing runs yet.
196
+ uv run --project "$SYNCMILL_SRC" syncmill workflow plan "Modernize auth and add migration tests"
197
+ # The exact `millflow:` prompt prefix is a planning-only shorthand.
198
+ uv run --project "$SYNCMILL_SRC" syncmill run "millflow: Modernize auth and add migration tests"
199
+ uv run --project "$SYNCMILL_SRC" syncmill workflow show <workflow-id>
200
+ # This separate command is the explicit execution approval.
201
+ uv run --project "$SYNCMILL_SRC" syncmill workflow run <workflow-id>
202
+ # Or detach the same durable execution and follow it from another shell / Runs tab.
203
+ uv run --project "$SYNCMILL_SRC" syncmill workflow run <workflow-id> --detach
204
+ uv run --project "$SYNCMILL_SRC" syncmill workflow status <workflow-id> --watch
205
+
206
+ # With Toolgraph control-plan preflight enabled, MillFlow freezes a body-free
207
+ # DAG at planning and checks those exact bytes before every run/resume. Each
208
+ # invocation gets separate preflight, stop, and Tracegraph-compatible evidence.
209
+
210
+ # Watch a concurrent run live in tmux — one pane per agent, auto-attached
211
+ uv run --project "$SYNCMILL_SRC" syncmill run "Design the caching layer" --strategy compete --monitor
212
+
213
+ # Drive one agent interactively yourself; syncmill runs the rest (repeatable)
214
+ uv run --project "$SYNCMILL_SRC" syncmill run "Refactor the auth module" --exclude claude
215
+
216
+ # Drive that agent in a DURABLE tmux session — survives detach, reattach by name
217
+ uv run --project "$SYNCMILL_SRC" syncmill session start claude # attach; Ctrl-b d detaches, agent keeps running
218
+ uv run --project "$SYNCMILL_SRC" syncmill session list # show active sessions
219
+ uv run --project "$SYNCMILL_SRC" syncmill session attach claude # reconnect later
220
+ uv run --project "$SYNCMILL_SRC" syncmill session stop claude # (or --all)
221
+
222
+ # Safer project-scoped interactive session in a persistent isolated worktree.
223
+ # Checkpoint exports a normal reviewable run artifact; close removes the worktree.
224
+ uv run --project "$SYNCMILL_SRC" syncmill session open codex --workspace isolated
225
+ uv run --project "$SYNCMILL_SRC" syncmill session isolated-list
226
+ uv run --project "$SYNCMILL_SRC" syncmill session checkpoint <session-id>
227
+ uv run --project "$SYNCMILL_SRC" syncmill session close <session-id>
228
+
229
+ # Compare & Apply room: run several unique providers in isolated worktrees,
230
+ # retain every attempt, select one candidate, then apply it explicitly.
231
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room create "Refactor auth" -a claude -a codex
232
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room round <room-id> --detach
233
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room candidates <room-id>
234
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room compare <room-id> [--round N | --all-rounds] [--json]
235
+ 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]
236
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room diff <candidate-id>
237
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room select <room-id> <candidate-id> [--expected-revision N] [--expected-digest SHA256] [--rationale TEXT]
238
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room apply <room-id> --verify
239
+ # After a worker crash and lease expiry, or an interrupted apply:
240
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room recover <room-id>
241
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room prune --dry-run
242
+
243
+ # Point at another repo, override the per-agent timeout (seconds)
244
+ uv run --project "$SYNCMILL_SRC" syncmill run "Fix the failing test" --repo /path/to/repo --timeout 600
245
+
246
+ # Inspect registered agents (priority order) and their settings
247
+ uv run --project "$SYNCMILL_SRC" syncmill agents
248
+
249
+ # Check the selected memory backend
250
+ uv run --project "$SYNCMILL_SRC" syncmill status
251
+
252
+ # Serve shared memory (and the work-board) read-only to external MCP clients (stdio)
253
+ uv run --project "$SYNCMILL_SRC" syncmill serve
254
+
255
+ # Run with a named team profile ([teams.<name>] in syncmill.toml)
256
+ uv run --project "$SYNCMILL_SRC" syncmill run "Fix the API" --team python-team
257
+
258
+ # Persistent work-board (disabled by default): plan items, drain them, review, done
259
+ SYNCMILL_BOARD__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill board add "Design the API schema" --goal backend
260
+ SYNCMILL_BOARD__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill board import-review-candidates candidates.json
261
+ SYNCMILL_BOARD__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill board run --loop --goal backend
262
+ SYNCMILL_BOARD__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill board update <id> --state done --sign-off
263
+
264
+ # Live TUI over the work-board (needs a TTY)
265
+ SYNCMILL_BOARD__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill dashboard
266
+
267
+ # Full seven-tab Board/Runs/Reports/Teams/Status/Sessions/Rooms command center.
268
+ # It still opens in limited mode when the board is disabled.
269
+ # Supported platforms: macOS and Linux (including browsing and editing).
270
+ uv run --project "$SYNCMILL_SRC" syncmill tui
271
+ ```
272
+
273
+ Detached `apply --detach` and `room round --detach` workers require POSIX process
274
+ and file-locking primitives. Their foreground forms need the same support, so
275
+ neither runs on Windows: execution is macOS and Linux only, and a Windows
276
+ invocation is refused before it can touch any state.
277
+
278
+ For a step-by-step first run with real agent CLIs (including billing/quota,
279
+ worktree isolation, and why `Files changed` is not automatically applied to your
280
+ checkout), see [Running real agents safely](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.md#running-real-agents-safely)
281
+ or the Korean guide's [실제 에이전트로 안전하게 실행하기](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.ko.md#실제-에이전트로-안전하게-실행하기).
282
+
283
+ ## Read-only MCP gateway (`syncmill serve`)
284
+
285
+ syncmill deliberately keeps its **agents** off MCP — the Supervisor is the sole reader/writer
286
+ of memory. `syncmill serve` adds a **one-way (read-only) MCP gateway** so external/interactive
287
+ MCP clients (Claude Code/Desktop, Cursor, …) can *read* syncmill's accumulated `shared`
288
+ knowledge — and, when the work-board is enabled, its board state — without breaking that
289
+ isolation. It is a separate, non-agent consumer: agents still never touch MCP, the Supervisor
290
+ stays the sole writer, and the gateway exposes **only** the `shared` namespace — never any
291
+ agent's private scope.
292
+
293
+ Six read-only tools (no write/delete/index — for memory or the board):
294
+
295
+ | Tool | Purpose |
296
+ |------|---------|
297
+ | `shared_search(query, top_k)` | Ranked excerpts from `shared` (each result carries its chunk id). |
298
+ | `shared_read(chunk_id)` | Full content of one `shared` chunk by id (refuses non-shared ids). |
299
+ | `shared_stats()` | Number of chunks in `shared`. |
300
+ | `board_list(state, goal, limit)` | Work items, most recent first (filterable). |
301
+ | `board_get(item_id)` | One work item in full — state, evidence, audit history. |
302
+ | `board_stats()` | Team-health stats (duplicate work, stalls, false-done). |
303
+
304
+ The `board_*` tools are always registered; with the board disabled they answer
305
+ `the work-board is not enabled on this server` while the shared tools keep working.
306
+
307
+ ```bash
308
+ # stdio (default) — register with an MCP client, e.g. Claude Code:
309
+ claude mcp add syncmill-shared -- uv run --project "$SYNCMILL_SRC" syncmill serve
310
+
311
+ # HTTP (streamable-http) for remote consumers — loopback by default:
312
+ uv run --project "$SYNCMILL_SRC" syncmill serve --http --port 8765 # http://127.0.0.1:8765/mcp
313
+ ```
314
+
315
+ **Security:** HTTP serving exposes read-only shared memory with **no authentication**. The bind
316
+ host defaults to `127.0.0.1` (loopback); a non-loopback `--host` is warned about at startup —
317
+ only use it on a trusted network or behind an authenticated proxy.
318
+
319
+ ## Configuration
320
+
321
+ Config is resolved from three layers, **highest priority first**:
322
+
323
+ 1. `SYNCMILL_*` environment variables.
324
+ 2. A project `syncmill.toml` in the selected target Git root.
325
+ 3. Built-in defaults.
326
+
327
+ So **env always overrides the file, and the file overrides defaults** — every
328
+ field stays env-overridable. The CLI flags `--repo` / `--strategy` / `--timeout`
329
+ sit above all three (an explicit per-invocation override).
330
+ `--repo` means one of two things, by command. To `run` it names the repository to
331
+ **work on**: it loads that project's `syncmill.toml` and pins `repo_root` to the
332
+ path you passed, so the flag beats an exported `SYNCMILL_REPO_ROOT`. To the
333
+ project commands (`diff`, `apply`, `runs *`, `room *`, `session open`, `doctor`,
334
+ …) it names **which project's configuration to load**; that config's `repo_root`
335
+ then resolves normally (env > that file > default) and may legitimately point at
336
+ a different checkout, which is what lets one project direct work at another.
337
+ Either way they resolve at the Git root of the path (defaulting to the current
338
+ directory), so they work from a subdirectory. Commands that take no target
339
+ (`agents`, `team list`, `report list`, `board *`, …) read `syncmill.toml` from
340
+ the invocation directory itself and do not search upward. Invalid target
341
+ configuration or project-state paths are reported instead of falling back to
342
+ caller config. The one exception is
343
+ the legacy `session start --repo`, which takes the path only as the interactive
344
+ agent's working directory: it resolves its config (including `agent_priority`)
345
+ from the invocation directory. Use `session open --workspace isolated` for a
346
+ project-scoped session that reads the target's configuration.
347
+
348
+ ### Environment variables
349
+
350
+ Every field is overridable via `SYNCMILL_*` environment variables (nested fields
351
+ use a `__` delimiter). These are the most common variables;
352
+ [`syncmill.toml.example`](https://github.com/memtomem/syncmill/blob/main/syncmill.toml.example) is a broader copyable reference
353
+ covering the common tables, and command `--help` documents per-invocation flags.
354
+ Neither is exhaustive — the field definitions in `src/syncmill/config.py` are.
355
+
356
+ | Variable | Default | Meaning |
357
+ | --- | --- | --- |
358
+ | `SYNCMILL_AGENT_PRIORITY` | `["claude","codex","kimi-code"]` | Agent order (JSON list) — fallback/stage/planner order and the default concurrent tie-break |
359
+ | `SYNCMILL_DEFAULT_STRATEGY` | `route` | Orchestration strategy (`route`, `pipeline`, `compete`, `council`, or `decompose`; or pass `--strategy`) |
360
+ | `SYNCMILL_COUNCIL__ROUNDS` | `1` | Council critique rounds (≥1) between propose and synthesize |
361
+ | `SYNCMILL_DECOMPOSE__MAX_SUBTASKS` | `8` | Max subtasks the decompose planner may produce (≥1) |
362
+ | `SYNCMILL_WORKFLOW__MAX_PARALLEL` | `4` | Maximum workflow nodes running concurrently |
363
+ | `SYNCMILL_WORKFLOW__MAX_AGENT_CALLS` | `32` | Maximum planner plus node attempts in one workflow |
364
+ | `SYNCMILL_WORKFLOW__MAX_DEPTH` | `6` | Maximum validated DAG depth |
365
+ | `SYNCMILL_WORKFLOW__DEADLINE_SECONDS` | `3600` | Whole-workflow execution deadline |
366
+ | `SYNCMILL_WORKFLOW__LEASE_TIMEOUT_SECONDS` | `1800` | Stale execution-owner lease timeout |
367
+ | `SYNCMILL_WORKFLOW__MAX_PROMPT_BYTES` | `2097152` | Maximum composed node prompt bytes |
368
+ | `SYNCMILL_WORKFLOW__MAX_PATCH_BYTES` | `33554432` | Maximum retained patch bytes per attempt |
369
+ | `SYNCMILL_CLAUDE__WORKFLOW_ENV_ALLOWLIST` | `[]` | Extra exact environment names exposed to Claude workflow calls (JSON list; similarly for Codex/Kimi) |
370
+ | `SYNCMILL_MONITOR__ENABLED` | `false` | Live tmux monitor for concurrent runs (or pass `--monitor`) |
371
+ | `SYNCMILL_REPO_ROOT` | `.` | Default target repo |
372
+ | `SYNCMILL_WORKSPACE__ROOT` | `~/.syncmill/workspaces` | Private snapshot/worktree root |
373
+ | `SYNCMILL_RUNS__ROOT` | `~/.syncmill/projects` | Durable project run store root |
374
+ | `SYNCMILL_CONVERSATIONS__ENABLED` | `true` | Capture and resume provider-native sessions |
375
+ | `SYNCMILL_SELECTION__MODE` | `verified` | Concurrent winner policy: `verified`, `priority`, or `judge` |
376
+ | `SYNCMILL_MEMORY__BACKEND` | `memtomem` | Long-term memory backend: `memtomem`, `mem0`, or `langgraph` |
377
+ | `SYNCMILL_MEMORY__DB_PATH` | `~/.memtomem/memtomem.db` | memtomem SQLite path |
378
+ | `SYNCMILL_MEMORY__MEMORY_DIR` | unset (memtomem default) | Explicit memtomem Markdown directory; set with `DB_PATH` for isolation |
379
+ | `SYNCMILL_MEMORY__MEM0__DATA_DIR` | `~/.syncmill/mem0` | Local mem0 history/embedded-Qdrant directory |
380
+ | `SYNCMILL_MEMORY__MEM0__TELEMETRY` | `false` | Mem0 telemetry value used on the process's first `mem0` import |
381
+ | `SYNCMILL_MEMORY__LANGGRAPH__DB_PATH` | `~/.syncmill/langgraph.db` | Persistent LangGraph SQLite store |
382
+ | `SYNCMILL_INTEGRATION__TRACE__ENABLED` | `false` | Write the body-free OTLP-JSON trace artifact |
383
+ | `SYNCMILL_INTEGRATION__TRACE__EXPORTER` | `file` | `file`, or `otlp-http` to also stream to Phoenix (requires the `phoenix` extra) |
384
+ | `SYNCMILL_INTEGRATION__TRACE__ENDPOINT` | unset | Phoenix OTLP/HTTP endpoint; config overrides `PHOENIX_COLLECTOR_ENDPOINT`, then defaults to `http://127.0.0.1:6006/v1/traces` |
385
+ | `SYNCMILL_INTEGRATION__TRACE__PROJECT_NAME` | unset | Phoenix project; config overrides `PHOENIX_PROJECT_NAME`, then defaults to `syncmill` |
386
+ | `SYNCMILL_MODEL_ROUTING__MODE` | `off` | Difficulty-based model routing: `off` or `observe` (experimental) |
387
+ | `SYNCMILL_MODEL_ROUTING__MODELS` | `{}` | Tier→provider→selector map (JSON), e.g. `{"low":{"codex":"<selector>"}}` |
388
+ | `SYNCMILL_CLAUDE__ENABLED` | `true` | Enable/disable an agent |
389
+ | `SYNCMILL_CLAUDE__TIMEOUT_SECONDS` | `300` | Per-agent timeout |
390
+ | `SYNCMILL_CLAUDE__MAX_TURNS` | `10` | Per-agent max turns |
391
+
392
+ (`CLAUDE`, `CODEX`, `KIMI_CODE` sub-configs all accept `ENABLED`,
393
+ `TIMEOUT_SECONDS`, `MAX_TURNS`. The hyphenated agent id `kimi-code` maps to the
394
+ `KIMI_CODE` env prefix, e.g. `SYNCMILL_KIMI_CODE__TIMEOUT_SECONDS`; `MAX_TURNS`
395
+ is only honored by `claude`.)
396
+
397
+ ### `syncmill.toml`
398
+
399
+ Drop a `syncmill.toml` in the target Git root to set a checked-in project
400
+ baseline. Commands that take `--repo` find it from that root, so a subdirectory
401
+ works; commands that take no target read the invocation directory only and do not
402
+ search upward, so run those from the root.
403
+ Copy [`syncmill.toml.example`](https://github.com/memtomem/syncmill/blob/main/syncmill.toml.example) to get
404
+ started. Every field has the same name as its config field, and `SYNCMILL_*` env
405
+ vars still override it:
406
+
407
+ ```toml
408
+ agent_priority = ["claude", "codex", "kimi-code"]
409
+ default_strategy = "compete"
410
+ repo_root = "."
411
+
412
+ [memory]
413
+ backend = "memtomem"
414
+ db_path = "~/.memtomem/memtomem.db"
415
+ # memory_dir = "~/.syncmill/memtomem-memories" # set with a private db_path
416
+
417
+ # Optional alternative (requires `uv sync --project "$SYNCMILL_SRC" --extra mem0`):
418
+ # backend = "mem0"
419
+ # [memory.mem0]
420
+ # data_dir = "~/.syncmill/mem0"
421
+ # telemetry = false
422
+
423
+ # Optional local BaseStore (requires `uv sync --project "$SYNCMILL_SRC" --extra langgraph`):
424
+ # backend = "langgraph"
425
+ # [memory.langgraph]
426
+ # db_path = "~/.syncmill/langgraph.db"
427
+
428
+ [claude]
429
+ timeout_seconds = 600
430
+ max_turns = 12
431
+
432
+ # Per-agent tables use the FIELD name — so kimi-code is the underscored
433
+ # [kimi_code], matching the SYNCMILL_KIMI_CODE__ env prefix.
434
+ [kimi_code]
435
+ timeout_seconds = 300
436
+ # Optional exact names only; wildcard credential exposure is rejected.
437
+ workflow_env_allowlist = []
438
+
439
+ # Native declarative workflows (`syncmill workflow ...` / exact `millflow:` prefix).
440
+ [workflow]
441
+ planner_agent = "claude" # claude or codex; planning is read-only
442
+ max_parallel = 4
443
+ max_agent_calls = 32
444
+ max_depth = 6
445
+ deadline_seconds = 3600
446
+ lease_timeout_seconds = 1800
447
+ max_prompt_bytes = 2097152
448
+ max_patch_bytes = 33554432
449
+ max_changed_files = 512
450
+
451
+ # Quality gates (#10): each entry is one command, all must exit 0.
452
+ [gate]
453
+ commands = ["pytest -q", "ruff check ."]
454
+
455
+ # Circuit breaker (#8): off by default.
456
+ [breaker]
457
+ enabled = true
458
+ failure_threshold = 3
459
+
460
+ # Capability matching (#9): keyed by the REAL agent id, so kimi-code keeps
461
+ # its hyphen here (unlike the [kimi_code] table above).
462
+ [capabilities.agents]
463
+ codex = ["python", "refactor"]
464
+ kimi-code = ["rust"]
465
+ ```
466
+
467
+ Notes:
468
+ - **Env wins:** `SYNCMILL_DEFAULT_STRATEGY=route` overrides `default_strategy`
469
+ above. Overriding one nested field (e.g. `SYNCMILL_CLAUDE__TIMEOUT_SECONDS`)
470
+ leaves the rest of that table from the file intact (sources deep-merge).
471
+ - **Discovery:** every `--repo <target>` command except the legacy
472
+ `session start` (whose `--repo` is only a working directory) loads
473
+ `<target>/syncmill.toml` — running from repository A against repository B
474
+ uses B's file, never A's (a target without a file resolves env + defaults at
475
+ the target). Commands that take no target read `syncmill.toml` from the
476
+ directory you invoke syncmill in. Missing file → just defaults + env. See
477
+ [Configuration](#configuration) for what the flag selects on each command.
478
+ - **Typo protection:** an unknown *top-level* key in the file is rejected with a
479
+ clear error rather than silently ignored.
480
+
481
+ ### Migrating memory backends
482
+
483
+ Migration is an explicit, source-retaining maintenance workflow. Stop active
484
+ `run`/`serve` processes, plan against empty destination namespaces, then apply
485
+ and verify the immutable snapshot:
486
+
487
+ ```bash
488
+ uv run --project "$SYNCMILL_SRC" syncmill memory migration plan --from memtomem --to mem0
489
+ # Any installed pair is supported, including LangGraph:
490
+ uv run --project "$SYNCMILL_SRC" syncmill memory migration plan --from mem0 --to langgraph
491
+ uv run --project "$SYNCMILL_SRC" syncmill memory migration apply <migration-id>
492
+ uv run --project "$SYNCMILL_SRC" syncmill memory migration verify <migration-id>
493
+ # Deletes only unchanged rows created by that migration:
494
+ uv run --project "$SYNCMILL_SRC" syncmill memory migration rollback <migration-id> --yes
495
+ ```
496
+
497
+ `~/.syncmill/migrations/` must be on a **local filesystem**. `apply`, `verify`,
498
+ and `rollback` serialize on a lock file there, and `flock` over a network mount
499
+ is emulated at best and silently local-only at worst — two hosts can then both
500
+ believe they hold it, both pass the empty-destination check, and each write its
501
+ own target ids. Whichever writes `state.json` last leaves the other's imported
502
+ rows untracked: invisible to `verify` and unreachable by `rollback`.
503
+
504
+ The default scope is `shared` plus `agent-runtime:*`; `board-run:*` is always
505
+ excluded. Artifacts live under `~/.syncmill/migrations/`; `snapshot.json` contains
506
+ the selected memories verbatim in plaintext and is never auto-purged. Protect the
507
+ directory and remove an artifact manually only after its receipt is no longer
508
+ needed. Migration never deletes the source, changes `memory.backend`, or
509
+ dual-writes, so cutover remains an operator decision after verification. The
510
+ plan also binds the artifact to the resolved destination path or Qdrant
511
+ endpoint/collection. Run apply, verify, and rollback with that same destination
512
+ configuration; a mismatch fails before the store or migration ledger is changed.
513
+ Legacy schema-v1 artifacts have no such binding and must be replanned before use.
514
+ If a v1 artifact was already applied, retain it and use its `state.json` target
515
+ IDs with the original destination settings for manual recovery; the v2 CLI will
516
+ not guess a store and delete automatically. Mem0 binding includes its history DB
517
+ because rollback writes a DELETE event there after removing the Qdrant row.
518
+
519
+ ## How it works
520
+
521
+ ```
522
+ User ─▶ Supervisor ─┬─▶ (read) memory backend → inject context into the prompt
523
+ ├─▶ snapshot HEAD + dirty + untracked state in a private clone
524
+ ├─▶ create per-agent worktrees from that immutable snapshot
525
+ ├─▶ Strategy ─▶ Agent subprocess (prompt in → stdout out)
526
+ ├─▶ atomically retain manifest + winner.patch
527
+ ├─▶ (write) memory backend ← share the result
528
+ └─▶ remove worktree
529
+ ```
530
+
531
+ - **Supervisor** is the sole mediator and the only reader/writer of the selected
532
+ memory backend. Backends are exclusive; switching does not migrate or dual-write
533
+ data automatically—the explicit maintenance workflow above performs a copy.
534
+ - **Default memtomem sharing** retains the producer's result in
535
+ `agent-runtime:<id>`, copies it to `shared` with `shared-from=<chunk-id>`
536
+ provenance, and searches the private/shared namespaces as one merged-ranking
537
+ pool. Pipeline stages, council proposals/critiques, decompose plans/subtasks,
538
+ and final results carry `pipeline-stage`, `council-proposal`,
539
+ `council-critique`, `decompose-plan`, `decompose-subtask`, and `task-result`
540
+ tags for exact filtered retrieval.
541
+ mem0 and LangGraph preserve the visible multi-namespace search/share behavior
542
+ through adapter fallbacks, without memtomem's structural provenance link.
543
+ - **Agents** are pure functions: `execute(prompt, workdir, timeout) → TaskResult`.
544
+ Each wraps a real CLI in JSON mode and parses its output.
545
+ - **RouteStrategy** (`route`, default) tries agents in priority order and
546
+ returns the first success; if all fail it returns an aggregated error.
547
+ - **PipelineStrategy** (`pipeline`) runs the agents as sequential stages: each
548
+ stage enriches its prompt with the most relevant prior results from `shared`
549
+ memory (best-effort) and edits the same worktree, so the chain builds on
550
+ itself. It is fail-fast — a stage failure stops the pipeline and reports which
551
+ stage broke.
552
+ - **CompeteStrategy** (`compete`) runs every agent **concurrently**, each in its
553
+ own isolated worktree, from the same enriched prompt, then converges under the
554
+ configured selection policy. The default `verified` policy keeps the first
555
+ gate-passing completed candidate in priority order (deterministic, never finish
556
+ time); `judge` may choose a lower-priority verified candidate.
557
+ All agents actually run (unlike `route`'s short-circuit); if none succeed it
558
+ returns an aggregated error. The winner's diff is surfaced (`files_changed`)
559
+ and shared. Worktrees are normally cleaned up; interrupted or recovery-marked
560
+ runs can retain a workspace for later inspection.
561
+ - **CouncilStrategy** (`council`) runs a multi-round deliberation through shared
562
+ memory: agents **propose** a solution concurrently (one worktree each), then
563
+ **critique** over `SYNCMILL_COUNCIL__ROUNDS` rounds — each reading the council's
564
+ proposals and prior critiques from `shared` and refining its own worktree — then
565
+ every member **synthesizes** a final solution and the winner is picked under the
566
+ same selection policy as `compete`. Deliberation
567
+ flows only through `shared` (agents never see each other's worktrees), so the
568
+ synthesized winner reflects more than one agent's input when shared memory
569
+ surfaces peers (best-effort and relevance-ranked, like the pipeline handoff; a
570
+ degraded memtomem never aborts the council — each member just falls back to its
571
+ own accumulated worktree).
572
+ - **DecomposeStrategy** (`decompose`) splits a compound task into subtasks and runs
573
+ them **across** agents: the highest-priority agent **plans** the breakdown (a JSON
574
+ subtask list; an unparseable/empty plan degrades to running the whole task as one
575
+ subtask), the subtasks are distributed round-robin over the agents and run
576
+ **concurrently** in isolated worktrees (each *completed* subtask publishes its
577
+ result to `shared`; a failed subtask is tolerated and skipped, and synthesis
578
+ proceeds as long as at least one completed), then every agent **synthesizes** the
579
+ subtask results into one solution and the winner is picked under the same
580
+ selection policy as `compete`. Integration is via shared knowledge,
581
+ not a file merge. Capped at `SYNCMILL_DECOMPOSE__MAX_SUBTASKS` subtasks.
582
+ - **Memory is advisory** — best-effort and non-fatal. A broken or missing
583
+ memtomem never aborts a task.
584
+ - **Workspaces** live under `~/.syncmill/workspaces/<run-id>/`. syncmill refuses
585
+ non-Git repositories, unborn repositories, unresolved operations,
586
+ submodules (including clean/initialized ones), and sparse checkouts instead of silently running in an empty temp
587
+ directory. Source branches, the source index, and the source object database
588
+ are not changed while the snapshot is made. A killed run's marked workspace is
589
+ reclaimed by the next normal run once its owner process is gone.
590
+ Recovery-marked workspaces are preserved; PID reuse can delay dead-owner
591
+ cleanup until the one-week safety backstop.
592
+ - **Result preservation:** completed Git results require a successfully captured
593
+ patch, including an explicit empty patch for a no-op. Capture failures retain
594
+ the workspace and mark the run `recovery_required`; they are never treated as
595
+ `no_changes`. Session and room workspaces are excluded from generic run cleanup,
596
+ including workspaces created by older versions.
597
+ - **Provider output:** each invocation allows 32 MiB of combined raw stdout and
598
+ stderr by default. Set `[codex].output_max_bytes` (or the corresponding agent
599
+ table), or `SYNCMILL_CODEX__OUTPUT_MAX_BYTES`, to a positive byte count to change
600
+ it. Overflow terminates that invocation and records a failure; normal strategy
601
+ fallback still applies. Quality gates and apply verification retain only the
602
+ last 4,000 characters for diagnostics while reading their output.
603
+ - **Run artifacts** live under
604
+ `~/.syncmill/projects/<project-id>/runs/<run-id>/`. A successful edit remains
605
+ `pending` until `syncmill apply`; application verifies the project, patch
606
+ digest, unchanged source fingerprint, and `git apply --check`, then writes a
607
+ receipt. Pending artifacts are never auto-pruned.
608
+ - **Integration report/trace retention** only counts and removes run directories
609
+ SyncMill can prove it owns. Unrelated directories in a custom `runs_dir` remain
610
+ untouched and do not count toward `keep_last`, so the physical directory total
611
+ may be higher than that setting.
612
+ - **Interrupted-apply recovery.** `syncmill apply` records `applying` (with an
613
+ owner and apply id) before touching the source and binds the receipt to that
614
+ apply id, so a crash mid-apply is decidable. `syncmill recover [<run-id>]`
615
+ reconciles any stranded standalone apply — finishing a completed one,
616
+ restoring an untouched source to `pending`, or freezing an undecidable one to
617
+ `recovery_required` for inspection — classified from the receipt, patch
618
+ digest, and source fingerprint. A live apply (still holding the repository
619
+ apply lock) is never touched, and room applies are left to `room recover`.
620
+ The `apply` path runs the same reconcile for its own run before retrying, and
621
+ the TUI Runs tab exposes it on `R`.
622
+
623
+ ## Provider-native conversations (`syncmill continue`)
624
+
625
+ Programmatic runs persist the winner's real provider session id. Claude uses
626
+ `--session-id`/`--resume` (and `--fork-session` for `continue --fork`), Codex uses
627
+ `codex exec resume`, and Kimi uses `--session`. Council members retain separate
628
+ sessions by agent and worktree across propose/critique/synthesis phases; isolated
629
+ candidates never share a session.
630
+
631
+ ```bash
632
+ uv run --project "$SYNCMILL_SRC" syncmill conversations list
633
+ uv run --project "$SYNCMILL_SRC" syncmill conversations show <run-id>
634
+ uv run --project "$SYNCMILL_SRC" syncmill continue <run-id> "Address the remaining review finding"
635
+ uv run --project "$SYNCMILL_SRC" syncmill conversations close <run-id>
636
+ ```
637
+
638
+ This is distinct from `syncmill session`, which is a raw interactive CLI kept
639
+ alive by tmux and edits the user's current working tree directly.
640
+
641
+ ## Difficulty-based model routing (experimental)
642
+
643
+ Route a run to a cheaper or stronger **provider-internal model** by task
644
+ difficulty — without changing which agents run, their order, or any strategy
645
+ semantics. You declare the tier→model mapping (syncmill ships no model names);
646
+ the selector is passed as the provider CLI's model flag (`claude --model`,
647
+ `codex -m`, `kimi -m`).
648
+
649
+ ```toml
650
+ [model_routing]
651
+ mode = "observe" # off (default) | observe
652
+
653
+ [model_routing.models.low]
654
+ claude = "<your-cheap-claude-selector>"
655
+ codex = "<your-cheap-codex-selector>"
656
+
657
+ [model_routing.models.high]
658
+ claude = "<your-strong-claude-selector>"
659
+ ```
660
+
661
+ - `syncmill run --difficulty low|medium|high "<prompt>"` **applies** the
662
+ configured selector for that tier immediately (even with `mode = "off"`).
663
+ Unmapped agents fall back to their provider CLI default; if *no* eligible
664
+ agent has a mapping the run fails up front rather than silently no-oping.
665
+ - `mode = "observe"` (or `--difficulty auto`) classifies each run with a
666
+ deterministic prompt heuristic (`prompt-complexity-v1`) and **records** the
667
+ recommendation in the run plan/manifest without passing any model flag — use
668
+ it to calibrate before trusting automatic routing. Routing covers direct
669
+ `syncmill run` (incl. `--monitor`) and `syncmill continue` only; board runs,
670
+ TUI jobs, and rooms deliberately don't route yet.
671
+ - `syncmill continue` keeps **selector affinity**: it resumes with the parent
672
+ run's stored selector (or no flag, for observe/legacy parents) even if your
673
+ mapping has changed since. This is affinity to the selector *string* — a
674
+ provider may re-resolve an alias to a newer model.
675
+ - Rollback is config-only (`mode = "off"` / remove the section), but note the
676
+ two paths that deliberately survive it: an explicit `--difficulty` flag, and
677
+ continuations of runs that stored a selector.
678
+
679
+ Automatic application (`enforce`) is reserved until the observe data proves the
680
+ classifier out — see the [difficulty routing contract](https://github.com/memtomem/syncmill/blob/main/docs/difficulty-model-routing-plan.md)
681
+ and promotion gates.
682
+
683
+ ## Live monitoring (`--monitor`)
684
+
685
+ `--monitor` gives a **live tmux view** of a concurrent run — one pane per agent,
686
+ streaming each agent's output as it happens, auto-attached:
687
+
688
+ ```bash
689
+ uv run --project "$SYNCMILL_SRC" syncmill run "Design the caching layer" -s compete --monitor
690
+ ```
691
+
692
+ It only applies to the concurrent strategies (`compete`, `council`, `decompose`);
693
+ for a sequential strategy, a non-TTY stdout, or a missing `tmux` it prints a notice
694
+ and runs normally (the monitor is a convenience, never a requirement; nothing is
695
+ installed beyond `tmux`).
696
+
697
+ How it works without breaking agent isolation: the agent contract is unchanged
698
+ (agents stay pure `(prompt, workdir, timeout)` functions). Output reaches tmux
699
+ through an ambient streaming seam the concurrent strategies open around each
700
+ fan-out, teeing each agent's output to a per-agent log. Because the orchestration
701
+ is deliberately sync (and `tmux attach` needs the foreground), the outer
702
+ `syncmill run` builds the tmux session and re-runs the *real* orchestration in a
703
+ control pane (the inner process), then attaches; the inner adds a live `tail` pane
704
+ per agent it actually runs and writes the result, which the outer prints on detach.
705
+ Enable persistently with `SYNCMILL_MONITOR__ENABLED=true`. Detaching (`Ctrl-b d`)
706
+ before the run finishes leaves it running in tmux — re-attach with the printed
707
+ `tmux attach -t …`.
708
+
709
+ ## Durable interactive sessions (`syncmill session`)
710
+
711
+ The complement to `--monitor`: where the monitor gives an *ephemeral* view of a
712
+ *programmatic* run, `session` runs an agent's **interactive** CLI (the `claude` /
713
+ `codex` / `kimi` REPL you type into) in a **dedicated tmux session that survives
714
+ detach and reattaches by name** — so a long interactive collaboration outlives a
715
+ closed terminal or dropped SSH.
716
+
717
+ ```bash
718
+ uv run --project "$SYNCMILL_SRC" syncmill session start [AGENT] # launch (or reattach), then attach; default AGENT = first in priority
719
+ uv run --project "$SYNCMILL_SRC" syncmill session list # active sessions + attached/detached state
720
+ uv run --project "$SYNCMILL_SRC" syncmill session attach TARGET # reconnect (TARGET = agent id or full session name)
721
+ uv run --project "$SYNCMILL_SRC" syncmill session stop [TARGET] [--all]
722
+ ```
723
+
724
+ This is the durable form of the interactive-collaboration mode: drive one agent
725
+ yourself (interactive use may be billed differently from programmatic runs) while
726
+ `syncmill run --exclude <agent>` orchestrates the rest, the two meeting in the
727
+ `shared` memory namespace (`session start` prints the exact `mem_add` /
728
+ `mem_agent_search` contract). Detach with `Ctrl-b d` — the agent keeps running in
729
+ tmux; `session attach <agent>` reconnects to the *same* live session.
730
+
731
+ Notes: the interactive session is **not** a syncmill agent — it runs the bare
732
+ binary and never enters the orchestration pipeline, so its edits land in your real
733
+ working tree (collaboration is via shared *knowledge*, not file merges). Every
734
+ subcommand needs `tmux` (it *is* the tmux feature — no fallback, unlike
735
+ `--monitor`); `start`/`attach` also need an interactive terminal. The session runs
736
+ with your current shell's environment (PATH/auth/`SYNCMILL_*`), restored via a
737
+ `0600` snapshot the launcher writes — never via the command line — so a stale tmux
738
+ server can't strip your auth. You can drive an agent that is *disabled* for
739
+ orchestration (`SYNCMILL_CLAUDE__ENABLED=false`); the session lineup is independent
740
+ of the enabled registry.
741
+
742
+ For code-changing work, the newer `session open --workspace isolated` path is the
743
+ safer default. It creates a project-scoped persistent worktree, validates the pane's
744
+ working directory before attaching, and never edits the source checkout directly.
745
+ `session checkpoint` converts its current patch into the same immutable run artifact
746
+ used by `runs list`, `diff`, and `apply`; `session close` removes the isolated
747
+ workspace. The original `start/list/attach/stop` commands remain available as the
748
+ explicit direct-worktree mode.
749
+
750
+ ## Compare & Apply rooms (`syncmill room`)
751
+
752
+ Rooms are an opt-in multi-CLI review loop. A round runs up to `max_parallel`
753
+ different enabled providers in persistent isolated worktrees, waits at a barrier,
754
+ and stores every success, failure, timeout, partial patch, and bounded output. A
755
+ candidate is eligible only after the agent succeeds and its configured quality gate
756
+ passes. Selection and application are separate operations; apply also rejects source
757
+ drift and digest tampering before reusing the normal run-artifact apply path.
758
+
759
+ ```bash
760
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room create "Refactor auth" -a claude -a codex
761
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room round <room-id> --detach
762
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room candidates <room-id>
763
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room compare <room-id> [--round N | --all-rounds] [--json]
764
+ 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]
765
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room diff <candidate-id>
766
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room select <room-id> <candidate-id> [--expected-revision N] [--expected-digest SHA256] [--rationale TEXT]
767
+ SYNCMILL_ROOMS__ENABLED=true uv run --project "$SYNCMILL_SRC" syncmill room apply <room-id> --verify
768
+ ```
769
+
770
+ Later rounds default to `--lineage continue-own`, preserving each provider's own
771
+ worktree and native session. Use `--lineage fresh` for clean baselines or
772
+ `--lineage branch-from --base-candidate <id>` to seed all members from one eligible
773
+ candidate. `continue-own` and `branch-from` resume each member's provider
774
+ conversation (warm prompt caches; a resumed member receives only its peers' new
775
+ outputs as a delta), `fresh` starts cold, `room round --fresh-session` drops the
776
+ stored session ids for one round, and `--fork` (claude members only) forks the
777
+ conversations. When providers report token usage, `room candidates` shows the
778
+ per-candidate cache hit-rate (e.g. `cache 85% (34.2k/40.1k)`). Each round takes
779
+ its own prompt (positional, or `--prompt-file`/`-` for multi-line input — `run`
780
+ and `room create` accept the same flag); omitting it reuses the creation prompt
781
+ with a warning, `room rounds <id>` lists the per-round prompt history, and the
782
+ TUI's `x` opens a round modal (prompt + lineage + base candidate). Round
783
+ prompts get best-effort shared-memory enrichment, and an applied candidate is
784
+ shared back to memory tagged `room-result`. Phase 1 deliberately requires unique providers and supports at most three
785
+ members; it does not yet multiplex several independent instances of the same CLI.
786
+ If a detached worker dies, `room recover <room-id>` moves its expired lease to
787
+ `recovery_required` after confirming that the worker is gone. The same command
788
+ reconciles an `applying` room after its apply owner is interrupted, preserving the
789
+ durable run evidence instead of requiring SQLite repair. Live owners are never
790
+ recovered, and retained worktrees and candidates are not deleted.
791
+ `room prune` applies `[rooms].keep_rooms` and `retention_days` only to terminal
792
+ rooms; active and recovery-required state is never pruned.
793
+
794
+ ## Work-board & team profiles
795
+
796
+ A single `run` preserves its result but does not provide queue state; the
797
+ **work-board** (`SYNCMILL_BOARD__ENABLED=true`,
798
+ off by default) adds durable, structured records of team work: items with a goal,
799
+ state (`planned → running → (blocked | review) → done`), dependencies, verification
800
+ evidence, and a handoff note, in their own SQLite db (`~/.syncmill/board.db`).
801
+ `board run` atomically claims the next dispatchable item and orchestrates it through
802
+ the normal pipeline — agents never touch the board, and marking an item `done`
803
+ requires verification (a passing quality gate, an evidence ref, or a human
804
+ sign-off). `board decide` records decisions agents must not re-litigate — injected
805
+ in full into matching board runs (best-effort: if the decisions can't be read, the
806
+ item runs without them); `board stats` surfaces duplicate work, stalls,
807
+ and false-dones. `board import-review-candidates` turns Tracegraph's body-free
808
+ failure-pattern report into idempotent, human-required `review` items; it never
809
+ runs agents or changes Toolgraph policy. `syncmill dashboard` is the compact live
810
+ board observer/editor. `syncmill tui` is the full seven-tab command center: it can
811
+ add/import/update board work, launch one selected item or a queue in a durable
812
+ background worker, reconnect to bounded local logs after the UI closes, and inspect
813
+ reports, teams, agents, and memory status. `q` closes only the UI; use the Runs tab's
814
+ explicit safe-stop action to interrupt a worker. SyncMill supports macOS and Linux.
815
+ Windows commands are rejected before execution; top-level `--help` and `--version`
816
+ remain available. Detached `x`/`g` execution, reconnect, and Runs `k`/`K` control
817
+ use POSIX `flock`, Unix sockets, and process-group signals. The dashboard's `p` key opens
818
+ recorded run-envelope reports through the same renderer as `report show`. **Team profiles**
819
+ (`[teams.<name>]` in `syncmill.toml`) bundle an agent lineup + strategy +
820
+ capabilities, selected per run (`--team`), by board goal (`board run --goal`), or
821
+ persistently (`team use`). See the user guide:
822
+ [The work-board](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.md#the-work-board) ·
823
+ [Team profiles](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.md#team-profiles) ·
824
+ [워크보드로 팀처럼 일하기](https://github.com/memtomem/syncmill/blob/main/docs/user-guide.ko.md#워크보드로-팀처럼-일하기).
825
+
826
+ ## Development
827
+
828
+ ```bash
829
+ uv sync --project "$SYNCMILL_SRC" --group dev # install pytest
830
+ uv run pytest # run the hermetic test suite (no network, no real agents/DB)
831
+ ```
832
+
833
+ The suite replaces agents and memory with in-memory fakes (see
834
+ `tests/conftest.py`), so it never touches the real `~/.memtomem` DB or the
835
+ `claude`/`codex`/`kimi` binaries.
836
+
837
+ The [2026-09-08 stabilization roadmap](https://github.com/memtomem/syncmill/blob/main/docs/improvement-roadmap-2026-09-08.ko.md)
838
+ reconciles historical handoffs with current source and documents memory GC recovery.
839
+ Opt-in real memory tests use temporary stores: `SYNCMILL_TEST_MEMTOMEM_REAL=1 uv run
840
+ pytest -q tests/test_memory_backend_memtomem_integration.py tests/test_memory_gc_process.py`.
841
+
842
+ ## Contributing
843
+
844
+ Pull requests are welcome. [CONTRIBUTING.md](https://github.com/memtomem/syncmill/blob/main/CONTRIBUTING.md) covers the
845
+ development setup, what a reviewable change looks like, and the one-time
846
+ [Contributor License Agreement](https://github.com/memtomem/syncmill/blob/main/CLA.md) every contributor signs on their first
847
+ pull request.
848
+
849
+ ## License
850
+
851
+ SyncMill is licensed under the Apache License 2.0 — see [LICENSE](https://github.com/memtomem/syncmill/blob/main/LICENSE).
852
+
853
+ Copyright 2026 DAPADA Inc. and syncmill contributors.