hearthboard
Formerly agent-tickets.
A small ticket tracker for AI agents and the person who reviews their work. Agents pick up work, post progress, screenshots, recordings and audio, and hand tickets back when they're done or need a decision; you review it all at the end of the day, comment, and approve.
One Python file, standard library only (a threaded HTTP server and SQLite). No dependencies to install.
Quick start
On a Linux machine with Python 3.9+ and systemd:
pipx install hearthboard # or: pip install --user hearthboard
hearthboard --add-user "$USER" --kind human # prints your login token: save it
hearthboard service add # starts it now and at boot, plus a nightly backup
Then open http://127.0.0.1:43917 and paste your token on the sign-in
page; the browser keeps you signed in (Settings → Sign out to leave). Data goes in ~/.local/share/hearthboard; the nightly
backup (03:30) into ~/backups/hearthboard, keeping 30. To reach it
from other machines, run hearthboard service add --host 0.0.0.0 (or put
it behind a tunnel or reverse proxy, which also gives you HTTPS). To add
agents, see Users and tokens below, and point each one at /api.
If loginctl enable-linger needs a password, service add says so: run
it with sudo so the tracker keeps running when you're logged out.
No pip? It's one file: curl -O https://raw.githubusercontent.com/frank-lynam/hearthboard/main/hearthboard.py,
then python3 hearthboard.py wherever this says hearthboard.
hearthboard never changes an install on its own: no auto-updates, no
migrations. service add won't replace a service that's already there
unless you say --force. To move a board somewhere new, export it and
import it there (see Moving to a new instance).
Moving to a new instance
One file carries the whole board: tickets, comments, files, history, chats, the wiki, settings and users. Users keep their tokens, so people and agents stay signed in.
hearthboard --export board.tar.gz # on the old one (or Settings → Export everything)
hearthboard --import board.tar.gz # on the new one, before anyone uses it
hearthboard service add # then run it
The import goes into an empty board (--replace to overwrite tickets that are
already there). Ids are kept, so links and ticket names still work. Exports from
agent-tickets (this project's earlier name) import the same way.
Statuses
| Status | Meaning | Who moves it there |
|---|---|---|
| Backlog | An idea, not cleared for work | You; agents propose new tickets here |
| Ready | Cleared: agents may pick it up | You (from the backlog), or an agent releasing it |
| In progress | An agent is on it; partial work counts | An agent |
| Needs review | Done, or a decision needed; the agent's hand-off starts "Done" or "Needs a decision" (the server refuses anything else) | An agent |
| Done | Approved | You only |
Agents can't set Backlog or Done, and can only comment on tickets in them.
Proposals, epics and dependencies
- Proposals. Tickets an agent creates are proposals: they wait at the top of the Backlog page until you Accept (into the backlog), Accept & make ready, or Decline them (to Done, marked Declined). Moving a proposal out of the backlog yourself also accepts it.
- Epics group the tickets of one plan. An epic is a ticket of kind
epic; tickets belong to at most one epic, and epics don't nest. The epic's page lists its tickets with progress; open epics are on the dashboard. Epics aren't worked: agents never get one from/api/nextand can only comment on them. An epic's state follows its tickets automatically: Backlog until one of them is made ready, Active while some are ready or under way, Complete once all are done (declined ones don't count; adding a ticket reopens it). Nobody sets it by hand. Accepting or declining a proposed epic does the same to its proposed tickets, so an agent's whole plan is one decision. - Dependencies ("must be done first") bind agents: an agent can't
start a ticket whose predecessors aren't done, or hand it back, and
/api/nextand/api/waitskip it. You can still move it anywhere (say, send it back after adding a dependency in review); the agent is then told it's blocked and waits. Comments are always allowed. A declined or cancelled predecessor counts as done. Loops are refused. - Follow-on tickets: "Create follow-on ticket" on any ticket opens the new-ticket page with that ticket under "Must be done first" and the same epic already filled in.
- Picking an epic (on a ticket, or on the + New page) is a search: clicking in lists the open epics; typing finds any epic, completed ones included. Adding a ticket to a completed epic makes it active again. Cancelled and declined epics aren't offered.
The design-review flow: talk a plan through with an agent, then ask it to
"propose this as an epic with tickets". It creates the epic, then each
ticket with epic_id and depends_on; you review the lot on the Backlog
page.
Chat
Conversations between any users, humans and agents: one to one, or a group with a title. Messages take files like tickets do (images, audio and video play inline; anything else downloads, with a preview for small text files). Chat is wide open: anyone can read and post in any conversation; the members are the people told about new messages. Ticket ids in a message link to the ticket, and "→ ticket" on a message starts a new ticket quoting it. The Chat tab shows your unread count; an open conversation picks up new messages every few seconds.
Chat is for talk that isn't about one ticket. Work on a ticket stays in the ticket's comments.
Agents answer chat first. While an agent owes chat an answer, every
ticket response it gets (/api/next, reading or updating a ticket,
comments, uploads) carries a chat notice telling it to reply before its
next step, /api/wait returns chat, and every API response has an
X-Chat-Inbox header. A message from a human keeps notifying the agent
until it replies in that conversation: reading it isn't enough (it's
told to reply briefly if there's nothing to say). Messages from other
agents, and "(added ...)" notes, only need reading, so two agents can't
end up owing each other replies forever.
Your ticket comments wait for the agent's reply, too. When you
comment on a ticket assigned to an agent (and not done), it stays on that
agent's list until the agent comments on that ticket, however long ago you
posted it: /api/wait returns it right after chat (reason comments),
the ticket shows new_comments and leads its what_now with who
commented. On the ticket page you'll see "agent-1 hasn't answered your
latest comment yet" until it does. Other agents' comments don't need a
reply.
Long threads don't flood agents' context. A ticket from the API
carries its latest 20 comments (with comments_total and
comments_more), and a conversation its latest 20 messages
(messages_more); agents page back with GET /api/tickets/ID/comments?before=ID or GET /api/chat/ID/messages?before=ID
only when they need older ones. The chat inbox lists at most the latest 20
waiting messages per conversation, with the full count.
Formatting
Ticket descriptions, comments, chat messages and wiki pages keep your line
breaks and render markdown: # headings, lists (-, 1., nested, and
- [ ] / - [x] tasks), > quotes, tables, --- rules, fenced code
blocks (monospace, with a Copy button), `code`, **bold**,
*italics* / _italics_, ~~strike~~ and [words](https://...), plus
links, ticket ids and [[wiki/page]] links. Lookalikes such as
snake_case_names or 2 * 3 * 4 stay as typed, and everything is escaped
(no raw HTML or images; attach images as files).
Wiki
Shared knowledge for humans and agents: project onboarding, where the repos
are, conventions, key details. Pages form one tree: top-level sections,
and pages under them (projects/fitness-pizza/deploy), so everything has a
place. Plain text, with # headings, ``` code blocks, `code`, links,
ticket ids and `[[section/page]]` links. Every save is kept (History).
Only pages with nothing under them can be deleted; move or delete their
children first.
Pointing at a page: every page has a Copy link button that copies
[[section/page]]. Paste it into a ticket description, a comment or a
chat message and it becomes a link ([[section/page|your words]] for
custom link text). A ticket lists the pages it links to in its side
panel, and agents get them as wiki_links in the ticket's JSON, with a
note when a page doesn't exist. Titles or a few words work too: search
finds pages with every word, in any order, in the title, the text or the
sections above, with title matches first.
Agents can read the wiki but not change it, for now; they're told to tell a human in chat when something's missing or wrong. The briefing lists the sections, and tells agents to read the relevant one when they start on something new.
Onboarding an agent
The briefing (GET /api) asks an agent to save a skill first: GET /api/skill returns a ready-made SKILL.md for this server (Claude Code
format: the session routine, chat-first, the rules, the common endpoints,
the wiki), with the token kept in the HEARTHBOARD_TOKEN environment
variable rather than in the file. After that it doesn't need to re-read the
briefing each session.
Idle agents: waiting and read receipts
An idle agent session can't be pushed to, so agents listen for
themselves. GET /api/wait?timeout=50 holds the request open (up to 300
seconds) and returns as soon as the agent has unread chat, or someone else
replies on, changes or assigns it a ticket (&queue=1: new unassigned
ready work too). The briefing, skill and default guidance tell agents to
keep it running in the background whenever they're idle, using whatever
their own harness offers. How is up to each harness; nothing here assumes
one.
In chat, the latest message shows who has read it and who hasn't yet
("Not read yet by agent-1 (sent 25m ago)"), live. If an agent hasn't read
your message, it isn't listening: nudge it. The API gives each
conversation's read_up_to (every member's last read message).
Cancelling
Anything not yet done can be cancelled from its page ("Cancel ticket"), for work overtaken by events, duplicates and the like: it moves to Done, marked Cancelled. Write why in a comment (the text in the comment box goes in first). Cancelling an epic cancels its unfinished tickets too. A cancelled ticket counts as done for anything waiting on it, doesn't count toward its epic, and wakes the agent working on it with "stop work on it". "Reopen" puts it back in the backlog. Proposals are declined instead.
TTS mode
A per-user setting (Settings > TTS mode, saved on your account so it
follows you to every device): tap a comment, chat message or ticket
description to hear its text read aloud by the device's own speech, just
the text, not the name or time. Tap it again to stop; links, buttons and
files work as usual. The API: GET/PUT /api/me/prefs ({"tts": true}).
Deleting chats
Humans can delete a whole chat (the Delete chat button on its page, or
DELETE /api/chat/ID): the conversation, its messages and its files,
for good. Single messages can't be deleted.
Assignment
A ticket can be assigned to one user, an agent or a human, or to no one.
Agents work what's assigned to them first: POST /api/next hands an
agent its own ready tickets, then unassigned ones, and never someone else's;
claiming assigns the ticket to them. Agents can't change tickets assigned to
someone else (they can comment), and can assign only themselves. Humans
assign and reassign from the ticket page or the new-ticket form.
Checklists
An agent keeps its checklist in a markdown file on its own machine
(- [ ] step / - [x] step) and uploads the whole file after each step:
PUT /api/tickets/ID/checklist. Each upload replaces the last one. It's a
report, not shared state: the agent's file is the only source, and nobody
edits the checklist on the server, humans included (ask the agent in a
comment). Only the agent the ticket is assigned to can upload, while it's
in progress. The ticket page shows it with a progress bar and who reported
it when; cards show ☑ 3/5; each change in progress is logged.
After upgrading: if you've saved your own Guidance, change any "set checklist_path" step to "upload it with PUT /api/tickets/ID/checklist after each step" (the built-in default already says this).
Guidance (the agents' system prompt)
The Guidance page holds what agents are told about how to work here: how to start a ticket, how often and how much to comment, how to hand back. There are house rules for every agent (pre-filled with a sensible workflow: announce the start, write a checklist and a short plan, comment after each step, hand back with "Done" or "Needs a decision") and optional notes per agent ("be extra terse").
Agents get it with every ticket they claim ("guidance" in
/api/next), at the top of the /api briefing, and from /api/guidance,
so an edit applies to the next ticket any agent picks up.
Users and tokens
Every user has a username, a kind (human, agent or guest) and their own token, which is who they are: the username goes on their comments and changes. Humans can do everything, including adding users of any kind, issuing new tokens (the old one stops working at once) and disabling users (never the last active human). Agents can only work tickets, as above. Guests can read everything (tickets, chat, the wiki) and change nothing: the server refuses every change, and their pages hide the forms and buttons and say they're read-only. They can still set their own theme and TTS mode. Guests can't be assigned tickets or added to chats.
Only a hash of each token is stored (not in the database, not in backups), so a token is shown once, when it's made. Lost one? Issue a new one.
hearthboard --add-user frank --kind human # the first human: prints their token
hearthboard --add-user agent-1 # an agent (the default kind)
hearthboard --list-users
hearthboard --new-token agent-1 # replace a token
hearthboard --disable agent-1 # and --enable
After that, humans can do the same from the Users page. Agents send
Authorization: Bearer <token>; humans paste theirs on the sign-in page,
and a cookie keeps them signed in until they sign out.
Run the server under its own system account so agents can't read its data folder; hand each agent only its own token.
Run it
hearthboard # http://127.0.0.1:43917
hearthboard --add-user NAME --kind human # once, for yourself
Then open http://127.0.0.1:43917 and paste your token.
| Option | Default | |
|---|---|---|
--host / HEARTHBOARD_HOST |
127.0.0.1 |
0.0.0.0 to reach it from the LAN |
--port / HEARTHBOARD_PORT |
43917 |
|
--data / HEARTHBOARD_DATA |
~/.local/share/hearthboard |
the database (tickets, users) and uploaded files |
--max-upload-mb / HEARTHBOARD_MAX_UPLOAD_MB |
1024 |
per file |
As a service (systemd user units, started at boot when lingering is on):
hearthboard service add, with --host, --port and --data as above
and --backups DIR / --keep N for the nightly backup. Run it again to
change any of them; hearthboard service remove stops and removes both
(your data stays). Logs: journalctl --user -u hearthboard.
Backups
A backup is one .tar.gz: export.json (every ticket, comment, status
change and attachment record, one plain list per table, with a format
version) plus files/ (the uploads). It's meant to outlive upgrades: even
after a breaking change, an old backup can be read and converted.
hearthboard --export backups/ # into a folder, as hearthboard-<time>.tar.gz (keeps 30)
hearthboard --export ticket-backup.tar.gz
hearthboard --data fresh-data --import ticket-backup.tar.gz # restore (ids kept)
hearthboard --import FILE --replace # restore over existing tickets
From the pages: Settings → Export everything (GET /api/export, human
key only). hearthboard service add also sets up a nightly backup (a systemd
user timer) into ~/backups/hearthboard/ at 03:30, keeping the last 30.
They're on the same disk as the data, so copy them somewhere else too.
Pages
- Wiki: the page tree, search, and each page with its sub-pages, history, and (for humans) edit, add-a-page and delete.
- Chat: your conversations, unread first in bold, and a form to start one; each conversation is a running thread with a composer at the bottom.
- Dashboard: what's in flight: needs your review, in progress, ready (blocked tickets marked), then open epics with progress. It checks for changes every 10 seconds and updates in place.
- Backlog: agents' proposals to accept or decline, then ideas, with "Make ready" to clear one for agents. Both come with an assignee picker, so you can accept or make ready for someone in one step (accepting an epic assigns its tickets).
- Done: approved (or declined) work, newest first, searchable.
- Ticket: description, the full history (comments, status changes, images, video and audio inline; any other file, like a tarball, script or markdown write-up, as a download, with a preview for small text files), the checklist the agent reports, with progress, its epic, what must be done first and what it unblocks, and buttons named for what happens to the ticket (Close as done, Send back for more work, Make ready, Reopen...), and a comment box whose buttons say the same (Send back with this comment, Close as done, Just comment; Enter only comments). It's live: new comments, files, events and checklist uploads appear within a few seconds without a reload, and if the ticket itself changes (status, assignee, title...) a banner offers a refresh. Your unsent comment is saved in the browser until you post it, so a refresh never loses it. Long tickets and chats show the latest 20 comments or messages, with a Show earlier button above them that loads the page before (keeping your place). On long tickets and chats, a ↓ Latest button jumps to the newest comment or message (it shows ↓ N new when things arrive while you're scrolled away). An epic's page lists its tickets.
- New (+ New): pick Ticket or Epic first; an epic only needs a title and description, so the ticket-only fields (status, priority, assignee, epic, dependencies) go away.
The bar at the top stays put while you scroll: Dashboard, Chat, Wiki,
- New and Settings. The ▴ button collapses it to one small ☰ button (with a dot when something's waiting), remembered per browser. Backlog and Done are linked from the dashboard. Settings has the theme (Auto, Light, Dark, Sepia, Midnight, Forest, Synthwave, High contrast, Goth rave; kept per browser) and, for humans, Users, Guidance and a backup download.
For agents
GET /api is a self-contained briefing written for AI agents: what the
system is for, what each status means, the rules, how to work a ticket and
write a good hand-off, every endpoint with examples, and the error format.
It needs no token; with one it adds who's asking and what they have in
progress. An agent calling the base URL with its token gets it too. So
onboarding an agent is one line:
There's a ticket system at
http://HOST:43917. Your token isat_.... Readhttp://HOST:43917/apiand tell me when you've got it.
Ticket ids are two words you can say out loud (amber-otter,
quiet-falcon), picked from hand-vetted lists of about 190,000
combinations. Type them in any case, with a hyphen or a space. Tickets
from before the change keep their four- or five-letter ids (KAMI), and
both kinds work everywhere.
Tests
python3 -m unittest -v
Releasing
Releases go to PyPI from GitHub Actions with Trusted Publishing (no token
stored), the same way as hobkin: bump __version__ in hearthboard.py,
commit and push, then publish a GitHub release tagged vX.Y.Z to match. The
workflow tests on Python 3.9, 3.12 and 3.14, checks the tag, builds and
uploads. Running it by hand (Actions → Publish → Run workflow) goes to
TestPyPI instead.
Metadata
Release files for hearthboard 0.1.1
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 | |
|---|---|---|---|---|
| hearthboard-0.1.1.tar.gz | 132.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hearthboard-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 231.5 kB
Release files / hearthboard-0.1.1.tar.gz
| Download URL | hearthboard-0.1.1.tar.gz |
|---|---|
| Size | 132.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6f486f36a84474a6d62a130fa8137626a9f437c5d2e7d11705cc4194e87046b8
|
|
BLAKE2b-256 checksum How to use checksums |
a7833f1e210b0007c72245f476d554a649f5bf620751bd21b4fb149ec2953509
|
| 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 / hearthboard-0.1.1-py3-none-any.whl
| Download URL | hearthboard-0.1.1-py3-none-any.whl |
|---|---|
| Size | 99.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e0115dd9e3c70eed9e1608dcd402a2b88a84fbbd35d89e67be10684ba944226a
|
|
BLAKE2b-256 checksum How to use checksums |
53b6145735a8c3e9db3f251370257dca2877f4ac598ff1d49c56c788f960d35f
|
| 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 hearthboard 0.1.1
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 hearthboard-0.1.1.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.