hobkin 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.
hobkin-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frank Lynam
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.
hobkin-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: hobkin
3
+ Version: 0.1.0
4
+ Summary: Unattended AI agents you run, talk to and inspect through plain files: one process each, no ports, one stdlib-only file.
5
+ Keywords: agent,llm,ai,unattended,autonomous,cli,claude,openai
6
+ Author: Frank Lynam
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-Expression: MIT
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: POSIX
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: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development
24
+ License-File: LICENSE
25
+ Project-URL: Homepage, https://github.com/frank-lynam/hobkin
26
+ Project-URL: Issues, https://github.com/frank-lynam/hobkin/issues
27
+
28
+ # hobkin
29
+
30
+ A small unattended agent: one process per agent, run through plain files. A
31
+ hobkin is a little hob, the house spirit that does the chores while the house
32
+ sleeps.
33
+
34
+ - **One file, standard library only.** `hobkin.py`, Python 3.12+. Nothing to install.
35
+ - **No ports.** It listens on nothing; it only makes outgoing calls to the model API.
36
+ - **Files are the interface.** Talk to it with `echo`, watch it with `tail -f`,
37
+ and see exactly what the model sees with `cat`.
38
+ - **Many per account.** Each agent has its own id and folder, and a lock stops two
39
+ processes running the same one.
40
+
41
+ ## Install
42
+
43
+ ```sh
44
+ pipx install hobkin # or: uv tool install hobkin
45
+ ```
46
+
47
+ That gives you the `hobkin` command. Python 3.9 or newer on Linux (or another
48
+ POSIX system); no dependencies. You can also skip installing and run the file
49
+ directly: `python3 hobkin.py`.
50
+
51
+ ## Quick start
52
+
53
+ ```sh
54
+ hobkin # a new agent: prints its id, e.g. 7f3a91c2
55
+ hobkin 7f3a91c2 # resume that agent later (its context is kept)
56
+ ```
57
+
58
+ The first run creates `~/.hobkins/_shared/config.json`. Set your model there (see
59
+ **Models**) and make sure the API key is reachable.
60
+
61
+ Then, from another terminal, chat with it:
62
+
63
+ ```sh
64
+ hobkin chat 7f3a91c2
65
+ ```
66
+
67
+ Its output appears as it comes, and each line you type (or paste) is sent;
68
+ `/stop` and the other commands work, and Ctrl-D leaves. Piped, it sends the
69
+ input, prints the reply and exits: `echo "status?" | hobkin chat 7f3a91c2`.
70
+
71
+ Or with plain files:
72
+
73
+ ```sh
74
+ echo "Clone https://example.com/repo and get its tests passing" > ~/.hobkins/7f3a91c2/in
75
+ tail -f ~/.hobkins/7f3a91c2/out
76
+ ```
77
+
78
+ Run it in the background however you like: `nohup hobkin 7f3a91c2 &`,
79
+ tmux, or a systemd user service.
80
+
81
+ ## An agent's folder
82
+
83
+ `~/.hobkins/ID/` (or `$HOBKIN_HOME/ID/`):
84
+
85
+ | File | What it is |
86
+ |---|---|
87
+ | `in` | A named pipe. `echo text > in` sends a message. A write waits until the agent is running; `hobkin send ID text` queues it instead. |
88
+ | `out` | Everything it says and does, with timestamps: messages, commands, output. `tail -f out` |
89
+ | `context.jsonl` | Exactly what the model is sent: line 1 is the system prompt, then one message per line. Rewritten after every change. |
90
+ | `MEMORY.md` | The agent's own notes. Part of its instructions on every turn, and kept when the conversation is compacted. |
91
+ | `state.json` | Model, context window (and where that number came from), status, pid, compaction count. |
92
+ | `work/` | Its working directory. |
93
+ | `shell.sh` | Its shell's saved state (folder, variables, functions, aliases), restored before each command. Secrets are never saved here. |
94
+ | `secrets.env` | Optional `KEY=value` lines, set as environment variables in its shell (never put in its context). |
95
+ | `config.json` | Optional per-agent settings over the shared ones. |
96
+ | `*.md` | Optional per-agent instructions; replace shared files of the same name. |
97
+ | `archive/` | Old contexts, kept when it compacts. |
98
+ | `queue`, `lock` | Messages sent while it was stopped; the process lock. |
99
+
100
+ `~/.hobkins/_shared/` holds what all agents share: `config.json`, `AGENTS.md` (how
101
+ to work as a hobkin: a task routine with checklists, failure handling,
102
+ organization, memory and tidying up; created on first run, edit freely. Once
103
+ edited it's never touched; an untouched copy of an older default is upgraded), an optional `SOUL.md`,
104
+ any other `.md` instructions, and `secrets.env`.
105
+
106
+ **The system prompt** is:
107
+
108
+ 1. `SOUL.md`, then `AGENTS.md`, then the other `.md` files in name order;
109
+ 2. the list of skills (see below);
110
+ 3. `MEMORY.md`.
111
+
112
+ Each `.md` file is taken from the agent's folder if it has one, otherwise from
113
+ `_shared/`. The prompt is rebuilt every turn, so edits take effect on the next one.
114
+
115
+ ## Skills
116
+
117
+ Skills use the same format as Claude Code and other agents: a folder holding a
118
+ `SKILL.md` with a `name` and `description` in its front matter, plus any scripts
119
+ or reference files. Install one by copying its folder into
120
+ **`~/.hobkins/_shared/skills/`**, and every agent can use it.
121
+
122
+ ```
123
+ ~/.hobkins/_shared/skills/agent-tickets/SKILL.md
124
+ ~/.hobkins/_shared/skills/release/SKILL.md
125
+ ~/.hobkins/_shared/skills/release/build.sh
126
+ ```
127
+
128
+ The prompt lists only each skill's name and description. When a task calls for
129
+ one, the agent opens it with the `skill` tool. That gives it the instructions,
130
+ the skill's folder as a full path, and its list of files, so it never has to
131
+ work out where scripts live. Dozens of skills cost a line each.
132
+
133
+ - An agent's own `skills/` folder can add skills, or replace a shared one with the
134
+ same name.
135
+ - `skill_dirs` in config adds more folders at the lowest priority, e.g.
136
+ `["~/.claude/skills"]` to reuse skills you already have.
137
+ - New skills are picked up on the next turn; no restart needed.
138
+
139
+ ## Talking to it
140
+
141
+ Anything you write to `in` is a message. If it's busy running a command, the
142
+ message is added to the result of that command, marked "[message from
143
+ operator]". A line that is just one of these is a command instead:
144
+
145
+ | Command | Effect |
146
+ |---|---|
147
+ | `/stop` | Finish cleanly and exit (Ctrl-C and SIGTERM do the same). |
148
+ | `/pause`, `/resume` | Stop and restart work; messages wait until `/resume`. |
149
+ | `/interrupt` | Kill the command it's running now (it's told it was interrupted). |
150
+ | `/compact` | Summarise and clear the conversation now. |
151
+ | `/status` | Write its status to `out`. |
152
+
153
+ `hobkin chat ID` puts both in one terminal (see Quick start). `hobkin list` shows all agents, their status and their last line of output.
154
+
155
+ ## Models
156
+
157
+ `config.json` names the model to use and describes each one:
158
+
159
+ ```json
160
+ {
161
+ "model": "claude",
162
+ "models": {
163
+ "claude": {"api": "anthropic", "base_url": "https://api.anthropic.com",
164
+ "model": "claude-sonnet-5-5", "key_env": "ANTHROPIC_API_KEY", "key_file": "~/.anthropic.key"},
165
+ "openrouter": {"api": "openai", "base_url": "https://openrouter.ai/api/v1",
166
+ "model": "anthropic/claude-sonnet-5.5", "key_file": "~/openrouter.key"},
167
+ "local": {"api": "openai", "base_url": "http://127.0.0.1:8080/v1", "model": "local-model"}
168
+ }
169
+ }
170
+ ```
171
+
172
+ - `api`: `anthropic` (the Messages API) or `openai` (Chat Completions: OpenAI,
173
+ OpenRouter, vLLM, llama.cpp, Ollama, ...).
174
+ - The key comes from the environment variable `key_env`, or else from the file
175
+ `key_file`. It's removed from the agent's shell environment.
176
+ - `headers` (optional): extra HTTP headers.
177
+ - `requests_per_minute` (optional): a cap on calls to this model. It's shared by
178
+ every agent using the same API, model and key, because that's what the
179
+ provider counts. The agents coordinate through a small lock file in
180
+ `_shared/` (holding a hash of the key, never the key). An agent at the cap
181
+ waits, and still answers commands such as `/stop` while it does.
182
+
183
+ **The context window is asked for, not hard-coded.** At start-up hobkin asks the API:
184
+
185
+ - Anthropic: `GET /v1/models/{model}` (`max_input_tokens`, `max_tokens`).
186
+ - OpenAI-style: the `/models` list (`context_length` as on OpenRouter,
187
+ `max_model_len` as on vLLM, and similar).
188
+ - llama.cpp's `/props` and Ollama's `/api/show`.
189
+
190
+ `context_tokens` on a model is optional: set it and that number is used as
191
+ given; leave it out (or `null`) and it's detected. If the API doesn't say,
192
+ set it. Failing that, hobkin
193
+ learns the limit from the first "context too long" error and remembers it.
194
+ `state.json` records where the number came from.
195
+
196
+ When the conversation passes `compact_at` (80%) of the window, the agent writes
197
+ itself a handoff note. The conversation is replaced by that note, with the
198
+ newest unanswered message carried over unchanged, and the old context goes to
199
+ `archive/`.
200
+
201
+ Other settings (shared or per agent): `bash_timeout` (600 s per command),
202
+ `output_limit` (30,000 characters of command output, head and tail kept),
203
+ `max_output_tokens` (16,000, capped at what the API reports), and
204
+ `idle_prompt` / `idle_seconds` (see below).
205
+
206
+ ## Its tools
207
+
208
+ - **`bash`** runs as your user, starting in `work/`. It behaves like one shell
209
+ that stays open:
210
+ - The first command runs as a login shell (your profile's PATH and so on).
211
+ - After every command, its state is saved to `shell.sh`: the folder,
212
+ variables, exports, functions and aliases. The next command starts from that
213
+ state. So `cd`, `export` and `source .venv/bin/activate` carry over, even
214
+ across restarts.
215
+ - Each command is still its own process, so a hung or crashed command can't
216
+ take the shell's state with it. Shell options like `set -e` don't carry over.
217
+ - `reset: true` starts a fresh login shell in `work/`.
218
+ - Every result ends with the folder it's now in.
219
+ - The environment has `secrets.env` plus `HOBKIN_ID`, `HOBKIN_DIR`,
220
+ `HOBKIN_WORK` and `HOBKIN_MEMORY`.
221
+ - **`read_file`** shows a file with line numbers (in pages), or lists a folder.
222
+ - **`write_file`** writes a whole file. **`edit_file`** replaces one exact piece
223
+ of text. The text goes in as is, with no shell quoting, which is where smaller
224
+ models most often go wrong.
225
+ - Errors are written to help: "appears 2 times (lines 3, 5)", or the closest
226
+ matching lines when the text isn't found.
227
+ - Line numbers pasted from `read_file` are stripped automatically, and
228
+ Windows line endings are handled.
229
+ - Paths are relative to the shell's folder; `$HOBKIN_WORK`, `$HOBKIN_MEMORY`
230
+ and `~` work.
231
+ - **`skill`** opens a skill by name.
232
+
233
+ **Nothing asks for approval:** an agent can do anything your account can. Agents
234
+ in one account stay apart by convention (their own folders), not by enforcement.
235
+ Use a separate account or container when that matters.
236
+
237
+ ## Waking up when idle
238
+
239
+ When it has nothing to do, it waits for a message. With `idle_prompt` set, it
240
+ also gets that message after `idle_seconds` of quiet. For example,
241
+ `"Check for new work."` every 300 seconds.
242
+
243
+ ## As an agent-tickets worker
244
+
245
+ Nothing in hobkin knows about agent-tickets; you give it the instructions and
246
+ the token:
247
+
248
+ ```sh
249
+ hobkin # note the id, then /stop it (Ctrl-C)
250
+ ID=7f3a91c2 URL=http://127.0.0.1:43917 TOKEN=the-token-for-this-agent
251
+ printf 'AGENT_TICKETS_URL=%s\nAGENT_TICKETS_TOKEN=%s\n' "$URL" "$TOKEN" > ~/.hobkins/$ID/secrets.env
252
+ chmod 600 ~/.hobkins/$ID/secrets.env
253
+ curl -s -H "Authorization: Bearer $TOKEN" "$URL/api/skill" --create-dirs \
254
+ -o ~/.hobkins/_shared/skills/agent-tickets/SKILL.md
255
+ echo '{"idle_prompt": "Run the agent-tickets wait loop (GET /api/wait) and do what it says.", "idle_seconds": 60}' \
256
+ > ~/.hobkins/$ID/config.json
257
+ hobkin $ID
258
+ ```
259
+
260
+ The agent-tickets skill is shared: it reads the token from
261
+ `AGENT_TICKETS_TOKEN`, so one copy serves every worker, each with its own
262
+ `secrets.env`. The idle prompt keeps it
263
+ checking for work, and it can also hold `/api/wait` open itself inside a
264
+ command. Give each worker its own token, so claims and comments show which
265
+ agent did what.
266
+
267
+ ## Releasing
268
+
269
+ Releases go to PyPI from GitHub Actions with Trusted Publishing (no token stored):
270
+
271
+ 1. Bump `__version__` in `hobkin.py`, commit and push.
272
+ 2. Publish a GitHub release tagged `vX.Y.Z` (matching `__version__`). The
273
+ workflow runs the tests on Python 3.9, 3.12 and 3.14, builds, checks the tag
274
+ matches, and uploads to PyPI.
275
+
276
+ Running the workflow by hand (Actions → Publish → Run workflow) uploads to
277
+ TestPyPI instead, for a dry run.
278
+
279
+ ## Tests
280
+
281
+ ```sh
282
+ python3 tests/test_hobkin.py
283
+ ```
284
+
285
+ These start real hobkin processes against `tests/fake_api.py`: a local
286
+ stand-in for both APIs that rejects malformed conversations. So the tests cost
287
+ nothing and don't need a key. They cover both API styles:
288
+
289
+ - commands, secrets, and hiding the model key from the shell;
290
+ - live and queued messages, the lock, `/interrupt`, messages arriving
291
+ mid-command;
292
+ - stopping and resuming, and recovery after a crash mid-command;
293
+ - compaction, learning the window from an error, and the newest request
294
+ surviving compaction;
295
+ - the shell's saved state (variables, arrays, functions, aliases, PATH, folder,
296
+ login profile, `reset`), with secrets never saved;
297
+ - the file tools (exact text with quotes, `$` and backslashes; repeated and
298
+ missing matches; pasted line numbers; paging) and the skill tool;
299
+ - skills (listing, which copy wins, front matter);
300
+ - `requests_per_minute` shared across two agents.
301
+
hobkin-0.1.0/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # hobkin
2
+
3
+ A small unattended agent: one process per agent, run through plain files. A
4
+ hobkin is a little hob, the house spirit that does the chores while the house
5
+ sleeps.
6
+
7
+ - **One file, standard library only.** `hobkin.py`, Python 3.12+. Nothing to install.
8
+ - **No ports.** It listens on nothing; it only makes outgoing calls to the model API.
9
+ - **Files are the interface.** Talk to it with `echo`, watch it with `tail -f`,
10
+ and see exactly what the model sees with `cat`.
11
+ - **Many per account.** Each agent has its own id and folder, and a lock stops two
12
+ processes running the same one.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ pipx install hobkin # or: uv tool install hobkin
18
+ ```
19
+
20
+ That gives you the `hobkin` command. Python 3.9 or newer on Linux (or another
21
+ POSIX system); no dependencies. You can also skip installing and run the file
22
+ directly: `python3 hobkin.py`.
23
+
24
+ ## Quick start
25
+
26
+ ```sh
27
+ hobkin # a new agent: prints its id, e.g. 7f3a91c2
28
+ hobkin 7f3a91c2 # resume that agent later (its context is kept)
29
+ ```
30
+
31
+ The first run creates `~/.hobkins/_shared/config.json`. Set your model there (see
32
+ **Models**) and make sure the API key is reachable.
33
+
34
+ Then, from another terminal, chat with it:
35
+
36
+ ```sh
37
+ hobkin chat 7f3a91c2
38
+ ```
39
+
40
+ Its output appears as it comes, and each line you type (or paste) is sent;
41
+ `/stop` and the other commands work, and Ctrl-D leaves. Piped, it sends the
42
+ input, prints the reply and exits: `echo "status?" | hobkin chat 7f3a91c2`.
43
+
44
+ Or with plain files:
45
+
46
+ ```sh
47
+ echo "Clone https://example.com/repo and get its tests passing" > ~/.hobkins/7f3a91c2/in
48
+ tail -f ~/.hobkins/7f3a91c2/out
49
+ ```
50
+
51
+ Run it in the background however you like: `nohup hobkin 7f3a91c2 &`,
52
+ tmux, or a systemd user service.
53
+
54
+ ## An agent's folder
55
+
56
+ `~/.hobkins/ID/` (or `$HOBKIN_HOME/ID/`):
57
+
58
+ | File | What it is |
59
+ |---|---|
60
+ | `in` | A named pipe. `echo text > in` sends a message. A write waits until the agent is running; `hobkin send ID text` queues it instead. |
61
+ | `out` | Everything it says and does, with timestamps: messages, commands, output. `tail -f out` |
62
+ | `context.jsonl` | Exactly what the model is sent: line 1 is the system prompt, then one message per line. Rewritten after every change. |
63
+ | `MEMORY.md` | The agent's own notes. Part of its instructions on every turn, and kept when the conversation is compacted. |
64
+ | `state.json` | Model, context window (and where that number came from), status, pid, compaction count. |
65
+ | `work/` | Its working directory. |
66
+ | `shell.sh` | Its shell's saved state (folder, variables, functions, aliases), restored before each command. Secrets are never saved here. |
67
+ | `secrets.env` | Optional `KEY=value` lines, set as environment variables in its shell (never put in its context). |
68
+ | `config.json` | Optional per-agent settings over the shared ones. |
69
+ | `*.md` | Optional per-agent instructions; replace shared files of the same name. |
70
+ | `archive/` | Old contexts, kept when it compacts. |
71
+ | `queue`, `lock` | Messages sent while it was stopped; the process lock. |
72
+
73
+ `~/.hobkins/_shared/` holds what all agents share: `config.json`, `AGENTS.md` (how
74
+ to work as a hobkin: a task routine with checklists, failure handling,
75
+ organization, memory and tidying up; created on first run, edit freely. Once
76
+ edited it's never touched; an untouched copy of an older default is upgraded), an optional `SOUL.md`,
77
+ any other `.md` instructions, and `secrets.env`.
78
+
79
+ **The system prompt** is:
80
+
81
+ 1. `SOUL.md`, then `AGENTS.md`, then the other `.md` files in name order;
82
+ 2. the list of skills (see below);
83
+ 3. `MEMORY.md`.
84
+
85
+ Each `.md` file is taken from the agent's folder if it has one, otherwise from
86
+ `_shared/`. The prompt is rebuilt every turn, so edits take effect on the next one.
87
+
88
+ ## Skills
89
+
90
+ Skills use the same format as Claude Code and other agents: a folder holding a
91
+ `SKILL.md` with a `name` and `description` in its front matter, plus any scripts
92
+ or reference files. Install one by copying its folder into
93
+ **`~/.hobkins/_shared/skills/`**, and every agent can use it.
94
+
95
+ ```
96
+ ~/.hobkins/_shared/skills/agent-tickets/SKILL.md
97
+ ~/.hobkins/_shared/skills/release/SKILL.md
98
+ ~/.hobkins/_shared/skills/release/build.sh
99
+ ```
100
+
101
+ The prompt lists only each skill's name and description. When a task calls for
102
+ one, the agent opens it with the `skill` tool. That gives it the instructions,
103
+ the skill's folder as a full path, and its list of files, so it never has to
104
+ work out where scripts live. Dozens of skills cost a line each.
105
+
106
+ - An agent's own `skills/` folder can add skills, or replace a shared one with the
107
+ same name.
108
+ - `skill_dirs` in config adds more folders at the lowest priority, e.g.
109
+ `["~/.claude/skills"]` to reuse skills you already have.
110
+ - New skills are picked up on the next turn; no restart needed.
111
+
112
+ ## Talking to it
113
+
114
+ Anything you write to `in` is a message. If it's busy running a command, the
115
+ message is added to the result of that command, marked "[message from
116
+ operator]". A line that is just one of these is a command instead:
117
+
118
+ | Command | Effect |
119
+ |---|---|
120
+ | `/stop` | Finish cleanly and exit (Ctrl-C and SIGTERM do the same). |
121
+ | `/pause`, `/resume` | Stop and restart work; messages wait until `/resume`. |
122
+ | `/interrupt` | Kill the command it's running now (it's told it was interrupted). |
123
+ | `/compact` | Summarise and clear the conversation now. |
124
+ | `/status` | Write its status to `out`. |
125
+
126
+ `hobkin chat ID` puts both in one terminal (see Quick start). `hobkin list` shows all agents, their status and their last line of output.
127
+
128
+ ## Models
129
+
130
+ `config.json` names the model to use and describes each one:
131
+
132
+ ```json
133
+ {
134
+ "model": "claude",
135
+ "models": {
136
+ "claude": {"api": "anthropic", "base_url": "https://api.anthropic.com",
137
+ "model": "claude-sonnet-5-5", "key_env": "ANTHROPIC_API_KEY", "key_file": "~/.anthropic.key"},
138
+ "openrouter": {"api": "openai", "base_url": "https://openrouter.ai/api/v1",
139
+ "model": "anthropic/claude-sonnet-5.5", "key_file": "~/openrouter.key"},
140
+ "local": {"api": "openai", "base_url": "http://127.0.0.1:8080/v1", "model": "local-model"}
141
+ }
142
+ }
143
+ ```
144
+
145
+ - `api`: `anthropic` (the Messages API) or `openai` (Chat Completions: OpenAI,
146
+ OpenRouter, vLLM, llama.cpp, Ollama, ...).
147
+ - The key comes from the environment variable `key_env`, or else from the file
148
+ `key_file`. It's removed from the agent's shell environment.
149
+ - `headers` (optional): extra HTTP headers.
150
+ - `requests_per_minute` (optional): a cap on calls to this model. It's shared by
151
+ every agent using the same API, model and key, because that's what the
152
+ provider counts. The agents coordinate through a small lock file in
153
+ `_shared/` (holding a hash of the key, never the key). An agent at the cap
154
+ waits, and still answers commands such as `/stop` while it does.
155
+
156
+ **The context window is asked for, not hard-coded.** At start-up hobkin asks the API:
157
+
158
+ - Anthropic: `GET /v1/models/{model}` (`max_input_tokens`, `max_tokens`).
159
+ - OpenAI-style: the `/models` list (`context_length` as on OpenRouter,
160
+ `max_model_len` as on vLLM, and similar).
161
+ - llama.cpp's `/props` and Ollama's `/api/show`.
162
+
163
+ `context_tokens` on a model is optional: set it and that number is used as
164
+ given; leave it out (or `null`) and it's detected. If the API doesn't say,
165
+ set it. Failing that, hobkin
166
+ learns the limit from the first "context too long" error and remembers it.
167
+ `state.json` records where the number came from.
168
+
169
+ When the conversation passes `compact_at` (80%) of the window, the agent writes
170
+ itself a handoff note. The conversation is replaced by that note, with the
171
+ newest unanswered message carried over unchanged, and the old context goes to
172
+ `archive/`.
173
+
174
+ Other settings (shared or per agent): `bash_timeout` (600 s per command),
175
+ `output_limit` (30,000 characters of command output, head and tail kept),
176
+ `max_output_tokens` (16,000, capped at what the API reports), and
177
+ `idle_prompt` / `idle_seconds` (see below).
178
+
179
+ ## Its tools
180
+
181
+ - **`bash`** runs as your user, starting in `work/`. It behaves like one shell
182
+ that stays open:
183
+ - The first command runs as a login shell (your profile's PATH and so on).
184
+ - After every command, its state is saved to `shell.sh`: the folder,
185
+ variables, exports, functions and aliases. The next command starts from that
186
+ state. So `cd`, `export` and `source .venv/bin/activate` carry over, even
187
+ across restarts.
188
+ - Each command is still its own process, so a hung or crashed command can't
189
+ take the shell's state with it. Shell options like `set -e` don't carry over.
190
+ - `reset: true` starts a fresh login shell in `work/`.
191
+ - Every result ends with the folder it's now in.
192
+ - The environment has `secrets.env` plus `HOBKIN_ID`, `HOBKIN_DIR`,
193
+ `HOBKIN_WORK` and `HOBKIN_MEMORY`.
194
+ - **`read_file`** shows a file with line numbers (in pages), or lists a folder.
195
+ - **`write_file`** writes a whole file. **`edit_file`** replaces one exact piece
196
+ of text. The text goes in as is, with no shell quoting, which is where smaller
197
+ models most often go wrong.
198
+ - Errors are written to help: "appears 2 times (lines 3, 5)", or the closest
199
+ matching lines when the text isn't found.
200
+ - Line numbers pasted from `read_file` are stripped automatically, and
201
+ Windows line endings are handled.
202
+ - Paths are relative to the shell's folder; `$HOBKIN_WORK`, `$HOBKIN_MEMORY`
203
+ and `~` work.
204
+ - **`skill`** opens a skill by name.
205
+
206
+ **Nothing asks for approval:** an agent can do anything your account can. Agents
207
+ in one account stay apart by convention (their own folders), not by enforcement.
208
+ Use a separate account or container when that matters.
209
+
210
+ ## Waking up when idle
211
+
212
+ When it has nothing to do, it waits for a message. With `idle_prompt` set, it
213
+ also gets that message after `idle_seconds` of quiet. For example,
214
+ `"Check for new work."` every 300 seconds.
215
+
216
+ ## As an agent-tickets worker
217
+
218
+ Nothing in hobkin knows about agent-tickets; you give it the instructions and
219
+ the token:
220
+
221
+ ```sh
222
+ hobkin # note the id, then /stop it (Ctrl-C)
223
+ ID=7f3a91c2 URL=http://127.0.0.1:43917 TOKEN=the-token-for-this-agent
224
+ printf 'AGENT_TICKETS_URL=%s\nAGENT_TICKETS_TOKEN=%s\n' "$URL" "$TOKEN" > ~/.hobkins/$ID/secrets.env
225
+ chmod 600 ~/.hobkins/$ID/secrets.env
226
+ curl -s -H "Authorization: Bearer $TOKEN" "$URL/api/skill" --create-dirs \
227
+ -o ~/.hobkins/_shared/skills/agent-tickets/SKILL.md
228
+ echo '{"idle_prompt": "Run the agent-tickets wait loop (GET /api/wait) and do what it says.", "idle_seconds": 60}' \
229
+ > ~/.hobkins/$ID/config.json
230
+ hobkin $ID
231
+ ```
232
+
233
+ The agent-tickets skill is shared: it reads the token from
234
+ `AGENT_TICKETS_TOKEN`, so one copy serves every worker, each with its own
235
+ `secrets.env`. The idle prompt keeps it
236
+ checking for work, and it can also hold `/api/wait` open itself inside a
237
+ command. Give each worker its own token, so claims and comments show which
238
+ agent did what.
239
+
240
+ ## Releasing
241
+
242
+ Releases go to PyPI from GitHub Actions with Trusted Publishing (no token stored):
243
+
244
+ 1. Bump `__version__` in `hobkin.py`, commit and push.
245
+ 2. Publish a GitHub release tagged `vX.Y.Z` (matching `__version__`). The
246
+ workflow runs the tests on Python 3.9, 3.12 and 3.14, builds, checks the tag
247
+ matches, and uploads to PyPI.
248
+
249
+ Running the workflow by hand (Actions → Publish → Run workflow) uploads to
250
+ TestPyPI instead, for a dry run.
251
+
252
+ ## Tests
253
+
254
+ ```sh
255
+ python3 tests/test_hobkin.py
256
+ ```
257
+
258
+ These start real hobkin processes against `tests/fake_api.py`: a local
259
+ stand-in for both APIs that rejects malformed conversations. So the tests cost
260
+ nothing and don't need a key. They cover both API styles:
261
+
262
+ - commands, secrets, and hiding the model key from the shell;
263
+ - live and queued messages, the lock, `/interrupt`, messages arriving
264
+ mid-command;
265
+ - stopping and resuming, and recovery after a crash mid-command;
266
+ - compaction, learning the window from an error, and the newest request
267
+ surviving compaction;
268
+ - the shell's saved state (variables, arrays, functions, aliases, PATH, folder,
269
+ login profile, `reset`), with secrets never saved;
270
+ - the file tools (exact text with quotes, `$` and backslashes; repeated and
271
+ missing matches; pasted line numbers; paging) and the skill tool;
272
+ - skills (listing, which copy wins, front matter);
273
+ - `requests_per_minute` shared across two agents.