nakedagent 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nakedagent-0.1.0/LICENSE +21 -0
- nakedagent-0.1.0/PKG-INFO +169 -0
- nakedagent-0.1.0/README.md +140 -0
- nakedagent-0.1.0/nakedagent/__init__.py +3 -0
- nakedagent-0.1.0/nakedagent/__main__.py +5 -0
- nakedagent-0.1.0/nakedagent/cli.py +196 -0
- nakedagent-0.1.0/nakedagent/driver.py +171 -0
- nakedagent-0.1.0/nakedagent/eventlog.py +275 -0
- nakedagent-0.1.0/nakedagent/functional.py +315 -0
- nakedagent-0.1.0/nakedagent/llm.py +124 -0
- nakedagent-0.1.0/nakedagent/loop.py +254 -0
- nakedagent-0.1.0/nakedagent/mcp.py +335 -0
- nakedagent-0.1.0/nakedagent/migrate_log.py +187 -0
- nakedagent-0.1.0/nakedagent/plugins.py +153 -0
- nakedagent-0.1.0/nakedagent/providers_draft.py +302 -0
- nakedagent-0.1.0/nakedagent/replay.py +78 -0
- nakedagent-0.1.0/nakedagent/sidekick.py +404 -0
- nakedagent-0.1.0/nakedagent/toolcall.py +124 -0
- nakedagent-0.1.0/nakedagent/tools.py +428 -0
- nakedagent-0.1.0/nakedagent.egg-info/PKG-INFO +169 -0
- nakedagent-0.1.0/nakedagent.egg-info/SOURCES.txt +46 -0
- nakedagent-0.1.0/nakedagent.egg-info/dependency_links.txt +1 -0
- nakedagent-0.1.0/nakedagent.egg-info/entry_points.txt +2 -0
- nakedagent-0.1.0/nakedagent.egg-info/top_level.txt +1 -0
- nakedagent-0.1.0/pyproject.toml +57 -0
- nakedagent-0.1.0/setup.cfg +4 -0
- nakedagent-0.1.0/tests/test_cli.py +126 -0
- nakedagent-0.1.0/tests/test_driver.py +254 -0
- nakedagent-0.1.0/tests/test_eventlog.py +112 -0
- nakedagent-0.1.0/tests/test_functional.py +137 -0
- nakedagent-0.1.0/tests/test_integration.py +94 -0
- nakedagent-0.1.0/tests/test_invariants.py +292 -0
- nakedagent-0.1.0/tests/test_llm.py +131 -0
- nakedagent-0.1.0/tests/test_loop.py +245 -0
- nakedagent-0.1.0/tests/test_mcp.py +102 -0
- nakedagent-0.1.0/tests/test_mcp_repair.py +201 -0
- nakedagent-0.1.0/tests/test_mcp_wire.py +153 -0
- nakedagent-0.1.0/tests/test_migrate_log.py +136 -0
- nakedagent-0.1.0/tests/test_plugins.py +208 -0
- nakedagent-0.1.0/tests/test_pr18_boundaries.py +145 -0
- nakedagent-0.1.0/tests/test_pr20_gate.py +154 -0
- nakedagent-0.1.0/tests/test_pr20_options.py +28 -0
- nakedagent-0.1.0/tests/test_replay_api_compat.py +69 -0
- nakedagent-0.1.0/tests/test_resume.py +184 -0
- nakedagent-0.1.0/tests/test_sidekick.py +189 -0
- nakedagent-0.1.0/tests/test_toolcall.py +291 -0
- nakedagent-0.1.0/tests/test_tools.py +369 -0
- nakedagent-0.1.0/tests/test_v04_contract.py +115 -0
nakedagent-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reuben Bowlby
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nakedagent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A coding agent with zero runtime dependencies. Stdlib-only Python.
|
|
5
|
+
Author-email: Reuben Bowlby <reuben@hummbl.io>
|
|
6
|
+
Maintainer-email: "HUMMBL, LLC" <reuben@hummbl.io>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Repository, https://github.com/hummbl-io/nakedagent
|
|
9
|
+
Project-URL: Homepage, https://github.com/hummbl-io/nakedagent
|
|
10
|
+
Project-URL: Documentation, https://github.com/hummbl-io/nakedagent#readme
|
|
11
|
+
Project-URL: Issues, https://github.com/hummbl-io/nakedagent/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/hummbl-io/nakedagent/releases
|
|
13
|
+
Keywords: coding-agent,zero-dependencies,stdlib-only,llm,autonomous-agents,software-engineering,replay,mealy-machine,ollama
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# nakedagent
|
|
31
|
+
|
|
32
|
+
A coding agent with **zero runtime dependencies**. `pip install`-free by
|
|
33
|
+
construction: `python -m nakedagent` runs on a stock Python 3.10+ interpreter,
|
|
34
|
+
nothing else.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
$ python -m nakedagent
|
|
38
|
+
nakedagent -- workspace: /home/you/myproject -- model: qwen2.5-coder:7b
|
|
39
|
+
Ctrl-D to exit.
|
|
40
|
+
|
|
41
|
+
> add a .gitignore for a Python project
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Why
|
|
45
|
+
|
|
46
|
+
Every other agent in this space — gptme, aider, OpenHands, langchain-based
|
|
47
|
+
tools — pulls in 20-40+ packages: HTTP clients, CLI-formatting libraries,
|
|
48
|
+
provider SDKs, telemetry. That's not a criticism of them; those dependencies
|
|
49
|
+
buy real capability (multi-provider abstraction, rich terminal rendering,
|
|
50
|
+
robust malformed-output recovery). But it also means every install inherits
|
|
51
|
+
whatever CVEs are sitting in that dependency tree, and updating any one
|
|
52
|
+
package can break the agent.
|
|
53
|
+
|
|
54
|
+
nakedagent takes the other side of that trade on purpose: `urllib` + `json` +
|
|
55
|
+
`subprocess` + `re`, all from the standard library, are enough to drive a
|
|
56
|
+
local model through a working tool-use loop. No supply chain, because there
|
|
57
|
+
is no supply.
|
|
58
|
+
|
|
59
|
+
## What you get for that
|
|
60
|
+
|
|
61
|
+
- **Tools**: `shell`, `read`, `write`, `patch` (search/replace edits, aider's
|
|
62
|
+
simplest edit format). That's the whole *foundation* tool surface.
|
|
63
|
+
`shell` is confirmation-first in interactive mode and requires `--allow-shell`
|
|
64
|
+
in non-interactive mode, with optional command filtering via
|
|
65
|
+
`--shell-allowlist`.
|
|
66
|
+
- **Model backend**: [Ollama](https://ollama.com) by default — local-first,
|
|
67
|
+
no API key required to try it. For models too large to run locally,
|
|
68
|
+
`--api openai` speaks the OpenAI-compatible format that hosted APIs,
|
|
69
|
+
vLLM/LM Studio and gateways share (see below).
|
|
70
|
+
- **Tool-call format**: the model writes a fenced code block whose language
|
|
71
|
+
tag is the tool name; nakedagent parses it out of the response text after
|
|
72
|
+
each turn. Works with any model that can write a code fence — no dependency
|
|
73
|
+
on a provider's native function-calling API.
|
|
74
|
+
- **Plugins**: drop a `.py` file in `.nakedagent/plugins/` (repo-local,
|
|
75
|
+
requires `--trust-workspace-plugins`) or `~/.nakedagent/plugins/`
|
|
76
|
+
(user-global, always loaded) that defines a `TOOLS` dict (add or
|
|
77
|
+
override tools) and/or a `DISABLE` list (remove tools entirely — e.g. a
|
|
78
|
+
read-only agent disables `shell` and `write`). Each tool carries a `.usage`
|
|
79
|
+
attribute that controls its prompt example, so a plugin replacing a tool
|
|
80
|
+
replaces its syntax too. No registration, no framework — see
|
|
81
|
+
[`DOCTRINE.md`](DOCTRINE.md) for the omakase framing. Repo-local plugins
|
|
82
|
+
are off by default because they execute with your privileges — only pass
|
|
83
|
+
the flag in workspaces you trust.
|
|
84
|
+
|
|
85
|
+
See [`docs/architecture/architecture.md`](docs/architecture/architecture.md) for how the loop and tool
|
|
86
|
+
parser work, including what was learned from reading gptme's and aider's
|
|
87
|
+
actual source before writing this.
|
|
88
|
+
|
|
89
|
+
## Quick start
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/hummbl-io/nakedagent.git
|
|
93
|
+
cd nakedagent
|
|
94
|
+
ollama pull qwen2.5-coder:7b # or any model you like
|
|
95
|
+
python -m nakedagent # interactive
|
|
96
|
+
python -m nakedagent "explain what this repo does" # one-shot
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Larger models
|
|
100
|
+
|
|
101
|
+
Small local models get the loop running; bigger models make it good. Point
|
|
102
|
+
nakedagent at any OpenAI-compatible server. The key comes from an
|
|
103
|
+
environment variable (`--api-key-env`, default `OPENAI_API_KEY`), never a flag:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# hosted API
|
|
107
|
+
OPENAI_API_KEY=... python -m nakedagent --api openai --host https://api.openai.com/v1 -m <model>
|
|
108
|
+
|
|
109
|
+
# self-hosted (vLLM, LM Studio) — no key needed
|
|
110
|
+
python -m nakedagent --api openai --host http://localhost:8000/v1 -m <model>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Shell safety for automation
|
|
114
|
+
|
|
115
|
+
For one-shot runs and other non-interactive use-cases, the shell tool is denied
|
|
116
|
+
unless explicit shell allowances are provided:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python -m nakedagent "run project checks" --allow-shell --shell-allowlist "git status" --shell-allowlist "ls"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`--shell-allowlist` is prefix-based. If you pass `--allow-shell` with an empty
|
|
123
|
+
allowlist, all non-interactive shell commands are blocked.
|
|
124
|
+
|
|
125
|
+
No `pip install` step. That's not an oversight — `nakedagent/` only imports
|
|
126
|
+
the standard library, so running it in place works.
|
|
127
|
+
|
|
128
|
+
## Provable execution: event log + replay
|
|
129
|
+
|
|
130
|
+
One-shot runs can go through the functional lane — the agent is a pure Mealy
|
|
131
|
+
machine (`functional.agent_reducer`: `(state, event) -> (state, actions)`),
|
|
132
|
+
and a thin driver (`driver.py`) performs the actual model/tool IO while
|
|
133
|
+
appending every event to a JSONL log:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
python -m nakedagent "add a .gitignore" --event-log run.jsonl
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Each logged event carries the Merkle state hash *after* that transition, so
|
|
140
|
+
the trace is a tamper-evident receipt. Replay reconstructs the run with zero
|
|
141
|
+
model calls and verifies the whole chain:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
python -m nakedagent.replay run.jsonl
|
|
145
|
+
# PASS run.jsonl: verified 5 events (1 tool results); final hash 6d1e0965…
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Status
|
|
149
|
+
|
|
150
|
+
MVP. Two wire formats (Ollama native + OpenAI-compatible), four foundation
|
|
151
|
+
tools, no streaming. The plugin
|
|
152
|
+
seam is the one extension surface — see [`DOCTRINE.md`](DOCTRINE.md).
|
|
153
|
+
Went through two independent peer reviews (headless GLM-5.2, and a
|
|
154
|
+
separately-running devin session, both 2026-09-09) before this first push —
|
|
155
|
+
between them they found five real bugs, all fixed with regression tests,
|
|
156
|
+
documented in [`docs/architecture/architecture.md`](docs/architecture/architecture.md) alongside what's
|
|
157
|
+
still genuinely not here and why.
|
|
158
|
+
|
|
159
|
+
## Research & Citation
|
|
160
|
+
|
|
161
|
+
If you use nakedagent in academic research or want to study its architecture, please cite our research paper:
|
|
162
|
+
|
|
163
|
+
> Reuben Bowlby, **"NakedAgent: A Zero-Dependency Coding Agent Architecture via Standard-Library Primitives and Provable Replay"**, HUMMBL Research, Zenodo Preprint, 2026.
|
|
164
|
+
|
|
165
|
+
Preprint PDF and LaTeX sources are available under [`paper/`](paper/). For machine-readable citation metadata, see [`CITATION.cff`](CITATION.cff).
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# nakedagent
|
|
2
|
+
|
|
3
|
+
A coding agent with **zero runtime dependencies**. `pip install`-free by
|
|
4
|
+
construction: `python -m nakedagent` runs on a stock Python 3.10+ interpreter,
|
|
5
|
+
nothing else.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
$ python -m nakedagent
|
|
9
|
+
nakedagent -- workspace: /home/you/myproject -- model: qwen2.5-coder:7b
|
|
10
|
+
Ctrl-D to exit.
|
|
11
|
+
|
|
12
|
+
> add a .gitignore for a Python project
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Every other agent in this space — gptme, aider, OpenHands, langchain-based
|
|
18
|
+
tools — pulls in 20-40+ packages: HTTP clients, CLI-formatting libraries,
|
|
19
|
+
provider SDKs, telemetry. That's not a criticism of them; those dependencies
|
|
20
|
+
buy real capability (multi-provider abstraction, rich terminal rendering,
|
|
21
|
+
robust malformed-output recovery). But it also means every install inherits
|
|
22
|
+
whatever CVEs are sitting in that dependency tree, and updating any one
|
|
23
|
+
package can break the agent.
|
|
24
|
+
|
|
25
|
+
nakedagent takes the other side of that trade on purpose: `urllib` + `json` +
|
|
26
|
+
`subprocess` + `re`, all from the standard library, are enough to drive a
|
|
27
|
+
local model through a working tool-use loop. No supply chain, because there
|
|
28
|
+
is no supply.
|
|
29
|
+
|
|
30
|
+
## What you get for that
|
|
31
|
+
|
|
32
|
+
- **Tools**: `shell`, `read`, `write`, `patch` (search/replace edits, aider's
|
|
33
|
+
simplest edit format). That's the whole *foundation* tool surface.
|
|
34
|
+
`shell` is confirmation-first in interactive mode and requires `--allow-shell`
|
|
35
|
+
in non-interactive mode, with optional command filtering via
|
|
36
|
+
`--shell-allowlist`.
|
|
37
|
+
- **Model backend**: [Ollama](https://ollama.com) by default — local-first,
|
|
38
|
+
no API key required to try it. For models too large to run locally,
|
|
39
|
+
`--api openai` speaks the OpenAI-compatible format that hosted APIs,
|
|
40
|
+
vLLM/LM Studio and gateways share (see below).
|
|
41
|
+
- **Tool-call format**: the model writes a fenced code block whose language
|
|
42
|
+
tag is the tool name; nakedagent parses it out of the response text after
|
|
43
|
+
each turn. Works with any model that can write a code fence — no dependency
|
|
44
|
+
on a provider's native function-calling API.
|
|
45
|
+
- **Plugins**: drop a `.py` file in `.nakedagent/plugins/` (repo-local,
|
|
46
|
+
requires `--trust-workspace-plugins`) or `~/.nakedagent/plugins/`
|
|
47
|
+
(user-global, always loaded) that defines a `TOOLS` dict (add or
|
|
48
|
+
override tools) and/or a `DISABLE` list (remove tools entirely — e.g. a
|
|
49
|
+
read-only agent disables `shell` and `write`). Each tool carries a `.usage`
|
|
50
|
+
attribute that controls its prompt example, so a plugin replacing a tool
|
|
51
|
+
replaces its syntax too. No registration, no framework — see
|
|
52
|
+
[`DOCTRINE.md`](DOCTRINE.md) for the omakase framing. Repo-local plugins
|
|
53
|
+
are off by default because they execute with your privileges — only pass
|
|
54
|
+
the flag in workspaces you trust.
|
|
55
|
+
|
|
56
|
+
See [`docs/architecture/architecture.md`](docs/architecture/architecture.md) for how the loop and tool
|
|
57
|
+
parser work, including what was learned from reading gptme's and aider's
|
|
58
|
+
actual source before writing this.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/hummbl-io/nakedagent.git
|
|
64
|
+
cd nakedagent
|
|
65
|
+
ollama pull qwen2.5-coder:7b # or any model you like
|
|
66
|
+
python -m nakedagent # interactive
|
|
67
|
+
python -m nakedagent "explain what this repo does" # one-shot
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Larger models
|
|
71
|
+
|
|
72
|
+
Small local models get the loop running; bigger models make it good. Point
|
|
73
|
+
nakedagent at any OpenAI-compatible server. The key comes from an
|
|
74
|
+
environment variable (`--api-key-env`, default `OPENAI_API_KEY`), never a flag:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# hosted API
|
|
78
|
+
OPENAI_API_KEY=... python -m nakedagent --api openai --host https://api.openai.com/v1 -m <model>
|
|
79
|
+
|
|
80
|
+
# self-hosted (vLLM, LM Studio) — no key needed
|
|
81
|
+
python -m nakedagent --api openai --host http://localhost:8000/v1 -m <model>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Shell safety for automation
|
|
85
|
+
|
|
86
|
+
For one-shot runs and other non-interactive use-cases, the shell tool is denied
|
|
87
|
+
unless explicit shell allowances are provided:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python -m nakedagent "run project checks" --allow-shell --shell-allowlist "git status" --shell-allowlist "ls"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--shell-allowlist` is prefix-based. If you pass `--allow-shell` with an empty
|
|
94
|
+
allowlist, all non-interactive shell commands are blocked.
|
|
95
|
+
|
|
96
|
+
No `pip install` step. That's not an oversight — `nakedagent/` only imports
|
|
97
|
+
the standard library, so running it in place works.
|
|
98
|
+
|
|
99
|
+
## Provable execution: event log + replay
|
|
100
|
+
|
|
101
|
+
One-shot runs can go through the functional lane — the agent is a pure Mealy
|
|
102
|
+
machine (`functional.agent_reducer`: `(state, event) -> (state, actions)`),
|
|
103
|
+
and a thin driver (`driver.py`) performs the actual model/tool IO while
|
|
104
|
+
appending every event to a JSONL log:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
python -m nakedagent "add a .gitignore" --event-log run.jsonl
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each logged event carries the Merkle state hash *after* that transition, so
|
|
111
|
+
the trace is a tamper-evident receipt. Replay reconstructs the run with zero
|
|
112
|
+
model calls and verifies the whole chain:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python -m nakedagent.replay run.jsonl
|
|
116
|
+
# PASS run.jsonl: verified 5 events (1 tool results); final hash 6d1e0965…
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Status
|
|
120
|
+
|
|
121
|
+
MVP. Two wire formats (Ollama native + OpenAI-compatible), four foundation
|
|
122
|
+
tools, no streaming. The plugin
|
|
123
|
+
seam is the one extension surface — see [`DOCTRINE.md`](DOCTRINE.md).
|
|
124
|
+
Went through two independent peer reviews (headless GLM-5.2, and a
|
|
125
|
+
separately-running devin session, both 2026-09-09) before this first push —
|
|
126
|
+
between them they found five real bugs, all fixed with regression tests,
|
|
127
|
+
documented in [`docs/architecture/architecture.md`](docs/architecture/architecture.md) alongside what's
|
|
128
|
+
still genuinely not here and why.
|
|
129
|
+
|
|
130
|
+
## Research & Citation
|
|
131
|
+
|
|
132
|
+
If you use nakedagent in academic research or want to study its architecture, please cite our research paper:
|
|
133
|
+
|
|
134
|
+
> Reuben Bowlby, **"NakedAgent: A Zero-Dependency Coding Agent Architecture via Standard-Library Primitives and Provable Replay"**, HUMMBL Research, Zenodo Preprint, 2026.
|
|
135
|
+
|
|
136
|
+
Preprint PDF and LaTeX sources are available under [`paper/`](paper/). For machine-readable citation metadata, see [`CITATION.cff`](CITATION.cff).
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""nakedagent -- a zero-dependency terminal coding agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .llm import APIS, DEFAULT_API_KEY_ENV, DEFAULT_HOST, LLMError
|
|
11
|
+
from .loop import run, run_interactive
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: list[str] | None = None) -> int:
|
|
15
|
+
p = argparse.ArgumentParser(prog="nakedagent", description=__doc__)
|
|
16
|
+
p.add_argument("prompt", nargs="?", help="run once with this prompt, then exit")
|
|
17
|
+
p.add_argument("-m", "--model", default="qwen2.5-coder:7b", help="model name or Ollama tag")
|
|
18
|
+
p.add_argument("-w", "--workspace", type=Path, default=Path.cwd())
|
|
19
|
+
p.add_argument(
|
|
20
|
+
"--host",
|
|
21
|
+
default=None,
|
|
22
|
+
help=(
|
|
23
|
+
f"model server URL (default: {DEFAULT_HOST} for ollama; required for "
|
|
24
|
+
"--api openai, including the version path, e.g. https://api.openai.com/v1)"
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
p.add_argument(
|
|
28
|
+
"--api",
|
|
29
|
+
choices=APIS,
|
|
30
|
+
default="ollama",
|
|
31
|
+
help="wire format: ollama (local, default) or openai (any OpenAI-compatible server)",
|
|
32
|
+
)
|
|
33
|
+
p.add_argument(
|
|
34
|
+
"--api-key-env",
|
|
35
|
+
default=DEFAULT_API_KEY_ENV,
|
|
36
|
+
metavar="VAR",
|
|
37
|
+
help=(
|
|
38
|
+
"environment variable holding the API key for --api openai "
|
|
39
|
+
f"(default: {DEFAULT_API_KEY_ENV}); the key itself is never a flag"
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
p.add_argument(
|
|
43
|
+
"--allow-shell",
|
|
44
|
+
action="store_true",
|
|
45
|
+
help="Allow shell tool execution in non-interactive contexts.",
|
|
46
|
+
)
|
|
47
|
+
p.add_argument(
|
|
48
|
+
"--shell-allowlist",
|
|
49
|
+
action="append",
|
|
50
|
+
default=[],
|
|
51
|
+
metavar="PATTERN",
|
|
52
|
+
help=(
|
|
53
|
+
"Allowed shell command prefixes when --allow-shell is set. "
|
|
54
|
+
"Repeatable. Example: --shell-allowlist 'git status' "
|
|
55
|
+
"--shell-allowlist 'ls'"
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
p.add_argument(
|
|
59
|
+
"--shell-timeout",
|
|
60
|
+
type=int,
|
|
61
|
+
default=120,
|
|
62
|
+
help="Timeout in seconds for shell tool execution.",
|
|
63
|
+
)
|
|
64
|
+
p.add_argument(
|
|
65
|
+
"--trust-plugins",
|
|
66
|
+
action="store_true",
|
|
67
|
+
help=(
|
|
68
|
+
"Load <workspace>/.nakedagent/plugins/*.py at startup. "
|
|
69
|
+
"Disabled by default to prevent arbitrary code execution on "
|
|
70
|
+
"untrusted repositories. Alias of --trust-workspace-plugins."
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
p.add_argument(
|
|
74
|
+
"--trust-workspace-plugins",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help=(
|
|
77
|
+
"Load <workspace>/.nakedagent/plugins/*.py at startup. Off by "
|
|
78
|
+
"default: workspace plugins are repo-controlled code running with "
|
|
79
|
+
"your privileges. Only set this in workspaces you trust. "
|
|
80
|
+
"User-global ~/.nakedagent/plugins/ always loads."
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
p.add_argument(
|
|
84
|
+
"--event-log",
|
|
85
|
+
type=Path,
|
|
86
|
+
metavar="PATH",
|
|
87
|
+
help=(
|
|
88
|
+
"Run through the functional driver and append every agent event "
|
|
89
|
+
"to a JSONL log at PATH. One-shot mode only. Verify with "
|
|
90
|
+
"`python -m nakedagent.replay PATH`."
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
p.add_argument(
|
|
94
|
+
"--resume",
|
|
95
|
+
type=Path,
|
|
96
|
+
metavar="PATH",
|
|
97
|
+
help=(
|
|
98
|
+
"Resume a suspended event log at PATH: replay-verify the recorded "
|
|
99
|
+
"prefix, then feed PROMPT as the human verdict that lifts the "
|
|
100
|
+
"suspension. PATH is reused as the event log — the suspended "
|
|
101
|
+
"trailer is replaced, the Merkle chain continues."
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
p.add_argument("--version", action="version", version=__version__)
|
|
105
|
+
args = p.parse_args(argv)
|
|
106
|
+
|
|
107
|
+
workspace = args.workspace.resolve()
|
|
108
|
+
shell_allowlist = tuple(
|
|
109
|
+
pattern.strip() for pattern in args.shell_allowlist if pattern.strip()
|
|
110
|
+
)
|
|
111
|
+
if args.shell_timeout <= 0:
|
|
112
|
+
print("error: --shell-timeout must be greater than 0", file=sys.stderr)
|
|
113
|
+
return 1
|
|
114
|
+
if args.event_log and not args.prompt:
|
|
115
|
+
print("error: --event-log requires a one-shot prompt", file=sys.stderr)
|
|
116
|
+
return 1
|
|
117
|
+
if args.resume and not args.prompt:
|
|
118
|
+
print("error: --resume requires PROMPT as the verdict input", file=sys.stderr)
|
|
119
|
+
return 1
|
|
120
|
+
if args.resume and args.event_log and args.resume != args.event_log:
|
|
121
|
+
print("error: --resume reuses the same path as --event-log; pass only --resume", file=sys.stderr)
|
|
122
|
+
return 1
|
|
123
|
+
host = args.host
|
|
124
|
+
if host is None:
|
|
125
|
+
if args.api != "ollama":
|
|
126
|
+
# No default: silently sending a prompt (and a key) to a server
|
|
127
|
+
# the user didn't name is worse than asking.
|
|
128
|
+
print("error: --api openai needs --host (e.g. https://api.openai.com/v1)", file=sys.stderr)
|
|
129
|
+
return 1
|
|
130
|
+
host = DEFAULT_HOST
|
|
131
|
+
llm_options = {}
|
|
132
|
+
if args.api != "ollama":
|
|
133
|
+
llm_options = {"api": args.api, "api_key_env": args.api_key_env}
|
|
134
|
+
# Plugin trust: two flag spellings, one opt-in. `trust_plugins` always
|
|
135
|
+
# rides along; `trust_workspace_plugins` is forwarded only when a trust
|
|
136
|
+
# flag was actually given -- downstream defaults govern otherwise.
|
|
137
|
+
plugin_trust: dict = {"trust_plugins": args.trust_plugins}
|
|
138
|
+
if args.trust_plugins or args.trust_workspace_plugins:
|
|
139
|
+
plugin_trust["trust_workspace_plugins"] = args.trust_workspace_plugins
|
|
140
|
+
try:
|
|
141
|
+
if args.prompt:
|
|
142
|
+
if args.event_log or args.resume:
|
|
143
|
+
# Deferred import keeps `nakedagent.driver.run_functional`
|
|
144
|
+
# patchable at call time (the CLI tests mock it there).
|
|
145
|
+
from .driver import run_functional
|
|
146
|
+
|
|
147
|
+
run_functional(
|
|
148
|
+
args.prompt,
|
|
149
|
+
args.model,
|
|
150
|
+
workspace,
|
|
151
|
+
host,
|
|
152
|
+
event_log_path=args.resume or args.event_log,
|
|
153
|
+
allow_shell=args.allow_shell,
|
|
154
|
+
shell_allowlist=shell_allowlist,
|
|
155
|
+
shell_timeout=args.shell_timeout,
|
|
156
|
+
llm_options=llm_options,
|
|
157
|
+
resume_from=args.resume,
|
|
158
|
+
**plugin_trust,
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
run(
|
|
162
|
+
args.prompt,
|
|
163
|
+
args.model,
|
|
164
|
+
workspace,
|
|
165
|
+
host,
|
|
166
|
+
allow_shell=args.allow_shell,
|
|
167
|
+
shell_allowlist=shell_allowlist,
|
|
168
|
+
shell_timeout=args.shell_timeout,
|
|
169
|
+
llm_options=llm_options,
|
|
170
|
+
**plugin_trust,
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
run_interactive(
|
|
174
|
+
args.model,
|
|
175
|
+
workspace,
|
|
176
|
+
host,
|
|
177
|
+
allow_shell=args.allow_shell,
|
|
178
|
+
shell_allowlist=shell_allowlist,
|
|
179
|
+
shell_timeout=args.shell_timeout,
|
|
180
|
+
llm_options=llm_options,
|
|
181
|
+
**plugin_trust,
|
|
182
|
+
)
|
|
183
|
+
except LLMError as e:
|
|
184
|
+
print(f"error: {e}", file=sys.stderr)
|
|
185
|
+
return 1
|
|
186
|
+
except (RuntimeError, ValueError) as e:
|
|
187
|
+
# Resume refusals and eventlog integrity failures land here.
|
|
188
|
+
print(f"error: {e}", file=sys.stderr)
|
|
189
|
+
return 1
|
|
190
|
+
except KeyboardInterrupt:
|
|
191
|
+
return 130
|
|
192
|
+
return 0
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
sys.exit(main())
|