CHANGELOG / WHAT SHIPPED

What each release changed.

Every release of hm writes a note, and this page publishes those notes as they were written. Nothing here is a summary of one. Where a note is technical it stays technical — it was written for whoever is about to upgrade, and shortening it for a visitor would mean deciding on their behalf which half mattered.

Two parts of each note are left out on purpose. Install and Release mechanics (after land) are addressed to whoever installs or cuts the release rather than to a reader, and how to get hm is answered on the download page, against the release profile rather than against one note. Entries carry no date because the notes carry none; the order is the order they shipped in.

v0.1.71search the board for a ticket and jump to it

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.70 and onto this tag. There is no nightly between them.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.70.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.71.

Highlights

This release is one change to the board, from the petition board-ticket-search-impl: a way to find a ticket on a busy board and open it, without hunting through the columns.

  • A search icon expands into a field. The board head carries a search icon before the order chips. A click opens a search field, and so does the slash key anywhere on the board outside a text field.
  • Typing shows a hit list. Matches appear in a short list under the field, ranked with an exact ticket id first, then an id prefix, then an id substring, then a match in the card copy or a ticket title. Ties keep the board's own order, and the list stops at 20 hits.
  • A hit opens the ticket's Detail. Clicking a hit does what clicking its card does: it selects the card and opens Detail. The search is a jump, not a filter, so the columns never hide a card while you type.
  • It gets out of the way. Escape clears the query, and a second Escape folds the field. The field also folds when Detail opens or the Works switches. The chrome stays quiet: neutral surfaces, no bead and no action on a hit row.

What makes it work

  • Cards carry their tickets' titles. Each ticket on a board card now carries its whole title. The title is read only by search and is never painted as card copy, so the card looks the same.
  • Ranking is one pure module. A new boardSearch module ranks hits over the board the desktop just painted, so search reads no new data and runs no new query. The key, slash and row rules sit beside it, each with its own unit tests.

What ships

Two CLI tarballs — hm-0.1.71-aarch64-apple-darwin.tar.gz and hm-0.1.71-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.71_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.70board cards speak in icons, and the board loads faster

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.69 and onto this tag. There is no nightly between them.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.69.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.70.

Highlights

This release is two changes to the board, from the petition board-card-icons-load. The first quiets the informational chrome on a card. The second makes the board load faster, and makes a Works switch read as a switch.

Card info chrome is icons, with the words on hover

  • The marks are icons at the end of the meta row. The Landed column's integration chip (Integrated, Not integrated, Ledger-only) and the Needs land card's evidence, self-check and land or refusal lines now paint as small icon marks at the end of the card's meta row: a check, a caution or a help mark. They used to be lines of text.
  • Hover or focus gives the meaning. Each mark carries its words as its hover title and its accessible name, so a screen reader reads the same copy the text line used to show. Keyboard focus on the card reveals the words on the card; at rest they stay hidden.
  • Nothing else moves. A pending mark still opens its Readiness row on a click. No mark is green, so the green bead stays the one live signal that a unit is waiting on a managed land.

The board loads faster

  • The board frame reads start together. The digest, the hand runs and the manifest board now start at the same time under the existing single-flight latch, instead of one after another. On a clone of the dogfood Works (520 manifests, 1185 run logs) a board frame went 1.4 s to about 0.7 s.
  • Card copy is one read. The board used to open the content store once per unit to quote each brief. It now reads every brief body in one query, and a manifest board on the same clone went 491 ms to 258 ms. A failed bulk read falls back to the per-unit reads.
  • A Works switch paints a reading state. When you switch Works, every column shows Reading until the new board arrives, instead of the previous Works' cards. The ledger tail starts beside the board read rather than behind it, and a read that settles after you have switched again no longer paints.

The design docs match what ships

Sections 1, 2 and 11 of docs/design/hm-evolution/ui.md now describe the card marks: icons whose words are the hover title and the accessible name, revealed on focus, with no green mark.

What ships

Two CLI tarballs — hm-0.1.70-aarch64-apple-darwin.tar.gz and hm-0.1.70-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.70_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.69the Needs land card is status, inside the card

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.68 and onto this tag. There is no nightly between them.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.68.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.69.

Highlights

This release is one change to the board, from the petition needs-land-card-only. It acts on the Principal's feedback about the Needs land column after v0.1.68: status belongs on the card, and a land is not a line to copy.

Needs land cards: in-card land status only

  • The land lines paint inside the card. The evidence, self-check and land lines on a Needs land card now sit inside the card, under its meta row, as text. The land line used to sit under the card, outside its chrome. A click on any of these lines is a click on the card, so it still drills to Detail and the ticket.
  • No Copy for terminal, and no Stage chip. The land line keeps its Lands to <landing_ref> · mode <mode> (<source>) text, and keeps the refusal sentence when the tip is off the landing ref. It no longer carries a Copy for terminal chip or a Stage fix… chip for the land or ledger-only command, and the card holds no command line at all. A land is your act, and you ask the Superintendent in conversation when you want the next step.
  • The board stays compact. Three lines is still the ceiling on a Needs land card, and each line is status, not a control. The green bead on the card, which marks a ticket that is complete but not landed, is the signal that the unit is waiting on a managed land.

The design docs match what ships

Section 2 of docs/design/hm-evolution/ui.md now says the three land-card lines are text inside the card, with no Copy for terminal and no Stage chip. Its test list pins that the land line carries no command, that no copy or stage chip is painted on a Needs land card, and that every land-line style is scoped to the card.

What ships

Two CLI tarballs — hm-0.1.69-aarch64-apple-darwin.tar.gz and hm-0.1.69-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.69_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.68a failed read says so, and the git findings reach Readiness

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.67 and onto this tag. There is no nightly between them.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.67.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.68.

Highlights

This release is one body of work: the evolution UI principles pass. It read every desktop surface that v0.1.67 shipped against hm's own design rules. Every surface was mounted, and the rules that carry weight held. One surface could never appear, and a failed read either went silent or looked empty. This release fixes both, and brings the design docs in line with what ships.

The git findings reach the Readiness pane

  • Two gatehouse rows can now appear. The Readiness pane did not collect the checks that only hm works doctor runs, so its two gatehouse rows never showed. It now adds gatehouse.ledger_landed_unintegrated and gatehouse.revert_unrecorded as advisories, each with doctor's staged command unchanged. The unintegrated row names the bench and the land mode, and quotes the reason a ledger-only land was given.
  • A second line for the next step. A Readiness row can carry a muted second line with what to do next.
  • The Not integrated chip links to its row. On the board, a landed manifest whose tip is off the landing ref wears a Not integrated chip. A click now opens the Readiness row for that manifest. The chip finds the row by the finding's code and manifest id, never by its prose.

A failed read says so, and keeps what it drew

  • One sentence for every failed read. When a read fails, the desktop now shows Could not read <thing>: <reason> beside the last value it drew. It never swaps in an empty list or a zero, because "nothing there" and "could not look" are different facts. This covers the board, Readiness, the ticket Evals floater, calibration, Outcomes, Seats and Organization, the belt floater, and the run card's launch read.
  • The board keeps its cards. A board read that fails keeps the cards it last drew, instead of emptying the lanes.
  • Outcomes stays current. The Outcomes pane re-reads after a gatehouse or review event, coalesced with the other Overview reads. Its window stays fixed.

Copy and mount fixes

  • The override confirm names the mode first. It now opens with Mode direct., Mode local. or Mode pr., then says what merges, into what, and whether it is pushed.
  • The Hand Session names its runtime. The session header carries a provider · model chip, read from the run's own launch record on every summary. When the record names no provider, the chip is left off rather than guessed.
  • One evidence label. The Evidence block on the Evals floater no longer shows its label twice.

The design docs match what ships

The copy deck in docs/design/hm-evolution/ui.md now carries the act labels the desktop ships. It also has the Could not read form, the three-line ceiling on a land card, the fixed Outcomes window, the seat delivery read, and the refusal block on the run card. The design index and the v4 delta now count ten Overview panes, which is what ships.

A correction to the v0.1.67 note

The v0.1.67 note said each Principal act has a Record as you… button. That is not what shipped. Each act has its own label: Approve as you…, Waive as you… and Land with override as you…. Only evidence uses Record as you…. These labels are now the copy deck's own. The rest of that paragraph stands: each label opens a confirm that shows the exact hm line, and nothing runs until you confirm.

What ships

Two CLI tarballs — hm-0.1.68-aarch64-apple-darwin.tar.gz and hm-0.1.68-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.68_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.67the desktop shows what the gate reads, and a hand fails at the door

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.66 and onto this tag. There is no nightly between them.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.66.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.67.

Highlights

Most of this release is one body of work: the hm evolution roadmap. It has three aims. A hand should fail at dispatch, not halfway, and close cleanly on the first try. A grader should see what the Principal attested. And the ledger should say whether the work got better. The core verbs landed first, and the desktop now shows each of them.

The desktop shows what the gate reads

  • Board integration chip. A landed manifest no longer reads "Landed" on its own. Beside it is the git state, in one of four chips, and a land line with the land mode and where that mode came from. A tip that is off the landing ref shows why the land would refuse, with a Copy for terminal line for the deliberate ledger-only close.
  • Land card lines. A Needs land card now says whose move it is. Line 1 is Needs your evidence: <check>, Evidence: pass · <check> · <tip>, or Evidence stale when the tip moved after it was recorded. Line 2 says what the hand ran: Hand ran n/m checks · agrees with grader, or the check it disagrees on. Neither line blocks; the gate reads neither.
  • Evidence on the ticket Evals floater. Each check shows its evidence state (needed, requested, pass, fail, stale), the note, who recorded it, at which tip, and the earlier records. The hand's self-check shows beside it. The form copies an hm eval evidence line with the note in a quoted heredoc. It never puts the note on the command line.
  • Calibration on the Evals pane. Each check shows how often its grader agreed with the Principal. A check with few labels reads thin · n labels instead of a rate. A rubric scoped to one manifest wears a manifest:<id> badge.
  • Outcomes pane. A new Overview pane, after Spend, counts what happened after each land: reverts, review comments, and routed repairs, per Line, over a window. Its subtitle is fixed: Reported, not a target.
  • Seats pane launch line. Each seat shows the launch a dispatch would get: provider, model, permission mode, belt size and MCP. A line appears when the seat's last run launched differently. The belt floater lists the effective rules with secret-store entries left out. A command an open ticket needs and the belt misses comes with the exact hm executor set line that covers it.
  • Run card launch and preflight. A run card shows the launch it ran with and every preflight row. A dispatch the gate refused lists every defect at once, each with its repair. A probe that did not answer reads unknown, never ok.
  • Readiness rows stage the doctor's own command. A row's chip is built from the finding's staged command, never lifted from prose. A verb a seat may run stages as Stage fix…. A Principal-only verb renders Copy for terminal and never reaches the agent.
  • Principal acts from the desktop. Approve, waive, override and evidence each have a Record as you… button beside Copy for terminal. It opens a confirm that shows the exact hm line. Nothing runs until you confirm. If the line would change, nothing runs. The override confirm re-reads the land mode and landing ref and names what will merge and push. The event records via = "desktop". A seat is still refused all four acts at every posture.
  • A live Hand Session on every agent CLI. Cursor, Codex, OpenCode and Pi hands now resume interactively, as Claude hands already did. When a session cannot resume, the pane names the provider from the run's own record and shows the transcript when there is one.

Principal evidence and hand self-checks

  • hm eval evidence records a Principal's pass or fail on a check, at the tip the eval last graded. On the git land path, a critical human check with current-tip evidence lets the land through. Evidence at an older tip is stale and counts for nothing. Evidence on a check that another grader owns changes no result. The note comes from stdin or --note-file. A seat is refused.
  • hm eval flag … --needs-evidence lets a hand say what the Principal should inspect, and where. It is a request; it lifts nothing.
  • hm ticket check records a command the hand ran and its exit code, at the tip hm reads from the bench. hm ticket complete --ran <cmd> records one on the way to the close. A command check then reports whether the hand's run agrees with the grader's. A disagreement never changes the gate's answer.
  • Calibration learns from evidence. Current-tip evidence on a judged check is one label. hm eval calibration marks a check with fewer than 5 labels thin.

A hand fails at dispatch, not halfway

  • Every dispatch carries its launch record. run.dispatched names the provider, model, permission mode, allowlist, MCP servers, preflight results and the hm that ran it. Server names only; no secret or server env reaches it.
  • Belt coverage preflight. A Claude seat that denies off-list commands, and whose allowlist misses a critical command check, is refused at dispatch with the exact hm executor set remedy. A standard miss warns. Under auto it warns. It is n/a under bypassPermissions and for every provider whose CLI takes no allowlist.
  • Credential probes. A Works can declare [[secrets.probe]] entries in hm.toml. A seat or ticket that requires a credential has its probe run before spawn, with no shell and a hard timeout. hm reads only its exit status and an optional headroom number. It reports ok, expired, missing, unknown, low headroom or undeclared. Every outcome except ok and unknown blocks, and doctor checks each declared probe.
  • A redrive remembers the last attempt. A redispatched ticket's prime opens with ## Last attempt: the last completion note, the checks that failed at the last graded tip, self-check disagreements and unresolved review comments. It is built from events only and capped at 40 lines.

Closed and landed each mean one thing

  • A second complete is a no-op. After the final touchpoint closes a ticket, hm ticket complete <id> --note … prints already complete and records the note once. A different note refuses with completion_note_conflict.
  • A ledger-only land refuses an unintegrated tip. hm gatehouse land without --git now refuses when hm/<manifest> is off the landing ref, and names the verb that would integrate it. --ledger-only --reason "…" closes it on purpose, and doctor keeps quoting the reason until the tip is integrated.
  • One land locus. Land, --verify-only and doctor resolve the same bench. manifest.landed records the land mode, where it came from, and the integration.
  • hm works wire seeds land_mode = local on a repo with no origin.
  • Doctor findings carry a staged command, so a remedy can be run exactly as doctor wrote it.
  • hm gatehouse revert records a revert of a land without touching git, and doctor raises gatehouse.revert_unrecorded for one that nothing records.
  • hm overview outcomes is the outcome series the Outcomes pane reads.

An eval binds where it was written

A rubric may declare scope: manifest:<id>. It then binds every ticket in that manifest and no ticket outside it. hm eval write --ticket <id> defaults a new rubric's scope to that ticket's manifest, and --scope works opts out. A rubric with none of bears_on, kinds or scope is still written, with a warning that it binds on words across the Works.

The Superintendent's model is scoped per runtime

hm executor set --model-provider <p> writes a seat's model for one agent CLI only, the twin of --level-provider. The desktop's Save as seat default uses it, so a model chosen on one runtime never reaches another. A saved model that the runtime does not list falls back to that runtime's default, with a preflight warning.

What ships

Two CLI tarballs — hm-0.1.67-aarch64-apple-darwin.tar.gz and hm-0.1.67-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.67_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.66scrap a finished manifest, bring up Pi, bind evals by scope

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.65 and onto this tag. There is no nightly between them.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.65.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.66.

Highlights

hm manifest abandon scraps a finished tip without landing it

A manifest whose tickets are all complete used to have one way out: the land. When that work must not reach main, cancelling the tickets one by one left a manifest of cancelled tickets that could still be merged and pushed.

Now the Principal can retire the whole unlanded tip in one act:

hm manifest abandon <manifest> --tip <sha> --reason "…" --dry-run
hm manifest abandon <manifest> --tip <sha> --reason "…"

--tip is the tip hm manifest changes prints. The dry run prints the plan — the tip, the landing, each commit, the tickets it retires and the pin — and writes nothing. The real run pins the tip once under refs/hm/abandoned/<manifest>, moves every complete ticket to cancelled, records manifest.abandoned and closes the manifest. The branch and the pin stay, and nothing is pushed. --by <ticket> names a landed ticket that carried the same intent.

While the pin is on a branch, hm gatehouse land refuses it with abandoned_work, and dispatch will not reuse it. That gate covers a single-ticket abandon as well. A Superintendent seat may preview with --dry-run; only the Principal can write. The verb refuses while any ticket is open, latched or blocked, so cancel those first. hm workflow show and doctor's manifest.landable check both name it beside the land.

Pi hands reach hm over MCP

A seat on the pi agent CLI could look installed and still fail. Two things caused it, and both are fixed.

  • One PATH for every agent CLI. An nvm-installed CLI is a script that starts with env node. hm spawned it with launchd's PATH, where there is no node, so it exited 127. Every probe and every spawn — Claude, Codex, Cursor, OpenCode and Pi — now runs with one child PATH: the CLI's own directory, then the login-shell PATH, then the inherited one. A probe sees the environment its spawn will get. On a 127, hm agent status names the interpreter hm could not find.
  • A Failed CLI is a repair, not an install. hm agent ensure plans a repair for a CLI that is present but fails, and runs nothing. hm agent auth says to repair first.
  • Pi trusts its bench for each run. Pi reads .pi/mcp.json only in a trusted project. In an untrusted one it still runs and exits 0, but with no hm tools. Every bench is a new path, so hm now passes --approve on every Pi run it spawns, host and hand alike.
  • The probe checks the model the lane will run. A hand runs only its seat's own Pi model, so an empty one is still refused at dispatch. The Superintendent and the setup conversation fall back to the default model, so the probe now reports that model instead of a false pi_model_required.
  • Pi sign-in is polled. Pi signs in with /login inside an interactive pi. hm polls pi auth check --no-refresh, which never prints or rotates a credential.

docs/reference/runbooks/pi-seat-switch.md walks a seat onto Pi and proves the live MCP turn.

An eval with bears_on binds by scope, never by words

A rubric that declares bears_on has said where it applies. Before, a ticket that only mentioned its words could still bind it at dispatch. Desktop tickets were graded on research-decision-pack that way, and their land waited on a check that did not apply to them.

Now a rubric with a non-empty bears_on binds only when the ticket names it or matches its path or kind. A text hit alone is not enough. A rubric with no bears_on keeps the word-score rule. Binds already frozen on a dispatch are not rewritten; waive or remap those as before.

What ships

Two CLI tarballs — hm-0.1.66-aarch64-apple-darwin.tar.gz and hm-0.1.66-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.66_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.65the Principal opts a Line into the cycle with one file

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.64 and onto this tag. There is no nightly between them: it carries the Principal's first opt-in path for the work-factory cycle directly.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.64.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.65.

Highlights

A Principal --file raises the rung as written

v0.1.64 shipped the work-factory cycle as opt-in, with hm cycle refine as the way a [cycle] rung changes. But a refine draft passes a review that keeps only changes answering a recorded process defect. A first opt-in has no such history, so the review emptied the Principal's own draft, and refine reported that it changed nothing. A greenfield Works or Line had no way in except editing hm.toml by hand.

Now a Principal running hm cycle refine --file <toml> --reason … raises the file as written. No Super pass reviews it: the candidate carries every field the file declares, and its review reads skipped — the Principal's own draft. It is still a candidate, so it takes effect only when the Principal promotes it with hm cycle promote.

A Superintendent's --file is unchanged. It skips the drafting pass, and the review still runs on it.

First opt-in: one file, then promote

The first enable of a Line, or of the Works, is a Principal policy act, not a process-defect fix. Its path is the Principal --file above, then promote:

hm cycle refine --line <id> --file rung.toml --reason "first opt-in"
hm cycle promote cycle-line-<id>-c<n>

A rung.toml holding only [cycle] and enabled = true is enough. Promoting it writes [cycle.lines.<id>] alone, and the Works [cycle], enabled included, stays as it was. A test pins exactly that. No other verb sets a rung: a seat cannot take this path, its candidate waits for the Principal, and no agent edits hm.toml by hand. The hm cycle row in hm workflow show and the cycle concept doc both name the path.

A review that strips a draft refuses instead of reporting no change

When the review takes a Superintendent's draft that changed fields and returns one that changes nothing, hm cycle refine now refuses. Before, it reported that the draft changed nothing, which hid what the review had removed. The error names the stripped fields and says no recorded process defect answers them.

The evidence bar that hm cycle refine --dry-run prints is a reading of the evidence, not a gate: a refine below it still runs, and it never blocks a --file draft.

What ships

Two CLI tarballs — hm-0.1.65-aarch64-apple-darwin.tar.gz and hm-0.1.65-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.65_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.64a quiet update row, the opt-in work factory, and hand retire

This is a stable release, and it moves the latest channel at dl.hmcortex.com off v0.1.63 and onto this tag. There is no nightly between them: it carries the update affordance, the work-factory cycle and hand retire directly.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.63.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.64.

Highlights

The desktop now tells you when a newer hm is published, and asks nothing of you until then. An Update row appears in the nav footer between Ledger and Feedback, or as a compact icon beside Feedback in a narrow window, only when dl.hmcortex.com carries a newer build than the one you run. It checks a minute after launch and every six hours after that.

The row opens a small popover with no scrim. It shows the version you run and the one on offer, the channel, a What's new link to that version's entry on hmcortex.com/changelog, and two buttons: Update and Not now. Not now hides that one tag on this device; the next release brings the row back. If hands are running, the popover says so before you update, but it never blocks you. After the app is swapped, the row stays up with Restart hm, so the new bundle starts when you choose.

hm update: the same update from a terminal

Behind the row is a new command, and it works on its own: hm update --check tells you whether a newer build exists, and hm update installs it.

