hobkin
An AI agent for austere environments: one stdlib-only Python file, no installs, no open ports. Use it from the command line, or as a worker on hearthboard. (A hobkin is a little hob, the house spirit that does the chores while the house sleeps.)
Get one running
Anywhere with Python 3.9+ (Linux or another POSIX system). Nothing to install:
curl -O https://raw.githubusercontent.com/frank-lynam/hobkin/main/hobkin.py
python3 hobkin.py https://openrouter.ai/api/v1 anthropic/claude-sonnet-4.5 sk-or-... --chat
As a background service: leave out --chat, and name it if you like. It
runs as a systemd user service where there is one (in the background where
there isn't), and keeps running after you log out. Talk to it whenever:
python3 hobkin.py https://openrouter.ai/api/v1 anthropic/claude-sonnet-4.5 sk-or-... --id helper
python3 hobkin.py chat helper
As a hearthboard worker: add the board's URL and the agent's token. The board's Users page shows this line, ready to copy, when you add an agent:
python3 hobkin.py https://openrouter.ai/api/v1 anthropic/claude-sonnet-4.5 sk-or-... \
--hearthboard http://board.lan:43917 at_THE_AGENTS_TOKEN
More workers on the same machine reuse the model: python3 hobkin.py --hearthboard http://board.lan:43917 at_ANOTHER_TOKEN.
Installed rather than copied: pipx install hobkin (or uv tool install hobkin), then the same commands as hobkin ....
What that does
That:
- checks the key works and the API offers that model (a typo gets the close
names back;
--forceskips the check for APIs that don't list their models); - sets it up: on a fresh install, as the default model in
~/.hobkins/_shared/config.json, with the key in_shared/keys/, readable only by you. On a machine that's already set up it changes nothing shared: the model and its key go in the new agent's own folder, and your other agents and settings stay as they are. Leave the key out and it asks for it, so it stays out of your shell history; leave it out for a local server with none; - starts a new agent as a systemd user service (see "Keeping agents running"),
or in the background where there's no systemd (or with
--no-service); - with
--chat, connects you to it. Without, it prints how.
The API style comes from the URL (anthropic.com is Anthropic's; anything else is
OpenAI-style: OpenRouter, vLLM, llama.cpp, Ollama's /v1, LiteLLM...); --api
overrides it. --id NAME names the agent instead of a made-up name like bodkin. Run it again with
another URL or model to start another agent on that one.
To see what an API offers before choosing: hobkin models URL [KEY]; hobkin models
lists your configured model's API, and /models does the same in a chat.
With --hearthboard URL TOKEN, it also checks the token with the board, then:
- saves the board's own skill (
/api/skill) in that agent's ownskills/, so it works by the board's current rules (and agents here that aren't workers don't mistake the board for their work system); - puts the token in that agent's
secrets.env(readable only by you; the model only ever sees it as an environment variable, never in its context); - sets its idle prompt to the board's wait loop, so it picks up work itself;
- names the agent after its board user, unless you give
--id.
Without URL MODEL KEY it uses the model you've already set up.
Why it's built this way
- One file, standard library only.
hobkin.pyand Python 3.9+. Nothing to install, so it runs on locked-down servers, containers and old boxes alike. - No ports. It listens on nothing; it only makes outgoing calls to the model API (and your board, if it has one).
- Files are the interface. Talk to it with
echo, watch it withtail -F, and see exactly what the model sees withcat. - Many per account. Each agent has its own id and folder, and a lock stops two processes running the same one.
- With or without a work system. Give it skills for a tracker or a queue and it pulls work from there; with none, its work is what you ask it in the chat, and between requests it waits rather than wandering the machine.
The long way
hobkin # a new agent: prints its name, e.g. bodkin
hobkin bodkin # resume that agent later (its context is kept)
The first run creates ~/.hobkins/_shared/config.json. Set your model there (see
Models) and make sure the API key is reachable.
Then, from another terminal, chat with it:
hobkin chat bodkin
In a terminal it's in colour, so things are easy to find when you scroll back:
the agent's name in magenta, file names, paths and code in green, bold in
bold, links underlined, tool calls dimmed and failed commands in red (agents are
asked to put names of things in backticks for this). Piped, or with NO_COLOR
set, it's plain text. Its replies appear as they come, and each tool call as one line (a command, a file
read, a failed exit status), without its output; hobkin chat ID --full shows the
output too, as the out file has it. Each line you type (or paste) is sent;
/stop and the other commands work, and Ctrl-D leaves. Output never wipes out
what you're typing (the line is redrawn below it), and you get the usual line
editing: ↑/↓ for earlier messages (kept in the agent's .chat_history),
←/→, Ctrl-R to search. Piped, it sends the
input, prints the reply and exits: echo "status?" | hobkin chat bodkin.
Or with plain files:
echo "Clone https://example.com/repo and get its tests passing" > ~/.hobkins/bodkin/in
tail -F ~/.hobkins/bodkin/out
Run it in the background however you like: nohup hobkin bodkin &,
tmux, or a systemd user service.
An agent's folder
~/.hobkins/ID/ (or $HOBKIN_HOME/ID/):
| File | What it is |
|---|---|
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. |
out |
Everything it says and does since the last compaction, with timestamps: messages, commands, output. At each compaction it's moved to archive/ and starts afresh. tail -F out |
context.jsonl |
Exactly what the model is sent: line 1 is the system prompt, then one message per line. Rewritten after every change. |
MEMORY.md |
The agent's own notes. Part of its instructions on every turn, and kept when the conversation is compacted. Past 80 lines, it's told to prune. |
BLOOPERS.md |
Its blooper reel: its own mistakes, told for laughs, each with the lesson. Also part of its instructions every turn, so the jokes double as lessons learned. Past 30 lines, it's told to keep the best. |
state.json |
Model, context window (and where that number came from), status, pid, compaction count. |
work/ |
Its working directory. |
reminders.json |
Its reminders (when, message, repeat). Readable and editable by hand. |
shell.sh |
Its shell's saved state (folder, variables, functions, aliases), restored before each command. Secrets are never saved here. |
secrets.env |
Optional KEY=value lines, set as environment variables in its shell (never put in its context). |
config.json |
Optional per-agent settings over the shared ones. |
*.md |
Optional per-agent instructions; replace shared files of the same name. |
archive/ |
At each compaction, the old conversation (context-*.jsonl, messages only) and its out log (out-*.log). Deleted after 30 days, except the newest. |
jobs/ |
Background jobs' output and exit codes; a finished job's folder goes after 30 days. |
queue, lock |
Messages sent while it was stopped; the process lock. |
~/.hobkins/_shared/ holds what all agents share: config.json, AGENTS.md (how
to work as a hobkin: a task routine with checklists, failure handling,
organization, memory and tidying up; created on first run, edit freely: it's never
overwritten, so new defaults reach it only if you delete it), an optional SOUL.md,
any other .md instructions, and secrets.env.
The system prompt is:
SOUL.md, thenAGENTS.md, then the other.mdfiles in name order;- the list of skills (see below);
MEMORY.md.
Each .md file is taken from the agent's folder if it has one, otherwise from
_shared/. The prompt is rebuilt every turn, so edits take effect on the next one.
Skills
Skills use the same format as Claude Code and other agents: a folder holding a
SKILL.md with a name and description in its front matter, plus any scripts
or reference files. Install one by copying its folder into
~/.hobkins/_shared/skills/, and every agent can use it.
~/.hobkins/_shared/skills/release/SKILL.md
~/.hobkins/_shared/skills/release/build.sh
The prompt lists only each skill's name and description. When a task calls for
one, the agent opens it with the skill tool. That gives it the instructions,
the skill's folder as a full path, and its list of files, so it never has to
work out where scripts live. Dozens of skills cost a line each.
- An agent's own
skills/folder can add skills, or replace a shared one with the same name. skill_dirsin config adds more folders at the lowest priority, e.g.["~/.claude/skills"]to reuse skills you already have.- New skills are picked up on the next turn; no restart needed.
Talking to it
Anything you write to in is a message, and the agent is told plainly what to
do with it, the way a work tracker's API would:
- When it's free:
[message from frank, Sat 2026-10-17 09:12: they're talking with you now. Answer them in your reply]. Set"operator": "frank"inconfig.json(the name people know you by, e.g. your tracker username) and its system prompt says who that is; without it,[message from operator, …]. - When it's busy: the message rides along with the result of the command
it was running:
[message from frank, …, while you were working: answer it in your next reply, then carry on]. If it goes on working without a word, it gets one reminder that you're waiting for an answer. - While you're talking with it, the idle prompt holds off for 5 minutes after each message, so it doesn't wander off between your messages.
- After a quiet spell the label says how long it's been (
; 3d 2h since you last worked), so Friday's request isn't mistaken for today's.
Automatic messages are marked and timed too ([from hobkin: idle prompt, …],
[Reminder #3 …], finished jobs), so the agent never mistakes them for you. A
line that is just one of these is a command instead:
| Command | Effect |
|---|---|
/stop |
Finish cleanly and exit (Ctrl-C and SIGTERM do the same). |
/pause, /resume |
Stop and restart work; messages wait until /resume. |
/interrupt |
Kill the command it's running now (it's told it was interrupted). |
/compact |
Summarise and clear the conversation now. |
/status |
Write its status to out. |
/reminders |
Write its reminders to out. |
/veto |
Block every announced command that's waiting to run (see "Announce first"). |
/model [NAME] |
Alone: the model in use and the ones configured. With a name: switch to that configured model, or to any model the agent's API offers (it gets a config entry like the current one). Saved in the agent's config.json, so it lasts; happens between steps. |
/models |
List the models the agent's API offers (the one in use starred), with their context windows. |
/limits |
Ask the API for the context window again (it's also re-asked before every compaction and after a "too long" error, so a window that grew or shrank is noticed). |
/restart |
Once the current step is done, start over in the same process on the code on disk now (after a git pull or pip install -U), and carry on. The agent can send it itself: a repeating reminder can pull and restart. |
hobkin chat ID puts both in one terminal (see Quick start). hobkin list shows all agents, their status and their last line of output.
Models
config.json names the model to use and describes each one:
{
"model": "claude",
"models": {
"claude": {"api": "anthropic", "base_url": "https://api.anthropic.com",
"model": "claude-sonnet-5-5", "key_env": "ANTHROPIC_API_KEY", "key_file": "~/.anthropic.key"},
"openrouter": {"api": "openai", "base_url": "https://openrouter.ai/api/v1",
"model": "anthropic/claude-sonnet-5.5", "key_file": "~/openrouter.key"},
"local": {"api": "openai", "base_url": "http://127.0.0.1:8080/v1", "model": "local-model"}
}
}
model picks one of models. The models are exactly the ones in your config
files (the three above are only the starter file), so a model you delete is
gone. An agent's own config.json can add models, or change settings on a
shared one (e.g. its requests_per_minute), and can pick a different model.
If model names one that isn't defined, hobkin stops and lists the ones that
are; if it's left out and there's only one model, that one is used.
api:anthropic(the Messages API) oropenai(Chat Completions: OpenAI, OpenRouter, vLLM, llama.cpp, Ollama, ...).- The key comes from the environment variable
key_env, or else from the filekey_file. It's removed from the agent's shell environment. headers(optional): extra HTTP headers.thinking(optional):"adaptive", a token budget (8000), or the API's own object. Anthropic only; it must be smaller thanmax_output_tokens.effort(optional):"low"to"max". It goes to Anthropic asoutput_config.effort, to OpenRouter asreasoning.effort, and to other OpenAI-style APIs asreasoning_effort.extra_body(optional): anything else your provider takes, merged into every request as is.prompt_cache(optional): on OpenAI-style APIs, mark the system prompt and the newest message for caching. It's on by default for Claude models (e.g. on OpenRouter), where re-reading the conversation then costs about 5% of the normal input price; a test session went from about $0.023 per call to $0.001. On OpenRouter, each call's reported cost is added up instate.json(spend_usd).requests_per_minute(optional): a cap on calls to this model. It's shared by every agent using the same API, model and key, because that's what the provider counts. The agents coordinate through a small lock file in_shared/(holding a hash of the key, never the key). An agent at the cap waits, and still answers commands such as/stopwhile it does.
When the provider rate-limits anyway (HTTP 429):
- It waits exactly as long as the API says. It reads
Retry-After(seconds or a date), OpenRouter'sX-RateLimit-Reset, Anthropic'santhropic-ratelimit-*-reset, and reset times inside the error body, then waits until then plus a second. - If the API gives no reset time, it waits 15 seconds, then 30, 60…, up to 5 minutes. A 40-second limit costs two blocked calls at most.
- The cooldown is shared. The first agent to hit a 429 records "blocked until
…" in
_shared/, and every other agent on the same API, model and key waits it out without trying, so a team causes one blocked call, not one each. - It's visible:
- the log says
rate limited by the API; the API said when (retry-after): waiting 41s (until 13:42:10); state.jsonsayswaiting (rate limited);- the heartbeat says
waiting out a rate limit (until 13:42:10)./stopand the other commands still work while it waits.
- the log says
The context window is asked for, not hard-coded. At start-up hobkin asks the API:
- Anthropic:
GET /v1/models/{model}(max_input_tokens,max_tokens). - OpenAI-style: the
/modelslist (context_lengthas on OpenRouter,max_model_lenas on vLLM, and similar). - llama.cpp's
/propsand Ollama's/api/show.
context_tokens on a model is optional: set it and that number is used as
given; leave it out (or null) and it's detected. If the API doesn't say,
set it. Failing that, hobkin
learns the limit from the first "context too long" error and remembers it.
Windows can change (a local server restarted with a different size): the API is
asked again before each compaction, after a "too long" error that doesn't say the
limit, and on /limits.
state.json records where the number came from.
When the conversation passes compact_at (80%) of the window, the agent writes
itself a handoff note. The conversation is replaced by that note, with the
newest unanswered message carried over unchanged, and the old conversation goes to
archive/ (messages only: the system prompt is rebuilt every turn anyway), along
with out, which starts afresh. The note tells the agent where the archive is,
so it can grep it.
Fallback models: "fallback_models": ["other", "local"] (names from
models) are tried in order when the model refuses outright: withdrawn,
renamed, a bad key, out of credit. Free models on OpenRouter come and go, so
it's worth having one. Busy and rate-limited models are waited out instead. A
restart goes back to model.
Typos and wrong types in config.json are reported at startup (in out and on
stderr), with suggestions such as "unknown setting 'idel_seconds' (did you mean
'idle_seconds'?)". They're warnings; nothing is refused.
Other settings (shared or per agent): bash_timeout (600 s per command by
default; the agent can ask for any longer timeout on a command),
output_limit (30,000 characters of command output, head and tail kept),
max_output_tokens (16,000, capped at what the API reports), and
idle_prompt / idle_seconds (see below).
Its tools
bashruns as your user, starting inwork/. It behaves like one shell that stays open:- The first command runs as a login shell (your profile's PATH and so on).
- After every command, its state is saved to
shell.sh: the folder, variables, exports, functions and aliases. The next command starts from that state. Socd,exportandsource .venv/bin/activatecarry over, even across restarts. - Each command is still its own process, so a hung or crashed command can't
take the shell's state with it. Shell options like
set -edon't carry over. reset: truestarts a fresh login shell inwork/.- Every result ends with the folder it's now in.
- The environment has
secrets.envplusHOBKIN_ID,HOBKIN_DIR,HOBKIN_WORKandHOBKIN_MEMORY.
read_fileshows a file with line numbers (in pages), or lists a folder.write_filewrites a whole file.edit_filereplaces one exact piece of text. The text goes in as is, with no shell quoting, which is where smaller models most often go wrong.- Errors are written to help: "appears 2 times (lines 3, 5)", or the closest matching lines when the text isn't found.
- Line numbers pasted from
read_fileare stripped automatically, and Windows line endings are handled. - Paths are relative to the shell's folder;
$HOBKIN_WORK,$HOBKIN_MEMORYand~work.
remindsets a reminder to wake itself later, andreminderslists or cancels them (see below).skillopens a skill by name.view_imageshows it an image (PNG, JPEG, GIF, WebP, under 5 MB): a screenshot of the UI it built, a chart, a photo. On OpenAI-style APIs the image follows the tool result as a user message, because tool messages can't hold images.fetch_urlreads a web page as text: headings, lists and tables kept, scripts and menus' styling dropped, links numbered with a list at the end. Long pages come in parts (offset,limit), JSON is pretty-printed, andsave_asdownloads anything else (a PDF, an image) into the workspace.web_searchsearches DuckDuckGo and returns the top ten results: title, URL, snippet.
The web
Normal sites need no setup. For an internal wiki behind a client certificate (PEM files), set it in config:
"web": {
"client_cert": {"cert": "~/certs/me.pem", "key": "~/certs/me.key",
"ca": "~/certs/corp-ca.pem", "password_file": "~/certs/key-password"}
}
- One certificate for everything: it's offered to any site that asks for
one, including SSO servers along a redirect chain. Most public sites never ask.
ca(your internal CA, trusted alongside the usual ones) andpassword_fileare optional. - SSO logins that need no typing are followed: redirects, meta refreshes,
and the auto-submitting forms that SAML and OpenID logins use (up to 8 steps).
Cookies are kept in the agent's
cookies.txt, so the next page skips the login. A login that wants a password shows up as the login page's text. - Pages built by JavaScript come back nearly empty. Give the agent a skill for those (an API, or a headless browser).
- Search uses DuckDuckGo's HTML page (
web.search_url, no key). DuckDuckGo sometimes answers servers and VPNs with a "bots use DuckDuckGo too" check instead of results; the agent is told so, and searching from a home connection usually works. To check a machine:curl -s -A Mozilla/5.0 -d q=test https://html.duckduckgo.com/html/ | grep -c result__a(a number above 0 means results).
Background jobs
bash with background: true returns at once ("background job #3 started"),
and when the job ends the agent gets a message with its exit code, how long it
took, and the last of its output. Jobs run detached in the shell's current
state, with their output in jobs/N/out.log. They outlive a hobkin restart: a
job that finished while hobkin was stopped is reported when it starts again.
That suits builds, test suites, downloads, and "naps", where a job switches off
a local model, uses the GPU, and starts the model again: nothing calls the model
while the job runs, and its end wakes the agent.
Announce first
Commands whose effects leave the machine are announced before they run:
- What counts (
announce_firstin config, a name and a regex each): force pushes, pushes to main, publishing packages, releases, image pushes, infrastructure changes, cloud deletions, and sending email or webhook messages. - The first try isn't run. hobkin tells the agent to post what it's about to
do and why where people will see it, then run the same command again with
announcedset. - Then it waits
announce_delay(2 minutes), long enough for someone to object in chat. After that, the same command runs. - To stop it, send
/veto, which blocks everything waiting. Or just object where it posted: the agent is told to check before it runs the command.
Everything else, anything inside the box, needs no announcement.
Daily budget
daily_budget_usd pauses an agent for the rest of the day once it has spent
that much. First it gets one last turn to say so where people will see it.
Spend counts only where the API reports each call's cost (OpenRouter does);
state.json keeps spend_today and the all-time spend_usd.
Nothing asks for approval: an agent can do anything your account can. Agents in one account stay apart by convention (their own folders), not by enforcement. Use a separate account or container when that matters.
Safe mode
For a system people depend on: "safe_mode": true in an agent's config.json
(or --safe on the one-line setup). The agent can read what its account can
read, but its commands can only write in its workspace, its memory files and its
job logs, with a private temp folder instead of /tmp.
-
It's enforced by the kernel, not by hobkin checking commands. Each command runs through a small helper that locks itself down with Landlock and then runs bash. Everything that command starts is locked down too, and it can't be undone from inside.
sudowon't work either, because nothing locked down can gain privileges. hobkin's own file tools follow the same rule, and the agent can't change its ownconfig.jsonto turn safe mode off. -
What's covered depends on the kernel, checked at start-up and written to the log, to
state.jsonand into the agent's instructions:Linux Landlock Blocked 5.13+ v1 writes outside the workspace 6.7+ v4 ...and TCP connections ( {"safe_mode": {"network": true}}allows them)6.12+ v6 ...and signals to other processes With no Landlock at all (an older kernel, it's turned off, or a container's security profile blocks it), a safe-mode agent won't start. It never runs unprotected.
-
Not covered: reading. Whatever it reads goes to the model provider, so run it as an account that can read only what it should. On kernels below 6.12, also run it as a separate user, so it can't signal your services' processes. Local services reached through socket files (Postgres, Docker) aren't blocked by Landlock either; a separate user without access to them covers that.
-
How it works with people: it investigates by reading. When something needs changing, or a command it can't run, it writes a script in
work/for-review/(what it does, why, and for changes, how to undo them) and asks you to review and run it. The script saves all its output towork/for-review/logs/, and gathers plenty while it's there (versions, config, the state before and after), so the agent reads the results itself: you run it and say "done".
Reminders
An agent can schedule its own wake-ups: remind with in ("20m", "2h",
"1h30m"), or at (local time: "2026-10-17 09:00", "09:00" for the next one,
"tomorrow 09:00"), optionally every to repeat. The reply states the exact
time it will fire, because small models are bad at time arithmetic. When it's
due, the agent gets a message: "[Reminder #3, set … for …. It's now …]" and
then its own text.
- Idle: it wakes straight away.
- Mid-task: nothing is interrupted. The reminder arrives with the result of the step in progress (after a long command finishes, for example), like your own mid-task messages.
- Paused: it waits until
/resume. - While hobkin wasn't running: it's delivered at the next start, marked late. A repeating one is delivered once, saying how many times were missed, and moves to its next time.
- No limits. Any number of reminders, repeating as often as it likes, even
every second. A repeating reminder that has fallen behind is delivered once,
with a count of how many times it was missed, so it can't flood the agent.
Using
everywhereinwas meant is the classic slip, so the reply points that out. reminders.jsonis a plain file: read it, edit it, or delete entries. The agent's current reminders are also listed in its system prompt.
Keeping agents running
Reminders and idle prompts only fire while the agent's process runs. An idle hobkin costs nothing (it sleeps; no API calls), so keep agents up as systemd user services:
hobkin service add tickets-1 # start it now, and at every boot
hobkin service remove tickets-1 # stop it and take it off boot
add does all of this:
- It writes one template unit,
~/.config/systemd/user/hobkin@.service, shared by all agents. - It points that unit at the hobkin you ran it with (the installed command, or
python3 /path/hobkin.py). - It enables and starts
hobkin@tickets-1. - It turns on linger, so agents keep running when you're logged out. If your system won't allow that without a password, it tells you the command to run.
hobkin list shows which agents run as services.
- Stopping and crashes:
/stopexits cleanly and stays stopped. A crash is restarted after 30 seconds, and systemd gives up after 5 failures in 10 minutes, so a broken config can't spin. - Logs: the agent's own log stays in its
outfile. The journal (journalctl --user -u hobkin@ID) gets startup errors, plus a heartbeat everyheartbeat_seconds(60) while the agent is on duty:hobkin ID active: workingwhile it works;hobkin ID active: on duty, next check in 7m (reminder #2)while it's idle with a wake-up scheduled (say, a tracker check with nothing to pick up).- It's silent while only a message can wake the agent, or when it's paused, so
a quiet journal means it's waiting for you. There's no heartbeat in a
terminal; set
heartbeat_secondsto 0 to turn it off.
- Alerts: when an agent pauses on its own (a model error, the daily budget)
or falls back to another model, it may not be able to say so itself. Set
alert_commandto a shell command and it runs with the news in$HOBKIN_ALERTand the agent'ssecrets.envin its environment, e.g.curl -s -H "Authorization: Bearer $TICKETS_TOKEN" --data-urlencode "text=$HOBKIN_ALERT" https://tickets.example/api/comment. - Keys: a service doesn't see your shell's environment.
key_fileworks as is; forkey_env, putNAME=valuelines in~/.config/hobkin/env(chmod 600).addwarns when that's needed. - Another
HOBKIN_HOMEgets its own template (hobkin-<hash>@.service), so homes never clash.
Waking up when idle
Hobkins don't nap. After idle_seconds (60) of quiet, an idle agent gets
idle_prompt, labelled [from hobkin: idle prompt]. The default is "You're
idle. Unacceptable! Grab work from your work sources, or hunt for something
useful to do." It stays quiet while the agent has a reminder set or a background
job running. Set idle_prompt to null to let it rest until a message or
reminder arrives. Each nudge is a model call, so pair a short idle_seconds
with a requests_per_minute you're happy with.
The gremlin
The default AGENTS.md makes a hobkin a relentlessly cheerful, work-greedy, cheeky little gremlin:
- Forgiveness over permission. It acts, then says what it did.
- Good enough beats better. It ships the working version and moves on.
- The opposite of a yes-man. It laughs at bad ideas, its own included, and does the better thing.
- Hot takes. It rates each task out of five stars.
- Never fakes success.
Small models skim long instructions, so hobkin also nudges at the moment each behaviour matters:
- A message from you carries a reminder to give a hot take first (a hello or a question just gets answered).
- A clear slip (a file-tool error, a command that wasn't found) gets a nudge towards the blooper reel.
- Writing that loses its voice (a reply, or a
.md/.txtfile, heavy with committee words like "robust", "comprehensive", "production-ready") gets "that reads like a committee wrote it", once per turn. - A file read only partly ends with how many lines are still unread, so the agent doesn't fill the rest in from imagination.
- When a working turn ends, there's one wrap-up check: did you report where the people are, any bloopers, more work to grab? It's sent once per turn, so it can't loop.
Busy or self-managing agents get no chatter. If the agent has set up its own next wake-up (a reminder, or a background job still running), the hot take, the wrap-up check and the idle nag stay quiet; it's clearly managing itself. A message that arrives mid-task gets no hot-take reminder either. The corrective nudges (echo, slips, committee voice, unread lines) still apply, since they cost nothing extra.
All of it is plain text in hobkin.py and AGENTS.md, so rein it in as you like.
As a hearthboard worker
Hearthboard (formerly
agent-tickets) is a ticket tracker for agents. --hearthboard URL TOKEN (see
the top) sets an agent up for it in one go; the running agent knows nothing
about hearthboard beyond the board's skill. By hand, that's:
hobkin # note the id, then /stop it (Ctrl-C)
ID=bodkin URL=http://127.0.0.1:43917 TOKEN=the-token-for-this-agent
printf 'AGENT_TICKETS_URL=%s\nAGENT_TICKETS_TOKEN=%s\n' "$URL" "$TOKEN" > ~/.hobkins/$ID/secrets.env
chmod 600 ~/.hobkins/$ID/secrets.env
curl -s -H "Authorization: Bearer $TOKEN" "$URL/api/skill" --create-dirs \
-o ~/.hobkins/$ID/skills/hearthboard/SKILL.md
echo '{"idle_prompt": "Run the hearthboard wait loop (GET /api/wait) and do what it says.", "idle_seconds": 60}' \
> ~/.hobkins/$ID/config.json
hobkin $ID
Each worker keeps the board's skill in its own skills/ folder, not
_shared/: an agent that isn't a worker shouldn't see the board as its work
system. The skill reads the token from HEARTHBOARD_TOKEN, and each worker has
its own in secrets.env. The idle prompt keeps it
checking for work, and it can also hold /api/wait open itself inside a
command. Give each worker its own token, so claims and comments show which
agent did what.
Releasing
Releases go to PyPI from GitHub Actions with Trusted Publishing (no token stored):
- Bump
__version__inhobkin.py, commit and push. - Publish a GitHub release tagged
vX.Y.Z(matching__version__). The workflow runs the tests on Python 3.9, 3.12 and 3.14, builds, checks the tag matches, and uploads to PyPI.
Running the workflow by hand (Actions → Publish → Run workflow) uploads to TestPyPI instead, for a dry run.
Measuring behaviour
The tests check that hobkin works; tests/eval_hobkin.py checks how well an agent
behaves on it, so changes to AGENTS.md or the nudges can be measured, not
eyeballed. It runs a real model (it costs calls; free models are fine) through
six scenarios with a tiny work system (a queue, a team chat, a skill) and scores
what it does:
| Scenario | What it checks |
|---|---|
queue |
does the job, ticks it off, reports in the team chat |
badidea |
rates your bad idea, pushes back, keeps sane filenames |
memory |
does work its stale memory says isn't there, and fixes that memory |
hello |
answers a hello, with no rating, no busywork, no talking through echo |
reading |
finds an answer far beyond the first page of a file |
slow |
doesn't block on a command longer than the timeout |
Most scenarios also check that it didn't ask what to do and kept its workspace tidy.
python3 tests/eval_hobkin.py --model nvidia/nemotron-3.5-super-vl:free \
--key-file ~/openrouter.key --runs 3 # --only queue,hello for some
It prints a line per run and a score per check across runs (e.g. queue 2/3 reported in the team chat). Run it before and after a change.
Tests
python3 tests/test_hobkin.py
These start real hobkin processes against tests/fake_api.py: a local
stand-in for both APIs that rejects malformed conversations. So the tests cost
nothing and don't need a key. They cover both API styles:
- commands, secrets, and hiding the model key from the shell;
- live and queued messages, the lock,
/interrupt, messages arriving mid-command; - stopping and resuming, and recovery after a crash mid-command;
- compaction, learning the window from an error, and the newest request surviving compaction;
- the shell's saved state (variables, arrays, functions, aliases, PATH, folder,
login profile,
reset), with secrets never saved; - the file tools (exact text with quotes,
$and backslashes; repeated and missing matches; pasted line numbers; paging) and the skill tool; - skills (listing, which copy wins, front matter);
requests_per_minuteshared across two agents;- reminders (firing while idle, arriving mid-command with that command's result,
validation, list and cancel, the prompt section, late and repeating after a
restart, a broken
reminders.json).
Metadata
Release files for hobkin 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| hobkin-0.3.0.tar.gz | 119.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hobkin-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 197.3 kB
Release files / hobkin-0.3.0.tar.gz
| Download URL | hobkin-0.3.0.tar.gz |
|---|---|
| Size | 119.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e54e66bb860adfcac48d69dfa53e5e0c3f37ad3417f2b7f1224f8ffd26d76ef6
|
|
BLAKE2b-256 checksum How to use checksums |
b1e5528428ae98a481d12edd709726c6a3e792b55c1006549b11aa444179595c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / hobkin-0.3.0-py3-none-any.whl
| Download URL | hobkin-0.3.0-py3-none-any.whl |
|---|---|
| Size | 77.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e948fa7f496907493ab110b6bbec98cf60004760864521cba2dbb941a241d4c
|
|
BLAKE2b-256 checksum How to use checksums |
2e49d4702b8a24c2ee2f999a938094f1ebd5c76fc971ff53769aa82ef28b9512
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for hobkin 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for hobkin-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.