Metadata-Version: 2.4
Name: tended
Version: 0.31.3
Summary: An LLM-maintained knowledge substrate: governed markdown wiki, queue-and-tend self-maintenance, connectors, composable SQL views, MCP serving, and a chat-first workbench.
Author: Muhamed Isabegovic
License-Expression: MIT
Project-URL: Homepage, https://github.com/misabegovic/tended
Project-URL: Repository, https://github.com/misabegovic/tended
Project-URL: Changelog, https://github.com/misabegovic/tended/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/misabegovic/tended/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=6
Provides-Extra: cluster
Requires-Dist: scikit-learn; extra == "cluster"
Provides-Extra: full
Requires-Dist: tiktoken; extra == "full"
Requires-Dist: datasette; extra == "full"
Requires-Dist: pytest; extra == "full"
Requires-Dist: scikit-learn; extra == "full"
Dynamic: license-file

# tended

[![PyPI](https://img.shields.io/pypi/v/tended)](https://pypi.org/project/tended/)

**A knowledge base your coding agent maintains for you.** Point it at
your repos, docs, and decisions; the agent keeps a synthesized,
browsable wiki of what's true, what was decided, and why — accurate
enough to regenerate the code from, surface cross-team overlaps, and
serve as any agent's working memory.

![the app — the rendered knowledge under an ambient health strip](https://raw.githubusercontent.com/misabegovic/tended/main/docs/workbench.png)

## How it works — three ideas

1. **Sources go in, immutable.** Repos, documents, conversations,
   connector pulls land under `sources/` and are never rewritten.
2. **The wiki is the synthesis.** The agent maintains `wiki/` —
   per-repo shelves, decisions, discussions, state — with every
   claim citing a source. Rendered as a searchable site.
3. **Your agent drives it; you just talk.** "What needs attention?"
   digests the queue. "Note this decision" lands it where the rules
   say. The rulebook is `AGENTS.md` — **you don't read it, your
   agent does.**

The pattern is [Karpathy's LLM-maintained
wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f);
this repo is that pattern productised: governance, health checks,
a queue that accumulates work while you're away (no scheduled LLM
runs, ever — see below), and one app to live in.

That core is single-operator and local-first. On top of it, an optional
**agentic tier** turns the brain into the coordination hub for *many*
agents — each authenticated, subscribing to what it cares about, woken
when a matching event lands — without the brain ever being a harness or
running an LLM on a schedule. It's off by default; see
[The agentic tier](#the-agentic-tier--hub-and-spoke-advanced-off-by-default).

## Quickstart

```bash
uvx tended init my-wiki          # or: pipx run tended init my-wiki
cd my-wiki
python3 tools/brain.py setup     # idempotent bootstrap: env, config, hooks, timer, UI
tools/brain                      # opens the app
```

This project was called brain and is now published as `tended`. The
files inside an instance keep the old name (`tools/brain.py`,
`brain.config.yml`, the skills), so nothing already born needs to change.

To work on the kernel itself, clone it instead:

```bash
git clone https://github.com/misabegovic/tended.git && cd tended
python3 tools/brain.py setup
```

That's the whole surface. The app is the **rendered knowledge** —
live-reloading as the agent works, with an ambient strip showing
health and what's waiting. Your agent runs where it already lives:
`python3 tools/brain.py install-agent --all` wires the brain's MCP
server into Claude Code, Codex, Cursor, OpenCode, or any MCP-aware
client — put your terminal beside the app window and talk.

Two guarantees worth knowing on day one:

- **No scheduled LLM runs, ever.** A local timer runs cheap
  deterministic producers that *queue* work; your interactive
  session digests it ("tend the brain"). Nothing bills while you
  sleep.
- **Nothing runs a shell for you.** The kernel spawns no harness
  subprocesses and embeds no terminal — your sessions are your own,
  on your logged-in subscription. `doctor` warns if metered API
  keys sit in the environment you launch your harness from.

## Onboarding

Two built-in paths, both inside the app (`brain`, then the nav):

- **New to the brain?** Click **Onboarding** — a ~10-minute deck
  covering the three ideas, governance, the daily loop, and a
  step-by-step **first-session tutorial** (steps 1–6, from `setup`
  to your first tend). It lives at `localhost:8765/onboarding/`.
- **New to a project the brain tracks?** Open that project's shelf
  home (e.g. `/your-repo/`) — every shelf renders a generated
  **Project overview**: a start-here reading path (purpose →
  architecture → state → conventions), open work, recent
  decisions, and freshness. Gaps are stated honestly, with the
  command that fills them. Send a new teammate the shelf link; ten
  minutes of reading replaces the first week of asking around.

---

Everything below is reference — the machinery your agent operates so
you don't have to.

## What ships in the shell

The **kernel** — mechanism, no content:

- **`AGENTS.md`** — the schema/protocol the agent follows: three
  layers (sources → wiki → schema), three levels (brain / org /
  repo), page kinds, governance, the `/shape` workflow,
  `ai_suggestion` discipline.
- **`tools/`**: `brain.py`, one CLI with 42 subcommands (`brain.py
  --help` lists them): the corpus gates (`validate`, `check`,
  `reflection-check`, `check-home-fresh`, `check-readme-fresh`,
  `check-no-notion-writes`, `check-no-personal-data`), reading and
  views (`views`, `stats`, `coverage`, `search`, `index`, `query`,
  `page`, `links`, `lint-page`, `verify-claims`, `personas`, `cluster`),
  the queue and operations (`inbox`, `attention`, `schedule`, `doctor`,
  `status`, `setup`, `serve`, `rotate-log`, `efforts`, `rebase`,
  `promote`, `snapshot`), connectors and the graph (`structure`,
  `enola`, `intent`, `sync-cursor`), the hosted tier (`agent-key`,
  `events`, `subscribe`, `subscriptions`) and birth and reach (`init`,
  `install-agent`, `install-sibling`). Beside it: the stdio MCP server,
  the spoke client, specialized agents, sibling-repo sync, local hooks
  and page templates.
- **`.claude/`** — the full slash-command surface (`/in`, `/capture`,
  `/ask`, `/sync`, `/groom`, `/attention`, `/shape`, `/continue`,
  `/zoom-out`,
  `/rfc`, `/pr`, `/review`, `/spawn`, `/rebase`, …) as skills, plus
  the three agent personas (PM / Tech Lead / Developer) that drive
  Shape Up.
- **`ui/`** — the purpose-built Astro app: the briefing (opinionated
  home), lifecycle-aware page rendering, Pagefind search, and an
  onboarding deck at `/onboarding/`.
- **`.github/workflows/`** — the validate gates and the daily
  scheduled-operations runner.
- **`tests/`** — kernel invariants (shape-only; no content coupling).

The **content** is yours: `wiki/` starts as an empty three-level
skeleton, `sources/` starts empty, `log/log.md` starts empty.

## Adopting the shell for a project

This repository is the tool's own project — real adoptions get their
own **instance**:

```bash
python3 tools/brain.py init ~/projects/my-brain --full --org "My Org"
cd ~/projects/my-brain && python3 tools/brain.py setup
```

`init --full` births a complete instance from the kernel manifest —
the whole mechanism plus the kernel's reusable decision trail, none of
this repo's self-tracking content — git-initialised and gate-passing.
The seeded trail is the schema/governance ADRs; the brain's own
in-flight feature work (epics and any `parent_epic`-linked children) is
filtered out, so an adopter inherits reusable decisions, not the brain
team's backlog. Then, inside the instance:

```bash
python3 tools/brain.py setup     # one command: .env, org, repos, hook, timer, UI deps
```

The wizard is idempotent — it prompts for the organisation name and
active repos (or takes `--org` / `--repos` / `--yes`), copies
`.env.example` to `.env` (local-first mode), installs the pre-commit
gate and the daily accumulation timer, and ends with the `doctor`
health checklist. `brain doctor` re-checks any time;
`brain dash` opens the local ops dashboard (health, tend queue,
quick start) at `localhost:8765/dash`.

Then:

1. **Author personas.** `.claude/personas/team/` (internal
   archetypes) and `users/` (customer archetypes) are authored
   per-organisation — see `.claude/personas/README.md`. The
   `agents/` roles ship with the kernel.
2. **Ingest.** Point the agent at a repo or document: `/in <source>`.
   Shelves grow under `wiki/<repo>/` as content earns them. From
   then on the loop is hands-off: the timer queues work, the
   session-start line tells you when there's something to digest,
   and `/tend` (or `brain tend` from any shell) digests it.
3. **Wire the reach surface** (optional). Register the MCP server,
   symlink `tools/brain` onto your PATH, run
   `brain.py install-sibling <repo>` to surface brain pages inside
   sibling-repo agent sessions.

## Repo layout

```
AGENTS.md           # schema / protocol the agent follows (CLAUDE.md → AGENTS.md)
brain.config.yml    # per-organisation config: org name, repo registry
.claude/            # slash commands, skills, settings, personas
sources/            # raw, immutable inputs (additive only)
wiki/               # synthesis layer — per-repo + org + brain-meta
log/                # append-only operations log (log.md)
tools/              # brain.py CLI, sync-siblings, hooks, templates
ui/                 # purpose-built Astro app (briefing + corpus + search) — src/content/docs symlinks ../wiki
tests/              # kernel invariant tests (pytest)
```

## Configuration

### Local Python venv (recommended)

`tools/preflight.sh` and the PreToolUse hook in
`.claude/settings.json` look for python3 at
`~/.local/share/mempalace-venv/bin/python3` and fall back to
system python3 when missing. The fallback works for `validate`
+ `check`, but `brain.py views` needs **tiktoken** to compute
the same per-page token counts as CI — without it `pages.json`
drifts and the views-up-to-date gate rejects the push.

Create the venv once with:

```bash
tools/setup-local.sh
```

### Machine-local term denylist (optional)

For public or shared brains (this repo included), keep terms that must never ship —
client names, internal repo names — in `.reflection-denylist`
(git-ignored, one term per line). `brain.py reflection-check
denylist` flags any tracked file containing them; the terms
themselves never enter the repo. An absent file is a clean no-op.

### Sibling-repo root

All tooling resolves sibling-repo paths through one configurable
root: the `BRAIN_PROJECTS_ROOT` environment variable, defaulting to
`~/projects/`. Set it in your shell profile if your repos live
elsewhere:

```bash
export BRAIN_PROJECTS_ROOT="$HOME/work"
```

## Everyday commands

```bash
python3 tools/brain.py validate     # frontmatter + section conformance
python3 tools/brain.py check        # source citations resolve
python3 tools/brain.py stats        # corpus shape
python3 tools/brain.py views        # regen wiki/_views/ (by-kind/team/repo, pages.json, ai-suggestions, custom role views as reader-facing briefs)
python3 tools/brain.py search '<q>' # hybrid keyword search
python3 tools/brain.py status       # single-pane health dashboard
python3 tools/brain.py inbox summary # the tend queue in one line
python3 tools/brain.py inbox ack <id> # reviewed, no change — suppress a recurring item until the page changes
python3 tools/brain.py inbox pending-grades # judged attention items awaiting a grade
python3 tools/brain.py agent-key issue <agent> # hosted-tier identity: per-agent HMAC key (git-ignored secret)
python3 tools/brain.py events emit --agent <a> --kind post --ref <r> # append a signed event
python3 tools/brain.py events since --agent <a> # verified events since the agent's cursor
python3 tools/brain.py subscribe --agent <a> --pattern 'repo:*' --wake-url <url> # wake on matching events
python3 tools/brain.py links        # link-graph health (orphans / hubs / suggestions)
python3 tools/brain.py index --schema # the derived-index schema (for view specs)
python3 tools/brain.py query '<sql>' # read-only SQL over the index
python3 tools/brain.py setup        # one-command bootstrap (idempotent)
python3 tools/brain.py doctor       # health checklist (operating mode, timer, hooks)
python3 tools/brain.py check-no-personal-data  # reject session URLs/personal data in a commit msg or PR body (stdin/--file)
tools/brain dash                    # local ops dashboard (serve + open /dash)
python3 tools/brain.py install-agent --all  # wire claude/cursor/codex/opencode to the MCP
tools/brain tend                    # open the agent with /tend
tools/install-timer.sh              # daily accumulation timer (systemd user / cron)
pytest tests/                       # kernel invariants
```

The inbox lives at `wiki/_state/inbox/` and is **machine-local and untracked** — it is one operator's queue, not shared state. The derived index therefore takes only *tracked* inbox items, so anything generated from it and committed (the `wiki/_views/custom/` audience views, which the UI links to) stays a function of the repo rather than of one machine. `brain.py inbox` reads the directory directly and always shows the full local queue.

### Pre-commit hook

```bash
ln -s ../../tools/git-hooks/pre-commit .git/hooks/pre-commit
```

Gates: `brain.py validate`, auto-stages the `intent stamp`
re-derivation (only pages the stamp itself touched) and the
`wiki/_views/` regen, `reflection-check intent-compile-drift`, and
`reflection-check links`. The enola binary itself never gates a
commit — skip-when-absent; its voice is the non-blocking session
hook.

### Intent-first session hooks

`tools/hooks/intent-first.sh` (four modes, registered in
`.claude/settings.json`) is the mechanical half of AGENTS.md
§ Working inside a sibling repo, rule 6. *Entering* a piece of
work: the first Edit/Write into each sibling repo is blocked once,
with the file's governing specs (`brain.py enola govern`) injected
into context and the instruction to align the PRD/ADR/build-notes
before the code moves; the gate re-arms on every user message.
*Leaving* a turn: when sibling code moved and no wiki page did, the
stop is blocked once and the agent must amend the governing page or
state why the specs already cover the change. The gates force the
reading and the accounting, never the verdict; an absent graph
degrades to a named skip. Sibling detection honours
`$BRAIN_PROJECTS_ROOT` (default `~/projects`).

## Workflow — slash commands

| Command                      | What it does                                                                                       |
|------------------------------|----------------------------------------------------------------------------------------------------|
| `/in <source>`               | Add something to the brain. Auto-routes; hands off to `/shape` when it spots a pitch or pre-existing decision. |
| `/capture <scope>`           | Capture in-flight signal (conversation, design discussion) without a source URL.                  |
| `/ask <question>`            | Query. Default factual lookup; escalates to plan / overlap / coverage by phrasing.                |
| `/sync`                      | Mechanical health sweep: sibling-repo fetch, lint, source-link check, schema validate, regen views. |
| `/tend [<budget>]`           | Digest the inbox — pending synthesis work queued by the deterministic producers. Budget = count / time-box / kind / id. |
| `/attention`                 | Groom the ranked shortlist of what deserves the day, built from connector snapshots + the inbox. Capped; cards are dismissed, not drained. |
| `/groom`                     | Judgement sweep: confidence demotion, insight decay, supersede→archive transitions.                |
| `/shape <scope> <pitch>`     | **The only path to ADRs/PRDs.** Manual by default — pauses at every load-bearing decision. `--auto`, `--pitch`, `--record`, `--epic`, `--rfc` modes. |
| `/continue <slug-or-PR#>`    | Resume in-flight `/shape` work; detects phase from artifact state.                                |
| `/zoom-out <target>`         | Per-work-item zoom-out brief — big-picture fit during deep focus.                                 |
| `/rfc <page>`                | Standalone RFC pass — append a multi-perspective section to a wiki page.                          |
| `/promote <insight>`         | Graduate `kind: insight` → `kind: initiative`.                                                    |
| `/pr <summary>`              | Open a PR for current changes; runs preflight, watches CI, chains to `/review` on green.          |
| `/review <PR#>`              | Review and auto-merge after every guardrail passes.                                               |
| `/spawn <slug> [--target …]` | Opt-in parallel-effort spawn: worktrees + branches + effort registry + background owner subagent. |
| `/list-efforts [<status>]`   | Read-only surface for in-flight parallel efforts.                                                 |
| `/rebase`                    | Cheap-rebase onto `origin/main`, auto-resolving `wiki/_views/` conflicts via regen.               |

Full protocols live in `.claude/skills/<skill>/SKILL.md`; governance
rules in `AGENTS.md` § Governance. PR descriptions are plain-English
executive summaries (see `.github/pull_request_template.md`) — no
personal data, no boilerplate.

## External integrations

| System            | State                | Notes                                                          |
|-------------------|----------------------|----------------------------------------------------------------|
| Datadog / Langfuse | ✅ pull connectors | Monitor+SLO state / prompt inventory → snapshots + state extracts for view tiles. |
| GitHub / Notion / Slack | ✅ pull connectors | Snapshot-writers: immutable files into `sources/`, inbox items out. Configure `connectors:` in `brain.config.yml` + read-only tokens in `.env`. |
| Structure           | ✅ pull connector  | Deterministic code-shape snapshots (source inventory + Python symbols) → architectural-drift inbox items via baseline diff; drift auto-clears once a wiki page cites the snapshot. No network / binary / LLM; read-only git with a clean-tree guard. Configure `connectors.structure.repos`. `brain.py structure findings` turns those facts into conservative, computed findings (`oversized-package`, `god-file`, and `symbol-blindness`, which reports the share of a repo the substrate cannot see past file level — symbol visibility is Python-only). `brain.py structure judge` records accepted / rejected / noise into a write-on-judgment ledger with **no pending state**: absence means unjudged, not queued. Per [`wiki/brain/adrs/structure-findings-and-verdict-ledger.md`](https://github.com/misabegovic/tended/blob/main/wiki/brain/adrs/structure-findings-and-verdict-ledger.md). |
| Architecture graph  | ⬜ optional tier   | `brain.py enola` — cluster snapshots over the same repos via the external [enola](https://github.com/enola-labs/enola) binary, with committed receipts in `wiki/_state/enola/`, drift reporting, merged explainer findings, a verdict ledger, `impact <symbol>` (fan-in / fan-out / named callers) from the on-disk fact set, `baseline` + `check` (grades a change against a pinned before — **reports, never gates**; its `declined` verdict means the snapshots were not comparable and reads as *not asked*, never as a pass), `coverage` (which cross-repo edges resolved, so a genuinely isolated service reads apart from one whose edges could not be followed), `explain`, `doctor` (whether the session hooks are *firing*, not merely configured), and an experimental `history` family (`log` / `show` / `diff` / `blame` / `gc`) that answers *when* something entered the architecture — a question a snapshot structurally cannot. The wiki itself compiles into this graph: every page carries a derived `enola_intent:` block (`brain.py intent stamp`, auto-restamped by the pre-commit hook, gated by the `intent-page-block` detector and preflight), repo citations become page-to-code anchors, and `enola govern <target>` answers the reverse query — *which compiled pages govern this file or symbol* (with relation trails), or, for a page path, *which code its anchors cover*. Verdict compilation went live with enola v0.3.9 (2026-08-06, the first release carrying the upstream intent standard from enola-labs/enola#197); under an older pinned binary the blocks still compile forward-compatibly and `govern` answers *no knowledge pages compiled* — a named skip, never an error. See [`wiki/brain/topics/intent-compilation.md`](https://github.com/misabegovic/tended/blob/main/wiki/brain/topics/intent-compilation.md). Session hooks are available via `enola install --hooks`: a baseline pinned at session start and a regression reported at session end, which is the one place the graph speaks unasked. The **ceiling** to the structure connector's **floor**: it answers what in-kernel extraction cannot, and it is allowed to be absent — nothing may depend on it being installed. Note the trade the other way: the connector reads `git ls-files` and therefore sees only *tracked* files, while enola walks the filesystem — so the ignore list is the only thing between the graph and a vendored dependency tree. The defaults exclude `.venv`, `site-packages`, `node_modules`, build output and the usual caches; extend them per repo via `connectors.enola.ignore`. The cluster is generated from `brain.config.yml` (`active_repos` + `connectors.enola.repos`); the generated `mcp-arch.yaml` and all `.enola/` artifacts are gitignored. |

| GitHub            | via `gh` CLI         | Pre-allowed in `.claude/settings.json`.                        |
| MCP               | ✅ `tools/brain-mcp.py` | Read-only; stdio or `--http` (loopback Host + Origin checks); `BRAIN_SERVING=1` excludes ai-suggestion drafts across *every* read surface (MCP, serve JSON API, search CLI, static build) + query audit log. |
| Hosted tier       | ✅ `BRAIN_HOSTED=1` (runs locally; no deploy story) | Authenticated, writable multi-agent tier. Per-agent symmetric HMAC keys (the hub can sign as any agent; see the trust notes in the agentic-tier section); `POST /api/events` is the agent write endpoint (any signed event kind) and `/api/act` writes are signed events too; `GET /api/events?since=<seq>` is the authenticated read; the auth boundary rejects appends with a mismatched signature at write time and drops lines that fail verification on read. **Owner-subscription wake**: subscribe to a ref-pattern with a wake URL, and a matching event POSTs a signed hint (seq + ref, no payload) through an SSRF guard, capped per event, with the cursor as the at-least-once backstop. Off by default — local-first is byte-for-byte unchanged. |
| Spoke client      | ✅ `tools/brain-agent.py` | The harness-side half: an agent in any harness runs it to `emit` / `pull` / `subscribe` / `listen` against a hosted brain, mirroring the server's signing. Config via `BRAIN_URL` / `BRAIN_AGENT_ID` / `BRAIN_AGENT_SECRET`. The brain is the hub; the harness stays the execution spoke. `agent-key provision <agent>` emits the env + listener + subscribe block to wire a harness in. |
| Specialized agents | ✅ `tools/agents/`  | Spoke agents that subscribe + react deterministically (wake → prep → emit a result note on the event stream, never an LLM; the note is not copied into the tend inbox): a drift-reconciler and an observability-triage agent (Sentry/Datadog/Langfuse-shaped alerts). Real observability integrations become connectors emitting the same alert events. |
| Local emulation   | ✅ `tools/emulate-agentic.py` | Runs the whole agentic loop on one machine — real hub + real spoke agents + emulated producers → wakes → reactions on a signed stream. No deployment; a live smoke test of the real code paths. It uses this repository's own keyring and event stream paths, so it refuses to start where either exists; run it in a throwaway checkout. |
| Datasette         | ✅ pilot             | `tools/serve-datasette.sh` — faceted browse + SQL + JSON API over the derived index (immutable mode). |
| mempalace         | optional             | Verbatim / semantic-recall layer.                              |

**Twelve of the twenty-six skills consult these substrates**, and every
one names both tiers in order — the structure connector first because it
always answers, the graph tier second because it is allowed to be absent.
`/sync` reports an architecture-drift step and the finding *count* (never
the list — that would be a backlog by another name); `wiki-ingest`
checks a structural claim as it is written, with the *no-signal* path as
the default rather than the weak branch; `/shape` consults it in the
Phase-1 deepdive and for blast radius in Phase 3; `/continue` before code
leaves local; `/groom` gains contradicted-claim, ledger-hygiene and
stale-citation triggers; and `/ask`, `wiki-query`, `wiki-plan`, `review`,
`zoom-out`, `wiki-overlap` and `wiki-coverage` each gain the consultation
their own judgment step was missing.

## The agentic tier — hub-and-spoke (advanced, off by default)

The brain is **not** a harness and never runs your agents — but it can
be the **coordination hub** for many agents that run in their own
harnesses. Turn on the hosted tier (`BRAIN_HOSTED=1`) and:

- Each agent gets a **per-agent identity** (`agent-key issue`) — an HMAC
  key that signs everything it writes.
- Agents write to a **signed event stream** (append-only by
  convention) and read it from a per-agent cursor. The auth boundary
  rejects appends whose signature does not match the claimed agent's key
  and drops lines that fail verification on read.
- An agent **subscribes** to what it cares about (a repo, a thread, a
  producer) and is **woken** when a matching event lands — a webhook
  hint (seq + ref, no payload) through an SSRF guard, with the cursor as
  the at-least-once backstop. A wake never invokes the agent; it reacts
  on its own next tend.
- The **spoke client** (`tools/brain-agent.py`) is the harness-side
  half: `emit` / `pull` / `subscribe` / `listen`. `agent-key provision
  <agent>` prints the env + listener + subscribe block to wire a harness
  in.
- **Specialized agents** (`tools/agents/`) subscribe and react
  *deterministically* — a drift-reconciler, an observability-triage
  agent, waking, prepping, and emitting a `note` event with the result,
  never invoking an LLM. Those notes live on the event stream (read them
  with `events since`); nothing copies them into the tend inbox
  (`wiki/_state/inbox/`) that `/tend` digests.

See the whole loop without deploying anything:

```bash
python3 tools/emulate-agentic.py   # real hub + real agents + emulated producers, on one machine
                                   # (run in a throwaway checkout; refuses if a keyring or stream exists)
```

**What the signatures do and do not prove.** Keys are symmetric
(HMAC-SHA256), so the hub holds every agent's secret and can sign as any
of them, and so can anyone who can read the keyring (`agent-keys.json`,
git-ignored, mode 0600). A valid signature means "a holder of this
agent's key wrote it", not "only this agent could have". There is no
replay or timestamp check. Nothing enforces append-only beyond the code
path (the files can be edited). Verification runs against the current
keyring, so revoking or rotating a key makes that agent's earlier events
fail verification and vanish from every read. Appends are serialized so
each event gets a unique `seq`. Asymmetric keys would fix the forgery
limit; they are not built.

**There is no deploy story for this tier.** `deploy/` ships the three
read-only surfaces only; the hosted tier is run and tested locally
(`tools/emulate-agentic.py`, which starts a hub on loopback). Deploying
a hub (hosting, TLS, key distribution, an identity-aware proxy) is
open and unplanned.

With the tier off, **local-first is byte-for-byte unchanged** — no
keyring, no stream, no daemon, no subscriptions. The two settled
principles hold throughout: no scheduled LLM (a wake is a hint, not a
trigger), and the brain is the hub, not a harness.

## Deploy

One infra-agnostic image (`deploy/`): `BRAIN_SURFACE` selects the
public surface (ui / mcp / datasette) on the platform's `$PORT`.
Railway: root `railway.toml` points at it. Local emulation beside a
development brain: `docker compose -f deploy/docker-compose.yml up`
(ports 9080–9082). Multiple brains per machine: `BRAIN_PORT` /
`BRAIN_MCP_PORT` + per-instance timer units. See `deploy/README.md`.

## Browse (UI)

```bash
cd ui && npm install                # first run only
npm run build                       # production build
npm run dev                         # local dev server
```

`ui/src/content/docs` symlinks to `../wiki`. The app's root is the
**briefing** — the brain's judgement of what needs you, what's in
flight, and what's on the table — and every wiki page renders inside
the app with lifecycle chrome (kind/status/confidence chips,
executive summary, AI-draft and superseded banners). `/graph/`
renders the link graph with per-edge provenance — solid for authored
edges, dashed-faint for machine-suggested ones, a ⚑ mark on
low-confidence pages the graph leans on. `/channels/` is the async
conversation surface: every topic is a channel, a post is an inbox
write, and the agent replies in-thread on the next tend (no live
chat). One **collaboration control** appears on every card, page, and
thread — comment / queue / post, plus edit and unqueue of your own
pending items — all backed by the inbox. Every page carries
**trust signals** from the provenance graph — how many pages rely on
it, and an "uncertain but load-bearing" flag on low-confidence pages
others depend on. Pagefind search at `/search/`. Local-first — share
via repo paths, not a hosted URL.