hm update reads the channel's VERSION at dl.hmcortex.com, picks the newest version, and fetches that release's assets with the same refusals as the installer. If no build exists for your platform, or SHA256SUMS is missing or does not match, nothing is installed. A downloaded binary must report the tag as its own --version before it replaces anything.

A CLI install is replaced in place. An hm.app install is updated from the DMG: the new bundle is copied beside the old one, renamed over it, and the old one is kept as .hm.app.old. An app that macOS is running from a translocated or mounted location, or that sits in a folder you cannot write, is refused, and hm names the download page instead. If you have both a CLI install and the app, the other one is brought to the same tag. Any other hm copies on PATH are reported and left alone.

The command asks before it swaps anything, unless you pass --yes. --check exits 0 when you are current and 10 when a newer build exists, and --json, --channel and --version are there for scripts. A nightly install also reads latest, so it sees a newer stable release. hm update is not offered over the MCP bridge: an agent cannot update the hm it is running under.

The work factory: an opt-in cycle for a manifest

A manifest can now run through a declared cycle of stations instead of plain ticket order. Nothing changes unless you opt in. A Works with no [cycle] table, or with enabled = false, runs today's cycle exactly as before, and a test pins that.

The cycle definition. A [cycle] table in hm.toml is resolved like [eval], field by field. The order is the manifest brief's own cycle fence, then the bench slot, then a [cycle.lines.<line-id>] table, then the Works [cycle], then the compiled default. Each field records which rung answered it. A Works can set a floor that no narrower rung lifts. The cycle is frozen on the manifest's first dispatch, and hm cycle status --manifest <id> prints it with the rung behind each field.

The stations. The cycle switches these on one at a time:

  • Round cap. Review send-backs are counted from the original ticket. Repair titles read (round k of N) instead of nesting, and a send-back past round_cap is held for the Principal with no hand latched.
  • Design gate. With design_gate on, the manifest's first ticket is its design, and no impl sibling starts until the design's critical evals pass. A failed critical check cuts the next design round under the same cap.
  • Fan-out. With fan_out = 2, set only in the manifest brief, two independent tickets can run at once, but only on different benches. Siblings that share a bench still run one at a time.
  • Ordered gates. A Works can declare a stack of [[cycle.gates]] entries, each one an eval, a lens or a hold. The stack is frozen and printed with the cycle. It is declared only: no station acts on it yet.
  • Kind-bound critics. With kind_critics on, a ticket's evals are also bound by what it delivers: video, docs, research or code. A code rubric no longer binds to a research memo. Two starter packs ship for this: video-ffprobe checks duration, a 720p minimum, an audio stream and a clean decode, and sourced-memo holds any document to a verdict up front and its sources.
  • Integrate run. An integrate list names critical evals that grade the manifest's joined tip once every ticket is complete. Only hm eval approve <manifest> starts the run, and only the Principal may approve it. A failed check holds the manifest's last land, and so does a later commit.

The Superintendent as conductor. At a station gate, the Superintendent decides whether to start the next round, send the work back, or hold it. By default it only proposes. hm cycle approve and hm cycle decline are the Principal's to run. With conduct = "auto" it acts on its own at internal stations only. The land, critical overrides and the round cap always wait for the Principal. Every transition is recorded as a cycle.station_* event.

Pause, spend and refine. hm cycle pause <manifest> and hm cycle resume <manifest> stop a manifest at its current station and start it again. hm cycle status and hm overview show spend for each station, priced from each ticket's own run log. Tickets that cannot be priced are named, and no spend cap is enforced. hm cycle refine drafts a change to a [cycle] rung from review comments, reckonings and recurring holds that name the process. The draft is reviewed and queued. Only the Principal's hm cycle promote writes it to hm.toml, and it takes effect from the next manifest.

On the desktop. The Overview has a read-only Cycle tab after Evals. It shows which rung answers each field and how many manifests are held at the round cap. The manifest detail has a one-line cycle header. On the Board, an opted-in manifest shows its stations, where each ticket is, and held or sent-back markers on cards. A Pause or Resume button sits beside them. Approve and Send back chips stage a message for the Superintendent but never send it. On today's cycle, none of this appears.

Retiring a hand, with a handoff

A hand seat that has finished its job can now be retired without erasing it. hm org seat retire <seat> --reason … keeps the seat's roster row, so its history still resolves. It drops the seat's executor and writes a handoff: a digest of the work it did by manifest, the choices it recorded, the skills it was given, and any threads it completed that have not landed. --dry-run shows what would happen. The command refuses all at once with every reason that applies: a seat that is latched, one that is not a hand, or the last hand on its Line. It can be run by the Principal or a Superintendent, never by a hand. hm org seat handoff show reads the handoff, and hm org seat handoff write writes it.

Nothing hands work to a retired seat. Latch assignment, a Line's hand pick, @ mentions, listeners and doctor's seat checks all skip it.

On the desktop Roster, a retired seat sits in a closed Retired fold at the foot of its Line. Its Assign chip is replaced by a Handoff chip, which opens the handoff in a floating reader. The Overview's Seats pane never counts a retired seat as idle capacity.

The Ticket and Super screens stay responsive under dispatch load

Under a burst of dispatches, the desktop's Ticket and Super screens could freeze. Hot ledger reads now run off the main thread. The board has one read in flight at a time, a ticket reader shows only its latest answer, and a North Star or board column that has not changed repaints nothing.

What ships

Two CLI tarballs — hm-0.1.64-aarch64-apple-darwin.tar.gz and hm-0.1.64-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.64_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.63Ticket Evals: a floating rubric reader, and `hm eval remap`

This is a stable release, and it moves the latest channel at dl.hmcortex.com from v0.1.62 to this tag. There is no nightly between them: it carries the Ticket Evals and eval-binding work that landed after v0.1.62 directly.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.62.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.63.

Highlights

Ticket Evals: the rubric opens in a floating reader

On the desktop's Ticket screen, a row of the Evals pane no longer unfolds its rubric under itself. It opens a floating reader, the same panel the Overview's Skills and Evals tabs read from, and Escape closes it. The reader is bound to the ticket: beside each check's ask it shows this ticket's result, along with the check's grader and criticality. When the corpus has moved past the revision the ticket was bound to, the reader still says so, and a hand's flags on the eval are listed there too.

A status strip and compact rows

The pane now opens with one line on the land, in the land card's own words: whether the ticket's evals block it, warn, or leave it eval-clear. Below the strip, each eval is one compact row — its name, its state, critical or standard, and its result counts, such as 1 fail · 4 pass — with a flagged chip when a hand has contested it. A warning the row already says, such as a failed standard check, is not repeated as a paragraph; a waiver or an eval that has not run still is.

The pane is still for reading. Running, waiving, flagging, remapping and overriding stay hm eval … commands and Principal acts on the land card.

hm eval remap: correct what a dispatch bound

A ticket's evals are chosen once, at dispatch, by ranking over its prose, and prose cannot always tell a ticket that implements a piece of research from the research itself. hm eval remap <ticket> --drop <slug> --add <slug> --reason … is the correction. Both flags repeat, and the reason is required.

A remap is recorded as eval.remapped after the dispatch, never by rewriting it, so the dispatch still shows what the hand was given. The runner, hm eval status, the land gate, waive and flag all read the remapped set, and a redispatch starts the set over.

Dropping an eval takes exactly the authority waiving it would: a critical or floor eval is the Principal's, a standard one the Principal's or a Superintendent's. A hand flags instead. An added eval is frozen as of the dispatch — the Works revision written then, or a bench pack as it stood at the dispatch base — and refused if it did not exist at that point, so nothing written after seeing the work grades it.

The land card shows each remap on its own (remapped) line. A remap is not a warning, eval-clear is computed over the remapped set, and eval refinement does not read a remap as evidence against a rubric. The MCP bridge carries the same command.

Named evals bind first

A ticket that names an eval as eval:<slug> now binds exactly what it names, with no ranked extras beside it. Eval search also treats a hyphenated compound as one term, so a tag like hm-core is reachable, and an eval's bears_on paths match at a path boundary in one direction only.

A grade-time advisory when the diff disagrees with the bind

When a ticket is graded, the run now records which bound evals the graded diff never touched and which unbound evals it did. hm eval status prints that as an advisory, pointing at hm eval remap to correct the bind with a reason. It is advisory only: nothing is skipped and nothing is remapped for you.

What ships

Two CLI tarballs — hm-0.1.63-aarch64-apple-darwin.tar.gz and hm-0.1.63-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.63_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.62Ticket Evals: read a ticket's bound eval rubric on its own screen

This is a stable release, and it moves the latest channel at dl.hmcortex.com from v0.1.61 to this tag. There is no nightly between them: it carries the eval and touchpoint work that landed after v0.1.61 directly.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.61.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.62.

Highlights

Ticket Evals: the bound rubric opens on the Ticket screen

On the desktop's Ticket screen, each row of the Evals pane is an eval the ticket was dispatched against, frozen at the revision it was bound to. A row now opens that rubric in a read-only reader under it, pending runs included: every check's id, what it asks, its grader, its criticality, and a command check's command shown as text.

The reader is the one the Overview's Evals tab already uses, so an eval reads the same in both places. When the corpus has moved past the revision the ticket was bound to, the reader says so rather than showing the newer text as if it applied.

The pane is for reading. Running, waiving, flagging and overriding stay hm eval … commands and Principal acts on the land card.

Eval judge seats

A judge check can now be graded by a seat as well as by a model. A Works seeds a generalist judge — judge-works, covering every jurisdiction — from docs/reference/templates/judge-seat.toml with hm org apply-template, and can add judges for a jurisdiction: code, architecture, chaos, ops, research or docs. An eval names its jurisdiction, eval-wide or per check, and can mark a check needs_tools; both are frozen with the eval at dispatch and shown by hm eval show.

The runner calls the judge. It picks a seat for the check's jurisdiction (preferring one on the ticket's Line), then the generalist, then the configured judge model; a seat that fails falls to the model with its error recorded. Each judge runs with a toolbelt built from its jurisdiction's preset, the Works' [eval.toolbelt.<jurisdiction>] presets and the seat's own allowed tools, over a deny floor that is always applied whole: no git writes, no hm, no gh writes, no Edit, Write, Agent or MCP. hm executor set and hm works doctor refuse a judge row that would reach past that floor.

A critical judge check asks a panel of three distinct judges. A majority with no dissent is the verdict; a split or a short panel goes to a human, with every vote kept. A judge turn has ten minutes, and a ticket's judging has thirty.

The model judge spawns in the scratch checkout at the ticket's tip, under the same PATH contract as the command grader, so a desktop-launched hm still finds claude. The land card adds an (eval-clear) line for each ticket whose checks all passed or were skipped. It is advisory, not a gate.

The command grader starts sh and finds cargo under a stripped PATH

A command check now runs /bin/sh in its own scratch checkout per run, with an environment built for it: HM_* dropped, and PATH widened to the inherited one plus cargo's home bin, hm's well-known directories and the system directories. A parent launched with a bare PATH — the desktop app — no longer reports "could not start sh" or misses cargo. Concurrent runs of one ticket no longer share a checkout, so one run can no longer remove another's working directory mid-check, and a missing checkout is reported as broken rather than as a failing check.

Touchpoints: one bench per ticket

A ticket's touchpoints are pinned to one bench. hm ticket touchpoint add refuses a bench other than the one its touchpoints already name, and the error gives the recipe instead: a sibling ticket with hm ticket add --depends-on, then one touchpoint add --bench on it. Further steps on the same bench are still allowed.

hm gatehouse land warns, for any older ticket whose touchpoints span benches, which benches this land does not merge. It is a warning only.

A ticket that is complete but not yet landed shows its Changes from the bench it is pinned to, not the manifest's binding.

What ships

Two CLI tarballs — hm-0.1.62-aarch64-apple-darwin.tar.gz and hm-0.1.62-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.62_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.61evals reach the stable channel, with an Evals pane on the desktop Overview

This is the first stable release since v0.1.59, and it moves the latest channel at dl.hmcortex.com to this tag. It carries the work of the two nightlies between them: v0.1.60-nightly brought evals to the CLI, and v0.1.61-nightly gave them a pane on the desktop Overview. This release is the same code as v0.1.61-nightly, published as a plain version.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.59.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.61.

Highlights

Evals: a corpus of checks beside the skill corpus

An eval is a named set of checks a ticket's work is graded against. It lives in the Works as kind=eval, or in a bench pack at evals/<slug>/EVAL.md, and is written, rolled back, superseded and retired the way a skill is. Each check names its grader — a command, a rules check, a judge, or a human — and a criticality.

A dispatch freezes the evals that apply to the ticket, at their current revision, and the prime lists them under ## Evals. A hand that disagrees with a check can say so with hm eval flag.

The eval runner grades completed tickets, and a critical failure holds the land

After a ticket completes, the eval follower grades the committed tip in a scratch worktree, against the frozen revision, and records the result on the ledger. hm eval status shows where a ticket stands, and the desktop's Ticket screen has an Evals pane.

A failed or pending critical check stops hm gatehouse land before any git work. Only the Principal can override it, with a reason the ledger keeps. A failed standard check lands with a warning.

When an eval's history shows it is wrong — an override, an accepted flag, a review comment that cites it — hm eval refine drafts a candidate revision and hm eval round raises one for each eval that crosses the bar. A candidate never binds until the Principal promotes it. hm eval calibration reports how often each check agreed with what happened next.

An Evals pane beside Skills on the desktop Overview

The Overview has an Evals tab next to Skills, laid out the same way: a library of every eval the Works knows — its own and its benches' packs, the Works copy winning where a slug is in both — with each eval's criticality and how many of its checks are critical, a search box, and a docked reader that opens an eval in full, every check with its grader and criticality.

When refine candidates are waiting, the pane says how many and stages hm eval candidates in the composer. A Works on file persistence has no candidate store; the pane says so beside the list instead of failing.

The pane is for reading. Writing, running, refining, promoting, declining, approving a held run, waiving and overriding stay hm eval … commands and Principal acts on the land card. It reads only while it is on screen, and refreshes when an eval.* event lands.

Jev, opt-in, for judging and for routing skills

A Works can opt in to Jev in the [jev] table of hm.toml. It is off by default, and a Works that has not opted in never reads the key or touches the network. When it is on, Jev classifies review comments, grades judge checks, and — with skill_routing set — chooses which skills a dispatch's prime names. Any Jev failure falls back and records why.

Without Jev, the prime's skill routing is sharper on its own: it reads an excerpt of the ticket's note, names a skill once, and names at most three.

Doctor reads each bench's own land mode

hm works doctor reads the land mode each bench resolves to and names the tier that set it, with the command that changes it. It also reports two new cases: benches of one repository that land differently, and a bench holding a local land its remote lacks.

What ships

Two CLI tarballs — hm-0.1.61-aarch64-apple-darwin.tar.gz and hm-0.1.61-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.61_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.61-nightlythe desktop Overview gets an Evals pane

This cut is a published nightly. It is tagged v0.1.61-nightly, carries the same three artifacts a stable release carries, and moves the nightly channel at dl.hmcortex.com. The latest channel stays on v0.1.59: a nightly is a prerelease and the receiver refuses it on the stable channel.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.60-nightly.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.61-nightly.

Highlights

An Evals pane beside Skills on the desktop Overview

v0.1.60-nightly brought evals to the CLI. This cut gives them a place on the desktop. The Overview has an Evals tab next to Skills, laid out the same way: a library of every eval the Works knows — its own and its benches' packs, the Works copy winning where a slug is in both — with each eval's criticality and how many of its checks are critical, a search box, and a docked reader that opens an eval in full, every check with its grader and criticality.

When refine candidates are waiting, the pane says how many and stages hm eval candidates in the composer. A Works on file persistence has no candidate store; the pane says so beside the list instead of failing.

The pane is for reading. Writing, running, refining, promoting, declining, approving a held run, waiving and overriding stay hm eval … commands and Principal acts on the land card. The Ticket screen's Evals pane, which shows each ticket's latest result, is unchanged.

The pane reads only while it is on screen, and refreshes when an eval.* event lands.

What ships

Two CLI tarballs — hm-0.1.61-nightly-aarch64-apple-darwin.tar.gz and hm-0.1.61-nightly-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.61-nightly_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with nightly moved to this tag and latest left where it stands.

v0.1.60-nightlya Works can grade its own tickets with evals

This cut is a published nightly. It is tagged v0.1.60-nightly, carries the same three artifacts a stable release carries, and moves the nightly channel at dl.hmcortex.com. The latest channel stays on v0.1.59: a nightly is a prerelease and the receiver refuses it on the stable channel.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. The bundle is ad-hoc signed, as on v0.1.59.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.60-nightly.

Highlights

Evals: a corpus of checks beside the skill corpus

An eval is a named set of checks a ticket's work is graded against. It lives in the Works as kind=eval, or in a bench pack at evals/<slug>/EVAL.md, and is written, rolled back, superseded and retired the way a skill is. Each check names its grader — a command, a rules check, a judge, or a human — and a criticality.

A dispatch freezes the evals that apply to the ticket, at their current revision, and the prime lists them under ## Evals. A hand that disagrees with a check can say so with hm eval flag.

The eval runner grades completed tickets, and a critical failure holds the land

After a ticket completes, the eval follower grades the committed tip in a scratch worktree, against the frozen revision, and records the result on the ledger. hm eval status shows where a ticket stands, and the desktop's Ticket screen has an Evals pane.

A failed or pending critical check stops hm gatehouse land before any git work. Only the Principal can override it, with a reason the ledger keeps. A failed standard check lands with a warning.

When an eval's history shows it is wrong — an override, an accepted flag, a review comment that cites it — hm eval refine drafts a candidate revision and hm eval round raises one for each eval that crosses the bar. A candidate never binds until the Principal promotes it. hm eval calibration reports how often each check agreed with what happened next.

Jev, opt-in, for judging and for routing skills

A Works can opt in to Jev in the [jev] table of hm.toml. It is off by default, and a Works that has not opted in never reads the key or touches the network. When it is on, Jev classifies review comments, grades judge checks, and — with skill_routing set — chooses which skills a dispatch's prime names. Any Jev failure falls back and records why.

Without Jev, the prime's skill routing is sharper on its own: it reads an excerpt of the ticket's note, names a skill once, and names at most three.

Doctor reads each bench's own land mode

hm works doctor reads the land mode each bench resolves to and names the tier that set it, with the command that changes it. It also reports two new cases: benches of one repository that land differently, and a bench holding a local land its remote lacks.

What ships

Two CLI tarballs — hm-0.1.60-nightly-aarch64-apple-darwin.tar.gz and hm-0.1.60-nightly-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.60-nightly_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with nightly moved to this tag and latest left where it stands.

v0.1.59the macOS app is bundle-signed, so the download opens instead of reading "damaged"

This is the first published release since v0.1.47. The nightlies between them were local cuts with no downloadable asset, so upgrading from v0.1.47 brings their work too: councils removed with the petition ladder kept, first-class review comments on ticket outputs, and opener windows that ship inside the app.

The reason for this cut is the macOS app. The v0.1.47 DMG carried an hm.app whose bundle had no signature of its own, only the linker's signature on the executable. A copy downloaded through a browser was therefore refused with "“hm” is damaged and can’t be opened", a dialog that offers no way through. This release's bundle is ad-hoc signed, and codesign --verify --deep --strict passes on it.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.59. The published macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

Highlights

The app is signed as a bundle, and the DMG check proves it

tauri.conf.json now sets the bundle's signing identity to -, so the bundler signs the sidecars and then the app, inside out. desktop-dmg-check.sh mounts the DMG and runs codesign --verify --deep --strict on the app it finds, so a DMG whose bundle does not verify fails the build instead of shipping.

Ad-hoc is not Developer ID. The signature names no developer and is not a Gatekeeper credential, so the first launch of a downloaded copy still stops. The dialog is now "“hm” Not Opened", which Privacy and Security can clear with Open Anyway, and no longer "damaged". docs/runbooks/install.md names both dialogs and what each one means.

Councils are removed, and the petition ladder stays

The council product is gone. The approval it gated is not: petitions are a destination of their own, with a rendered detail view and whole-row click, and comment-on-record is kept.

Review comments live on outputs, not only on changes

A ticket's outputs carry first-class review comments. Every output row has the same review bubble a change row does, a review sheet offers two ways out, and the handoff reports what it actually did. Comment ids are unique across the Works, so the first comment on a second ticket saves.

Opener windows ship inside the app

hm-opener ships inside hm.app. Excalidraw, video and Markdown open for review inside the app as well as in a browser, and a review session names the repo its resource is relative to, so a comment lands in the Works that opened it.

What ships

Two CLI tarballs — hm-0.1.59-aarch64-apple-darwin.tar.gz and hm-0.1.59-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.59_aarch64.dmg (production account plane, ad-hoc bundle-signed), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.58-nightlyOutputs get review bubbles, and a second ticket's first comment saves

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.58-nightly.

Highlights

Every output row has a review bubble

An output row on the ticket screen now carries the same comment bubble a change row does: a bare glyph when nothing is open, a count when something is. It opens the floating thread on that row, reading the comments filed against that output. Review and hand off from an output scopes the review sheet to exactly that output's comments. The app cannot yet start a new comment on an output, so the bubble says so and names the --output flag instead of offering a compose box that would post to the wrong place.

