flywheel-relay 0.2.5__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 (99) hide show
  1. flywheel_relay-0.2.5/CHANGELOG.md +120 -0
  2. flywheel_relay-0.2.5/LICENSE +110 -0
  3. flywheel_relay-0.2.5/MANIFEST.in +4 -0
  4. flywheel_relay-0.2.5/PKG-INFO +338 -0
  5. flywheel_relay-0.2.5/README.md +319 -0
  6. flywheel_relay-0.2.5/docs/ACCOUNTABILITY.md +71 -0
  7. flywheel_relay-0.2.5/docs/BENCHMARKS.md +63 -0
  8. flywheel_relay-0.2.5/docs/GITHUB-ONLY-INSTALL.md +56 -0
  9. flywheel_relay-0.2.5/docs/MCP-PARITY-DESIGN-20260908.md +18 -0
  10. flywheel_relay-0.2.5/docs/RELEASE-0.2.0-CANDIDATE.md +65 -0
  11. flywheel_relay-0.2.5/docs/REMOTE-SETUP.md +159 -0
  12. flywheel_relay-0.2.5/docs/art/accountability-lane.svg +19 -0
  13. flywheel_relay-0.2.5/docs/art/clause-ladder.svg +17 -0
  14. flywheel_relay-0.2.5/docs/art/endpoint-ladder.svg +19 -0
  15. flywheel_relay-0.2.5/docs/art/relay-header.svg +1 -0
  16. flywheel_relay-0.2.5/docs/art/relay.art.json +198 -0
  17. flywheel_relay-0.2.5/pyproject.toml +33 -0
  18. flywheel_relay-0.2.5/setup.cfg +4 -0
  19. flywheel_relay-0.2.5/src/flywheel_relay.egg-info/PKG-INFO +338 -0
  20. flywheel_relay-0.2.5/src/flywheel_relay.egg-info/SOURCES.txt +97 -0
  21. flywheel_relay-0.2.5/src/flywheel_relay.egg-info/dependency_links.txt +1 -0
  22. flywheel_relay-0.2.5/src/flywheel_relay.egg-info/entry_points.txt +2 -0
  23. flywheel_relay-0.2.5/src/flywheel_relay.egg-info/requires.txt +3 -0
  24. flywheel_relay-0.2.5/src/flywheel_relay.egg-info/top_level.txt +1 -0
  25. flywheel_relay-0.2.5/src/relay/__init__.py +29 -0
  26. flywheel_relay-0.2.5/src/relay/__main__.py +4 -0
  27. flywheel_relay-0.2.5/src/relay/approvals.py +57 -0
  28. flywheel_relay-0.2.5/src/relay/architect.py +32 -0
  29. flywheel_relay-0.2.5/src/relay/async_runs.py +328 -0
  30. flywheel_relay-0.2.5/src/relay/bisect.py +110 -0
  31. flywheel_relay-0.2.5/src/relay/cert.py +142 -0
  32. flywheel_relay-0.2.5/src/relay/claim_grounding.py +135 -0
  33. flywheel_relay-0.2.5/src/relay/compaction.py +221 -0
  34. flywheel_relay-0.2.5/src/relay/contract.py +146 -0
  35. flywheel_relay-0.2.5/src/relay/conventions.py +43 -0
  36. flywheel_relay-0.2.5/src/relay/edit_plan.py +108 -0
  37. flywheel_relay-0.2.5/src/relay/endpoints.py +267 -0
  38. flywheel_relay-0.2.5/src/relay/hashline.py +60 -0
  39. flywheel_relay-0.2.5/src/relay/injection_probe.py +101 -0
  40. flywheel_relay-0.2.5/src/relay/integrity.py +156 -0
  41. flywheel_relay-0.2.5/src/relay/intent_audit.py +149 -0
  42. flywheel_relay-0.2.5/src/relay/local_agent.py +313 -0
  43. flywheel_relay-0.2.5/src/relay/local_agent_cli.py +513 -0
  44. flywheel_relay-0.2.5/src/relay/local_git.py +70 -0
  45. flywheel_relay-0.2.5/src/relay/local_loop.py +296 -0
  46. flywheel_relay-0.2.5/src/relay/local_mcp.py +363 -0
  47. flywheel_relay-0.2.5/src/relay/local_repomap.py +219 -0
  48. flywheel_relay-0.2.5/src/relay/local_review_agent.py +93 -0
  49. flywheel_relay-0.2.5/src/relay/local_session.py +95 -0
  50. flywheel_relay-0.2.5/src/relay/local_tools.py +300 -0
  51. flywheel_relay-0.2.5/src/relay/messages_api.py +144 -0
  52. flywheel_relay-0.2.5/src/relay/oauth.py +267 -0
  53. flywheel_relay-0.2.5/src/relay/remote_cli.py +40 -0
  54. flywheel_relay-0.2.5/src/relay/remote_mcp.py +275 -0
  55. flywheel_relay-0.2.5/src/relay/remote_oauth.py +182 -0
  56. flywheel_relay-0.2.5/src/relay/remote_state.py +121 -0
  57. flywheel_relay-0.2.5/src/relay/review.py +168 -0
  58. flywheel_relay-0.2.5/src/relay/run_view.py +207 -0
  59. flywheel_relay-0.2.5/src/relay/session_store.py +61 -0
  60. flywheel_relay-0.2.5/src/relay/tools_prompt.py +34 -0
  61. flywheel_relay-0.2.5/src/relay/udiff.py +80 -0
  62. flywheel_relay-0.2.5/src/relay/verified_bon.py +111 -0
  63. flywheel_relay-0.2.5/src/relay/watch.py +73 -0
  64. flywheel_relay-0.2.5/tests/test_ambient_repo_map.py +142 -0
  65. flywheel_relay-0.2.5/tests/test_apply_diff.py +101 -0
  66. flywheel_relay-0.2.5/tests/test_approvals.py +135 -0
  67. flywheel_relay-0.2.5/tests/test_architect.py +240 -0
  68. flywheel_relay-0.2.5/tests/test_async_runs.py +403 -0
  69. flywheel_relay-0.2.5/tests/test_bisect.py +88 -0
  70. flywheel_relay-0.2.5/tests/test_cert.py +179 -0
  71. flywheel_relay-0.2.5/tests/test_claim_grounding.py +77 -0
  72. flywheel_relay-0.2.5/tests/test_compaction.py +118 -0
  73. flywheel_relay-0.2.5/tests/test_conventions.py +43 -0
  74. flywheel_relay-0.2.5/tests/test_edit_plan.py +175 -0
  75. flywheel_relay-0.2.5/tests/test_endpoints.py +169 -0
  76. flywheel_relay-0.2.5/tests/test_hashline_edits.py +184 -0
  77. flywheel_relay-0.2.5/tests/test_injection_probe.py +59 -0
  78. flywheel_relay-0.2.5/tests/test_integrity.py +45 -0
  79. flywheel_relay-0.2.5/tests/test_intent_audit.py +85 -0
  80. flywheel_relay-0.2.5/tests/test_local_agent.py +232 -0
  81. flywheel_relay-0.2.5/tests/test_local_agentic.py +505 -0
  82. flywheel_relay-0.2.5/tests/test_local_git.py +89 -0
  83. flywheel_relay-0.2.5/tests/test_local_mcp.py +680 -0
  84. flywheel_relay-0.2.5/tests/test_local_repomap.py +100 -0
  85. flywheel_relay-0.2.5/tests/test_local_review_agent.py +75 -0
  86. flywheel_relay-0.2.5/tests/test_messages_api.py +57 -0
  87. flywheel_relay-0.2.5/tests/test_oauth.py +205 -0
  88. flywheel_relay-0.2.5/tests/test_parallel_calls.py +66 -0
  89. flywheel_relay-0.2.5/tests/test_release_metadata.py +241 -0
  90. flywheel_relay-0.2.5/tests/test_remote_mcp.py +231 -0
  91. flywheel_relay-0.2.5/tests/test_remote_oauth_http.py +182 -0
  92. flywheel_relay-0.2.5/tests/test_remote_state.py +187 -0
  93. flywheel_relay-0.2.5/tests/test_repo_art.py +174 -0
  94. flywheel_relay-0.2.5/tests/test_review.py +80 -0
  95. flywheel_relay-0.2.5/tests/test_run_view.py +114 -0
  96. flywheel_relay-0.2.5/tests/test_session_store.py +86 -0
  97. flywheel_relay-0.2.5/tests/test_verified_bon.py +68 -0
  98. flywheel_relay-0.2.5/tests/test_watch.py +125 -0
  99. flywheel_relay-0.2.5/tools/check_release_metadata.py +185 -0
