Young's Mind
01 Research

Hermes native features fit our fleet (2026-10-20)

Young: "Study with Tam and Echo. Here are more sources from Hermes, see it good fit for our team."

Hermes native features fit our fleet (2026-10-20)

Young: "Study with Tam and Echo. Here are more sources from Hermes, see it good fit for our team."

Verdict: excellent fit. We were about to build most of this ourselves. Every mechanism the ground rules call for — work-queue, one board, heartbeat, staleness detection, polling without re-prompting — already ships in Hermes, and is already installed on LAB. The work is configuration, not construction.


1. The measurement that changes the plan

We specced a work-queue in a2a-agent-reporting-system-2026-10-20.md as "A2A + a tasks.db row per instruction". Hermes already has it, and it is the same database we already point at.

/home/lab/.hermes/kanban.db  →  tables: tasks, task_links, task_comments,
                               task_events, task_runs, task_attachments,
                               kanban_notify_subs

The tasks table already carries every column our design needed:

ColumnOur design wantedStatus
assigneeowner✅ exists
statusstate (ready/running/review/blocked/done)✅ exists
last_heartbeat_atheartbeat + staleness✅ exists
claim_lock, claim_expiresone-owner enforcement✅ exists
created_by, created_at, started_at, completed_attimeline✅ exists
goal_mode, goal_max_turnsthe Ralph loop per card✅ exists
completion_contractexit-proof contract✅ exists
tenantper-tenant scoping✅ exists
consecutive_failures, last_failure_errorcircuit breaker✅ exists
block_kind, block_recurrencesblocker reporting✅ exists
workspace_kind, workspace_pathisolated worker workspace✅ exists
model_override, provider_override, skillsper-card lane config✅ exists
resultagent's report back✅ exists
idempotency_keydedup on re-dispatch✅ exists

Measured state: 0 tasks, 0 rows. The board exists and is empty — the schema is ready, nothing has ever been dispatched through it. That is the gap, not the tooling.

CLI surface (measured live, hermes kanban --help):init · boards · create · swarm · list · show · assign · reclaim · reassign · diagnostics · link · unlink · claim · comment · attach · complete · edit · block · schedule · unblock · request-review · request-changes · reopen-review · promote · archive · tail · dispatch · daemon · watch · stats · notify-subscribe · log · runs · heartbeat · assignees · context · specify · decompose · gc · repair

2. Feature-by-feature fit

2.1 Goals (/goal) — judge-driven continuation

A standing objective that survives turns. After each turn a lightweight judge model decides done / continue / blocked; if continue, Hermes feeds a continuation prompt back into the same session and keeps working.

CommandWhat it does
/goal <text>Set the goal; first turn fires immediately
/goal draft <text>Draft a structured completion contract, then set it
/goal showPrint the active goal's completion contract
/goal status / /goalShow goal, status, turns used
/goal pause / /goal resume / /goal clearLifecycle control
/goal wait <pid> [reason]Park the loop on a background process; auto-resume on exit
/goal gate add <command>Quality gate — a shell command that must pass before "done"

Why this is the single best fit for our fleet: the gate feature is our exit-proof rule. "Nothing is live until the exit proof passes" (prod-implementation-stages) becomes /goal gate add "curl -sf http://127.0.0.1:8091/health". The judge cannot mark done until the gate passes — enforced by Hermes, not by an agent's good intentions.

Boundary (documented, important): /goal is single-session. It never creates a kanban card, never assigns work to another profile, never fans out. The overlap is deliberate and small: a kanban card created with --goal runs the same continuation engine inside that one card's worker session. It borrows the engine, not the board.

Our use: per-card iteration on "build the Docker images until arm64 pulls succeed and the health gate passes" — with a shell gate, not a prose claim.

2.2 Heartbeat (/heartbeat) — idle-only recurring prompt

One recurring instruction for the current session. Fires when the session is idle and the interval elapsed, as a normal user-role turn — same conversation, same context, same prompt cache.