A cleaner floating thread

The floating thread has a header that stays put while it scrolls, state chips, rows that highlight under the pointer, and the two exits — Via Superintendent and Direct — side by side as cards, with the recommended one marked. The buttons, keys and placement behave as before.

Comment ids are unique across the Works

A comment id is unique across the whole Works, but the next id was counted within one ticket. The first comment on a second ticket therefore reused an existing id and failed to save, which showed up as an opener window exiting with an error when commenting on a video or other output. Ids are now allocated across the Works, and a regression test covers two tickets.

What ships

One local macOS DMG — hm_0.1.58-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. The same build rebuilds the Excalidraw, video and markdown host bundles inside hm-opener. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.57-nightlyReview comments hand off honestly, and a batch can go straight back to its hand

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.57-nightly.

Highlights

The ticket screen keeps up with its own review

The ticket screen now repaints when an event about its own ticket lands, of any kind. Comments, badges, the handoff chip and an open thread refresh in place, without rebuilding the rows. A comment's thread opens inside its bubble rather than in a separate drawer, and Escape steps back from thread to bubble to closed. The bubble is placed from the comment's own button, so it no longer jumps to the corner of the window.

A review sheet with two ways out

Review a ticket's comments on one sheet, grouped by the file they are on. Tick the ones to send, then pick an exit. Via Superintendent hands the batch to the Superintendent's panel as a draft. Direct sends the batch straight back to the seat that did the work, as a repair ticket that resumes the reviewed hand's session while that session is still the Line's last run. Direct refuses before it writes anything if the run is still live, a Hand Session is open, or the seat is gone, and the button says why.

In a thread you can edit or withdraw your own open comments until they are routed. After routing, a chip in the header shows what happened to the batch and links to the repair, and the sheet offers Accept and Reopen.

The handoff tells the truth

Comments written from the app or an opener window are authored by the Principal, not by whichever seat launched the app. A ticket shows a pending handoff only when every open comment is in a routed batch. Praise and comments that ask for nothing are held back from the repair and shown apart, and the repair's brief reads exactly the batch it was cut for.

The Works database moves to version 16 to hold the two new comment intents.

What ships

One local macOS DMG — hm_0.1.57-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. The same build rebuilds the Excalidraw, video and markdown host bundles inside hm-opener. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.56-nightlyMarkdown opens in the app, and video comments go to the right Works

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.56-nightly.

Highlights

Markdown documents open for review

A .md or .markdown output now opens in its own host, in a browser tab or inside the app. The document is shown read-only, and a comment on it is a file-level review comment on that output. The host renders with raw HTML switched off, so a document under review cannot run script in a window that can write comments. It reads only the session's own resource, and reaches hm through the same transport as the Excalidraw and video hosts.

A video comment lands in the Works that opened it

A comment on a video in the app was written with no Works named. The app inherits its launcher's environment, and an inherited HM_WORKS beats the working directory. So a video opened for one Works could play, and then its comment went to another, which answered that no such output was registered. The app now names the session's Works on every comment, the same way the browser loop already did.

What ships

One local macOS DMG — hm_0.1.56-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. The same build rebuilds the Excalidraw, video and markdown host bundles inside hm-opener. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.55-nightlyopener hosts work in both windows

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.55-nightly.

Highlights

Excalidraw and video open inside the app, not only in a browser

The previous cut put hm-opener in the app, but a host running in it could not reach hm. The shell has no server: it answers any path it has no file for with the host's index.html and a 200. So the Excalidraw host's request for its session read a page of HTML, and the parse died on the first <. The video host had the same hole for its session, its media and its comments.

Both hosts now go through one shared transport, connectors/opener-host. It asks which window it is in. Inside the app it calls the shell's own commands and streams media over hm-media://. In a browser tab it uses the routes the browser loop always served. A host no longer reaches hm any other way, and a route that answers with a page instead of data is named in the error rather than surfacing as a bare parse failure.

The Excalidraw canvas keeps its size

Excalidraw 0.18 ships its stylesheet apart from its script, and the host did not import it. Without it the canvas grew without bound and cleared itself after a stroke, in both windows. The host imports the stylesheet now.

What ships

One local macOS DMG — hm_0.1.55-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. The host bundles inside hm-opener are rebuilt by the same build. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.54-nightlythe app carries its own opener window

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.54-nightly.

Highlights

hm-opener ships inside the app

The app bundle now holds three programs instead of two: hm-desktop, the hm it drives, and hm-opener beside them in Contents/MacOS/. hm looks for the shell as a sibling of itself, so a Principal who installed the DMG can move a Works to the mini-app runtime with hm opener default --runtime mini-app and nothing else — no node, no checkout of this repository, no shell path in hm.toml.

The shell is compiled with every opener host the build knows, which today is Excalidraw and video. The DMG check now expects exactly those three programs in Contents/MacOS/ and runs the shell, and the sidecar step refuses to build a shell whose hosts are not built, because that shell would open an empty window.

An opener can run in a browser or in a mini-app

A Works can aim an opener at the browser loop it had or at the compiled shell, and neither is a second contract. Both are handed the same review-grammar/1 session file, and both write a comment back by spawning the hm that session names with the body on standard input. Only the program changes.

The choice is stated in two places and the more specific one wins: a registration that names its runtime keeps it, then the Works default in the [openers] table of hm.toml, then the browser. hm opener inspect prints the program a launch would start and where that choice came from. A Works aimed at a shell that is not there fails the launch and names the path. It does not quietly open a browser, because a Principal who chose a runtime is entitled to be told they did not get it.

The shell itself is one Tauri binary with its host picked by --host. What it decides — the arguments, the session, which bytes a request may read, ranges, the write-back call — is plain Rust tested without a webview. It serves only rows the session listed and hm verified, and it spawns exactly one program: the hm the session names.

Also in this cut

The Works database moves to schema v15. The openers table gains a runtime column, and an empty value means the row states nothing rather than browser, so one edit to the Works default can move every opener that stated nothing.

What ships

One local macOS DMG — hm_0.1.54-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.53-nightlythe review session names its own repo

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.53-nightly.

The number after 0.1.51-nightly is 0.1.53-nightly because 0.1.52-nightly was a separate local cut whose own note travels with its own manifest. The work that cut carried — the outputs registry, the desktop Outputs group, session file v2, the video opener and the Review with… wire — is in this build too, because it landed on main before either nightly was bumped.

Highlights

A review session names the repo its resource is relative to

A Review with… session carried a resource and a Works root, and the resource is a path out of the ticket's diff — relative to the repo that diff was read from, which is the manifest's worktree rather than the Works. A host had to guess between the two, and for a ticket that is complete and not landed the guess has no right answer: the file is committed in the manifest's worktree and in no other checkout, so the review that decides whether it should land could not open it until it had.

hm now writes the root itself. The session's extras gain review_root, filled from the same read the change list is and canonicalized — the worktree while it exists, the bench once reclaim has pruned it. There is no flag that passes one in: the desktop shells out to hm opener launch and sends nothing, so an absolute path never travels a hop out of the webview. An output launch writes no root, because an output's resource really is Works-relative.

A host that is given a root resolves under that root and under nothing else. A session without one falls back to Works-then-bench, which is every session written before the field existed and every output launch.

The Excalidraw opener hosts a session

The Excalidraw connector now reads the session the launcher hands it rather than a query string. launch.sh puts the session file in HM_OPENER_SESSION and opens the root path — a browser on localhost could never fetch a file URL off the query string anyway, which is why this host used to sit on its empty state with a good session beside it.

The dev server gains three routes. One returns the session record, re-read on every request, so the page gets the record and never the file's own path; it answers 503 when the environment variable is unset and only then, so the empty state cannot stand in for a broken launch. One opens the single resource the session names, tried against the session's roots with a containment check on each and taking no parameter at all, so anything trailing the route is refused. One writes a comment back by spawning the session's own hm with the anchor the session carries and the body on standard input, so a body that opens with a dash survives.

Failures land on screen instead of in a console nobody opened, and the generic argv proxy that no server config ever declared is gone.

A decision pack on opener mini-apps

A research-only decision pack weighs hosting opener connectors as Tauri mini-apps. The verdict is a hybrid: one opener sidecar hosting the built connector bundles, with the review-grammar/1 session file and the comment contract left unchanged. No opener code, no lockfile and no registration changed for it.

Also in this cut

The desktop's command module imported one half of the pair it derives, so the shell feature would not compile. It is a one-line import. Nothing in CI builds that feature, so the break shipped green on main and only a DMG build could see it.

What ships

One local macOS DMG — hm_0.1.53-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.51-nightlymicro work-comments

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.51-nightly.

Highlights

First-class review comments on ticket outputs

Ticket outputs now carry review comments as a first-class record. A new review_comments table (migration v11) stores comments keyed by ticket and artifact. The event stream gains review.comment.* events, and the CLI exposes hm review comment and hm review artifact to write and query them. The prime document includes a Review section between Note and Mail, so a dispatched hand sees prior review feedback before it begins. A grammar version review-grammar/1 governs the comment schema.

Review handoff and rules classifier

Review comments are handed off to the next ticket on a manifest via a batch handoff with a typed classifier. The rules classifier always runs; the Jev classifier is optional. A review_classifications table (migration v12) stores the classification output. The Superintendent routes handoff results — redrive for actionable comments, fresh dispatch otherwise. Landing a ticket with open review comments produces a warning. Abandon, cancel, and supersede derive a moot classification so the comment trail closes cleanly.

Resource bubble and drawer with opener registry

The desktop gains a resource bubble and a resource drawer. The bubble surfaces a resource preview inline; the drawer opens from it with full detail, including a Super card. An opener plugin registry backs the drawer: each resource type declares an opener, and the registry resolves which plugin handles a given resource. The registry is extensible — new openers register without modifying the drawer.

Opener plugin SDK via hm-connectors

A new hm-connectors crate provides the opener plugin SDK. Opener plugins implement a trait that declares what resource types they handle and how to render them. The SDK is a compile-time dependency, not a runtime one — plugins are statically linked into the desktop binary. The crate ships alongside the existing workspace crates and follows the same versioning.

Excalidraw embed host

An Excalidraw embed host uses @excalidraw/excalidraw to render .excalidraw files inside the resource drawer. The host is an example of the opener plugin SDK: it registers for the Excalidraw resource type and renders an interactive canvas. The embed is read-only in this cut — editing is out of scope.

Settings Integrations and Plugins inventory

Settings gains an Integrations tab that shows the installed plugins and their status. The tab reads from the opener registry and from the connector configuration. Each row names the plugin, its resource types, and whether it is active. This is an inventory view — enable and disable controls are not part of this cut.

What ships

One local macOS DMG — hm_0.1.51-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.50-nightlypetitions UI cleanup

This cut is a local production nightly. It is not tagged, carries no downloadable asset, and moves no channel. The image is installed on the operator's machine and nowhere else.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com.

CLI and desktop stay one version. The DMG on this build is 0.1.50-nightly.

Highlights

The file composer is gone from petitions

The petition detail view no longer carries the file composer — the form that let a user attach a file from within the petition screen. The composer was unreachable in practice (no petition flow called it) and its presence suggested a capability the view did not intend. It is removed rather than hidden.

Bubble-up petition detail with rendered markdown

The petition detail is now a Skills-style bubble-up reader floater rather than an inline expansion. Selecting any petition row opens a floater panel that renders the petition body as markdown — the same rendered treatment the rest of the desktop gives to authored prose. The raw source is no longer shown.

Whole-row click

Petition list rows respond to a click anywhere on the row, not only on the title text. This is the same interaction model the Skills list already uses. The click target is the row element itself, so no invisible link or button stretches across it.

Comment-on-record kept

The comment-on-record field — the reason attached to an approval or a decline — is kept as-is. It was not part of the cleanup; it is called out here because removal of the file composer might suggest a broader teardown that did not happen.

What ships

One local macOS DMG — hm_0.1.50-nightly_aarch64.dmg — installed to /Applications/hm.app and ~/.local/bin/hm. No CLI tarballs, no tag, no release workflow, no download channel update.

v0.1.48-nightlythe council product is gone, the petition ladder stays

This cut is a nightly, and it is the first one published as a release. Earlier nightlies were local images with no tag and no downloadable asset; this one is tagged v0.1.48-nightly, carries the same three artifacts a stable release carries, and moves the nightly channel at dl.hmcortex.com. The latest channel does not move: a nightly is a prerelease and the receiver refuses it on the stable channel.

It is a removal release. Two products leave hm — councils in full, and the hm feed reader — and one screen survives the first of them under its own name. Nothing is deprecated in place: the code, the commands, the tools and the prescriptions in the documentation are deleted, so there is no half-retired surface for a reader to find and trust.

The macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not the published image.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.48-nightly. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

Councils are removed, and the approval they gated is not

The council was a deliberation surface layered over a mechanism that never needed it. The mechanism is the petition ladder, and it is untouched: a petition is drafted, filed, approved or declined, and the ledger records who did which. What is gone is the room the ladder used to be reached through.

So the whole family leaves at once. In the core: the council module and its seats store, the council host, the council session address and every match arm it owned, the council directories under a Works, and the council field on a Reckoning record. In the CLI: the hm council command and each of its subcommands, and the council header hm reckoning list used to print. The command catalog loses the row, which is what removes the five hm.council.* tools from the agent surface as well — an agent can no longer call what a person can no longer run.

The one rule that lived behind the council is kept and only reworded. Applying an agents template still needs an approved petition; the message says so in those words now, in the command, in the doctor finding and in the template.

Petitions is a destination of its own

In the desktop, the council rail is gone and the Chamber is re-homed as Petitions, on the nav rail beside everything else. This is a rename and a re-parent rather than a rewrite: the screen called hm petition before and it calls hm petition now.

Decline joins Approve on the filed rung. It is two steps, and it refuses an empty reason, because the reason is what the ledger records.

The deliberation thread, the Clerk records body and the four council commands behind them are removed with the rail. The shared timeline primitive they left behind stays, with the ladder as its one reader.

The feed is deleted, and the line it rendered survives

hm feed is gone — the command, the core module, its end-to-end smoke test and the terminal-drawing dependencies only it used. It was a second way to read what the ledger already answers.

The one part worth keeping moved out first. Every feed row was a one-line summary of a ledger event, and activity already projected the same line onto its own rows, so that summary is now a module of its own with both readers importing it. A kind is spelled one way wherever it is printed.

The documentation stops prescribing the feed. The agent guide, the architecture reference, four runbooks and the teaching walkthrough name hm query events, hm events tail and hm manifest activity instead, and the two plans that introduced the feed carry supersession notes.

hm feedback and hm reckoning run --feedback are a different thing with a similar name, and they are untouched.

What ships

Two CLI tarballs — hm-0.1.48-nightly-aarch64-apple-darwin.tar.gz and hm-0.1.48-nightly-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.48-nightly_aarch64.dmg (production account plane), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with nightly moved to this tag and latest left where it stands.

v0.1.47the download is a public address, and the Superintendent reads the machine

This is the first published release since v0.1.38. The eight cuts between them were local nightlies with no GitHub assets, so upgrading from v0.1.38 brings their work too: a public downloads tree with one door into it, an Overview that reads the machine instead of restating the board, a hand lifecycle that resumes, and a gopass prompt that reaches a GUI pinentry.

It is also the first release whose publish job copies the release into the public tree at dl.hmcortex.com. Nothing about the GitHub release page changed — the same tarballs, the same DMG, the same SHA256SUMS — but the same bytes now sit at a stable address that needs no account, no token and no repository to reach.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.47. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH. The published macOS app talks to production — auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com; staging is an opt-in bake (make desktop-dmg-staging) and it is not the published asset.

Highlights

The download is public, and the gate is gone

Releases publish to dl.hmcortex.com under one grammar — latest for a plain vX.Y.Z, nightly for a prerelease — with stable asset names beside the versioned ones, so a link written once survives the next release. The public install script is the one door into that tree: it reads HM_DOWNLOADS_URL, takes a channel through HM_CHANNEL or a pin through HM_VERSION, and refuses outright when SHA256SUMS or its own checksum line is missing. The token-gated GitHub-API and HM_DOWNLOAD_* paths are deleted rather than deprecated.

The account plane lost the gate with them: the download module, the grant and channel stores, six routes, every DOWNLOAD_ key and the membership SQL that guarded them are gone, and boot serves idempotent drops in reverse reference order instead. The landing page and the docs point at the tree, and a copy check refuses the retired access vocabulary over every rendered page — so no reassuring sentence about who may download outlives the door it described.

The publish itself is a job of its own on the release workflow, in its own GitHub Environment, holding one receive-only key and no cluster credential. It runs only after the private release has passed every assertion, sends the same bytes under stable names, and checks each one against the release's own SHA256SUMS before anything is sent. The receiver stages into a hidden directory, verifies, and only then renames into releases/<tag>: a half-sent or refused upload is visible at no address. A channel moves forward only, and a tag that is already published is refused — a fix ships as a new tag.

Overview reads the machine rather than restating the board

Ten regions that were a second view of the board are gone. Readiness and Flow answer the question instead of asking it, Seats and Organization are the board on the seat axis, and Spend reports a Works rather than a laptop. Skills stopped assuming the corpus was used and started measuring it.

A hand that resumes, and a unit that can be asked what it shipped

Four holes in the hand lifecycle — all of them the same shape — are closed. A ticket can be asked what happened to it, and mail says who read it. A landed unit stopped answering the wrong question about its own diff, and a landed manifest can say what it shipped.

The Superintendent panel stops freezing, and its timeline reads a week

The panel no longer freezes at maximum. Its left pane is a table of contents rather than a list of other rooms; a procedure opens in a closable floater over the Overview instead of under the list. The timeline opens on one week and can be searched, a click on a past message is one read, the Ticket screen shows its own history, and the mail thread stays. Reckonings lead with what stopped a hand.

A provider CLI resolves to one file, and the prompt reaches a GUI pinentry

gopass and op resolve through the shared agent-CLI ladder — a Works pin, HM_*_BIN, well-known prefixes, a login-shell PATH read lazily, the process PATH, then bare — and every secrets spawn goes through it. A candidate under the Works benches root, any .worktrees/ checkout or TMPDIR is refused at every rank and never spawned. A blocked pinentry is a prompt_required rather than a hang: spawns run in their own process group under a Works timeout.

Prompt-per-access replaced the unlock window, and the prompt now reaches a pinentry that can draw. hm chooses one itself — the Works pin, a GUI pinentry-program from gpg-agent.conf, the platform GUI candidates, then the same names on PATH — probes each candidate for its flavor without reading a secret, and refuses a curses, tty or emacs pinentry. For the prompted call alone it puts an owner-only shim directory at the front of PATH, and removes it when it is done; the silent read, your GnuPG and gopass configuration, and GPG_TTY are left alone. hm secrets pinentry list/probe/set/clear is how a Principal chooses and checks one, and hm secrets check --dispatch still never prompts — it names the selected pinentry and says prompt_required (supported) only when there is a GUI session and a GUI pinentry to answer it.

What ships

Two CLI tarballs — hm-0.1.47-aarch64-apple-darwin.tar.gz and hm-0.1.47-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.47_aarch64.dmg (production account plane), and a SHA256SUMS covering all three. The same three artifacts are published to dl.hmcortex.com under stable names, with latest moved to this tag.

v0.1.46-nightlya gopass prompt reaches a GUI pinentry, never curses

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.46-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

The prompt picks a probed GUI pinentry

On 0.1.45 a prompted gopass read could still fail: gopass runs a bare pinentry, and on this kind of host that name resolves to the terminal (curses) pinentry, which cannot draw on a pipe. The pty hm owns was never the problem, and it is unchanged.

hm now chooses the pinentry itself, in a fixed order:

  1. the Works pin [secrets] pinentry_bin (an absolute executable path; a relative or unusable pin is refused, never skipped past);
  2. a pinentry-program from gpg-agent.conf, only if it is a GUI one;
  3. the platform GUI candidates — pinentry-mac on macOS, the Qt, GNOME and GTK pinentries on Linux;
  4. the same names on PATH, from absolute directories nobody else can write.

Every candidate is probed by asking pinentry for its flavor. The probe reads no secret and asks for no passphrase. A curses, tty or emacs pinentry, or one that does not answer, is refused.

A prompt-only shim, nothing global

For the prompted call alone, hm puts a private shim directory at the front of PATH so that gopass's pinentry is the chosen GUI binary. The directory is owner-only, created and retargeted atomically, checked before use, and removed when hm is done with it. The silent read, your GnuPG and gopass configuration, and GPG_TTY are left alone.

Choose and check the pinentry from hm

  • hm secrets pinentry list shows the candidates, their rank, and which one is selected.
  • hm secrets pinentry probe <path> says whether a binary is GUI-capable.
  • hm secrets pinentry set <path> probes before it writes the Works pin, and clear removes it. Both are recorded as audit events.

hm secrets check --dispatch still never prompts. It now names the selected pinentry, and says prompt_required (supported) only when there is a GUI session and a GUI pinentry. Otherwise it says prompt_unsupported and names what is missing.

A failed prompt is recorded with a redacted class only: pinentry_isatty, cli_fallback, cancelled or other. Provider output is never recorded.

What ships

The local macOS desktop DMG hm_0.1.46-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