@@ -0,0 +1,120 @@
1
+ # Changelog
2
+
3
+ ## 0.2.5, 2026-09-22
4
+
5
+ Relay now publishes to PyPI as `flywheel-relay`. The install command changes, so
6
+ this is a release rather than a metadata edit.
7
+
8
+ Source version metadata is not release availability proof; release availability
9
+ is established only by the accepted Git tag, uploaded GitHub Release assets, and
10
+ matching hash readback. Do not publish or recommend the bare PyPI name
11
+ `relay-agent`; that public namespace belongs to an unrelated project and is not
12
+ the HarperZ9 Relay distribution.
13
+
14
+ ### Changed
15
+
16
+ - The distribution name is `flywheel-relay`. `pip install flywheel-relay` is the
17
+ documented path, and releases carry PEP 740 attestations recording which
18
+ workflow built the bytes. The import name, the module layout and the `relay`
19
+ console script are unchanged.
20
+ - The hash-verified GitHub route is kept and still supported, for anyone who
21
+ would rather check the bytes than trust an index. See
22
+ `docs/GITHUB-ONLY-INSTALL.md`, updated for the new asset names.
23
+ - `tools/check_release_metadata.py` compares whitespace-normalized text, so a
24
+ phrase split across a wrapped line no longer fails a guard that no change in
25
+ wording had broken.
26
+
27
+ ### Note
28
+
29
+ 0.2.4 was never released. A stray `v0.2.4` tag points at a commit that is not on
30
+ main and whose own `pyproject.toml` reads 0.1.0, so this release skips that
31
+ number rather than reusing it.
32
+
33
+ ## 0.2.3, 2026-09-17
34
+
35
+ Status: GitHub-only patch release. Source version metadata is not release
36
+ availability proof; release availability is established only by the accepted Git
37
+ tag, uploaded GitHub Release assets, and matching hash readback. Do not publish
38
+ or recommend the bare PyPI name `relay-agent`; that public namespace is not the
39
+ HarperZ9 Relay distribution.
40
+
41
+ ### Added
42
+
43
+ - Architect mode can run an attributed planning pass before a plain single-run
44
+ agent execution, then pass that proposal to the implementer as context.
45
+ - `--architect` refuses watch, MCP, probe, view, verify, bisect, health,
46
+ best-of, and other modes until those paths have explicit planner semantics.
47
+
48
+ ### Distribution boundary
49
+
50
+ - Source version metadata, changelog text, and built local artifacts are not
51
+ release availability proof. The release is established by the accepted Git tag,
52
+ uploaded GitHub Release assets, and matching hash readback.
53
+ - Install from a pinned HarperZ9 GitHub commit or from a hash-verified GitHub
54
+ Release wheel. Missing checksum entries or hash mismatches stop before
55
+ `pip install`.
56
+
57
+ ## 0.2.2, 2026-09-13
58
+
59
+ Status: GitHub-only patch release. Do not publish or recommend the bare PyPI
60
+ name `relay-agent`; that public namespace is not the HarperZ9 Relay
61
+ distribution.
62
+
63
+ ### Fixed
64
+
65
+ - Background MCP/agent runs with `RELAY_RUN_ROOT` now durably checkpoint
66
+ witnessed partial ledgers while they are still running, so a server restart can
67
+ reload observed progress as an `interrupted` partial run instead of losing it.
68
+ - Final result persistence and running checkpoint writes are serialized so a
69
+ final `done` record is not replaced by an older running checkpoint.
70
+
71
+ ### Limits
72
+
73
+ - A partial checkpoint is observed progress only. It is not a completed result,
74
+ rollback guarantee, or acceptance verdict; `local_agent_result` returns `done`
75
+ only after the final result record persists.
76
+
77
+ ## 0.2.1, 2026-09-10
78
+
79
+ Status: GitHub-only patch release. Do not publish or recommend the bare PyPI name
80
+ `relay-agent`; that public namespace is not the HarperZ9 Relay distribution.
81
+
82
+ ### Fixed
83
+
84
+ - MCP stdio now returns JSON-RPC parse/invalid-request/invalid-params errors
85
+ for malformed input, invalid request ids, and non-object `tools/call` params
86
+ without echoing rejected input or stopping the server.
87
+
88
+ ## 0.2.0, 2026-09-08
89
+
90
+ Status: GitHub-only release with hash-verified GitHub Release artifacts
91
+ uploaded by the release owner.
92
+
93
+ ### Added
94
+
95
+ - `local_agent_run` and `local_agent_start` accept the existing CLI route and
96
+ acceptance dials: `backend`, `model`, `max_tokens`, `check`, `test_cmd`, and
97
+ `compact_budget`.
98
+ - Blocking and background MCP runs return a `relay.mcp-run-request/v1` binding
99
+ with admitted backend/model/root/effective gate choices, request gate intent,
100
+ and hashes of goal/check/test command text.
101
+ - Background run status/result/list preserve the request binding across process
102
+ restarts when `RELAY_RUN_ROOT` is configured.
103
+ - MCP results report the last witnessed assistant route with receipt id,
104
+ `model_ref`, and ledger sequence when available.
105
+
106
+ ### Fixed
107
+
108
+ - `DONE` background results are not exposed before the final done/result/finished
109
+ record is durable.
110
+ - MCP scalar validation for run/start authority fields rejects string integers,
111
+ floats, booleans in integer fields, non-string route fields, and negative
112
+ limits.
113
+ - `local_agent_runs.limit` now rejects coercive or negative values.
114
+
115
+ ### Distribution boundary
116
+
117
+ - This candidate is GitHub-only. Do not publish or recommend the bare PyPI name
118
+ `relay-agent`; that public namespace is not the HarperZ9 Relay distribution.
119
+ - Install from a pinned HarperZ9 GitHub commit or from a hash-verified GitHub
120
+ Release wheel after the release owner publishes one.
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Zain Dana Harper
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include CHANGELOG.md
2
+ recursive-include docs *.md
3
+ recursive-include docs/art *.json *.svg
4
+ include tools/check_release_metadata.py
@@ -0,0 +1,338 @@
1
+ Metadata-Version: 2.4
2
+ Name: flywheel-relay
3
+ Version: 0.2.5
4
+ Summary: A zero-dependency, accountable coding agent that runs on any model endpoint: local (served 14B/32B or Ollama) or online (codex/claude/gemini/deepseek via your own keys, subscription CLI, or gateway), with automatic failover, a gated tool loop, and a hash-chained re-verifiable session ledger.
5
+ Author: Zain Dana Harper
6
+ License-Expression: LicenseRef-FSL-1.1-MIT
7
+ Project-URL: Homepage, https://github.com/HarperZ9/relay
8
+ Keywords: coding-agent,local-llm,ollama,llm-router,failover,mcp,offline,agent
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Software Development :: Code Generators
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Requires-Python: >=3.11
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest>=8; extra == "test"
18
+ Dynamic: license-file
19
+
20
+ <p align="center"><img src="docs/art/relay-header.svg" alt="relay: accountable coding agent. Every run leaves a certificate a stranger can check offline." width="100%"></p>
21
+
22
+ # relay
23
+
24
+ **A zero-dependency, accountable coding agent that runs on any model endpoint.**
25
+ Local models when you're offline, your subscription or API when you need more,
26
+ automatic failover across all of them, and every run is a re-verifiable,
27
+ git-anchored trajectory. Stdlib only.
28
+
29
+ ```
30
+ python -m pip install flywheel-relay
31
+
32
+ relay --health --online # which model tiers are live?
33
+ relay "explain this function" --file app.py
34
+ relay --agent "fix the off-by-one in paginate()" --root . --allow-write --auto-commit
35
+ relay --mcp # serve the agent to any MCP client
36
+ ```
37
+
38
+ Relay publishes to PyPI as `flywheel-relay`, with PEP 740 attestations recording
39
+ which workflow built the bytes. The bare name `relay-agent` belongs to an
40
+ unrelated project and is not this distribution.
41
+
42
+ If you would rather verify the bytes yourself than trust the index, the
43
+ hash-verified path still works and is still supported: a pinned HarperZ9 GitHub
44
+ commit or a GitHub Release wheel, where a missing checksum entry or a hash
45
+ mismatch stops before `pip install`. See
46
+ [`docs/GITHUB-ONLY-INSTALL.md`](docs/GITHUB-ONLY-INSTALL.md).
47
+
48
+ ## Reaches every endpoint (with your own credentials)
49
+
50
+ <p align="center"><img src="docs/art/endpoint-ladder.svg" alt="The endpoint ladder from prompt to cloud, with rungs tried in order and free tiers first." width="100%"></p>
51
+
52
+ One ladder, tried in order, failing over on exhaustion or error, free/private
53
+ tiers first so you only spend metered tokens when you have to:
54
+
55
+ | Tier | Reached by |
56
+ |---|---|
57
+ | **local** | a served 14B/32B (`serve.py`) → Ollama (largest pulled model) |
58
+ | **plan / max** | the official CLI (`claude`, `codex`) using your subscription auth |
59
+ | **api** | `codex` / `claude` / `glm` / `gemini` / `deepseek` public APIs + `<PROVIDER>_API_KEY` |
60
+ | **provider** | a gateway (OpenRouter, ...) via `<PROVIDER>_PROVIDER_BASE_URL` |
61
+ | **cloud** | a cloud OpenAI-compatible endpoint via `<PROVIDER>_CLOUD_BASE_URL` + `_CLOUD_KEY` |
62
+
63
+ Legitimate by construction: keys come from the environment, subscriptions from
64
+ your own authenticated CLI, gateways from a base URL you set. Nothing is forged,
65
+ no cover identity is minted, no session token is harvested, no billing is evaded.
66
+ A missing credential just drops that tier from the ladder.
67
+
68
+ One rule inside that is worth stating, because it is the difference between a
69
+ gateway and a leak. A gateway rung points at an arbitrary base URL that you set,
70
+ so it may use only its own dedicated `<PROVIDER>_PROVIDER_KEY`. It never falls
71
+ back to that provider's official API key, because replaying your real credential
72
+ to a third-party URL is exactly the failure the rung exists to avoid. With no
73
+ provider key set, the gateway is called unauthenticated and the official secret
74
+ stays where it is. A rung whose credential is absent is never added to the ladder
75
+ in the first place, so a missing key is a shorter ladder rather than an error at
76
+ call time.
77
+
78
+ ## An actual coding agent, not a chat box
79
+
80
+ `--agent` runs a permission-checked tool loop the model drives:
81
+
82
+ - **`repo_map`**: a compact code outline (Python via `ast`; JS/TS/Go/Rust/Java/
83
+ C#/Swift/PHP/Ruby via patterns) so the model finds the right file.
84
+ - **`edit_file`**: precise search/replace where the target must match exactly
85
+ once, so an ambiguous edit is refused, not guessed.
86
+ - **`edit_lines`**: hash-anchored edits. A `read_file` with `"hashed": true`
87
+ returns every line as `<8hex>|<line>`, and the model edits by that anchor
88
+ instead of by repeating the line. It is compact, and an anchor computed against
89
+ a stale view will not match, so a mismatched edit fails closed rather than
90
+ landing on the wrong line.
91
+ - **`edit_plan`**: a coordinated multi-file change applied as one all-or-nothing
92
+ checkpoint. Every hash-anchored op is resolved first; if any anchor is stale,
93
+ ambiguous, or overlaps another op, nothing is written. Each op carries a receipt
94
+ (its resolved line, that line's pre-image, and the anchor) so a stranger can
95
+ recompute the anchor and confirm the edit landed exactly where the plan said.
96
+ - **`apply_diff`**: applies a unified diff to one file, fail-closed. A hunk whose
97
+ context does not match the current file exactly is refused with nothing written,
98
+ so a model that emits diffs gets the same no-silent-misapply guarantee. Unlike a
99
+ fuzzy applier, drift is a refusal, not a wrong-place edit.
100
+ - **`read_file` / `list_dir`**: confined to `--root`. `read_file` takes an
101
+ optional `"hashed": true` for the anchored view above.
102
+ - **`write_file`**: off by default; enabled with `--allow-write`; confined to `--root`.
103
+ - **`run`**: off by default; enabled with `--allow-exec`. A shell can write, so
104
+ `--allow-exec` implies write, and unlike the file tools `run` is not confined to
105
+ `--root` (it sets only the working directory). A denylist refuses a few literal
106
+ destructive spellings: a guardrail against a small model wrecking the tree, not
107
+ a security boundary.
108
+
109
+ Two opt-in loop features, both witnessed:
110
+
111
+ - **`--interactive`**: prompt for approval before every mutating call. Each decision
112
+ is a hash-chained ledger entry bound to the call's exact bytes, so the `.rvc` can
113
+ prove a human gated the step and the approved bytes match the executed bytes. Off
114
+ by default, and a headless run is byte-identical to one without it.
115
+ - **`--compact-budget N`**: once the prompt passes `N` tokens, fold older turns into
116
+ one summary so the loop keeps running in any context window, pinning the task
117
+ anchor and the policy text. Every fold records the folded-span and summary hashes
118
+ on the ledger, and the untruncated trajectory stays there, so shrinking the prompt
119
+ never loses the record.
120
+
121
+ ## Watch mode: a marker comment, in any editor
122
+
123
+ No editor plugin, so it works the same in vim, Notepad, or a hex editor: drop a
124
+ comment with the marker anywhere in the tree and relay picks it up.
125
+
126
+ ```bash
127
+ relay --watch --root . --allow-write # polls for "RELAY:" comments; Ctrl-C to stop
128
+ ```
129
+
130
+ ```python
131
+ def add(a, b):
132
+ return a - b # RELAY: this should add, not subtract
133
+ ```
134
+
135
+ Each marker becomes its own agent goal with its own witnessed ledger, through the
136
+ exact same gated tool loop as any other run. The model is told to remove the
137
+ marker itself via `edit_file` once it has acted, so even a change you triggered by
138
+ typing a comment, not a prompt, is never a bypass of the ledger. `--watch-marker`
139
+ changes the trigger string; `--watch-interval` the poll period.
140
+
141
+ ## Project conventions, once
142
+
143
+ Drop an `AGENTS.md` or `CONVENTIONS.md` at your project root and every `--agent`
144
+ / `--watch` run folds it into the system prompt automatically (verbatim, never
145
+ summarized, length-bounded so an oversized file degrades instead of blowing a
146
+ small model's context). `--no-conventions` opts out.
147
+
148
+ ## Ambient repo context
149
+
150
+ `--agent`/`--watch` fold a bounded repo map into the system prompt automatically
151
+ (`--root`, stopped at 20 files and capped at 4096 UTF-8 bytes so it never grows
152
+ unbounded on a large tree): the model starts with the codebase's shape instead
153
+ of spending its first turn calling `repo_map` to ask for it. It can still call
154
+ `repo_map` itself for more detail or a subdirectory; this is a head start, not a
155
+ replacement. `--no-repo-map` opts out.
156
+
157
+ This closes a real, verified gap in *what context the model has* (Copilot's
158
+ agent mode does this too). It is not a claim about the small local model's
159
+ tool-use reliability, which is a separate, already-known limitation (see
160
+ Architect mode below). Live runs during development showed high run-to-run
161
+ variance in whether the model actually calls `edit_file` at all, on identical
162
+ input, with and without the ambient map. That variance predates this change and
163
+ is not attributed to it here.
164
+
165
+ ## Architect mode: plan with one model, implement with another
166
+
167
+ ```bash
168
+ relay --agent "add rate limiting to fetch()" --root . --allow-write \
169
+ --architect claude-plan --online --check "pytest -q"
170
+ ```
171
+
172
+ A planning turn runs first on the backend you name. It can be any tier Relay
173
+ already reaches: local, subscription, API, gateway, or cloud. Relay folds that
174
+ plan into the implementer's goal as an attributed proposal. The implementing
175
+ agent still gets the current project context, reads the real code, and may
176
+ adapt or ignore the plan if the code points to a better path. Bare
177
+ `--architect` uses the first healthy backend. Architect mode is currently
178
+ limited to plain single-run `--agent`; Relay refuses `--architect` with
179
+ non-agent modes, watch/MCP/probe/view/verify/bisect/health commands, and
180
+ `--best-of` until those paths have explicit planner semantics.
181
+
182
+ ## The wedge: a provable run
183
+
184
+ <p align="center"><img src="docs/art/accountability-lane.svg" alt="Eight stages from goal to certificate, ending in allow, refuted, or unverifiable." width="100%"></p>
185
+
186
+ Every turn, tool call, and result is appended to a **hash-chained session
187
+ ledger**. A saved run is tamper-evident: reload it and `verify()` re-derives the
188
+ chain (a broken chain is refused, not loaded). With `--auto-commit`, relay stages
189
+ only the files the ledger recorded as edits and carries the checkpoint in the
190
+ message, so the commit binds the witnessed edit set; unrelated or shell-written
191
+ working-tree changes are left out, never attributed to the run. Each model turn
192
+ also carries a content-addressed receipt whose id a stranger can re-derive from
193
+ the saved record. No other coding agent gives you a run you can *prove*, not just
194
+ read.
195
+
196
+ ## Prove it works, not just that it ran
197
+
198
+ A witnessed trajectory proves *what* the agent did. It does not prove the edits are
199
+ *correct*: a model can finish confidently and leave a broken tree. Pass `--check`
200
+ and relay closes that gap: after the agent finishes, it runs your acceptance command
201
+ once, witnesses the result on the ledger, and **accepts** the run only if it passes.
202
+
203
+ ```bash
204
+ relay --agent "fix the failing test in paginate()" --root . --allow-write \
205
+ --check "pytest -q" --auto-commit
206
+ ```
207
+
208
+ The check carries *your* authority, not the model's: it runs outside the tool
209
+ permission boundary and is never a call the model can emit or steer. A failed check means the run is not
210
+ accepted, `--auto-commit` is skipped (a broken tree is never committed on your
211
+ behalf), and the exit code is non-zero, so `--agent --check` works as a CI check over the
212
+ agent's own work. `accepted` = a provable trajectory whose acceptance check held.
213
+
214
+ And the pass has to be *earned*. A rule-based reward-hacking guard reads the
215
+ witnessed edit set: if the agent made the check green by editing the test that grades
216
+ it, or by injecting a `pytest.skip` / `sys.exit`, the pass is flagged UNTRUSTED and
217
+ the run is not accepted. A gamed green is never committed. The flags ship with the
218
+ run under their own hash, re-checkable; the guard is non-learned and only ever turns
219
+ an accept into a refusal, never the reverse.
220
+
221
+ ## Prove the boundary holds (prompt-injection robustness)
222
+
223
+ Third-party data an agent reads (a file, a webpage, a tool result) can carry an
224
+ instruction that tries to make it exfiltrate, overwrite, or escape. relay's defense
225
+ is the boundary: tool output is data, never a command, and writes and exec are off by
226
+ default. `relay --probe-injection` measures that defense. It runs a fixed,
227
+ inspectable corpus of injection scenarios through the permission-checked executor, assuming the
228
+ worst case that the model was fully fooled and emitted exactly the smuggled call,
229
+ and reports **containment** with a re-derivable receipt. It exits non-zero if any
230
+ scenario is not contained, so it works as a CI check.
231
+
232
+ ```bash
233
+ relay --probe-injection # safe default: every injection contained
234
+ relay --probe-injection --allow-exec # honest: an open shell is a superset capability
235
+ ```
236
+
237
+ It generates no attacks (the corpus is readable data) and it can fail, so it is a
238
+ real measurement, not a reassurance. Harden the defender, measure it, feed the
239
+ failures back.
240
+
241
+ ## A run a reviewer can read
242
+
243
+ Every `--agent` run also ships a **reviewability projection** derived purely from the
244
+ witnessed ledger, in the terms a senior reviewer checks first: which files were
245
+ `edited_unread` (changed without ever being read), which edits no passing check
246
+ covered (`unverified_edits`), the failed-call scars, and a `reviewability` score over
247
+ read-before-write, verified, and clean-call ratios. Alongside it, a `risk` table tiers
248
+ each edit by mechanical signals (lines, nesting depth, branching, duplicate lines);
249
+ a high-tier edit **demands** a stronger receipt. These are facts, never generated
250
+ prose, so a surface can enforce them. Expert reviewers get the middle of the run, not
251
+ just its ending.
252
+
253
+ ## The proof toolkit: five ways to check a run
254
+
255
+ The witnessed ledger is the substrate for five checks a stranger can run offline.
256
+ The full capability matrix and the honest nulls are in
257
+ [docs/ACCOUNTABILITY.md](docs/ACCOUNTABILITY.md); the benchmark posture is in
258
+ [docs/BENCHMARKS.md](docs/BENCHMARKS.md).
259
+
260
+ - **See it.** `relay --view run.jsonl` draws the run as a hash-chained timeline.
261
+ Flip one byte in the saved run and exactly one edge snaps red, verdict REFUTED.
262
+ - **Certify it.** `--cert run.rvc` writes a few-KB proof-carrying certificate;
263
+ `python verify_cert.py run.rvc` re-derives ALLOW / UNVERIFIABLE / REFUTED offline,
264
+ no model and no re-execution, with zero dependencies.
265
+ - **Select by proof.** `--best-of 8 --check "pytest -q"` runs the goal eight times
266
+ and keeps the verified winner. A run that passed by editing the grader ranks below
267
+ an honest run that scored higher.
268
+ - **Localize a regression.** `--bisect run.jsonl --root <clean> --check "pytest -q"`
269
+ replays the witnessed edit set and names the first edit that broke the tests.
270
+ - **Ground the summary.** relay checks the final answer against the ledger: a summary
271
+ that claims the tests pass over a failed check is REFUTED, even with an intact chain.
272
+
273
+ Those three verdicts are ordered, and the order is the point. `verify_cert.py`
274
+ returns REFUTED first, UNVERIFIABLE next, and reaches ALLOW only when nothing
275
+ earlier fired. A confirmed contradiction therefore outranks an inability to check,
276
+ and both outrank acceptance, so a clause the verifier cannot re-derive can never
277
+ be rounded up to a pass. The exit code follows: zero on ALLOW, non-zero on either
278
+ of the other two, which is what makes it usable as a check in someone else's CI.
279
+
280
+ <p align="center"><img src="docs/art/clause-ladder.svg" alt="The eight clause types a relay acceptance contract may carry, one to a row, with whether the standalone verifier that ships inside every certificate can re-derive each one. chain_intact re-derives before any clause is read, so a flipped byte is refuted whether or not the contract asked. check_not_gamed, no_claimed_history, no_edit and steps_approved re-derive from the file alone: the ledger carries the edits, the reasoning and the approvals they read. tests_pass, reviewability and claim_grounded need the in-tree verifier, because they read per-turn receipts, a diff-level reviewability pass and a syntax-level scan that the vendored file does not carry. Those three report unverifiable rather than assuming they hold." width="100%"></p>
281
+
282
+ Which clause sits in which row is not a matter of taste. Five of the eight
283
+ re-derive from the certificate alone, because the ledger inside it carries the
284
+ edits, the reasoning and the approvals those clauses read. The other three want
285
+ the per-turn receipts, the diff-level reviewability pass, or the syntax-level
286
+ scan for a reward hack, and none of those travel in the file. The vendored
287
+ verifier names them unverifiable and stops there.
288
+
289
+ ## Use from an agent (MCP)
290
+
291
+ `relay --mcp` is a zero-dep stdio MCP server exposing `local_agent_health`,
292
+ `local_agent_chat`, `local_agent_run`, and the background `local_agent_start` /
293
+ `local_agent_status` / `local_agent_result` loop. Point Claude Code (or any MCP
294
+ client) at it to use relay as a fallback tier, e.g. keep working on local models
295
+ when a hosted quota runs out.
296
+
297
+ The MCP run tools accept the same bounded routing and acceptance dials as the
298
+ local CLI agent path: `backend`, `model`, `max_tokens`, `check`, `test_cmd`, and
299
+ `compact_budget`, in addition to `goal`, `root`, `allow_write`, `allow_exec`,
300
+ `max_steps`, and `online`. Results carry a request binding with the admitted
301
+ effective backend/model/gate choices, including that exec implies write, and
302
+ hashes of the goal/check commands. Results also include the last witnessed
303
+ assistant backend/model receipt when a run reaches the agent loop.
304
+
305
+ For background runs, set `RELAY_RUN_ROOT` to make progress durable across a
306
+ server restart. `local_agent_start` snapshots the run record when it starts and
307
+ the agent loop asks for a durable checkpoint after witnessed progress, so a
308
+ fresh server can reload the partial ledger and report `interrupted` instead of
309
+ silently losing the entries. A partial checkpoint is only bytes-on-disk evidence
310
+ for observed progress; it is not a completed result, a rollback guarantee, or an
311
+ acceptance verdict. `local_agent_result` reports `done` only after the final
312
+ result record is persisted.
313
+
314
+ ## Library
315
+
316
+ ```python
317
+ from relay import LocalAgent, available_backends, build_endpoints, run_agent
318
+
319
+ agent = LocalAgent(backends=available_backends() + build_endpoints()) # local + online
320
+ print(agent.send("hi")["content"][0]["text"])
321
+ ```
322
+
323
+ ## License
324
+
325
+ Relay is fair-source: open to read, run, and build on, with commercial use reserved so the project can fund its own development. See [LICENSE](LICENSE).
326
+
327
+ ## What this believes
328
+
329
+ This tool is one part of a family that holds a single belief steady across
330
+ every surface: knowledge open to anyone who can attain the means; acceptance
331
+ decided by external checks, never reputation; every result re-runnable;
332
+ honest nulls first-class; ownership earned by comprehension; learning woven
333
+ into the work. The full text lives in [CREDO.md](CREDO.md).
334
+ The long form of this belief: [The Unbundling](https://github.com/HarperZ9/flywheel/blob/fix/release-model-identity/docs/essays/2026-07-13-the-unbundling.md).
335
+
336
+ ---
337
+
338
+ **[Zentropy Labs](https://github.com/ZentropyLabs-ai)** · order out of entropy. An independent lab building evidence-first tools that leave a re-checkable artifact behind. Built by Zain Dana Harper in Seattle. The full workbench is at [Project Telos](https://harperz9.github.io).