Hermes build note · Kanban learning pipeline

Building a review-gated Kanban learning loop for Hermes docs

A small feature with a big boundary: let Hermes notice documentation changes and queue learning work, but do not let discovery mutate memory, skills, or config without review.

Milo H · June 27, 2026 TDD first 30 docs pages discovered sticky blocked cards

The problem

Hermes has living documentation. Skills and memory are also living systems. The tempting but wrong move is to scrape the docs and immediately patch the agent’s long-term behavior. That would be fast, and it would also be a foot-gun.

The build I landed is intentionally conservative. It turns documentation deltas into Kanban review cards. A reviewer or worker can then extract operational knowledge, propose skill patches, and mark memory candidates. Nothing durable happens just because a page changed.

Diagram 1 · Source to review card discovery is not integration
Hermes docs sitemap.xml landing links public source Discover normalize URLs filter assets 30 pages in smoke Hash state sha256 content new or changed idempotent reruns Kanban card blocked needs_input review required Extraction format commands · config · pitfalls skill patches · memory candidates Review boundary no auto memory writes no auto skill patches The loop is allowed to observe. Integration remains a separate, explicit act.

The implementation shape

The core code lives in a thin, testable module: hermes_cli/learn.py. It owns discovery, state, task body generation, result formatting, CLI parsing, and slash-command parsing. The rest of Hermes only routes into it.

The first useful slice was deliberately narrow: hermes docs, not a general crawler, not a self-modifying learner, not a new model tool.

The rule: docs become review cards. Review cards may produce proposed durable changes. Durable changes still need explicit approval.
Diagram 2 · Command routing dedicated path versus open-ended learning
Dedicated learning workflow Existing generic /learn behavior CLI / TUI / Gateway /learn hermes docs detect exact syntax hermes docs run_slash() command output Kanban blocked cards Any other /learn URLs · notes · dirs build_learn_prompt() standards-guided prompt normal agent turn preserve role alternation This avoids breaking the old /learn while adding a precise command-style workflow.

The bug that mattered

The subtle failure was Kanban semantics. Creating a task with initial_status="blocked" stored status='blocked', but it did not make the block sticky. The list and dispatcher paths call kb.recompute_ready(). That promotion pass can move non-sticky blocked tasks back to ready when they have no parent dependency.

For a review gate, that is exactly wrong. The fix was to create the task with the normal path, then call the official block API with kind="needs_input". That emits a real blocked event, which recompute_ready() respects.

task_id = kb.create_task(..., idempotency_key=key, tenant="hermes-docs")
task = kb.get_task(conn, task_id)
if task and task.status in {"ready", "running"}:
    kb.block_task(
        conn,
        task_id,
        reason="Review gate: inspect docs delta before any skill/memory/config write",
        kind="needs_input",
    )
Diagram 3 · Sticky review gate blocked means blocked until reviewed
create_task() idempotent insert ready by default block_task() kind = needs_input writes blocked event sticky blocked recompute_ready skips it review is the only exit bad old path: non-sticky blocked can be promoted human review promote or patch The state machine encodes the policy: observation can be automatic; integration is deliberate.

What landed

01 · Module

hermes_cli/learn.py owns docs discovery, page hashing, state persistence, task generation, CLI parsing, and slash parsing.

02 · Routing

CLI, TUI, and gateway surfaces route exact hermes docs requests to the command path and leave generic /learn alone.

03 · Tests

The test slice covers sitemap discovery, fallback docs links, idempotency, changed-page detection, slash routing, and sticky review gates.

Verification

The feature was built test-first. The important regression test fails if a review card becomes ready after kb.recompute_ready(). That is the guardrail that caught the original mistake.

uv run --extra dev python -m pytest tests/hermes_cli/test_learn_docs.py -q -o 'addopts='
# 5 passed

uv run --extra dev python -m pytest tests/tui_gateway/test_protocol.py -q -o 'addopts='
# 72 passed

uv run --extra dev ruff check hermes_cli/learn.py tests/hermes_cli/test_learn_docs.py
# All checks passed

A real write-path smoke test ran in a temporary HERMES_HOME. It created two learning cards, verified both remained blocked after recompute, and verified both had blocked events with needs_input.

What this is not

  • It is not autonomous skill mutation.
  • It is not a general web crawler.
  • It is not a background daemon yet.
  • It is not a replacement for a human reviewer deciding what belongs in durable agent memory.

The right next step is a review command that opens one blocked docs-learning card, extracts a clean candidate patch, and keeps the final write behind an explicit approval step.

Why the design matters: agent learning is only useful if it can forget bad ideas before they fossilize. Kanban gives the learning loop a place to pause, inspect, and reject. That is the difference between “self-improving” and “quietly corrupting itself.”