v0.1.45-nightlya headless gopass read asks the Principal, and carries on

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.45-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

Prompt-per-access replaces the unlock window

The contract is now the one the Principal expected: any headless gopass read may ask for the passphrase, and the request carries on once it is answered. Every read still runs silently first, so a warm age agent answers without a dialog. Only gopass's terminal-read failure escalates, and only on a host with an Aqua login session; without one the read fails as prompt_unsupported, with guidance to use op or env for unattended runs. There is never a curses pinentry.

On escalation hm takes a per-user lock, re-checks the silent read in case another hand's prompt already warmed the agent, and only then re-runs the gopass child alone on a pty hm owns, in its own session and process group, so pinentry-mac can show. The hand itself never gets a terminal. The prompt has a deadline, [secrets] prompt_timeout_seconds (default 120, clamped to 10–600); on the deadline or a cancel the whole group, pinentry-mac included, is killed. One answered prompt keeps every later read silent for gopass's own agent timeout, and hm leaves that setting alone.

The per-Works unlock window is retired: hm secrets unlock, its TTL and auto-lock are gone, and hm secrets status removes a stale ledger/secrets_unlock.json. hm secrets lock stays, and status now reports the agent as running (locked) when it is.

Dispatch checks tell the truth without prompting

hm secrets check --dispatch never prompts. It classifies each key as warm, prompt_required (supported) or prompt_unsupported; the first two pass. Dispatch is refused only when the host has no GUI session and a silent read of the seat's first key fails.

The Desktop names who is asking

Each prompt is recorded as secrets.prompt.shown, .resolved, .timed_out, .cancelled or .unsupported, attributed to the Works, manifest, ticket, seat and provider. No key path, value, passphrase or provider output is recorded. The rail shows Passphrase requested with the ticket and hand while the dialog is up, then the outcome for 15 minutes. It reads only the ticket, seat and outcome fields.

What ships

The local macOS desktop DMG hm_0.1.45-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

v0.1.44-nightlydownloads leave the gate, a provider CLI resolves the same way twice, and a Superintendent can abandon

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.44-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

The download is public, and the gate is gone

Releases publish to dl.hmcortex.com under one grammar — latest for a plain vX.Y.Z, nightly for a prerelease — with stable asset names beside the versioned ones. The public install script is the one door into that tree: it reads HM_DOWNLOADS_URL, takes a channel through HM_CHANNEL or a pin through HM_VERSION, and refuses outright when SHA256SUMS or its own checksum line is missing. The token-gated GitHub-API and HM_DOWNLOAD_* paths are deleted rather than deprecated.

The account plane lost the gate with them. download.rs, the grant and channel stores, six routes, every DOWNLOAD_ key and the membership SQL that guarded them are gone, and boot now serves four idempotent drops in reverse reference order — those were run against a real Postgres 16, on a fresh database, on a cleaned one and on one built to the pre-cutover shape, and a second boot is a no-op each time. The landing page and the docs point at the tree: two 302s — the macOS download and the install script — and a copy check that refuses the retired access vocabulary over every rendered page, so no reassuring sentence about who may download outlives the door it described.

A provider CLI resolves to one file, and says which

gopass and op now resolve through the shared agent-CLI ladder: a Works pin, HM_*_BIN, well-known prefixes, a login-shell PATH read lazily only after the first three miss, the process PATH, then bare. Every secrets spawn goes through it — secrets check, check --dispatch, the MCP proxy, a webhook auth_path, mcp inspect, agent status, lock, unlock and auto-lock — and hm secrets list/check/status print the path and rank they landed on, the same line the doctor finding carries.

A candidate under the Works benches root, any .worktrees/ checkout or TMPDIR is refused at every rank, named with its rank, and never spawned: there is no bare fallback behind that refusal. The Works can pin the file outright with [secrets] gopass_bin / op_bin, absolute or ~/-anchored so one synced hm.toml names the same install on every machine it reaches; a relative path is refused rather than resolved against a cwd two processes do not share. A bad pin is the one error in the secrets doctor family (secrets.provider_bin_invalid), and it can only fire on a Works that wrote the key.

A blocked pinentry or an unanswered 1Password dialog is now a prompt_required rather than a hang: spawns run in their own process group under [secrets] resolve_timeout_seconds (default 20, ceiling 120), and the group is killed when it expires.

A ticket summary reads as a summary

Completion and progress notes arrive as one flat line, so the desktop drew them as a single paragraph. A flat note now gets a paragraph per sentence, a bold lead label, and a list when a labelled run carries three or more semicolon parts. A note that already has its own line breaks passes through verbatim.

hm ticket abandon is on the MCP catalog, under policy A

The verb worked at a terminal but reached no agent surface: the catalog carried no abandon row and the tool list is generated from the catalog and nowhere else. It is tool 151 now, with the lifecycle paragraph — what it pins, what it refuses, who may call it — and a Recovery subsection for a stranded complete ticket.

The actor gate used to refuse every seat:* caller, which is what hm mcp serve always is. Policy A: the Principal or a seat carrying the Superintendent role may abandon; a Hand may not, and neither may a seat the roster does not hold — so the accidental discard this guard exists for, the seat that executed the work retiring it, stays walled out. The mandate is read from the roster's role rather than the shape of a seat id, so superintendent-<line> stays a convention and not a password. The manifest-note author gate asked the same question privately; both callers now share org::seat_is_superintendent.

What ships

The local macOS desktop DMG hm_0.1.44-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

v0.1.43-nightlythe timeline reads a week and searches, and a Works can land without origin

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.43-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

The timeline pane no longer reads the whole session. It opens on the sends within seven days of the newest one, at least 20 and at most 200, newest first, and Show older reads one more page per press. The heading counts what it holds. On a 49.6 MB record the newest page reads in 36 ms, where the whole session took 116 ms.

A search box sits above the rows. It matches every term, case-insensitively, against the full body of the Principal's own sends, newest first, with a one-line snippet per hit, 50 at a time and Show older matches after that. Escape clears it. Typing in it never refreshes the timeline, and the rows still carry no message text.

A click on a past message is one read

The 0.1.42-nightly note left this open: a click on an older send read the record 500 rows at a time and mounted every turn after it, and for 72 of 669 older sends the scroll landed on the wrong one. In this image a send that is not already mounted is fetched with a single read of the page that ends at it, shown read-only and labelled as this conversation, and a client message id is only matched against turns the record has not yet numbered — so another launch's id can no longer claim the scroll.

A Works with no origin can land

A Works can declare [gatehouse] land_mode = "local". hm gatehouse land --git --approve-push then merges the manifest branch and advances the local refs/heads/<landing> itself: a compare-and-swap update-ref when the branch is checked out nowhere, merge --ff-only in a clean worktree that has it checked out, and a refusal naming the worktree when that worktree is dirty or the tip moved. Nothing is fetched or pushed, and a refusal leaves no gatehouse events. A local land records gatehouse.integrated and prints "not pushed". --verify-only, resolve and abandon work under the same mode.

Dispatch and hm bench branch now cut hm/<manifest> on a checkout with no origin, basing it on the local landing branch, so a hand there no longer spawns detached and hm bench commit no longer refuses.

Doctor gains three findings: a pr or direct Works whose bench has no origin, a local Works whose bench has one, and a Landed manifest whose branch never reached the landing ref. A Works that has not declared a mode gets a warning, not an error. Bench freshness, hm bench sync and hm works settle compare against the local landing ref under local. The runbook is docs/reference/runbooks/gatehouse-local-land.md.

Reckonings lead with what stopped a hand

The Reckonings screen opens on a findings list, one row per stop: signal, when, unit, seat, repeat and redispatch counts, the hand's first line and a ruling chip. The rulings timeline folds shut beneath it. A finding reads in a floater with its evidence in stream order and same-signature repeats; Rule… opens the reckon drawer prefilled, and Ask stages a line in the Superintendent composer. Nothing writes from the floater.

The rail's Reckonings item carries a badge counting unseen findings, and opening the screen clears it. The decompress-notes column only appears when an advisor is configured.

What ships

The local macOS desktop DMG hm_0.1.43-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

v0.1.42-nightlythe freeze fix reaches the image, and a procedure reads in a floater

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.42-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

The Superintendent panel stops freezing at maximum

0.1.41-nightly was bundled before the fix landed, so the freeze reported on it was the same freeze. The panel's message timeline is a keyed list over every send in the session, and it was keyed on a client message id minted by a per-process counter. The record outlives every process, so the ids repeat — on this Works 651 sends carry 136 distinct ids — and Svelte throws on a duplicate key in production as well as in development. The throw escaped the update, and every later update died the same way for as long as the pane was mounted: the whole window frozen, Close included. Maximum and a wide drag are the two geometries that mount the timeline.

In this image the timeline keys on the record's own sequence number, a new send's id carries a per-launch nonce, a scroll target resolves by sequence first, and the pane sits inside a boundary whose failure is the pane's own empty line, so a throw inside it can no longer take the window down.

Not in this image: a click on a past message still reads the record 500 rows at a time and then mounts every turn after that send, and for 72 of 669 older sends the scroll lands on the wrong one. That fix is carved as its own ticket and is not built yet.

A procedure reads in a closable floater over the Overview

Selecting a skill in the Overview's skills browser opens its document in a floating reader over the pane, no longer appended under the list. The list stays live and another row opens in the same floater. The close button, a second click on the open row, or Escape dismisses it and returns focus to the row. The reader's head names where the procedure is held, and keyboard focus survives the list redrawing.

The 0.1.41-nightly note described a docked second column as decided. What shipped is the floater; the docked grid is not in this image.

The Ticket screen shows its own history, and the mail thread stays

The Ticket screen reads the ticket's own ledger activity. The mail thread used to live inside the composer, which hides as soon as a run stops being live, so the thread and its unread state vanished on exactly the tickets where unread mail collects. The messages are now drawn under every route; the composer is still gated.

A landed manifest can say what it shipped

hm manifest changes answers for a whole manifest what hm ticket changes answers for one ticket: the git range, the artifacts the ledger pinned at land, and a per-ticket breakdown that keeps a row for a ticket that shipped nothing. The desktop's unit view gains a Resources section on the same read.

That desktop half landed on main and was then dropped by a later merge before any image carried it, which left main unable to build the app at all. This cut restores it as it landed, so this is the first image that has it.

Settings ▸ Automation drops its audit list

The Automation pane shows its three toggles and nothing else. The audit list under them is deleted end to end, desktop command included; the journals it read stay on the ledger. A gatehouse merge briefly brought two files of it back, and that repair is in this image too.

What ships

The local macOS desktop DMG hm_0.1.42-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

v0.1.41-nightlythe cut that makes the fix its own release, and a procedure that asked for a second column

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.41-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

The compile fix that unblocked the last cut is now a fact on main

0.1.40-nightly could not be bundled from a clean tree. hm-desktop calls hm_core::setup_runtime_offers() from src/setup.rs and hm-core never re-exported it at the crate root, so the shell failed to compile with E0425 — a break introduced by the hand-lifecycle work that landed with the offers themselves. The re-export landed after 0.1.40-nightly's note was written, which left the note describing an image the repository could not reproduce.

This is the version where the two agree. The code in this bundle is the code in 0.1.40-nightly's Highlights, built from a tree that compiles without a repair on top of it, so the note and the image name the same thing.

A procedure opens beside the list, not under it — decided, not yet built

The desktop Overview's skills browser appends a procedure's whole document below the list, the withheld line and the findings, at the bottom of a single 1000-pixel column, and nothing scrolls the reader to it. Reading one procedure therefore costs the reader the list and their place in it.

The cause is layout and nothing else — the per-slug read is already complete and already bridged — so the decision pack that shipped this cut says split the browser into a list column and a docked reader column, using the grid idiom the Reckonings and Council screens already use and its 840-pixel collapse. List virtualisation is deferred: at the measured corpus size it would buy nothing and cost a scroll position.

The pack is research. No UI, no bridge call, no core change and no golden re-captured — it reads the shipped pane and says what the next ticket should build. It is on record at docs/reference/research/overview-skills-side-pane/.

What ships

The local macOS desktop DMG hm_0.1.41-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

Nothing in the running surfaces changed between 0.1.40-nightly and this image. A Principal already on 0.1.40-nightly gains a version number that matches a buildable tree, and nothing else.

v0.1.40-nightlya hand that resumes, a conversation with an index, and a unit that can be asked what it shipped

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.40-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

Four holes in the hand lifecycle, all of them the same shape

hm knew the answer in each of these and no shipped path could reach it.

A dispatched Codex hand wrote an opaque runs/<ticket>.log in every build that could dispatch one, because its stdout parser sat behind the codex-executor feature with only the live session calling it. The parser is ungated now and lives beside the others. Opencode was missing both halves — its adapter asked for no structured output at all, so there was nothing to parse even in principle; it asks for JSON now and a new adapter reads what it writes.

Resume was one stored column read by two different lanes, which is how a Cursor seat's captured chat id could have been handed to a Claude resume flag. It splits. Dispatch resume works for every runtime — a redispatch re-attaches to the conversation that ticket's own last run left, in each runtime's spelling — while the live Hand Session stays Claude's alone, because that is the argv the host builds. Capture is dialect-aware, where before it was the Claude reader alone and a Codex hand's thread id sat in its own log with nothing able to reach it.

The pre-Works setup conversation was Claude and Codex only, for a real reason: three hosts register the bridge by writing a config file into the directory they run in, and that directory is a prospective path which must survive byte for byte if the seal never comes. The invariant was right and the premise was not — a file host has to write its config somewhere, not into the directory under discussion. It runs in a scratch workspace on the device plane now, and the prospective path reaches it as the bridge's own argument, which is the only handle a setup conversation was ever supposed to act through. All five host it; the test asserts both halves on every one.

What is left is packaging, and it says so rather than going quiet: hm names every runtime it knows with its own sentence about why one is not offered, the desktop prints that under the menu, and hm executor probe grew a setup section — the seat-less lane's readiness, which a probe that walks the seat could not answer at all.

And a Claude hand could not be dispatched at a chosen effort while two runtimes that do not ship could. Both closed, and neither passes a default: an unset level means no flag.

The Superintendent's left pane is a table of contents, not a list of other rooms

The rail listed other sessions. hm has one live session — resume replaces it and the store enforces that — so a permanently mounted column of other conversations was the affordance of a product hm is not. What a long conversation actually lacks is an index, so that is what the pane is: the Principal's own sends, each with its time, its input tokens and the delta from the previous measured send, and a click that scrolls the conversation to that message. Past sessions became a header control, read when it is opened and never while it is closed.

No percentage anywhere. There is no context-window ceiling in hm, so a meter would have to invent its own denominator — the part that goes wrong first and most silently, per provider, without a release. A turn that landed no result carries a gap, drawn as a dash and never as a zero, because zero is a measurement and absence is not.

Behind it, three bounds each got a door. The transcript read reports whether more remains rather than whether it is finished, and a page is asked for by an exclusive record-wide sequence — the key a replay and a live tail already reconcile on — so a page requested mid-stream cannot slide under an append. The live transcript's mounted window is a state that only ever grows, and when it has nothing left to grow into the same control reads the record's previous page. The read-only reading pane, which used to mount every projected turn in one frame with no window at all, gets the same window and the same door. Both restore the scroll position by exactly the height that appeared above the viewport, and a row that is listed is a row that can be reached.

The index sidecar was not built, and that is a measurement rather than a taste: the read rejects mid-turn traffic at the byte level before the JSON parser sees a line, and it costs 113 ms for 49.6 MB and 195,207 rows — nearly twice the record the research profiled — paid once per session open, off the thread.

A ticket can be asked what happened to it, and mail says who read it

hm ticket activity and hm manifest activity are one projection over the event scope and the mail queue: typed rows for mail, runs, latches, state changes and lands, newest first, with the receipt inline on a queued message. It reads and appends nothing, and it is catalogued read-only, so the bridge offers it with no approval card.

The receipt is the part that had been wrong in a way nobody could see. A receipt is any pull by a seat that has held this ticket's latch, taken from the latch history rather than from whoever holds it now — moving a latch no longer un-reads what a hand read. A Principal pull is still not a receipt. Consecutive pulls that carried nothing fold into one row, which is 67 percent of the mail volume measured on this Works, and they fold per entity so a manifest scope cannot attribute one ticket's checks to another.

Closing a unit now records unread mail as a fact — the count and the sequences, at complete, cancel and land — and never as a refusal. Unread mail blocks no close. Land asks again, because mail can arrive between the two terminals, but it does not repeat a verdict that has not moved.

A landed unit stopped answering the wrong question about its own diff

The change range for a landed unit was anchored at the land merge's first parent, which is the landing tip at land time rather than the point the work diverged. That refused 16 percent of landed ranges outright and, worse, degraded the rest into the landing branch's own drift reported under one ticket's name. The range is taken from the divergence point now and read three-dot, which is what keeps the loosened base from turning a refusal into a wrong answer.

The repo and the Line come from the append-only ledger rather than the live registry, for the reason the bug existed: land releases the manifest binding and prunes the worktree, so the registry is the wrong source for a question asked after the act that empties it. In-flight units keep the reading they had. Under a manifest-wide land the merge names no ticket at all, and the bench's own commit record supplies the tip — the only thing that narrows that merge back to one ticket.

What ships

The local macOS desktop DMG hm_0.1.40-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

The desktop surface for ticket and manifest activity is not in this image: the projection and its two commands landed, the pane did not. So the activity plane is reachable from the CLI and the bridge on this cut, and not from a screen.

v0.1.39-nightlyOverview stopped restating the board and started reading the machine

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.39-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

Ten regions that were a second view of the board are gone

Overview was measured against the loop that shipped after it was scoped, and the measurement was the case for retiring it. Four of its ledger regions re-folded the Cadence board's own partition — Insights was the board's fold re-counted, and Recently landed said so in its own comment. Six more reported this laptop under a Works header: they carried no Works, Line or seat identity, 48% of the spend they showed belonged to other products, and they classified 0 of 103 sessions as automation while the ledger held 1,174 dispatches.

Meanwhile three planes hm already knew about had no surface at all: readiness, the skill-evolution plane, and 5,595 rows of tool calls.

All ten regions are retired and the shell is re-cut as a tab strip over six panes — Readiness, Flow, Seats, Organization, Skills and Spend. The two source planes are now apart in the markup and not only in the copy: the ledger's live region stands above the panes that ride its tail, and the agent-data plane sits inside Spend, the one pane that will ever read a saved snapshot.

Readiness and Flow answer the question instead of asking it

Readiness is the digest field that had been crossing the bridge unread: this Works' own blockers and advisories, the seat every Superintendent surface is acting as, and whether that seat was found or assumed. A finding now names its fix in a field rather than in a sentence, so a webview is not parsing hm's prose for a verb. A finding whose message names no command carries none, and that row simply has no offer. The chip stages into the Superintendent composer and stops there — settle and land are Principal acts, and the ban on running one is a token check over the painters rather than a promise in a comment.

Flow is two sources and one question. The digest says which open units are not moving now; a new host read says how often this machine stalls at all and on what — patrol stalls by their own reason, the runs and lands that stopped, the reclaim work behind them, and the bridge's own failure rate, each against dispatches. It counts and never lists: nothing it returns carries a ticket, a manifest or a seat, because the moment this pane names units it is the Dispatched column drawn a second time. The window every rate is taken over rides along and is printed, because a denominator a reader cannot see is one they have to trust.

Seats and Organization are the board on the seat axis

The Cadence board pivots on the unit. Seats pivots on the seat, and the rule that keeps it one pane rather than a second answer is that nothing it returns names a unit. That rule cost something real: the ledger records no seat on a completion or a land, so credit has to travel through the unit. The fold walks the log in order holding the seat each unit was last latched to, then drops the ids once they have done their work. Scoring on the actor instead would have given every land in this Works to whoever ran the gatehouse.

The seats missing from one side of the join are kept rather than tidied away. A roster seat never dispatched to gets a row of zeroes, because zero is the answer to what it has delivered and a missing row would read as no data. A seat the ledger dispatched to that the roster no longer names keeps its row and stays in the denominator, so no rate here is counted against a smaller machine than the one that ran. Delivery divides by latches rather than by attempts: a unit retried three times and landed once is one delivery.

Organization is the Line pivot over the same read, and it counts without returning a verdict. The charter rule is that a Reckoning shows overload, and a pane that printed the word beside a seat count would be returning the ruling the Reckoning exists to return.

Spend reports a Works rather than a laptop

The retired Spend card summed every agent session on this machine under a Works header. The job was right and the scope was missing, so the scope became the gate: a real, tested mapping of working directory to Line and manifest, built by inverting the function hm already uses to make those paths and corroborated by replaying every dispatch over it. On this Works, 7 benches indexed and 1,101 dispatches replayed, 1,015 of them agreeing.

A project label is not an identity — one label covers 161 directories across two Lines here — so cost is joined through the session list, folded to label and directory and nothing else. What comes out is two figures and a denominator, because one figure would be a claim: a floor of 479.37 and a ceiling of 3,853.76 against 4,000.34 spent on this machine, with every label it could not attribute named and its evidence beside it.

The agentsview CLI is a shipped dependency now, so hm install puts it beside hm and hm reads that copy before it trusts a PATH a Finder launch never set. It is still never started.

Skills stopped assuming the corpus was used and started measuring it

