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.
- syncmill/__init__.py +11 -0
- syncmill/agents.py +2064 -0
- syncmill/artifact_io.py +201 -0
- syncmill/artifacts.py +1946 -0
- syncmill/board.py +2060 -0
- syncmill/breaker.py +327 -0
- syncmill/cli.py +4986 -0
- syncmill/command_errors.py +32 -0
- syncmill/config.py +1118 -0
- syncmill/dashboard.py +935 -0
- syncmill/gates.py +179 -0
- syncmill/gateway.py +234 -0
- syncmill/git_env.py +99 -0
- syncmill/isolated_session.py +431 -0
- syncmill/matcher.py +127 -0
- syncmill/memory.py +1067 -0
- syncmill/memory_backend.py +1907 -0
- syncmill/memory_migration.py +962 -0
- syncmill/model_routing.py +482 -0
- syncmill/models.py +244 -0
- syncmill/monitor.py +775 -0
- syncmill/phoenix_exporter.py +339 -0
- syncmill/planning.py +512 -0
- syncmill/platform_support.py +14 -0
- syncmill/preflight.py +1129 -0
- syncmill/project.py +1067 -0
- syncmill/report.py +502 -0
- syncmill/review_candidates.py +107 -0
- syncmill/room_compare.py +2380 -0
- syncmill/room_raw.py +484 -0
- syncmill/room_viewer.py +604 -0
- syncmill/rooms.py +4044 -0
- syncmill/session.py +444 -0
- syncmill/strategies/__init__.py +82 -0
- syncmill/strategies/base.py +910 -0
- syncmill/strategies/compete.py +233 -0
- syncmill/strategies/council.py +429 -0
- syncmill/strategies/decompose.py +570 -0
- syncmill/strategies/pipeline.py +221 -0
- syncmill/strategies/route.py +130 -0
- syncmill/supervisor.py +1423 -0
- syncmill/teamfile.py +571 -0
- syncmill/telemetry_hook.py +231 -0
- syncmill/terminal.py +245 -0
- syncmill/tracer.py +548 -0
- syncmill/tui.py +3537 -0
- syncmill/tui_collaboration.py +499 -0
- syncmill/tui_jobs.py +1407 -0
- syncmill/tui_render.py +156 -0
- syncmill/workflow.py +3746 -0
- syncmill/worktree.py +1045 -0
- syncmill-0.2.0.dist-info/METADATA +853 -0
- syncmill-0.2.0.dist-info/RECORD +56 -0
- syncmill-0.2.0.dist-info/WHEEL +4 -0
- syncmill-0.2.0.dist-info/entry_points.txt +2 -0
- 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.
|