CommandWhat it does
/heartbeat every <interval> <prompt>Set/replace the heartbeat. Intervals 90s, 10m, 2h, 1d (min 60s)
/heartbeat statusShow heartbeat, interval, time to next fire
/heartbeat pause / /heartbeat resume / /heartbeat clearLifecycle
/hbAlias

Behaviour that matters for us:

  • Idle-only — never interrupts a running turn; fires at next idle poll
  • Missed ticks coalesce — one turn, not a backlog; timer re-anchors on every fire
  • User messages win — a queued user message always takes priority
  • Owned by the setting surface — a heartbeat set from a Telegram chat fires from the gateway and replies into that chat
  • Gateway recovery — startup restores active heartbeats using persisted conversation and thread routing; no new chat message required
  • Persistence — state in SessionDB.state_meta keyed heartbeat:<session_id>; follows context-compression session rotations
  • Quiet when nothing to say — a scheduled turn may end with NO_REPLY/[SILENT]; gateway sends nothing
  • Don't-invent-work guard — the injected prompt tells the agent to reply briefly and stop when nothing meaningful changed

Our use: this is the "poller that doesn't re-prompt" we wanted. Min's /heartbeat every 5m Poll each agent's A2A endpoint and report only status changes — the don't-invent-work guard is exactly the "no busywork" rule the ground rules need.

2.3 Loops (/loop) — timer-driven recurrence

Re-runs a prompt on a recurring cadence inside the current session. Each wakeup is a real agent turn: Hermes reads current state fresh, does the work, reports back.

Two cadence modes:

  • Fixed interval — you set the clock (/loop 2m poll the build)
  • Self-paced — omit the interval; starts at 1 min floor, backs off exponentially (2m, 4m, 8m, up to 15 min ceiling) while replies stop changing, snaps back to floor when a reply differs. Change detection is a local digest comparison (timestamps ignored), so idle waits cost nothing extra.

Stop conditions: agent ends reply with LOOP_COMPLETE · --times N run cap · --until <condition> (judge-gated, fail-open) · /loop stop · backstop loops.max_ticks (default 100).

Our use: the self-paced mode is the "watch a deploy and tell me when it changes" pattern. The --until condition is a quality gate on the loop itself.

2.4 Heartbeat vs Loops vs Cron vs Goals vs Kanban — the decision table

SurfaceRuns inSurvives restartHow manyUse for
/heartbeatThis conversation (full context)Yes (SessionDB)One per session"Keep an eye on X in this thread while we work"
/loopThis conversation (real turn each tick)Session-lifetimeOne per sessionPolling external state, iterate-until-green
hermes cronFresh isolated session per tickYes (durable scheduler)UnlimitedStanding jobs, reports, watchdogs, deliveries
/goalThis conversation (judge-driven loop)SessionOne per sessionKeep iterating on ONE objective until achieved
KanbanBoard of many tasks (own worker process + session each)Yes (SQLite rows forever)UnlimitedMany independent tasks, dependencies, handoffs, multiple profiles

The rule of thumb, stated by Hermes: if the recurring prompt needs the conversation's context → /heartbeat. If it's a self-contained job → cron. If it's one objective with a definition of done → /goal. If work crosses agent boundaries or needs discoverability → Kanban.

We are using all five, for five different jobs. That is not confusion — that is the right decomposition.

2.5 Kanban — the board we already specced

Durable SQLite-backed task board shared across all Hermes profiles.

Two surfaces, one database:

  • Agents drive it through a dedicated kanban_* toolset — kanban_show, kanban_list, kanban_complete, kanban_request_review, kanban_request_changes, kanban_block, kanban_heartbeat, kanban_comment, kanban_attach, kanban_attach_url, kanban_attachments, kanban_create, kanban_link, kanban_unblock
  • Humans/scripts/cron drive it through hermes kanban … on the CLI, /kanban … as a slash command, or the dashboard

The distinction that matters for our ground rules: delegate_task is a function call (RPC fork→join, anonymous subagent, no resumability, audit trail lost on compression). Kanban is a work queue where every handoff is a row any profile or human can see and edit — named profiles with persistent memory, block→unblock→re-run, crash→reclaim, human-in-the-loop, N agents over a task's life, durable audit trail forever, peer coordination.