A corpus can be complete and reach nobody. Nine active skills, one promoted, a round on record — and no number anywhere that would say whether a dispatched hand ever saw one. The fact that answers it was already on the ledger and unread: a dispatch carries the slugs the ranking wrote into that hand's brief, which is the only record that a procedure ever left the store. Reach is therefore a core fold on the walk the round count already took, and hm skill evolve status prints it too — a second implementation in the desktop would be a second place deciding what reach is.

It reports two shares because one would libel this machine. The lifetime figure is 17 of 1,179, which reads as a plane nobody uses; the truth is that 17 of the 17 dispatches since the first primed one carried a skill, and the gap between the two numbers is when retrieval shipped rather than how well it works. Both are on the screen with the day that divides them, and a Works whose corpus has never reached a hand gets a sentence rather than a rate, because an absence is not a low number.

What the panes are not allowed to do

The same three disciplines are held by tests in all six. No pane folds the board's partition a second time or takes a cadence count from the agent data. Each whole-ledger fold is gated on its own pane being on screen and routed only by the event families that can move a number on it, so a Principal who never opens a pane never pays for its read. And every pane links rather than judges: promote, decline, settle and land stage into the composer on one path, and the verbs are banned from the painters.

What ships

The local macOS desktop DMG hm_0.1.39-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

The divergence from the design authority this shell was cut against — no Agent health region, no Reckoning cloud — is written up as an amendment rather than taken silently. The tool-call plane the research ranked stays without a surface: it is named here as the one measured gap this revamp did not close.

v0.1.38the site looks like the app, and a finished round says how it ends

This is the first published release since v0.1.33. The four cuts between them were local nightlies with no GitHub assets, so upgrading from v0.1.33 brings their work too: the Superintendent agent runtime and its panel, the skill corpus with retrieval and evolution, and an MCP policy that survives the database.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.38. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH. The published macOS app talks to production; staging is an opt-in bake (make desktop-dmg-staging) and it is not the GitHub Release asset.

Highlights

The desktop is a download

  • hm_0.1.38_aarch64.dmg is published beside the tarballs: an unsigned macOS application, Apple silicon, macOS 11 or later. The release job builds it on the same runner that builds the CLI, SHA256SUMS covers it, and the job refuses to publish a DMG whose version disagrees with the tag.
  • The app carries its own hm. An application launched from Finder inherits launchd's PATH and never sees ~/.local/bin, so the binary ships inside the bundle and the app prefers it over any CLI on PATH. Nothing to install first, and an hm you already have is left alone.
  • It is unsigned, and opening it takes one detour: System Settings → Privacy & Security → Open Anyway, once per copy. Not Control-click → Open, which macOS 15 removed. The macOS section of the install runbook is the walkthrough; signing and notarization are not scheduled.

hmcortex.com is the application at web scale

The site and the app were two designs. The site now spells its colours, its typefaces and its chrome in the application's own vocabulary: one light scheme with no second one declared anywhere, Inter and JetBrains Mono self-hosted from the same four subsetted files the desktop ships, the icon tile beside the name as the brand mark, and red returned to meaning refusal and nothing else — a numbered index, an in-sentence link and a pending slot were all wearing it.

The front door is the application's sign-in shape, and the workbench beside it is the shipped shell rather than an illustration of one: a rail of Works, a board of Ready, In flight, Dispatched and Landed, cards written in hm's own id grammar, a still connection dot, a docked launcher. Eight keyframes became one fade.

The seven secondary pages — download, pricing, docs, changelog, the three trust pages and contact — were restyled onto the same tokens, and the alias block that had been holding them legible in the meantime is retired. A new test reads the desktop's stylesheet off disk and holds all twenty-one names the two share to the same value, so the two surfaces cannot drift apart quietly again.

No third-party origin is contacted for a font or anything else, and the privacy page says that rather than naming two addresses it no longer uses.

A finished skill round is visible, and says how it ends

A skill round succeeds by closing every candidate — promote and decline both end one with a cancel — which left the finished round with a roster of nothing but cancelled tickets, and no card. The Principal's own completed work went invisible at the moment it was done.

A non-empty roster now always earns a card; only a ticketless manifest has none. The round also says how it closes: the brief names the closing act, and hm skill promote and hm skill decline echo it when a verdict closes the last open candidate. Both are prints. Nothing lands on their account, and nothing waits on a round.

Needs land asks the land gate rather than guessing. A card carries the land policy, the readiness verdict and the reason it is not ready, and the cycle reads the same call — the third opinion about what is landable stops existing. Under a per-ticket policy the column is the union of the gate's verdict and every member being terminal, so a unit the gate refuses keeps its card, because it still needs landing.

A card names where its unit came from, and doctor names a landable one

A manifest card carries the source it was opened from as a kind and a slug, so a storefront can label a unit's origin without parsing its id. It is an origin and not a placement: a round and an ordinary unit in the same state land in the same column, and a test holds that.

hm works doctor now reports every Active unit the Works' own land policy would accept, and hm patrol run carries the same line as advice. It is the exact complement of the existing warning about a complete ticket that never landed, and it asks the land gate rather than deriving a fourth opinion. hm manifest status prints that verdict for a single unit someone already thought to name. All of it is advisory: it never moves the doctor's verdict, it raises nothing stuck, and a --fix sweep will not land on its own — held by a test, because landing a unit is a fact a Principal has to be in the loop for.

What ships

Two CLI tarballs — hm-0.1.38-aarch64-apple-darwin.tar.gz and hm-0.1.38-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.38_aarch64.dmg (production account plane), and a SHA256SUMS covering all three.

v0.1.38-nightlythe call that ends, and the launch that is told

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.38-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

The Superintendent send that could not end

run_tool_call was made an async Tauri command while its body still built a private tokio runtime and blocked on it. A runtime refuses to start inside a runtime, so the task panicked before the bridge was ever spawned and the webview's promise was neither resolved nor rejected. Every symptom the panel showed was that one promise: the send sat forever, the queue behind it drained onto a status nothing would set again, and closing and re-opening the panel came back to the same wedge. The only exit was reconnecting the Works, which discards the transcript.

The slash menu read correctly while firing a command did not, and that difference was an accident of an attribute rather than an invariant. Both commands are async now and the blocking hand goes to a blocking pool. Every routed call settles on a terminal outcome — completed, cancelled or timeout — and a stop says whether the command had reached the child, because from this side of the pipe "nothing ran" and "it may already have landed" are otherwise indistinguishable.

One bridge under the Superintendent, and a save that is proved

Three surfaces were called the Superintendent's bridge and only one of them was seat-pinned. The panel's own client hand-built its argv and pinned nothing, so a write routed through the panel recorded no seat, and the class gate had no row to read a posture off. Both hands spawn from one argv builder now, and a write no longer leaves the bridge on a failure: a read may still degrade to a stale-but-honest menu, but a write, a dispatch or an outward call is refused rather than retried through a subprocess that skips the seat pin and the gate at once.

A Save that reached the disk and one that only said so were the same outcome on screen. A set is whole now or it never happened — the row is re-read from disk after the write, and a failure in that read puts the file back exactly as it was found — and the panel's Level control writes the field the runtime actually reads. Audit rows name the initiating actor, its level and the effective seat separately, rather than whichever was known.

A hand is told what it holds, and refused a launch it cannot finish

The brief a hand is instructed to read named a ticket, an acceptance and a close, and answered neither of the two questions it has on its first turn: what am I holding, and what may I do with it. The prime opens with a Launch section now — the seat and role, the resolved hm and its rung, the bench and the branch this launch cut, the provider and the model with who chose each, the permission mode, the toolbelt, the connectors this dispatch disabled — and carries a Skills section beside the company context, up to five authored procedures ranked against the paths the acceptance names, out of a corpus the Works keeps and each bench can extend under skills/.

Six readiness checks then run before the work rather than after it: a close route, the surfaces the brief promises by name, a connector the policy disabled that is still in the toolbelt, a bench outside its Line, a branch that is not the one the launch cut, and a bench git can resolve no committer identity in — the cheapest check for the most expensive failure, a commit that fails at the end of a spent run. A refusal opens with the phase it stopped in and every line ends in a next action.

What ships

The local macOS desktop DMG hm_0.1.38-nightly_aarch64.dmg (production account plane), built from 22075fc. No CLI tarballs, and no GitHub assets.

Authoring an MCP policy at manifest scope is still open: there is no set_manifest_mcp_policy and no CLI flag for it, so a manifest column can only be filled by the backfill the last cut added, never by a hand.

v0.1.37-nightlythe policy a preview can be read from

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.37-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

A policy set before its column existed is replayed into it

The last cut gave tickets and manifests an mcp_policy column, but a migration only reaches writes made after it lands. A policy set before it lives on in its ticket.mcp_policy_set event and nowhere else, so the row reads back empty forever: the seat-wired connector resolves to optional, and a locked credential store refuses the run the operator had explicitly excluded it from. Two live tickets in this Works were in exactly that state.

Migration 0008 replays the latest such event into any column that has nothing for it. A migration rather than a script, so every Works self-heals the next time it is opened. Latest-wins, so a policy cleared after the column landed stays cleared; rows written since are untouched; a payload that does not parse is skipped rather than stored.

A dry run names the policy it resolved, not just the servers

hm run dispatch --dry-run printed the resolved requirement and left the declaration out. A ticket that declared nothing still printed mcp required: hm — the seat's mandatory bridge, injected structurally — which is also what an hm-only policy prints, so the two were indistinguishable. The optional set that a locked store actually gates on never appeared at all.

The preview now leads with what the ticket declared, and says none declared (seat wiring only) when it declared nothing, then prints the optional servers beside the required and disabled ones. That is the line that separates a connector being excluded from a connector simply not being required.

What ships

The local macOS desktop DMG hm_0.1.37-nightly_aarch64.dmg (production account plane), built from 960a217. No CLI tarballs, and no GitHub assets.

Authoring an MCP policy at manifest scope is still open: there is no set_manifest_mcp_policy and no CLI flag for it, so a manifest column can only be filled by the backfill this cut adds, never by a hand.

v0.1.36-nightlythe MCP policy survives the database

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.36-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

A ticket's MCP policy is read back the way it was written

The ticket-scoped MCP contract shipped in the last cut, but only half of it reached SQLite: the insert named eleven columns and the policy was not one of them, and the read path handed back a default. A ticket created with --mcp-disable linear therefore arrived at dispatch carrying nothing, the connector fell back to optional, and a locked credential store refused the very run the operator had excluded it from. Dual mode lost it in the same place — it wrote a correct file and then read the lossy row.

Migration 0007 gives tickets and manifests a mcp_policy column, JSON-encoded and defaulting to empty, and the save and read paths carry it the way depends_on and touchpoints already do. A blank column reads as "no policy" rather than making the row unreadable, so an existing Works upgrades without a backfill.

A dry run predicts the refusal it was going to hit

hm run dispatch --dry-run ran every preflight except the scoped secrets gate, which is the one a locked store actually trips. A preview could be clean and the live run refuse a second later, with nothing in between to explain it. The gate now runs on the same resolved policy the live path uses, against the same store, so the preview and the run agree — including the case where a disabled server is the reason there is nothing to unlock.

What ships

The local macOS desktop DMG hm_0.1.36-nightly_aarch64.dmg (production account plane), built from b958b88. No CLI tarballs, and no GitHub assets.

Authoring an MCP policy at manifest scope is still open: there is no set_manifest_mcp_policy and no CLI flag for it, and the persistence this cut adds is what that work will land on.

v0.1.35-nightlyMCP reach, on a local production desktop

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.35-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

A seat reaches the tools its ticket allows, and no others

MCP is now something a Works configures rather than something a hand inherits. hm mcp registers, inspects and removes a seat's servers; a ticket carries an MCP policy, and dispatch resolves the effective connector set before it spawns anything — the secrets preflight is scoped to the servers that are actually enabled, and a disabled server is filtered out of the executor's config rather than left for the agent to decline. Onboarding belts a new MCP-capable seat at wire time, and hm works doctor reports the seats it has not belted.

The account register: paths, never values

An account is declared once in hm.toml — label, provider, owner, the paths its credentials live at, and what it reaches. SeatExecutor.auth follows the same rule: it names where a credential is, not what it is. Nothing in the ledger holds a secret.

A Line can be retired

hm org line remove drops a Line and every seat on it, fail-closed: every blocker is reported in one refusal, each naming the command that clears it. There is no force flag, and no bench tree is ever deleted.

A named refusal for remote MCP

OAuth and remote-HTTP MCP servers are not supported in this cut, and the refusal now says so in those words rather than failing somewhere further in. The design for the home they will get is written down beside it.

What ships

The local macOS desktop DMG hm_0.1.35-nightly_aarch64.dmg (production account plane), built from 8a0e8d0. No CLI tarballs, and no GitHub assets.

v0.1.34-nightlylocal production desktop, no published assets

This cut is a local nightly on the production account plane. It is not a GitHub Release. There is no tag on origin and no downloadable tarball.

The macOS app talks to production: auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com. Staging is an opt-in bake (make desktop-dmg-staging) and it is not this image.

CLI and desktop stay one version. The DMG on this cut is hm_0.1.34-nightly_aarch64.dmg. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

Highlights

A production-plane desktop built on the machine that runs it

GitHub Actions is not cutting this release. The image is built locally with make desktop-dmg and installed on this Mac. When a numbered release can publish again, it will. Rebuilt from 1810ac3 so hm secrets unlock / status / lock and check --dispatch ship; the version stays 0.1.34-nightly.

What ships

The local macOS desktop DMG hm_0.1.34-nightly_aarch64.dmg (production account plane). No CLI tarballs, and no GitHub assets.

v0.1.33Create or join a Works is a Super-shaped conversation

Highlights

The setup pane talks the way the Superintendent does

Create or join a Works used to be a question machine wearing a chat costume: fixed order, closed answer sets as buttons, and hm's own refusal sentences as the only correction loop. The Superintendent panel already did free prose, streaming turns, markdown, interrupt, and an approval card. A Principal who lives in that panel met the setup pane and found a form that answered back.

The pane now hosts the same agent-session engine, one step before there is a Works for any of it to be about. An agent probes the prospective path, proposes one hm works init or hm works onboard, and waits. The Principal approves. The first event in the new company's ledger is works.initialized by principal — nobody on a seat, because there is no seat yet.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.33. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH. The published macOS app talks to production; staging is an opt-in bake (make desktop-dmg-staging) and it is not the GitHub Release asset.

The toolbelt is a seat-less bridge, not the whole catalog

The conversation's tools arrive through hm mcp serve --pre-works. --seat is refused, an ambient HM_SEAT is stripped from every child, and the class gate is on by construction. Against a directory that is not a Works yet, the catalog is six rows: init, onboard, doctor, workflow show, and the two probes that answer create-or-join before an argv is proposed. The other hundred-odd rows would be a wall of not a Works errors, and offering them would be the shell looking more capable than it is.

Widening that list is editing a reviewed table, not flipping a flag. Nothing in the environment admits a row.

The session itself is a twin of the Superintendent's, not a fork: the same stream lifecycle, turn events, interrupt, and class-gate handshake, with three substitutions. The working directory is the prospective path. The runtime comes from the device plane, never org/executors.toml. The record is written on that same plane, never under the directory whose seal may never come.

Only Claude and Codex may host it. Three of the five agent hosts register hm mcp serve by writing a config file into the workspace they run in, and that workspace is a directory that has to be left byte-for-byte as it was found if the Principal walks away. A pick this build cannot host is refused by name.

The seal is a card the Principal actually reads

A held works init or works onboard used to be a hold like any other: an invocation string and an Approve button. That is the right card for hm ticket ready. It is the wrong card for the one write that makes a company, because the fact that matters most — this path already holds a Works, so this joins one rather than making one — is not visible in the argv at all.

The plan machine now runs backwards. A held argv is replayed through the same setup plan the question machine already knew, and the hold carries create versus join, which of the two seals, the Line, the North Star, and the sentences that must have been on the card. The argv still runs verbatim. The surface renders what the gate derived rather than deriving a second answer of its own.

A refused argv is a sentence, not a card. --interactive, --north-star-draft, an unknown Line, a repository that is not a root, a relative path, a --north-star at a Works that keeps its own — each comes back as an error carrying hm's own words, no hold is written, and nothing runs. The agent sees the refusal and the next turn is the correction.

The only write door on the live pane is that hold-approve. The webview reaches no hm subprocess; the bridge runs the argv after the Principal says yes.

The question machine is still there, as the floor

A machine that cannot host the conversation still has to set a Works up. The question machine is kept whole and moved rather than rewritten: same five questions, same plan card, same subprocess seal. It is the second of three rungs. A live agent that never comes up, or that dies before it announces itself, degrades onto that walk with an honest line and the Principal's own first sentence counted once. A host that did come up and then failed keeps its named cause rather than being torn down over a guessed one.

The Settings → General form is the last rung, named rather than opened. It still asks for all four values at once, including a Line a brand-new Works can only have one of.

A conversation may resume until the directory is a Works

Walking away mid-setup used to lose the thread. Opening the same directory again, on the same runtime, inside ninety minutes, resumes onto the same record. The pane replays what was said and names why: remembered, or new, because the path is a Works now, because it went cold, or because the runtime changed.

The key dies the instant the directory holds an hm.toml. The seal is what puts that file there, so the transcript is cleared at seal without a flag threaded back from the screen. A Claude session id is not a Codex thread, and one host is never handed the other's resume.

Composer chips fill a sentence and hand the caret back. They do not send, and they spell no flag. A click is not an answer hm is handed.

What ships

Two CLI tarballs — hm-0.1.33-aarch64-apple-darwin.tar.gz and hm-0.1.33-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.33_aarch64.dmg (production account plane), and a SHA256SUMS covering all three.

v0.1.32the desktop talks to production, and a ticket carries its own note and its own changes

Highlights

This is the production desktop cutoff

The published macOS app talks to production. auth.hmcortex.com, api.hmcortex.com and account.hmcortex.com are the plane a bundle reaches when nobody named one at build time — including every bundle the release job makes, which passes no profile at all. Staging is an opt-in bake (make desktop-dmg-staging) and it is not the GitHub Release asset.

CLI and desktop stay one version. The tarballs and the DMG on this release are 0.1.32. Installing the app neither requires nor disturbs a CLI install; the app still prefers the hm it carries over anything on PATH.

A ticket and a manifest can hold a note

hm already drafted a ticket description and dropped it. Intent lived in acceptance, and three manifests off one brief drew three identical board cards because the card quoted the brief, which is not a unit.

Notes are documents now. ticket_note and manifest_note are kinds in content.db. Two templates ship compiled-in and are overridden through hm.toml, not an agent plugin. hm ticket note and hm manifest note write, show and fill over one surface: the body arrives on stdin or --file, never on argv. A write whose sections match the resolved template records that template; anything else is free-hand. fill writes the guidance-stripped skeleton and refuses to replace an authored note without --force.

A ticket that is still being drafted keeps its ## Plan as the ticket note instead of throwing the body away. The unit Detail description reads the manifest note first — sentence, then the brief's first prose, then an empty slot — so six manifests that share a brief no longer describe themselves with one shared sentence. The board card does not read the note. A filled-but-unwritten template yields no prose rather than speaking in the Principal's voice.

Resources and activity stay derived. There is no comment stream beside mail.

A ticket's Changes is that ticket's commit range

The delta under a ticket used to be the whole manifest branch wearing a ticket's name. Under per-ticket land that was wrong twice: ticket N showed t1 through tN, and once a ticket landed the per-ticket reset left it with nothing to show at all.

The range is now the ticket's own ledger anchors. The tip is the land merge's two parents (land merges --no-ff, so the merge is a git identity) or the integrated verified sha, else the worktree HEAD. The base is the first rung that is still an ancestor of that tip — the ticket's own earliest dispatch base, the manifest's cut, a sibling's landed tip. Uncommitted work is a separately labelled group, never folded into the range. When no anchor survives, the read says so rather than showing a plausible wrong range.

The commits never went anywhere when a worktree was reclaimed. Gatehouse land merges in the bench, not in the worktree, so a landed ticket's Changes is read from the bench after the worktree is gone. An unlanded ticket is not offered the landing branch as a tip to borrow — that fabrication would have shown every sibling manifest's files under this ticket's name. hm ticket changes is the CLI of the same read the desktop draws.

Notes and Activity live on the Ticket screen

Four panes that were ticket-grained — Mentions, the Hand run, Mail, the Hand Session — had been mounted on the unit because they landed before the Ticket screen existed. They move whole: same ids, same reads, same four channels, and every one of them is about the ticket the screen is open on, never a sibling the board card was drilled into.

Notes is the screen's first prose, above the spine: the whole markdown body, read-only. Writing is hm ticket note write, gated in hm-core on who may write and on the ticket still being open. Absence is one answer for three states — no note, no content.db, and a template nobody wrote into.

The empty "no hand has run" slot retires into the run card, which already said the same fact under the same state. Changes keeps its commit-range read and is renamed Resources on the head; the element, the command and the hm-core call keep their names. No second resolver, no artifacts field, no third prose channel.

Query can see a note, and a manifest's activity includes its tickets

The free-text q searches a ticket's note body. Title, acceptance and completion note were the fold justified by hm having no description; the note is the description column arriving, and a search that could not see it would have stopped seeing the field the storefront made primary.

