rangers 0.6.1__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.
rangers-0.6.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AlexKay28
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,6 @@
1
+ include LICENSE
2
+ include README.md
3
+ include SKILL.md
4
+ include docs/cheatsheet.png
5
+ include docs/*.md
6
+ include scripts/cheatsheet.py
rangers-0.6.1/PKG-INFO ADDED
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.4
2
+ Name: rangers
3
+ Version: 0.6.1
4
+ Summary: Agent-as-a-library: drive headless coding-agent harnesses from Python
5
+ Author: AlexKay28
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AlexKay28/ranger
8
+ Project-URL: Documentation, https://github.com/AlexKay28/ranger/tree/main/docs
9
+ Project-URL: Repository, https://github.com/AlexKay28/ranger
10
+ Project-URL: Issues, https://github.com/AlexKay28/ranger/issues
11
+ Keywords: agent,ai,automation,codex,coding-agent,opencode
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.2; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: twine>=6.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # ranger
34
+
35
+ ![CI](https://github.com/AlexKay28/ranger/actions/workflows/ci.yml/badge.svg)
36
+
37
+ Agent-as-a-library. Run the [opencode](https://opencode.ai) or
38
+ [codex](https://github.com/openai/codex) coding agents headlessly from plain
39
+ Python code — for agentic-loop prototyping. You write code, not chat.
40
+
41
+ The whole design follows one paradigm: **the agent is a function call —
42
+ Python owns the loop, the harness is just the data plane.** Plan → act →
43
+ check → repeat maps 1:1 onto expression, statement and condition. See
44
+ [docs/PARADIGM.md](docs/PARADIGM.md) for the seven laws every feature is
45
+ reviewed against.
46
+
47
+ AI coding agents: start with [SKILL.md](SKILL.md) for the library's safe
48
+ workflow, contracts, and repository contribution rules.
49
+
50
+ <p align="center">
51
+ <img src="https://raw.githubusercontent.com/AlexKay28/ranger/main/docs/cheatsheet.png" alt="ranger command cheatsheet" width="760">
52
+ </p>
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install rangers
58
+ ```
59
+
60
+ The distribution is named **`rangers`** on PyPI; the Python import remains
61
+ `import ranger`. For development from a clone: `pip install -e .[dev]`.
62
+ Maintainers: see the [publishing checklist](docs/PUBLISHING.md) before any
63
+ release.
64
+
65
+ Requires a harness CLI on PATH: [opencode](https://opencode.ai) (default
66
+ backend) and/or [codex](https://github.com/openai/codex). On a distro-pegged
67
+ pip (e.g. 22.x) an editable install can fail with "build backend is missing
68
+ the build_editable hook" — fix the tool, not the package:
69
+ `pip install -U pip`, then retry.
70
+
71
+ ## Quick start (no clone needed)
72
+
73
+ Straight from GitHub, then a one-line sanity check in your terminal:
74
+
75
+ ```bash
76
+ pip install git+https://github.com/AlexKay28/ranger
77
+
78
+ python -c "import ranger; print(ranger.Ranger.models(match='claude'))"
79
+ ```
80
+
81
+ The check needs no model or API budget — it just lists what your local
82
+ opencode CLI offers. Got model ids back? You're ready:
83
+
84
+ ```python
85
+ import ranger
86
+ agent = ranger.Ranger(model="<one of the ids above>")
87
+ print(agent.ask("say ok"))
88
+ ```
89
+
90
+ Runnable applications live in [examples/](examples/): quickstart, model
91
+ discovery, a full agentic loop, a self-hosted project-growth loop, plus three
92
+ small day-to-day recipes — repair a red test command, independently review a
93
+ local diff before merge, review one file, and draft release notes from git
94
+ history. They are organized by use case in the
95
+ [examples index](examples/README.md): getting started, code work, monitoring,
96
+ multi-agent, backends, and no-model dummy runs.
97
+
98
+ Live progress: each command draws a morphing line on stderr while it runs
99
+ (`⠹ search · TODO items in this repo · 00:03`) and settles as
100
+ `✓ search · 00:07 · <first line of answer>` — opencode-wizard style, colors
101
+ included. It auto-disables in pipes/tests and honors `NO_COLOR`;
102
+ turn it off with `pretty=False`.
103
+
104
+ Model priority: explicit `Ranger(model=...)` → `$RANGER_MODEL` env var →
105
+ package default.
106
+
107
+ ## Usage
108
+
109
+ ```python
110
+ agent = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5")
111
+ agent.summarize("notes.md") # -> str summary of the file
112
+ agent.write("a one-page report on X") # -> path of the file the agent wrote
113
+ agent.do("rename all *.txt files in data/ to *.md") # -> report of CLI-like work
114
+ agent.search("where do we parse config?") # -> multiline findings report
115
+ agent.ask("anything at all") # -> raw prompt, raw answer
116
+ agent.check("all tests pass") # -> YES/NO verdict as a bool
117
+ agent.gate("python -m pytest -q") # -> exit-code booleans, no model
118
+ agent.until("make the tests green", gate_cmd="python -m pytest -q")
119
+ # -> loop: plan/do/verify until green
120
+ ```
121
+
122
+ Each call is one fresh headless `opencode run` in the agent's working directory
123
+ (`cwd=`), with permissions auto-approved so it can actually touch the filesystem.
124
+ Pass `session="..."` to share context between calls, `model=` to pick any
125
+ `provider/model` ID from `opencode models`.
126
+
127
+ ## Model discovery & configuration
128
+
129
+ ```python
130
+ import ranger
131
+
132
+ ranger.Ranger.models() # -> all model IDs
133
+ ranger.Ranger.models(match="claude") # -> substring filter
134
+
135
+ smart = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5")
136
+ fast = smart.with_config(model="openrouter/google/gemini-2.5-flash", agent="build")
137
+ ```
138
+
139
+ Every command method also accepts per-call overrides:
140
+
141
+ ```python
142
+ fast.search("todo items", title="quick scan")
143
+ smart.do("refactor auth", timeout=1800)
144
+ ```
145
+
146
+ Overridable: `model`, `timeout`, `auto`, `session`, `agent` (opencode agent
147
+ name like "build"/"plan"), `title`, `thinking`. Unknown keywords raise
148
+ `TypeError` so typos fail loudly.
149
+
150
+ See [docs/commands.md](docs/commands.md) for the full command catalog —
151
+ 15 methods at three maturity tiers: what's shipped, what's next, and
152
+ what needs bigger machinery (gates, retries, safety rails) —
153
+ [docs/API.md](docs/API.md) for the complete, per-setting API reference
154
+ (kept mechanically in sync with the code by the test suite), and
155
+ [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for a high-level tour of
156
+ the codebase and its three architectural decisions.
157
+
158
+ ## Context state (memory)
159
+
160
+ The agent remembers. Every command exchange is recorded to
161
+ `agent.context` in classic OpenAI message format
162
+ (`{"role": "user"|"assistant"|"system", "content": ...}`) and recent
163
+ history (within a character budget) is recalled into later runs — so
164
+ follow-ups like "now do the same for tests/" just work.
165
+
166
+ ```python
167
+ agent.context # the message list
168
+ agent.context_summarize() # -> digest of what happened so far
169
+ agent.context_compact() # collapse history to one system digest
170
+ agent.context_clear() # wipe; returns what was cleared
171
+ agent.ask("...", memory=False) # one stateless run
172
+ ranger.Ranger(memory=False) # or a fully stateless agent
173
+ ```
174
+
175
+ Context memory colors your dialogues, never your facts: `ask`/`do`/`write`
176
+ recall the recent conversation; `check`/`search`/`summarize` always run
177
+ history-independent, so a verdict is about the repo, not the chat
178
+ ([PARADIGM.md](docs/PARADIGM.md), law 4).
179
+
180
+ ## Backends (harness adapters)
181
+
182
+ The same API drives different harnesses; the setting is one constructor
183
+ argument, per-call overridable:
184
+
185
+ ```python
186
+ import ranger
187
+
188
+ agent = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5") # opencode (default)
189
+ gpt = ranger.Ranger(backend="codex") # model from ~/.codex/config.toml
190
+ gpt = ranger.Ranger(backend="codex", model="gpt-5.1-codex")
191
+
192
+ ranger.register_backend("mine", MyHarnessAdapter) # your own — one small class
193
+ ```
194
+
195
+ Harness differences (flags, sessions, sandboxes, output schemas) are
196
+ absorbed by one adapter class per backend — see the Backends section of
197
+ [docs/API.md](docs/API.md) for the full capability map and the recipe for
198
+ adding your own harness.
199
+
200
+ ## Multi-agent interactions
201
+
202
+ Two (or three) agents, and the expected patterns as ready-made methods —
203
+ verdicts are grammar, budgets are honest, reviewers run free of their own
204
+ chat history:
205
+
206
+ ```python
207
+ both = builder.second_opinion(critic, prompt) # {mine, theirs, differs}
208
+ verdict = critic.review(subject, author=builder) # APPROVE/REJECT/REQUEST_CHANGES
209
+ outcome = builder.refine_with(critic, goal) # the two-agent until()
210
+ duel = critic.challenge(builder) # adversarial claim attack
211
+ talk = ranger.debate(builder, critic, topic, judge=skeptic)
212
+ ```
213
+
214
+ See the Multi-agent interactions section of [docs/API.md](docs/API.md)
215
+ and [`examples/multiagent/multi_agent.py`](examples/multiagent/multi_agent.py).
216
+
217
+ ## Skills allow-list
218
+
219
+ Grant the agent specific skills or operating rules per run — a markdown
220
+ file or directory rides along via opencode's `--file` attachment; plain
221
+ prose entries are injected as prompt grants.
222
+
223
+ ```python
224
+ agent = ranger.Ranger(skills=[
225
+ "skills/haiku/SKILL.md", # file -> --file attachment
226
+ "skills/pack", # dir -> all its *.md attached
227
+ 'Always answer in British spelling', # prose -> prompt grant
228
+ ])
229
+ agent.do("write the changelog", skills=["docs/changelog_style.md"]) # per call
230
+ ```
231
+
232
+ ## AGI loop, the point of the whole thing
233
+
234
+ ```python
235
+ agent = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5")
236
+
237
+ for i in range(10):
238
+ hits = agent.search(f"TODO and unimplemented spots, pass {i}")
239
+ plan = agent.ask(f"Pick ONE thing to fix from:\n{hits}\nReply with a one-line fix plan.")
240
+ report = agent.do(plan)
241
+ print(f"[pass {i}] {report}")
242
+ ```
@@ -0,0 +1,210 @@
1
+ # ranger
2
+
3
+ ![CI](https://github.com/AlexKay28/ranger/actions/workflows/ci.yml/badge.svg)
4
+
5
+ Agent-as-a-library. Run the [opencode](https://opencode.ai) or
6
+ [codex](https://github.com/openai/codex) coding agents headlessly from plain
7
+ Python code — for agentic-loop prototyping. You write code, not chat.
8
+
9
+ The whole design follows one paradigm: **the agent is a function call —
10
+ Python owns the loop, the harness is just the data plane.** Plan → act →
11
+ check → repeat maps 1:1 onto expression, statement and condition. See
12
+ [docs/PARADIGM.md](docs/PARADIGM.md) for the seven laws every feature is
13
+ reviewed against.
14
+
15
+ AI coding agents: start with [SKILL.md](SKILL.md) for the library's safe
16
+ workflow, contracts, and repository contribution rules.
17
+
18
+ <p align="center">
19
+ <img src="https://raw.githubusercontent.com/AlexKay28/ranger/main/docs/cheatsheet.png" alt="ranger command cheatsheet" width="760">
20
+ </p>
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install rangers
26
+ ```
27
+
28
+ The distribution is named **`rangers`** on PyPI; the Python import remains
29
+ `import ranger`. For development from a clone: `pip install -e .[dev]`.
30
+ Maintainers: see the [publishing checklist](docs/PUBLISHING.md) before any
31
+ release.
32
+
33
+ Requires a harness CLI on PATH: [opencode](https://opencode.ai) (default
34
+ backend) and/or [codex](https://github.com/openai/codex). On a distro-pegged
35
+ pip (e.g. 22.x) an editable install can fail with "build backend is missing
36
+ the build_editable hook" — fix the tool, not the package:
37
+ `pip install -U pip`, then retry.
38
+
39
+ ## Quick start (no clone needed)
40
+
41
+ Straight from GitHub, then a one-line sanity check in your terminal:
42
+
43
+ ```bash
44
+ pip install git+https://github.com/AlexKay28/ranger
45
+
46
+ python -c "import ranger; print(ranger.Ranger.models(match='claude'))"
47
+ ```
48
+
49
+ The check needs no model or API budget — it just lists what your local
50
+ opencode CLI offers. Got model ids back? You're ready:
51
+
52
+ ```python
53
+ import ranger
54
+ agent = ranger.Ranger(model="<one of the ids above>")
55
+ print(agent.ask("say ok"))
56
+ ```
57
+
58
+ Runnable applications live in [examples/](examples/): quickstart, model
59
+ discovery, a full agentic loop, a self-hosted project-growth loop, plus three
60
+ small day-to-day recipes — repair a red test command, independently review a
61
+ local diff before merge, review one file, and draft release notes from git
62
+ history. They are organized by use case in the
63
+ [examples index](examples/README.md): getting started, code work, monitoring,
64
+ multi-agent, backends, and no-model dummy runs.
65
+
66
+ Live progress: each command draws a morphing line on stderr while it runs
67
+ (`⠹ search · TODO items in this repo · 00:03`) and settles as
68
+ `✓ search · 00:07 · <first line of answer>` — opencode-wizard style, colors
69
+ included. It auto-disables in pipes/tests and honors `NO_COLOR`;
70
+ turn it off with `pretty=False`.
71
+
72
+ Model priority: explicit `Ranger(model=...)` → `$RANGER_MODEL` env var →
73
+ package default.
74
+
75
+ ## Usage
76
+
77
+ ```python
78
+ agent = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5")
79
+ agent.summarize("notes.md") # -> str summary of the file
80
+ agent.write("a one-page report on X") # -> path of the file the agent wrote
81
+ agent.do("rename all *.txt files in data/ to *.md") # -> report of CLI-like work
82
+ agent.search("where do we parse config?") # -> multiline findings report
83
+ agent.ask("anything at all") # -> raw prompt, raw answer
84
+ agent.check("all tests pass") # -> YES/NO verdict as a bool
85
+ agent.gate("python -m pytest -q") # -> exit-code booleans, no model
86
+ agent.until("make the tests green", gate_cmd="python -m pytest -q")
87
+ # -> loop: plan/do/verify until green
88
+ ```
89
+
90
+ Each call is one fresh headless `opencode run` in the agent's working directory
91
+ (`cwd=`), with permissions auto-approved so it can actually touch the filesystem.
92
+ Pass `session="..."` to share context between calls, `model=` to pick any
93
+ `provider/model` ID from `opencode models`.
94
+
95
+ ## Model discovery & configuration
96
+
97
+ ```python
98
+ import ranger
99
+
100
+ ranger.Ranger.models() # -> all model IDs
101
+ ranger.Ranger.models(match="claude") # -> substring filter
102
+
103
+ smart = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5")
104
+ fast = smart.with_config(model="openrouter/google/gemini-2.5-flash", agent="build")
105
+ ```
106
+
107
+ Every command method also accepts per-call overrides:
108
+
109
+ ```python
110
+ fast.search("todo items", title="quick scan")
111
+ smart.do("refactor auth", timeout=1800)
112
+ ```
113
+
114
+ Overridable: `model`, `timeout`, `auto`, `session`, `agent` (opencode agent
115
+ name like "build"/"plan"), `title`, `thinking`. Unknown keywords raise
116
+ `TypeError` so typos fail loudly.
117
+
118
+ See [docs/commands.md](docs/commands.md) for the full command catalog —
119
+ 15 methods at three maturity tiers: what's shipped, what's next, and
120
+ what needs bigger machinery (gates, retries, safety rails) —
121
+ [docs/API.md](docs/API.md) for the complete, per-setting API reference
122
+ (kept mechanically in sync with the code by the test suite), and
123
+ [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for a high-level tour of
124
+ the codebase and its three architectural decisions.
125
+
126
+ ## Context state (memory)
127
+
128
+ The agent remembers. Every command exchange is recorded to
129
+ `agent.context` in classic OpenAI message format
130
+ (`{"role": "user"|"assistant"|"system", "content": ...}`) and recent
131
+ history (within a character budget) is recalled into later runs — so
132
+ follow-ups like "now do the same for tests/" just work.
133
+
134
+ ```python
135
+ agent.context # the message list
136
+ agent.context_summarize() # -> digest of what happened so far
137
+ agent.context_compact() # collapse history to one system digest
138
+ agent.context_clear() # wipe; returns what was cleared
139
+ agent.ask("...", memory=False) # one stateless run
140
+ ranger.Ranger(memory=False) # or a fully stateless agent
141
+ ```
142
+
143
+ Context memory colors your dialogues, never your facts: `ask`/`do`/`write`
144
+ recall the recent conversation; `check`/`search`/`summarize` always run
145
+ history-independent, so a verdict is about the repo, not the chat
146
+ ([PARADIGM.md](docs/PARADIGM.md), law 4).
147
+
148
+ ## Backends (harness adapters)
149
+
150
+ The same API drives different harnesses; the setting is one constructor
151
+ argument, per-call overridable:
152
+
153
+ ```python
154
+ import ranger
155
+
156
+ agent = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5") # opencode (default)
157
+ gpt = ranger.Ranger(backend="codex") # model from ~/.codex/config.toml
158
+ gpt = ranger.Ranger(backend="codex", model="gpt-5.1-codex")
159
+
160
+ ranger.register_backend("mine", MyHarnessAdapter) # your own — one small class
161
+ ```
162
+
163
+ Harness differences (flags, sessions, sandboxes, output schemas) are
164
+ absorbed by one adapter class per backend — see the Backends section of
165
+ [docs/API.md](docs/API.md) for the full capability map and the recipe for
166
+ adding your own harness.
167
+
168
+ ## Multi-agent interactions
169
+
170
+ Two (or three) agents, and the expected patterns as ready-made methods —
171
+ verdicts are grammar, budgets are honest, reviewers run free of their own
172
+ chat history:
173
+
174
+ ```python
175
+ both = builder.second_opinion(critic, prompt) # {mine, theirs, differs}
176
+ verdict = critic.review(subject, author=builder) # APPROVE/REJECT/REQUEST_CHANGES
177
+ outcome = builder.refine_with(critic, goal) # the two-agent until()
178
+ duel = critic.challenge(builder) # adversarial claim attack
179
+ talk = ranger.debate(builder, critic, topic, judge=skeptic)
180
+ ```
181
+
182
+ See the Multi-agent interactions section of [docs/API.md](docs/API.md)
183
+ and [`examples/multiagent/multi_agent.py`](examples/multiagent/multi_agent.py).
184
+
185
+ ## Skills allow-list
186
+
187
+ Grant the agent specific skills or operating rules per run — a markdown
188
+ file or directory rides along via opencode's `--file` attachment; plain
189
+ prose entries are injected as prompt grants.
190
+
191
+ ```python
192
+ agent = ranger.Ranger(skills=[
193
+ "skills/haiku/SKILL.md", # file -> --file attachment
194
+ "skills/pack", # dir -> all its *.md attached
195
+ 'Always answer in British spelling', # prose -> prompt grant
196
+ ])
197
+ agent.do("write the changelog", skills=["docs/changelog_style.md"]) # per call
198
+ ```
199
+
200
+ ## AGI loop, the point of the whole thing
201
+
202
+ ```python
203
+ agent = ranger.Ranger(model="openrouter/anthropic/claude-sonnet-4.5")
204
+
205
+ for i in range(10):
206
+ hits = agent.search(f"TODO and unimplemented spots, pass {i}")
207
+ plan = agent.ask(f"Pick ONE thing to fix from:\n{hits}\nReply with a one-line fix plan.")
208
+ report = agent.do(plan)
209
+ print(f"[pass {i}] {report}")
210
+ ```
rangers-0.6.1/SKILL.md ADDED
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: ranger
3
+ description: Use the ranger Python library to drive headless coding agents from ordinary Python. Load BEFORE writing a Ranger loop, agent-backed repair or review workflow, multi-agent composition, custom backend, or tests around ranger. Triggers - ranger, rangers, Ranger(), agent-as-a-library, headless coding agent in Python, ask/do/check/until/refine_with, coding-agent loop, агент как библиотека, цикл агента на Python, ранжер. Do NOT use for a hosted agent runtime, background task queue, or streaming chat server - ranger intentionally has none; Python owns the loop.
4
+ ---
5
+
6
+ # ranger — agent-as-a-library
7
+
8
+ `rangers` is the PyPI distribution; the import is `ranger`:
9
+
10
+ ```python
11
+ # shell: pip install rangers
12
+ import ranger
13
+
14
+ agent = ranger.Ranger(cwd="/path/to/project", timeout=180)
15
+ answer = agent.ask("Find the smallest safe fix for the failing parser test.")
16
+ ```
17
+
18
+ Every live Ranger call starts one headless harness CLI process and blocks until
19
+ it returns. Python, not an agent runtime, owns the control flow.
20
+
21
+ ## Non-negotiable rules
22
+
23
+ | Need | Use | Do not substitute |
24
+ |---|---|---|
25
+ | Open-ended answer or one small plan | `ask(prompt)` | A custom chat loop |
26
+ | Make a filesystem change | `do(goal)` | A claim that a change happened |
27
+ | Produce one document | `write(task, path=...)` | Parsing prose for a filename |
28
+ | Read-only codebase findings | `search(goal)` / `summarize(file)` | `do()` |
29
+ | Agent-verified fact | `check(condition)` | Truthy prose; it must parse YES/NO |
30
+ | Deterministic acceptance | `gate(shell_command)` | An agent's opinion |
31
+ | Bounded repair loop | `until(..., gate_cmd=..., max_iter=N)` | An unbounded `while` loop |
32
+ | Independent review | `critic.review(subject, author=claims)` | Trusting the author report |
33
+
34
+ **Use a mechanical gate whenever one exists.** For a repair, the shell test
35
+ command decides success:
36
+
37
+ ```python
38
+ result = agent.until(
39
+ "Make the parser tests pass; reproduce first and make the smallest fix.",
40
+ gate_cmd="python -m pytest -q tests/test_parser.py",
41
+ max_iter=2,
42
+ )
43
+ if not result["achieved"]:
44
+ raise RuntimeError(result["report"])
45
+ ```
46
+
47
+ `until` never invents success after its budget runs out. `gate()` returns only
48
+ an exit-code boolean and does not call a model.
49
+
50
+ ## Costs, cancellation, and state
51
+
52
+ - Set an explicit `timeout=` for real work and a small `max_iter`/`rounds`
53
+ budget for every loop. A loop costs one or more full CLI calls per iteration.
54
+ - Ctrl+C / notebook interrupt raises `KeyboardInterrupt`, stops the CLI client,
55
+ and retires the progress spinner. Long loops put honest partial state in the
56
+ interrupt message; nothing is claimed complete.
57
+ - `ask`, `do`, and `write` record and recall bounded `agent.context`. Treat it
58
+ as conversational color, never as a fact store.
59
+ - `check`, `search`, `summarize`, `review`, challenge judgments, and debate
60
+ judges are memory- and session-free by contract. Do not try to override that
61
+ purity with `session=`.
62
+ - A model answer is not a success signal. `check()` rejects ambiguous,
63
+ contradictory, or uncertain replies with `RangerError`.
64
+
65
+ ## Multi-agent work
66
+
67
+ Interactions are normal Python compositions, not a scheduler or swarm.
68
+ Pass another agent's report as **unverified claims** and let the receiver verify
69
+ the repository itself:
70
+
71
+ ```python
72
+ builder = ranger.Ranger(cwd=".", timeout=180)
73
+ critic = ranger.Ranger(cwd=".", timeout=180)
74
+
75
+ outcome = builder.refine_with(
76
+ critic,
77
+ "Add a regression test for empty parser input and make it pass.",
78
+ max_iter=2,
79
+ )
80
+ if not outcome["approved"]:
81
+ print(outcome["reviews"][-1]["changes"])
82
+ ```
83
+
84
+ Use the smallest fitting pattern:
85
+
86
+ - `second_opinion(other, prompt)` — compare two answers; disagreement is the
87
+ signal, not a vote.
88
+ - `review(subject, author=...)` — strict `VERDICT`, `FINDINGS`, `CHANGES`.
89
+ - `challenge(other, claim=..., rounds=1)` — adversarial evidence check.
90
+ - `refine_with(critic, goal, max_iter=2)` — do → review → revise.
91
+ - `ranger.debate(a, b, topic, judge=...)` — surface factual disagreements;
92
+ the judge digests evidence and never crowns a winner.
93
+
94
+ Do not parse model prose yourself. The interaction methods validate their
95
+ frozen grammars and raise `RangerError` on malformed or conflicting replies.
96
+
97
+ ## Backends and models
98
+
99
+ - Use `Ranger(backend="opencode")` (default) or `Ranger(backend="codex")`.
100
+ The public API stays the same; CLI flags and output parsing do not.
101
+ - Use OpenRouter-style public model IDs in repository files. Do **not** add
102
+ internal provider/model names to code, docs, examples, tests, or commits.
103
+ - For a new harness, register a small adapter with
104
+ `ranger.register_backend(name, adapter_cls)`; do not fork command logic.
105
+ - Resolve a model only when needed: explicit per-call model beats constructor,
106
+ then `RANGER_MODEL`, then the effective backend's default.
107
+
108
+ ## Contributing to this repository
109
+
110
+ - Read [docs/PARADIGM.md](docs/PARADIGM.md) before changing public behavior.
111
+ The core laws are grammar over vibes, explicit Python state, and a
112
+ replaceable harness data plane.
113
+ - Keep normal tests fully mocked. Agent calls use the
114
+ `ranger.subprocess.run` seam; `gate()` uses `ranger.subprocess.Popen`.
115
+ Do not put live opencode/codex calls in the test suite.
116
+ - Add truth-table tests for parsers, verdicts, option precedence, and loop
117
+ outcomes. The suite has a strict one-minute budget.
118
+ - Update `docs/API.md`, README, examples, and docs gates whenever public API
119
+ changes. Examples must be main-guarded and import-safe.
120
+ - Run `python -m pytest -q`; for releases also run `python -m build` and
121
+ `python -m twine check --strict dist/*`. See [docs/PUBLISHING.md](docs/PUBLISHING.md).
122
+
123
+ ## Execution-bug feedback for maintainers
124
+
125
+ If a user asks you to use ranger and you encounter a reproducible library or
126
+ harness-execution bug, help the maintainers learn from it: create an issue at
127
+ [`AlexKay28/ranger`](https://github.com/AlexKay28/ranger/issues) when GitHub
128
+ authentication and issue-creation permission are available. If they are not,
129
+ give the user the complete ready-to-paste issue instead. Do this after a
130
+ minimal reproduction attempt — not for a malformed prompt, a missing CLI, an
131
+ expired model quota, or an expected `RangerError` caused by invalid input.
132
+
133
+ **Never include secrets.** Redact API tokens, credentials, private prompts,
134
+ absolute private paths, and proprietary source. Link to public output only.
135
+
136
+ Use this issue format:
137
+
138
+ ````markdown
139
+ ## ranger execution bug
140
+
141
+ **ranger version:** `ranger.__version__`
142
+ **Python / OS:** `python --version`; operating system
143
+ **Backend / model:** `opencode|codex`; public model ID or `redacted`
144
+ **Command or minimal code:**
145
+ ```python
146
+ # smallest self-contained reproduction
147
+ ```
148
+ **Expected behavior:** what should have happened
149
+ **Actual behavior:** exact exception, exit code, or unexpected result
150
+ **Reproduction:** always / intermittent; smallest known steps
151
+ **Relevant non-secret context:** timeout, session/memory setting, and whether
152
+ the problem remains with `memory=False` and a fresh session
153
+ ````
154
+
155
+ Title issues as: `bug: <short observable failure>`. Before filing, search
156
+ existing open issues for the same error and add reproduction details there
157
+ instead of creating a duplicate. The report is feedback, not a substitute for
158
+ the user's immediate workaround: state the workaround or honest blocker too.
159
+
160
+ ## Choose an example before inventing a workflow
161
+
162
+ See [examples/README.md](examples/README.md) for runnable recipes:
163
+
164
+ - `examples/code-work/fix_failing_tests.py` — bounded repair with a real test
165
+ command as the gate.
166
+ - `examples/code-work/simple_code_review.py` — one file, one strict verdict.
167
+ - `examples/monitoring/project_health.py` — read-only health snapshot.
168
+ - `examples/multiagent/multi_agent.py` — all collaboration patterns.
169
+ - `examples/dummy/mechanical_gates.py` — no model or login required.