What we get that we specced by hand:

  • One owner per task — enforced by assignee + atomic claim (claim_lock)
  • Blocked → unblock → re-run, with block_kind and block_recurrences
  • Crash → reclaim (hermes kanban reclaim)
  • Audit trail in task_events + task_comments — never lost to compression
  • kanban_heartbeat — the heartbeat mechanism, on the board
  • hermes kanban watch — a live event stream (--assignee, --tenant, --kinds, --interval) — this is Min's poller
  • hermes kanban dispatch — spawns workers (--dry-run, --max, --failure-limit)
  • hermes kanban daemon — the dispatcher as a service
  • hermes kanban swarm — parallel workers → verifier → synthesizer graph
  • hermes kanban boards — one board per project/workstream — this is how we get the "one board" rule right without one board for everything

Completion checkpoint before the iteration cap: dispatcher-owned workers get one checkpoint notice near 90% of their finite iteration budget, attached to a fresh tool result while another tool-capable call remains. A commit or diff alone never automatically completes a task — the worker must call kanban_complete after verifying the task contract, or persist a progress comment and continue.

Deadlock guard worth knowing: do NOT kanban_link a support card to the card it is meant to unblock — the link makes the support card a child of the blocked parent, so it is gated behind the parent it exists to unblock and neither ever runs. Reference the parent id in the support card's body instead.

2.6 Kanban worker lanes — the contract for Heart agents

This is the piece that makes Heart Fleet on every host implementable in Hermes terms rather than bespoke.

Hermes Kanban  =  canonical task lifecycle + audit trail
Worker lane    =  implementation executor for one assigned card
Reviewer       =  human or human-proxy that gates "done"
GitHub PR      =  upstreamable artifact (optional, for code lanes)

A lane provides three things:

  1. An assignee string — matched against a Hermes profile name (default) or a registered non-spawnable identifier (plugin lane shape). Tasks whose assignee doesn't resolve are left on ready with a skipped_nonspawnable event — not silently dropped or executed by an arbitrary fallback. That is exactly our "emptiness-is-loud" rule, built in.
  2. A spawn mechanism — for profile lanes: hermes -p <assignee> chat -q <prompt> inside the task's pinned workspace, with env vars set: HERMES_KANBAN_TASK, HERMES_KANBAN_DB, HERMES_KANBAN_BOARD, HERMES_KANBAN_WORKSPACES_ROOT, HERMES_KANBAN_WORKSPACE, HERMES_KANBAN_RUN_ID, HERMES_KANBAN_CLAIM_LOCK, HERMES_PROFILE, HERMES_TENANT.
    For non-Hermes lanes (plugin-registered), the plugin supplies its own spawn_fn. ← This is how Heart (SolOS V3 / opencode) becomes a lane: a plugin lane whose spawn_fn runs the V3 worker on a host with opencode v2.
  3. A lifecycle terminator — every claim ends in exactly one of: kanban_complete(summary=..., metadata=...) → done · kanban_request_review(summary=..., metadata=..., reviewer=...) → review · kanban_block(reason=...) → blocked (dispatcher respawns on kanban_unblock).

