autoforge-agent 0.4.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 (113) hide show
  1. autoforge_agent-0.4.0/PKG-INFO +477 -0
  2. autoforge_agent-0.4.0/README.md +463 -0
  3. autoforge_agent-0.4.0/autoforge/__init__.py +87 -0
  4. autoforge_agent-0.4.0/autoforge/__main__.py +16 -0
  5. autoforge_agent-0.4.0/autoforge/agent.py +2971 -0
  6. autoforge_agent-0.4.0/autoforge/autonomy/__init__.py +14 -0
  7. autoforge_agent-0.4.0/autoforge/autonomy/confirm.py +130 -0
  8. autoforge_agent-0.4.0/autoforge/autonomy/policy.py +245 -0
  9. autoforge_agent-0.4.0/autoforge/autonomy/roles.py +191 -0
  10. autoforge_agent-0.4.0/autoforge/autonomy/selfmod.py +146 -0
  11. autoforge_agent-0.4.0/autoforge/autonomy/spawn.py +227 -0
  12. autoforge_agent-0.4.0/autoforge/autonomy/topology.py +235 -0
  13. autoforge_agent-0.4.0/autoforge/browser.py +924 -0
  14. autoforge_agent-0.4.0/autoforge/cli.py +1119 -0
  15. autoforge_agent-0.4.0/autoforge/configfile.py +86 -0
  16. autoforge_agent-0.4.0/autoforge/core/__init__.py +29 -0
  17. autoforge_agent-0.4.0/autoforge/core/agent.py +271 -0
  18. autoforge_agent-0.4.0/autoforge/core/compaction.py +765 -0
  19. autoforge_agent-0.4.0/autoforge/core/lineedit.py +678 -0
  20. autoforge_agent-0.4.0/autoforge/core/llm.py +324 -0
  21. autoforge_agent-0.4.0/autoforge/core/message.py +89 -0
  22. autoforge_agent-0.4.0/autoforge/core/steering.py +280 -0
  23. autoforge_agent-0.4.0/autoforge/cpu/__init__.py +99 -0
  24. autoforge_agent-0.4.0/autoforge/cpu/kernel.py +442 -0
  25. autoforge_agent-0.4.0/autoforge/cpu/ops.py +652 -0
  26. autoforge_agent-0.4.0/autoforge/cpu/probe.py +569 -0
  27. autoforge_agent-0.4.0/autoforge/cpu/safety.py +463 -0
  28. autoforge_agent-0.4.0/autoforge/cpu/tune.py +564 -0
  29. autoforge_agent-0.4.0/autoforge/ecosystem.py +485 -0
  30. autoforge_agent-0.4.0/autoforge/forge/__init__.py +21 -0
  31. autoforge_agent-0.4.0/autoforge/forge/adversary.py +206 -0
  32. autoforge_agent-0.4.0/autoforge/forge/evolution.py +476 -0
  33. autoforge_agent-0.4.0/autoforge/forge/fuzzer.py +328 -0
  34. autoforge_agent-0.4.0/autoforge/forge/generator.py +591 -0
  35. autoforge_agent-0.4.0/autoforge/forge/invariance.py +369 -0
  36. autoforge_agent-0.4.0/autoforge/forge/metacog.py +177 -0
  37. autoforge_agent-0.4.0/autoforge/forge/pipeline.py +304 -0
  38. autoforge_agent-0.4.0/autoforge/forge/sandbox.py +301 -0
  39. autoforge_agent-0.4.0/autoforge/forge/validity.py +608 -0
  40. autoforge_agent-0.4.0/autoforge/forge/verifier.py +365 -0
  41. autoforge_agent-0.4.0/autoforge/gpu/__init__.py +116 -0
  42. autoforge_agent-0.4.0/autoforge/gpu/backend_triton.py +421 -0
  43. autoforge_agent-0.4.0/autoforge/gpu/bench.py +30 -0
  44. autoforge_agent-0.4.0/autoforge/gpu/kernel.py +430 -0
  45. autoforge_agent-0.4.0/autoforge/gpu/ops.py +349 -0
  46. autoforge_agent-0.4.0/autoforge/gpu/probe.py +267 -0
  47. autoforge_agent-0.4.0/autoforge/gpu/safety.py +248 -0
  48. autoforge_agent-0.4.0/autoforge/gpu/units.py +39 -0
  49. autoforge_agent-0.4.0/autoforge/mcp.py +1050 -0
  50. autoforge_agent-0.4.0/autoforge/measure.py +368 -0
  51. autoforge_agent-0.4.0/autoforge/modes.py +299 -0
  52. autoforge_agent-0.4.0/autoforge/notify.py +339 -0
  53. autoforge_agent-0.4.0/autoforge/route/__init__.py +3 -0
  54. autoforge_agent-0.4.0/autoforge/route/router.py +217 -0
  55. autoforge_agent-0.4.0/autoforge/schedule.py +427 -0
  56. autoforge_agent-0.4.0/autoforge/setup_wizard.py +232 -0
  57. autoforge_agent-0.4.0/autoforge/skills.py +496 -0
  58. autoforge_agent-0.4.0/autoforge/store.py +611 -0
  59. autoforge_agent-0.4.0/autoforge/timing.py +375 -0
  60. autoforge_agent-0.4.0/autoforge/tools/__init__.py +4 -0
  61. autoforge_agent-0.4.0/autoforge/tools/composition.py +143 -0
  62. autoforge_agent-0.4.0/autoforge/tools/registry.py +392 -0
  63. autoforge_agent-0.4.0/autoforge/tools/spec.py +230 -0
  64. autoforge_agent-0.4.0/autoforge/vision.py +279 -0
  65. autoforge_agent-0.4.0/autoforge/web/__init__.py +5 -0
  66. autoforge_agent-0.4.0/autoforge/web/server.py +689 -0
  67. autoforge_agent-0.4.0/autoforge/webtools.py +1382 -0
  68. autoforge_agent-0.4.0/autoforge_agent.egg-info/PKG-INFO +477 -0
  69. autoforge_agent-0.4.0/autoforge_agent.egg-info/SOURCES.txt +111 -0
  70. autoforge_agent-0.4.0/autoforge_agent.egg-info/dependency_links.txt +1 -0
  71. autoforge_agent-0.4.0/autoforge_agent.egg-info/entry_points.txt +3 -0
  72. autoforge_agent-0.4.0/autoforge_agent.egg-info/requires.txt +5 -0
  73. autoforge_agent-0.4.0/autoforge_agent.egg-info/top_level.txt +1 -0
  74. autoforge_agent-0.4.0/pyproject.toml +24 -0
  75. autoforge_agent-0.4.0/setup.cfg +4 -0
  76. autoforge_agent-0.4.0/tests/test_autoforge.py +626 -0
  77. autoforge_agent-0.4.0/tests/test_browser.py +872 -0
  78. autoforge_agent-0.4.0/tests/test_cli.py +412 -0
  79. autoforge_agent-0.4.0/tests/test_compaction.py +545 -0
  80. autoforge_agent-0.4.0/tests/test_compaction_wiring.py +149 -0
  81. autoforge_agent-0.4.0/tests/test_cpu.py +433 -0
  82. autoforge_agent-0.4.0/tests/test_ecosystem.py +618 -0
  83. autoforge_agent-0.4.0/tests/test_execution_samples.py +175 -0
  84. autoforge_agent-0.4.0/tests/test_forge_failure_feedback.py +91 -0
  85. autoforge_agent-0.4.0/tests/test_generator_coercion.py +166 -0
  86. autoforge_agent-0.4.0/tests/test_host_facts.py +117 -0
  87. autoforge_agent-0.4.0/tests/test_invariance.py +269 -0
  88. autoforge_agent-0.4.0/tests/test_json_repair.py +278 -0
  89. autoforge_agent-0.4.0/tests/test_lineedit.py +317 -0
  90. autoforge_agent-0.4.0/tests/test_live_progress.py +244 -0
  91. autoforge_agent-0.4.0/tests/test_llm_client.py +111 -0
  92. autoforge_agent-0.4.0/tests/test_llm_response_shape.py +129 -0
  93. autoforge_agent-0.4.0/tests/test_llm_retry.py +204 -0
  94. autoforge_agent-0.4.0/tests/test_mcp.py +668 -0
  95. autoforge_agent-0.4.0/tests/test_mcp_http.py +611 -0
  96. autoforge_agent-0.4.0/tests/test_memory_injection.py +195 -0
  97. autoforge_agent-0.4.0/tests/test_modes.py +180 -0
  98. autoforge_agent-0.4.0/tests/test_notify.py +405 -0
  99. autoforge_agent-0.4.0/tests/test_policy_gates.py +445 -0
  100. autoforge_agent-0.4.0/tests/test_reach.py +172 -0
  101. autoforge_agent-0.4.0/tests/test_reasoning_budget.py +192 -0
  102. autoforge_agent-0.4.0/tests/test_roles.py +209 -0
  103. autoforge_agent-0.4.0/tests/test_schedule.py +528 -0
  104. autoforge_agent-0.4.0/tests/test_self_knowledge.py +189 -0
  105. autoforge_agent-0.4.0/tests/test_setup.py +461 -0
  106. autoforge_agent-0.4.0/tests/test_skills.py +742 -0
  107. autoforge_agent-0.4.0/tests/test_steering.py +476 -0
  108. autoforge_agent-0.4.0/tests/test_topology.py +135 -0
  109. autoforge_agent-0.4.0/tests/test_v02.py +570 -0
  110. autoforge_agent-0.4.0/tests/test_validity.py +404 -0
  111. autoforge_agent-0.4.0/tests/test_vision.py +561 -0
  112. autoforge_agent-0.4.0/tests/test_web.py +378 -0
  113. autoforge_agent-0.4.0/tests/test_web_live.py +182 -0
