Documentation update log

Documentation update log

This log tracks the evolution of the knowledge base: page additions, deprecations, and structural refactors. It is deliberately decoupled from software release notes and the repository changelog.

2026-10-08

Releases carry standalone binaries

  • Change: a release attaches a standalone binary archive per platform: static Linux on x86_64 and arm64, and macOS on Apple silicon and Intel, each with a SHA-256 checksum. The binary is the default build, server and client in one.
  • Update: Quickstart adds installing from a release binary.

2026-10-07

The serving hub takes an online backup

  • Change: a running hub can back itself up. POST /api/v1/backups, admin only, writes the offline backup's layout and manifest.json into a new directory named for the UTC time under backup_dir (HUB_BACKUP_DIR), so check and restore read it unchanged. Online backup is off while backup_dir is unset; one inside the data directory is refused at startup and again, symlinks resolved, before each backup. One backup runs at a time.
  • Change: agent-hub backup --url URL asks a running hub for the backup as the admin (HUB_ADMIN_TOKEN) and prints where it landed. The offline backup's refusal while a hub holds the store now names --url beside stopping the hub and snapshotting the volume.
  • Change: deleting or overwriting files (artifact and project deletes, a prune's commit, a live artifact write and the update that seals it) waits while an online backup runs, so the store snapshot never names a blob the backup lacks and every copied blob holds the bytes its row describes. The blob copy runs off the request workers, so the hub keeps serving.
  • Change: both backups leave out a project's files that a delete moved aside and could not remove, which the next start removes anyway.
  • Update: Operations describes the online backup, what it guarantees across files, a cron and a systemd timer example, and that old backups are the operator's to prune; the quickstart lists backup_dir; the operating model, the human surface and components name the route and the admin boundary it sits behind.

A question or an approval can carry a deadline

  • Change: question_post, and signal_append with kind approval, take an optional expires_in_seconds (60 to 2592000). An approval may name on_expiry as approve or decline and declines by default; a question closes with no answer. An item still open at its deadline is resolved by the hub, recorded with hub as the actor and expired: true, and agents see it in inbox_read, inbox_wait and the notification trailer as they see a human resolution. A decision after the deadline is refused. Out-of-range input is refused as invalid_argument. The sweep runs under embedded stdio as well as over HTTP.
  • Change: hub is a reserved agent id. An agent a store from before already holds under it has its token revoked when the hub opens the store, and is refused a new one.
  • Change: the Inbox says what a deadline will do on a waiting row and card ("Declines itself in 3h"), and says that an item expired, and how, in Earlier, on its card and on the project feed.
  • Update: Agent surface gains a section on deadlines, Data model lists the new inbox columns, Human surface describes the wording, and Using the hub as a brain and the agent-hub skill tell an agent how to ask with a deadline.
  • Decision: ADR 0027 records why an agent-set deadline on one item is not retention, so the human remains the garbage collector.

A closed app can be nudged through a target the operator runs

  • Change: with notify_url (HUB_NOTIFY_URL) set, the hub POSTs a fixed plain-text sentence to that URL when a question, an approval or an enrolment request starts waiting on the human. The body names no project, agent, title, id or count. notify_token is sent as Authorization: Bearer, and sends are coalesced to one per notify_interval_secs (default 60) with a trailing send for anything that arrived in the quiet period. Delivery runs in the background and never fails the write. Unset, nothing is sent.
  • Change: /metrics carries agenthub_notify_sends_total by result, and agent-hub config lists the three settings with the token masked and the URL shown by its origin.
  • Update: Operations gains a section with a self-hosted ntfy example, Deploy shows the two variables, the quickstart settings table lists the keys, and the quickstart, human surface and README say that a closed app still raises nothing unless a target is set.
  • Decision: ADR 0026 refines ADR 0016: an operator-chosen target and a contentless body are added, and Web Push stays deferred.

A question can suggest answers the human picks with one tap

  • Change: question_post takes an optional options, 2 to 6 suggested answers of at most 80 characters each, trimmed, one line, none blank and no two the same. A list outside those bounds is refused whole with invalid_argument. The options ride in the question's payload, so every read of the question returns them as payload.options. A pick is an ordinary answer whose body is the option's text.
  • Change: the inbox shows a question's options as quick answers above its composer, on the open card and under a row's Reply. One press answers, with no confirmation, and the free-text composer stays.
  • Update: Agent surface describes options and how a pick reads back; Human surface replaces "not yet shipped" for quick answers with the behaviour; Data model names the question payload's fields. The agent-hub skill tells an agent when to pass options and to compare the answer body with them.
  • Update: the open inbox item screenshots (inbox-item, both widths and themes) are recaptured, since the seeded open question now suggests answers.

An artifact version can be held live while an agent writes it

  • Change: a version can be held live while an agent writes it. The MCP tool artifact_draft mints the next version and points the artifact at it, and every later call mutates that one version in place; artifact_update seals it by moving the current version onto it. The artifact's default public URL keeps serving the last sealed version throughout, so a published link never shows half-finished work. The live version is read at ?version=N, or at ?live=1 when its number is not known.
  • Change: GET /api/v1/artifacts/:id/live reports the live state (live, idle or sealed), the version, live_rev, the agent and the session, and the artifact viewer shows a live band naming the agent.
  • Update: Artifacts replaces its "no live editing" statement with the live model; Agent surface lists artifact_draft and Human surface lists the live route and the viewer's live state. The agent-hub skill's artifact reference tells an agent to hold a version live and open the returned viewer URL in its own browser.
  • Decision: ADR 0025 records that a live version is a pointer on the artifact and not a draft, and why the public URL holds still while one is live.

The artifact viewer's comments bar holds the bottom of the viewport

  • Fix: the viewer's comments bar was built at the end of the document, so it floated mid-page while the document scrolled and left the viewport with the last paragraph. It holds the bottom of the reader's viewport now, above the phone's fixed tab bar, and the document scrolls under it. A regression check reads the bar's own geometry at rest, mid-scroll and at the end of the document, so a bar that scrolls away fails the run.

2026-10-06

The embedded tailnet ships in the default build

  • Change: the tailnet feature joins the default feature set, so the embedded tailnet endpoint is compiled into every default build. It stays opt-in at runtime: HUB_TAILNET is still what joins the tailnet, and the container image, built with --no-default-features, does not carry it.
  • Update: Quickstart says the endpoint is compiled in by default rather than needing cargo build --features tailnet.

Agents are told what needs attention on the next tool result

  • Change: every successful MCP tool result may carry a notifications member listing what needs the caller's attention: an answer to a question it posted or a decision on an approval it posted, and events matching a standing subscription it registered with the new notify_subscribe tool (notify_unsubscribe removes one). Each item is delivered once, tracked by a server-side cursor keyed on the agent's actor.
  • Update: Agent surface documents the trailer, the two tools, and the delivery-once rule.
  • Change: the agent-hub skill gains references/notifications.md and references/subscriptions.md.
  • Update: Using the hub as a brain tells an agent to read the trailer and to subscribe instead of polling, and Data model lists the per-agent feed cursor and the two notification tables.
  • Decision: ADR 0024 records that notification lives in the hub rather than in each harness.

The behavioural invariants move to Playwright Test

  • Change: .agents/scripts/invariants.py is replaced by e2e/invariants-01-shell.spec.mjs, e2e/invariants-02-decisions.spec.mjs, e2e/invariants-03-screens.spec.mjs and e2e/invariants-04-project.spec.mjs, on Playwright Test, under the invariants project and its own seeded hub. The script and the web/invariants make target are gone, and web/e2e runs the whole browser suite. The checks assert rendered values and behaviours with web-first assertions rather than driving Playwright by hand with fixed waits. The router exposes its registered screen list to the render-guard check, which is the same shape as the sync counter the shell already exposes.

The PWA static checks move onto standard tools

  • Change: the PWA's static checks are Vitest specs rather than .agents/scripts/check-web.py. The stylesheet and design-token rules (token presence, contrast, the one palette, the 12px type floor) live in .agents/js-tests/styles.test.mjs over the css-tree parse; the build and asset rules (the served asset table, the vendor manifest, the glyph set, the native-modal ban, the first-party script parse, the shell basics) live in .agents/js-tests/web-assets.test.mjs. The make web/check target is gone and make web/units runs both. ADR 0023 records the decision, and the contributor guide the tooling.
  • Update: Human surface names make web/units for the vendor manifest check.

The scripts fold into skills, and the accessibility audit moves to Playwright

  • Change: .agents/scripts/ is being reduced to the checks that still have no standard tool. hub_harness.py is the seeded-hub skill and wiki_screens.py moved into the capture-wiki-screenshots skill. The bundle is validated with okf validate rather than a bespoke script, and the emoji, agent-work-identifier and hook-fixture scripts and hooks are gone.
  • Change: the accessibility audit is e2e/a11y.spec.mjs, on @axe-core/playwright under Playwright Test, replacing a11y.py. The contributor guide records the tooling.
  • Change: the focus-ring and Connect-spacing check is e2e/focus-rings.spec.mjs, on Playwright Test, replacing focus-rings.py. It runs in its own projects for the three widths it needs, one a coarse pointer, against their own seeded hubs.

The frontmatter runner moves to Vitest

  • Change: .agents/scripts/test-frontmatter.mjs becomes .agents/js-tests/frontmatter.test.mjs, on Vitest, and make web/frontmatter is removed. The three layers are kept: the fixture corpus compared byte for byte, the properties over generated input, and the differential layer that answers generated cases with the Rust reference through cargo. The differential skips rather than fails without a toolchain, and runs in make check, where cargo is present.

The wiki publishes to GitHub Pages

  • Add: .github/workflows/pages.yml renders the bundle with the okf-wiki action and deploys it to Pages, after the repository's own validator. The action runs the published image, so the mermaid diagrams render with no network. Pages must be set to build from a workflow once.

The wiki gains diagrams

  • Add: mermaid diagrams where a picture carries more than the prose: the one-process shape in Overview, the endpoint and wrapper boundaries in Components, the two kinds of durable state in Data model, and the session start and question sequence in Agent surface.

The container can be deployed as a service

  • Creation: added Deploy the hub as a service, a guide to running the container under systemd with Podman Quadlet or Docker, with an auto-start policy, loginctl enable-linger so a per-user service starts on boot with no login, and the compose file. The repository ships the Quadlet unit as deploy/agent-hub.container.

2026-10-05

The guide is served over MCP as a skill

  • Add: the hub declares the final Skills extension (io.modelcontextprotocol/skills) with directoryRead, and serves the installable agent-hub skill over MCP. skills/list and skills/get return the frontmatter and a complete manifest with per-file SHA-256 digests and sizes, resources/read serves the files under skill://agent-hub/, and resources/directory/read lists the tree. A client that reads the extension loads the guide from the hub; one that does not reads the same files as ordinary resources. Agent surface states it, so a bootstrap can hand off to a skill over MCP.

The skill splits into a served bootstrap and an installable guide

  • Change: one served document was doing two jobs with two lifecycles. It is now a thin bootstrap at GET /bootstrap/SKILL.md (the hub's address, how to get a token, connect, wire a harness, and prove it), and a progressive operating guide under skills/agent-hub/ with a small SKILL.md and references/ read on demand, installed with npx skills add abn/agent-hub. The bootstrap file is the canonical source and the binary embeds it at build time, so a user who installs the skill first can still bootstrap from it. Agent surface and Human surface name the new path, and Using the hub as a brain points at the installable guide.

The project artifact stage's More button opens its menu

  • Fix: the More control in a project's own Artifacts segment drew a three-dot glyph and answered nothing. It opens the stage's overflow menu now, carrying Start a thread, Comments, Copy raw, Copy path, Copy link, and Open in browser, each doing what the stage can honour. Share is left out because the stage keeps no share sheet of its own. The menu hangs from its trigger in the stage header, the same pattern the project header uses.

The captures repaint across every screen

  • Update: the seeded capture set is re-run in full and committed, so the images match the code as it stands. The previous set had drifted since its own repaint and 49 of 68 frames no longer matched the current build. Every feature screen at both widths and both themes: home, inbox and an open item, projects, the project feed, sessions and the session detail, artifacts and the artifact viewer, wiki and a reader page, search, storage, settings, access, connect, and More.

The viewer's menu hangs from its trigger and the sheet shows one head

  • Fix: the artifact viewer's overflow menu was anchored to the viewer's own positioned box, which spans the index, the stage and the comments column, so it painted over the comments column instead of under its button. It is drawn in the control band now and hangs from the trigger. Human surface describes that band and the menu it carries.
  • Fix: the comment sheet stacked the drawer's own "Comments" head above the compose head, drawing two titles and two close buttons in one sheet. One surface shows one head, so the drawer's head belongs to the list alone. Human surface describes the sheet.

The agent surface answers with what the hub knows

  • Fix: a share link is absolute once HUB_PUBLIC_URL is set, so an agent sharing outward hands on a link it does not have to build. Without a declared public address it stays relative: an address read off the request is the upstream's behind a path-stripping proxy and carries no prefix. The url the create and read routes return follows one rule, so one link is one answer. Artifacts says which is which.
  • Add: /api/v1/projects marks a personal space with is_personal, on the listing and the single-project read alike. An agent's listing holds every other agent's personal space, because a token reads every ordinary project, and a client had to infer which rows those were from the space- id prefix. They stay listed: they are readable, and the hub marks them rather than hiding them. Agent surface states the field.
  • Fix: a path sent to the project store that names no namespace is refused naming /fs/, the one namespace that store has. It used to answer with the brain's own refusal, which offered /kv/ and /fs/, and the knowledge base has no /kv/ for a caller to be redirected to. The session store keeps naming both, because it has both.
  • Fix: Using the hub as a brain claimed there was no server-side per-agent feed cursor and that the agent kept its own under recovery_path. The hub keeps one per agent per project: a feed_read with no since resumes from it and advances it. The session-start sequence drops its since argument, the recovery document is left to hold what the hub cannot know, and the agent surface says the same in its bootstrap convention.

ADR 0023: prove the interface with standard tools

  • Add: ADR 0023 records the decision to prove the interface with standard dev-only tools rather than scripts that pattern-match source text: Playwright Test with role locators and auto-waiting, toHaveScreenshot for visuals, @axe-core/playwright for accessibility, Vitest for client logic, Stylelint and css-tree for the stylesheet, TypeScript and tsc --noEmit for types, and cargo-mutants and Stryker for mutation. AGENTS.md gains the "Tests assert behaviour, not source text" rule. The migration is phased and each phase deletes what it replaces.

The captures repaint for the focus, chips and inbox fixes

  • Update: the seeded captures are refreshed after the v1 fixes: the Connect focus-ring and spacing work, the status pill on one line, the search scope chips, the storage summary on the canvas, the storage and settings rhythm, the wiki row overflow and search-index gutter, the inbox gutter, and the long-summary subject and message. Both widths, both themes.

The client toolchain arrives, and the pure functions are held directly

  • Creation: the PWA gains a Node toolchain of development tools only, in a package.json of devDependencies with the lockfile committed. There is no bundler and no runtime dependency, so the shipped binary and the container do not read it. Vitest holds the pure client functions directly, imported from web/ where they live: a summary's split into subject and message, the reading of an event's kind into a sentence, and the display path a wiki hit is shown under. A rename no longer fails one of these tests, and a branch that is dropped does.
  • Creation: make web/units runs that suite and is part of make check beside the browser gates. make web/types type-checks the client sources with tsc --noEmit over checkJs.
  • Update: the contributor guide records the targets and the client toolchain. make web/types is not a gate yet: the first run reports 273 errors over 31 files, and it joins check when that count reaches zero.
  • Note: the client browser checks and the pattern-matching source-text assertions are unchanged. They are replaced by the phases that port them, and deleted as their replacements land.

2026-10-04

The v1 fixes repaint the captures

  • Update: the seeded captures are refreshed after the v1 fix round, at both widths in both themes. The screens that moved are the project feed, artifacts and sessions (the section switcher now reads all four labels at the default index width), the wiki index and reader, the phone wiki index, Storage (the project name is a link), Settings (the Connect row, and the Version row at 1.0.0), the phone session detail (it now opens inside the project shell), and Home (the Unread chip's number). The capture script also clears the data directory the hub actually opens, so a second run no longer seeds into an existing store and fails.

Search announces its match position

  • Fix: the count beside a search result's Next and Previous buttons is now a live status. Stepping through matches moves the counter and the current match, but nothing a screen reader announced, so the reader was given no position. The counter carries role="status" and aria-live="polite".

The wiki tree's directory rows stop addressing pages

  • Fix: a wiki directory row no longer points at a page. On a desktop the full tree already draws what the directory holds, so the row is a label and does not navigate; on a phone it drills in through the directory address. A row that kept a page address requested a page that does not exist and answered "That page is not in the wiki" for a folder drawn in the tree beside it. The browser invariants hold the row to a directory address, or none at all.

Storage and Settings reach the places they name

  • Fix: the desktop Storage table's PROJECT name opens its project. The name was an anchor with no destination, so it was outside the keyboard path and clicking it left the hash on #/storage. The phone row already linked to the project, so the desktop table now uses the same project address and one address serves both widths.
  • Fix: Settings carries a Connect row at both widths, in the THIS HUB group beside Storage and Agents and tokens. Human surface says of Connect, "Settings has no field of its own; it links here to change a token", but the row was absent, so the only ways back to Connect were a refused request or signing out, and changing a token cost the session first. The row only navigates: the token is still entered on Connect and nowhere else.

The v1 release: install paths, changelog and release runbook

  • Add: Quickstart opens with an install section: the container image with a podman run line and the required HUB_ADMIN_TOKEN, a local podman build that stamps the commit into the Version row, the compose file, and the from-source path. It states the two honest limits up front: background push notifications are deferred and the embedded tailnet endpoint is experimental. README.md carries the same install path and a status paragraph that names them.
  • Add: CHANGELOG.md at the repository root records the release in Keep a Changelog form, grouped into Added, Changed and Fixed and drawn from the conventional-commit history. The repository had no tagged release before this one, so the 1.0.0 section covers the project to date.
  • Add: RELEASING.md is the runbook: preconditions, the version bump, the annotated tag, the image build and push, the GitHub release and its notes taken from the changelog section, post-release verification against the published image, and the rollback of the image, the release and the store.
  • Add: .github/workflows/release.yml publishes on a v* tag. It builds the Containerfile with GIT_COMMIT set to the tagged commit, pushes the image to GitHub Container Registry as the version and as latest, probes the pushed image's readiness, then creates the GitHub release with the matching changelog section as its body.
  • Update: the shipped version moves to 1.0.0 in Cargo.toml and Cargo.lock. The build script already stamps the commit, so nothing about the release is written by hand.

The wiki index and the section switcher, at the sizes they actually get

  • Fix: on a phone the wiki index at its root drew a breadcrumb whose only crumb was Wiki, which the tools row above already names. The breadcrumb now appears only inside a directory, where there is something to go back from.
  • Fix: on the desktop the project's four section tabs were 284px wide with their counts, and the default 300px index pane leaves the switcher 224px, so Sessions was clipped to "Se" and butted against the overflow button. The index pane is a query container: below 360px the counts drop and the tabs take 6px of padding instead of 10, so all four read in full at the default width, and at 360 and above the counts come back. At the 260px minimum the switcher still scrolls.
  • Update: Human interface states both rules. The switcher rule is recorded as a departure in DESIGN.md.

The wiki gains product screenshots

  • Add: docs/assets/screens/ holds the main feature screens at desktop (1440x900) and phone (390x844) width in both themes, captured from a seeded scratch hub with dummy data, and embedded in Human interface, Human surface and Quickstart. They are refreshed by .agents/scripts/wiki_screens.py, whose skill is capture-wiki-screenshots.

2026-10-03

Using the hub as a brain, and the honest harness claims

  • Add: Using the hub as a brain is the adoption path for the remote-brain use case: the MCP server block per harness, the session-start sequence, the one-shot hook form, a migration note for notes kept under a harness home, and a plain statement of what is manual.
  • Update: Agent surface describes the bootstrap convention as one an agent follows rather than one that replaces harness scaffolding, states where the feed cursor lives, and lists the share routes on the human surface. session_start reports the session row's handoff beside the recovery path. Overview names the knowledge base's use case.

The reviewed edges: store, surface, and operations

  • Update: the event ceiling bounds artifact writes and the knowledge base's lifecycle signal as well as feed posts; a missing artifact version names itself; orphan session brain files are reconciled at startup. Operations documents the practical ceilings, the interrupted-restore recovery, and the manual rollback. The MCP gate answers a storage failure with its real status. Agent surface documents the retry and paging contracts the tool schemas now carry.

2026-10-02

Metrics, doctor, and the tailnet rebuild

  • Add: Operations documents GET /metrics and the doctor command. The hub overrides its MCP call_tool so an unknown tool name answers with a hub not_found tool result rather than a bare protocol error.
  • Update: blob IO runs on the blocking pool; the embedded tailnet endpoint rebuilds its device on a full session drop instead of only reconnecting the listener.

The practical ceilings, and blob IO off the async workers

  • Add: Operations gains a Practical limits section: sessions are uncapped and a brain file is refused at 1 GiB, a project's feed holds HUB_EVENTS_PER_PROJECT, a page is capped at 1 MiB and a knowledge base file at 1 GiB with history and last-write read by a linear scan of the brain engine's own write log rather than a hub index, an artifact blob is capped at 50 MiB, and a search reads at most 5000 rows.
  • Update: Artifact blob reads and writes run on the Tokio blocking pool, so a transfer up to the 50 MiB cap no longer holds an async worker.

The feed has a ceiling, ids survive a clock jump, and the hub drains

  • Add: Operations documents the project event ceiling (HUB_EVENTS_PER_PROJECT), the graceful SIGTERM drain, and the health subcommand the container healthcheck runs. Agent surface notes the ceiling on signal_append; quickstart lists the new keys.
  • Fix: event ids are clamped forward across a wall-clock step, and readiness reflects a store that is newer than the binary.

Backup, restore and honest readiness

  • Add: Operations is the runbook for backup, verify, restore, upgrade and roll back, and it is linked from the usage index.
  • Update: Human surface states the readiness probe's real legs (supported version, store identity, content, free space) rather than the single version query it used to describe.

Log headings are OKF-conformant

  • Update: docs/log.md gives each date a bare YYYY-MM-DD heading with the entry title as a subheading, so the bundle passes the OKF validator's okf/reserved/log-date-heading rule as well as the repository check.

The agent can wait, and discovery is self-serve

  • Add: Agent surface documents inbox_wait, an agent-side long poll over the same ticker the enrolment poll uses, and the since cursor on inbox_read; the guide is served as an MCP resource (agenthub://skill) with its URL returned by whoami; and question_post no longer advertises an addressee, since agent-to-agent messaging is deferred.
  • Update: agent-hub tools prints the inputSchema; the API no longer advertises the ignored actor/session_id on artifact publish, and the duplicate session_id on feed_read and artifact_list is gone.

The first run works end to end

  • Fix: Agent surface records that deciding an enrol_request admits or refuses the agent in the decision's own transaction, with the subject read from the event so a forged approval admits nobody, and that the enrol client records the hub URL beside the token. Quickstart documents the startup line and that the control surface needs an admin token even on loopback.
  • Update: Human interface gains a First run section: the rail route to Projects, the empty Home call to action, the token issue on a phone, the setup in the reveal, the enrolment reason, and the no-admin-token Connect state.

Wiki comment threads, the page sheet and the Home review line

  • Add: Project knowledge base and the human interface record the shipped wiki surface in full: comment threads on a page (open threads, a quoted anchor, resolved threads folded under a count), the New page and Save to wiki sheet, the full tree above 768px and the drill-in below, the stale clock glyph, the agent-instruction empty state, and the project header overflow.
  • Update: The storage report carries knowledge_needs_review, the Home storage card's one housekeeping line. The store's kb_comments table is migration 18.

The wiki reviews, promote and conflict path

  • Update: Project knowledge base adds the Review control on the reader, the Needs review list, Save to wiki from a session brain entry, and the editor's in-place conflict path (keep yours or reload the hub's). Rename and move, a rendered-compare merge, and page comment threads are not built; the hub has no page-comment store.

2026-09-29

The wiki human surface

  • Add: Project knowledge base documents the project's fourth section, Wiki: one meta=1 tree, a reader that keeps the frontmatter out of the body and lists backlinks, and an editor that writes back the version it read. The Wiki home carries the counts and the Recent changes and Lint screens; rename, move, a rendered-compare merge, comment threads and the review action are not in this version.

2026-09-28

The share and access copy round

  • Update: Artifacts states the share sheet's real shape: Make link before a share, the link with a copy control and a confirmed Revoke link when one exists, and Delete artifact for a protected artifact, whose withdrawal is deletion because the key never reached the hub.
  • Note: The agent screen renders no read or write level, because a grant is access or no access. The project's own screen carries the lock control (make confidential, make public), and an agent that makes a project confidential leaves a signal feed event.

2026-09-26

Artifact session lineage is creation, not the last write

  • Correct: Artifacts and the data model said an artifact records the session it was "published or updated" during. The row, the search document, the session listing and the API always carried the publishing session; only the update's own feed event carries the writer. An update leaves the artifact's session lineage alone, as it leaves actor alone. The 2026-09-22 entry below is corrected by this one.
  • Update: Artifacts documents capability-based sharing for plain artifacts via revocable tokens, version pinning to the snapshot active at share creation, access concealment preventing existence oracles, and key-governed lifecycle for encrypted artifacts where withdrawal is deletion.

2026-09-25

The operating model is stated once

  • Add: Operating model states the trust posture in one place: one operator, their agents, one node; open by default; every agent reaches every ordinary project and its own personal space; a confidential project needs a grant, and a grant is access or no access; the admin boundary is privilege and not use; a plain artifact link is a revocable capability and a protected artifact's key never reaches the hub. Multi-tenant isolation and defence against a caller that already holds a token are not goals.
  • Update: Human surface no longer claims the whole REST surface is admin-only; it names the privileged verbs and links the model page. Agent surface and Data model record that grants carry no read or write levels.

Markdown rendering sanitization and plain-text comment quotes

  • Update: Human surface documents that rendered session markdown files are sanitized through an allowlist to strip dangerous elements and URL schemes, and resolved comment quotes are inserted safely as plain text.

Final-path artifact blob orphaning and startup reconciliation

  • Update: Data model documents immediate cleanup of promoted final-path files on update transaction failure, and startup reconciliation of on-disk artifact blobs against committed version metadata.

Idempotency namespaces, target binding, and resolved question immutability

  • Update: Agent surface documents that idempotency keys are namespaced per operation and event kind, bound to target entities to prevent cross-entity replay, and that resolved questions reject subsequent answers.

Session ownership atomicity, guarded transitions, and lease validation

  • Update: Agent surface documents that ended or reassigned sessions clear active leases and reject subsequent brain writes.
  • Update: Data model documents guarded session store transitions and file-locked validation of session activity on brain mutation.

Search boundary body truncation, write limits, and locked fork snapshot

  • Update: Components documents character-boundary safe search body truncation at the indexing boundary.
  • Update: Data model documents that session forking copies the brain file under the source session's write lock through the engine to guarantee snapshot consistency across search rows and audit log entries.

Project write barrier and generation-scoped deletion quarantine

  • Update: Data model records the project status column in the projects table. Active project status and authorization are validated inside write transactions across events, artifacts, sessions, and comments, and deletion isolates file directories into unique quarantined paths prior to metadata removal to protect recreated slugs.

2026-09-24

Round 13.1: Version row, agents as a list and item, summary between hairlines

  • Update: Human interface records the desktop changes round 13.1 makes. Settings' THIS HUB group ends with a Version value row, which reads the version and short commit the binary was built from and promises no destination. Agents and tokens is a list-and-item screen with an index beside a stage, not a Settings-shaped form. The storage summary sits on the canvas between hairlines rather than in a card.

Desktop round 12 spine: knowledge tone and content/chrome split

  • Update: Human interface documents the storage-only knowledge tone (an olive pair set apart from the question tone so a byte count does not read as a question) and the content/chrome split: round 12's content rules hold at both widths, its chrome rules are phone-only, and the desktop keeps its reserved 52px header and 40px control row.

2026-09-23

Phone inbox and search tools rows, flat rows, and search title mark

  • Update: Human interface documents the mobile inbox tools row (filter field plus Unread chip without sync line), the mobile search tools row with horizontally scrollable scope chips and counts, flat rows without card wrappers, and search result titles shown once with an action-tinted mark highlight.

Home screen mobile welcome-first and flat rows

  • Update: Human interface documents Home welcoming at rest on a phone (no bar, no gear, greeting at x 16 following time of day, status sentence in prose, flow chips, and flat rows with storage summary as the only card) and compressed when scrolled past the greeting (standard 52px bar fading in with title at x 48 and chips pinned in the 44px sticky tools row).

Project tools row, brain entry stage, and artifact agent grouping

  • Update: The human surface documents the mobile project tools row with segmented tabs and filter toggle, reading brain fs files in the stage with rendered preview, provenance line, and back control, reading kv entries in the entry aside with copy control, and grouping artifacts by publishing actor in the artifact index.

Phone frame, tools row, and more tab root

  • Update: Human interface documents the phone frame (collapsing header from 76px at rest to 52px when scrolled past 20px, with hysteresis expanding at 8px, 120ms ease-out transition, instant under reduced motion, and a 48px reserved leading slot), the sticky 44px tools row with no-wrap chips and buttons, and the five-tab mobile navigation bar introducing the More tab root for Storage, Agents and tokens, Settings, and the sync line.

Artifact thread counts on listing and reads

Artifact author recording

  • Update: The agent surface, Data model, and Artifacts document author persistence on artifacts. Publishing records the resolved principal identity in artifacts.actor, which cannot be forged or supplied by the caller. Updating an artifact retains the original creator. Artifact queries, listings, and reads across REST and MCP carry the author field, returning null for rows that predate the column.

Read single brain entry over REST

  • Update: The human surface documents GET /api/v1/sessions/:id/brain/entry?path=, returning one entry's text content, kind, size, and written timestamp when recorded. Directory paths answer 409 Conflict, non-UTF-8 bytes answer 422 Unprocessable Content, and reads against missing brains answer 404 without creating a file.

The shell's keyboard path and pane resizing

  • Update: Human interface documents / going to the list's own filter field rather than to the Search screen, and c toggling the comments aside, which is the keyboard path the design's shell asks for.

2026-09-22

The design contract is written down

  • Add: DESIGN.md at the repository root states the design contract for the human surface: tokens and the one documented deviation, the type scale and the 12px floor, the twelve glyphs, the one shell (rail, index, stage, aside) and the rule that the frame does not move, the components, the screens, the interaction and keyboard rules, the alert hierarchy, the copy voice, and the twelve build gates. It records where the build deviates from the designer's handoff and why.
  • Update: Human interface corrects its desktop description to the app rail that ships and points at DESIGN.md for the designed shell.

Session lineage for artifacts and feed

  • Update: The agent surface, Data model, and Artifacts document session lineage on artifacts and session filtering across feed and artifacts. Artifact publish and update record the caller session derived from the authenticated principal, with callers unable to forge lineage. Feed and artifact listings support optional session filtering via REST routes and MCP tools (feed_read, artifact_list), returning empty results when querying an unknown or pruned session. Artifact search documents are preserved during session pruning.

2026-09-23

Agent self-enrolment and pending token refusal indistinguishability

  • Update: The agent surface documents the self-enrolment workflow (agent-hub enrol, POST /api/v1/enrol, and GET /api/v1/enrol/status?wait=N), operator approval and refusal via inbox, secure 0600 token storage in config.toml, and the security invariant guaranteeing pending token refusal is byte-for-byte indistinguishable from unrecognised tokens.

2026-09-22

Unified TOML configuration and inspect commands

  • Update: Quickstart documents layered TOML configuration (config.toml) shared by hub and client across system and user locations, overridden by environment variables, and the agent-hub config inspection commands (--path, --check).

Trust removal and confidential projects

  • Update: ADR 0021 moves to stable as trust is removed from the principal, policy, API, and schema. Authenticated agents read and write all ordinary projects, and confidential projects are completely absent without an explicit grant.
  • Update: Data model, Quickstart, and Overview remove HUB_TRUST_DEFAULT and trust levels from agent records, and document confidential projects.

Project deletion overflow menu and typed confirmation manifest

  • Update: The human surface documents the project deletion flow: an overflow menu in the project header with Project settings, Copy path, and Delete project (excluded on personal spaces), a dedicated 330px modal dialog featuring an impact manifest with counts for Artifacts, Threads, Files on disk, and Agents that have written, and slug-matching typed confirmation before execution.

Settings gear relocated to home and project creation sheet

  • Update: The human surface documents relocating the Settings gear control from the Projects header to the Home header at phone viewports (< 720px), replacing the Projects header gear with a 36px New project button, introducing a dedicated Projects empty state with a 48px primary action, and adding the responsive project creation sheet and modal with live slug derivation and conflict resolution.

Grouped settings layout, segmented controls, and alert states

  • Update: The human surface documents the Settings screen organized into four groups (Appearance, Alerts, Access, This browser) with seven controls total and no sub-pages except Access. Appearance provides segmented controls for Theme and Density with pointer-derived consequence copy, and a switch for single-key shortcuts. Alerts renders four states (Not asked yet, Granted with master switch and kind toggles, Blocked, or Unsupported). This browser documents local token retention and forgets it locally upon signing out without affecting other browsers or agents, with an ink Sign out action behind confirmation. On mobile (390px) groups stack vertically with 12px mono uppercase labels; on desktop (1100px) groups render in a two-column layout with 132px label column and 560px cards.

Radius hierarchy, fixed trigger labels, and glyph additions

  • Update: The human surface documents the interface radius hierarchy and trigger conventions: containers are rounder than what they contain, pills are reserved for values rather than doors, and dropdown or action triggers use regular button radius with a fixed label, a chevron indicator, and a value chip. The Access row in Settings renders the 17px ID card glyph instead of the key glyph. Additional glyphs for ID card, sign-out, trash, bell, bell-off, and check are added to the icon system.

Desktop home layout and inline waiting row actions

  • Update: The human surface documents the desktop Home layout: a single 640px measure column held left against the permanent app rail with natural margin filling remaining window width, and inline action buttons (Approve for approvals, Reply for questions) rendered directly on waiting rows at desktop widths.

Desktop storage screen

  • Update: The human surface documents the desktop Storage screen layout from 720px: four summary tiles across the top (On Disk, Artifact Blobs, Session Brains, and Reclaimable with a Prune all action) followed by a full-width multi-column table replacing mobile drill-down cards. The table details per-project usage across Share, Total, Blobs, Brains, Reclaimable, Last Write, and Prune. Projects with no ended sessions show a dash for reclaimable space rather than 0 B, Prune buttons appear solely on rows with reclaimable bytes, and free host disk space is omitted with an explanatory footnote.

Desktop search layout, preview stage, and match highlighting

  • Update: The human surface documents the desktop Search layout from 1100px: a 420px results index beside a preview stage, allowing readers to preview search results without opening. Match highlighting marks the active match with --accent-bg and a 1px accent ring, and others with background alone, paired with a match counter and step controls in the stage header. Scope pills include an interactive dismissible project filter chip, and result groups remain in a unified list.

Desktop artifacts list grid and three-pane viewer

  • Update: The human surface documents the desktop artifacts list and viewer layout. The gallery presents a 5-up card grid at desktop widths with 9px unselectable preview ornaments, a lock tile for encrypted artifacts, a Group trigger button with value pill, and a Cards and Table view segment. The viewer redraws into a three-pane desktop shell with a 280px index column, a 640px document measure, and a 320px comments margin column. Resolved comment threads carry a check glyph and the word Resolved.

Desktop four-zone layout for sessions and brain file viewer

  • Update: The human surface documents the desktop four-zone layout for Sessions: the navigation rail, 340px session index, 300px brain tree pane, and dedicated file viewer in the stage. Session rows maintain a 44px height with two-line layout showing id and working name, the ended group header carries prune all with size, handoff notes display directly in the detail header, and brain tree items render full file names without truncation.

Desktop project screen layout, aside, and inline actions

  • Update: The human surface documents the desktop project screen layout: a 320px aside at 1280px and above (toggleable from 1100 to 1279px) with three sections (Right now, Storage, and Latest artifacts), and inline Approve and Reply controls on feed rows.

App rail, pane layout zones, and prose measure

  • Update: The human surface documents the desktop shell layout: a permanent vertical app rail replacing the horizontal top bar (200px fixed at 1100px and above, 56px icon rail at 720 to 1099px), the four layout zones (Rail, Index, Stage, and Aside) across breakpoints, exclusion of personal agent spaces from the rail, and relocation of the viewport width cap to a 640px measure container for prose.

Agent creation and project grant forms on access screen

  • Update: The human surface documents the restored agent creation and project grant controls on the Access screen (#/access). Operators can register new agent identities by id and display name, and grant project access with binary assignment.

The token is the identity

  • Add: ADR 0021 records the identity model the hub is moving to: a token names who is calling rather than which agent, a declared agent name sets attribution only, every call authenticates, ordinary projects are open to any token, and a confidential project is reached by grant and is absent to everyone else. Trust is removed rather than reinterpreted, and grants are binary.
  • Note: the record is proposed, not stable. The interface already has no trust; the API, the policy and the served skill still carry it, and the divergence is listed in the record itself. It exists because the decision lived only in conversation, which is how the two halves came apart.

2026-09-21

Share sheet and per-artifact password choice

  • Update: Artifacts and The human surface document that the per-project password policy setting has been removed. Encryption is a per-artifact choice made when sharing through the share sheet in the artifact viewer overflow menu. The sheet provides a public link, an optional password switch that encrypts before leaving the device, separate copy actions for the link and password, and link revocation.

Document comments, text anchors, and margin cards

  • Update: The human surface and Artifacts document inline document comments: open comments anchored to the version being read highlight quoted text with a tint and 2px underline, point anchors render a gutter pin, and body line-height expands from 1.6 to 1.7 in commented documents. Minor formatting differences are absorbed by normalising whitespace and case. Comments on older versions link directly to that version rather than re-anchoring. On mobile, comments render in a bottom sheet for individual threads or a full drawer list with collapsed resolved rows; on desktop from 900px, prose stays at 560px beside a fixed 272px comments margin column with interactive cards.

Access screen and identity model documentation

  • Update: The human surface and the agent surface document the standalone Access screen (#/access), replacing the previous trust management model. Grants are binary per project; tokens act as their own identities and may be shared by multiple agents. The Access screen displays the admin token with a copy control and configuration origin note, agent records with personal space paths and per-agent token reissue, revocation, and ungranting actions under a confirmation dialog, and revoked tokens as history. Confidential projects are absent rather than refused.
  • Update: The human surface documents that search scope chips follow the project feed's unified chip treatment: 32px pills (13/500) on a single scrolling row with sentence case labels and an ink fill on the active chip. A pseudo-element provides the 44px tap target floor under a coarse pointer. Search chips display no count when no per-scope count is known prior to running a query.

Compact artifact title bar, glyph set, and viewer geometry

  • Update: The human surface documents the compact 60px artifact viewer chrome: a 44px top row holding the back chevron, mono path, and three glyph buttons (start-a-thread or comments with count, copy-raw with toast feedback, and overflow menu), and a 16px meta line below holding author, version control, size, and age. Prose starts at 104px under the 24px document title.

The PWA serves correctly behind a path-stripping reverse proxy

  • Update: Quickstart documents that a reverse proxy may mount the hub on a path (https://host/hub/) as long as it strips the prefix before forwarding, that the shell normalises a trailing-slash-free entry on its own, and that this needs no configuration: there is no base-path environment variable.

Phone shell layering, mobile settings route, and install icons

  • Update: The human surface documents the phone shell fixes: the fixed tab bar carries z-index: 20 so list row controls cannot paint over it while staying below toasts, dialogs, and drawers. The projects index screen at #/projects adds a Settings gear icon beside the New link so Settings is reachable on a phone from Home without visiting a project. The web app manifest includes raster PNG icons (192px, 512px, and maskable) generated from the mark and served by the embedded shell table.

Resolved questions carry attached answers in inbox

  • Update: The human surface and the agent surface document that a resolved question in the inbox carries its attached answer object holding who replied, when, and what was written, matching the shape returned by inbox_read.

Artifact viewer redraw, version sheet, and grouped list

  • Update: The human surface documents the redrawn artifact viewer, version sheet, and grouped artifacts gallery. The artifacts list groups by Day (default), Agent, or Kind with count badges in headers. The viewer eliminates the nested bordered card and inner scroller in favour of page-level scroll, 16px gutters, and a 640px prose width; the chrome carries a 44px back chevron, mono path, and overflow menu; the document renders its own H1. The version sheet replaces inline dropdowns with 44px rows and an accent rail marking Current. The comments strip renders only when threads exist, and chrome links are never underlined.

Sessions list and session detail redraw

  • Update: The human surface documents the redrawn sessions list and detail views. The list groups sessions into active and ended sections with counts and a "Prune all" control on the ended header. Rows lead with the owner in their meta line, display size in a right-hand column, and use a stretched link to make the entire row pressable. The detail view removes separate stat cards in favor of a single unified meta line, middle-truncates the session ID in a copy control with full ID copied to clipboard and 44px tap reach, unifies keys and files into a single brain tree with kv/ and fs/ folders and leaf names only, and replaces disabled prune buttons with a single primary action and informative helper sentence.

Projects index screen

  • Update: The human surface documents the projects index screen at #/projects, which lists all projects with agent, artifact, and footprint counts, amber waiting or accent unread badges, and a collapsible fold for personal agent spaces.

Project feed chips redraw and row grammar

  • Update: The human surface documents the redrawn project feed chips and row grammar: 32px pills (13/500) on one scrolling row with 6px kind dots, sentence case labels, and counts appended ("All · 9", "Questions · 2", "Approvals · 1", "Finished · 3"). Selected chip carries an ink fill. Artifact and Session chips are dropped as they duplicate project tabs, kinds with zero events are hidden, sibling artifact publishes from one agent within two minutes collapse into one row ("published N artifacts"), and event verbs are lower case and past tense while objects are kept as written.

2026-09-20

Client-side rendering restored for protected markdown artifacts

  • Update: Artifacts records that browser-side rendering is restored for decrypted protected markdown artifacts using vendored marked.js with total-escaping override, ensuring authored angle brackets remain text while supporting callouts and mermaid diagrams. Public markdown artifacts continue using server-side rendering.

Evaluations for multiple tokens and offline knowledge base

  • Note: Agent identity and trust and the data model record that multiple live tokens per agent is to be evaluated. It is not being built and is not refused: today the system issues one token per agent and records neither device nor last use, so supporting multiple tokens would require tracking per-token provenance and timestamps.
  • Note: The human surface and the project knowledge base record that offline reading for the knowledge base is to be evaluated: today what is cached is the app shell and its assets, while page content is not.

Embedded stdio is supported with isolation

  • Update: The operational contract and the quickstart document that embedded stdio mode is supported standalone against the local data directory as the local admin when no HUB_URL is set. Pointing embedded stdio at a data directory already held by a running hub fails at startup with a clear message: a hub is already using this directory; set HUB_URL to reach it instead.

Device-local snooze for waiting items

  • Update: The human surface documents device-local snooze for waiting items. Snoozed items leave Waiting on you for 1 hour, are listed in a Snoozed group where they can be brought back, and snoozing is immediately undoable through a toast.

Decided approvals and answered questions in Earlier

  • Update: The human surface documents resolved items moving to Earlier alongside read items, showing their outcome in words and decision note or answer body without decision controls, with the Earlier count reflecting both read and resolved items.

One markdown renderer for artifacts

  • Update: The human surface records that markdown artifact rendering is consolidated onto the hub's server-side renderer in src/markdown.rs, with strict escaping preserved across both the public standalone route and the in-app viewer, eliminating client-side markdown parsing libraries.
  • Update: The human surface notes that an artifact event row on the project feed links to that artifact in the viewer within its project, while rows without an entity destination in the app remain unlinked.

Search matches by prefix with whole-word ranking

  • Update: The human surface updates the Search row to describe prefix matching: unquoted words match terms that begin with the typed query, whole-word matches rank above prefix-only matches, and balanced quoted phrases remain exact.

The shipped skill says how to write for the human

  • Update: the bootstrap skill served at /SKILL.md gains a short section on writing for the human: the summary stands alone, the ask comes first, detail goes in the body, a question's subject is the question, and nothing is thanked or apologised for. The hub is the only thing an agent reads before it writes to a person, so the document that teaches the tools now teaches the voice with them.

One way to set a token, and it checks

  • Update: The human surface says the Connect screen is the only place a token is entered, so a token is never kept without the hub having accepted it. Settings keeps no field of its own: it says whether this device holds a token and links to that screen to change it, beside Sign out. Saving a preference does not touch the token.

A route change focuses the screen's heading

  • Update: The human surface says a route change moves focus to the new screen's own heading rather than to the whole content region, so the focus ring frames the heading, and that a screen which has already placed focus inside itself keeps it.

Desktop shell polish and vertical centring for Connect

  • Update: The human surface notes that on desktop the Connect card is vertically centred in the available space.

Sessions layout on phone width

  • Update: The human surface describes the Sessions screen phone layout, where the list is shown alone rather than stacking beside an unrequested detail pane, an opened session replaces the list, closing it returns focus to the opened row, and controls meet the tap target floor.

Feed chips meet the tap target floor on a single scrolling row

  • Update: The human surface records that the project feed's kind filter chips meet the 44px tap target floor on a single scrolling row with sentence case labels and counts, superseding earlier wrapped layouts.

A screen that asks for the access token

  • Update: The human surface adds the Connect screen, where a reader enters the hub's access token. A refused request sends them there with the route it interrupted, the token is checked against the hub before it is kept, and a refusal shows the hub's own words beside the field. Settings says whether this device holds a token and offers Sign out, which asks first and then forgets it.

The Inbox calls a project by its name

  • Update: The human surface says an Inbox row's footer and the open card name a project by its display name, and by its slug when no name comes with it, as the other screens do.

An inbox item names its project

  • Update: The human surface lists project_display_name on every inbox item, over REST and over inbox_read alike, null for an id no project row carries. Home's waiting_items are the same entries, so their shape is unchanged.

The decision dialogs take a note

  • Update: The human surface says the Approve and Decline dialogs offer an optional note, how its count, the hub's refusal of one too long and Esc over a written note behave, and that the project feed shows the note on the decision's row. The inbox does not list resolved items, so it shows no decided approval.

A search row says what it found

  • Update: The human surface says a search row draws the event kind and actor of a feed hit, the version and size of an artifact hit, and the session name and status of a session brain hit.

Home lists the waiting queue itself and names the node

  • Update: The human surface says Home's waiting card is drawn from waiting_items, three shown and the rest counted from waiting, and that Home carries the status strip's node line from node where the top bar is not on screen. Home is still one request.

The Storage screen draws a row's four parts

  • Update: The human surface says a Storage row now shows a project's events beside its sessions, artifacts and knowledge, in its bar and in words, that the summary card names the shared part of the hub database and gives its size, and that projects holding nothing fold under a count while an emptied project keeps its row.

The screens call a project by its name

  • Update: The human surface says the Storage rows and their prune dialogs, Home's rows and the search rows name a project by its project_display_name, and by its slug when no name comes with it. Links still carry the slug. The inbox listing carries no display name, so its rows still print the slug.

The storage report weighs only new events

  • Update: The human surface says how a storage row's events_bytes stays cheap on a long feed: the first report weighs the feed, later ones add the events appended since, and a committed prune, a project delete or ten minutes start it over.

A decision's note is kept, capped and handed back

  • Update: The human surface describes the note a decision may carry: stored as payload.note on the decision's feed event, returned as decision.note on the approval's inbox entry, and refused with a 413 past 2000 characters without deciding anything. The inbox and decision routes join the response table. The served skill document tells an agent how to read the outcome of its approval. The dialogs do not offer a note yet.

A feed snippet is never serialized JSON

  • Update: The human surface says what a search snippet is made of. A feed hit shows the event payload's body when it is a string and the summary otherwise, where it used to show the opening of the payload's JSON. The corpus is written as before, so every payload word still matches and an existing store needs no rebuild. The served skill document tells an agent to put the sentence worth reading in body.

A search hit says what kind of thing it is

  • Update: The human surface and the agent surface list what a search hit carries by family: event_kind and actor for a feed hit, version and size_bytes for an artifact, session_name and session_status for a session brain entry. The fields are read after the result is ranked and confined, so the order is unchanged and nothing of a project the caller cannot see is shown. The served skill document names them for agents.

Home carries the node and the head of the waiting queue

  • Update: The human surface lists two more fields on the home response: node, the host and mode Storage already carries, and waiting_items, the newest five items that wait on the human, newest first, shaped as inbox entries. waiting stays the size of the whole queue, and Home is still one request.

A storage row splits four ways and an empty project keeps its row

  • Update: The human surface lists events_bytes on each storage row and events_shared_bytes beside by_kind, and says how the rows add up: sessions, artifacts and knowledge sum to their kinds exactly, and the rows' event bytes plus the shared part of the hub store make by_kind.events. Every project now has a row, so one a prune has just emptied stays listed with zeros.

Responses name a project as the projects list does

  • Update: The human surface lists project_display_name on the storage rows, on Home's recent events and unseen rows, and on every search hit. It is the name the projects list shows, read once per response, and it is null for an id no project row carries. The screens still print the slug.

Edited since review is about bytes, not seconds

  • Update: The knowledge base restates how trust reaches edited_since_review. The write log now notes which write brought in a page's newest verification, and the page is edited when its newest write stored other bytes than that one. A page an agent writes with its own verified block no longer reads as edited when the second ticks before its write lands, and an edit in the same second as a review no longer passes as reviewed.
  • Update: The knowledge base says what counts as code when links are read for backlinks and lint: a fence that holds a shorter fence, an indented block outside a list, and a code span of any length. It also says that a page's link to itself neither lists it as its own referrer nor keeps it from being reported as an orphan, and names the one case still read as prose, an indented block nested in a list.

A first line that only looks like an opener is refused

  • Update: The knowledge base lists a new cause of a 400 from review and promote: a page whose first line starts with --- and is not exactly --- (--- # comment, ---yaml). The hub used to treat such a page as having no frontmatter and wrote a second block above the first. A first line of four or more dashes is still body text.

A thread id names the start of a thread

  • Update: The served skill document (GET /SKILL.md) says that a thread_id given to signal_append must name the event that starts a thread, that the id of a reply is refused with the thread to name instead, and that a retried write with the same idempotency_key is answered with the first call's id before its thread is looked at again.
  • Update: Human interface now records what the entry of 2026-09-19 said it did: the inbox card's close control, the address being replaced on closing so Back does not reopen the card, where focus goes afterwards, Esc leaving a half-written answer alone from the field or the Send button, and the query being sent as typed. That entry now names the page that recorded each change at the time.
  • Update: Human surface no longer says that under 1% of the volume no storage segment would be a pixel wide: just under the threshold the bar would still be a few pixels.

The viewer's theme control names where a press goes

  • Update: Human surface records the artifact viewer's theme control: one glyph, for the theme a press switches to, and a name that says so. Both glyphs used to be drawn at once under the name "Toggle theme", in the app's viewer and on the artifact page, and for any artifact but an HTML one the press in the app's viewer changed nothing. The viewer now names the theme in the frame's address, and the framed page shows no control of its own, so the two cannot disagree.

Home's storage bar is to scale or absent

  • Update: Human surface records that Home's storage card keeps to the Storage screen's threshold. Under 1% of the volume it draws no bar and says "Under 1% of the volume is used"; from 1% up the fill is the share, with no minimum width. It used to widen the fill to two pixels, which no share under 0.6% of the bar is.

Focus after a card whose row is folded away

  • Update: Human surface records where focus goes when an inbox card closes and its row sits under a folded Earlier: to the Earlier disclosure. It used to stay on the page region.

Closing the inbox card by its own control

  • Update: Human surface records that the inbox card's close control leaves the card the way Esc does. The control used to push a new address, so Back reopened the card the reader had just closed.

A review is not an edit since the review

  • Update: The knowledge base says how trust treats a review's own write. It used to compare clocks only, so a page could read edited_since_review the moment a human reviewed it on a busy node.

Tests keep their files under the build tree

  • Update: The contributor guide records where a test may write: under target/tmp, through the shared test directory or the browser harness's scratch root, never the system temp directory, which is often memory and keeps what a killed run leaves behind. A hook enforces it.

The address the hub logs

  • Update: Quickstart notes that a HUB_BIND with port 0 takes a free port, and that the hub listening log line names the address the listener was given rather than the one configured.

2026-09-19

The inbox card, the selection, search as typed, and the checks

  • Update: Human surface records the inbox card's close control ("Back to inbox" on a phone, "Close" on the desktop, named by the words it shows), Esc closing the card except over a half-written answer and only on the Inbox, and focus returning to the row the card was opened from. Human interface recorded Esc alone at the time.
  • Update: Human interface records that the keyboard selection follows focus into a row, and that / on the Search screen focuses that screen's own field.
  • Update: Human surface records that the Search screen sends the query as typed and the hub makes it safe for the index. This replaces the earlier note that the screen sends only the words of a query.
  • Update: The storage summary bar is drawn against what is used when under 1% of the volume is used, and says so; it stays to scale either way.
  • Update: The contributor guide describes the Node skip in make web/check and HUB_REQUIRE_BROWSER, and what the accessibility walk covers at its two widths.

Knowledge base backend and promote

  • Creation: Project knowledge base documents the ten REST routes the human surface reads and writes pages through, each with its response and its refusals, the path rules both surfaces share, the limits, and the brain_promote tool.
  • Update: A page write, delete, review and promote go through one write path whether they arrive over REST or the agent tools. A path is made canonical before the store, the write log, the search corpus or lint keys on it; a key-value path, a control character and a backslash are refused on both surfaces.
  • Update: A review is recorded under the hub's own name for the human and takes the version the human read, so a page that changed since is a conflict and is not stamped. Deleting a page that does not exist is not_found and leaves no log row, no signal and no file.
  • Update: The history scans the whole write log, so total is the real count and a page keeps its last writer however many writes came after. The bound is the knowledge base file's own size limit, and a cut page says truncated.
  • Update: A knowledge base page is capped at 1 MiB. A session brain value keeps its 4 MiB cap.
  • Note: There is no move or rename, and wiki links are not followed.

A search query is words, and a thread is in its project

  • Update: The served skill document (GET /SKILL.md) now says how a search query is read: any text is accepted, a quoted phrase is a phrase, punctuation and the bare operators are left out, and a query with no word in it finds nothing rather than failing. It also says that a thread_id given to signal_append must name an event in the same project.

Clearing an artifact label on update

  • Update: artifact_update accepts an explicit null or empty string to clear an existing label, matching how envelope: null unprotects an artifact. Omitting the label keeps the current version's label. Described in artifacts.

Integrity verification for vendored scripts

  • Update: Documented the manifest and integrity check for third-party scripts under web/vendor/. Added guidance to the human surface architecture document and contributor guide explaining how web/vendor/MANIFEST.json tracks source, license, version, and SHA-256 digests.

2026-09-18

Storage you can read and prune from

  • Update: The Storage screen now matches the design. It names the data path and the node, shows what is used against the volume's capacity, and stacks a bar by kind over a legend that gives every kind its byte figure, including a kind that holds nothing. Each project row carries its total, its own bar, the split in words, and a Prune button showing what its ended sessions would free. Prune all lists the projects, session counts and bytes in a review dialog before anything is sent. Both prunes open on Keep, send one request, and can be undone from the toast for 30 seconds. The project rows are in the keyboard map, and a hub whose projects hold nothing shows the empty state.
  • Note: The hub reports no per-project share of the event store, so a project row splits sessions, artifacts and knowledge only. When the volume cannot be measured the capacity is left out and the bar is drawn against what is used.

Home as the day at a glance

  • Update: Home now matches the design. The title is the reader's day and part of day, over a summary line of what waits, what is unread and how many agents are active. A "Waiting on you" card in the action tone carries the queue's count and the waiting items among the newest events, and hands the rest to the Inbox. "Newest across projects" names the project on every row, links each to its project feed, and draws the unseen dot from the per-project cursor counts. A storage card links to Storage with used against capacity, a bar, the same share in words, and what a prune would free. When nothing waits and nothing is new, the cards give way to the quiet empty state. All of it is read from the one Home response, and the rows join the keyboard map.
  • Note: Described in human surface. The Home response has no node name, no project display names and no list of the waiting queue, so Home omits the node line, names projects by slug, and lists only the waiting items that are among the newest events.

A project's own settings screen

  • Update: Project settings ships as its own screen, behind a gear in the project header. It edits the name, shows the slug in mono as text because the slug is read-only after creation, and sets the artifact password policy from a radio group over the hub's three values. Save is disabled until something differs, sends one request naming only what changed, and a refusal from the hub lands beside the control it is about without costing the reader what they typed. Leaving with edits pending asks first. The retention card is present and marked reserved, with a link to Storage and no control. Delete project sits on the screen behind the existing confirmation; the list under the global Settings screen stays.
  • Note: The architecture and design pages no longer list Project settings as intended design.

Search that answers as you type

  • Update: The Search screen now matches the design. A 48px field with a clear button answers as it is typed in, scope chips narrow it to the feed, artifacts or session brains, and a results line gives the count and the time the hub measured for the query. Results are grouped by family with the matched words marked in each snippet, and the rows join the keyboard map. The query and the scope live in the route, so reload and Back keep them.
  • Note: The screen sends the words of a query rather than its punctuation, and builds each marked snippet from text nodes, so neither what a reader typed nor what an agent wrote is read as markup or as index syntax. A project scope, a date scope and landing on the hit inside its destination remain intended design.

The project feed

  • Update: The project feed now matches the design. The kind filters are one scrolling line of chips led by All. Today and Yesterday are open, and older days sit behind an "Earlier" disclosure that carries the hub's count of what it holds and pages further back on the feed's own cursor. Rows are part of the keyboard map, older days included once they are open.
  • Update: The feed reads and moves the per-project read cursor. An event above it carries a dot, a heavier title and the word "Unread"; viewing an unfiltered feed in a visible tab posts the newest id, and a filtered page posts nothing. The human surface page no longer says the PWA leaves the route uncalled.
  • Update: An empty feed offers "Copy MCP setup": the connection details the hub's skill document gives, filled with this hub's origin and never with the reader's own token. Where the browser has no clipboard, as on a plain LAN address, the text is shown selected to be copied by hand.

An inbox that can be read

  • Update: The Inbox now matches the design. It reads in three groups, Waiting on you and Unread with their counts and Earlier for what has been read, folded on the desktop. A row carries a one-line body on a waiting item and a footer of project and agent; an approval offers Decline beside Approve, each asked for in a dialog. Opening a row shows the item as a card with its answers at full size, and the open item lives in the address.
  • Update: Read state reaches the screen. An unread row carries a dot, its weight and the word Unread for a reader who cannot see either. Opening a row, a swipe right, or the row's Mark read control marks it read, with an undo; the header carries Mark all read and an Unread only filter that survives a reload.
  • Update: The Inbox row swipes and the pull to refresh ship, each with a control that does the same thing: a swipe left uncovers a waiting row's actions and decides nothing, and a last-synced line with a Refresh control stands in for a spinner. With reduced motion asked for, nothing slides under the finger.
  • Note: Quick answers on a question, a snooze under a swipe, and a note sent with a decision stay intended design.

A session, its brain as a tree

  • Update: The sessions screen and its detail view now match the design. A session row carries the state dot, the owner, a mono id and size, and a chevron into the detail, which shows three stat cards (started, events, brain size), the lineage and handoff note, the session's newest feed event as one line, and the brain as a drill-down tree: ARIA roles per the design, expand and collapse with lazy-loaded children, arrow-key navigation, and a real focus trail. The pinned action bar names End session and Prune (ends first), which asks first and stays reversible.

Reaching the app without a mouse

  • Update: A timestamp is no longer a stop of its own in the tab order. A row carried one each, so a long feed cost a Tab press per row and every stop said the same date. The full timestamp is still the element's accessible name and its hover title, a press or a tap still swaps it in, and the row is now what the tab ring reaches. A painted list parks its selection on the first row, so the list opens to a reader who has never pressed j.
  • Update: The single-key shortcuts can be switched off. Settings carries the switch beside the theme and the density, the help panel says where it is, and with it off no character key fires. Esc and Tab are unaffected.
  • Update: The focus ring survives forced-colours mode. It was a box shadow, which such a browser drops, over an outline the rules turned off, so a reader there saw no ring anywhere. A transparent outline now sits under the designed shadow.
  • Update: A text field's border is drawn one step darker than the design's hairline. An empty field has nothing inside it that says a control is there, so its border alone carries the 3:1 non-text minimum. Outline buttons keep the hairline, because their own label identifies them.
  • Update: A toast no longer takes the keyboard from a reader who is writing. It still moves focus to its undo otherwise, and the live region announces the message and the way back either way.
  • Note: Described in human interface and human surface.

The shell and the project view

  • Update: The mobile tab bar draws the four destinations as labelled icon tabs, with the Inbox unread badge riding on the icon; the desktop top bar carries the wordmark, the nav links with the same badge, an inline search field with a slash hint, the node line from the storage response and a gear to Settings.
  • Update: Each project is an address of its own. The feed, the artifact gallery and the sessions list are segmented tabs under #/projects/<id>/, every segment marked current with its own route, and the older per-project addresses redirect there. The artifact gallery is the design's card grid, with a preview tile per card and a real version, size and age line. The artifact viewer is a route too (#/artifacts/<id>), so reload and the browser's Back keep the artifact on screen, and its chrome adds a back button, a title and meta line, a version list, a theme control and, on desktop, an Open raw view.
  • Update: A desktop list plus detail layout is a layout primitive screens opt into. Sessions is the first consumer, with a 420px list pane beside a detail pane at wide widths and the same stacked view on the phone.
  • Note: Described in human interface and human surface.

Remembering an artifact password, honestly

  • Update: The password gate offers to remember a password only where the browser will actually keep it. Opened from inside the app the artifact runs in a frame with no origin of its own, where storage is refused, so there the checkbox is not shown at all rather than shown and ignored.
  • Update: A password remembered on an artifact's own page can now be forgotten. Once it unlocks the artifact by itself, the page header carries a "Forget password" control that drops the stored password and says so in the page, and the next visit asks for it again. Described in artifacts.

Unlocking a protected artifact inside the app

  • Note: Unlocking now runs from the Unlock button's activation, which Enter in the password field reaches too. Opened from inside the app the artifact runs in a frame where the browser blocks a form submission outright, so until now the button did nothing there.

Time, keys, and a theme that keeps up

  • Update: A timestamp is a component rather than a truncated ISO string. A row shows a compact relative form in the reader's own locale and counts it up while the app is open; the full local timestamp is the element's accessible name, its hover title, and what one press shows. Described in the human interface.
  • Update: The screens with rows share one keyboard map: / for search, j and k through the rows, Enter to open, a and r for the two inbox verbs, Esc for what is on top, and ? for the list. The selection is a real focus move, and nothing fires while the reader is typing or while a dialog holds the keyboard.
  • Update: With the theme preference on "system", an operating system that changes while the app is open now changes the app with it, status bar included. It previously waited for the next navigation. The preference is read back defensively, so a value this app never wrote cannot reach the root element.
  • Note: An empty-state component carries the design's four parts and the copy for each screen in one table. The screens still show their own single sentence; each adopts the component as it is reworked.

The app asks and reports in its own components

  • Update: Pruning a session now asks first. A confirmation dialog names the session, keeps on Esc or on its safe action, holds focus inside itself and hands it back to the control that opened it; the prune request goes out only once the dialog is answered. Before this, the first click pruned. Described in the human interface.
  • Update: What an action did is reported in a toast that a screen reader announces, carrying a counting undo for the 30 seconds a prune stays reversible. It sits above the tab bar, dismisses on a swipe down, on Esc or on its dismiss control, and one is on screen at a time.
  • Update: A question is answered in a composer under the item rather than in a browser prompt. It is multi-line, sends on Enter where there is a keyboard and on its send button everywhere, and a refused send keeps what was typed and says why in place.
  • Update: No screen opens a browser prompt, confirm or alert any more. Approving, revoking a token, deleting a comment and deleting a project all ask through the same dialog, a failed write reports in the toast or in the composer that tried it, and focus follows the control that was pressed instead of falling to the top of the page. Described in the human surface.
  • Note: The prune dialog names the session and its agent but no size: the session listing route reports no byte count, and no number is shown that the hub has not given.

A kind is a shape, and the type scale stands up

  • Update: Each event kind now draws its own mark inside the badge, and the row carries a hidden word for the kind, so nothing in a feed row is told apart by colour alone. Described in the human interface.
  • Update: Headings sit where the foundation puts them: 28 for a page title, 22 for a section, 12 uppercase for a group label, with the item title, row title, meta and mono steps available to the screens that want them. Buttons keep their labels on one line, respond to hover and press, and drop to the inline size inside a row while keeping a full target under a thumb.
  • Note: The action tone and the approval kind are one value again, and the palette exists once: the manifest, the shell, the icon and the artifact frame are checked against the token file, and a colour that is not a token fails the check.

A project decides what it asks of a protected artifact

  • Update: The artifact password policy is enforced where artifacts are written, so every writer meets it: a project set to require protection refuses content with no envelope, one that keeps its artifacts in plain text refuses content with one, and optional, the default, leaves the choice to the agent. A refusal names the project and what to send instead, and writes nothing. See artifacts.
  • Update: An update now says what happens to the protection: leaving envelope out carries the current one forward, passing one protects the new version under it, and passing null publishes the new version in the clear. A protected artifact in a project that has turned protection off moves into the clear that way, keeping its id, its history, its comments and the links already shared, and the refusal names that request. Each version keeps what it was published as, so one artifact can hold a protected version and a plain one. See artifacts.
  • Note: The rule applies to the version being written, never backwards. An artifact published under another policy stays as it is and stays readable; only its next version has to comply.

A project can be renamed and set up

  • Update: A project is read on its own route and changed on a new one: its display name, and what it asks of a protected artifact (off, optional or required, and optional for every project that has not said otherwise). A field the body does not name is left alone. Both routes are in the human surface; the Project settings screen is not built yet.
  • Note: The slug is read-only after creation, because it is the name every MCP call, every other table and every blob path uses; a body that tries to change it is refused. An agent's personal space can be renamed and set up like any other project, though deleting it is still refused.

A project feed remembers how far it was read

  • Update: Each project carries one cursor, the newest event the human has seen. The feed read returns it, a route advances it when a feed is opened, and the count above it rides on the project listing and on Home, so a tab row and a Home row draw the same dot without a request of their own. The routes are in the human surface; the screens do not call them yet.
  • Note: The cursor only moves forward, and only to an event of that project: an older id, an id from elsewhere, and an id that names nothing all leave it where it was and say so in the answer. Deleting a project takes its cursor with it. The data model sets this beside the inbox's read state, which is the other thing entirely.
  • Note: Upgrading an existing hub seeds each project's cursor at its newest event, so the first launch after the upgrade is quiet rather than lit by the whole backlog. A project created afterwards starts with no cursor, so its first events are new.

The human can mark inbox items read

  • Update: The inbox carries explicit read state. One entry is marked read or unread, and every unread entry can be marked read at once, optionally within one project. The listing takes unread_only. The routes are in the human surface; the screens do not call them yet.
  • Note: Read is one axis and waiting on you is another. An entry that waits on a decision, or one already resolved, has no read state: marking it read answers with its unchanged status rather than taking it out of the waiting queue. Nothing is read by scrolling past it. The data model says what read means and does not mean.
  • Note: Read state does not reach agents. The agents' inbox read reports an entry the human has read as unread, with the timestamps it already had, and has no read status to filter on, so no agent can learn which of its reports the human opened or when. The inbox listing is ordered by event id rather than by update time, so reading an item never moves it or shifts a page.

Every number the human surface shows has a route

  • Update: The human surface reported bytes, counts and timings that no route carried. Each now has one: the volume's capacity and free space beside what the data directory holds, totals by kind, what a prune would reclaim, per-session brain size, a session's event count and its last line, per-project event, artifact, session and wiki page counts, agents at work, and a search result count, with whether the page was capped, and the time the query itself took. A brain listing returns entry objects with a type and a size, one directory level at a time. Every field is listed in the human surface.
  • Update: A volume that cannot be measured reports its capacity and free space as absent, and the surface says it does not know rather than showing zero of zero. File and volume numbers are memoised for ten seconds behind a counter every write bumps; counts are indexed and never cached.
  • Update: Storage can prune every ended session of one project, or of every project, with the same soft delete, undo window and sweep as pruning one. It never touches an active session, a feed event, an artifact or a project knowledge base, and returns one undo token per session.
  • Update: An agent counts as active while it owns a live session touched inside a window, and a session is now touched by every tool call that resolves it rather than only at start and end, so an agent that only posts signals or asks questions still counts. The window is HUB_ACTIVE_WINDOW_SECS, capped at thirty days, and the node's name is HUB_NODE_NAME, both in the quickstart.
  • Note: An event now names the session it was written during. Existing events are backfilled from the lifecycle payloads that already carried one, and a payload that does not parse is left without a session rather than stopping the upgrade; everything written before this change keeps no session on ordinary work, so a session detail count covers what happened after the upgrade. Pruning deletes exactly what it did before, which the data model states.

The app is one module per screen

  • Update: The PWA is now a set of ES modules rather than one script: an entry that names the screens and wires the events, a shared core, and one module per screen with the comments drawer in its own. Nothing the interface shows changed. Described in the human surface.
  • Update: A screen whose requests come back after the reader has moved on no longer paints over the screen that replaced it, and Home reads its endpoint once per visit instead of twice.
  • Note: A browser smoke pass joins the gates, beside the accessibility audit: it visits every route against a seeded hub and fails on a missing heading, a console error, an unhandled rejection or a failed request. Like the audit it skips where the browser toolchain is absent. Listed in the contributor guide.

A session belongs to the agent that started it

  • Note: Sessions started before this change keep the owner they were written with. A standalone agent-hub mcp records HUB_AGENT_ID, or local when it is unset, while a token transport records the token's agent. An agent that moves from standalone stdio to the proxy under a different identity starts fresh sessions, and reaches its earlier work by picking it up with from. The served skill contract says how.
  • Update: A session name is now the caller's own. The same name under another agent is a different session with its own brain, so two agents that pick nightly no longer share working state, and each resumes its own. Asking for a name a pruned session still holds is refused with conflict and a pruned_session_id= tail while the human's undo can still restore it. Documented in the data model, the agent surface, and the served skill contract.
  • Update: session_start takes from and picks up another agent's work without the human arranging anything. The hub chooses what that means from the source's state: an ended session is adopted, keeping its id, brain and handoff note while ownership moves, and a running one is forked into a copy that leaves the source undisturbed. The result reports pickup with the mode and the note. Recorded in sessions belong to their agent.
  • Update: session_end takes an optional handoff note, kept on the session and in the feed event and returned to whoever picks the session up. It never enters the brain, so ending a session that never wrote still leaves no brain file. Only a session's owner may end it; the local admin still can.
  • Update: session_list gives an agent the sessions it may read, with owner, status, last activity, handoff summary and lineage. The REST session listing gains the same owner and handoff plus a resolved lineage object, and a session the human needs to move has a reassign route.
  • Note: brain_put and brain_delete now accept a session that names the caller's own active session, so one client passes the same argument to a read and a write. Any other session is forbidden with an owner= tail.
  • Note: Adopt, fork, reassign and end-with-handoff reach the human feed as ordinary session events. Nothing here waits on human approval.

A one-line read of the project knowledge base

  • Update: agent-hub kb get|put|list|delete reads and writes the project knowledge base without a quoted JSON object. kb get prints the page as markdown, defaulting to /fs/index.md, so a session-start hook pipes shared knowledge into a context window in one line; --json prints the tool's result instead. A path outside /fs is taken as relative to it, and a failed read prints nothing on stdout. Documented in the quickstart, the served skill contract, and the hub client.
  • Note: HUB_PROJECT joins the client settings, in the environment or in the config file, and supplies the project when no --project flag does.

Reading another session's brain

  • Update: brain_get and brain_list take an optional session, either {session_id} or {agent, name} with a project_id, and read that session's brain. Read access to the target's project is the whole rule, and reading needs no active session of the caller's own. Documented in the agent surface, the data model, and the served skill contract.
  • Note: Writes are unchanged and stay with the owner's active session: brain_put and brain_delete refuse a session argument, because one working-state file has one writer. Knowledge meant for another agent belongs in the project knowledge base.
  • Note: A read never creates a brain file, and a session the human has pruned reads as not_found from the moment it is marked. An agent without access cannot tell a session it may not read from one that does not exist.
  • Update: search takes session_id to narrow results to one session's brain content, under the same project confinement as every other search.

A hook reads the project knowledge base

  • Update: The served skill contract shows a one-shot call reading a project knowledge base page with no session, which is how a harness hook puts shared knowledge into context on any machine.

Reaching the hub from another machine

  • Update: agent-hub mcp is a proxy to a running hub whenever HUB_URL names one: one connection for the life of the process, the hub's own tools, errors, and identity. With nothing configured it still serves the local data directory standalone and says so. Documented in the quickstart, the agent surface, the served skill contract, and the hub client.
  • Update: agent-hub call <tool> [json] and agent-hub tools make one-shot calls for harness hooks: JSON on stdout, the hub's error object on stderr, and exit codes that separate a down hub, a refused token, and a missing setting.
  • Note: The three client settings, HUB_URL, HUB_TOKEN, and HUB_AGENT_ID, are read from the environment first and then from ~/.agent-hub/config, an env-style file with the same key names. HUB_AGENT_ID sets the actor only in the embedded standalone mode; against a running hub the token decides.
  • Note: A subcommand the binary does not know now exits 2 instead of starting a hub.

A durable knowledge base per project

  • Update: The brain tools now reach two stores. store: "session" is the session's working state as before; store: "project" is a durable knowledge base, one per project, that every agent with project write shares and that no prune touches. store is required on a write and defaults to "session" on a read. Documented in the agent surface, the data model, the served skill contract, and decision 0018.
  • Update: A read returns a version, the content hash of what it read, and a write accepts it back as if_version so a page is written only while nothing changed underneath. absent creates a page that does not exist yet. A mismatch is a conflict whose message ends current_version=sha256:....
  • Update: The AgentFS invariant in the contract now reads "per session and per project": one file per session and one per project, both behind the one wrapper that is the single writer per file.
  • Update: A listing entry is now an object with its path, its type and its size rather than a bare path, and knowledge base pages are searchable as the kb family. Storage usage reports a project's knowledge base bytes, which are never prunable.
  • Note: Deleting a project removes its knowledge base with everything else it owns. That is the only thing that removes one.

What the artifact cap promises over HTTP

  • Note: The served skill contract no longer reads as if 50 MiB of content always fits down the wire. The content cap is 50 MiB; over HTTP the whole tool call also has to fit the transport limit, so content that needs a lot of JSON escaping has less than 50 MiB of room. No limit changed.

The iteration range the artifact viewer accepts

  • Update: The protected-artifact envelope is accepted only with an iterations count between 100000 and 10000000; the client writes 600000. Documented in artifacts and the served skill contract.
  • Note: An envelope outside that range, or one naming another algorithm, now tells the human the viewer does not accept its settings. It used to report a wrong password, which sent the human back to a field that could never open it.

The node holds every brain

  • Update: The first invariant now reads "no vendor cloud, and no brain off the node" in place of "no remote brain", in the overview, the goals, and the local hub decision. The rule is unchanged: nothing is hosted by anyone else. The old wording could be read as forbidding agents on other machines from reaching their brain on the node, which is what the hub is for.

What the published container port carries

  • Note: The quickstart compose section now states that the published port is plain HTTP on every interface, so the admin token and the responses cross the network unencrypted, and names the two supported ways to close that: a TLS-terminating reverse proxy with HUB_PUBLIC_URL set, or a tailnet. No default changed.

An external origin the operator can set

  • Update: HUB_PUBLIC_URL names the address callers reach the hub at. When set it is what the artifact frame policy, the artifact link previews, and the served bootstrap skill all use, instead of the address derived from the request headers and the bind. Documented in the quickstart with the other environment keys.

The identity trail is the human's to read

  • Update: Identity audit events stay out of the search corpus, and an agent's feed read never returns one. The admin still reads the trail through the project feed route by kind. Documented in the data model.

Session start stops returning a server path

  • Update: session_start returns the session id alone. The brain file path it used to hand back named nothing any brain tool accepts and disclosed the server's on-disk layout; the file path stays on the human-facing session surface. Documented in the agent surface and the served skill contract.

Brain values carry a size ceiling

  • Update: A single session brain value is capped at 4 MiB, matching the request body ceiling, and an oversized write is refused with payload_too_large before anything is stored. Documented in the agent surface and the served skill contract.

Readiness asks the engine

  • Update: /readyz now queries the store for its schema version instead of repeating the version cached at startup, and reports 503 problem details when the store does not answer or has drifted from it. /healthz stays a liveness check. Documented on the human surface and in the quickstart.

Every REST refusal is problem details

  • Update: The human surface states the status each refused request carries: an oversized body, a missing JSON content type, a bad query or path, and an unserved method are all problem details now, where the last four used to be plain text or an empty body.

Request body limits per surface

  • Update: The 4 MiB cap on a REST request body is stated on the human surface, and the served skill contract states the larger cap the agent transport carries.
  • Note: The agent transport carries the artifact cap plus the call around it, so a 50 MiB artifact publishes over HTTP as well as over stdio.

Corrections against shipped behaviour

  • Update: The served skill guide now says plainly that local stdio opens the data directory itself as a standalone process, cannot attach to a data directory a hub process already has open, and fails at startup on the engine's exclusive lock; an agent that wants a running hub uses streamable HTTP. It also fixes the feed read order description, states that markdown artifacts render in the browser rather than on the hub, documents the per-project open inbox cap next to the per-agent one, lists every registered tool including the version, deletion, and comment tools, and notes that a comment also accepts an idempotency key.
  • Update: The README and the wiki index no longer claim the session detail view and the embedded tailnet are still intended design; both ship.
  • Update: The human interface and human surface pages now mark swipe gestures, an explicit read state, the brain tree, the desktop list plus detail layout, and a dedicated project settings screen as intended design, not yet shipped, matching what the PWA actually renders today.
  • Update: The agent surface page's event kind family count and the human surface page's route table are corrected to match the code.

Artifact viewer on the design foundation

  • Update: The public artifact page reuses the design tokens: warm canvas, humanist type, a header with back button, title, version line, picker, and theme icons, a foundation password gate with lock tile, remember-me, and ciphertext fingerprint, and a prose baseline for rendered markdown. Documented in the artifacts guide and the human surface.

Comments on artifacts

  • Update: Artifacts carry discussion with optional point or quote anchors, resolution state, and per-comment delete tokens. Documented in the artifacts guide, the agent surface, the human surface (routes and viewer drawer), the data model, and the served skill contract. Quotes are refused on protected versions, and the public page shows the thread read-only, never on a protected artifact.

Artifact viewer that runs, unlocks, and previews

  • Update: The public artifact page is a host shell around a sandboxed frame with a theme toggle and version picker, an unlock form for protected artifacts, and link previews with a built-in card. Markdown renders in the page with tables, callouts, and self-hosted diagrams. Documented in the artifacts guide and the human surface.

Artifact versions, conflicts, and deletion

  • Update: Artifacts carry display metadata (description, favicon mark, version label) and an immutable, addressable version history. Documented in the artifacts guide, the agent surface, the human surface, the data model, and the served skill contract.
  • Update: Concurrent updates use optimistic concurrency: artifact_update accepts the base version and refuses a stale write with a conflict naming the current version unless forced. History reads (artifact_versions, versioned get and raw, ?version=N on the public page) and artifact_delete are documented in the same pages.

Usage guide and served skill

  • Creation: Added artifacts, a usage guide for publishing, versioning, protecting, and reading artifacts, and a project and agent setup walkthrough in the quickstart.
  • Update: Corrected the quickstart's opening, which still described the agent and human surfaces as unbuilt, and added HUB_AGENT_ID to the environment table.
  • Creation: Added a public GET /SKILL.md route that serves a bootstrap guide with the caller's own origin rendered in from the forwarded or request host, so an agent that can already reach the hub learns how to connect and what the tools are. The human surface lists the route.
  • Update: The served document is the single tool contract: it carries the argument shapes, feed and inbox statuses, pagination, error codes, and the artifact authoring rules. The installable agent skill keeps the workflow and the offline bootstrap and defers to the served document for the contract, so the two cannot drift.

2026-09-17

Inbox action-item cap

  • Creation: Added the inbox action-item cap: a question or an approval is an open item on the human, and the writer caps how many one actor may leave open in a project and how many may accumulate in the project at all. A refused write returns rate_limited (HTTP 429) and changes nothing. The defaults are generous, and HUB_INBOX_ACTION_PER_AGENT and HUB_INBOX_ACTION_PER_PROJECT set them, with zero disabling a check.
  • Update: The agent surface documents the refusal as a structured tool error alongside the other write errors, and the human surface and human interface describe the waiting queue grouped by actor so an agent that leaves many items is one block with its own count.

Markdown rendering

  • Update: The public artifact route and the in-app viewer render a markdown artifact to HTML instead of showing its source. Raw HTML embedded in the markdown is escaped, and the rendered page keeps the sandboxed document and restrictive content security policy of every artifact page, so a published note cannot script or load anything external.
  • Update: GET /api/v1/artifacts/:id returns a rendered HTML field for a public markdown artifact, which the viewer frames without same-origin access. A protected artifact carries no rendered value, because its plaintext never reaches the server; the viewer keeps showing the decrypted source as text.
  • Note: The renderer is hand-rolled for a fixed subset (headings, paragraphs, emphasis, inline and fenced code, lists, and links). It adds no dependency to the binary and guarantees that every source character is escaped, which is why it is preferred over a full parser.

Engine lock wait

  • Update: Every store connection now sets a bounded engine busy timeout, so a writer that loses the immediate-transaction race waits for the lock and then replays to the same result instead of returning database is locked. The busy handler is per-connection and the engine builder has no timeout, so all connection creation goes through one store helper.
  • Update: A concurrency test over same-key artifact publish, question post, and answer proves that eight parallel writers serialise to one result and none surfaces the lock.

Question id discoverability

  • Update: question_post now returns question_id alongside event_id and thread_id, all the same value, so a client has the id answer_post needs without inferring it. The answer_post description names that source.
  • Update: The agent surface page documents the id relationship for the question and answer tools.

Feed cursor

  • Update: An empty forward feed poll returns the since cursor it was given rather than none, so a polling client keeps its place instead of losing it. A non-empty page, a backward (before) page, and a mixed query keep their existing cursors.

Tailnet coverage

  • Update: The embedded tailnet opt-in is now automatic. Setting HUB_TAILNET acknowledges the library's experimental guard, so the endpoint starts as documented instead of failing its own startup check.
  • Creation: Added HUB_TAILNET_CONTROL_URL, so the endpoint can point at a self-hosted control server; the public control plane remains the default.
  • Update: The gate compiles and tests the feature build. The tailnet configuration tests cover the missing-key, bad-port, control-URL, and feature-refusal paths. The live join and serve path stays a documented, manual test, because it needs a real tailnet.

Accessibility gate

  • Update: The accessibility gate now runs in two layers. A hermetic contract check computes WCAG contrast for the theme token pairs, enforces the 12px type floor, and asserts the focus ring, the reduced-motion block, and the 44px interactive minimum. An optional headless axe audit renders the eight screens in both themes when Playwright, a browser, and axe are present, and skips cleanly when they are not.
  • Update: The light action token was darkened so the action pill text clears the AA contrast minimum, which the new contract check surfaced.
  • Note: Axe covers the rendered DOM, ARIA, labels, heading order, and computed contrast; the contract check covers the type floor and the presence of the focus and reduced-motion rules. Neither replaces a manual keyboard pass.

Notifications descoped

  • Update: Background push is deferred beyond v1. Real delivery with the app closed needs a browser push service, a third party in the transport path that the local-first design avoids, and a secure context. The shipped surface stays an opt-in in-app notification, raised while the app runs.
  • Creation: Added GET /api/v1/stream, an admin-gated server-sent freshness stream. It carries no event data, only a tick when a write changes the inbox or feed, so an open app refreshes its waiting badge without polling.
  • Note: A later revision can add opt-in Web Push with a contentless, end-to-end encrypted payload if a vendor transport is accepted.

Agent-surface hardening

  • Update: A non-admin caller no longer learns whether a project, artifact, or session exists. A missing resource and a denied one return the same authorization failure, so neither the error code nor its message can be used as an existence oracle.
  • Note: Blocking artifact IO from async handlers and the admin token held in browser local storage are accepted for a single-operator node, with the reasoning recorded in the blob module and the human surface page.

Retry safety

  • Update: artifact_publish and artifact_update accept an optional idempotency key, so a retry after a dropped response returns the original artifact and version instead of a duplicate or a second version.
  • Update: The approval decision route accepts an optional idempotency key and returns the original answer on a replay rather than a conflict.
  • Update: An idempotency key is scoped to the operation that used it and can carry the artifact and version it produced, added by schema version 3. Prune keeps a key whose event still exists, so a keyed write that survives a prune still resolves.

Feed design

  • Update: The Project feed groups events by day (Today, Yesterday, or the date) and filters by kind with per-project chips; the Home recent list is grouped the same way.
  • Update: The Inbox keeps its "Waiting on you" and "Unread" groups and carries the row action inline, so a waiting question or approval is answered or decided without opening it. A handled item leaves the queue; the feed keeps its history.
  • Creation: Added POST /api/v1/approvals/:id/decision, an admin-gated route that records an approval decision as an answer on the approval's thread and resolves the waiting item. An approval is decided once; a second decision is a conflict.
  • Update: An approval forces needs_action at the event writer, like a question, and a feed event carries its inbox status so a resolved item stops offering its action.

Polish and reach

  • Creation: A session opens into a detail view with its brain keys and files and its End and Prune actions, backed by an admin-gated GET /api/v1/sessions/:id/brain that does not create a brain on a read.
  • Creation: Added DELETE /api/v1/projects/:id, a destructive action under Settings that removes every row and file scoped to the project. An agent's personal space is refused.
  • Creation: Opt-in inbox notifications. Permission is requested only from the Settings control, and only waiting-on-you items notify; without permission or support the feature degrades silently.
  • Creation: An optional embedded tailnet endpoint behind a cargo feature that is off by default, serving the same router on the node's tailnet address. It stays experimental and IP-addressed.
  • Update: The MCP bearer scheme is case-insensitive, an artifact is authorized before its blob is read, and prune drops the idempotency keys whose events it removed.
  • Update: The human feed surfaces hide the hub's own system audit events; an explicit kind filter still reaches them.
  • Note: True background push, delivered with the app closed, is outstanding; notifications today are opt-in and raised while the app runs.
  • Note: The headless accessibility audit remains outstanding.

Identity and access

  • Creation: Agents have a stable identity, one token at a time, a trust level, and a personal space. Issuing a token revokes the previous one in the same transaction, and revocation is agent-keyed.
  • Creation: Every MCP tool and every REST read is authorized before it touches state. A trusted agent reads broadly and writes shared projects and its own; an untrusted agent reaches its own space and explicit grants. Search and the inbox are confined to a caller's visible projects.
  • Creation: Added the whoami MCP tool, the admin-only REST identity routes (POST and DELETE /api/v1/agents/:id/token, and the grants routes), and GET /api/v1/artifacts/:id for the in-app viewer.
  • Creation: The PWA gains Agents and access under Settings, and an artifact viewer that decrypts protected artifacts in the browser and renders agent-authored HTML only in a sandboxed frame.
  • Update: The hub serves the REST API, the PWA, and MCP at /mcp on one listener in one process, and runs the prune sweeper there.
  • Update: Identity changes are audited as system feed events, in the same transaction as the change.
  • Update: The control-surface admin token is required when the bind is not loopback. The stdio transport is the local admin; HTTP requires a token.
  • Note: The session detail view, project deletion, push notifications, and a headless accessibility audit remain outstanding.

2026-09-16

Installable PWA

  • Creation: Ship the interface as static assets from the binary: the design tokens, a vanilla app shell, a manifest, and a service worker. The screens are Home, Inbox, Project feed, Artifacts, Sessions, Storage, Search, and Settings, with a four-tab mobile bar and a desktop top bar.
  • Creation: Added the REST routes the app reads: GET and POST /api/v1/projects and GET /api/v1/storage.
  • Update: The interface is framed by a content security policy, artifacts render only in a sandboxed frame, and the static web check runs as part of the gate.
  • Note: The Agents and access surface, the session detail view, and project deletion are still intended design; they land in a later change.
  • Creation: Added the MCP search tool and the REST GET /api/v1/search route over the corpus already written by the feed, artifact, and brain paths. Results are ranked by text relevance and grouped by corpus family, with project and type filters and a short snippet.
  • Update: The ranked query uses the shape the engine's full-text index method recognises, so relevance ordering is live; project and type filters are applied after the ranked fetch.

Artifacts and prune

  • Creation: The MCP server adds artifact_publish, artifact_update, artifact_get, and artifact_list. Blobs live on the data volume; the store holds metadata and an optional encryption envelope. A publish or update appends a feed event and refreshes the search corpus; a protected artifact indexes its title only.
  • Creation: Added the REST routes GET /api/v1/projects/:id/artifacts, GET /artifacts/:id, DELETE /api/v1/storage/sessions/:id, and POST /api/v1/prune/undo/:token.
  • Update: The public artifact route frames untrusted content in a sandboxed document with a restrictive content security policy, so a published page never runs in the hub origin.
  • Update: Prune now requires an ended session, refuses to undo past its window, removes the session's indexed events, and is committed by a periodic sweep.

Inbox and questions

  • Creation: The MCP server adds question_post, answer_post, and inbox_read. A question opens a thread, lands on the feed, and enters the inbox as an action item; an answer closes the thread and resolves it.
  • Creation: The inbox is a projection over events: finished work lands as unread, action items as action. The home summary counts unread and waiting items and lists recent events.
  • Creation: Added the REST routes GET /api/v1/home, GET /api/v1/inbox, and POST /api/v1/questions/:id/answer.
  • Update: A question roots its own thread and enters the inbox in the same write as the event, whichever tool wrote it, and an answer must name its question.
  • Update: A malformed answer body is now a problem-details response.

Brain and sessions

  • Creation: The MCP server adds session and brain tools: session_start and session_end, the brain_get, brain_put, brain_list, and brain_delete group over the /kv/ and /fs/ namespaces, and an active session per connection. A brain write is mirrored into the search corpus.
  • Creation: The session store records the mapping from an agent session name to a brain file, idempotent start and resume, and a retry-safe end, with lifecycle events on the feed.
  • Creation: Added the REST routes GET /api/v1/sessions and POST /api/v1/sessions/:id/end.
  • Update: A session's agent identity now comes from the authenticated principal, never a request field.
  • Update: make check now runs the docs bundle check, so documentation cannot fall behind silently.

Feed surface

  • Creation: The MCP server exposes the feed: signal_append writes an event, and feed_read pages a project feed with next_since and next_before cursors. The streamable HTTP transport requires a bearer token.
  • Creation: Added the REST feed route GET /api/v1/projects/:id/feed, with RFC 9457 problem details.
  • Update: Recorded the event store design: append-only events with ULID ids, cursor paging, idempotency keys, payload limits, and write-through indexing into the search corpus.
  • Update: Corrected the stale "not implemented yet" notes on the overview, the architecture index, the agent surface, and the human surface.

Implementation and packaging

  • Creation: Opened the usage section with the quickstart, covering the binary build, environment configuration, the health probes, and running with the container and compose file. The hub is an early work in progress and the page says so.
  • Update: Recorded the container packaging: a multi-stage Containerfile, a deploy/compose.yaml, and a .dockerignore.
  • Update: Corrected the root index and this log, which still said the bundle had no usage section and that a runnable binary did not exist.

Grounding and design handoff

  • Update: Amended 0003 to record that AgentFS is embedded as a crate and vendored, 0004 that prune is reversible, and 0006 that engine-native search is confirmed and centralised in the hub store.

  • Creation: Added 0010 one pinned engine, 0011 MCP primary with A2A deferred, 0012 agent identity and trust, 0013 per-session serialization, 0014 optional embedded tailnet, and 0015 the design foundation.

  • Update: Reworked the data model with agent identity and trust tables, the search corpus, the artifact envelope, the closed kind set, and reversible pruning. Updated components, agent surface, and human surface to match.

  • Creation: Added the human interface describing the design tokens, screens, alert hierarchy, accessibility gate, and copy rules.

  • Update: Corrected the overview to place artifact blobs on the data volume and note the centralised engine-native search index.

  • Creation: Opened the bundle with the operational baseline. Added the root index, this log, the overview, the design section (goals, terminology), the architecture section (components, data model, agent surface, human surface), nine decision records, and the contribution section (guide, maintainer guide).

  • Note: At the baseline the bundle had no usage or reference section, because the hub did not run and a page describing how to run it would have been fiction. The usage section opened once the first runnable binary existed.

  • Note: Every architecture page describes an intended design, not shipped behaviour, and says so. Pages are rewritten against the code as the code lands.