turno 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.
- turno-0.1.0/LICENSE +21 -0
- turno-0.1.0/MANIFEST.in +3 -0
- turno-0.1.0/PKG-INFO +224 -0
- turno-0.1.0/README.md +201 -0
- turno-0.1.0/pyproject.toml +51 -0
- turno-0.1.0/setup.cfg +4 -0
- turno-0.1.0/turno/__init__.py +3 -0
- turno-0.1.0/turno/__main__.py +5 -0
- turno-0.1.0/turno/autopilot.py +1873 -0
- turno-0.1.0/turno/backlog.py +58 -0
- turno-0.1.0/turno/claude-agents.json +14 -0
- turno-0.1.0/turno/cli.py +7 -0
- turno-0.1.0/turno/codex-agents/implementer.toml +14 -0
- turno-0.1.0/turno/codex-agents/planner.toml +25 -0
- turno-0.1.0/turno/codex-agents/reviewer.toml +15 -0
- turno-0.1.0/turno/config.py +445 -0
- turno-0.1.0/turno/control.py +649 -0
- turno-0.1.0/turno/coord.py +200 -0
- turno-0.1.0/turno/engines.py +599 -0
- turno-0.1.0/turno/feed.py +82 -0
- turno-0.1.0/turno/history.py +71 -0
- turno-0.1.0/turno/messages.py +133 -0
- turno-0.1.0/turno/onboard.py +276 -0
- turno-0.1.0/turno/overrides.py +105 -0
- turno-0.1.0/turno/panel.py +418 -0
- turno-0.1.0/turno/paths.py +25 -0
- turno-0.1.0/turno/prompts/engine-claude.md +14 -0
- turno-0.1.0/turno/prompts/engine-codex.md +5 -0
- turno-0.1.0/turno/prompts/resume.md +14 -0
- turno-0.1.0/turno/prompts/setup.md +97 -0
- turno-0.1.0/turno/prompts/unit.md +74 -0
- turno-0.1.0/turno/pypi.py +114 -0
- turno-0.1.0/turno/repo_setup.py +199 -0
- turno-0.1.0/turno/schedule.py +74 -0
- turno-0.1.0/turno/server.py +263 -0
- turno-0.1.0/turno/service.py +201 -0
- turno-0.1.0/turno/upgrade.py +132 -0
- turno-0.1.0/turno/web/app.js +1168 -0
- turno-0.1.0/turno/web/i18n/en.js +244 -0
- turno-0.1.0/turno/web/i18n/pt-BR.js +244 -0
- turno-0.1.0/turno/web/index.html +39 -0
- turno-0.1.0/turno/web/style.css +129 -0
- turno-0.1.0/turno.egg-info/PKG-INFO +224 -0
- turno-0.1.0/turno.egg-info/SOURCES.txt +45 -0
- turno-0.1.0/turno.egg-info/dependency_links.txt +1 -0
- turno-0.1.0/turno.egg-info/entry_points.txt +2 -0
- turno-0.1.0/turno.egg-info/top_level.txt +1 -0
turno-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paulo Martins
|
|
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.
|
turno-0.1.0/MANIFEST.in
ADDED
turno-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: turno
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runs the Codex CLI and Claude Code unattended on your repositories, inside your subscription quota, with a local web panel.
|
|
5
|
+
Author: Paulo Martins
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PauloFelipeM/turno
|
|
8
|
+
Project-URL: Issues, https://github.com/PauloFelipeM/turno/issues
|
|
9
|
+
Keywords: codex,claude-code,agents,scheduler,automation
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Software Development
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# turno
|
|
25
|
+
|
|
26
|
+
turno runs the Codex CLI and Claude Code unattended on several of your repositories, inside the
|
|
27
|
+
quota of your ChatGPT and Claude subscriptions. It works through each project's backlog one short
|
|
28
|
+
session at a time, following a fixed method: spec, then plan, then execution, with planner,
|
|
29
|
+
reviewer and implementer subagents. Decisions that matter reach your phone (ntfy) as a question
|
|
30
|
+
with buttons, or wait for you in the panel. A local web panel shows what is running, what happened
|
|
31
|
+
and how much of each backlog is left, and lets you control everything.
|
|
32
|
+
|
|
33
|
+
> **Status:** alpha. It runs one person's projects every day and is being generalized. The panel,
|
|
34
|
+
> the phone texts and what the agents write for you are in English or Brazilian Portuguese.
|
|
35
|
+
> Linux, macOS and Windows with WSL2.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pipx install turno # or: uv tool install turno
|
|
41
|
+
turno onboard
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`turno onboard` checks the machine (Python, git, tmux, Docker, the Codex and Claude CLIs), asks a
|
|
45
|
+
few questions (language, which engines to use, ntfy, the background service), creates `~/.turno`,
|
|
46
|
+
installs a service that starts the panel at boot, and opens the panel at http://localhost:8765.
|
|
47
|
+
Then add your projects in the panel's settings (⚙). From there on everything happens in the
|
|
48
|
+
browser: the service brings the panel up, the panel starts the enabled engines.
|
|
49
|
+
|
|
50
|
+
Scripted install: `turno onboard --language en --engines codex,claude --no-ntfy --service --yes`.
|
|
51
|
+
`turno onboard` is safe to run again: it never overwrites a file without asking.
|
|
52
|
+
|
|
53
|
+
### Requirements
|
|
54
|
+
|
|
55
|
+
- Python 3.11 or newer (turno uses only the standard library)
|
|
56
|
+
- The Codex CLI and/or Claude Code, logged in with your subscription
|
|
57
|
+
- tmux (the loops run in it, so they outlive the terminal and the panel)
|
|
58
|
+
- git; Docker if your projects need it (turno brings each project's stack up before a unit; when
|
|
59
|
+
Docker is down it opens Docker Desktop on macOS and WSL, and waits for you on Linux)
|
|
60
|
+
- Optional: the [ntfy](https://ntfy.sh) app on your phone. Without it, decisions wait in the panel
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
- **Units.** A unit is one step of the cycle: apply decision answers, recover an interrupted task,
|
|
65
|
+
run the next task of a plan, finish an item, write a plan, write a spec or propose a backlog. Each
|
|
66
|
+
unit is one short CLI session. The orchestrator ends it by writing
|
|
67
|
+
`<project>/.superpowers/autopilot/last-unit.json` (`worked`, `waiting`, `idle` or `blocked`) and
|
|
68
|
+
`backlog.json` (the progress the panel shows).
|
|
69
|
+
- **Quota.** Before a unit, turno reads the 5-hour and weekly usage windows and only starts if the
|
|
70
|
+
usage plus 1.5 times the unit's learned average cost fits under the project's cap. Projects are in
|
|
71
|
+
priority order; a project that does not fit does not hold up a cheaper one.
|
|
72
|
+
- **Nothing left half-done.** An interrupted unit (usage limit, error, end of a time window) is
|
|
73
|
+
resumed before anything else once its wait is over.
|
|
74
|
+
- **Two engines.** One loop runs Codex, another runs Claude Code (by default only on weekends). They
|
|
75
|
+
never work on the same project, or on two projects that share Docker ports, at the same time. A
|
|
76
|
+
unit cut at the end of the Claude window is handed over (the "baton") to the other engine.
|
|
77
|
+
|
|
78
|
+
Each project describes its own rules in `docs/autopilot.md`: where the backlog, specs, plans and
|
|
79
|
+
ledgers live, how to verify work and the git policy. The panel can draft that file for you (see
|
|
80
|
+
"Preparing a new project"). The protocol the orchestrator follows is in
|
|
81
|
+
[`turno/prompts/unit.md`](turno/prompts/unit.md).
|
|
82
|
+
|
|
83
|
+
## Safety model
|
|
84
|
+
|
|
85
|
+
- **Your work is not touched.** A project with uncommitted changes that are not turno's is skipped,
|
|
86
|
+
and its Docker stack is not stopped. The only thing turno itself changes in a project is one line
|
|
87
|
+
in the clone's `.git/info/exclude`; everything else is written by the agent, inside the project's
|
|
88
|
+
own rules.
|
|
89
|
+
- **Fixed rules above any message.** The agents never weaken tests, read `.env` files, force-push
|
|
90
|
+
or drop data, whatever a message says (step 7 of the protocol).
|
|
91
|
+
- **The panel is local.** It listens on 127.0.0.1 only, refuses other hosts and origins, and every
|
|
92
|
+
write needs a custom header a foreign page cannot send.
|
|
93
|
+
- **The ntfy topic is a secret.** It lives in `~/.turno/secrets.toml` (mode 600), is shown once when
|
|
94
|
+
`onboard` creates it, and never appears in the config, the logs or the panel. Anyone who knows the
|
|
95
|
+
topic can answer decisions and send messages to the agents.
|
|
96
|
+
- **No surprise installs.** The panel checks PyPI for a newer version at most once a day (a single
|
|
97
|
+
GET; `"update_check": false` turns it off) and only tells you; `turno update` upgrades when you run it.
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
Everything lives in `~/.turno` (or `$TURNO_HOME`): `config.json`, the state, logs, run files and
|
|
102
|
+
history. The panel's settings screen edits `config.json`; the loops re-read it on every turn, so a
|
|
103
|
+
change applies within one turn, without a restart. An invalid file never stops a loop: it keeps the
|
|
104
|
+
last good configuration and logs the problem.
|
|
105
|
+
|
|
106
|
+
| Part | What it holds |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `projects` | Name, path, optional `docker_group` (projects that bind the same ports) and `weekly_cap` per engine. The order is the priority |
|
|
109
|
+
| `engines.<engine>` | `enabled`, `autostart` (the service starts it), orchestrator model and effort, `roles` (planner, implementer, reviewer), `primary_cap`, `safety_factor`, initial costs, `blocked_cooldown_hours`, `schedule` (a weekly window) |
|
|
110
|
+
| `language` | `en` or `pt-BR`: the panel, the phone texts and what the agents write for you |
|
|
111
|
+
| `notify` | `enabled`, the ntfy server and the decision timeout. The topic stays in `secrets.toml` |
|
|
112
|
+
| `update_check` | `false` stops the daily PyPI check |
|
|
113
|
+
|
|
114
|
+
[`config.example.json`](config.example.json) is a complete example. A project's name never changes
|
|
115
|
+
after it is created: state, claims, notes and history use it.
|
|
116
|
+
|
|
117
|
+
## The panel
|
|
118
|
+
|
|
119
|
+
The panel shows the quota of each engine with its cap and reset time, what each loop is doing,
|
|
120
|
+
every project with its state and backlog progress, the running unit live (commands, file changes,
|
|
121
|
+
which subagent was called) and the unit history with outcome, duration and cost.
|
|
122
|
+
|
|
123
|
+
| Control | What it does |
|
|
124
|
+
|---|---|
|
|
125
|
+
| Start / Stop (per engine) | Starts the loop in tmux, or stops it like a Ctrl+C: the unit is resumed on the next start |
|
|
126
|
+
| "Needs you" | Pending decisions to answer (with an optional comment) and blocked projects to reactivate |
|
|
127
|
+
| Pause, Next, Interrupt (project card) | Skip a project; give it the next new unit; stop its running unit and pause it |
|
|
128
|
+
| Project panel | A message to the agents, the project's caps and, while it waits to retry, "Try now" |
|
|
129
|
+
| ⚙ Settings | Projects (add, edit, reorder, remove), Engines (on/off, start with the panel, models, caps, window, a quota check) and General (language, ntfy) |
|
|
130
|
+
| Footer | The running version, a notice when PyPI has a newer one, and "Restart turno" |
|
|
131
|
+
|
|
132
|
+
"Restart turno" does what `turno restart` does: the loops stop (their units resume), the service
|
|
133
|
+
restarts the panel, and the panel starts the engines again.
|
|
134
|
+
|
|
135
|
+
## Commands
|
|
136
|
+
|
|
137
|
+
| Command | What it does |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `turno onboard` | Check the machine, write `~/.turno`, install the service, open the panel |
|
|
140
|
+
| `turno serve [--port 8765] [--start-engines]` | The panel; `--start-engines` also starts the enabled engines (the service uses it) |
|
|
141
|
+
| `turno service install\|uninstall\|status` | The background service: a systemd user unit on Linux and WSL, a LaunchAgent on macOS |
|
|
142
|
+
| `turno restart` | Stop the loops, restart the service; the panel starts them again |
|
|
143
|
+
| `turno update` | `pipx upgrade turno` or `uv tool upgrade turno`, then `turno restart` |
|
|
144
|
+
| `turno start` / `turno stop` | Start or stop the loops of the enabled engines in the tmux session `autopilot` |
|
|
145
|
+
| `turno status` / `turno check` | Quota and project states / what the loop would do now (reads the quota) |
|
|
146
|
+
| `turno decisions`, `turno answer <project> <id> <option>` | Pending decisions, from the terminal |
|
|
147
|
+
| `turno reset <project>` | Put an `idle`, `blocked` or `waiting` project back to `active` |
|
|
148
|
+
| `turno test-notify` | Send a test notification |
|
|
149
|
+
| `turno --engine claude <command>` | Any command for the Claude instance (the default is `codex`) |
|
|
150
|
+
|
|
151
|
+
The service runs `turno serve --start-engines`. Its unit says `KillMode=process`, so only the panel
|
|
152
|
+
belongs to it: the loops live in tmux and stay alive when the service restarts or stops. Without systemd (WSL without it, containers) or
|
|
153
|
+
launchd, keep the panel running yourself, for example in tmux. On Linux, run
|
|
154
|
+
`loginctl enable-linger $USER` once so the service starts at boot without a login.
|
|
155
|
+
|
|
156
|
+
## Decisions and messages from the phone
|
|
157
|
+
|
|
158
|
+
When a decision is significant (user-facing behaviour, money, a data model that is hard to reverse,
|
|
159
|
+
legal matters, scope), the orchestrator writes it with two or three options and a default, and the
|
|
160
|
+
project waits. Answer with the notification buttons or by typing the letter in the topic (`B`, or
|
|
161
|
+
`my-app B` when several projects are waiting). Without an answer, the default applies at the
|
|
162
|
+
deadline. Without ntfy the question waits in the panel's "Needs you" strip, with the same deadline.
|
|
163
|
+
Any other text you type in the topic becomes a message to the agents of the projects it names (or
|
|
164
|
+
of all of them), read by the next unit.
|
|
165
|
+
|
|
166
|
+
## Preparing a new project
|
|
167
|
+
|
|
168
|
+
A repository that never ran turno needs `docs/autopilot.md` and `.superpowers/` ignored by git. In
|
|
169
|
+
⚙ Settings, a project without that file shows "Prepare with an agent": write what the project should
|
|
170
|
+
become, and a draft unit proposes `docs/autopilot.md`, an `AGENTS.md` if there is none, the initial
|
|
171
|
+
backlog and a short note for you. Review it in the project panel, ask for changes or approve; the
|
|
172
|
+
apply unit makes one commit, `docs: prepare for turno`, on the current branch (no push).
|
|
173
|
+
|
|
174
|
+
## Updating
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
turno update
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
It upgrades with the tool that installed turno and restarts. The panel tells you when a newer
|
|
181
|
+
version exists; it never installs anything by itself.
|
|
182
|
+
|
|
183
|
+
### Upgrading from a source checkout
|
|
184
|
+
|
|
185
|
+
If you ran turno from a clone (`python3 autopilot.py ...`), keep the clone and install it in
|
|
186
|
+
editable mode, so `git pull` keeps updating it. Order matters: a loop that is still running with
|
|
187
|
+
the old code reads `prompts/*.md` and `claude-agents.json` from the clone on every unit, and those
|
|
188
|
+
files move into `turno/` with the `git pull`. So stop everything first:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
cd ~/src/turno
|
|
192
|
+
python3 autopilot.py stop # still the old code: stops the loops (stop a hand-started `serve` too)
|
|
193
|
+
git pull
|
|
194
|
+
pipx install -e .
|
|
195
|
+
turno service install
|
|
196
|
+
turno restart # or: turno start
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Stop a hand-started `serve` before installing the service: port 8765 must be free.
|
|
200
|
+
|
|
201
|
+
`~/.turno` (config, state, history) is used as it is. `python3 autopilot.py ...` keeps working, and
|
|
202
|
+
`turno stop` recognises loops started either way. With an editable install `turno update` only
|
|
203
|
+
reminds you to `git pull`.
|
|
204
|
+
|
|
205
|
+
## Troubleshooting
|
|
206
|
+
|
|
207
|
+
- **The loops stopped after a reboot.** Check the service with `turno service status`; on Linux,
|
|
208
|
+
`loginctl enable-linger $USER`. Without a service, run `turno start`; units resume where they stopped.
|
|
209
|
+
- **Sleep or hibernation pauses everything.** Keep the machine awake while it should work.
|
|
210
|
+
- **A project never runs.** Uncommitted changes that are not turno's keep it out until the tree is
|
|
211
|
+
clean; `turno check` says what the loop would do and why.
|
|
212
|
+
- **Logs.** `~/.turno/autopilot.log` and `autopilot-claude.log` (the loops), `restart.log` (restarts
|
|
213
|
+
from the panel), and the `.jsonl` and `.err` files of the latest runs in `runs/` and
|
|
214
|
+
`runs-claude/`. The panel's history links each unit to its runs. With systemd,
|
|
215
|
+
`journalctl --user -u turno` has the panel's output.
|
|
216
|
+
|
|
217
|
+
## Contributing
|
|
218
|
+
|
|
219
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The invariants a change must keep are in
|
|
220
|
+
[AGENTS.md](AGENTS.md).
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT. See [LICENSE](LICENSE).
|
turno-0.1.0/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# turno
|
|
2
|
+
|
|
3
|
+
turno runs the Codex CLI and Claude Code unattended on several of your repositories, inside the
|
|
4
|
+
quota of your ChatGPT and Claude subscriptions. It works through each project's backlog one short
|
|
5
|
+
session at a time, following a fixed method: spec, then plan, then execution, with planner,
|
|
6
|
+
reviewer and implementer subagents. Decisions that matter reach your phone (ntfy) as a question
|
|
7
|
+
with buttons, or wait for you in the panel. A local web panel shows what is running, what happened
|
|
8
|
+
and how much of each backlog is left, and lets you control everything.
|
|
9
|
+
|
|
10
|
+
> **Status:** alpha. It runs one person's projects every day and is being generalized. The panel,
|
|
11
|
+
> the phone texts and what the agents write for you are in English or Brazilian Portuguese.
|
|
12
|
+
> Linux, macOS and Windows with WSL2.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pipx install turno # or: uv tool install turno
|
|
18
|
+
turno onboard
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`turno onboard` checks the machine (Python, git, tmux, Docker, the Codex and Claude CLIs), asks a
|
|
22
|
+
few questions (language, which engines to use, ntfy, the background service), creates `~/.turno`,
|
|
23
|
+
installs a service that starts the panel at boot, and opens the panel at http://localhost:8765.
|
|
24
|
+
Then add your projects in the panel's settings (⚙). From there on everything happens in the
|
|
25
|
+
browser: the service brings the panel up, the panel starts the enabled engines.
|
|
26
|
+
|
|
27
|
+
Scripted install: `turno onboard --language en --engines codex,claude --no-ntfy --service --yes`.
|
|
28
|
+
`turno onboard` is safe to run again: it never overwrites a file without asking.
|
|
29
|
+
|
|
30
|
+
### Requirements
|
|
31
|
+
|
|
32
|
+
- Python 3.11 or newer (turno uses only the standard library)
|
|
33
|
+
- The Codex CLI and/or Claude Code, logged in with your subscription
|
|
34
|
+
- tmux (the loops run in it, so they outlive the terminal and the panel)
|
|
35
|
+
- git; Docker if your projects need it (turno brings each project's stack up before a unit; when
|
|
36
|
+
Docker is down it opens Docker Desktop on macOS and WSL, and waits for you on Linux)
|
|
37
|
+
- Optional: the [ntfy](https://ntfy.sh) app on your phone. Without it, decisions wait in the panel
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
- **Units.** A unit is one step of the cycle: apply decision answers, recover an interrupted task,
|
|
42
|
+
run the next task of a plan, finish an item, write a plan, write a spec or propose a backlog. Each
|
|
43
|
+
unit is one short CLI session. The orchestrator ends it by writing
|
|
44
|
+
`<project>/.superpowers/autopilot/last-unit.json` (`worked`, `waiting`, `idle` or `blocked`) and
|
|
45
|
+
`backlog.json` (the progress the panel shows).
|
|
46
|
+
- **Quota.** Before a unit, turno reads the 5-hour and weekly usage windows and only starts if the
|
|
47
|
+
usage plus 1.5 times the unit's learned average cost fits under the project's cap. Projects are in
|
|
48
|
+
priority order; a project that does not fit does not hold up a cheaper one.
|
|
49
|
+
- **Nothing left half-done.** An interrupted unit (usage limit, error, end of a time window) is
|
|
50
|
+
resumed before anything else once its wait is over.
|
|
51
|
+
- **Two engines.** One loop runs Codex, another runs Claude Code (by default only on weekends). They
|
|
52
|
+
never work on the same project, or on two projects that share Docker ports, at the same time. A
|
|
53
|
+
unit cut at the end of the Claude window is handed over (the "baton") to the other engine.
|
|
54
|
+
|
|
55
|
+
Each project describes its own rules in `docs/autopilot.md`: where the backlog, specs, plans and
|
|
56
|
+
ledgers live, how to verify work and the git policy. The panel can draft that file for you (see
|
|
57
|
+
"Preparing a new project"). The protocol the orchestrator follows is in
|
|
58
|
+
[`turno/prompts/unit.md`](turno/prompts/unit.md).
|
|
59
|
+
|
|
60
|
+
## Safety model
|
|
61
|
+
|
|
62
|
+
- **Your work is not touched.** A project with uncommitted changes that are not turno's is skipped,
|
|
63
|
+
and its Docker stack is not stopped. The only thing turno itself changes in a project is one line
|
|
64
|
+
in the clone's `.git/info/exclude`; everything else is written by the agent, inside the project's
|
|
65
|
+
own rules.
|
|
66
|
+
- **Fixed rules above any message.** The agents never weaken tests, read `.env` files, force-push
|
|
67
|
+
or drop data, whatever a message says (step 7 of the protocol).
|
|
68
|
+
- **The panel is local.** It listens on 127.0.0.1 only, refuses other hosts and origins, and every
|
|
69
|
+
write needs a custom header a foreign page cannot send.
|
|
70
|
+
- **The ntfy topic is a secret.** It lives in `~/.turno/secrets.toml` (mode 600), is shown once when
|
|
71
|
+
`onboard` creates it, and never appears in the config, the logs or the panel. Anyone who knows the
|
|
72
|
+
topic can answer decisions and send messages to the agents.
|
|
73
|
+
- **No surprise installs.** The panel checks PyPI for a newer version at most once a day (a single
|
|
74
|
+
GET; `"update_check": false` turns it off) and only tells you; `turno update` upgrades when you run it.
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
Everything lives in `~/.turno` (or `$TURNO_HOME`): `config.json`, the state, logs, run files and
|
|
79
|
+
history. The panel's settings screen edits `config.json`; the loops re-read it on every turn, so a
|
|
80
|
+
change applies within one turn, without a restart. An invalid file never stops a loop: it keeps the
|
|
81
|
+
last good configuration and logs the problem.
|
|
82
|
+
|
|
83
|
+
| Part | What it holds |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `projects` | Name, path, optional `docker_group` (projects that bind the same ports) and `weekly_cap` per engine. The order is the priority |
|
|
86
|
+
| `engines.<engine>` | `enabled`, `autostart` (the service starts it), orchestrator model and effort, `roles` (planner, implementer, reviewer), `primary_cap`, `safety_factor`, initial costs, `blocked_cooldown_hours`, `schedule` (a weekly window) |
|
|
87
|
+
| `language` | `en` or `pt-BR`: the panel, the phone texts and what the agents write for you |
|
|
88
|
+
| `notify` | `enabled`, the ntfy server and the decision timeout. The topic stays in `secrets.toml` |
|
|
89
|
+
| `update_check` | `false` stops the daily PyPI check |
|
|
90
|
+
|
|
91
|
+
[`config.example.json`](config.example.json) is a complete example. A project's name never changes
|
|
92
|
+
after it is created: state, claims, notes and history use it.
|
|
93
|
+
|
|
94
|
+
## The panel
|
|
95
|
+
|
|
96
|
+
The panel shows the quota of each engine with its cap and reset time, what each loop is doing,
|
|
97
|
+
every project with its state and backlog progress, the running unit live (commands, file changes,
|
|
98
|
+
which subagent was called) and the unit history with outcome, duration and cost.
|
|
99
|
+
|
|
100
|
+
| Control | What it does |
|
|
101
|
+
|---|---|
|
|
102
|
+
| Start / Stop (per engine) | Starts the loop in tmux, or stops it like a Ctrl+C: the unit is resumed on the next start |
|
|
103
|
+
| "Needs you" | Pending decisions to answer (with an optional comment) and blocked projects to reactivate |
|
|
104
|
+
| Pause, Next, Interrupt (project card) | Skip a project; give it the next new unit; stop its running unit and pause it |
|
|
105
|
+
| Project panel | A message to the agents, the project's caps and, while it waits to retry, "Try now" |
|
|
106
|
+
| ⚙ Settings | Projects (add, edit, reorder, remove), Engines (on/off, start with the panel, models, caps, window, a quota check) and General (language, ntfy) |
|
|
107
|
+
| Footer | The running version, a notice when PyPI has a newer one, and "Restart turno" |
|
|
108
|
+
|
|
109
|
+
"Restart turno" does what `turno restart` does: the loops stop (their units resume), the service
|
|
110
|
+
restarts the panel, and the panel starts the engines again.
|
|
111
|
+
|
|
112
|
+
## Commands
|
|
113
|
+
|
|
114
|
+
| Command | What it does |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `turno onboard` | Check the machine, write `~/.turno`, install the service, open the panel |
|
|
117
|
+
| `turno serve [--port 8765] [--start-engines]` | The panel; `--start-engines` also starts the enabled engines (the service uses it) |
|
|
118
|
+
| `turno service install\|uninstall\|status` | The background service: a systemd user unit on Linux and WSL, a LaunchAgent on macOS |
|
|
119
|
+
| `turno restart` | Stop the loops, restart the service; the panel starts them again |
|
|
120
|
+
| `turno update` | `pipx upgrade turno` or `uv tool upgrade turno`, then `turno restart` |
|
|
121
|
+
| `turno start` / `turno stop` | Start or stop the loops of the enabled engines in the tmux session `autopilot` |
|
|
122
|
+
| `turno status` / `turno check` | Quota and project states / what the loop would do now (reads the quota) |
|
|
123
|
+
| `turno decisions`, `turno answer <project> <id> <option>` | Pending decisions, from the terminal |
|
|
124
|
+
| `turno reset <project>` | Put an `idle`, `blocked` or `waiting` project back to `active` |
|
|
125
|
+
| `turno test-notify` | Send a test notification |
|
|
126
|
+
| `turno --engine claude <command>` | Any command for the Claude instance (the default is `codex`) |
|
|
127
|
+
|
|
128
|
+
The service runs `turno serve --start-engines`. Its unit says `KillMode=process`, so only the panel
|
|
129
|
+
belongs to it: the loops live in tmux and stay alive when the service restarts or stops. Without systemd (WSL without it, containers) or
|
|
130
|
+
launchd, keep the panel running yourself, for example in tmux. On Linux, run
|
|
131
|
+
`loginctl enable-linger $USER` once so the service starts at boot without a login.
|
|
132
|
+
|
|
133
|
+
## Decisions and messages from the phone
|
|
134
|
+
|
|
135
|
+
When a decision is significant (user-facing behaviour, money, a data model that is hard to reverse,
|
|
136
|
+
legal matters, scope), the orchestrator writes it with two or three options and a default, and the
|
|
137
|
+
project waits. Answer with the notification buttons or by typing the letter in the topic (`B`, or
|
|
138
|
+
`my-app B` when several projects are waiting). Without an answer, the default applies at the
|
|
139
|
+
deadline. Without ntfy the question waits in the panel's "Needs you" strip, with the same deadline.
|
|
140
|
+
Any other text you type in the topic becomes a message to the agents of the projects it names (or
|
|
141
|
+
of all of them), read by the next unit.
|
|
142
|
+
|
|
143
|
+
## Preparing a new project
|
|
144
|
+
|
|
145
|
+
A repository that never ran turno needs `docs/autopilot.md` and `.superpowers/` ignored by git. In
|
|
146
|
+
⚙ Settings, a project without that file shows "Prepare with an agent": write what the project should
|
|
147
|
+
become, and a draft unit proposes `docs/autopilot.md`, an `AGENTS.md` if there is none, the initial
|
|
148
|
+
backlog and a short note for you. Review it in the project panel, ask for changes or approve; the
|
|
149
|
+
apply unit makes one commit, `docs: prepare for turno`, on the current branch (no push).
|
|
150
|
+
|
|
151
|
+
## Updating
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
turno update
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
It upgrades with the tool that installed turno and restarts. The panel tells you when a newer
|
|
158
|
+
version exists; it never installs anything by itself.
|
|
159
|
+
|
|
160
|
+
### Upgrading from a source checkout
|
|
161
|
+
|
|
162
|
+
If you ran turno from a clone (`python3 autopilot.py ...`), keep the clone and install it in
|
|
163
|
+
editable mode, so `git pull` keeps updating it. Order matters: a loop that is still running with
|
|
164
|
+
the old code reads `prompts/*.md` and `claude-agents.json` from the clone on every unit, and those
|
|
165
|
+
files move into `turno/` with the `git pull`. So stop everything first:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
cd ~/src/turno
|
|
169
|
+
python3 autopilot.py stop # still the old code: stops the loops (stop a hand-started `serve` too)
|
|
170
|
+
git pull
|
|
171
|
+
pipx install -e .
|
|
172
|
+
turno service install
|
|
173
|
+
turno restart # or: turno start
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Stop a hand-started `serve` before installing the service: port 8765 must be free.
|
|
177
|
+
|
|
178
|
+
`~/.turno` (config, state, history) is used as it is. `python3 autopilot.py ...` keeps working, and
|
|
179
|
+
`turno stop` recognises loops started either way. With an editable install `turno update` only
|
|
180
|
+
reminds you to `git pull`.
|
|
181
|
+
|
|
182
|
+
## Troubleshooting
|
|
183
|
+
|
|
184
|
+
- **The loops stopped after a reboot.** Check the service with `turno service status`; on Linux,
|
|
185
|
+
`loginctl enable-linger $USER`. Without a service, run `turno start`; units resume where they stopped.
|
|
186
|
+
- **Sleep or hibernation pauses everything.** Keep the machine awake while it should work.
|
|
187
|
+
- **A project never runs.** Uncommitted changes that are not turno's keep it out until the tree is
|
|
188
|
+
clean; `turno check` says what the loop would do and why.
|
|
189
|
+
- **Logs.** `~/.turno/autopilot.log` and `autopilot-claude.log` (the loops), `restart.log` (restarts
|
|
190
|
+
from the panel), and the `.jsonl` and `.err` files of the latest runs in `runs/` and
|
|
191
|
+
`runs-claude/`. The panel's history links each unit to its runs. With systemd,
|
|
192
|
+
`journalctl --user -u turno` has the panel's output.
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The invariants a change must keep are in
|
|
197
|
+
[AGENTS.md](AGENTS.md).
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "turno"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Runs the Codex CLI and Claude Code unattended on your repositories, inside your subscription quota, with a local web panel."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
authors = [{ name = "Paulo Martins" }]
|
|
14
|
+
keywords = ["codex", "claude-code", "agents", "scheduler", "automation"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Environment :: Web Environment",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Topic :: Software Development",
|
|
25
|
+
]
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/PauloFelipeM/turno"
|
|
30
|
+
Issues = "https://github.com/PauloFelipeM/turno/issues"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
turno = "turno.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools]
|
|
36
|
+
# The data folders have no __init__.py; they are listed so setuptools does not warn that they are absent.
|
|
37
|
+
packages = ["turno", "turno.prompts", "turno.web", "turno.web.i18n"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.dynamic]
|
|
40
|
+
version = { attr = "turno.__version__" }
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.package-data]
|
|
43
|
+
turno = [
|
|
44
|
+
"prompts/*.md",
|
|
45
|
+
"claude-agents.json",
|
|
46
|
+
"codex-agents/*.toml",
|
|
47
|
+
"web/*.html",
|
|
48
|
+
"web/*.js",
|
|
49
|
+
"web/*.css",
|
|
50
|
+
"web/i18n/*.js",
|
|
51
|
+
]
|
turno-0.1.0/setup.cfg
ADDED