hm query events --manifest rolls a manifest's activity up from its tickets. The events read used to filter exactly one entity, so a manifest's stream was only what was written against the manifest id. The manifest and every ticket bound to it now bind into one filter over the same sequence order. Closed tickets stay in the set. --entity and --manifest are two spellings of one axis; both at once is a parse error, not an empty stream that reads like a quiet manifest.

What ships

Two CLI tarballs — hm-0.1.32-aarch64-apple-darwin.tar.gz and hm-0.1.32-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.32_aarch64.dmg (production account plane), and a SHA256SUMS covering all three.

v0.1.31a landed ticket moves its own manifest on, and the gatehouse gets a column to wait in

Highlights

The manifest moves itself on after a ticket lands

The wait that cost 123 of 241 hours of handoff sat strictly after the merge: the gates were clear, and nothing dispatched the next ticket until a human came back to type it. Detection was never the gap. hm.app is running, its ledger tail polls every second, and the merge was already visible to it — nobody in the process had the job of acting on it.

run::continue_next now answers one tick: which manifests a delta named, whether their gates are clear, and whether this ticket already had its dispatch. hm run continue --since is the follower's command, and the desktop tail runs it over the subprocess seam, because a latch and a dispatch are writes and the shell holds no write handle. The cursor rule moved into hm-core (follow_start / follow_events) now that this tail is no longer the ledger's only follower, and every act records run.continued with the triggering event's kind and sequence.

It is off by default for one release, behind a per-Works hm.toml switch. It never lands and never pushes, and it notifies no Superintendent: a finished manifest is held at ready-to-land, where the board's new Needs land column is already showing it to the Principal.

Three gates were added to the decider on the way, two of them found by the first live run. A ticket still in grooming, and a seat already holding a latch on the manifest, are named waits rather than latches the write path refuses on contact, and the read-only dispatch preview now runs inside the tick. Both of the follower's own event kinds are excluded from triggering it, and the idempotence predicate keys on any dispatch attempt rather than on run.dispatched alone — that event is written before the spawn, so a dispatched-only test triggered by a spawn failure would look past its own attempt and retry once per second.

A cycle says why it is standing still

next_cycle_step could say what the next mechanical move was but never why a cycle stood still: a ticket it called Dispatch might be one a Hand was already running, or one the dispatch gates would refuse on contact. Those are different answers and they wanted different words.

CycleStep::Await now means only the first — work in flight. The second is CycleStep::WaitingOn, whose gate carries the remedy in every variant, crossed with a read-only gate state: land policy, unmet depends_on, the Hand's run state, preflight refusals. The decider is pure, so the transition table is a table test, and collection sits below a marked I/O line and only reads.

Gate order mirrors plan_dispatch with the Hand check ahead of it, because a ticket in flight is not a ticket to re-gate. Live dispatch behaviour is unchanged down to the message: the executing cycle raises the identical error from the same gate at the I/O boundary. What changed is hm run cycle --dry-run, which now reports the gate it hits and the one the plan walks into further down, where it used to abort on a blocked ticket or print "ready to land" over a stopped plan. The dispatch preview also predicts the coupling the real dispatch would inherit, so the rehearsal and the run agree about one more thing.

The default Hand comes from the manifest's own Line

Auto-latch asked the roster for "a Hand" and got the first one, which on this Works is the product-engineering Hand for every Line — so a manifest bound to operations would have been latched to another Line's Hand the moment the follower started latching on its own.

Resolution is Line-first now. A manifest names its Line by explicit relation: a recorded bench binding, then a ticket already latched to a seat. The Line then names its own Hand — the seeded one for that Line, else a Hand seated on it — and resolution never leaves the Line it resolved. A Hand-less Line yields the seat the Works has to add, so the latch fails naming it rather than quietly handing the work to a neighbour. The cycle's default seat became an option, because "unset" was the state it could not hold: the old hard-coded default was indistinguishable from an operator's --seat, so no caller could resolve it.

The gatehouse gets a column, and the board scrolls to reach it

A unit whose whole roster was finished but whose phase was still active sat in Ready, whose stage line reads "Work · awaiting a seat". It is awaiting the gatehouse, not a seat — and on the dogfood Works 33 of the 43 Ready cards were that shape. Under per-ticket land it is also refusing the next dispatch on its own manifest, so Ready was filing a blocker as work nobody had started.

The fold gained a fifth group and the grid a fifth column between Dispatched and Landed: head "Needs land", stage line "Gatehouse · awaiting land", empty "Nothing waiting to land.", in the amber a held Hand already wears. No new token — the column is a fifth state of the same lifecycle. Precedence is Dispatched, In flight, Ready, Needs land, Landed, at manifest grain only: the ticket partition keeps its four keys, so the cadence ladder, the Overview insights and the Detail chip are untouched, and the ladder gets no Gatehouse rung because every rung on it counts tickets and Needs land is a fact about a unit.

Five columns do not fit the default window at the width a card was fitted to, so the panel buys the track with sideways scroll rather than with card width. The floor is the width a column had when four split the default window, so the four that were already right keep their size and the fifth is reached sideways; past about 1300 pixels the flexible half takes over and the scrollbar disappears. Taking fifty pixels off every card at every width was the alternative, and was not taken.

The two key sets are now one type — the renderer's group IS the fold's column key rather than a union written out a second time — and the board walks the column registry instead of naming one render call per column, so a column registered with the grid is painted the moment it is registered.

Every column paints its ticket mix

The card's bead row was only painted in Dispatched. Ready and In flight are exactly where mixed states are invisible: none-started against some-complete, and a live Hand on a latched unit. Both now carry the same yellow, green, red and grey row, and Needs-land cards keep it too — green-for-complete is the tell that a unit is finished rather than stuck. A unit already through the land gate still gets no row, because its column has said the only thing the mix could add.

Board to Unit to Ticket

The design's third destination returns as a screen and not as its data. A ticket row in the unit's Tickets list now navigates instead of expanding in place, and the touchpoint spine moved onto the screen with it, so it is still drawn in exactly one place. The back chip is the unit's key, one level, not the board's.

Every field is the same work unit read the unit view already made, so the screen costs no read of its own and repaints on the same ledger tick. The empty state is the design's own sentence — "No hand has run on this ticket yet." — shown on hm-core's never-dispatched state and no other, so a run that happened but offers this pane no channel is never called a run that did not.

Not drawn: the subline's Line crumb. This read carries no Line, and hm-core attributes a unit to one by explicit relation, a rule the webview must not keep a second copy of.

What a hand changed, opened in something that can read it

The Ticket screen's empty artifact slot is filled with the one thing hm actually has: the ticket's bench worktree delta. The read was written and tested in hm-core all along and has had no surface since the design-v2 layout removed the Changes-primary detail. It is back on the desktop's read allowlist, minus its patch, because this screen draws no inline diff and a patch on every ledger tick would be the cost of a view nobody scoped.

The granularity is the ticket. A ticket resolves to one bench, so it has one delta — the design draws artifacts per touchpoint, hm has no per-touchpoint change, and none is invented. Unavailable and clean stay two different sentences: a reclaimed bench is not a bench with nothing in it, and neither is a row.

Open with is a guarded command, not a permission. The webview sends an index into the list it drew; the shell re-reads the delta, resolves the index, canonicalizes, and re-checks that the result is still under that ticket's own worktree — and the application must be one this machine just offered, because an unchecked bundle path would make the command run anything on anything and the path guard worth nothing. The launch is the opener the account link already rides, called in exactly one place, and the webview is granted no opener permission.

Zed first for code and for the worktree is hm's preference and not a registry read: on this laptop the default for a Rust file is whatever claimed plain text, and a TypeScript file offers a video player because the extension is also an MPEG transport stream. The machine's own default is still offered, and marked as the default. Classification is hm-core's, beside the diff — the extension plus git's own text or binary verdict, where the verdict can only demote — and it answers one question, whether hm hoists a code editor, so "other" is not a failure, it is no preference.

Probed rather than assumed: Zed declares it opens folders, so handing it the worktree is real. What is not real is a native "open as a diff" verb — nothing reachable through the system's launch services asks an editor to show a branch delta — so the worktree opens as a folder and a single file opens as a file, and that is the whole of what ships. Opening a change in a remote app is a boundary crossing, and stays a connector, later, if ever.

The roster list groups by Line

The Roster screen ran the seats out flat. The file it is read from does hold one relation — a Line names seats — so the list now draws that level and only that level: one group per Line, in the file's order, seats unsorted inside it, and a trailing group for the seats whose Line the roster no longer holds. That group says in words that the roster has drifted rather than inventing a Line to file those seats under, and a Line seating nobody says that too.

Roster is not a graph, and this does not make it one. A group is a filter over the single roster answer already in hand; no seat points at another seat, no group holds another group, and the leaf row is unchanged with exactly one call site. The group hangs on the Lines screen's spine and wears its head chrome, so one Line reads the same on both screens.

What ships

Two CLI tarballs — hm-0.1.31-aarch64-apple-darwin.tar.gz and hm-0.1.31-x86_64-unknown-linux-gnu.tar.gz — the macOS desktop DMG hm_0.1.31_aarch64.dmg, and a SHA256SUMS covering all three.

v0.1.30the board ranks what is happening now, and a card says which of its tickets is doing it

Highlights

Dispatched is a run state, not a run file

The Cadence board decided a unit was Dispatched by asking whether a run log existed. list_hand_runs returns every log a Works has ever written — 672 of them on the dogfood Works — because the Detail Activity pane still reads ended transcripts, and the only thing that ever took a log back out of the column was a ticket completion inside the digest's eight-event window. So a unit's column was a function of how busy the Works had been since it finished rather than of its own state. On the dogfood Works, 175 of 177 units that had already been through the land gate were painted in Dispatched, and the Landed column read zero.

The gate is now the run's state — live or paused — which is the field the read already derives from the PID probe plus the ledger's last lifecycle event. An ended run earns no column. Paused stays Dispatched, because SIGSTOP holds the process and there is a Hand to resume; ended-but-still-latched falls through to In flight, where the latch is the remaining fact.

No read changed to do it. Every log still comes back, ended ones included, and Activity, the run badge and the run-state selector are untouched — the filter is one predicate inside the pure fold. Landed still means the manifest's own phase written by the land, and the units that had been stolen from it return by falling through the hottest-work precedence rather than by retargeting the column.

Latest-first, and the chips that flip it

The read behind the board answers id-sorted and the fold preserved that, so the columns ranked units by the order their ids happened to be minted. That put the release which was actually running at rank 143, under 142 units finished weeks earlier. The order was never chosen — it was inherited from a sort that exists so the payload is stable, not so the board is legible.

The board now paints latest-first, with a two-chip control in the board head that switches to oldest-first. Each card carries the one clock every unit has, its manifest creation instant, off the list the read already loads: no new read, no ledger event. Each column then ranks by the freshest instant it can honestly name — Dispatched takes the run log's modification time, Landed takes the newest ticket completion in the digest window, and Ready, In flight, or either of the other two when its richer instant is missing, take the creation instant.

Instants are parsed rather than string-compared, so a source that ever carries an offset cannot silently re-rank the board, and one that will not parse sorts last in both directions: that card is not the oldest thing on the board, it is the one the board cannot place. Ties break on id, so the same partition paints the same column twice rather than reshuffling equal-timestamped cards on every ledger tick.

Ordering lives in the paint, not in the partition. The partition is a fact about the Works; the order is the Principal's choice, and every column takes the same one — a board ranking Landed by time and Ready by id would be two boards. The chips reuse the Chamber filter's pill geometry and active tint at board scale, carry no counts because the columns already do, and cost no read: pressing one repaints from the last digest and cards rather than re-reading the Works, so no refresh can land between the press and the paint.

A card paints its ticket mix on the bead it already had

A folded manifest card showed one dot. A live Hand, a sibling waiting on the land gate and a blocked one all looked the same, which is the question the fold created when it stopped painting one card per ticket.

The card now carries one bead per showable member, in roster order, inside the meta row the subtitle already occupies — not a fourth copy slot, and not a size change. A member is yellow when the board places it in Dispatched, because the run outranks whatever the ledger still says; red when it is blocked, or when its run has ended while the ticket is still latched, which is the closest thing the ledger records to a failure since hm has no failed ticket state; green when it is complete or landed, waiting on the unit's own gate; and the dormant grey the bead already was for everything else. Open, and a latch with no Hand on it, are not finished waiting, and painting them green would make never-started and complete-but-not-landed the same colour.

The bead is a fact about the member and the column a fact about the unit, so the class is read from the ticket partition and never re-derived from the manifest phase. Cancelled members are skipped exactly as the placement fold skips them. A unit already through the gate gets no row at all, because its column has said the only thing the mix could add. The row caps at eight beads: a bead plus the meta row's gap is thirteen pixels a member against a column that still has to hold the subtitle, and past the cap the leftover members go unpainted rather than earning a second row that would grow the card.

The roster list wears the v4 card

The design's Roster screen is an org chart of cards — a root, standing columns, connectors, indented reports. Only the card travels here; the list stays the flat seat list the loader answers with.

Each row gained the Line's colour as a left spine and a square dot beside the id, reusing the Lines screen's hashed swatch rather than inventing a second palette, and the Line label moved under the head in mono to match the card's meta row. The row also gained a status word, and what that word says is narrower than the design's: the design coloured a runtime liveness that this read cannot prove, because a seat is four fields in a roster file and health is keyed by snapshot agent ids rather than seat ids. So the word is what the roster does rule on — seated when the seat's Line resolves, and line unresolved when it points at a Line this roster does not hold, which is real drift a hand-edited file produces. Inventing a colour per seat would have read as a fact.

Explicitly not ported, and now gated rather than forgotten: the parent pointer, the kind, the connectors and reports indent, create, retire and promote, one-off dispatch, New roster, and per-seat spend and cap.

A wired seat stops being called unconfigured

Asking whether a seat can be reached by mail resolved the seat's executor provider before looking at its toolbelt. Providers are feature-gated, so the answer depended on which ones the asking binary had been compiled with: the desktop, which compiles the pilot hands, and the default-feature CLI printed two different reasons for the same seat on the same queue write, and the CLI's was that the seat had no entry in the executors file — a sentence about the Principal's Works that was not true of it.

Reachability never needed the provider resolved, so it no longer resolves one. It reads the seat's own entry and reports what the [seat.mcp] declaration says, which means the two binaries now agree about the Works in front of them, and a seat that genuinely declares no toolbelt is told that rather than told it does not exist. Found by dogfooding @ addressing in the Superintendent and in the Detail Mail pane against this Works.

The v4 Cadence Tracker HTML is the design authority

The design authority moved to the v4 Cadence Tracker HTML, pinned by digest and byte count so a fork cannot quietly become the thing cuts are measured against, and the earlier note that named the v3 file as the right visual variation was corrected — the Roster screen is not a graph. Alongside it, the delta document now records the full parity between the canonical HTML and what is shipped, so a product cut keeps, re-scopes or drops each item against the authority rather than against a copy of it, names the v3 git pin, records that the Superintendent typeahead's one-off entries are deliberately not ported, and keeps class-gated dispatch on hold. The three-class bead mapping the board now paints was written down as research before any of it was built, against the partition that existed at the time.

v0.1.29the board shows the work, not the ticket rows the work was cut into

Highlights

A manifest is one card, and the board stopped painting one per ticket

A five-ticket manifest was five cards on the Cadence board, each titled with a ticket name the Principal never wrote, each sitting in whichever column its own ticket had reached. The unit of work is the manifest — it is what a brief opens, what a petition argues for, and what a land closes — and the board was the one surface that could not say so.

It could not say so because the read under it cannot answer it. The digest the board renders from carries a ticket line with no manifest id on it; its in-flight states are Open, Latched and Blocked, so every terminal ticket falls off the board entirely; its Landed column is a ticket completion caught inside an eight-event window; and its list of active manifests filters the landed ones out. Every one of those is right for the ladder and the insight counts that read them. None of them is a manifest.

So there is a second read beside the digest rather than a change to it. manifest_board returns every manifest with its phase, its whole ticket roster — Complete, Cancelled and Landed included — and two lines of copy taken off the unit's own brief or petition. It is composed in the desktop shell out of four reads the core already published, and it opens no ledger handle, writes no event and moves no schema: a manifest arrives on the board because the ledger already says it exists, not because a projection was taught to keep a second copy of it.

The column a card lands in is the hottest work on the unit

Folding a roster into one card means choosing which member speaks for it, and ticket-level placement has the precedence backwards. On a ticket, Landed wins, because a finished ticket is finished. On a manifest, Landed losing is the whole point: a completed sibling must never pull a live dispatch into the Landed column and take a run the Principal is watching off the board.

So the fold reads Dispatched, then In flight, then Ready, then Landed, and the first one with a member on it is the column. Cancelled members earn no column at all, and a unit whose entire roster is cancelled has no card rather than an empty one. The Landed column now means what the gate means — the manifest's own phase, written by the land — and not a ticket completion that happened to fall inside a window of recent events. A unit whose tickets are all complete but which has not landed yet sits in Ready, which is the true statement about it: the work is done and the land has not run.

The grouping key is the manifest id the ledger stores on the ticket. Ticket ids are never parsed for it. That manifest-tN looks like a join is a convention the writer follows, and a view that reversed it would be reading a naming scheme as if it were a foreign key.

The ticket partition is an input to the fold and not a casualty of it. The cadence ladder, the chips and the Overview insight counts keep reading it, keep counting tickets, and keep their meaning — the fold is added beside them.

A card carries the words the unit was opened with

The three slots on a card were an id, a title and a meta line, and the title was a ticket's. Now the id is the manifest's, the title is the first heading of the unit's brief or petition with the house prefix stripped, and the meta line is the first prose sentence under that heading, past the label rows.

Both are clipped where they are read, at 48 and 36 characters, because the card has no line clamp and no maximum height — clipping in CSS would be the size change this work is not allowed to make. An artifact that is missing, or that has no heading, degrades to its own source slug read as words with an empty second line. It never falls back to the manifest slug, which is a copy of the id the card already prints, and it never invents a count line to fill the space. One unreadable brief costs one card its copy; it does not fail the read.

Clicking a card still drills a ticket, because that is what the Detail view resolves — the member that earned the column is the one it opens on, and the highlight follows it back.

The panel that opens cannot retitle the unit

The work-unit Detail titled itself from the manifest slug and described itself with a tally: the phase, a ticket count, a touchpoint count. Three facts already visible in the tree underneath, standing in for the one thing the panel could not say, which was what the unit is.

It now carries the same two lines the card printed, read through the same copy function, so the board and the panel cannot disagree about what a unit is called. Where the artifact offers no sentence the description is empty rather than refilled with the count line it replaced.

The grouping rule was written down before any of it was built. The research note under docs/reference/research/ seals the precedence and the copy derivation against the partition that existed at the time, with no runtime change in it, so the four changes above implement a decision rather than discover one.

The cursor a spawn resolves is the Cursor CLI

A shim on this machine's PATH at ~/.local/bin/cursor execs a different vendor's agent. Resolution took the first cursor it found, which meant a Cursor seat could spawn a runtime nobody asked for — and the failure was invisible from inside hm, because the program name on the left of the spawn is the one that was asked for either way.

Resolution now skips that wrapper and prefers the CLI the Cursor application installs, falling back to cursor-agent when that is the real one. The hand's argv is unchanged: cursor agent with -f, as it was. (#348)

A dry run refuses what the live land refuses

hm gatehouse land --git --dry-run previewed a whole-manifest land under a per-ticket policy — the exact shape the live land declines. A preview that says yes where the real thing says no is worse than no preview: it is the one an operator trusts.

