Hierarchical Task Network

story · live

All of my agents coordinate over a shared knowledge graph of work items.

Write-up Latent Labs

Steering a game studio's work graph from a Discord channel, with agents picking up the work.

Role: architect and sole engineer · Aug–Sep 2026 · Stack: Beads (Dolt-backed task graph), Gas City, Hermes agent harness, SvelteKit, Python · Status: shipped in the NEXUS kernel; live in Holodeck.

Read with Software Factories, the larger system this sits inside.

The problem

Holodeck is a game studio system where most implementation is done by AI agents. Agents are fast. What runs short is work that is shaped well enough for an agent to pick up, and human decisions that arrive in hours, not days.

Before this project the studio's plan lived in a markdown roadmap and in Discord threads. Agents couldn't read intent from either, and humans couldn't see what agents were waiting on. Two failures kept repeating:

  • Open-ended human tasks stalled. "Design: the temple main floor" sat for days because there was no clear finish line.
  • Decisions got lost. The design partner posted the finished Oracle Temple layout in Discord, an agent recorded it on the right task within the hour, and then nothing happened. The graph knew, but nobody was told.

What I built

One shared task graph, with a skill that lets humans and agents author into it at every level of the hierarchy.

  milestone   "A player can walk the temple floor, save, load, sleep"
     │          (human-stated, player-observable acceptance)
     ├─ phase epic 1 ──[human gate: sign-off]
     │     ├─ leaf: decision          owner:human
     │     ├─ leaf: content-request   owner:human
     │     └─ leaf: engineering       delegate:city  → harness gate
     └─ phase epic 2 ──[human gate]   (whole subtree hidden until released)
           └─ ...
  • /roadmap, a skill in the NEXUS kernel. It turns goals a human states into a gated graph: milestones, phase epics, and leaf tasks that can be dispatched. The whole structure is written in one atomic bd create --graph call. The skill proposes and the human confirms; it structures goals but never invents them.
  • Roadmap identity lives in the repo's linked-data graph. A directory's _.jsonld names the root epic of its roadmap. The studio has one roadmap and Olympus has its own (any title can bind one), all in one database, each addressable from its place in the repo tree. The binding is the only thing committed to git. Every human-readable view is generated from the graph and never edited by hand, so there are never two sources of truth to keep in sync.
  • Phases are gated. A human gate on a phase epic hides that phase's entire subtree from every "what's ready" query. One bd gate resolve releases it atomically. Sign-off is a single act in the graph, not a meeting note.
  • Every leaf has a kind with a mechanical finish line. The work decomposition contract defines seven kinds: decision, content-request, spec, engineering, transcription, discovery and playtest-feedback. Each must name what proves it done: a harness gate, a schema validator, or where a decision gets recorded. "Looks good" is not an acceptance criterion.

How it connects

  #roadmap (Discord)                         holodeck-web
      │  human states a goal                 SvelteFlow "living map"
      ▼                                       ▲  SSE push, read-only
  holodeck-hermes ── /roadmap ──► beads graph ─┤
      ▲   (proposes, human confirms)   │  ▲    └► roadmap-map → digest cards
      │                                │  │
  roadmap-steward  ◄── reads ──────────┘  │ ingest captures Discord
  surfaces what waits on a human          │ replies onto beads
  the moment it exists                    │
                                    holodeck-gascity
                                    mayor slings routed leaves
                                    to agent pools → PR → close
  • holodeck-hermes is the studio's Discord agent. It loads /roadmap in #roadmap, so goal-setting happens in conversation.
  • holodeck-gascity is the agent fleet. Pools only see leaves that are ready, explicitly routed and not epics. The skill creates leaves unrouted by default and marks intent as owner:human or delegate:city. Routing stays a deliberate act.
  • Ingest captures the substance of Discord discussion onto the right task, and only proposes status changes, never infers them. The steward tells humans what is waiting on them. It started as one daily digest and now surfaces each item the moment it exists. Together they close the loop: talk → graph → prompt → talk.
  • roadmap-map is one pure function from the graph to a JSON map per goal. The Discord digest cards and holodeck-web's live graph view both render from it, so every picture of the roadmap agrees.

Decisions that mattered

The skill decomposes; the fleet doesn't. I read both forked codebases and the live system. Gas City ships no decomposer, and epics can't be dispatched by construction. So the skill owns decomposition down to dispatchable leaves. Work that genuinely can't be known yet stays one discovery leaf, never a fake subtree. A "decompose me" hand-off to a planner agent is the designed upgrade, not a competitor.

Two agent runtimes, one skill source. The Discord agent is not Claude Code: Hermes discovers skills in a completely different way and ignores Claude plugin manifests. Rather than fork the skill, I ship one source directory exposed twice: a plugin manifest for Claude Code sessions, and a kernel entrypoint change for Hermes. Skill updates reach the gateway through an ordinary submodule pin bump, with no image rebuild.

Hermes and Gas City stay separate planes. Merging the containers would have been simpler and wrong. The fleet assumes it can crash and reconcile, carries its own scoped credentials (so it can't burn the gateway's budget), and changes its checkouts in ways that must not share a working tree. They coordinate through git and the shared graph.

Cycle time is a design property. The contract names three loops:

Loop Feedback Target
L1, agent self-check headless harness gates minutes, closes unattended
L2, human steering steward → reply → ingest hours, never a day
L3, playtest session-service feedback artifact → task hours to days

The bottleneck was L2. A request filed at 9am surfaced in the 4pm digest and was captured the next day, two orders of magnitude slower than the agents. The fix: I retired the daily digest (2026-09-01). Human-facing leaves are now posted the moment they exist, and a context-advantage test decides between asking a human and researching first. If the human's edge is context only they have, ask. If the gap can be closed, file a discovery first, so the eventual decision takes minutes.

What went wrong, and what I learned

  • Several traps fake success. bd init without an explicit database silently creates a private graph where every command "works". Applied graph nodes inherit no labels. Closing a task with children quietly orphans them. The skill now carries a trap list, because each of these looked like success in testing.
  • A dependency edge on a milestone hides everything beneath it from bd ready. Milestone prerequisites moved into acceptance text instead of edges.
  • Structure is not state. An early version answered "what's blocking?" from the graph's shape alone, and confidently asked the design partner for a decision already recorded on the task. The skill now has to read a task's comments before naming it as a blocker.
  • Principles meet operations. I set the rule that a graph's client belongs where its work's context lives, and moved NEXUS tasks off Holodeck's server. When the NEXUS-side runtime wasn't ready, I parked them back on Holodeck's graph as a dated, recorded exception rather than quietly bending the rule.

Outcome

  • /roadmap shipped in the NEXUS kernel and is used by both Claude Code sessions and Hermes agents in Discord.
  • Holodeck's roadmap runs end to end: goal in Discord → gated graph → ingest capture → steward surfacing, with leaves shaped for fleet dispatch. Milestones were backfilled and every open leaf is labelled with its kind.
  • The same graph feeds holodeck-web's live map. That view is the basis for this portfolio's own graph navigation.

Connected

NEXUS Graph

Graph in Development

Hierarchical Task NetworkBeadsDoltGas CityHermesSvelteKitPythonJustin HromalikCreative director

Tech: Beads, Dolt, Gas City, Hermes, SvelteKit, Python.

People: Justin Hromalik, Creative director.

Outputs: Hierarchical Task Network.