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 +21 -0
- hobkin-0.1.0/PKG-INFO +301 -0
- hobkin-0.1.0/README.md +273 -0
- hobkin-0.1.0/hobkin.py +1392 -0
- hobkin-0.1.0/pyproject.toml +42 -0
- hobkin-0.1.0/tests/fake_api.py +79 -0
- hobkin-0.1.0/tests/test_hobkin.py +241 -0
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.
|