Both paths now go through one policy check, so a --git --dry-run without a --ticket under a per-ticket policy refuses as a policy mismatch, in the same words and for the same reason. A dry run that names its ticket still previews, and the ledger-only dry run is untouched. (#349)

A failed check keeps the table that explains it

hm works doctor and hm executor probe earn their keep by printing a table: which seat, which runtime, what answered. Over MCP, a non-zero exit turned that into an error result and the table went with it, so the Superintendent showed the bail and nothing about why — on the two commands whose entire output is the diagnosis.

The error result now carries the command's own output rather than replacing it. The bridge puts the table in the content text as well as in the structured result, the desktop host fills its output from whichever of the two is there, and the panel's send path joins both streams. A probe still exits non-zero when a seat fails; it just no longer takes the reason with it. (#350)

v0.1.28the panel stops keeping second copies, and the belt it runs on is enforced

Highlights

The Superintendent's six action chips are gone, and nothing they did went with them

The floating panel carried a row of six chips — Toolbelt, Manifests, Draft Manifest, Assign seat, Pause run, Terminate run. They were honest when they shipped: each was a typed route into the live runtime rather than a label, two firing a real hm call on click and four staging a command the runtime must not invent an argument for. What they were not was the catalog. They were a hand-written list of six commands sitting above a menu that reads the whole catalog from the session itself.

The / menu is tools/list — the same list hm workflow show renders, grouped by its own lifecycle phases and described by its own guidance. Every command a chip named is in it (workflow show, manifest list, manifest create, latch assign, run pause, run terminate), and so is every command no chip ever named. Mid-run steer has a better home than either: the run card for a dispatched ticket, which sends the id it is already drilled into instead of asking the Principal to type one back. Its Hold and Terminate are unchanged.

So the row is deleted — actions.ts, ActionChips.svelte and their test — and the panel is left with the surfaces that cannot fall behind the runtime: a composer, a catalog menu that grows on its own, and a run card that knows which run it is looking at. (#330)

The slot the chips left holds the agent's own configuration

The header chip has always named which runtime hosts the chat. It never said how that runtime was configured — and in this Works it was already running --model claude-opus-5 off the Superintendent seat, with nothing on the panel saying so. Under the approval cards there is now a row that does: Model, Level where the runtime has a flag for one, Posture, and a quiet Save as seat default.

It is the second axis of the same switcher, not a second switcher. The chip picks the CLI; the row picks how that CLI is configured, and it re-reads when the chip changes — each runtime has its own model namespace and its own knobs, and Cursor has no Level control at all because its level rides inside the model slug.

A pick is a session override, remembered per runtime so a relaunch comes back on it, and it never touches org/executors.toml — that seat is shared with decompose, and one model string cannot hold five namespaces. Claude takes a model or posture change hot, on the same stdin the stop button already writes to, so the conversation is not restarted to change what is answering it. A level is a spawn flag, so it re-opens on the same conversation and says so before it does.

Posture is hm's own, not a vendor's permission list: it is the class gate that decides whether a write raises a card, the bridge re-reads it on every held call, and it applies on every runtime rather than only on the one that takes a flag for it.

Every value on the row is what the host reported it resolved, never the pick that was asked for — a change that failed leaves the live value standing. (#331)

The row is now true on every runtime, not just the two that read a model

Codex, Pi and Opencode each spawned the Superintendent on a model written into their host module, and read neither the row above them nor the seat below it. A Model pick there was a label — so the row disabled it and said why. That is gone, because the reason is: all three hosts now build their argv from the one resolution the panel writes to, on the open path and the per-turn path alike.

A level travels with it, in each CLI's own vocabulary rather than a word hm invented — Claude --effort, Codex model_reasoning_effort, Pi --thinking, Opencode --variant. It is passed only when one is set: an unset level means the CLI's own default answers, never hm's idea of a middle setting. Cursor still has no Level control, for the reason it never had one — its level rides inside the model slug.

The seat row grew the matching homes, so a session pick has somewhere durable to be promoted to on every runtime: effort under [seat.claude] and variant under [seat.opencode] join the model_reasoning_effort and thinking their Codex and Pi siblings already carried. Unset stays unwritten — a seat that names no level materializes no table.

None of it widens a sandbox. Codex still runs read-only with approvals never, Pi still excludes the three mutating built-ins, Opencode still ships its posture in opencode.json, and the guards that assert so are unchanged. (#332)

Every Line's Superintendent arrives with a row to be addressed at

Save as seat default writes hm executor set --seat superintendent-<line> with no --provider in it, because the row it is updating is supposed to already be there. Only hm works wire ever authored that pair, so a Line that arrived any other way answered the Principal's Model pick with an error about a flag they were never shown.

The pair is now seeded with the Line itself: works init, works line add, and works onboard for any Line the roster carries and the executors file does not. Seeding is an address and not a wiring — the script provider and nothing else — and it is additive, so a seat already answering on claude keeps its model, posture and toolbelt when a Line is added beside it. org apply-template stays roster-only on purpose, and the runbook says so rather than leaving it as an inconsistency to rediscover. Doctor's line.executor_mismatch was the same gap read from the other side, and it goes quiet for a seeded Line. (#333)

A ticket's Mail box takes an @, and the panel's @ knows what is open

The Detail's Mail composer now spends the same sigil the Superintendent's does, on the same authority — list_agent_mentions for the rows and address_agent_mentions, with this ticket as the mention's work unit, before the body travels. The write underneath is the one it already had: a single hm ticket mail through the write hand, QUEUED until the hand pulls it. The trigger, the rows in flight, the highlight and the dismissal moved into one module, so the two menus cannot disagree about what @ means. (#335)

The floating panel needed the other half of that. It resolved a mention with no work unit at all, which let the seat's own latch answer precisely when the Principal was looking at a ticket that seat is not latched to. The shell now says what is open and the store hands it along — leaving the Detail takes the Given unit away — and both mention paths, the pre-send guard and the rewrite that travels, go through one address. With nothing open, the latch fills the target exactly as before. (#336)

Where a mention resolves onto a ticket, the panel can carry the whole write itself, because a mail body rides argv where a call's body rides stdin. So it does, and it stages it: the composer pre-fills the command and the Principal reads it and sends. Naming a seat queues nothing by itself. Where there is no ticket, nothing is staged and the panel says which miss it was — several latches, no latch, a manifest with no queue at its address, or two names and two queues — and the line travels to the agent instead, which reaches the same command over MCP. Neither route paints a dispatch: QUEUED is the ceiling both stop at. (#337)

The menu draws its superintendent-… rows rather than filtering them out, and says what they are: mail lands on a ticket, and that ticket's queue is read by the hand latched to it. Hiding was the worse half of the trade — the resolve rules on the roster and not on this menu, so a hidden row is a name that still resolves when it is typed, with the explanation taken away. (#338)

The ticket says who has worked it, and the rail's Roster row opens

The composer answers "who am I naming". Nothing on screen answered the other half: who has already worked this ticket, and could any of them be reached about it now. hm-core has had that answer since hm listen — the latch holder plus every seat whose runs left events on the ticket, each resolved through the same router the composer reads — and no surface asked it. One read is added and one row under the Tickets tree draws it, in the host's order and with the host's own sentence per row. Three reach states rather than two, because whether anything is listening now and whether there is anywhere to listen at are different facts, and one grey chip over both would be the lie the row exists to avoid. Display-only, and that is a decision: un-reaching a seat is not a verb hm has. (#339)

The nav rail has carried a disabled Roster row since the shell's first draft, with a tooltip saying it would land with its own row of work. This is that row: one read of the roster, and one screen that draws what it answers. A seat is an id, a Line, a role, a mandate and the hats it stacks — so there are no standing or one-off chips, no per-seat spend bar and no create or retire control, because none of those are objects the model has. Delivery and spend go where they live: two header links open Overview's own agent health and spend regions rather than restating a figure per row, since those rows are keyed by a different id and that panel is a Works-wide aggregate. The one affordance a row carries hands the composer the catalog's own latch assign line with the seat named and the ticket left for the Principal. The list is the roster's, whole and in its order. (#340)

The Superintendent's belt is MCP-only, and the runtime now enforces it

Super is an hm entry point rather than a shell, and that was held by convention. It is now held by the argv: a Claude Super session filters Bash and Agent out of the tools it registers, a Super seat refuses --toolbelt instead of quietly accepting a second one, and Opencode's webfetch is an explicit off rather than an unstated default. (#342)

Reads stop asking. The class gate already knew which verbs only read, so Super's reads travel without raising a card — the Principal was approving a list and a show one at a time on the way to the call that mattered. The wait on a card is bounded, so a deny that never comes back names the argv it was holding instead of hanging with nothing on screen. (#343)

Burst work has a path that does not invent a seat. A Super that needs a Hand to take a repair opens a maintenance manifest and dispatches one Principal card — a Repair title and its acceptance — and run status lists every live Hand on the Line, so the choice is made against what is actually running. The Works-side MCP wiring a Hand needs in order to pull its mail is recorded in the runbook. (#344)

Super reads the public web host by host, and none of it opens a file write: Claude registers WebFetch and WebSearch behind the same belt filter, and Opencode sets webfetch true explicitly. Cursor, Codex and Pi stay documented and unverified, and are named as such. (#345)

A completed ticket's leftover latched_seat is history, not a live latch. Mention, listen and mail read it the same way now, so all three agree the Hand is gone rather than one of them offering to reach a seat whose work is over. (#346)

v0.1.27the switch opens, and the runtime that refused it says why

v0.1.26 gave the Cursor Superintendent permission to call the hand it registered. That fix wrote the permission file — and wrote it in a shape cursor-agent's own schema rejects, so switching Claude → Cursor opened a panel onto a runtime that had already exited. It exited loudly, on stderr, into a pipe the host inherited and a Finder-launched desktop points at /dev/null. The 0.1.26 rule holds and gets its second half here: a runtime that died must not arrive wearing the agent's voice — and when it said why, the Principal reads its sentence.

That is the switch working. The rest of this release is what a working switch turned out to need behind it: a conversation that survives the switch instead of restarting on the far side of it, a hand that finds its CLI when the desktop spawned it, a seat whose dontAsk is honoured by the thing that kept asking, a tree that dispatch can branch without a checkout error, and a main thread that is not a command queue.

Highlights

The file that admits Cursor's tool calls now satisfies Cursor's config schema

cursor-agent validates <workspace>/.cursor/cli.json against a schema that requires permissions.deny as an array. hm wrote permissions.allow alone, so the file the 0.1.26 fix materialized was the file that killed the runtime: "Invalid project config … path [permissions, deny] … Required", exit 1, before the agent said a word. The merge now writes deny beside allow — the documented shape — so a switch to Cursor opens on a config the CLI loads.

It widens nothing: an empty deny list denies nothing extra and grants nothing extra, there is still no Shell(...) entry and no --force/--yolo, and the merge is still a merge — an operator who keeps their own denies keeps exactly their list, a re-run adds nothing, and a file that does not parse is named rather than replaced. (#324)

A Cursor turn that dies before it speaks carries the vendor's own sentence

The Cursor host inherited its child's stderr, discarded its exit status, and turned an empty stdout into (the agent returned no output) — an Ok turn, zero events, and the panel reading the shape correctly as the agent runtime did not start with nothing to say about why. The one place the reason existed was the pipe the host had thrown away.

Stderr is now piped and drained (on its own thread, so a child cannot deadlock filling it), and a turn that exits non-zero or never streams a single event fails with the last of that stderr as its sentence — through the same failed terminal a refused Claude resume goes through. The placeholder survives only where it is honest: a child that ran, streamed, exited clean, and left no narration. The desktop hint table reads Invalid project config as stale/invalid .cursor/cli.json, ahead of the permission rule that would otherwise send the operator hunting for a grant over a sentence that is really about a file's shape. (#324)

One Works conversation, and the runtime it was last held by

The Superintendent had a conversation per runtime and no memory of which one the Principal was last in. Switching Claude → Cursor opened an agent that had heard nothing, a Cmd-Q dropped a resume key that close was the only writer of, and a terminal hm superintendent rotated the identity the desktop's live bubble was still holding.

The session cache now remembers last_provider — the runtime that last held the conversation, written when it resolves a resume key rather than when it is picked, so a runtime that died without speaking never becomes the one a relaunch opens on. The resume key is persisted the moment the host resolves it, and the desktop's exit hook is the orderly teardown rather than the only save, because close is not a promise a Cmd-Q keeps.

What a new runtime is owed is composed rather than copied. render_handoff_brief reads the one shared conversation log every runtime already records into — there is no second store — and SessionSeed decides per open: a fresh chat takes turn-zero plus the handoff, a resume takes the handoff alone, and a runtime resuming its own conversation takes nothing. Claude receives it through --append-system-prompt; the one-shot dialects (Cursor, Codex, Opencode) prepend it to the first turn, which is the only ingress they have; Pi carries it in argv. A session with no pick opens on the remembered runtime, filtered through the hosts this build actually compiled — and hm superintendent re-attaches by default, with --new as the rotation. (#328)

A hand spawned from the desktop resolves the agent CLI the way its host does

ledger_poll's three spawn paths and StdProbeHost::probe_binary handed the bare name to Command::new. A hand dispatched from the desktop's MCP sidecar inherits Finder's PATH — /usr/bin:/bin:/usr/sbin:/sbin — so it reached for a claude or a cursor that is not on it, and a probe of that same seat failed a dispatch which would have run. Both now go through the resolve_agent_cli the hosts already use (explicit override, well-known install prefixes, login-shell PATH), and a spawn that still fails reports every candidate it tried rather than the bare name it could not find. (#325)

A seat told not to ask runs its writes, traced

The Superintendent's live path held every write-class hm call for an approval card — including a --dry-run dispatch, which dispatches nothing — and read the acting seat's permission_mode only to keep dispatch held. So a Principal who had already set that seat to dontAsk was still approving each petition, ticket and bind by hand.

class_gate now maps write-class under dontAsk and bypassPermissions to an allow by seat policy: the call runs, and the same resolution row an approval would have written is appended anyway, carrying the seat and the mode that granted it — so a write with no card is still on the transcript and in the journal. Dispatch stays Hold under every mode, and an unwired seat, an unset mode or an unreadable org file all stay Hold: failing the read must not be the way to be asked less.

Read verbs are classified off the tool's command path, where no user prose can reach, and --help, -h and --dry-run off the argv hm has already built, which is the only place they appear. The four Superintendent hosts resolve the acting seat rather than pinning a constant, so hm executor set --seat superintendent-<line> --permission-mode dontAsk is a control with an effect instead of one with neither effect nor error. (#326)

hm works settle — the tree dispatch was about to branch

Dispatch wrote the seat's .hm/prime.md into the worktree before cutting hm/<manifest>, so in a repo that tracks the prime, git refused the cut: "Your local changes to the following files would be overwritten by checkout". The prime now moves after the cut, and every branch arm first restores hm's own regenerable output (.hm/*, .cursor/*.json, .pi/mcp.json) — operator work is refused by name, with the settle line, instead of through a raw git error.

hm works settle is the standing remedy. Its safe tier restores hm-generated dirt and fast-forwards clean benches, --stash parks operator work in a labelled entry, and --discard needs a typed confirmation. hm works doctor gains bench.dirty_worktree — ERROR for hm dirt on a repo bench, WARN otherwise — and a --json shape to read it from. (#327)

The Superintendent commands come off Tauri's main thread

The eleven Superintendent-family commands are now #[tauri::command(async)] — bodies, channel, registry and event vocabulary unchanged. A synchronous command is dispatched on the main thread, so a turn taking a minute held the thread every other command arrives on: the interrupt meant to stop that turn queued behind the turn it was meant to stop, and the tail tick stopped for the length of the reply. (#322)

v0.1.26the Superintendent opens, and says so when it does not

v0.1.25 shipped the live Superintendent; this release is about the moment it is launched. Every fix here is on the seam between the panel and a vendor CLI — which conversation gets resumed, what a refused one looks like on the wire, what a Cursor turn is actually allowed to call, where the pi binary is, and what the Principal reads when the process never started at all. The rule underneath them is one rule: a runtime that died must not arrive wearing the agent's voice.

Tip: 1931a21 (E-super-launch, #314).

Highlights

A resume id belongs to the runtime that minted it

The Works session cache held one agent_session_id for the whole Works, but a resume id is a dialect's id — a Claude uuid, a Cursor chatId, a Pi session file. After a Cursor or Pi conversation, opening Claude handed claude --resume a foreign id it rejects, and the panel opened dead.

The cache now holds agent_sessions, keyed by the ExecutorProvider name. Open reads its own runtime's slot, respawn re-reads it, close writes that slot alone — so a switched runtime opens fresh rather than resuming someone else's conversation, and never overwrites a key it cannot use. A 0.1.25 cache still parses; it reads as no resumable session, because nothing on disk says which runtime minted that lone id. (#315)

A failed Claude terminal is a failure, not silence

The CLI closes a refused --resume with an ordinary-looking terminal: subtype: error_during_execution, is_error: true, the sentence in errors[] and nothing in result. The adapter read only result/text, so a session that never opened came back as the Superintendent having nothing to say — and the panel then retried the same bad id.

That terminal now closes the turn as failed, carrying the CLI's own sentence, classified StaleResume when the conversation was refused and AgentHostError otherwise. is_error alone is still a reply: a turn that narrates its own error is a turn. The Works session recovers it the way it recovers a dropped pipe, with the one difference that matters — the respawn goes out without the refused id, dropped from both the cache and the panel's resumed claim. A reconnect that fails again closes the stream with the vendor's line rather than a generic dropped-session status, and the desktop hint table reads no conversation found as a stale resume id, not a missing binary. (#316)

Cursor may call the hand it registered

Registering hm mcp serve in .cursor/mcp.json and approving it with --approve-mcps is not permission to call it: a headless -p turn auto-rejects every individual tools/call, so a Superintendent that looked wired reached the catalog and was refused at every tool.

The missing half is now materialized beside it — Mcp(hm:*) merged into the workspace's .cursor/cli.json permissions.allow, on both paths a turn arrives by (the one-shot run and the always-alive open spawner). It is a merge, not a write: the operator's own keys, allows and denies are written back untouched, a re-run adds nothing, and a file that does not parse is named rather than replaced. It widens nothing else — no Shell(...) entry, no --force/--yolo — and the write/dispatch half of the catalog is still held by hm mcp serve --class-gate for Principal approval.

pi is resolved the way a login shell would resolve it

A Finder-launched desktop inherits /usr/bin:/bin:/usr/sbin:/sbin, and pi sits off that list. The Pi host was the last sibling handing a bare name to the OS. Both its paths now go through resolve_agent_cli — the HM_PI_BIN override, the well-known install prefixes, the login-shell PATH, then the process PATH — resolved at open and per turn. And when there is nothing to find, the failure names the binary and every candidate tried, instead of the old io error at <works root>, which pointed at a working directory that was never the problem.

A turn that died before its host spoke reads as a spawn failure

A CLI that dies at spawn does not always say so in a classifiable way: it closes the turn with is_error set, no words, and nothing in errors[]. The empty-result fallback then reported the host's no-output placeholder — a dead runtime wearing the Superintendent's voice, and the one thing a Principal cannot debug from.

The panel now reads the shape. The store latches whether the host has ever announced itself (the CLI's system line, any word or work from the agent, the no-stream notice), and a wordless turn while that is still false lands as a Runtime notice naming the death, with the same operator hint a failed tool result gets — PATH / HM_BIN, stale resume id, permission, MCP unreachable. The failed-before-init half arrives as a throw and is reframed identically: the seam's sentence as the body, under the runtime's name instead of the agent's.

Three boundaries keep it honest. The latch is per conversation, not per turn — the always-alive host announces itself once and every turn after rides the same child — and it resets wherever a host is spawned. A host that does not stream is never read this way. And a cancelled or failed terminal is left alone: the first is the Principal stopping the turn, the second is the vendor's own sentence and cause, both more specific than a shape read off the stream. projectRecord applies the identical rule, so a reload says what the Principal was told the first time. (#319)

hm superintendent is the digest, and now says so

The terminal command reads the ledger, prints the Today digest and the toolbelt, rotates a cache under runs/superintendent/, and exits. Every sentence around it said otherwise — the help called it "the Principal's sole comms entry", the catalog row called it an "always-alive orchestrator session", the run opened with "Superintendent session started". A Principal who believed those sat at a prompt waiting for a conversation that was never coming, and read the silence as a broken agent.

The help, the catalog row, and the banner now carry one claim: this is the digest and the cache, it spawns nothing, and the live Superintendent is the desktop app's bubble — which starts its runtime over hm mcp serve against the same cache --resume re-attaches to. The banner says it on the run, not only in --help, because the misreading happens at the prompt. SEAM.md carried the inverse of the same claim from before the panel landed; it now names the floating bubble as the live seam and the CLI as the digest read beside it. (#320)

Release plumbing

The first release to carry the app found the artifact one directory out of reach: upload-artifact roots the archive at the fixed prefix of its path, so releases/*/macos/*.dmg shipped <version>/macos/ inside the artifact. It got that far because sha256sum … | tee reports tee's status, so an unmatched glob was a green step with an incomplete SHA256SUMS behind it. The DMG is now staged flat, and a missing artifact fails where it happens.

v0.1.25reaching a prior worker, prose while it is written, and the desktop as an app you install

This release ships three packs that arrived together: hm listen, the explicit way to reach a worker who already touched a work unit; Superintendent happening-prose, which streams a Claude or Cursor turn as it is written rather than as confetti at the end; and the macOS desktop bundle, which puts hm inside the app so a downloaded .app never depends on the login shell's PATH. Underneath all three, hm_core grew four named layers — comms, host, policy, stream — so the streaming surface has one owner instead of several.

Everything that landed on main after v0.1.24 is here: 216 commits, including the council bodies, the run card, billing, and the staging plane's move to Nomad. The CLI remains the mutation source of truth on every surface below.

Highlights

hm listen — a target is built, never parsed

  • hm listen list --ticket|--manifest prints everyone a work unit has a record of, each with the reach it actually has. Nothing is filtered: a seat that worked the unit and is gone comes back listed and unreachable, with the sentence saying which, and a unit nobody has touched is an honest empty rather than a fallback to the whole roster.
  • hm listen show --mention '@name' is the resolve that happens before a call — the roster rules on the name, and what comes back is the explicit --seat / --ticket argv to call with. A name the roster does not hold prints as that answer, with the near misses, and still exits zero.
  • hm listen call --seat … --ticket … says one thing to one explicit target. The body is read from stdin, never argv: a message that happens to read as a verb would otherwise classify the whole invocation read-class and skip the approval card the Principal expects.
  • The same pair reaches an agent runtime over the MCP bridge as list_reachable / call_agent, generated from the hm workflow show catalog rather than hand-registered — the catalog row is the registration.
  • Delivery is comms' queue for the ticket the target holds the latch on, so a call is mail that commits; a live Claude seat is nudged after the mail lands.

The rule the surface is written against: hm may look a name up, but the lookup is the registry's and it happens before the call. The moment a vendor CLI is asked to find an hm seat in its own mention typeahead, the answer stops being the roster's. Free-text @ in an arbitrary hm subcommand is therefore not the CLI path — the explicit listen subcommands are.

Superintendent prose, as it is written

  • A live Claude or Cursor turn streams its narration while it is still being produced, and a long think is said to be a think while it is one, instead of a blank working fold.
  • The closing-summary contract carries onto the live session, and an empty result falls back to the accumulated narration rather than replaying the blank behind it — the Principal reads what the agent actually said.
  • The last turn's work stays on screen while the next one starts, and the effort a turn is spending is visible rather than inferred.
  • Live MCP writes and dispatch are held for operator approval instead of running silently; tool failures surface instead of disappearing.
  • A headless Cursor turn trusts its workspace so it does not stall, and is read through --stream-partial-output.

The desktop is an app you install

  • The macOS bundle is on: hm ships inside hm.app as a Tauri sidecar, so an app launched from Finder — which inherits launchd's PATH, never a login shell's — can still run every write. make desktop-dmg builds it; make desktop-dmg-staging builds the same bundle against the staging account plane, under releases/<version>/macos-staging/.
  • The account plane is baked at build time, and the session says which one issued it. A runtime environment variable cannot point a downloaded bundle somewhere else.
  • Sign-in is composed around the device grant — no password field, no provider buttons, no gate — and the account session is a row you can sign out of, which means gone.
  • The Works you had open is a square in the rail; window geometry, recent Works, and per-Works cursors persist; the theme is read before there is a window.
  • Every webview binding declares when it stops being true.

hm_core, in four named layers

comms (the conversation layer, its queue, and one router that answers where a reply goes), host (one owner per agent dialect, so a host can hold a pipe without reading it), policy (the gate, the classifier, the allowlist), and stream (one streaming surface, whether a pipe held it or a file did). The reclassify dropped twelve addresses and shrank lib.rs onto five names.

Councils, petitions, and the run card

  • One timeline row shared by every council body; the petition remembers who filed it, deliberates, and ends in a list that ends. Briefs are listable, the council session is reachable, and the Clerk records the body.
  • Activity, Mail and Hand Session became one run card, with the turn rows, the focused drawer, a status dot over four states, and an action row over the verbs that actually exist.
  • hm ticket mail is a queue a dispatched hand pulls; a per-ticket Hand Session resumes the hand rather than dispatching it.

Billing, feedback, and the staging plane

  • Stripe test-mode checkout, portal, and signed webhooks for staging, over a plan catalogue and entitlement read model, with Team billing UI on account-web and desktop Settings.
  • hm feedback send is Works-agnostic, with a labelled Feedback row in the nav-footer; the relay is POST /feedback on the account plane, which accepts then delivers to Discord from a server-side webhook, never a client artifact. Discord egress lives in hm-connectors behind a non-default feature.
  • Staging deploy is a Nomad submit over hm-*-staging jobspecs and GHCR images, with the planned-prod systemd path split away from it. Outbound mail goes through Pingram, and hmcortex.io is retired — hmcortex.com is the name.

v0.1.24E0-align: a deploy seat, the credentials it never reads, and Reckonings

This release ships the E0-align pack: a named deploy seat, written toolbelts, a path policy that refuses to pre-approve a secret read, Works-level secret providers, and seat-scoped MCP with the credential held in the server's process rather than in the hand's environment. It also carries the Reckonings pack and everything else that landed on main after v0.1.23. The CLI remains the mutation source of truth on every surface below.

Highlights

The deployer seat, and a toolbelt you can write

  • docs/reference/templates/deployer-seat.toml stands up an Operations Line with superintendent-operations, hand-operations, and a dedicated deployer-operations seat, applied through hm org apply-template --file so org/roster.toml is never hand-edited. The id sits outside the hand- prefix on purpose — no hand inherits the deploy toolbelt by being a hand — and its role stays hand, so the doctor auth probe and the charter excerpt in a prime still cover it.
  • hm executor set --seat <id> writes one seat's executor entry — provider, script, model, permission mode, allowed tools, timeout, budget — with --dry-run and --json. Only the fields passed move, the seat must exist in the roster, and every write records executor.seat_set.
  • hm executor toolbelts ships the three documented belts: build-hand, deployer (build-hand plus gh and rsync), and superintendent (MCP-only). The compiled default stays narrow; widening is a per-seat opt-in.

Route, don't read

  • A path policy judges an allowed_tools list entry by entry. Refused on the write path: a pre-approved secret store (gopass, pass, vault, op, sops), an environment dump (env, printenv, export), a nested shell, sudo, and a bare Bash or Bash(*). Warned: Bash(ssh *), Bash(scp *) and Bash(curl *) — authorable shapes that want a narrower one.
  • The gradient is deliberate: refuse on write, warn on read. hm executor set names every offending entry and writes nothing; hm executor probe and hm works doctor (executor.tool_policy) report a list that was hand-edited into org/executors.toml, so the finding appears wherever the belt came from.
  • Said plainly in every place the policy appears: an allow list is a permission list, not a tool filter, and none of it is containment. Containment is --strict-mcp-config plus an MCP-held credential, which is the next two entries.

Secret providers, and keys injected server-side

  • [secrets] in hm.toml names the provider as a property of the Works: gopass, onepassword, env for CI, or none — the default, which resolves nothing rather than probing whatever store happens to be installed. Paths follow <prefix>/<env>/<KEY>, a grammar narrow enough that a pasted credential cannot parse as one.
  • Keys reach the MCP server's process and nowhere else. The seat says which servers it gets; the Works says how those servers' keys resolve. A seat can never widen its own credential reach, and injection is refused outright for a seat that has not asked for a strict MCP surface.
  • Nothing discloses a value. hm secrets list shows paths plus the exact command hm would run, hm secrets check shows presence and a truncated sha256 and exits non-zero when a key is missing, and there is no reveal flag on any surface. hm works doctor gains warn-only secrets.* findings and never calls the provider — a health check must not block on a passphrase.

Seat MCP on the Claude hand

  • [seat.mcp] resolves a seat's MCP servers into the hand it dispatches (--mcp-config and --strict-mcp-config). A seat with no table passes neither, which leaves the hand on the operator's ambient MCP surface rather than on none. Both Claude superintendent argv builders now pass --strict-mcp-config, so the registered catalog is the whole MCP surface instead of an addition to it.
  • hm mcp serve --seat <id> pins HM_SEAT on every child command, so an MCP-routed hm ticket complete is attributed by contract rather than by process-environment inheritance.
  • hm mcp proxy --seat <id> --server <name> spawns a declared server with the resolved credentials in that server's own environment, stdio inherited, exiting with the server's status. Every credentialed server a seat declares is re-pointed at the proxy before the hand's config is built, so the hand's argv carries names and a path and no value. One mcp.proxy.launched event per spawn records the seat and the environment variable names only, and hm mcp stays out of the tool catalog so an agent cannot call the launcher as a tool. Verified end to end against Porkbun's own DNS server: a real handshake through the proxy lists its tools, and a live call reaches the API with the injected keys.
  • hm works doctor gains secrets.seat_not_strict, which catches at check time the seat that would fail at spawn.

Reckonings

  • The record — hm reckoning run allocates an R-041-shaped per-Works key and requires an authored --title. Nothing derives a title from the feedback paragraph beside it.
  • The read half — hm reckoning list with inclusive --since and --until bounds, and hm reckoning pending for landed manifests still owed a ruling, longest-waiting first. A backwards window is refused rather than answered with an empty list that reads like nothing happened.
  • The decompress pipeline — [reckonings] decompress_window in hm.toml is a fortnight by default, bucketed from a fixed epoch Monday so a window has the same identity on every machine. The store lives at .hm/reckonings/decompress.db, beside the ledger and never inside it, and is safe to delete. hm reckoning decompress show reads it and never recomputes.
  • The desktop screen — the timeline spine reads the record, the notes column reads the store, one range picker resolves both, and a drawer turns the landed-but-unreckoned list into a ruling by running the same command the CLI runs. Browsing a retained window issues no advisor calls at all.

Also on main since v0.1.23

Shipped before the pack above and released here for the first time: Lines as a Works-wide unit-of-work roll-up (hm line list) and the desktop Lines screen; near-real-time Overview, where a burst of ledger events costs one board read rather than fifty; the account plane — sign-in, organisations, invitations and settings sync, with its API rewritten in Rust; and the marketing site, whose changelog publishes this note as written.

Upgrade notes

  • Workspace version is 0.1.24; no config migration is required.
  • [secrets] and [seat.mcp] are both absent-by-default. A Works that adds neither behaves exactly as it did on v0.1.23.
  • A hand-edited allowed_tools list is still supported. If it holds something the path policy refuses, hm works doctor now says so — the entry is reported, not rewritten.
  • Run hm works doctor after upgrading to pick up the new secrets.*, executor.tool_policy and executor.unattended_auth findings.

v0.1.23Epic 13: Overview desktop + agentsview projection

This release ships the Epic 13 Overview pack on the packaged hm binary: hm overview (agentsview Option B aggregates + Option C projection cache), Cadence desktop Overview with design-faithful chrome, and E13·11 polish for actionable first snapshots and centralized Agent data status UX. CLI remains the mutation source of truth; Overview is a read surface over hm-core::overview.

Highlights

hm overview (CLI)

  • hm overview — Works-scoped U-row over agentsview's public aggregate API (stats, usage, health); never opens sessions.db, never auto-starts the agentsview daemon.
  • --refresh — drop/rebuild the hm-owned projection cache under .hm/overview/projection.db so Cadence paints fast/offline after a cold pull.
  • --json / --since — machine-readable report and window tuning; freshness ladder fresh | stale | source-cold | unavailable (see docs/runbooks/agentsview-cadence-ops.md).

Cadence desktop Overview

  • Nav destination — Overview is a built Cadence screen (not a Board morph); reads only through hm overview / hm-core::overview.
  • Regions — Insights, Agent health, Reckoning cloud, Recently landed, spend / session volume / model mix / activity / tooling extension panels; honest empty when the projection or archive is cold.
  • E13·11 — actionable first snapshot CTA, centralized Agent data status, compact previews with disclosure, Pulse/Mixes grids, synced_at-only recency.

Install / release smoke

  • tests/install/smoke.sh asserts hm overview --help documents --refresh and hm overview --refresh succeeds on a fresh Works (unavailable is ok).

Upgrade notes

  • Workspace version is 0.1.23; no config migration is required.
  • Set AGENTSVIEW_DATA_DIR when agentsview's archive is not at ~/.agentsview.
  • Run hm overview --refresh after agentsview ingests new sessions to rebuild the projection cache.

v0.1.22Epic 9: desktop shell, Superintendent runtime, and MCP pack

This release lands the Epic 9 Interface-tier pack: a Principal-facing desktop shell layered over the same hm-core public API the CLI uses, a floating Superintendent runtime that drives Works only through hm, mid-run steer as an execution gate beside Gatehouse land, structured Hand transcripts (Pi + Claude) in Detail Activity, and a catalog-driven stdio MCP bridge. Epic 9 does not fork domain logic — CLI remains the mutation source of truth; desktop and MCP are additional shells. Plan record: docs/plans/e9-*-*.md.

Highlights

Desktop shell layering

  • Three-region Cadence shell — nav · center board · Detail/right inspector over a live Works, with no mock ledger data. Reads go through hm-core public APIs; writes spawn hm --works <path> … (E9·1–E9·4, E9·6–E9·7).
  • Floating Superintendent — a global floating agent panel, not a right-pane tab. Opening context is the same hm herald brief --session projection the CLI prints; every send/action chip runs through hm_desktop::runtime::run_hm_tool (E9·7 in-app runtime + UI refinement).
  • Detail / Changes / Activity — board drill-in, bench diff, and Hand monitor stay read surfaces; the Superintendent panel never owns Hand tokens or a private ledger bus. Seam: crates/hm-desktop/SEAM.md.

Mid-run steer (execution gate)

  • hm run pause <ticket> — SIGSTOP on the recorded liveness PID; PID file kept; run.paused audited; ticket stays latched.
  • hm run terminate <ticket> — SIGTERM then SIGKILL; PID dropped; run.terminated audited; redirect is ordinary hm run dispatch.
  • Desktop exposes the same gate from floating-panel chips and Detail Properties (E9·8). Preserve #4: execution gate lives alongside Gatehouse land, not merged with it.

Hand transcript (Pi + Claude)

  • Detail Activity parses runs/<ticket>.log into structured turns (tool calls, results, assistant text, run summary) when the log is Pi JSONL or Claude stream-json, and falls back to the opaque tail otherwise (E9·9 / E9·9b). Presentation read only — no second capture bus, never fed into the Superintendent panel.

MCP bridge (B1 / rmcp)

  • hm workflow show --json — machine-readable projection of the binary-shipped workflow catalog (schema_version, tools with dotted ids, coarse read_only hints). One catalog source; no hand-mapped tool table (preserve #6).
  • hm mcp serve — stdio MCP server (rmcp, dynamic router) that registers tools from that catalog, filters out hm mcp itself, captures child hm stdout/stderr so JSON-RPC stdout stays pure, and audits each call as mcp.tool.called (actor mcp). Mutating tools still append normal domain events through CLI → hm-core (preserve #7).
  • Architecture authority: research bench docs/reference/research/paperclip/hm-mcp-bridge.md (product plan: e9-mcp-bridge.md).

Operator surfaces & docs

  • hm workflow show catalog refreshed for Epic 9 CLI rows (hm mcp serve, hm run pause / terminate, hm herald brief --session, hm superintendent, workflow show --json).
  • ARCHITECTURE.md Interface tier documents desktop + MCP shells, steer, and transcript seams; design authority remains docs/design/README.md + Cadence HTML.

Still open

  • E9·5 / #69 — no-fork contract test that desktop invokes only hm-core public API — still open. The seam and runtime already follow that rule; the automated contract test is not part of this cut.

Upgrade notes

  • Workspace version is 0.1.22; no config migration is required.
  • Desktop app version inherits the workspace crate version (no hardcoded tauri.conf.json version).
  • New CLI: hm mcp serve, hm run pause / terminate, hm herald brief --session, hm workflow show --json. Existing Works continue unchanged.

v0.1.21Epic 11: org/workspace structural parity pack

This release lands the Epic 11 family of Paperclip parity ports: the org/workspace read surfaces (hm line show, hm org tree, hm bench slots) that make hm's single-tenant Works → Line → slot → binding → worktree axis inspectable, plus the negative guards that keep the fold onto that axis honest. Epic 11 is a structural completeness epic — it proves Paperclip's org nesting (company → project → project_workspace → execution_workspace) is fully accounted for as a blend/fold with a cited verdict per row, not a verbatim schema port. Every backlog row is green or deferred-with-reason under docs/plans/e11-epic-completeness-audit.md and the nine docs/plans/e11-*-parity.md seam docs.

Highlights

Org/workspace read surfaces

  • hm line show <line> — read-only Line workspace boundary: the Line's landing branch, its registered bench slots (each with landing branch + workspace status), and manifest→slot bindings (--json for machine output). This is Paperclip's project_workspace_id FK folded onto the Line seam (E11·2).
  • hm org tree — read-only flattened org tree (Works → Lines → bench slots → manifest bindings, --json), each Line node the hm line show boundary. Folds Paperclip's nested company → project → project workspace navigation onto hm's single-tenant org (E11·3).
  • hm bench slots — reads the declarative slot registry ([[benches.slots]] / [[benches.bindings]] in hm.toml, Works-wide or --line-scoped, --json) with no git walk or mutation — distinct from hm bench list, which walks the bench tree on disk. Paperclip's per-run realizeExecutionWorkspace folded to a declarative registry (E11·8).

Structural guards (fold honesty)

  • Single-tenant boundary — hm works doctor → works.tenancy_leak [ERROR] fires if a folded multi-tenant/path column (company_id, project_id, repo_url, persisted cwd) ever leaks onto disk. hm's Works is single-tenant; those columns are deliberately unported (E11·1).
  • Cross-repo routing guard — hm works doctor → works.cross_repo_route [ERROR] fires if a bench_ref escapes its Line subtree. One path family; cwd is derived via resolve_bench_path, never a persisted per-row repo_url (E11·5).
  • Column-fold audit — hm works doctor → bench.column_fold [INFO] enumerates every one of Paperclip's 24 execution_workspaces columns and the Epic 8 absorption ledger (#84–#88 absorbed; #89 land queue explicit non-absorb), so the disposition of each column is witnessable live (E11·9).

Workspace lifecycle & strategy

  • Lifecycle transitions — the isolation-slot workspace record now carries the idle / in_review / cleanup_failed transitions on top of the Epic 8 active → archived baseline, surfaced through hm latch release, hm bench push, hm bench list, and the bench.workspace_cleanup_failed [WARN] doctor finding (E11·6).
  • Provision strategy — git_worktree vs project_primary is derived from slot shape in provision_slot_workspace (a named slot worktrees; the Line-default is the shared primary), not a persisted strategy column; visible via hm bench list / hm line show (E11·7).

Touchpoint keying

  • Touchpoint precedence — hm keys workspaces by ticket-step touchpoint, where Paperclip keys by issue. A touchpoint's bench binding wins over the manifest binding at resolution, surfaced in the hm query board BENCH column (E11·4).

Operator surfaces & docs

  • hm workflow show — the hm works doctor catalog row now lists the three Epic 11 guard findings (works.tenancy_leak, works.cross_repo_route, bench.column_fold) alongside the Epic 8 bench/workspace findings; the golden snapshot is refreshed.
  • ARCHITECTURE.md — the workspaces/isolation section gains an Epic 11 structural-parity note (read surfaces + guards), the doctor findings list picks up the three E11 guards, and the parity-plans index adds the Epic 11 family.

Upgrade notes

  • Workspace version is 0.1.21; no config migration is required.
  • The new Epic 11 doctor findings surface automatically on the next hm works doctor. works.tenancy_leak and works.cross_repo_route are [ERROR] and flip report.ok; bench.column_fold is [INFO] only. All three are read-only audits over existing state — nothing new is persisted.

v0.1.20Epic 8: Paperclip parity upgrade pack

This release lands the Epic 8 family of Paperclip parity ports: the workspace runtime that lets several Manifests run parallel hands on one Line without colliding, plus the content revision-history, audit-trail, and work-graph read surfaces that upstream Paperclip already had. Every seam is audited fold-by-fold under docs/plans/e8-*-parity.md.

Highlights

Parallel hands, isolated by slot

  • Per-manifest workspace isolation — each Manifest binds to a named isolation slot from the Line's [benches] registry; dispatch cwd, prime bundle, and gatehouse land all resolve through one bench path, so two Manifests on the same Line never share on-disk state.
  • Durable workspace records — a named slot now carries a lifecycle record (active → idle / in_review / archived / cleanup_failed), surfaced as workspace.opened / workspace.archived events and bench.workspace doctor findings. The Line-default bench keeps no record (matches upstream).
  • Branch-coherence guard — a latched Manifest's worktree must stay on hm/<manifest>. A drifted worktree now fails hm works doctor (bench.branch_mismatch [ERROR]) and is refused at dispatch preflight before any hand spawns. --no-cut-branch dispatch is exempt.

Landing & reclaim

  • Per-slot landing branch — each slot declares its landing branch (slot → manifest → Works → main cascade), seeded on wire. Drifted slots can be backfilled with hm works wire --reseed-landing --bench <ref>; doctor warns on bench.landing_branch_stale.
  • FIFO land queue — concurrent lands onto a shared landing branch serialize behind a per-scope ticket lock, granted in land-request order. A queued land whose producing bench moved is invalidated (Superseded) and re-dispatched rather than pushed stale.
  • Dead-executor slot reclaim — when a detached executor dies without completing, hm patrol run --fix now frees the isolation slot it held and stale-marks its workspace record, so a dead run can't silently exhaust the slot pool. Manifest complete/land archives the slot's record, gated by a close-readiness verdict.

Content, audit & read surfaces

  • Revision history & restore — content writes append a revision only when the content hash changes; the chain is readable and restorable (list / load / restore_document_revision). A restore appends a document.restored audit event to the works.db ledger.
  • Work-graph read — hm query board --q <substr> --limit <n> and hm ticket list --q <substr> do a case-insensitive substring match over title + acceptance (+ completion note); hm query events can filter the new restore audit via --kind document.restored or --entity <kind>/<slug>. Limits are clamped (board [1,1000], events [1,500]).

Operator surfaces & docs

  • hm workflow show refreshed for the Epic 8 catalog — manifest status and ticket-progress lines, dispatch --no-cut-branch, and the new Epic 8 doctor flags.
  • Docs — ARCHITECTURE.md and the build-walkthrough teaching HTML now cover the Epic 8 workspace runtime; the bench-reclaim runbook documents the reclaim/close-readiness flow.

Upgrade notes

  • Workspace version is 0.1.20; no config migration is required.
  • New doctor findings (bench.workspace*, bench.branch_mismatch, bench.landing_branch_stale, bench.landing_aliased) surface automatically on the next hm works doctor. bench.branch_mismatch is the only one that flips report.ok.