buildwright 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.
@@ -0,0 +1,15 @@
1
+ # --- buildwright: role -> model (registry ids; defaults to the package's models.yaml) ---
2
+ # BW_MODELS_FILE=./models.yaml # project-local registry (optional)
3
+ BW_ROLE_PLANNER=local/qwen3
4
+ BW_ROLE_WORKER=remote/gpt-4o-mini
5
+ BW_ROLE_REVIEWER=local/qwen3
6
+
7
+ # --- provider keys (only the ones you use) ---
8
+ OPENAI_API_KEY=
9
+ ANTHROPIC_API_KEY=
10
+ LITELLM_API_KEY=
11
+
12
+ # --- Langfuse (optional: without keys, tracing is disabled) ---
13
+ LANGFUSE_PUBLIC_KEY=
14
+ LANGFUSE_SECRET_KEY=
15
+ LANGFUSE_HOST=http://localhost:3000
@@ -0,0 +1,20 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ quality:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v5
14
+ with:
15
+ enable-cache: true
16
+ - run: uv python install 3.11
17
+ - run: uv sync
18
+ - run: uv run ruff check .
19
+ - run: uv run mypy src
20
+ - run: uv run pytest -q
@@ -0,0 +1,18 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ pypi:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ # Trusted publishing: cap token d'API; PyPI valida el workflow + repo + tag
13
+ id-token: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv build
18
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ .env
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .mypy_cache/
8
+ dist/
9
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jordi Marçal Poy
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,119 @@
1
+ Metadata-Version: 2.5
2
+ Name: buildwright
3
+ Version: 0.1.0
4
+ Summary: Per-agent model routing for AI agents: LangGraph orchestration, PydanticAI agents, LiteLLM-ready registry, Langfuse tracing
5
+ Project-URL: Homepage, https://github.com/jordimarsal/buildwright
6
+ Project-URL: Repository, https://github.com/jordimarsal/buildwright
7
+ Project-URL: Issues, https://github.com/jordimarsal/buildwright/issues
8
+ Author: Jordi Marçal Poy
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,langfuse,langgraph,litellm,llm,model-routing,pydantic-ai,spec-driven-development
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: langfuse>=3.0
22
+ Requires-Dist: langgraph>=1.0
23
+ Requires-Dist: pydantic-ai-slim[openai]>=1.0
24
+ Requires-Dist: pydantic-settings>=2.4
25
+ Requires-Dist: pyyaml>=6.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # buildwright
29
+
30
+ *The craftsman who builds per the spec* — one model per agent: mechanical steps
31
+ go to the **local** model (cheap, fast), important decisions to the **remote**
32
+ one (strong). The role→model mapping is configuration, not code — switching
33
+ models touches zero lines of pipeline.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ uv tool install buildwright # once published to PyPI
39
+ # or for development:
40
+ uv sync
41
+ ```
42
+
43
+ ## Division of roles (one tool, one job)
44
+
45
+ | Piece | Role | Where it lives |
46
+ |---|---|---|
47
+ | **LangGraph** | the ORDER of the steps (graph, state, retries) | `graph.py` |
48
+ | **PydanticAI** | EACH STEP (agent with structured output) | `agents.py` |
49
+ | **LiteLLM** | WHO serves the model (optional proxy, fallbacks) | `litellm/config.yaml` |
50
+ | **Langfuse** | WHETHER IT WORKED (traces, scores) | `observability.py` |
51
+
52
+ ## The pipeline
53
+
54
+ ```
55
+ START → plan (local) → execute (remote) → review (local) → END
56
+ Plan struct. final answer Review struct.
57
+ ```
58
+
59
+ Each node is a PydanticAI agent; the model is decided by the **role** in
60
+ `Settings` (`BW_ROLE_*`), resolved against the `models.yaml` registry.
61
+
62
+ ## Quickstart
63
+
64
+ ```bash
65
+ uv sync
66
+ cp .env.example .env # adjust BW_ROLE_* and keys
67
+
68
+ uv run buildwright models # list local and remote models
69
+ uv run buildwright roles # role -> model mapping
70
+ uv run buildwright ask "write a snake game in python with pygame"
71
+ ```
72
+
73
+ With no endpoints available, `ask` fails naming the exact model and the missing
74
+ env var.
75
+
76
+ ## Local and remote models
77
+
78
+ The whole registry speaks **OpenAI-compatible** (`models.yaml`):
79
+
80
+ - **local/qwen3** → Ollama at `localhost:11434/v1`
81
+ - **local/llamacpp** → `llama-server` at `localhost:8080/v1` (your GGUF)
82
+ - **remote/gpt-4o-mini, gpt-4o** → OpenAI (key via `OPENAI_API_KEY`)
83
+ - **remote/claude-sonnet** → Anthropic's OpenAI-compat layer
84
+ - **litellm/*** → via the LiteLLM proxy (see `docker-compose.yml`)
85
+
86
+ Registry resolution: `BW_MODELS_FILE` > project-local `./models.yaml` > the
87
+ `models.yaml` bundled with the package. Adding a model = adding 5 lines.
88
+ Reassigning one to an agent = changing a `BW_ROLE_*` in `.env`.
89
+
90
+ ## Optional full stack (LiteLLM + Langfuse + Ollama)
91
+
92
+ With Docker, `docker compose up -d` brings up LiteLLM (routing/fallbacks),
93
+ Langfuse (traces) and Ollama. Langfuse keys show up in its admin UI; copy them
94
+ to `.env` and tracing turns itself on (without keys, the tracer is a no-op).
95
+
96
+ ## Publishing to PyPI
97
+
98
+ Trusted publishing — no API tokens:
99
+
100
+ 1. Create `github.com/jordimarsal/buildwright` and push `main`.
101
+ 2. On pypi.org: *Add a pending publisher* → owner `jordimarsal`, repo
102
+ `buildwright`, workflow `release.yml`, environment `pypi`.
103
+ 3. `git tag v0.1.0 && git push --tags` → the workflow runs `uv build` and
104
+ publishes.
105
+
106
+ ## Tests
107
+
108
+ ```bash
109
+ uv run pytest # offline: pydantic-ai's TestModel, zero network
110
+ ```
111
+
112
+ ## Anti-mess rules
113
+
114
+ 1. Everything speaks OpenAI protocol; PydanticAI never imports litellm.
115
+ 2. One agent = one node = one job. No agents inside agents.
116
+ 3. Tracing registered exactly once (`observability.py`); never scattered.
117
+ 4. Offline evals in CI rule; Langfuse is production monitoring.
118
+ 5. Each piece joins when it has a real trigger (fallbacks→LiteLLM,
119
+ non-linear flow→LangGraph is already in).
@@ -0,0 +1,92 @@
1
+ # buildwright
2
+
3
+ *The craftsman who builds per the spec* — one model per agent: mechanical steps
4
+ go to the **local** model (cheap, fast), important decisions to the **remote**
5
+ one (strong). The role→model mapping is configuration, not code — switching
6
+ models touches zero lines of pipeline.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ uv tool install buildwright # once published to PyPI
12
+ # or for development:
13
+ uv sync
14
+ ```
15
+
16
+ ## Division of roles (one tool, one job)
17
+
18
+ | Piece | Role | Where it lives |
19
+ |---|---|---|
20
+ | **LangGraph** | the ORDER of the steps (graph, state, retries) | `graph.py` |
21
+ | **PydanticAI** | EACH STEP (agent with structured output) | `agents.py` |
22
+ | **LiteLLM** | WHO serves the model (optional proxy, fallbacks) | `litellm/config.yaml` |
23
+ | **Langfuse** | WHETHER IT WORKED (traces, scores) | `observability.py` |
24
+
25
+ ## The pipeline
26
+
27
+ ```
28
+ START → plan (local) → execute (remote) → review (local) → END
29
+ Plan struct. final answer Review struct.
30
+ ```
31
+
32
+ Each node is a PydanticAI agent; the model is decided by the **role** in
33
+ `Settings` (`BW_ROLE_*`), resolved against the `models.yaml` registry.
34
+
35
+ ## Quickstart
36
+
37
+ ```bash
38
+ uv sync
39
+ cp .env.example .env # adjust BW_ROLE_* and keys
40
+
41
+ uv run buildwright models # list local and remote models
42
+ uv run buildwright roles # role -> model mapping
43
+ uv run buildwright ask "write a snake game in python with pygame"
44
+ ```
45
+
46
+ With no endpoints available, `ask` fails naming the exact model and the missing
47
+ env var.
48
+
49
+ ## Local and remote models
50
+
51
+ The whole registry speaks **OpenAI-compatible** (`models.yaml`):
52
+
53
+ - **local/qwen3** → Ollama at `localhost:11434/v1`
54
+ - **local/llamacpp** → `llama-server` at `localhost:8080/v1` (your GGUF)
55
+ - **remote/gpt-4o-mini, gpt-4o** → OpenAI (key via `OPENAI_API_KEY`)
56
+ - **remote/claude-sonnet** → Anthropic's OpenAI-compat layer
57
+ - **litellm/*** → via the LiteLLM proxy (see `docker-compose.yml`)
58
+
59
+ Registry resolution: `BW_MODELS_FILE` > project-local `./models.yaml` > the
60
+ `models.yaml` bundled with the package. Adding a model = adding 5 lines.
61
+ Reassigning one to an agent = changing a `BW_ROLE_*` in `.env`.
62
+
63
+ ## Optional full stack (LiteLLM + Langfuse + Ollama)
64
+
65
+ With Docker, `docker compose up -d` brings up LiteLLM (routing/fallbacks),
66
+ Langfuse (traces) and Ollama. Langfuse keys show up in its admin UI; copy them
67
+ to `.env` and tracing turns itself on (without keys, the tracer is a no-op).
68
+
69
+ ## Publishing to PyPI
70
+
71
+ Trusted publishing — no API tokens:
72
+
73
+ 1. Create `github.com/jordimarsal/buildwright` and push `main`.
74
+ 2. On pypi.org: *Add a pending publisher* → owner `jordimarsal`, repo
75
+ `buildwright`, workflow `release.yml`, environment `pypi`.
76
+ 3. `git tag v0.1.0 && git push --tags` → the workflow runs `uv build` and
77
+ publishes.
78
+
79
+ ## Tests
80
+
81
+ ```bash
82
+ uv run pytest # offline: pydantic-ai's TestModel, zero network
83
+ ```
84
+
85
+ ## Anti-mess rules
86
+
87
+ 1. Everything speaks OpenAI protocol; PydanticAI never imports litellm.
88
+ 2. One agent = one node = one job. No agents inside agents.
89
+ 3. Tracing registered exactly once (`observability.py`); never scattered.
90
+ 4. Offline evals in CI rule; Langfuse is production monitoring.
91
+ 5. Each piece joins when it has a real trigger (fallbacks→LiteLLM,
92
+ non-linear flow→LangGraph is already in).
@@ -0,0 +1,46 @@
1
+ # Optional stack: LiteLLM (gateway) + Langfuse (traces) + Ollama (local models).
2
+ # The app works without it (direct models via .env); this is for when you want
3
+ # fallbacks, budgets and production traces.
4
+ services:
5
+ litellm:
6
+ image: ghcr.io/berriai/litellm:main-stable
7
+ ports: ["4000:4000"]
8
+ environment:
9
+ LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY:-sk-master}
10
+ OPENAI_API_KEY: ${OPENAI_API_KEY:-}
11
+ LANGFUSE_PUBLIC_KEY: ${LANGFUSE_PUBLIC_KEY:-}
12
+ LANGFUSE_SECRET_KEY: ${LANGFUSE_SECRET_KEY:-}
13
+ LANGFUSE_HOST: http://langfuse:3000
14
+ volumes:
15
+ - ./litellm/config.yaml:/app/config.yaml:ro
16
+ command: ["--config", "/app/config.yaml", "--port", "4000"]
17
+ depends_on: [ollama]
18
+
19
+ ollama:
20
+ image: ollama/ollama:latest
21
+ ports: ["11434:11434"]
22
+ volumes:
23
+ - ollama:/root/.ollama
24
+
25
+ langfuse-db:
26
+ image: postgres:16-alpine
27
+ environment:
28
+ POSTGRES_USER: langfuse
29
+ POSTGRES_PASSWORD: langfuse
30
+ POSTGRES_DB: langfuse
31
+ volumes:
32
+ - langfuse-db:/var/lib/postgresql/data
33
+
34
+ langfuse:
35
+ image: langfuse/langfuse:2
36
+ ports: ["3000:3000"]
37
+ environment:
38
+ DATABASE_URL: postgresql://langfuse:langfuse@langfuse-db:5432/langfuse
39
+ NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-change-me}
40
+ SALT: ${SALT:-change-me}
41
+ NEXTAUTH_URL: http://localhost:3000
42
+ depends_on: [langfuse-db]
43
+
44
+ volumes:
45
+ ollama:
46
+ langfuse-db:
@@ -0,0 +1,196 @@
1
+ # Design — buildwright as a Harness runtime
2
+
3
+ **Date:** 2026-10-27
4
+ **Status:** draft (pre-implementation)
5
+ **Scope:** `buildwright` — new `harness/` package. No changes required in `harness-standard` to start.
6
+
7
+ ---
8
+
9
+ ## 1. Context
10
+
11
+ `harness-standard` today is a **protocol** (agents, SDD workflow, gates, batching,
12
+ verification) executed *inside* a coding-agent TUI (OpenCode / Claude Code). The
13
+ protocol is enforced by prose ("NEVER skip the spec phase") plus scripts
14
+ (`init.sh`, `check-traceability.py`, `audit-security.sh`). The leader is an LLM
15
+ reading a decision table. Discipline exists because LLMs drift.
16
+
17
+ `buildwright` today is a per-agent model router: LangGraph orchestration,
18
+ PydanticAI agents with structured outputs, local/remote model registry, Langfuse.
19
+
20
+ **Idea:** buildwright becomes a third *host* of the harness protocol — the
21
+ unattended one. OpenCode is the interactive host (human drives). buildwright is
22
+ the production-line host (protocol drives, human approves at gates). Same
23
+ artifacts, same status machine, same gates.
24
+
25
+ Notably, the harness protocol already anticipates this host. `leader.md` says:
26
+ *"dispatch one read-only subagent … (**cheap model if the runtime allows
27
+ per-dispatch model choice**)"*. buildwright is the runtime that allows it.
28
+
29
+ ## 2. Core insight — the graph IS the leader
30
+
31
+ The leader's decision table is a state machine; LangGraph is a state machine.
32
+ The LLM leader disappears; the graph assumes its table:
33
+
34
+ | Leader mandate (prose today) | buildwright runtime (structural) |
35
+ |---|---|
36
+ | "NEVER launch implementer when a feature is `pending`" | no `pending → in_progress` edge exists |
37
+ | Human approval gate at `spec_ready` | LangGraph `interrupt()` — the process parks; state persisted in a checkpointer |
38
+ | "One feature at a time" | state guard: only one `in_progress` slot in the graph state |
39
+ | Batching 2–4 tasks, never 5+ | deterministic batch builder slices `tasks.md`; the number 5 is unrepresentable |
40
+ | "Verify on disk after every batch" | code: stat files, parse `- [x]`, run gates — no chat claim is even possible |
41
+ | Stall recovery ("Nothing was written…", 2 stalls → fresh session) | stall counter in state → re-dispatch with exact message → new agent instance |
42
+ | Context ~70% → fresh session | token counter per agent instance → rotate instance |
43
+ | Anti-telephone-cord (results to files) | structured outputs are written to disk by the runtime; there is no chat |
44
+ | Effort scaling table | complexity = cheap classifier node (structured output), overridable at the spec gate |
45
+
46
+ What stays LLM-driven is exactly what needs judgment: **spec-author**
47
+ (writes specs), **implementer** (writes code), **reviewer** (judges semantics),
48
+ **gauntlet passes** (adversarial checklists), **log-reader**. Everything
49
+ mechanical becomes edges and validators that cannot forget, skip, or improvise.
50
+
51
+ ## 3. Constraint enforcement map
52
+
53
+ Harness constraints, soft→hard migration:
54
+
55
+ | Constraint | TUI era | buildwright |
56
+ |---|---|---|
57
+ | SDD workflow order | prose | graph topology (**hard**) |
58
+ | `harness/init.sh` green before done | implementer told to run it | subprocess gate; exit ≠ 0 or final line ≠ `[OK]` → loop back (**hard**) |
59
+ | Feature list schema | `validate-feature-list.py` | Pydantic model at load & write (**hard**) |
60
+ | Traceability R⟷test | reviewer + script | run `check-traceability.py` as gate node; reviewer semantic pass on top (**hard + soft**) |
61
+ | `audit_level` basic/standard/strict | reviewer reads config | code reads config, runs `audit-security.sh` / `bench.sh`, rejects on non-zero (**hard**) |
62
+ | Conventional Commits | `commit-msg` hook | same hook; runtime commits via git tool (**hard**, unchanged) |
63
+ | Architecture & conventions docs | instructions + reviewer | injected as instructions from `docs/*.md` + reviewer checklist (**soft**, as today) |
64
+ | Approbation record (`APPROVAL`) | implementer writes it after human msg | runtime writes it only after completion-gate `approve` (**hard**) |
65
+
66
+ Rule of thumb: **if harness already has a script for it, it becomes a gate
67
+ node; if it only has prose, it becomes instructions + a structured-output
68
+ checklist for the reviewer.**
69
+
70
+ ## 4. Role → model routing (the original buildwright value)
71
+
72
+ ```
73
+ [roles.harness]
74
+ spec-author = remote/claude-sonnet # highest-leverage text in the pipeline
75
+ implementer = remote/gpt-4o # or big local coder model
76
+ reviewer = remote/gpt-4o-mini # semantic pass, medium model
77
+ gauntlet-g* = local/qwen3 # mechanical checklists, high volume
78
+ log-reader = local/qwen3 # as leader.md explicitly anticipates
79
+ ```
80
+
81
+ Langfuse gives the missing feedback loop: per feature — cost per role, cycles,
82
+ stalls, review verdicts. The `retro: <n> dispatches · <s> stalls · <r> restarts`
83
+ line becomes machine-generated, plus `· $<cost> · <model per role>`.
84
+
85
+ `harness/runtime.yaml` is per-project, so **language routing needs no extra
86
+ mechanism**: the Java project pins `implementer: remote/claude-sonnet`, the
87
+ Python project pins a python-strong model — each in its own file, which is the
88
+ hard source of truth. buildwright `BW_ROLE_*` stay as global defaults; the
89
+ project file always wins.
90
+
91
+ ## 5. Graph sketch
92
+
93
+ ```
94
+ load_project → select_feature(pending) → classify_complexity(cheap)
95
+ → spec_author → spec_gate ⏈ interrupt(approve/reject)
96
+ → [approved] mark in_progress
97
+ → implement_batch(next batch from tasks.md, with tools, in worktree)
98
+ → verify_on_disk ── fail ──→ implement_batch (stall/cycle counters)
99
+ → reviewer ── CHANGES_REQUESTED (≤2) ──→ implement_batch
100
+ → traceability_gate (check-traceability.py) ── fail → implement_batch
101
+ → audits (audit_level) ── fail → implement_batch
102
+ → completion_gate ⏈ interrupt(approve/reject)
103
+ → finalize: APPROVAL record, status done, history.md, retro line
104
+ ```
105
+
106
+ Tools per agent mirror the frontmatter the templates already declare
107
+ (`reviewer.md` / `leader.md`: `tools: write: false`) — **the runtime parses that
108
+ frontmatter and wires tool availability accordingly.** The templates become config.
109
+
110
+ - `implementer`: `read_file`, `write_file`, `edit_file`, `run_command` (allowlist)
111
+ - `reviewer`, `gauntlet`, `log-reader`, `spec-author`: read-only + `run_command`
112
+ (gates/scripts only)
113
+ - `run_command`: cwd pinned to the feature worktree, command allowlist read from
114
+ **`harness/runtime.yaml`** — project-owned, hard source of truth; safe built-in
115
+ defaults when absent — plus timeouts and output > ~20 lines → archived to
116
+ `harness/logs/<feature>/T<n>.log`, returns path + tail (the existing
117
+ big-outputs protocol, now enforced by the tool itself)
118
+
119
+ Isolation: one **git worktree per feature**; the completion gate decides
120
+ merge-or-discard. Mirrors OpenCode's own worktree strategy.
121
+
122
+ ## 6. Artifacts are the interface
123
+
124
+ buildwright reads and writes **exactly** the harness artifacts — no fork:
125
+
126
+ - `harness/feature_list.json` (schema-validated), `harness/specs/<name>/{requirements,design,tasks}.md`
127
+ - `harness/progress/{current,history,impl_<name>,review_<name>}.md`, `CHECKPOINTS.md`, `APPROVAL`, `harness/logs/`
128
+
129
+ Consequences: a feature can be specced interactively in OpenCode and implemented
130
+ overnight by buildwright; or specced by buildwright, approved by a human, and
131
+ implemented in OpenCode. `harness-standard` stays runtime-agnostic — buildwright
132
+ is just a second, stricter interpreter of the same protocol.
133
+
134
+ Source of truth for specs remains **markdown** (`tasks.md` checkboxes). The
135
+ runtime parses it (reusing `check-traceability.py` conventions). A machine
136
+ readable `tasks.json` mirror is a possible future harness evolution — explicitly
137
+ *not* a requirement, to avoid forking the protocol.
138
+
139
+ ## 7. CLI surface
140
+
141
+ ```
142
+ buildwright harness status # board + role→model plan (read-only)
143
+ buildwright harness run [--feature F3] [--max-batches N]
144
+ buildwright harness approve [comments] # resumes the parked graph (checkpointer)
145
+ buildwright harness reject "comments" # back to spec_author / implement_batch
146
+ ```
147
+
148
+ LangGraph sqlite checkpointer ⇒ gates survive process restarts; `run` is
149
+ resumable, like the harness "Interrupted session" case in the leader table.
150
+
151
+ ## 8. Requirements (EARS)
152
+
153
+ - WHEN a feature is `pending` THE SYSTEM SHALL route it to `spec_author` and SHALL NOT grant write tools to any agent before the spec gate approves.
154
+ - WHEN `harness/init.sh` exits non-zero or its last line is not `[OK]` THE SYSTEM SHALL return control to `implement_batch` with the log path, up to a configured cycle cap.
155
+ - WHEN a batch produces no file changes THE SYSTEM SHALL count a stall, re-dispatch with `Nothing was written: <paths>…`, and after 2 stalls start a fresh agent instance.
156
+ - WHEN the reviewer issues `CHANGES_REQUESTED` twice THE SYSTEM SHALL park the feature `blocked` at a human gate.
157
+ - WHEN a human gate is reached THE SYSTEM SHALL persist graph state and wait; `approve`/`reject` resumes it.
158
+ - THE SYSTEM SHALL NOT write status `done` unless an `APPROVAL` record exists, written after an explicit completion-gate approval.
159
+ - WHEN `audit_level` is `standard`/`strict` THE SYSTEM SHALL run the corresponding scripts and reject on non-zero exit.
160
+ - WHEN `"parallel": true` THE SYSTEM MAY run independent features concurrently, maintaining exactly one `in_progress` per worktree and honoring `depends_on`.
161
+
162
+ ## 9. Risks & honest limits
163
+
164
+ 1. **Agency = blast radius.** File writes + shell on a real repo. → worktree isolation, allowlisted commands, gates decide merge-or-discard, human completion gate before touching `main`.
165
+ 2. **Not for ambiguous work.** Taste, UX, exploratory refactors stay in the TUI with a human driving. buildwright executes *specified* work; the human gates exist to catch wrong specs.
166
+ 3. **Cycle cost.** implement→verify→review loops burn tokens. → cheap/local models on high-frequency nodes, cycle caps, Langfuse cost telemetry per feature.
167
+ 4. **Spec parsing fragility.** Markdown conventions are parseable but not contractual (mitigated by reusing `check-traceability.py` logic and by schema-validating everything that *is* JSON).
168
+ 5. **Leader judgment gaps.** Complexity classification and slicing are approximated by a cheap classifier node; the spec gate is the human override point.
169
+
170
+ ## 10. Phases
171
+
172
+ | Phase | Deliverable | Risk |
173
+ |---|---|---|
174
+ | **0** | `buildwright harness status` — detect harness project, schema-validate `feature_list.json`, show board + planned role→model mapping. Read-only. | none |
175
+ | **1** | Single batch, supervised: implementer with tools in a worktree → `verify_on_disk` → diff report. Human reviews every step. | low |
176
+ | **2** | Full feature loop: batching queue, stall detection, reviewer + audits + traceability gate, human gates via checkpointer, APPROVAL/history/retro written by runtime. | medium |
177
+ | **3** | Multi-feature & parallel mode (`depends_on`), gauntlet as cheap-local nodes, wekan mirror if configured. | medium |
178
+ | **4** | Feedback: cost/quality per role-model from Langfuse, golden fixtures (harness `evals-fixtures/`) as regression tests for the runtime itself, routing tuned by measured pass-rate per node. | low |
179
+
180
+ ## 11. Decisions (2026-10-27)
181
+
182
+ - **D1 — Allowlist config: `harness/runtime.yaml`.** Accepted as the project-level
183
+ hard source of truth: command allowlist, timeouts, and role→model overrides.
184
+ buildwright `BW_ROLE_*` remain global defaults; the project file always wins.
185
+ - **D2 — Per-language implementer routing.** No dedicated mechanism needed: the
186
+ *target* project declares its stack-appropriate implementer model in its own
187
+ `runtime.yaml` (Java project → java-strong model, Python project → python-strong).
188
+ Revisit only if Langfuse telemetry shows language-specific failure patterns.
189
+ - **D3 — Packaging: standalone installable tool.** Not a harness module (hosts
190
+ install once globally, like OpenCode itself — heavy deps don't belong in every
191
+ target repo) and not primarily a compose stack. A `docker-compose` stays as an
192
+ optional deployment mode for unattended/overnight runs; `harness-standard`
193
+ gets a docs pointer only, so the contract remains artifacts-only.
194
+ Name (decided 2026-10-27): **buildwright** — "the craftsman who builds per the
195
+ spec" (PyPI free, no notable GitHub project). License: MIT, matching
196
+ harness-standard. Hosted on GitHub (portfolio, Pages, trusted publishing).
@@ -0,0 +1,20 @@
1
+ # Optional LiteLLM proxy: centralized routing, fallbacks and budgets.
2
+ # App code never talks to it directly: it speaks OpenAI protocol against
3
+ # http://localhost:4000/v1 (the litellm/* entry in models.yaml).
4
+ model_list:
5
+ - model_name: gpt-4o-mini
6
+ litellm_params:
7
+ model: openai/gpt-4o-mini
8
+ api_key: os.environ/OPENAI_API_KEY
9
+
10
+ - model_name: qwen3
11
+ litellm_params:
12
+ model: ollama/qwen3:8b
13
+ api_base: http://ollama:11434
14
+
15
+ litellm_settings:
16
+ # traces of EVERY call sent to Langfuse, without touching app code:
17
+ success_callback: ["langfuse"]
18
+
19
+ general_settings:
20
+ master_key: os.environ/LITELLM_MASTER_KEY
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "buildwright"
7
+ version = "0.1.0"
8
+ description = "Per-agent model routing for AI agents: LangGraph orchestration, PydanticAI agents, LiteLLM-ready registry, Langfuse tracing"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Jordi Marçal Poy" }]
13
+ requires-python = ">=3.11"
14
+ keywords = [
15
+ "llm",
16
+ "agents",
17
+ "model-routing",
18
+ "langgraph",
19
+ "pydantic-ai",
20
+ "litellm",
21
+ "langfuse",
22
+ "spec-driven-development",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Developers",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ ]
34
+ dependencies = [
35
+ "pydantic-ai-slim[openai]>=1.0",
36
+ "langgraph>=1.0",
37
+ "langfuse>=3.0",
38
+ "pydantic-settings>=2.4",
39
+ "PyYAML>=6.0",
40
+ ]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/jordimarsal/buildwright"
44
+ Repository = "https://github.com/jordimarsal/buildwright"
45
+ Issues = "https://github.com/jordimarsal/buildwright/issues"
46
+
47
+ [project.scripts]
48
+ buildwright = "buildwright.cli:main"
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=8.0",
53
+ "ruff>=0.6",
54
+ "mypy>=1.11",
55
+ "types-PyYAML>=6.0",
56
+ ]
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = ["src/buildwright"]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+
64
+ [tool.ruff]
65
+ line-length = 100
66
+ src = ["src", "tests"]
@@ -0,0 +1,3 @@
1
+ """Per-agent model routing: cada agent amb el seu model, local o remot."""
2
+
3
+ __version__ = "0.1.0"