The scope fence (directly relevant to our uid-boundary work): a task assignment belongs to the dispatcher worker, not to every program it starts. Hermes subprocess helpers carry a non-owner fence into shells, execution kernels, cron deliveries, hooks, language servers, and ordinary stdio MCP servers. Later children remain fenced even when a script removes the inherited task ID: CLI and tool mutations are rejected rather than treating that script as an orchestrator. Honest limit, stated by Hermes: this is cooperative runtime scoping, not OS confinement — it does not stop arbitrary code deliberately erasing lineage metadata or opening SQLite directly. That is why our uid-boundary fence (Sol's t_2b2636f9) is still a separate, real requirement.

Reviewer role: the dispatcher loads the bundled sdlc-review skill unless kanban.review_dispatch is disabled. A reviewer approves with kanban_complete, returns actionable rework with kanban_request_changes, or escalates a genuine external blocker with kanban_block. This is our "one voice to Young" with a mechanism — Young is the reviewer, Min is the orchestrator.

2.7 Automation blueprints — ready-to-run automations

Blueprints are ready-to-run automations. Pick one, fill a couple of fields, Hermes schedules it as a cron job — no cron syntax required. Works from every surface: dashboard Cron page → Blueprints tab, CLI/TUI/messengers (/blueprint <name>), desktop "Send to App". The name match is forgiving (prefix or near-spelling resolves).

Never schedules anything silently — you always confirm before the job is created.

The 16 built-ins (measured from cron/blueprint_catalog.py, 791 lines):

KeyTitleFit for us
morning-briefMorning briefing: calendar, weather, agendaYes — Pulse daily brief
workday-startWeekday nudge with agenda + top prioritiesYes — Brain workday start
evening-winddownEnd-of-day check-in, tomorrow at a glanceMaybe — Pulse EOD
weekly-reviewWeekly recap: done, open, prioritiesYes — fleet weekly review (Min's Rule 10)
competitor-watchTrack named companies for material newsYes — Echo's competitor monitor
price-watchWatch a product, flight, hotel, listingYes — Pulse/investing price alerts
bill-renewal-watchHeads-up before recurring payment/subscriptionYes — our OpenCode subs + Oracle + domain renewals
important-mailCheck inbox, ping ONLY about mail that mattersMaybe — needs O365 wiring
news-digestTopic news digest, dedupedYes — Pulse news
learn-dailyOne bite-sized lesson a dayMaybe — agent skill drip
meal-planMeal planningNo (personal)
hydration-moveHealth nudgesNo (personal)
gratitude-journalGratitude journalingNo (personal)
habit-checkinHabit check-insNo (personal)
custom-reminderOne-off reminderMaybe
on-this-dayRecall this day in historyNo (personal)

Plugin blueprints are prefixed with the plugin name (/blueprint teamtools:standup) so a plugin can never replace a built-in. Plugins load per profile, so a plugin blueprint is listed only in profiles where that plugin is enabled. A job created from a plugin blueprint is an ordinary cron job; disabling the plugin removes it from the catalog but jobs already created keep running.

Writing our own: a blueprint is just a skill with a metadata.hermes.blueprint block in its SKILL.md frontmatter. Plugin authors can register one directly with ctx.register_automation_blueprint().

2.8 Cron — the durable scheduler

A single cronjob_manage tool with action-style operations. Jobs can be one-shot or recurring, attach zero-to-many skills, deliver to origin chat / local files / platform targets, run in fresh agent sessions, run in no-agent mode (script on a schedule, stdout delivered verbatim, zero LLM involvement), and fire on external events (a webhook route with cron_job set fires the job the moment something happens).

Model resolution at fire time: per-job pin → cron.model in config.yaml → the main agent model. Per-job reasoning effort is separately pinnable (none…ultra). Cron-run sessions cannot recursively create more cron jobs — runaway scheduling loops are disabled.

Our measured current cron state (LAB):

ProfileJobs
defaultsol-persona-sync (every 30m)
mirasolos-backup-github (03:00), restore-proof-daily (03:30), spend-ledger-daily (09:00)
solkanban-obsidian-sync (every 30m)
novanone
echonone

Gap: no agent has a coordination cron. Nobody is polling the board, nobody is probing the fleet, nobody is reporting to Min on a schedule. That is the missing mechanism, and cron is the tool for it.

3. What this changes in our design

We speccedHermes already hasChange
tasks.db row per instructionkanban.db tasks tableDrop the spec. Use kanban.db.
One Kanban file (fleet-board.md)hermes kanban boards (one per workstream)Board = the DB. Keep the .md as a projection for the vault, not the source.
Min's work-queue dispatcher cronhermes kanban dispatch + daemonUse the built-in.
Polling agents for stalenesshermes kanban watch + kanban_heartbeatUse the built-in.
"Nothing is live until the exit proof passes"/goal gate add <command>Enforce it in Hermes, not in prose.
"Iterate until done" (agent keeps working)/goal + judgeUse it.
"Poller that doesn't re-prompt"/heartbeat (idle-only, don't-invent-work guard)Use it.
"Watch a deploy, tell me when it changes"/loop (self-paced)Use it.
Standalone daily/weekly jobscron + blueprintsUse blueprints for the ready-made ones.
Heart as a worker laneplugin lane + spawn_fnThis is how Heart-on-every-host works.
"One voice to Young"reviewer role (kanban_request_review)Young reviews, Min orchestrates.
Empty agents show RED, not silentskipped_nonspawnable eventBuilt in.

4. The fit is not perfect — three real gaps

  1. Heart agents are not Hermes profiles. They are SolOS V3 workers on opencode. A plugin lane with a custom spawn_fn is the documented path, but it is not built yet. Until then, Heart cards cannot be dispatched by the board. This is Sol's lane.
  2. The A2A dialect split is still open (Heart=v1.0, Pulse=v0.3, Brain=both). A board that dispatches across planes needs one dialect. Until fixed, a card dispatched to Pulse or Heart can be accepted and then silently fail.
  3. Kanban is a single-host SQLite file. ~/.hermes/kanban.db on LAB. The Pulse laptop and prod have their own. A board row created on LAB is not visible to Tam on the laptop. Our A2A reporting design (Min polls each agent) is still needed for cross-host state — the board covers same-host coordination, A2A covers cross-host.

Honest note: the earlier a2a-agent-reporting-system-2026-10-20.md spec is not superseded — it covers the cross-host case Hermes Kanban does not. But its same-host half is now redundant with the board.

#ActionOwnerWhy now
1Create the fleet board: hermes kanban boards → one board per workstreamMinBoard exists (empty). Without a board, nothing dispatches.
2Move the 19 fleet-board.md cards into real kanban cardsMinThe .md is currently the register; the DB should be canonical.
3Wire hermes kanban dispatch --dry-run into a Min cronSolProves dispatch without spawning
4hermes kanban daemon as a systemd user unit on LABSolThe dispatcher becomes a service, not a cron tick
5/goal gate on the Docker-image cards (exit proof as a shell command)SolTurns "done" into a measured claim
6/heartbeat on Min: poll A2A endpoints, report only changesMinThe poller Young asked for
7Heart plugin lane (spawn_fn → V3 worker)Sol + TamUnblocks Heart on every host
8Blueprints: morning-brief (Pulse), weekly-review (fleet), bill-renewal-watch (subs), competitor-watch (Echo)Mira + EchoReady-made, no cron syntax
9Unify A2A dialect; record dialect: per peer in the registrySolBlocked for cross-plane dispatch
10Bridge cross-host board state via A2A (or accept the split)Min + TamYoung's ruling needed on board topology

6. Open questions for Young

  1. Board topology — one board per workstream (SolOS, Pulse, Prod-tenants) or one fleet board? Hermes supports both (kanban boards). The ground rules say "one board"; the reality is 3 units. My rec: one board per workstream, one fleet board as the view.
  2. Does the vault .md board stay? The ground rules made it the register. Hermes says the DB is canonical and the .md a projection. My rec: DB canonical, .md generated by the existing kanban-obsidian-sync cron (Sol's, already every 30m). No manual .md editing.
  3. Who reviews? kanban_request_review needs a reviewer identity. My rec: Young (one voice, real gate) — Min is the orchestrator, not the approver.

🎯 Min's Read: the fit is real and larger than I expected — Hermes Kanban is the work-queue we specced, with the columns already in the schema, and /goal gate is our exit-proof rule made enforceable rather than aspirational. The construction work is done; what remains is wiring, and three honest gaps (Heart is not a Hermes lane yet, the A2A dialect split blocks cross-plane dispatch, and the board is single-host so cross-host state still needs A2A). The spec I wrote yesterday is half-redundant now — and that is worth saying plainly rather than defending the doc.

Copyright © 2026