hybrid-forge 0.2.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 (63) hide show
  1. hybrid_forge-0.2.0/LICENSE +201 -0
  2. hybrid_forge-0.2.0/PKG-INFO +470 -0
  3. hybrid_forge-0.2.0/README.md +441 -0
  4. hybrid_forge-0.2.0/forge/__init__.py +8 -0
  5. hybrid_forge-0.2.0/forge/__main__.py +6 -0
  6. hybrid_forge-0.2.0/forge/artifacts.py +186 -0
  7. hybrid_forge-0.2.0/forge/budget.py +286 -0
  8. hybrid_forge-0.2.0/forge/cli.py +2697 -0
  9. hybrid_forge-0.2.0/forge/config.py +1628 -0
  10. hybrid_forge-0.2.0/forge/evidence.py +672 -0
  11. hybrid_forge-0.2.0/forge/failures.py +1061 -0
  12. hybrid_forge-0.2.0/forge/imports.py +293 -0
  13. hybrid_forge-0.2.0/forge/ingest.py +882 -0
  14. hybrid_forge-0.2.0/forge/llama.py +497 -0
  15. hybrid_forge-0.2.0/forge/loop.py +8121 -0
  16. hybrid_forge-0.2.0/forge/manifests.py +211 -0
  17. hybrid_forge-0.2.0/forge/memory.py +1071 -0
  18. hybrid_forge-0.2.0/forge/patch.py +1033 -0
  19. hybrid_forge-0.2.0/forge/presets.py +172 -0
  20. hybrid_forge-0.2.0/forge/profile.py +164 -0
  21. hybrid_forge-0.2.0/forge/prompts.py +3447 -0
  22. hybrid_forge-0.2.0/forge/providers/__init__.py +175 -0
  23. hybrid_forge-0.2.0/forge/providers/_http.py +170 -0
  24. hybrid_forge-0.2.0/forge/providers/anthropic_api.py +251 -0
  25. hybrid_forge-0.2.0/forge/providers/base.py +668 -0
  26. hybrid_forge-0.2.0/forge/providers/claude_cli.py +307 -0
  27. hybrid_forge-0.2.0/forge/providers/gemini.py +166 -0
  28. hybrid_forge-0.2.0/forge/providers/llamacpp.py +536 -0
  29. hybrid_forge-0.2.0/forge/providers/openai_compat.py +434 -0
  30. hybrid_forge-0.2.0/forge/ratify.py +721 -0
  31. hybrid_forge-0.2.0/forge/replay.py +345 -0
  32. hybrid_forge-0.2.0/forge/repomap.py +143 -0
  33. hybrid_forge-0.2.0/forge/respec.py +1426 -0
  34. hybrid_forge-0.2.0/forge/routes.py +118 -0
  35. hybrid_forge-0.2.0/forge/secrets.py +69 -0
  36. hybrid_forge-0.2.0/forge/state.py +1815 -0
  37. hybrid_forge-0.2.0/forge/tokens.py +80 -0
  38. hybrid_forge-0.2.0/forge/toolchain.py +801 -0
  39. hybrid_forge-0.2.0/forge/tools.py +474 -0
  40. hybrid_forge-0.2.0/forge/ui/__init__.py +1 -0
  41. hybrid_forge-0.2.0/forge/ui/index.html +380 -0
  42. hybrid_forge-0.2.0/forge/ui/server.py +362 -0
  43. hybrid_forge-0.2.0/forge/wizard.py +805 -0
  44. hybrid_forge-0.2.0/hybrid_forge.egg-info/PKG-INFO +470 -0
  45. hybrid_forge-0.2.0/hybrid_forge.egg-info/SOURCES.txt +61 -0
  46. hybrid_forge-0.2.0/hybrid_forge.egg-info/dependency_links.txt +1 -0
  47. hybrid_forge-0.2.0/hybrid_forge.egg-info/entry_points.txt +2 -0
  48. hybrid_forge-0.2.0/hybrid_forge.egg-info/requires.txt +4 -0
  49. hybrid_forge-0.2.0/hybrid_forge.egg-info/top_level.txt +1 -0
  50. hybrid_forge-0.2.0/pyproject.toml +60 -0
  51. hybrid_forge-0.2.0/setup.cfg +4 -0
  52. hybrid_forge-0.2.0/tests/test_blind_grading.py +163 -0
  53. hybrid_forge-0.2.0/tests/test_blind_grading_defects.py +192 -0
  54. hybrid_forge-0.2.0/tests/test_forge.py +23084 -0
  55. hybrid_forge-0.2.0/tests/test_handback_ui.py +143 -0
  56. hybrid_forge-0.2.0/tests/test_handback_ui_render.py +54 -0
  57. hybrid_forge-0.2.0/tests/test_lint.py +112 -0
  58. hybrid_forge-0.2.0/tests/test_memory.py +716 -0
  59. hybrid_forge-0.2.0/tests/test_multimodal.py +692 -0
  60. hybrid_forge-0.2.0/tests/test_recorded_output.py +311 -0
  61. hybrid_forge-0.2.0/tests/test_sample_project.py +803 -0
  62. hybrid_forge-0.2.0/tests/test_tools.py +457 -0
  63. hybrid_forge-0.2.0/tests/test_wizard.py +867 -0
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,470 @@
1
+ Metadata-Version: 2.4
2
+ Name: hybrid-forge
3
+ Version: 0.2.0
4
+ Summary: Autonomous plan-and-execute coding loop over any models you bring.
5
+ Author: Nik
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/nschroeder8472/hybrid-forge
8
+ Project-URL: Repository, https://github.com/nschroeder8472/hybrid-forge
9
+ Project-URL: Issues, https://github.com/nschroeder8472/hybrid-forge/issues
10
+ Keywords: llm,agent,codegen,llama.cpp,claude,automation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: dev
26
+ Requires-Dist: flake8>=7; extra == "dev"
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # Hybrid Forge
31
+
32
+ An autonomous plan-and-execute coding loop. You define a feature, say go, and a
33
+ daemon runs the backlog to completion — planning, building, verifying, and
34
+ reviewing — pausing on its own when a usage window closes and resuming when it
35
+ reopens.
36
+
37
+ The point is not to replace Claude with a local model. It is to spend expensive
38
+ reasoning tokens on the parts that need reasoning, and let a cheaper model
39
+ absorb the token-heavy bulk generation for the cost of electricity.
40
+
41
+ ## Measured throughput
42
+
43
+ Four runs against one repository, on two local models sharing a single 32 GB
44
+ card. Every figure is read from that project's `run.db`.
45
+
46
+ | Run | Backlog | Tickets | Attempts | Model calls | Tokens | Wall clock |
47
+ |---|---|---:|---:|---:|---:|---:|
48
+ | 1 | TypeScript port of a Godot level format | 9 | 18 | 169 | 3.00 M | 5 h 32 m |
49
+ | 2 | Canvas view for that format | 5 | 10 | 86 | 2.27 M | 2 h 00 m |
50
+ | 3 | Defects found reviewing run 2 | 3 | 3 | 54 | 1.13 M | 57 m |
51
+ | 4 | The one ticket run 3 could not place | 1 | 1 | 13 | 0.33 M | 15 m |
52
+ | | **Total** | **18** | **32** | **322** | **6.73 M** | **8 h 44 m** |
53
+
54
+ 8 of the 18 tickets passed on their first attempt. The longest uninterrupted
55
+ run was 5 h 32 m.
56
+
57
+ Delivered by those runs and present in the repository now:
58
+
59
+ | | |
60
+ |---:|---|
61
+ | 34 | TypeScript files |
62
+ | 1,236 | lines of source |
63
+ | 2,213 | lines of tests |
64
+ | 184 | tests passing |
65
+ | 4 / 4 | verify commands exiting 0 (`lint`, `typecheck`, `typecheck:browser`, `test`) |
66
+
67
+ 1 of those 34 files was edited by hand, between run 2 and run 3: 5 tests removed
68
+ from one file. They were there because run 2's spec put "the test command exits
69
+ 0" on every ticket — a criterion the harness already settles — and the tester
70
+ encoded it the only way a criterion can be encoded, as tests that shell out to
71
+ run the commands. Reviewing run 2 found it, the tests were removed, and the rule
72
+ is now enforced where it was broken: the tester is told the harness runs those
73
+ commands, and `/forge-spec-check` reports the criterion at authoring time. Runs
74
+ 3 and 4 were specified without it and needed no such edit.
75
+
76
+ Run 4 is the same shape one step further. Run 3 left one ticket parked because
77
+ its spec named no test file, so the tester's output landed outside the ticket's
78
+ own scope where the executor could not repair it — and because its criteria
79
+ asserted things about a DOM entry point that no test in the project can reach.
80
+ Both are now reported before a run starts, by `forge ingest` and by
81
+ `/forge-spec-check`. Respecified against what a test can actually assert, the
82
+ same work landed in one attempt.
83
+
84
+ Two earlier runs are excluded from the table. Both stopped for causes since
85
+ fixed, so neither describes what the loop does now:
86
+ [docs/CANVAS-POSTMORTEM.md](docs/CANVAS-POSTMORTEM.md).
87
+
88
+ ## The loop is not a conversation
89
+
90
+ The orchestrator is a Python daemon that owns the state machine and reads its
91
+ next move from SQLite. No model decides what happens next.
92
+
93
+ That distinction is the whole design. A loop driven by a model inside a chat
94
+ session dies when the context window fills, when the process is killed, or when
95
+ a usage limit is hit at 2am. This one survives all three, because none of them
96
+ were holding the plan.
97
+
98
+ ```
99
+ forged (daemon)
100
+ ├─ state: .hybridforge/run.db
101
+ ├─ RATIFY every role signs off on the ticket before it is built
102
+ ├─ BUILD executor writes the implementation against the spec
103
+ ├─ APPLY edits land on disk; anything outside scope is rejected
104
+ ├─ TESTS tester encodes the ticket's criteria — never its own
105
+ ├─ VERIFY lint / typecheck / test, before any model reviews
106
+ ├─ REVIEW reviewer reads the diff against the spec
107
+ ├─ RECORD durable outcomes to memory (opt-in, usually nothing)
108
+ └─ COMMIT optional
109
+ loop until DONE | BLOCKED | stopped
110
+ ```
111
+
112
+ Diagrams of the loop, the two ways work enters it, and what differs between a
113
+ greenfield repo and an existing one: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
114
+
115
+ ## Bring your own models
116
+
117
+ Four roles — `planner`, `executor`, `tester`, `reviewer` — and any model can play
118
+ any of them. Local models are llama.cpp; cloud is whichever vendor you have:
119
+
120
+ | kind | reaches |
121
+ |---|---|
122
+ | `llamacpp` | **the local backend.** `llama-server` in router mode, swapping checkpoints on one endpoint as the loop alternates roles |
123
+ | `openai` | OpenAI, and the gateways that speak its wire — OpenRouter, LiteLLM, Together, DeepSeek |
124
+ | `anthropic` | Anthropic Messages API |
125
+ | `gemini` | Google Gemini |
126
+ | `claude-cli` | headless `claude -p`, so planning and review run on an existing Claude subscription rather than an API key |
127
+
128
+ One local backend is deliberate. Forge used to carry four — Ollama and friends
129
+ through `openai`, FreeToken, and a `command` escape hatch — and each had its own
130
+ way of being asked what it was serving, its own way of being told to load
131
+ something else, and its own silent failure. An Ollama name that never matched
132
+ because config omitted `:latest`; an engine that answered to any model id and
133
+ echoed it back. Every one of them cost a run before it was found, and carrying
134
+ them meant diagnostics could only say what all four had in common.
135
+
136
+ With one, they can name the thing in front of you: the preset a checkpoint is
137
+ spawned from, the `--models-max` slot another model is holding, the reasoning
138
+ budget a thinking model is spending its whole answer on, the context window out
139
+ of the argv rather than out of a guess. `forge models` writes the preset from
140
+ your config, so the numbers in the two files cannot drift apart.
141
+
142
+ It also means forge can install the backend rather than describe it. `forge llama
143
+ install` fetches a pinned llama.cpp build, picks CUDA over Vulkan by reading the
144
+ GPU's compute capability, and verifies the download against the SHA-256 GitHub
145
+ published for it before unpacking. The same checkpoint measured 16 tok/s on a
146
+ Vulkan build and 353 on CUDA, and nothing reports the slow path — see
147
+ [docs/LLAMA-PACKAGING.md](docs/LLAMA-PACKAGING.md).
148
+
149
+ ### Two models that work
150
+
151
+ A pairing that has run this loop end to end, on a single 32 GB card. It is one
152
+ machine and one project rather than a benchmark, so take it as a starting point
153
+ that is known to work rather than as the answer:
154
+
155
+ | Role | Checkpoint | Why this one |
156
+ |---|---|---|
157
+ | `planner`, `reviewer` | **Nemotron-3-Nano-Omni-30B-A3B-Reasoning**, Q4_K_M | An A3B MoE: 30B of weights, ~3B active, so it reads a long ticket and a long diff at around 150 tok/s. Planning and review are the roles that read the most and write the least, which is exactly what a sparse model is cheap at |
158
+ | `executor`, `tester` | **Qwen3.8-27B**, UD-Q4_K_M | Dense, ~40 tok/s, and the executor emits whole files — the role where being right per token beats being fast per token. It is the half of the run worth spending the slower model on |
159
+
160
+ Both at `ctx-size = 131072`, both `exclusive`, `--models-max 1`. They do not
161
+ co-reside: about 25 GiB resident each against 32 GB of VRAM, so the router
162
+ swaps checkpoints as the loop alternates roles, and that swap is why
163
+ `loop.ratifyOrder` is worth grouping by model — see `/forge-setup`.
164
+
165
+ One run, for scale: five tickets, all landing, 120 minutes, 86 model calls and
166
+ 2.27M tokens — 1.10M in and 415K out through the executor pair, 545K in and
167
+ 210K out through the planner pair. All of it local, on electricity.
168
+
169
+ **Set `reasoningBudget` on both, before the first run.** Neither of these is
170
+ usable without it. Unbounded, the Nemotron spent an entire 32,768-token output
171
+ budget on hidden reasoning and never began its answer — forge notices, throws
172
+ the call away and retries with thinking off, at a cost of roughly 93 seconds of
173
+ wasted generation per planner and reviewer call. 8,192 for the Nemotron and
174
+ 6,144 for the Qwen leave both room to answer. The budget is one number per
175
+ checkpoint, so size it against the *smaller* of the two output budgets the
176
+ roles sharing it are given.
177
+
178
+ Two smaller things that cost a run each if missed. Nemotron's "Omni" is
179
+ multimodal, and a preset generated from `--models-dir` loads the vision
180
+ projector beside a text-only checkpoint, spending VRAM no role here uses —
181
+ `no-mmproj = true`. And the Qwen returns its `<think>` block inline in
182
+ `content` rather than in `reasoning_content` depending on the chat template;
183
+ forge strips it at the provider boundary, which is why it is worth knowing that
184
+ a reply looking truncated in the logs may have been trimmed there.
185
+
186
+ Adding a backend is one module and one registry line. Nothing in the loop, the
187
+ budget gate, or the dashboard knows which kind it is talking to.
188
+
189
+ ## Waiting is a feature
190
+
191
+ Subscription plans enforce a rolling usage window, and when it is exhausted the
192
+ Claude CLI does not return a `429` with a header — it prints a sentence. The
193
+ budget gate parses that into a reset time and parks the run in
194
+ `waiting_budget`, which is a live state, not a failure: the dashboard shows when
195
+ the window reopens and the loop wakes itself up.
196
+
197
+ The same gate checks context windows before a call rather than after, so a
198
+ prompt that will not fit is trimmed or the ticket is flagged for splitting —
199
+ instead of the backend silently truncating an implementation.
200
+
201
+ ## Project memory reaches the executor
202
+
203
+ The daemon retrieves prior decisions from MemPalace before each ticket and
204
+ passes them to both the executor and the reviewer, so a local model does not
205
+ relitigate a convention settled weeks ago. It speaks MCP directly — no Claude
206
+ Code in the path — and discovers the server's tool surface at connect time
207
+ rather than hardcoding names that shift between MemPalace versions.
208
+
209
+ Retrieved context is the *droppable* part of a prompt: when a ticket does not
210
+ fit the executor's context window, memory yields and the spec stays. A memory
211
+ outage degrades the run to "no context", never ends it.
212
+
213
+ Write-back is the other direction, and it is opt-in. After a ticket passes
214
+ review the loop can ask whether the work settled a decision or produced a
215
+ correction worth keeping, and record it. Off by default, because retrieval only
216
+ reads while recording mutates a store every future session reads back with no
217
+ undo — and because a memory full of ticket narration is worse than an empty
218
+ one. `dryRun` logs what it would write without writing it. Entries that match a
219
+ credential shape are refused before any network call, and destructive tools are
220
+ never selected.
221
+
222
+ ## Where each piece runs
223
+
224
+ **The daemon goes where the project goes.** It writes the executor's files, runs
225
+ your lint and test commands, and builds the diff the reviewer reads — all
226
+ against a local working tree. It is not a compute service you can point at a
227
+ repo across the network.
228
+
229
+ So the split is by weight, not by role:
230
+
231
+ | Machine | Runs |
232
+ |---|---|
233
+ | GPU host | `llama-server` in router mode, serving the local models; MemPalace |
234
+ | Your workstation | the repo, the daemon, Claude Code, the toolchain |
235
+
236
+ Model calls and memory reads cross the network; files, git, and builds stay
237
+ local. Any network the two machines share will do — the only requirement is
238
+ that the daemon can reach the model and memory endpoints, and that those
239
+ endpoints are not reachable by anything else, since neither has authentication
240
+ of its own. This is also the only workable shape for a macOS-only
241
+ toolchain — `xcodebuild` and the simulators cannot run in a Linux container, so
242
+ verification has to happen on the Mac.
243
+
244
+ The daemon is stdlib-only Python, so "install it next to the project" is
245
+ `pip install -e .`, not a deployment.
246
+
247
+ **Containerizing is for the verify step, not the daemon.** `commands` are
248
+ ordinary shell strings, so isolating model-authored code from your host needs
249
+ no special support:
250
+
251
+ ```json
252
+ "commands": {
253
+ "test": "docker run --rm -v \"/abs/path/repo\":/w -w /w python:3.12-slim python -m pytest -q"
254
+ }
255
+ ```
256
+
257
+ That is worth doing on its own merits — today those commands run directly on
258
+ your machine, and they are running code a model just wrote.
259
+
260
+ ## Start from a plan written anywhere
261
+
262
+ The loop's input does not have to come from this tool:
263
+
264
+ ```bash
265
+ forge ingest plan.md # a spec from the Claude app, a web chat, or a human
266
+ forge go
267
+ ```
268
+
269
+ If the document already contains ticket-shaped sections they are used verbatim —
270
+ no model re-reads them, and the acceptance criteria stay the ones their author
271
+ wrote. Only freeform documents go through the planner.
272
+
273
+ ## What it deliberately does not do
274
+
275
+ **The executor never decides scope.** It gets an explicit spec, an allowed file
276
+ list, and acceptance criteria. Every edit is checked against that list before it
277
+ touches disk, and paths that escape the project root are refused outright.
278
+
279
+ **The executor never authors its own acceptance criteria.** A model that writes
280
+ both the implementation and the test it is judged against will encode its bugs
281
+ as passing tests.
282
+
283
+ **Triage is not delegated.** A ticket routed `withheld:<reason>` is left for a
284
+ human even if that stalls the backlog. The reason travels in the route —
285
+ `withheld:security`, `withheld:concurrency`, `withheld:interface` — so a ticket
286
+ parked in March still says in May what it was parked for. Auth, concurrency,
287
+ migrations, and public API surface stay with a person.
288
+
289
+ **A bug is never fixed on faith.** `forge bug` writes a test that asserts the
290
+ correct behavior and requires it to *fail* before any fix is attempted. A fault
291
+ that cannot be demonstrated parks for a human rather than being fixed against a
292
+ guess — see [docs/BUG-LOOP.md](docs/BUG-LOOP.md).
293
+
294
+ **A `BLOCKED:` never retries.** An underspecified spec does not improve by being
295
+ asked again — the run parks the ticket and says what was ambiguous.
296
+
297
+ ## Monitoring
298
+
299
+ `forge go` serves a dashboard on `127.0.0.1:8799`: backlog with per-ticket
300
+ status, live event stream, recent steps, per-model token usage, and
301
+ pause/resume/stop. It reads the same SQLite the loop writes, so a crashed
302
+ dashboard cannot take a run with it and a restarted one reattaches with no
303
+ handshake.
304
+
305
+ It has no authentication and its stop button ends a run, so it binds to
306
+ loopback and warns on startup if you point `ui.host` anywhere else. Tunnel in
307
+ rather than widening the bind address, or put something that authenticates in
308
+ front of it.
309
+
310
+ ## Commands
311
+
312
+ ```bash
313
+ forge init [--defaults] # set up .hybridforge/ for this repo, with prompts
314
+ forge doctor # probe every configured model
315
+ forge ingest <file|-> # spec or plan -> reviewable backlog
316
+ forge go [--plan f] [--open]# run until done or stopped
317
+ forge go --retries N # requeue and respec what did not land, N more
318
+ # times; -1 = until clean or stopped
319
+ forge status # one-shot summary
320
+ forge retry [--respec] # requeue failed tickets, optionally re-specced
321
+ forge bug "<report>" # reproduce a bug, then fix it
322
+ forge toolchain # what tests each language; set up what nothing does
323
+ forge criteria [ID --accept N]
324
+ # adopt a criterion the loop proposed and refused
325
+ forge advise <ID> "<note>" # a note the planner and executor read next pass
326
+ forge release <ID> "<why>" # hand a withheld ticket back to the executor
327
+ forge discharge <ID> # mark a withheld ticket done — you wrote the code
328
+ forge models # write the llama.cpp preset from your config
329
+ forge llama [status|install|list]
330
+ # fetch or inspect the pinned llama.cpp build
331
+ forge replay # re-read a past run's output with today's parsers
332
+ forge prune # delete the artifact trees of old runs
333
+ forge pause | resume | stop # applied after the current step, never mid-patch
334
+ forge ui # dashboard without running the loop
335
+ forge ui --host IP --port N # bind it elsewhere, this run only (no auth!)
336
+ ```
337
+
338
+ `advise`, `release` and `discharge` are the return channel: a run that parks a
339
+ ticket can be answered without restarting it, and a ticket you implemented by
340
+ hand can be closed without pretending the loop did it. See
341
+ [docs/HANDBACK.md](docs/HANDBACK.md).
342
+
343
+ Two Claude Code plugins sit beside the CLI rather than wrapping it. **Forge
344
+ Setup** (`/forge-setup`) handles the cold start — install check, endpoint
345
+ probes, the machine profile, and this repo's verify commands and never-delegate
346
+ list. **Forge Spec** (`/forge-spec`, `/forge-spec-check`) is where a feature
347
+ gets designed into a document `forge ingest` parses verbatim, so the acceptance
348
+ criteria stay in the words a human wrote. Running the loop stays in the
349
+ terminal, where it survives the session.
350
+
351
+ ## Setup asks once
352
+
353
+ `forge init` prompts for the endpoints, **probes each one while you are still
354
+ sitting there**, and writes what it learned to a machine-level profile
355
+ (`~/.config/hybrid-forge/profile.json`, `%APPDATA%\hybrid-forge\` on Windows).
356
+ The next repo starts from those answers, so the second setup is Enter-through
357
+ except for the things that repo actually decides.
358
+
359
+ A wrong endpoint found now costs one retyped line. The same wrong endpoint found
360
+ by `forge go` costs the run.
361
+
362
+ For the verify commands it does not guess at all. It collects the repo's own CI
363
+ workflow, Makefile, and contributing guide, hands them to the planner model, and
364
+ asks what this project actually runs — so you get `cargo nextest run --workspace`
365
+ because that is what CI runs, not `cargo test` because a `Cargo.toml` exists.
366
+ Nothing found means an empty field, which the loop skips. A wrong `test` command
367
+ does not fail once; it fails `maxAttempts` times per ticket and parks the whole
368
+ backlog, looking exactly like a bad executor model.
369
+
370
+ Credentials are never stored. Providers resolve keys through `apiKeyEnv`, the
371
+ *name* of an environment variable, and that name is what the profile keeps.
372
+
373
+ ```bash
374
+ forge init # prompts, probes, remembers
375
+ forge init --defaults # no questions; writes a config to edit by hand
376
+ ```
377
+
378
+ With no terminal attached — piped, redirected, or run from a script — it takes
379
+ every default and says so rather than blocking on stdin nobody is watching.
380
+
381
+ First time through, [docs/QUICKSTART.md](docs/QUICKSTART.md) walks the whole
382
+ setup — llama.cpp and which models to run, MemPalace, the daemon, and a narrated
383
+ first `forge init`. [docs/SETUP.md](docs/SETUP.md) is the reference behind it:
384
+ every option, every alternative, and the full security discussion. The daemon is
385
+ stdlib-only Python 3.10+ — a failed `pip install` is a bad way to discover that
386
+ an overnight run never started.
387
+
388
+ [docs/BUG-LOOP.md](docs/BUG-LOOP.md) covers `forge bug` — the loop that has to
389
+ reproduce a fault before it is allowed to fix it, and what it refuses to do when
390
+ it cannot.
391
+
392
+ [docs/CANVAS-POSTMORTEM.md](docs/CANVAS-POSTMORTEM.md) is the shortest way to
393
+ see what this loop's failures actually look like: a backlog that parked without
394
+ writing a line because the parser dropped three fifths of its criteria, and the
395
+ run after it that went green while deleting the project's dependencies.
396
+
397
+ [docs/LOOP-INVARIANTS.md](docs/LOOP-INVARIANTS.md) is the one to read before
398
+ adding a step, a role, or any check that attributes blame. Eighteen rules that
399
+ hold across the whole harness — read scope versus write scope, why an anchor the loop
400
+ wrote is not an anchor, why attribution must come from diagnostic blocks and
401
+ never from raw output. Each was learned by breaking it.
402
+
403
+ [docs/CONFIG.md](docs/CONFIG.md) is the key-by-key reference for
404
+ `.hybridforge/config.json`, with a populated example at
405
+ [templates/config.sample.json](templates/config.sample.json) to copy from.
406
+
407
+ [docs/ROADMAP.md](docs/ROADMAP.md) holds what is not built yet and why — the
408
+ bug-report loop first among it.
409
+
410
+ ## Layout
411
+
412
+ ```
413
+ forge/providers/ adapter layer — one module per backend
414
+ forge/loop.py the state machine
415
+ forge/budget.py context accounting + rate-limit gate
416
+ forge/state.py SQLite: runs, tickets, steps, events, usage, control
417
+ forge/memory.py MCP client for project memory (read + guarded write)
418
+ forge/secrets.py credential detection for anything about to be persisted
419
+ forge/ingest.py outside spec/plan -> backlog
420
+ forge/patch.py model output -> file writes, with scope enforcement
421
+ forge/manifests.py what a build manifest declared before a rewrite
422
+ forge/prompts.py per-role prompts (a contract the parsers depend on)
423
+ forge/ratify.py the pre-build sign-off pass
424
+ forge/respec.py revising a ticket from why it failed
425
+ forge/routes.py delegate vs withheld:<reason>, and what withheld means
426
+ forge/evidence.py which files a ticket may read
427
+ forge/llama.py fetching and verifying the pinned llama.cpp build
428
+ forge/presets.py config -> the llama.cpp router preset
429
+ forge/wizard.py interactive `forge init` — asks, probes, never hangs
430
+ forge/toolchain.py reads the repo's CI/docs to find its verify commands
431
+ forge/profile.py machine-level endpoints, reused by the next repo
432
+ forge/ui/ dashboard
433
+ plugins/forge-setup/ Claude Code plugin: machine + repository setup
434
+ plugins/forge-spec/ Claude Code plugin: spec authoring, triage, memory
435
+ examples/sample-project/ the fixture a loop change is run against
436
+ scripts/sample_workspace.py copies that fixture somewhere a run may write
437
+ tests/ python -m unittest discover tests
438
+ ```
439
+
440
+ ## Changing the loop
441
+
442
+ Unit tests say a change is what you meant. They do not say what it does to a
443
+ run, and most of what this project knows came from watching a real backlog
444
+ fail. `examples/sample-project` is the cheapest imitation of that: two builds,
445
+ a three-ticket spec on the parsed path, one dependency between tickets, a green
446
+ baseline, and a bug the suite does not catch.
447
+
448
+ ```
449
+ python scripts/sample_workspace.py # copy it somewhere a run may write
450
+ cd <the path it prints>
451
+ forge --root . doctor # the coverage matrix, no tokens spent
452
+ forge ingest SPEC.md # three tickets, parsed
453
+ forge go
454
+ ```
455
+
456
+ Run it against a copy, never in place — a run writes code, a database and an
457
+ artifact tree, and the committed tree is a fixture. `.gitignore` holds the
458
+ fixture as an allow-list so anything a run leaves behind is ignored rather than
459
+ staged, and `tests/test_sample_project.py` pins what a run depends on: both
460
+ suites green, the spec parsed rather than replanned, every path it names owned
461
+ by a build, every ticket carrying its own test file, the four paths the spec
462
+ must find missing, and the seeded defect still a defect. Those run with the
463
+ ordinary suite and spend nothing.
464
+
465
+ ## Status
466
+
467
+ Working end to end, and young. Run it on a low-stakes slice first. The failure
468
+ mode to watch for is not "the code doesn't compile" — it is plausible code that
469
+ quietly does the wrong thing, which is what the review step and the scope
470
+ checks exist to catch.