spice-harness 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.
- spice_harness-0.1.0/LICENSE +21 -0
- spice_harness-0.1.0/PKG-INFO +181 -0
- spice_harness-0.1.0/README.md +165 -0
- spice_harness-0.1.0/pyproject.toml +55 -0
- spice_harness-0.1.0/setup.cfg +4 -0
- spice_harness-0.1.0/spice/__main__.py +10 -0
- spice_harness-0.1.0/spice/agent/SKILL.md +42 -0
- spice_harness-0.1.0/spice/agent/activation.py +60 -0
- spice_harness-0.1.0/spice/agent/cli.py +173 -0
- spice_harness-0.1.0/spice/agent/driver.py +206 -0
- spice_harness-0.1.0/spice/agent/gitshadow.py +182 -0
- spice_harness-0.1.0/spice/agent/identity.py +48 -0
- spice_harness-0.1.0/spice/agent/lifecycle.py +724 -0
- spice_harness-0.1.0/spice/agent/maximcli.py +218 -0
- spice_harness-0.1.0/spice/agent/maxims.py +339 -0
- spice_harness-0.1.0/spice/agent/renewal.py +119 -0
- spice_harness-0.1.0/spice/agent/sidechannel.py +226 -0
- spice_harness-0.1.0/spice/agent/watchdog.py +242 -0
- spice_harness-0.1.0/spice/agent/wrap.py +546 -0
- spice_harness-0.1.0/spice/cli/entry.py +94 -0
- spice_harness-0.1.0/spice/cli/mounts.py +97 -0
- spice_harness-0.1.0/spice/cli/parser.py +81 -0
- spice_harness-0.1.0/spice/cli/recovery.py +86 -0
- spice_harness-0.1.0/spice/config.py +313 -0
- spice_harness-0.1.0/spice/configcli.py +151 -0
- spice_harness-0.1.0/spice/errors.py +11 -0
- spice_harness-0.1.0/spice/flexstate.py +119 -0
- spice_harness-0.1.0/spice/hooks/cli.py +120 -0
- spice_harness-0.1.0/spice/hooks/commitmsg.py +138 -0
- spice_harness-0.1.0/spice/hooks/doctor.py +572 -0
- spice_harness-0.1.0/spice/hooks/install.py +145 -0
- spice_harness-0.1.0/spice/hooks/precommit.py +538 -0
- spice_harness-0.1.0/spice/hooks/refguard.py +127 -0
- spice_harness-0.1.0/spice/locking.py +106 -0
- spice_harness-0.1.0/spice/mail/acks.py +592 -0
- spice_harness-0.1.0/spice/mail/attachments.py +247 -0
- spice_harness-0.1.0/spice/mail/inbox.py +663 -0
- spice_harness-0.1.0/spice/mail/readout.py +57 -0
- spice_harness-0.1.0/spice/mail/watch.py +384 -0
- spice_harness-0.1.0/spice/paths.py +171 -0
- spice_harness-0.1.0/spice/policy.py +96 -0
- spice_harness-0.1.0/spice/procs.py +211 -0
- spice_harness-0.1.0/spice/repocfg.py +62 -0
- spice_harness-0.1.0/spice/serve/agentapi.py +178 -0
- spice_harness-0.1.0/spice/serve/app.py +981 -0
- spice_harness-0.1.0/spice/serve/attachments.py +50 -0
- spice_harness-0.1.0/spice/serve/audio.py +108 -0
- spice_harness-0.1.0/spice/serve/browser/artifacts.py +25 -0
- spice_harness-0.1.0/spice/serve/cli.py +106 -0
- spice_harness-0.1.0/spice/serve/diagnostics.py +296 -0
- spice_harness-0.1.0/spice/serve/drive.py +9 -0
- spice_harness-0.1.0/spice/serve/filewatch.py +140 -0
- spice_harness-0.1.0/spice/serve/images.py +172 -0
- spice_harness-0.1.0/spice/serve/livebus.py +412 -0
- spice_harness-0.1.0/spice/serve/markdown.py +297 -0
- spice_harness-0.1.0/spice/serve/messages.py +751 -0
- spice_harness-0.1.0/spice/serve/payloads.py +432 -0
- spice_harness-0.1.0/spice/serve/static/app.audio.js +348 -0
- spice_harness-0.1.0/spice/serve/static/app.controls.js +123 -0
- spice_harness-0.1.0/spice/serve/static/app.groups.js +614 -0
- spice_harness-0.1.0/spice/serve/static/app.js +115 -0
- spice_harness-0.1.0/spice/serve/static/app.lanes.js +659 -0
- spice_harness-0.1.0/spice/serve/static/app.panes.js +583 -0
- spice_harness-0.1.0/spice/serve/static/app.render.js +599 -0
- spice_harness-0.1.0/spice/serve/static/app.shell.js +1265 -0
- spice_harness-0.1.0/spice/serve/static/app.stream.js +745 -0
- spice_harness-0.1.0/spice/serve/static/app.types.js +75 -0
- spice_harness-0.1.0/spice/serve/static/favicon.ico +0 -0
- spice_harness-0.1.0/spice/serve/static/index.css +1423 -0
- spice_harness-0.1.0/spice/serve/steering.py +72 -0
- spice_harness-0.1.0/spice/serve/teams.py +656 -0
- spice_harness-0.1.0/spice/serve/typecheck.py +77 -0
- spice_harness-0.1.0/spice/serve/web.py +67 -0
- spice_harness-0.1.0/spice/serve/websocket.py +182 -0
- spice_harness-0.1.0/spice/serve/worktrees.py +124 -0
- spice_harness-0.1.0/spice/sessions/analysis.py +520 -0
- spice_harness-0.1.0/spice/sessions/briefing.py +894 -0
- spice_harness-0.1.0/spice/sessions/cli.py +906 -0
- spice_harness-0.1.0/spice/sessions/commandaudit.py +209 -0
- spice_harness-0.1.0/spice/sessions/commandrecords.py +212 -0
- spice_harness-0.1.0/spice/sessions/meter.py +366 -0
- spice_harness-0.1.0/spice/sessions/records.py +361 -0
- spice_harness-0.1.0/spice/sessions/resolve.py +51 -0
- spice_harness-0.1.0/spice/sessions/slices.py +200 -0
- spice_harness-0.1.0/spice/sessions/util.py +91 -0
- spice_harness-0.1.0/spice/studies/cli.py +159 -0
- spice_harness-0.1.0/spice/studies/complexity.py +280 -0
- spice_harness-0.1.0/spice/studies/envpolicy.py +104 -0
- spice_harness-0.1.0/spice/studies/fileloc.py +256 -0
- spice_harness-0.1.0/spice/studies/localpaths.py +48 -0
- spice_harness-0.1.0/spice/studies/magicnums.py +270 -0
- spice_harness-0.1.0/spice/studies/shape.py +134 -0
- spice_harness-0.1.0/spice/studies/walk.py +192 -0
- spice_harness-0.1.0/spice/tasks/alloc.py +81 -0
- spice_harness-0.1.0/spice/tasks/cli.py +559 -0
- spice_harness-0.1.0/spice/tasks/config.py +419 -0
- spice_harness-0.1.0/spice/tasks/gitsync.py +418 -0
- spice_harness-0.1.0/spice/tasks/identity.py +104 -0
- spice_harness-0.1.0/spice/tasks/lanes.py +143 -0
- spice_harness-0.1.0/spice/tasks/ops.py +856 -0
- spice_harness-0.1.0/spice/tasks/render.py +354 -0
- spice_harness-0.1.0/spice/tasks/tw.py +114 -0
- spice_harness-0.1.0/spice/worktrees.py +131 -0
- spice_harness-0.1.0/spice_harness.egg-info/PKG-INFO +181 -0
- spice_harness-0.1.0/spice_harness.egg-info/SOURCES.txt +133 -0
- spice_harness-0.1.0/spice_harness.egg-info/dependency_links.txt +1 -0
- spice_harness-0.1.0/spice_harness.egg-info/entry_points.txt +2 -0
- spice_harness-0.1.0/spice_harness.egg-info/requires.txt +3 -0
- spice_harness-0.1.0/spice_harness.egg-info/top_level.txt +1 -0
- spice_harness-0.1.0/tests/test_acks.py +261 -0
- spice_harness-0.1.0/tests/test_agentshim.py +60 -0
- spice_harness-0.1.0/tests/test_clirecovery.py +69 -0
- spice_harness-0.1.0/tests/test_config.py +124 -0
- spice_harness-0.1.0/tests/test_doctor.py +217 -0
- spice_harness-0.1.0/tests/test_hooks.py +707 -0
- spice_harness-0.1.0/tests/test_images.py +125 -0
- spice_harness-0.1.0/tests/test_inbox.py +203 -0
- spice_harness-0.1.0/tests/test_lifecycle.py +823 -0
- spice_harness-0.1.0/tests/test_livebus.py +312 -0
- spice_harness-0.1.0/tests/test_markdown.py +120 -0
- spice_harness-0.1.0/tests/test_mounts.py +59 -0
- spice_harness-0.1.0/tests/test_payloads.py +257 -0
- spice_harness-0.1.0/tests/test_serve.py +1088 -0
- spice_harness-0.1.0/tests/test_servebrowserartifacts.py +33 -0
- spice_harness-0.1.0/tests/test_servediagnostics.py +104 -0
- spice_harness-0.1.0/tests/test_servefilewatch.py +109 -0
- spice_harness-0.1.0/tests/test_sessionanalysis.py +241 -0
- spice_harness-0.1.0/tests/test_sessioncommands.py +275 -0
- spice_harness-0.1.0/tests/test_sessions.py +619 -0
- spice_harness-0.1.0/tests/test_sessionslices.py +164 -0
- spice_harness-0.1.0/tests/test_speechprep.py +403 -0
- spice_harness-0.1.0/tests/test_studies.py +160 -0
- spice_harness-0.1.0/tests/test_taskcli.py +217 -0
- spice_harness-0.1.0/tests/test_tasks.py +632 -0
- spice_harness-0.1.0/tests/test_teams.py +79 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Infima Labs
|
|
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,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spice-harness
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simultaneous Production, Integration, and Control Environment: an agent harness for coding repositories.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/infimalabs/spice
|
|
7
|
+
Project-URL: Repository, https://github.com/infimalabs/spice
|
|
8
|
+
Project-URL: Issues, https://github.com/infimalabs/spice/issues
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: lizard>=1.21
|
|
13
|
+
Requires-Dist: ruff>=0.11
|
|
14
|
+
Requires-Dist: watchfiles>=1.1
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# spice
|
|
18
|
+
|
|
19
|
+
**Simultaneous Production, Integration, and Control Environment.**
|
|
20
|
+
|
|
21
|
+
spice is an installed agent harness: wrap, steer, supervise, coordinate, and
|
|
22
|
+
audit coding agents across the repos they work on. Install it once, point it at
|
|
23
|
+
a repository, and it provides a closed loop around the agents working there:
|
|
24
|
+
|
|
25
|
+
- the agent's **transcript is the single source of truth**, and
|
|
26
|
+
- the repo's **filesystem is the single channel of steering**;
|
|
27
|
+
|
|
28
|
+
supervision, coordination, conscience, and hygiene are derived mechanically
|
|
29
|
+
from those two surfaces.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install spice-harness # or: uv tool install spice-harness
|
|
35
|
+
cd /path/to/your/repo
|
|
36
|
+
spice init # hooks, spice.sh shim, state scaffolding
|
|
37
|
+
spice dev doctor # verify drivers, backends, and policy
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`spice init` writes machine-local git hook shims under `.spice/` (ignored via
|
|
41
|
+
`.git/info/exclude`) and a tracked `spice.sh` shim. Repo-tracked policy lives
|
|
42
|
+
in your `pyproject.toml` under `[tool.spice.*]` tables. Entrypoint resolution
|
|
43
|
+
is worktree-true: when the current repo is the spice source checkout, generated
|
|
44
|
+
shims and supervisor children put that checkout first on `PYTHONPATH` and run
|
|
45
|
+
`python -m spice`; ordinary target repos use the installed product.
|
|
46
|
+
|
|
47
|
+
A project can set its default supervised-agent launch model and thinking in
|
|
48
|
+
tracked config, either by editing `pyproject.toml` or by running
|
|
49
|
+
`spice config agent --scope project --model ... --thinking ...`:
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[tool.spice.agent]
|
|
53
|
+
model = "gpt-5.4"
|
|
54
|
+
thinking = "low"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
An operator can override those defaults for just the current worktree:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
spice config agent --scope worktree --model gpt-5.4 --thinking low
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Resolution order is explicit launch flags, then worktree config, then tracked
|
|
64
|
+
project config, then the driver defaults.
|
|
65
|
+
|
|
66
|
+
A repo can also mount its own tooling into the `spice` namespace:
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
[tool.spice.commands]
|
|
70
|
+
deploy = "./scripts/deploy.sh"
|
|
71
|
+
bench = ["python", "-m", "myproj.bench"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`spice deploy --env staging` then runs the mounted command from the repo
|
|
75
|
+
root with the remaining arguments passed through verbatim. Built-in verbs
|
|
76
|
+
always win; a mount that shadows one fails loudly.
|
|
77
|
+
|
|
78
|
+
Mounted names are intentionally one-level verbs (`^[a-z][a-z0-9-]*$`), not
|
|
79
|
+
nested command paths. A repo with a large tool family mounts one namespace
|
|
80
|
+
owner and keeps family grouping inside that repo tool's own arguments:
|
|
81
|
+
|
|
82
|
+
```toml
|
|
83
|
+
[tool.spice.commands]
|
|
84
|
+
toolbox = ["uv", "run", "toolbox"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`spice toolbox lint css --fix` then dispatches `lint css --fix` to `toolbox`.
|
|
88
|
+
Do not encode families as dotted, spaced, or ad-hoc hyphenated spice mount
|
|
89
|
+
names such as `lint.css`, `lint css`, or one mount per subcommand; those
|
|
90
|
+
groupings belong behind the mounted repo tool's explicit contract.
|
|
91
|
+
|
|
92
|
+
### Library seam for repo tools
|
|
93
|
+
|
|
94
|
+
Mounted commands and tracked pre-commit extensions may import a deliberately
|
|
95
|
+
narrow Python seam from `spice` instead of vendoring harness scaffolding. This
|
|
96
|
+
surface is source-stable for target repos: public names in the modules listed
|
|
97
|
+
below are not removed or renamed silently, and incompatible changes require an
|
|
98
|
+
explicit contract update. Underscored names remain private.
|
|
99
|
+
|
|
100
|
+
- `spice.errors`: `SpiceError` for user-facing command failures.
|
|
101
|
+
- `spice.policy`: constitution constants and `flex_limit`.
|
|
102
|
+
- `spice.flexstate`: flex-limit sticky-state persistence and rename helpers.
|
|
103
|
+
- `spice.locking`: cross-platform advisory file locks.
|
|
104
|
+
- `spice.paths`: repo-root, state-dir, atomic write, and tool-resolution helpers.
|
|
105
|
+
- `spice.procs`: process-group spawn, liveness, and termination helpers.
|
|
106
|
+
- `spice.repocfg`: tracked `[tool.spice]` table readers.
|
|
107
|
+
- `spice.studies.walk`: tracked/staged path walkers, repo policy exclusions,
|
|
108
|
+
staged renames, and git blob reads.
|
|
109
|
+
- `spice.studies.fileloc`, `spice.studies.complexity`,
|
|
110
|
+
`spice.studies.magicnums`, and `spice.studies.envpolicy`: finding
|
|
111
|
+
dataclasses plus `scan_*`, `detect_*`, and `render_*_board` helpers for
|
|
112
|
+
project-specific studies.
|
|
113
|
+
|
|
114
|
+
Everything else is an internal implementation detail unless this section names
|
|
115
|
+
it. A repo tool that needs an unlisted module should either vendor that helper
|
|
116
|
+
or first add the helper to this seam with tests and a stability note.
|
|
117
|
+
|
|
118
|
+
## The loop
|
|
119
|
+
|
|
120
|
+
| Surface | Command | What it does |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| Wrapper | `spice agent run -- <cmd>` (or `./spice.sh`) | Runs shell commands with proxy routing, git-shadow env, and steering injection on stderr. |
|
|
123
|
+
| Lifecycle | `spice agent ensure` / `supervise` | One worktree-bound agent per worktree, started under a neutral skill prompt, watched by a durable supervisor. |
|
|
124
|
+
| Steering | filesystem inbox under `.spice/inbox/` | Durable operator messages; items retire only when the agent semantically ACKs their key in its transcript. |
|
|
125
|
+
| Tasks | `spice task …` | Phase-native Taskwarrior board shared by all worktrees; `task next` is allocator-owned; git sync happens at task boundaries. |
|
|
126
|
+
| Sessions | `spice session` | Transcript forensics: the no-arg briefing is the primary rehydration product, with context-pressure metering. |
|
|
127
|
+
| Cockpit | `spice serve` | Localhost web UI: lanes over worktrees, live transcript streams, lifetime control (Renew / Steer / Drive), task-filter routing, fused lane groups backed by server-side teams; `spice serve teams` and `spice serve browser-artifact-path <file>` expose operator diagnostics for smoke runs. |
|
|
128
|
+
| Conscience | `spice maxim …` | Builtin maxims judged against assistant prose by a local model; violations come back as inbox steering. |
|
|
129
|
+
| Constitution | `spice dev pre-commit` / `spice study …` | Namespace packages, path shape, LOC/byte/complexity flex+sticky gates, magic-number ratchet, env-literal inventory, commit-message policy. |
|
|
130
|
+
|
|
131
|
+
Session analysis is intentionally tiered. The current tier includes
|
|
132
|
+
`spice session phases` for contiguous working-phase spans and
|
|
133
|
+
`spice session messages` for message-level side/phase/flavor filtering.
|
|
134
|
+
Deeper report families that depend on richer topic/bucket modeling belong in
|
|
135
|
+
a separate analytics tier after the basic phase/message surfaces harden.
|
|
136
|
+
|
|
137
|
+
## The constitution
|
|
138
|
+
|
|
139
|
+
The pre-commit gate is the executable form of the project's opinions — see
|
|
140
|
+
[spice/policy.py](spice/policy.py). Highlights:
|
|
141
|
+
|
|
142
|
+
- namespace packages only; no `__init__.py` under declared package roots;
|
|
143
|
+
- file names match `^_*[0-9a-z]+_*$`; splitting a file requires naming the
|
|
144
|
+
seam (no `*2.py`, no generic continuation shards);
|
|
145
|
+
- files flex to 1500 lines but a breach holds them to 1000 until they shrink;
|
|
146
|
+
routines flex the same way around CCN 20 / length 80;
|
|
147
|
+
- magic-number regressions are a ratchet against `HEAD`, not an amnesty;
|
|
148
|
+
- env-literal inventory covers `SPICE_*` and `CODEX_THREAD_ID` by default;
|
|
149
|
+
target repos can add tracked name regexes with
|
|
150
|
+
`[tool.spice.policy] env_name_patterns`;
|
|
151
|
+
- commit subjects fit in 100 columns; bodies are auto-folded.
|
|
152
|
+
|
|
153
|
+
The gate applies to spice itself: this repository is its own first target.
|
|
154
|
+
Target repos can keep their own tracked gate lanes under the same hook by
|
|
155
|
+
declaring mounted commands and pre-commit policy:
|
|
156
|
+
|
|
157
|
+
```toml
|
|
158
|
+
[tool.spice.commands]
|
|
159
|
+
fmt-cs = ["dotnet", "format", "--verify-no-changes"]
|
|
160
|
+
|
|
161
|
+
[tool.spice.policy]
|
|
162
|
+
pre_commit = ["fmt-cs", { label = "assets", run = ["python3", "-m", "tools.assets"] }]
|
|
163
|
+
pre_commit_success = [{ label = "clear asset sticky state", run = ["python3", "-m", "tools.assets", "--clear-sticky"] }]
|
|
164
|
+
|
|
165
|
+
[tool.spice.policy.pre_commit_builtins]
|
|
166
|
+
formatters = false
|
|
167
|
+
"magic-numbers" = { label = "project magic", run = ["python3", "-m", "tools.magic"] }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Built-in pre-commit keys are `repo-shape`, `staging`, `repo-docs`,
|
|
171
|
+
`formatters`, `env-policy`, `file-shape`, `complexity`, and `magic-numbers`.
|
|
172
|
+
They run before extension steps unless an individual built-in is disabled or
|
|
173
|
+
replaced in tracked policy. `pre_commit_success` uses the same command shape as
|
|
174
|
+
`pre_commit`, but runs only after the whole gate has passed, alongside sticky
|
|
175
|
+
state cleanup.
|
|
176
|
+
|
|
177
|
+
## Status
|
|
178
|
+
|
|
179
|
+
Work in progress: an extraction-in-progress toward a standalone, releasable
|
|
180
|
+
product. Surfaces are still settling; the loop described above is real and
|
|
181
|
+
exercised daily, and this repository is its own first target.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# spice
|
|
2
|
+
|
|
3
|
+
**Simultaneous Production, Integration, and Control Environment.**
|
|
4
|
+
|
|
5
|
+
spice is an installed agent harness: wrap, steer, supervise, coordinate, and
|
|
6
|
+
audit coding agents across the repos they work on. Install it once, point it at
|
|
7
|
+
a repository, and it provides a closed loop around the agents working there:
|
|
8
|
+
|
|
9
|
+
- the agent's **transcript is the single source of truth**, and
|
|
10
|
+
- the repo's **filesystem is the single channel of steering**;
|
|
11
|
+
|
|
12
|
+
supervision, coordination, conscience, and hygiene are derived mechanically
|
|
13
|
+
from those two surfaces.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pip install spice-harness # or: uv tool install spice-harness
|
|
19
|
+
cd /path/to/your/repo
|
|
20
|
+
spice init # hooks, spice.sh shim, state scaffolding
|
|
21
|
+
spice dev doctor # verify drivers, backends, and policy
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`spice init` writes machine-local git hook shims under `.spice/` (ignored via
|
|
25
|
+
`.git/info/exclude`) and a tracked `spice.sh` shim. Repo-tracked policy lives
|
|
26
|
+
in your `pyproject.toml` under `[tool.spice.*]` tables. Entrypoint resolution
|
|
27
|
+
is worktree-true: when the current repo is the spice source checkout, generated
|
|
28
|
+
shims and supervisor children put that checkout first on `PYTHONPATH` and run
|
|
29
|
+
`python -m spice`; ordinary target repos use the installed product.
|
|
30
|
+
|
|
31
|
+
A project can set its default supervised-agent launch model and thinking in
|
|
32
|
+
tracked config, either by editing `pyproject.toml` or by running
|
|
33
|
+
`spice config agent --scope project --model ... --thinking ...`:
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[tool.spice.agent]
|
|
37
|
+
model = "gpt-5.4"
|
|
38
|
+
thinking = "low"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
An operator can override those defaults for just the current worktree:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
spice config agent --scope worktree --model gpt-5.4 --thinking low
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Resolution order is explicit launch flags, then worktree config, then tracked
|
|
48
|
+
project config, then the driver defaults.
|
|
49
|
+
|
|
50
|
+
A repo can also mount its own tooling into the `spice` namespace:
|
|
51
|
+
|
|
52
|
+
```toml
|
|
53
|
+
[tool.spice.commands]
|
|
54
|
+
deploy = "./scripts/deploy.sh"
|
|
55
|
+
bench = ["python", "-m", "myproj.bench"]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`spice deploy --env staging` then runs the mounted command from the repo
|
|
59
|
+
root with the remaining arguments passed through verbatim. Built-in verbs
|
|
60
|
+
always win; a mount that shadows one fails loudly.
|
|
61
|
+
|
|
62
|
+
Mounted names are intentionally one-level verbs (`^[a-z][a-z0-9-]*$`), not
|
|
63
|
+
nested command paths. A repo with a large tool family mounts one namespace
|
|
64
|
+
owner and keeps family grouping inside that repo tool's own arguments:
|
|
65
|
+
|
|
66
|
+
```toml
|
|
67
|
+
[tool.spice.commands]
|
|
68
|
+
toolbox = ["uv", "run", "toolbox"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`spice toolbox lint css --fix` then dispatches `lint css --fix` to `toolbox`.
|
|
72
|
+
Do not encode families as dotted, spaced, or ad-hoc hyphenated spice mount
|
|
73
|
+
names such as `lint.css`, `lint css`, or one mount per subcommand; those
|
|
74
|
+
groupings belong behind the mounted repo tool's explicit contract.
|
|
75
|
+
|
|
76
|
+
### Library seam for repo tools
|
|
77
|
+
|
|
78
|
+
Mounted commands and tracked pre-commit extensions may import a deliberately
|
|
79
|
+
narrow Python seam from `spice` instead of vendoring harness scaffolding. This
|
|
80
|
+
surface is source-stable for target repos: public names in the modules listed
|
|
81
|
+
below are not removed or renamed silently, and incompatible changes require an
|
|
82
|
+
explicit contract update. Underscored names remain private.
|
|
83
|
+
|
|
84
|
+
- `spice.errors`: `SpiceError` for user-facing command failures.
|
|
85
|
+
- `spice.policy`: constitution constants and `flex_limit`.
|
|
86
|
+
- `spice.flexstate`: flex-limit sticky-state persistence and rename helpers.
|
|
87
|
+
- `spice.locking`: cross-platform advisory file locks.
|
|
88
|
+
- `spice.paths`: repo-root, state-dir, atomic write, and tool-resolution helpers.
|
|
89
|
+
- `spice.procs`: process-group spawn, liveness, and termination helpers.
|
|
90
|
+
- `spice.repocfg`: tracked `[tool.spice]` table readers.
|
|
91
|
+
- `spice.studies.walk`: tracked/staged path walkers, repo policy exclusions,
|
|
92
|
+
staged renames, and git blob reads.
|
|
93
|
+
- `spice.studies.fileloc`, `spice.studies.complexity`,
|
|
94
|
+
`spice.studies.magicnums`, and `spice.studies.envpolicy`: finding
|
|
95
|
+
dataclasses plus `scan_*`, `detect_*`, and `render_*_board` helpers for
|
|
96
|
+
project-specific studies.
|
|
97
|
+
|
|
98
|
+
Everything else is an internal implementation detail unless this section names
|
|
99
|
+
it. A repo tool that needs an unlisted module should either vendor that helper
|
|
100
|
+
or first add the helper to this seam with tests and a stability note.
|
|
101
|
+
|
|
102
|
+
## The loop
|
|
103
|
+
|
|
104
|
+
| Surface | Command | What it does |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| Wrapper | `spice agent run -- <cmd>` (or `./spice.sh`) | Runs shell commands with proxy routing, git-shadow env, and steering injection on stderr. |
|
|
107
|
+
| Lifecycle | `spice agent ensure` / `supervise` | One worktree-bound agent per worktree, started under a neutral skill prompt, watched by a durable supervisor. |
|
|
108
|
+
| Steering | filesystem inbox under `.spice/inbox/` | Durable operator messages; items retire only when the agent semantically ACKs their key in its transcript. |
|
|
109
|
+
| Tasks | `spice task …` | Phase-native Taskwarrior board shared by all worktrees; `task next` is allocator-owned; git sync happens at task boundaries. |
|
|
110
|
+
| Sessions | `spice session` | Transcript forensics: the no-arg briefing is the primary rehydration product, with context-pressure metering. |
|
|
111
|
+
| Cockpit | `spice serve` | Localhost web UI: lanes over worktrees, live transcript streams, lifetime control (Renew / Steer / Drive), task-filter routing, fused lane groups backed by server-side teams; `spice serve teams` and `spice serve browser-artifact-path <file>` expose operator diagnostics for smoke runs. |
|
|
112
|
+
| Conscience | `spice maxim …` | Builtin maxims judged against assistant prose by a local model; violations come back as inbox steering. |
|
|
113
|
+
| Constitution | `spice dev pre-commit` / `spice study …` | Namespace packages, path shape, LOC/byte/complexity flex+sticky gates, magic-number ratchet, env-literal inventory, commit-message policy. |
|
|
114
|
+
|
|
115
|
+
Session analysis is intentionally tiered. The current tier includes
|
|
116
|
+
`spice session phases` for contiguous working-phase spans and
|
|
117
|
+
`spice session messages` for message-level side/phase/flavor filtering.
|
|
118
|
+
Deeper report families that depend on richer topic/bucket modeling belong in
|
|
119
|
+
a separate analytics tier after the basic phase/message surfaces harden.
|
|
120
|
+
|
|
121
|
+
## The constitution
|
|
122
|
+
|
|
123
|
+
The pre-commit gate is the executable form of the project's opinions — see
|
|
124
|
+
[spice/policy.py](spice/policy.py). Highlights:
|
|
125
|
+
|
|
126
|
+
- namespace packages only; no `__init__.py` under declared package roots;
|
|
127
|
+
- file names match `^_*[0-9a-z]+_*$`; splitting a file requires naming the
|
|
128
|
+
seam (no `*2.py`, no generic continuation shards);
|
|
129
|
+
- files flex to 1500 lines but a breach holds them to 1000 until they shrink;
|
|
130
|
+
routines flex the same way around CCN 20 / length 80;
|
|
131
|
+
- magic-number regressions are a ratchet against `HEAD`, not an amnesty;
|
|
132
|
+
- env-literal inventory covers `SPICE_*` and `CODEX_THREAD_ID` by default;
|
|
133
|
+
target repos can add tracked name regexes with
|
|
134
|
+
`[tool.spice.policy] env_name_patterns`;
|
|
135
|
+
- commit subjects fit in 100 columns; bodies are auto-folded.
|
|
136
|
+
|
|
137
|
+
The gate applies to spice itself: this repository is its own first target.
|
|
138
|
+
Target repos can keep their own tracked gate lanes under the same hook by
|
|
139
|
+
declaring mounted commands and pre-commit policy:
|
|
140
|
+
|
|
141
|
+
```toml
|
|
142
|
+
[tool.spice.commands]
|
|
143
|
+
fmt-cs = ["dotnet", "format", "--verify-no-changes"]
|
|
144
|
+
|
|
145
|
+
[tool.spice.policy]
|
|
146
|
+
pre_commit = ["fmt-cs", { label = "assets", run = ["python3", "-m", "tools.assets"] }]
|
|
147
|
+
pre_commit_success = [{ label = "clear asset sticky state", run = ["python3", "-m", "tools.assets", "--clear-sticky"] }]
|
|
148
|
+
|
|
149
|
+
[tool.spice.policy.pre_commit_builtins]
|
|
150
|
+
formatters = false
|
|
151
|
+
"magic-numbers" = { label = "project magic", run = ["python3", "-m", "tools.magic"] }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Built-in pre-commit keys are `repo-shape`, `staging`, `repo-docs`,
|
|
155
|
+
`formatters`, `env-policy`, `file-shape`, `complexity`, and `magic-numbers`.
|
|
156
|
+
They run before extension steps unless an individual built-in is disabled or
|
|
157
|
+
replaced in tracked policy. `pre_commit_success` uses the same command shape as
|
|
158
|
+
`pre_commit`, but runs only after the whole gate has passed, alongside sticky
|
|
159
|
+
state cleanup.
|
|
160
|
+
|
|
161
|
+
## Status
|
|
162
|
+
|
|
163
|
+
Work in progress: an extraction-in-progress toward a standalone, releasable
|
|
164
|
+
product. Surfaces are still settling; the loop described above is real and
|
|
165
|
+
exercised daily, and this repository is its own first target.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spice-harness"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Simultaneous Production, Integration, and Control Environment: an agent harness for coding repositories."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"lizard>=1.21",
|
|
14
|
+
"ruff>=0.11",
|
|
15
|
+
"watchfiles>=1.1",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/infimalabs/spice"
|
|
20
|
+
Repository = "https://github.com/infimalabs/spice"
|
|
21
|
+
Issues = "https://github.com/infimalabs/spice/issues"
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
spice = "spice.cli.entry:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["."]
|
|
28
|
+
include = ["spice*"]
|
|
29
|
+
namespaces = true
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.package-data]
|
|
32
|
+
"spice.agent" = ["*.md"]
|
|
33
|
+
"spice.serve.static" = ["*.css", "*.js", "*.ico"]
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
target-version = "py312"
|
|
42
|
+
line-length = 88
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
|
|
47
|
+
[tool.spice.policy]
|
|
48
|
+
package_roots = ["spice"]
|
|
49
|
+
|
|
50
|
+
[tool.spice.agent]
|
|
51
|
+
model = "gpt-5.5"
|
|
52
|
+
thinking = "xhigh"
|
|
53
|
+
|
|
54
|
+
[tool.spice.tasks]
|
|
55
|
+
stems = ["session", "mail", "hooks", "studies", "cli", "lifecycle", "tests"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""`python -m spice` — the installation-independent entrypoint.
|
|
2
|
+
|
|
3
|
+
The agent supervisor respawns through this module so a detached process finds
|
|
4
|
+
the same interpreter. Worktree source checkouts are put first on PYTHONPATH;
|
|
5
|
+
ordinary target repos use the installed package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from spice.cli.entry import main
|
|
9
|
+
|
|
10
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spice
|
|
3
|
+
description: Start or resume a worktree-bound spice agent from a neutral skill prompt. Use when a server or wrapper launches an agent for an existing worktree and the initial prompt must not contain operator prose.
|
|
4
|
+
metadata:
|
|
5
|
+
short-description: Worktree-bound spice agent bootstrap
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# spice
|
|
9
|
+
|
|
10
|
+
You were started by spice, the Simultaneous Production, Integration, and
|
|
11
|
+
Control Environment. The initial prompt is only a bootstrap signal, not the
|
|
12
|
+
operator's request.
|
|
13
|
+
|
|
14
|
+
Before sending any assistant prose, run these commands in this order:
|
|
15
|
+
|
|
16
|
+
1. `spice agent activation`
|
|
17
|
+
2. `spice session`
|
|
18
|
+
3. `spice task status`
|
|
19
|
+
|
|
20
|
+
If a tool call is impossible, say only what prevented it and stop. Otherwise, let the command outputs establish context first, then respond to pending steering directly. Use those outputs, side-channel steering, and the active task board as your source of truth. Do not infer a durable task from this skill invocation.
|
|
21
|
+
|
|
22
|
+
If continuity is clipped, deepen with `spice session sweep --count N`, `spice session timeline --limit N`, `spice session turns --turn-id ... --view full`, `spice session compactions`, or `spice session commits`.
|
|
23
|
+
|
|
24
|
+
## Working Rules
|
|
25
|
+
|
|
26
|
+
- Stay in the current worktree unless live steering explicitly changes scope.
|
|
27
|
+
- Recover lane identity from current repo state and `spice agent activation`; do not trust prior messages over current worktree state.
|
|
28
|
+
- Run shell commands through `./spice.sh` when the repo provides it, or `spice agent run -- <command>` otherwise.
|
|
29
|
+
- Use `./spice.sh proxy <command>` when a command must keep native argument and output semantics; with no proxy installed the wrapper drops `proxy` and runs the command directly, while steering injection still applies.
|
|
30
|
+
- Pull work with `spice task next`, not by eyeballing a board. `task next` returns the globally-best ready task across all open boards and claims it; the selected board is derived from the claimed task, not stored as a hidden default.
|
|
31
|
+
- Completing a task phase advances it: use `spice task done <handle> --validation "..."` to move a task from implementation into its review phase, then run `spice task next` for reviewer assignment. Do not manually claim your own review; if `task next` assigns it anyway, treat that as an allocator assignment and verify the task description is current before `spice task review <handle> --finding clean --note "description current; ..."`. Read the printed `advanced ... -> <phase>` / `completed ...` line, then run `task next` again; a task is not finished while later phases remain.
|
|
32
|
+
- Use `spice task add` for backlog items and `spice task note` for small observations attached to a task.
|
|
33
|
+
- When the tooling itself fights you (weak default, surprising output, a command that did not work as emitted), record it with `spice task oops "..." --severity ... --kind ...`. It files the friction as a task on the deferred `oops` triage board (a human works that hatch); capture the speed bump rather than silently working around it.
|
|
34
|
+
- Read side-channel steering before acting and acknowledge it through the normal agent workflow.
|
|
35
|
+
- Use `SAY:` in an assistant message only for genuine blockers, decisions worth operator attention, or important milestones. Do not shell out to `say` directly.
|
|
36
|
+
- Treat a dirty worktree as pressure toward commit, split, or cleanup.
|
|
37
|
+
- Do not spawn sub-agents.
|
|
38
|
+
- Keep going while progress is real, but let the selected task, its board, and task notes shape the work instead of treating this skill as a standing user demand.
|
|
39
|
+
|
|
40
|
+
## Prompt Boundary
|
|
41
|
+
|
|
42
|
+
The wrapper must never pass operator prose as the initial prompt. If you need the current ask, recover it from `spice session`, `spice task status`, and side-channel messages.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""The activation packet: the contract an agent reads before doing anything.
|
|
2
|
+
|
|
3
|
+
`spice agent activation` is the first command a freshly started worktree
|
|
4
|
+
agent runs (the skill mandates it). It binds the ambient thread id into the
|
|
5
|
+
lane's agent state, installs the git hooks, refreshes the baseline when safe,
|
|
6
|
+
and prints the working contract: git hygiene, validation expectations, and
|
|
7
|
+
the command surface that is the agent's source of truth.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def activation_git_hygiene_lines() -> list[str]:
|
|
16
|
+
return [
|
|
17
|
+
(
|
|
18
|
+
"work_commit_contract=commit your changes into coherent, validated "
|
|
19
|
+
"local history before completing a task; amend or reshape your own "
|
|
20
|
+
"commits freely while iterating — task done captures exactly the "
|
|
21
|
+
"commits you made"
|
|
22
|
+
),
|
|
23
|
+
(
|
|
24
|
+
"work_focus_contract=you only ever manage your own local git state; "
|
|
25
|
+
"synchronizing it with everyone else's work happens automatically at "
|
|
26
|
+
"task boundaries, so there is nothing to pull or push — just build "
|
|
27
|
+
"tasks and complete them"
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def activation_source_root_lines(repo_root: Path) -> list[str]:
|
|
33
|
+
return [f"project_source_root={repo_root.resolve()}"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def activation_browser_validation_lines() -> list[str]:
|
|
37
|
+
return [
|
|
38
|
+
(
|
|
39
|
+
"browser_validation_contract=for executable live browser checks, "
|
|
40
|
+
"start with the Playwright MCP server named playwright; if no live "
|
|
41
|
+
"browser path is available, report that browser coverage did not "
|
|
42
|
+
"run and restore Playwright MCP before continuing; do not "
|
|
43
|
+
"substitute static tests or non-browser checks for required "
|
|
44
|
+
"browser coverage"
|
|
45
|
+
)
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def activation_command_surface_lines() -> list[str]:
|
|
50
|
+
return [
|
|
51
|
+
"session=spice session",
|
|
52
|
+
"task_status=spice task status",
|
|
53
|
+
"task_next=spice task next",
|
|
54
|
+
"task_show=spice task show <handle>",
|
|
55
|
+
"tasks=spice task list",
|
|
56
|
+
'task_done=spice task done <handle> --validation "..."',
|
|
57
|
+
"inbox_steering=automatic wrapper/side-channel injection; no public mail command",
|
|
58
|
+
"side_channel=operator steering arrives through the supervisor socket",
|
|
59
|
+
"initial_prompt_policy=skill_invocation_only",
|
|
60
|
+
]
|