foreman-plane 0.1.0__tar.gz

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 (35) hide show
  1. foreman_plane-0.1.0/CHANGELOG.md +37 -0
  2. foreman_plane-0.1.0/LICENSE +21 -0
  3. foreman_plane-0.1.0/MANIFEST.in +10 -0
  4. foreman_plane-0.1.0/PKG-INFO +253 -0
  5. foreman_plane-0.1.0/README.md +230 -0
  6. foreman_plane-0.1.0/README.zh.md +216 -0
  7. foreman_plane-0.1.0/docs/IMPLEMENTATION_REPORT.md +184 -0
  8. foreman_plane-0.1.0/docs/PROTOCOL.md +304 -0
  9. foreman_plane-0.1.0/docs/SPEC.md +327 -0
  10. foreman_plane-0.1.0/examples/task_card.md +43 -0
  11. foreman_plane-0.1.0/pyproject.toml +38 -0
  12. foreman_plane-0.1.0/scripts/publish.sh +44 -0
  13. foreman_plane-0.1.0/setup.cfg +4 -0
  14. foreman_plane-0.1.0/src/foreman/__init__.py +13 -0
  15. foreman_plane-0.1.0/src/foreman/acceptance.py +140 -0
  16. foreman_plane-0.1.0/src/foreman/cli.py +292 -0
  17. foreman_plane-0.1.0/src/foreman/config.py +175 -0
  18. foreman_plane-0.1.0/src/foreman/contract.py +234 -0
  19. foreman_plane-0.1.0/src/foreman/errors.py +51 -0
  20. foreman_plane-0.1.0/src/foreman/gate.py +202 -0
  21. foreman_plane-0.1.0/src/foreman/ledger.py +238 -0
  22. foreman_plane-0.1.0/src/foreman/mcp_server.py +233 -0
  23. foreman_plane-0.1.0/src/foreman/runner.py +398 -0
  24. foreman_plane-0.1.0/src/foreman_plane.egg-info/PKG-INFO +253 -0
  25. foreman_plane-0.1.0/src/foreman_plane.egg-info/SOURCES.txt +33 -0
  26. foreman_plane-0.1.0/src/foreman_plane.egg-info/dependency_links.txt +1 -0
  27. foreman_plane-0.1.0/src/foreman_plane.egg-info/entry_points.txt +2 -0
  28. foreman_plane-0.1.0/src/foreman_plane.egg-info/top_level.txt +1 -0
  29. foreman_plane-0.1.0/tests/test_acceptance.py +88 -0
  30. foreman_plane-0.1.0/tests/test_cli.py +158 -0
  31. foreman_plane-0.1.0/tests/test_contract.py +104 -0
  32. foreman_plane-0.1.0/tests/test_gate.py +115 -0
  33. foreman_plane-0.1.0/tests/test_ledger.py +183 -0
  34. foreman_plane-0.1.0/tests/test_mcp_server.py +142 -0
  35. foreman_plane-0.1.0/tests/test_runner.py +115 -0
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
+ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 0.1.0
8
+
9
+ First release.
10
+
11
+ ### Added
12
+
13
+ - **Task card contract** (`foreman.contract`): four markers — `【id】`,
14
+ `【depends_on】`, `【needs_api】`, `【验收】` — tolerant of full-width/half-width
15
+ brackets, casing and colon variants. A marker that yields no legal token is
16
+ reported through an explicit `*_unparsed` flag instead of being silently
17
+ treated as "no dependency".
18
+ - **Two-level dependency gate** (`foreman.gate`): hard dependencies must be
19
+ terminal, `pass` and accepted; soft dependencies only have to exist and are
20
+ reported as `needs_api_pending`. Six blocking reasons: `task_id_missing`,
21
+ `dependency_unparsed`, `dependency_cycle`, `dependency_unknown`,
22
+ `dependency_pending`, `dependency_failed`, `dependency_unaccepted`.
23
+ - **Append-only ledger** (`foreman.ledger`): JSONL events (`dispatch`,
24
+ `running`, `blocked`, `done`, `cancelled`, `replan`), state folding, task
25
+ id idempotency and `UnknownTaskError` for unknown task ids.
26
+ - **Objective artefact acceptance** (`foreman.acceptance`):
27
+ `path::min_bytes=N::sha256=<hex>::contains=<substr>::exit_code=N`.
28
+ - **Execution body adapters** (`foreman.runner`): `claude`, `codex`, `dsh`,
29
+ a `generic` template and custom agents declared in `agents.json`.
30
+ - **MCP server** (`foreman.mcp_server`): JSON-RPC 2.0 over stdio with NDJSON
31
+ framing, three tools (`foreman_dispatch`, `foreman_status`,
32
+ `foreman_cancel`).
33
+ - **CLI** (`foreman.cli`): `dispatch`, `status`, `ledger`, `verify`, `mcp`,
34
+ `config`, with `--json` output and stable exit codes.
35
+ - Zero third-party dependencies, Python >= 3.10, `unittest` test suite.
36
+
37
+ [0.1.0]: https://example.invalid/foreman-plane/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Foreman Plane contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ include LICENSE
2
+ include README.md
3
+ include README.zh.md
4
+ include CHANGELOG.md
5
+ recursive-include docs *.md
6
+ recursive-include examples *.md
7
+ recursive-include scripts *.sh
8
+ recursive-include tests *.py
9
+ global-exclude __pycache__/*
10
+ global-exclude *.py[cod]
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: foreman-plane
3
+ Version: 0.1.0
4
+ Summary: A task governance plane that runs outside the agent harness: dispatch, dependency gates, objective acceptance, append-only ledger.
5
+ Author: Foreman Plane contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Anthony-Vivalink/foreman-plane
8
+ Project-URL: Source, https://github.com/Anthony-Vivalink/foreman-plane
9
+ Project-URL: Issues, https://github.com/Anthony-Vivalink/foreman-plane/issues
10
+ Project-URL: Changelog, https://github.com/Anthony-Vivalink/foreman-plane/blob/main/CHANGELOG.md
11
+ Keywords: agents,mcp,orchestration,task-governance,audit
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Build Tools
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # foreman-plane
25
+
26
+ **A task governance plane that runs *outside* your agent harness.**
27
+
28
+ foreman-plane (the "foreman") is the layer *above* the agent, not inside it.
29
+ Claude Code, Codex, DeepSeek Harness or any CLI agent are pluggable
30
+ execution bodies; the plane owns the parts an agent session cannot own:
31
+ dispatch, dependency gating, objective acceptance and an append-only audit
32
+ trail.
33
+
34
+ ```
35
+ ┌───────────────────────────────────────────┐
36
+ │ foreman-plane (the foreman) │
37
+ │ contract → gate → ledger → acceptance │
38
+ └───┬───────────────┬───────────────┬───────┘
39
+ │ MCP/stdio │ MCP/stdio │ MCP/stdio
40
+ ┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
41
+ │ Claude │ │ Codex │ │ DSH │ ← execution bodies
42
+ └──────────┘ └──────────┘ └──────────┘
43
+ ```
44
+
45
+ * **Cross session** — the ledger outlives every conversation.
46
+ * **Cross instance** — several agents share one task graph.
47
+ * **Cross day** — a task dispatched today can be resumed tomorrow by any body.
48
+ * **Outside the harness** — the plane is a plain process; no agent framework
49
+ has to be patched to use it.
50
+
51
+ Zero third-party dependencies. Python ≥ 3.10. Standard library only.
52
+
53
+ ---
54
+
55
+ ## Quick start
56
+
57
+ ```bash
58
+ # 1. run it straight from the checkout
59
+ cd foreman-plane
60
+ export PYTHONPATH="$PWD/src"
61
+ export FOREMAN_HOME="$PWD/.foreman"
62
+
63
+ # 2. declare one execution body
64
+ cat > "$FOREMAN_HOME/agents.json" <<'JSON'
65
+ {"agents": {"mybot": {"command": "mybot run --prompt {task}"}}}
66
+ JSON
67
+
68
+ # 3. dispatch a task card
69
+ cat > card.md <<'MD'
70
+ 【id】T-20261109-20
71
+ 【depends_on】T-20261109-01
72
+ 【验收】reports/report.md::min_bytes=200
73
+ 写一份报告。
74
+ MD
75
+
76
+ python3 -m foreman.cli dispatch --file card.md --agent mybot --json
77
+ ```
78
+
79
+ `python -m foreman.cli` and the `foreman` console script are equivalent.
80
+
81
+ ---
82
+
83
+ ## The task card
84
+
85
+ A task card is plain text. Four markers may appear anywhere in it:
86
+
87
+ | marker | meaning |
88
+ | --------------- | ------------------------------------------------------------ |
89
+ | `【id】` | task id, `T-YYYYMMDD-NN` |
90
+ | `【depends_on】` | **hard** dependency — the upstream must be done and `pass` |
91
+ | `【needs_api】` | **soft** dependency — an artefact that only has to exist |
92
+ | `【验收】` | acceptance artefact plus optional objective criteria |
93
+
94
+ ```text
95
+ 【id】T-20261109-20
96
+ 【depends_on】T-20261109-01
97
+ 【needs_api】T-20261109-01#/var/lib/foreman/api/order_book.json
98
+ 【验收】reports/order_book_20261109.md::min_bytes=200::contains=结论
99
+
100
+ 请读取 order_book.json,产出分析报告并给出结论小节。
101
+ ```
102
+
103
+ Marker syntax is forgiving (`【id】`, `[id]`, `ID:`, full-width spaces and
104
+ colons all work), but a dependency that cannot be parsed is **never**
105
+ silently dropped: the dispatch is refused with `dependency_unparsed`.
106
+
107
+ ---
108
+
109
+ ## What the plane guarantees
110
+
111
+ ### Hard gate — a task never runs too early
112
+
113
+ A task with `【depends_on】` only starts when every upstream task is
114
+ terminal, passed **and** accepted. Otherwise the plane writes a `blocked`
115
+ event and stops. There is no `dispatch` event for a task that did not run,
116
+ so the ledger cannot show a ghost dispatch.
117
+
118
+ ```text
119
+ 【depends_on】T-20261109-01 → T-20261109-01 done, verdict=pass → dispatch
120
+ → T-20261109-01 still running → blocked(dependency_pending)
121
+ → T-20261109-01 unknown → blocked(dependency_unknown)
122
+ → T-20261109-01 verdict=fail → blocked(dependency_failed)
123
+ → unparsable dependency marker → blocked(dependency_unparsed)
124
+ ```
125
+
126
+ ### Soft gate — a task may start without a missing artefact
127
+
128
+ `【needs_api】` marks artefacts that must be *ready* but do not have to be
129
+ finished. A missing artefact is reported as `needs_api_pending` and the
130
+ task still runs.
131
+
132
+ ### Objective acceptance — the artefact decides, not the model
133
+
134
+ ```text
135
+ 【验收】reports/report.md::min_bytes=200::sha256=<hex>::contains=结论
136
+ ```
137
+
138
+ Supported criteria: `min_bytes`, `sha256`, `contains`, `exit_code`. All
139
+ criteria are ANDed, relative paths resolve against the working directory,
140
+ and an unknown criterion fails closed.
141
+
142
+ ### Append-only audit trail
143
+
144
+ Every decision is one JSON line in `${FOREMAN_HOME}/ledger.jsonl`:
145
+
146
+ ```json
147
+ {"ts":"2026-11-09T09:41:02.113456+00:00","event":"dispatch","task_id":"T-20261109-20","actor":"foreman","depends_on":["T-20261109-01"],"needs_api":[],"acceptance":"reports/report.md::min_bytes=200","needs_api_pending":false,"detail":{}}
148
+ ```
149
+
150
+ Event types: `dispatch`, `running`, `blocked`, `done`, `cancelled`,
151
+ `replan`. History is never rewritten or deleted.
152
+
153
+ ---
154
+
155
+ ## MCP integration
156
+
157
+ The plane ships an MCP server that speaks JSON-RPC 2.0 over stdio with
158
+ NDJSON framing (one JSON object per line). Point any MCP client at it:
159
+
160
+ ```bash
161
+ python3 -m foreman.mcp_server
162
+ ```
163
+
164
+ For a harness that loads MCP servers from a patch file, the entry looks
165
+ like this (the exact field names follow the MCP client in use — only the
166
+ stdio JSON-RPC handshake is fixed by the plane):
167
+
168
+ ```yaml
169
+ # ~/.dsh/profiles/headless/cordis.patch.yml
170
+ mcp:
171
+ servers:
172
+ - name: foreman
173
+ command: python3
174
+ args:
175
+ - -m
176
+ - foreman.mcp_server
177
+ env:
178
+ FOREMAN_HOME: /var/lib/foreman
179
+ ```
180
+
181
+ Three tools are exposed:
182
+
183
+ | tool | purpose |
184
+ | ----------------- | ---------------------------------------------------------- |
185
+ | `foreman_dispatch`| dispatch a task card (parse → gate → run → accept) |
186
+ | `foreman_status` | ledger status of one task or of everything |
187
+ | `foreman_cancel` | cancel a task (unknown ids are rejected) |
188
+
189
+ ---
190
+
191
+ ## CLI
192
+
193
+ ```
194
+ foreman dispatch --file TASK.md [--agent NAME] [--workdir DIR] [--force] [--json]
195
+ foreman status [--task-id ID] [--json]
196
+ foreman ledger list | show | ready | blocked | cancel | replan
197
+ foreman verify --acceptance "<验收串>"
198
+ foreman mcp
199
+ foreman config
200
+ ```
201
+
202
+ Exit codes: `0` success · `2` blocked by the gate · `3` artefact acceptance
203
+ failed · `1` any other error.
204
+
205
+ ---
206
+
207
+ ## Configuration
208
+
209
+ | variable | meaning |
210
+ | ----------------- | ---------------------------------------------------- |
211
+ | `FOREMAN_HOME` | plane home (ledger, runs, agents.json) |
212
+ | `FOREMAN_TIMEOUT` | execution timeout in seconds (default 900) |
213
+ | `FOREMAN_AGENT` | default execution body (default `generic`) |
214
+ | `FOREMAN_ACTOR` | actor recorded in the ledger (default `foreman`) |
215
+
216
+ `FOREMAN_HOME` is resolved as: the environment variable → `$HOME/.foreman`
217
+ → `<cwd>/.foreman`. When `HOME` is not set the plane still works.
218
+
219
+ ---
220
+
221
+ ## How this differs from other approaches
222
+
223
+ * **Against "the agent will remember"** — a session that forgets cannot
224
+ enforce a dependency. The gate is a separate process with its own
225
+ durable state, so it keeps working across sessions, agents and days.
226
+ * **Against "the agent will self-report"** — the verdict is derived from an
227
+ artefact on disk (`min_bytes`, `sha256`, `contains`, `exit_code`), not from
228
+ a sentence in the transcript. A task that promises a report but writes no
229
+ report fails.
230
+ * **Against "one big agent"** — the plane does not choose a model and does
231
+ not care which agent runs a task. Execution bodies are interchangeable
232
+ commands; governance stays in one place.
233
+ * **Against "a workflow engine"** — there is no DAG runtime, no scheduler and
234
+ no background worker. The plane is a ledger plus a gate: it decides
235
+ *whether* a task may start, and leaves *how* to the body that is wired in.
236
+ * **Against "a chat transcript as state"** — the ledger is append-only and
237
+ machine readable, so it can be queried, diffed and audited by something
238
+ that is not an LLM.
239
+
240
+ ---
241
+
242
+ ## Development
243
+
244
+ ```bash
245
+ PYTHONPATH=src python3 -m unittest discover -s tests -v
246
+ ```
247
+
248
+ The specification lives in [`docs/SPEC.md`](docs/SPEC.md), the wire
249
+ contract in [`docs/PROTOCOL.md`](docs/PROTOCOL.md).
250
+
251
+ ## License
252
+
253
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,230 @@
1
+ # foreman-plane
2
+
3
+ **A task governance plane that runs *outside* your agent harness.**
4
+
5
+ foreman-plane (the "foreman") is the layer *above* the agent, not inside it.
6
+ Claude Code, Codex, DeepSeek Harness or any CLI agent are pluggable
7
+ execution bodies; the plane owns the parts an agent session cannot own:
8
+ dispatch, dependency gating, objective acceptance and an append-only audit
9
+ trail.
10
+
11
+ ```
12
+ ┌───────────────────────────────────────────┐
13
+ │ foreman-plane (the foreman) │
14
+ │ contract → gate → ledger → acceptance │
15
+ └───┬───────────────┬───────────────┬───────┘
16
+ │ MCP/stdio │ MCP/stdio │ MCP/stdio
17
+ ┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
18
+ │ Claude │ │ Codex │ │ DSH │ ← execution bodies
19
+ └──────────┘ └──────────┘ └──────────┘
20
+ ```
21
+
22
+ * **Cross session** — the ledger outlives every conversation.
23
+ * **Cross instance** — several agents share one task graph.
24
+ * **Cross day** — a task dispatched today can be resumed tomorrow by any body.
25
+ * **Outside the harness** — the plane is a plain process; no agent framework
26
+ has to be patched to use it.
27
+
28
+ Zero third-party dependencies. Python ≥ 3.10. Standard library only.
29
+
30
+ ---
31
+
32
+ ## Quick start
33
+
34
+ ```bash
35
+ # 1. run it straight from the checkout
36
+ cd foreman-plane
37
+ export PYTHONPATH="$PWD/src"
38
+ export FOREMAN_HOME="$PWD/.foreman"
39
+
40
+ # 2. declare one execution body
41
+ cat > "$FOREMAN_HOME/agents.json" <<'JSON'
42
+ {"agents": {"mybot": {"command": "mybot run --prompt {task}"}}}
43
+ JSON
44
+
45
+ # 3. dispatch a task card
46
+ cat > card.md <<'MD'
47
+ 【id】T-20261109-20
48
+ 【depends_on】T-20261109-01
49
+ 【验收】reports/report.md::min_bytes=200
50
+ 写一份报告。
51
+ MD
52
+
53
+ python3 -m foreman.cli dispatch --file card.md --agent mybot --json
54
+ ```
55
+
56
+ `python -m foreman.cli` and the `foreman` console script are equivalent.
57
+
58
+ ---
59
+
60
+ ## The task card
61
+
62
+ A task card is plain text. Four markers may appear anywhere in it:
63
+
64
+ | marker | meaning |
65
+ | --------------- | ------------------------------------------------------------ |
66
+ | `【id】` | task id, `T-YYYYMMDD-NN` |
67
+ | `【depends_on】` | **hard** dependency — the upstream must be done and `pass` |
68
+ | `【needs_api】` | **soft** dependency — an artefact that only has to exist |
69
+ | `【验收】` | acceptance artefact plus optional objective criteria |
70
+
71
+ ```text
72
+ 【id】T-20261109-20
73
+ 【depends_on】T-20261109-01
74
+ 【needs_api】T-20261109-01#/var/lib/foreman/api/order_book.json
75
+ 【验收】reports/order_book_20261109.md::min_bytes=200::contains=结论
76
+
77
+ 请读取 order_book.json,产出分析报告并给出结论小节。
78
+ ```
79
+
80
+ Marker syntax is forgiving (`【id】`, `[id]`, `ID:`, full-width spaces and
81
+ colons all work), but a dependency that cannot be parsed is **never**
82
+ silently dropped: the dispatch is refused with `dependency_unparsed`.
83
+
84
+ ---
85
+
86
+ ## What the plane guarantees
87
+
88
+ ### Hard gate — a task never runs too early
89
+
90
+ A task with `【depends_on】` only starts when every upstream task is
91
+ terminal, passed **and** accepted. Otherwise the plane writes a `blocked`
92
+ event and stops. There is no `dispatch` event for a task that did not run,
93
+ so the ledger cannot show a ghost dispatch.
94
+
95
+ ```text
96
+ 【depends_on】T-20261109-01 → T-20261109-01 done, verdict=pass → dispatch
97
+ → T-20261109-01 still running → blocked(dependency_pending)
98
+ → T-20261109-01 unknown → blocked(dependency_unknown)
99
+ → T-20261109-01 verdict=fail → blocked(dependency_failed)
100
+ → unparsable dependency marker → blocked(dependency_unparsed)
101
+ ```
102
+
103
+ ### Soft gate — a task may start without a missing artefact
104
+
105
+ `【needs_api】` marks artefacts that must be *ready* but do not have to be
106
+ finished. A missing artefact is reported as `needs_api_pending` and the
107
+ task still runs.
108
+
109
+ ### Objective acceptance — the artefact decides, not the model
110
+
111
+ ```text
112
+ 【验收】reports/report.md::min_bytes=200::sha256=<hex>::contains=结论
113
+ ```
114
+
115
+ Supported criteria: `min_bytes`, `sha256`, `contains`, `exit_code`. All
116
+ criteria are ANDed, relative paths resolve against the working directory,
117
+ and an unknown criterion fails closed.
118
+
119
+ ### Append-only audit trail
120
+
121
+ Every decision is one JSON line in `${FOREMAN_HOME}/ledger.jsonl`:
122
+
123
+ ```json
124
+ {"ts":"2026-11-09T09:41:02.113456+00:00","event":"dispatch","task_id":"T-20261109-20","actor":"foreman","depends_on":["T-20261109-01"],"needs_api":[],"acceptance":"reports/report.md::min_bytes=200","needs_api_pending":false,"detail":{}}
125
+ ```
126
+
127
+ Event types: `dispatch`, `running`, `blocked`, `done`, `cancelled`,
128
+ `replan`. History is never rewritten or deleted.
129
+
130
+ ---
131
+
132
+ ## MCP integration
133
+
134
+ The plane ships an MCP server that speaks JSON-RPC 2.0 over stdio with
135
+ NDJSON framing (one JSON object per line). Point any MCP client at it:
136
+
137
+ ```bash
138
+ python3 -m foreman.mcp_server
139
+ ```
140
+
141
+ For a harness that loads MCP servers from a patch file, the entry looks
142
+ like this (the exact field names follow the MCP client in use — only the
143
+ stdio JSON-RPC handshake is fixed by the plane):
144
+
145
+ ```yaml
146
+ # ~/.dsh/profiles/headless/cordis.patch.yml
147
+ mcp:
148
+ servers:
149
+ - name: foreman
150
+ command: python3
151
+ args:
152
+ - -m
153
+ - foreman.mcp_server
154
+ env:
155
+ FOREMAN_HOME: /var/lib/foreman
156
+ ```
157
+
158
+ Three tools are exposed:
159
+
160
+ | tool | purpose |
161
+ | ----------------- | ---------------------------------------------------------- |
162
+ | `foreman_dispatch`| dispatch a task card (parse → gate → run → accept) |
163
+ | `foreman_status` | ledger status of one task or of everything |
164
+ | `foreman_cancel` | cancel a task (unknown ids are rejected) |
165
+
166
+ ---
167
+
168
+ ## CLI
169
+
170
+ ```
171
+ foreman dispatch --file TASK.md [--agent NAME] [--workdir DIR] [--force] [--json]
172
+ foreman status [--task-id ID] [--json]
173
+ foreman ledger list | show | ready | blocked | cancel | replan
174
+ foreman verify --acceptance "<验收串>"
175
+ foreman mcp
176
+ foreman config
177
+ ```
178
+
179
+ Exit codes: `0` success · `2` blocked by the gate · `3` artefact acceptance
180
+ failed · `1` any other error.
181
+
182
+ ---
183
+
184
+ ## Configuration
185
+
186
+ | variable | meaning |
187
+ | ----------------- | ---------------------------------------------------- |
188
+ | `FOREMAN_HOME` | plane home (ledger, runs, agents.json) |
189
+ | `FOREMAN_TIMEOUT` | execution timeout in seconds (default 900) |
190
+ | `FOREMAN_AGENT` | default execution body (default `generic`) |
191
+ | `FOREMAN_ACTOR` | actor recorded in the ledger (default `foreman`) |
192
+
193
+ `FOREMAN_HOME` is resolved as: the environment variable → `$HOME/.foreman`
194
+ → `<cwd>/.foreman`. When `HOME` is not set the plane still works.
195
+
196
+ ---
197
+
198
+ ## How this differs from other approaches
199
+
200
+ * **Against "the agent will remember"** — a session that forgets cannot
201
+ enforce a dependency. The gate is a separate process with its own
202
+ durable state, so it keeps working across sessions, agents and days.
203
+ * **Against "the agent will self-report"** — the verdict is derived from an
204
+ artefact on disk (`min_bytes`, `sha256`, `contains`, `exit_code`), not from
205
+ a sentence in the transcript. A task that promises a report but writes no
206
+ report fails.
207
+ * **Against "one big agent"** — the plane does not choose a model and does
208
+ not care which agent runs a task. Execution bodies are interchangeable
209
+ commands; governance stays in one place.
210
+ * **Against "a workflow engine"** — there is no DAG runtime, no scheduler and
211
+ no background worker. The plane is a ledger plus a gate: it decides
212
+ *whether* a task may start, and leaves *how* to the body that is wired in.
213
+ * **Against "a chat transcript as state"** — the ledger is append-only and
214
+ machine readable, so it can be queried, diffed and audited by something
215
+ that is not an LLM.
216
+
217
+ ---
218
+
219
+ ## Development
220
+
221
+ ```bash
222
+ PYTHONPATH=src python3 -m unittest discover -s tests -v
223
+ ```
224
+
225
+ The specification lives in [`docs/SPEC.md`](docs/SPEC.md), the wire
226
+ contract in [`docs/PROTOCOL.md`](docs/PROTOCOL.md).
227
+
228
+ ## License
229
+
230
+ MIT — see [`LICENSE`](LICENSE).