@@ -0,0 +1,477 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoforge-agent
3
+ Version: 0.4.0
4
+ Summary: Agent framework with verifiable self-made tools (the forge pipeline).
5
+ Author: Zhang Yangyi
6
+ License: MIT
7
+ Keywords: agents,llm,tools,self-improvement,verification
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.28
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7; extra == "dev"
13
+ Requires-Dist: pytest-xdist; extra == "dev"
14
+
15
+ # autoforge
16
+
17
+ An agent framework that **builds its own tools** — and makes them earn their keep.
18
+
19
+ Most agent frameworks stop at "the model can call tools." A few let the model
20
+ *write* tools. Almost none ask the question that actually decides whether
21
+ self-made tools help or hurt:
22
+
23
+ > **Does the tool fire when it should, and stay quiet when it shouldn't?
24
+ > And does it keep working after the tenth call?**
25
+
26
+ `autoforge` is built around that question. Tool creation is free; tool *trust*
27
+ is earned through a three-stage closed loop, and kept honest by a live ledger.
28
+
29
+ ```
30
+ FORGE ──────> VERIFY ──────> SEAL (creation)
31
+ generate 5-check gate register in lifecycle
32
+
33
+ OBSERVE ────> JUDGE ───────> ACT (maintenance)
34
+ ledger degraded? quarantine / rehab / retire
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quick start
40
+
41
+ One command, any machine, nothing to clone:
42
+
43
+ ```bash
44
+ pip install autoforge-agent
45
+ auto setup # one wizard: provider, key, model
46
+ auto # the REPL
47
+ ```
48
+
49
+ `autoforge-agent` is the distribution name; the import is `autoforge` and the
50
+ command is `auto` (or `autoforge`).
51
+
52
+ Working from a checkout instead:
53
+
54
+ ```bash
55
+ git clone https://github.com/<you>/autoforge && cd autoforge
56
+ ./auto # macOS / Linux / WSL / Git Bash
57
+ auto # Windows cmd (PowerShell: .\auto)
58
+ ```
59
+
60
+ The launcher checks your Python (3.10+), fetches `requests` once if it is
61
+ missing, and drops you into the REPL. Nothing else to set up. `auto.cmd` ships
62
+ alongside it so Windows works without touching your PATH.
63
+
64
+ Prefer `auto` on your PATH everywhere? Install the console script instead:
65
+
66
+ ```bash
67
+ pip install -e .
68
+ auto # now a real entry point, any directory
69
+ ```
70
+
71
+ ### Configure once, then forget about it
72
+
73
+ ```bash
74
+ auto setup
75
+ ```
76
+
77
+ One wizard: pick a provider, paste a key, confirm the model. It writes
78
+ `~/.autoforge/config.json`, tests the endpoint with a single short request, and
79
+ from then on `auto` just runs — in this terminal and in every new one.
80
+
81
+ ```
82
+ provider:
83
+ * 1) aiping.cn gateway (hosted, needs an API key)
84
+ 2) Ollama on this machine (local, no key)
85
+ 3) Something else (any OpenAI-compatible endpoint)
86
+ ```
87
+
88
+ Every prompt shows the current value and takes it on a bare Enter, so re-running
89
+ the wizard is how you change one field without retyping the rest. To see what is
90
+ actually in effect, and which layer supplied each value:
91
+
92
+ ```bash
93
+ auto config
94
+ ```
95
+
96
+ ```
97
+ base_url https://aiping.cn/api/v1 config C:\Users\you\.autoforge\config.json
98
+ model DeepSeek-V4.1-Flash config C:\Users\you\.autoforge\config.json
99
+ api_key QC-5...e262 (len 68) config C:\Users\you\.autoforge\config.json
100
+ max_tokens 3000 config C:\Users\you\.autoforge\config.json
101
+ proxy True config C:\Users\you\.autoforge\config.json
102
+ ```
103
+
104
+ Resolution order is **flag > environment > config file > default**, so a one-off
105
+ override never means re-running the wizard:
106
+
107
+ ```bash
108
+ auto --model other-model # this invocation only
109
+ AUTOFORGE_MODEL=other-model auto # this shell only
110
+ auto --base-url http://127.0.0.1:11434/v1 --model qwen2.5:7b --no-proxy
111
+ auto --policy supervised # keep the harness, drop the latitude
112
+ AUTOFORGE_POLICY=supervised auto # same, this shell only
113
+ ```
114
+
115
+ Why bother with a file when environment variables exist: a variable exported
116
+ *after* a terminal was opened is invisible to that terminal — Windows and POSIX
117
+ alike inherit the environment at process start. That turns configuration into
118
+ "it worked a minute ago, in the other window." A file has no such lag, which is
119
+ why `auto setup` is the supported path and the env vars are the escape hatch.
120
+
121
+ `auto setup` is also safe to run with no terminal: without a tty it takes
122
+ whatever the flags and environment already say, saves them, and exits instead of
123
+ blocking on a prompt.
124
+
125
+ `auto` on its own drops you into a REPL: type a recurring need in plain language
126
+ and the agent decides whether to forge, verify and keep a tool for it. Forged
127
+ tools persist for the rest of the session.
128
+
129
+ ```bash
130
+ auto --help # all flags
131
+ auto forge "<need>" --out t.json # forge one tool, one shot, then exit
132
+ auto list # inspect artifacts written by --out
133
+ auto list autoforge_tools/t.json # dump one artifact
134
+ ```
135
+
136
+ Point it at a local model (no key, no network) — either through `auto setup`
137
+ above, or per-invocation:
138
+
139
+ ```bash
140
+ # local, CPU-friendly: drops the LLM-driven checks
141
+ AUTOFORGE_BASE_URL=http://127.0.0.1:11434/v1 AUTOFORGE_MODEL=qwen2.5:7b \
142
+ AUTOFORGE_FAST=1 auto
143
+ ```
144
+
145
+ Local endpoints never use the socks proxy and never need a key, so those two
146
+ questions are skipped automatically.
147
+
148
+ Inside the REPL: `/help`, `/tools` (library + health), `/report` (policy and
149
+ self-amendments), `/trace` (the decision log), `/reset`, `/quit`.
150
+
151
+ ### The keyboard stays yours
152
+
153
+ A run is not a modal dialog. In `chat` the input line is live the whole time —
154
+ the run narrates itself *above* it, so you can keep typing while a tool is
155
+ running:
156
+
157
+ ```
158
+ [14:02:11] turn 1 +0.4s asking the model…
159
+ [14:02:19] +8.6s -> bash
160
+ … waiting on model (12s)
161
+ you> also handle the empty file case
162
+ ```
163
+
164
+ Type a sentence mid-run and it reaches the agent at its next step, labelled as
165
+ a correction rather than a new task. `/status` asks where the run is, `/stop`
166
+ ends it after the current step, and a leading space sends something that starts
167
+ with `/` as text. Paste a block and it collapses to a one-line
168
+ `[Pasted text #1: 40 lines → …]` placeholder — the text is kept on disk and
169
+ expanded again before it reaches the model.
170
+
171
+ The behaviour degrades honestly: a pipe, a redirect, or a test gets the plain
172
+ cooked-mode reader and no heartbeat, because there is no terminal to own.
173
+
174
+ Offline demo, zero API keys:
175
+
176
+ ```bash
177
+ python examples/demo_offline.py
178
+ python -m pytest tests/ -o addopts= -q
179
+ ```
180
+
181
+ Use the library directly:
182
+
183
+ ```python
184
+ import os
185
+ from autoforge import ForgeAgent, OpenAICompatClient
186
+
187
+ llm = OpenAICompatClient(
188
+ model="deepseek-v3",
189
+ base_url="https://aiping.cn/api/v1",
190
+ api_key=os.environ["AIPING_API_KEY"],
191
+ )
192
+ agent = ForgeAgent(llm)
193
+ print(agent.run("Reverse this string: hello").content)
194
+ ```
195
+
196
+ ---
197
+
198
+ ## The problem this solves
199
+
200
+ Four failure modes kill self-made-tool agents in practice. Each has a paper or
201
+ a production report behind it, and each maps to one mechanism here.
202
+
203
+ | Failure | What happens | autoforge's answer |
204
+ |:---|:---|:---|
205
+ | **Constraint Tax** | A tool is correct but the agent never calls it — the trigger is unreachable | `ToolVerifier.check_trigger` — positive probes run against a live agent |
206
+ | **Over-triggering** | A tool fires on everything, stealing calls from better tools | `check_negative` — negative probes assert it stays quiet |
207
+ | **Misevolution** | A tool works at birth, then silently rots | `ToolStats` ledger + auto-quarantine below threshold |
208
+ | **Semantic routing** | Retrieval picks the tool that *reads* right, not the one that *runs* right | `BehaviourRouter` — scores on observed success, not just text |
209
+
210
+ Sources: the `Constraint Tax` effect (schema constraints starving tool-call
211
+ tokens), ICLR'26 "Misevolution" (self-evolving agents introducing regressions
212
+ via tool creation/reuse), Memento-Skills (behaviour-aligned skill routing).
213
+
214
+ ---
215
+
216
+ ## Architecture
217
+
218
+ ```
219
+ autoforge/
220
+ ├── core/
221
+ │ ├── message.py # provider-neutral Message / ToolCall
222
+ │ ├── llm.py # LLMClient protocol; OpenAI-compat + Mock impls
223
+ │ └── agent.py # the tool-calling loop (deliberately thin)
224
+ ├── tools/
225
+ │ ├── spec.py # ToolSpec + ToolState lifecycle + ToolStats ledger
226
+ │ ├── registry.py # hot-swap registry, auto-quarantine, reporting
227
+ │ └── composition.py # DAG composition — tools built out of tools
228
+ ├── forge/
229
+ │ ├── sandbox.py # out-of-process execution + measured reach report
230
+ │ ├── generator.py # LLMToolGenerator + offline TemplateGenerator
231
+ │ ├── verifier.py # execution + robustness + adversarial + trigger + negative
232
+ │ ├── pipeline.py # forge→verify→seal, plus judge/rehab
233
+ │ ├── fuzzer.py # 30+ edge probes per tool (robustness)
234
+ │ ├── invariance.py # metamorphic oracles — probes mean nothing without them
235
+ │ ├── adversary.py # an LLM attacker that tries to break each tool
236
+ │ ├── evolution.py # population competition — mutants race, best survives
237
+ │ ├── validity.py # independent gate + frozen baseline (anti-misevolution)
238
+ │ └── metacog.py # proactive gap discovery + pre-forging
239
+ ├── route/
240
+ │ └── router.py # behaviour-aligned tool routing
241
+ ├── gpu/
242
+ │ ├── bench.py # benchmarking that cannot lie about its units
243
+ │ └── units.py # ms-vs-seconds audits, the do_bench lesson as code
244
+ ├── cpu/
245
+ │ ├── probe.py # what this machine actually is, measured not assumed
246
+ │ ├── ops.py # the problem set: a described problem, not an instance
247
+ │ ├── kernel.py # compile → cache → load, refusing targets it cannot run
248
+ │ ├── safety.py # preflight: the checks that run before anything executes
249
+ │ └── tune.py # legal search over flags/source, verified then raced
250
+ ├── autonomy/
251
+ │ ├── policy.py # AutonomyPolicy — freedom is the default, all True
252
+ │ ├── selfmod.py # every self-modification, with rationale + audit log
253
+ │ ├── spawn.py # derive child agents (shared or isolated registry)
254
+ │ └── topology.py # Topology + TopologyDesigner — agent designs its own team
255
+ ├── store.py # SQLite persistence (tools, versions, deps, baselines, events)
256
+ └── agent.py # ForgeAgent — everything wired together
257
+ ```
258
+
259
+ ### Tool lifecycle
260
+
261
+ ```
262
+ DRAFT ──verify──> PROBATION ──earn──> ACTIVE ──decay──> QUARANTINED
263
+ ^ │ │
264
+ └──────rehab─────────┴──────────────────┘
265
+ └──retire──> RETIRED
266
+ ```
267
+
268
+ Only `PROBATION` and `ACTIVE` are injected into the model's context. Creation
269
+ is unrestricted — you can forge anything — but **context budget and trust are
270
+ earned**, which is what keeps a growing tool library from drowning the prompt.
271
+
272
+ ### What makes it different
273
+
274
+ | Framework | Its gap | autoforge |
275
+ |:---|:---|:---|
276
+ | Hermes Agent | Skills are *documents*, not executable; tool changes need a session reset | Tools are code, hot-swapped, verified |
277
+ | Claude Code | Fixed toolset; permissions govern *calling*, not *creating* | Governs the tool lifecycle itself |
278
+ | OpenHands / CodeAct | Action-as-code, but no quality governance | Same expressiveness, plus a ledger |
279
+ | Voyager | Has a skill library + self-verification, but retrieves by text | Retrieves by behaviour |
280
+ | Tea Agent / ATLASS | Can forge tools; trigger problem unsolved | Trigger + negative verification |
281
+
282
+ ---
283
+
284
+ ## Design decisions
285
+
286
+ **Tools are data.** A `ToolSpec` is a claim with a contract: implementation,
287
+ schema, trigger probes, effect signature, provenance, verification record, and
288
+ a live reliability ledger. Nothing about a tool is a boolean.
289
+
290
+ **Sandbox bounds blast radius, not capability.** Process isolation + timeout +
291
+ env scrubbing — so an infinite loop or an `os._exit()` can't take down the
292
+ agent. Builtins are *not* crippled by default: an agent that can't use the
293
+ standard library can't forge useful tools. `restrict_builtins=True` narrows the
294
+ surface when you want it, and `Sandbox(runner=...)` is the hook for real OS-level
295
+ containment.
296
+
297
+ **Verification asks five orthogonal questions.** "Does it run?" "Does it survive
298
+ garbage input?" "Can an adversary talk it into misbehaving?" "Does it fire when
299
+ it should?" and "Does it stay quiet when it shouldn't?" Most frameworks only
300
+ test the first. All five are checked, and the negative probe guards the failure
301
+ mode that's worse than silence.
302
+
303
+ **Retries feed the failure back.** A failed verification becomes the next
304
+ generation prompt's context, so the model repairs rather than re-rolls.
305
+
306
+ **Quarantine is a trust signal, not a wall.** A degraded tool leaves the
307
+ context but `force=True` still runs it. `rehab()` puts it back on trial.
308
+
309
+ **Routing weights are inspectable policy**, not an opaque vector index:
310
+
311
+ ```
312
+ score = w_text·similarity + w_success·success_rate + w_trust·state_trust
313
+ − w_cost·cost_penalty − w_over·over_trigger_penalty
314
+ ```
315
+
316
+ **Every freedom is declared, and each declaration is classified.** Two presets
317
+ ship — `full` (the default: nothing denied) and `supervised` — selected with
318
+ `--policy` or `AUTOFORGE_POLICY`, and `autoforge config` prints which is live.
319
+ Each field in the policy is labelled *enforced* (a gate you can watch close),
320
+ *partial*, *confirm* (off means "not without a yes": the tool stops and asks
321
+ before it runs), or *declared-only* (a promise no code path keeps yet — an
322
+ empty class today, kept so a future unclassified field shows up loudly). The
323
+ report is not decoration: `my_capabilities` hands the same classification to the
324
+ agent, `set_autonomy` tells you whether switching a freedom off closes a door,
325
+ narrows one, or turns it into a question, and `describe()` prints
326
+ `[asks before running: ...]` — so "switched off" can never quietly mean
327
+ "still on".
328
+
329
+ The four execution freedoms (`may_read_filesystem`, `may_write_filesystem`,
330
+ `may_access_network`, `may_install_packages`) are the *confirm* ones. Switching
331
+ one off makes `ToolRegistry.call` ask, once per run, about any tool whose own
332
+ declared scope needs it — a tool that declares nothing is treated as capable of
333
+ everything and is therefore asked about too. Nobody to ask (a headless run, the
334
+ web harness's worker threads) means it does not run; an unanswered prompt is
335
+ never a yes. On a terminal the question is asked by `cli._TerminalConfirmer`,
336
+ which defaults to No and prints the tool, the switch and the arguments. Tools
337
+ declare their scope in `agent.BUILTIN_SCOPES`; a test fails if the table and the
338
+ tool specs ever disagree.
339
+
340
+ ---
341
+
342
+ ## Proof it works
343
+
344
+ `examples/demo_offline.py` runs three parts with no network:
345
+
346
+ **Part 1** — forges `word_stats` from a one-line need, verifies it
347
+ (execution + 2 triggers + negative), promotes it to ACTIVE, then calls the
348
+ forged code out-of-process and confirms it's injected into context.
349
+
350
+ **Part 2** — a tool's upstream breaks. The ledger catches the rot on the third
351
+ failure, auto-quarantines it, removes it from context, blocks further calls —
352
+ then `rehab()` puts it back on trial.
353
+
354
+ **Part 3** — two near-identically-described JSON parsers. The one with **higher
355
+ text similarity** has a 0% success rate; the router correctly picks the other
356
+ one. This is the whole argument for behaviour-aligned routing in one output.
357
+
358
+ `examples/demo_misevolution.py` reproduces three ways a self-evolving tool
359
+ population goes wrong, then asserts each is closed:
360
+
361
+ | Exploit | Old behaviour | Now |
362
+ |:---|:---|:---|
363
+ | Delete your own guardrail | 0.800 → **1.000** for identical behaviour | Neutralised — a missing check class is a failed check class |
364
+ | Shrink the exam | 6-probe tool tied a 50-probe tool | Evidence mass decides, and saturates so it can't be farmed |
365
+ | Silent scope creep | Undetected | Boolean veto, audited, before fitness is ever computed |
366
+
367
+ A fourth exploit lived one layer down, in the robustness check itself — and it
368
+ was the worst of the set, because it sat on the **forge** path, where every tool
369
+ ever created has to pass through it:
370
+
371
+ | Exploit | Old behaviour | Now |
372
+ |:---|:---|:---|
373
+ | Ignore `"ISBN "` prefix | 19/19 probes survived, **passed** | Fails: output changes under a transform that must not change it |
374
+ | Return a constant `"nope"` | 19/19 probes survived, **passed** | Fails: degenerate — validates nothing |
375
+ | Return constant `True` | 19/19 probes survived, **passed** | Fails: degenerate — accepts everything |
376
+
377
+ The probes were never the problem — the `ISBN ` probe was already being
378
+ generated. The problem was the oracle: a probe was scored `survived` iff the
379
+ tool did not raise (`ok = out is not None`), so *wrong-but-total* functions
380
+ scored 100%. A probe means nothing without a verdict behind it.
381
+
382
+ `forge/invariance.py` supplies verdicts of two kinds, neither authored by the
383
+ tool being scored:
384
+
385
+ - **computed** — true of any honest implementation, so the verifier derives
386
+ them: `defined`, `deterministic`, and `non_degenerate` (a tool must not emit
387
+ one constant across well-formed *and* garbage input).
388
+ - **declared** — semantic obligations needing domain knowledge the verifier
389
+ lacks (is `"ISBN "` part of the value or noise around it?). The generator
390
+ asserts these at birth; `FrozenBaseline` then keeps them, so a later mutant
391
+ cannot quietly drop one.
392
+
393
+ They are metamorphic, not exact: nothing labels the correct output for a novel
394
+ input, but you can still assert how outputs must *relate*. Scope is
395
+ deliberately narrow — free-text parameters like `title` get no relations at
396
+ all, because there the whitespace and casing are the content.
397
+
398
+ The fix has three parts, and none of them is a bigger penalty term:
399
+
400
+ 1. **The denominator is fixed.** `_compute_fitness` scores against
401
+ `REQUIRED_CHECK_CLASSES`, not against however many checks the mutant chose to
402
+ declare. Omitting a probe scores exactly as if it failed.
403
+ 2. **The gate is independent and boolean.** `ValidityGate` runs *before*
404
+ verification and returns admissible / not. A veto is not a number a mutant
405
+ can out-earn by being good at the task — there is no trade to make.
406
+ 3. **The baseline only grows.** `FrozenBaseline` pins the obligations a tool
407
+ had when it was trusted, persists them, and `extended_with` lets it absorb
408
+ *new* probes permanently. Without the ratchet, each generation freezes its
409
+ own predecessor and guardrails erode one step at a time.
410
+
411
+ ---
412
+
413
+ ## Extending
414
+
415
+ Add a generator:
416
+
417
+ ```python
418
+ class MyGenerator:
419
+ def generate(self, need: str, context: str = "") -> GeneratedTool: ...
420
+ ```
421
+
422
+ Add a sandbox backend (Docker, nsjail, a cloud runner):
423
+
424
+ ```python
425
+ Sandbox(runner=lambda code, entry, args: SandboxResult(...))
426
+ ```
427
+
428
+ Swap the model — anything speaking `/chat/completions`:
429
+
430
+ ```python
431
+ OpenAICompatClient(model=..., base_url=..., api_key=..., proxies={...})
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Status
437
+
438
+ v0.4.0 — the self-growth layer is complete: forge → verify (execution,
439
+ robustness, adversarial, trigger, negative) → seal, plus evolution, proactive
440
+ gap-filling, tool composition, self-modification with an audit trail, agent
441
+ spawning, and self-designed multi-agent topology. On top of it an
442
+ anti-misevolution layer: an independent validity gate, a fitness function the
443
+ mutant cannot author, a frozen baseline that only ratchets forward, Pareto
444
+ selection so safety cannot be paid for with capability, and metamorphic
445
+ oracles so the robustness layer actually has a verdict. Underneath it a native
446
+ layer: the machine is measured rather than assumed, a target ISA the host cannot
447
+ run is refused before the compile rather than after the crash, a benchmark's
448
+ units are audited so a `ms` label cannot sit over a 1000x value, and kernel
449
+ search reports its own noise floor instead of promoting the fastest noisy run.
450
+ 1142 tests passing. MIT.
451
+
452
+ **Breaking since v0.3.0:** the robustness check now has an oracle. Tools that
453
+ previously passed it by not raising will fail if they are degenerate or break a
454
+ declared normalisation relation.
455
+
456
+ Known limits: the default sandbox is process isolation, not a security
457
+ boundary against adversarial code (`restrict_builtins` narrows it; use a
458
+ `runner` for real containment). A *confirm* freedom has no approval flow in the
459
+ browser: the web harness runs agents in worker threads with no terminal, so it
460
+ answers "nobody to ask" and refuses rather than hanging a request on input it
461
+ cannot show you. Running `supervised` over the web UI therefore refuses the
462
+ gated tools instead of prompting for them — the CLI is the surface where the
463
+ question can actually be put to a person. It also does **not** separate the agent from the
464
+ host: forged code runs as a subprocess of the agent process on the same machine,
465
+ with the whole host filesystem and outbound network. `Sandbox.reach(probe=True)`
466
+ measures that with a real round-trip rather than asserting it, and
467
+ `my_capabilities` reports the measurement — because an agent that answers "can
468
+ you reach my machine?" from its tool list gets the answer wrong. The same rule
469
+ covers memory: the tool ledger is sqlite on disk (`ToolStore.report()`), forged
470
+ tools are persisted so they survive a restart, and `my_history` reads the ledger
471
+ and the self-modification log back. A measured self-report is appended to the
472
+ system prompt on every request, so the agent's description of itself is
473
+ recomputed from the machine instead of drifting as prose. Routing uses lexical
474
+ similarity rather than embeddings — swap `text_similarity` for a vector index
475
+ when the library is big enough to need it. `RoleType.FORGE` is declarative only:
476
+ a topology names a forge role, it does not yet change which tools that child can
477
+ reach.