Console
The read-mostly observability UI at /console - the complete list of what it can change, how sign-in works, and what each page shows.
On this page
The console is the owner's window onto a system whose actual clients are agents.
It is html/template plus vanilla JS plus SSE, served by internal/console from
the same binary as everything else - no node, no npm, no React, no build step. It
is not how Seamless is driven; it is how you watch it.
What the console can change
The console is read-mostly. That is a design claim, so here is the whole list
- every write it is capable of, taken from the
POSTroutes ininternal/console/console.go. There are no others.
| Action | Route | Effect |
|---|---|---|
| Archive a memory | POST /console/memories/{id}/archive |
Routes through lifecycle.Archive: the memory is stamped invalid and leaves every index, its file stays on disk with a tombstone. |
| Force-release a task claim | POST /console/tasks/{id}/release |
The owner override: releases a claimed task's lock regardless of who holds it, reopening it for any agent. Not reachable from the agent MCP tools. |
| Approve a captured plan | POST /console/plans/{slug}/approve |
The escape hatch for a Claude Code approval whose PostToolUse never fired: flips the cc-plan note to plan-status:approved and creates the tracking task, exactly as the hook would have. Only applies to a CC capture. |
| Star / unstar an entity | POST /console/favorites/{kind}/{id} |
Toggles the same starred flag the favorite_set MCP tool sets: a starred memory pins into briefings and gets a post-fusion recall boost. For memories and notes the star lives in frontmatter; it never bumps updated. |
| Ask the gardener for proposals | POST /console/gardener/request |
Interprets a natural-language maintenance request into pending proposals. It never mutates a memory. |
| Plan a project split | POST /console/gardener/split |
Interprets a split request into a plan batch of pending proposals (one split setup plus one reproject per memory). Also never mutates a memory. |
| Apply one proposal | POST /console/gardener/{id}/apply |
Carries out that proposal's effect. |
| Dismiss a proposal | POST /console/gardener/{id}/dismiss |
Drops it without acting. The pattern is raised again if new evidence for it arrives after the decision. |
| Hide a proposal forever | POST /console/gardener/{id}/hide |
Drops it without acting and blocks the pattern permanently - no recurrence re-raises it. Listed under Hidden forever. |
| Unhide a pattern | POST /console/gardener/{id}/unhide |
Lifts a forever block. The proposal stays resolved; the gardener may propose the pattern again the next time it recurs. |
| Retarget a reproject proposal | POST /console/gardener/{id}/retarget |
Rewrites a pending reproject's destination project before it is applied. Reproject proposals only. |
| Apply a whole plan batch | POST /console/gardener/plan/{slug}/apply |
Applies every pending proposal in a plan, split setup first so the child projects exist before the memories move. Best-effort: it applies what it can, reports how many landed, and leaves the rest pending. |
| Save briefing settings | POST /console/settings/briefing |
Writes the briefing knobs as a runtime override row in the DB. It never writes the config file. |
| Reset briefing settings | POST /console/settings/briefing/reset |
Clears the override row, reverting to the file/env configuration. |
| Save optional features | POST /console/settings/features |
Writes the feature switches as a stored override row in the DB. It changes what is exposed - console screens and the matching agent tools - and deletes nothing. |
| Reset optional features | POST /console/settings/features/reset |
Clears the override row, reverting to the file/env configuration - which, unless you set the keys there, means every optional feature is off again. Still deletes nothing. |
| Save a project family | POST /console/settings/families/save |
Creates a family or replaces one family's name and member set - the same project_families setting seamlessd family manages. Members come from a closed picker of registered projects, so a typo cannot create an inert member. |
| Delete a project family | POST /console/settings/families/delete |
Removes the whole family. Its projects lose the sibling-findings channel; nothing else about them changes. |
| Sign in / sign out | POST /console/login, POST /console/logout |
Sets or clears the console cookie. Touches no data. |
Read the shape of that list. There is no "create memory", no "edit note", no "delete", no "add task", no "start session". The direct writes to knowledge state are archive a memory, approve a captured plan, and the star flag; the rest either manage gardener proposals - which are themselves proposals, reviewed before they do anything - or free a lock, or set a configuration knob (briefing overrides, feature switches, project families) that shapes future briefings, or what is exposed, without touching any memory's content.
This is deliberate, and it is the same principle as the gardener's propose-only contract. The store is written by agents doing work, with provenance attached. A console that could quietly edit a memory would produce knowledge that came from nowhere, attributable to no session, explaining nothing.
Every write action redirects back with a flash message (?notice= for success,
?error= for failure) rather than rendering a result page in place, so a reload
never repeats the action.
Signing in
There is one credential in the whole system: the static bearer key
(mcp.api_key). It guards /api/mcp, the hook endpoints, and the console alike.
Two ways to present it:
- A browser trades the key for a cookie at
/console/login. The cookie value is a SHA-256 hash of the key, not the key - so the raw credential never sits in the browser's cookie jar. It isHttpOnly,SameSite=Lax, and scoped to/console. - The
seamCLI sends the key as a bearer token on theAuthorizationheader, and asks for JSON.
Unauthenticated browsers are redirected to the login page with a ?next= that is
validated against an open-redirect (an off-site or absolute candidate becomes
/console/). Unauthenticated JSON callers get a 401.
Public routes are the login page and the static assets (console.css,
interactions.js, search.js, favicon.svg). Everything else requires the key.
make console
make console # open in the default browser, already signed in
make console-chrome # same, but force Google Chrome
This builds, then runs seamlessd console-open, which renders a one-shot
self-submitting login page to a 0600 temp file and opens it. The page POSTs the
key to /console/login, which sets the cookie and 303s into the console - so you
land on an authenticated page with nothing to paste. It refuses to run if
mcp.api_key is empty or the server is not answering /healthz.
make console-chrome exists for agents: they drive Chrome, so this hands the auth
cookie to the browser they can actually see. (--browser is macOS-only.)
Three ways to render a page
Every route answers in the shape the caller asked for:
- HTML by default - the full page, layout and all.
- JSON when the caller sets
?format=jsonor anAcceptheader that wants JSON and not HTML. This is howseamreads the console's data. - An HTML fragment for entity details when the caller passes
?peek=1- the detail pane loads it without a page navigation - or?reader=1, the richer reader fragment the library screens (memories, notes, tasks, plans, labs, trials) swap in place.
The event page composes its full page from the same detail-body block its
peek fragment renders, so the two cannot drift. Session and project
deliberately do not: their fragments are compact summaries of much richer
bespoke pages. Memory, note, task, plan, lab, and trial detail URLs render
their library screen with that entity open in the reader.
Strictly-validated query params (?sort, ?scope, ?tab, ?w) return a 400
naming the bad param and listing the valid values, rather than silently falling
back to a default - so an agent driving the console by URL sees the fix.
GET /console/events is the SSE stream: every recorded event as one JSON data:
frame, with a ping every 25 seconds. ?feed=interactions opts into the richer
transport-level rows the Interactions screen consumes.
Overview
/console/
The landing page and the health check. It carries:
- Counts - active memories (broken down by kind), notes, sessions, and tasks by status.
- Retrieval health over a selectable window (
?w=24h|7d|30d|all): injection volume, a trend chart, the reach rate (distinct active memories that actually surfaced, over all active memories), sessions reached, and the most-injected memories. - Coverage - the share of in-window sessions that retained anything, with a per-channel breakdown (findings, memories, notes, trials) and a windowed trend. The channels overlap, so the shares need not sum to 100%.
- Projects at a glance - the top projects by recent activity, drawn from the same batched query the Projects board uses, so a row here reconciles with a row there exactly.
- Agent-reported mishaps - recent incidents agents explicitly supplied to
session_end, attributed through the reporting session's harness and model. Warning tones appear only when reports exist; an empty rail is a positive "No mishaps reported" state. - Recent activity - the last twelve events, each linking to its detail page.
The four judged vitals at the top (memory reach, knowledge continuity, context
injections, sessions reached) are drill-down links carrying the selected
window: reach, injections, and sessions reached land on the Retrieval screen
whose hero and delivery funnel are the same numbers over the same report, and
continuity lands on the Sessions list filtered to ?retained=no - the
sessions that kept nothing. An empty-state card stays linked, because the
destination explains why there is nothing.
Live sessions are counted TTL-aware (active and heartbeated within the idle
threshold), so the headline matches the Sessions screen rather than the raw
active count that an idle session inflates until the reaper runs.
Now
/console/now
The exploded live view: what every agent is doing right now, across every project and plan at once. Where the project workspace is task-centric, Now is agent-centric - the unit is the live session, and everything it holds rides its card. The sidebar entry's badge is the live agent count, and the page refreshes on every event kind (tool calls included - here they are the signal, not noise), morphing in place like every other screen.
Top to bottom:
- The titlebar - live agent count and the fleet-wide pulse: an events-per- five-minutes sparkline over the last hour, all kinds, deliberately unfiltered by scope.
- A scope strip - one chip per project with a live agent (
?scope=<slug>; the empty project scope filters asglobal). Filtering narrows every zone except the pulse. - On duty - one card per live session: harness+model pill, project, last heartbeat (cards are toned hot/warm/quiet by heartbeat age), session wall clock, cumulative tokens, a star toggle, every claim it holds with a live lease countdown, and a short trail of what it just produced. A live agent holding nothing says so: "no claim held - roaming".
- Loose ends -
in_progresstasks no live agent is carrying: a lapsed lease, a claimless start (tasks_update status=in_progresswithout a claim), or a holder that went quiet mid-lease. A lapsed claim offers release claim with no confirmation - the lease is already dead, so there is no live holder to interrupt, only a stale lock to clear. - Plans in motion - a horizontally scrolling rail of every incomplete plan across every project, done/in-flight progress bars and ready counts, cards dimmed once a plan has rested for 24h. Each links to its project's Plans & tasks tab.
- Up next - the cross-project ready queue: claimable this instant, plan steps included, each naming what closing it would unblock.
- The wire - the freshest business events, scope-filtered.
Everything links onward - sessions, tasks, plans, projects, events - and the lease countdowns tick client-side between refreshes. With the gamification feature on, the page also carries the day tape, the personal-records rail, the hot-streak pulse, and celebration moments; off (the default), none of that renders.
Interactions
/console/interactions
The clean live feed of what agents are actually doing: MCP tool calls, hook injections, recall-miss prompts, session lifecycle, and the plan-mode capture stream. A fresh page starts empty and listens from that moment forward; merely visiting never restores old rows.
History is explicit and additive. Choose a recent window and select Add when earlier context is useful; live rows stay in place, and Load older events paginates only inside that chosen window. Filters operate over the rows already in memory, by event category and session lane. Pausing buffers new arrivals rather than discarding them.
Each compact row expands just enough to expose its request/result or injected
text. Selecting the inspector keeps that context beside the stream and links to
the full event page. A recall via the MCP tool records both a
retrieval.injected and a tool.call; the injected twin is dropped here because
the tool call carries the same content plus its arguments. Session lifecycle
twins are kept on purpose, as feed markers.
Search
/console/search
One query across every entity the console can link to. Memories and notes come
through the same fused FTS + semantic retrieval that recall
uses, with snippets; tasks, plans, trials, projects, and sessions have no FTS
mirror and match by LIKE.
Stable references get their own lookup lane. An exact memory name or note slug
ranks ahead of token-overlap matches and is shown beside the result's display
title. A full memory, note, task, session, or trial ULID works
case-insensitively; an 8-character-or-longer ULID prefix finds every match of
those kinds without
letting a short prefix such as 01 flood the page. Identifier matches keep the
canonical ID-based detail link and are labeled separately from keyword and
semantic matches.
The command palette (⌘K, available on every page) fetches this same route with
?format=json&fast=1, which drops the semantic leg - a query per keystroke must
never cost a remote embedding round-trip.
The semantic leg is nearest-neighbor: there is always a "nearest" memory,
however far, so a semantic-only hit must clear search.semantic_floor (cosine
similarity, default 0.3) to appear - without the floor any query, including
nonsense, would fill the page to its limit. A hit the keyword leg also matched
is exempt. Every hit the semantic leg found shows its similarity as a
percentage, so you can see where relevance falls off; keyword-only hits show a
highlighted snippet instead. Agent-facing recall applies no floor - an agent
can judge a weak hit for itself.
Coverage is deliberately partial in one place: events are excluded because the telemetry stream has its own Interactions surface and would flood results.
Projects
/console/projects
The board: one row per project, with live sessions, total sessions, open and
blocked tasks, memory count, inherited memories, reach rate, and last activity.
Grouped by family (?group=family|flat) and sortable (?sort=recent|coverage|name).
The global ("") scope appears as a row but is not a project and has no detail
link.
Selecting a project opens the project workspace - a seven-tab page over that project alone:
| Tab | Shows |
|---|---|
| Overview | The project's metrics, memory kinds, injection trend, recent events. |
| Plans & tasks | Per-plan step timelines with each step's status, claiming session, lease countdown, and blocking dependency; plus the ready queue. |
| Sessions | The project's sessions, with the tasks each currently holds. |
| Memories | The project's memories with a lineage cell - provenance session, or a supersession pointer - plus the memories it inherits from a parent that a strict per-slug count excludes. |
| Notes | The project's notes. |
| Interactions | The project-scoped slice of the feed. |
| Context | The effective SessionStart flow into and out of the project: global and parent memory pools, sibling-family channels, and split lineage. |
A retired project still renders, with its banner - kept for provenance. Only an unknown slug is a 404.
Sessions
/console/sessions, /console/sessions/{id}
The list separates active (live: active and heartbeated within the idle TTL)
from idle (active but gone quiet past it, awaiting the reaper) from
completed and expired. Filterable by status, searchable, windowed, and
filterable by retention (?retained=yes|no): whether the session left a
durable artifact behind - non-empty findings, or a written memory, note, or
recorded trial, the same covered-ness test the coverage numbers apply. The
Overview's continuity vital links straight to ?retained=no, so its click
answers "which sessions dropped knowledge".
A session's page is the workspace: its findings (rendered), its full event timeline as interaction rows, per-session counts (tool calls, memory reads and writes, items injected, and read-after-inject), the tasks it currently claims with their lease countdowns, and the memories it produced.
Memories
/console/memories, /console/memories/{id}
A two-pane library: a rail of memories grouped by project (global first, kinds in canonical order, each dot colored by kind) beside a full-height reader. Sortable by name, recency, reach, utility, or starred; filterable by a substring of name, description, kind, or tag. Inactive memories collapse into an archived-and-superseded group at the rail's end, each carrying its status and, when superseded, what replaced it.
The reader renders the body uncapped (through the markdown layer, with raw HTML
disabled and a sanitizer on the output), the metadata - kind, project, tags,
timestamps, the session that produced it - its reach counts, its
utility score with the per-signal
demand breakdown behind it, the vscode:// link straight to the file, and its
supersession neighbors in both directions: what replaced it, and what it
replaced. The actions here are
star - the flag that pins it into briefings and boosts recall - and
archive.
Opening /console/memories auto-opens the most recently updated match; a
memory's own URL opens the same screen with it selected. Clicking rail items
swaps the reader in place (real URLs, browser Back works), and j / k step
through the rail.
Notes
/console/notes, /console/notes/{id}
The same library shape for notes: a project-grouped rail (global "" first),
sortable by recency, title, or starred, filterable by title, description, or
tag. The
reader renders the note as a document - uncapped body in a measured reading
column, description, tags, word count, source URL, and the file path with an
editor link.
Tasks
/console/tasks, /console/tasks/{id}
The same library shape, with the rail grouped into four buckets: ready (no unfinished blocker), in progress, blocked, and closed (done and dropped merged, newest first, capped at 25 with a count of the rest, collapsed by default). The reader carries the task's body, claim and lease state, and both dependency directions; force-release is the action, and it is the owner override - it takes the lock from whoever holds it.
Plans
/console/plans, /console/plans/{slug}
The same library shape, with the rail grouped by phase (in progress, ready, done) and scoped by the window selector in the rail's tools (24h by default). Both kinds of plan share the rail:
- captures - Claude Code plan-mode captures (
cc-plannotes), with their lifecycle status, iteration count, and cached subagent runs. - composed - plain plans-as-composition plans (a
note tagged
plan:<slug>plus its tasks), which have none of the capture-only fields.
A capture owns its slug; composed plans fill only the rest. The reader shows the rendered plan body, the step tasks, and the notes attached to the composition (supporting notes and agent caches). Approve appears here, for captures only.
Each plan also carries a model tokens rollup - the cumulative transcript
tokens of every session attributed to the plan (any session that moved a step,
or captured the plan) - compact in the rail (~483k tok) and qualified in the
reader (~483k model tokens · 3 sessions (1 unreported)). Attribution is
whole-session on purpose: tokens are only ever known per session, so a
session's full burn counts toward each plan it touched, counted once however
many steps it moved. Claude Code reports tokens at session end, so a live
session stays unreported until it finishes; a session that touched more than
one plan is disclosed as shared rather than split by guesswork - which is
also why plan totals must never be summed across plans.
Labs
/console/labs, /console/labs/{name}
The research-lab surface (the console twin of lab_open / trial_record /
trial_query). Labs and Trials are one optional feature
and ship off; while it is off both screens answer with a short "switched off"
page and neither appears in the nav.
A lab is not a stored entity - it is the label its trials carry, a stable name for one line of investigation - so this screen is an aggregation over the trials table and there is nothing to write.
The same library shape: a rail of labs, most recently active first, each with its trial count and pass/fail tallies. The reader shows one lab's whole identity - outcome tallies (pass, fail, partial, inconclusive, and other for free-form or empty outcomes), the projects and sessions its trials touched, first and last activity - and its trial history, newest first, each entry linking into the Trials screen. Long histories cap at 100 with a pointer to the uncapped, filterable view.
Trials
/console/trials, /console/trials/{id}
The flat, filterable view over every recorded trial - the console twin of the
trial_query MCP tool, and part of the same
optional feature as Labs. The rail groups trials
by lab (a group sits where its newest trial does) and filters by ?lab= and
?outcome=. Outcomes are
free-form by design, so ?outcome= is an exact-match filter rather than a
validated enum; the seg offers the conventional values (pass, fail,
partial, inconclusive).
The reader shows one trial's full record: what changed, expected vs actual
side by side (the actual pane tinted by outcome), the structured metrics
trial_record captured, and its provenance - lab, project, and the recording
session, each linked. Trial hits also surface in search and the
command palette, and a session's page lists the trials it recorded.
Context
/console/context
The briefing topology that the plans board does not show: which knowledge pools are eligible at SessionStart, which configured edges are currently enabled by the effective briefing settings, and where project splits moved durable memory. It covers global memory, one-way parent-memory inheritance, bidirectional sibling families (findings and the opt-in memory channel), unregistered-scope warnings, and retired-project split lineage reconstructed from the memory-move event log.
?scope=all renders every known project scope, with global memory shown as the
shared source pool; ?scope=project&project=<slug> focuses the same topology on
one project. The legacy /console/relations route permanently redirects here
and preserves its query string.
Reachable from the Projects board.
Retrieval
/console/retrieval
The circulation report: is stored knowledge actually reaching agents, and at what cost? The hero pairs the reach ring (distinct active memories that surfaced, over all active memories) with the window's volume and cost - injections, sessions reached, and estimated tokens injected. Everything follows the selectable observation window except where a panel says otherwise.
Five zones below it:
- Delivery path - the funnel as a flow: injections → distinct memories → sessions reached, ending in the knowledge-base coverage meter (how many active memories are still waiting to surface).
- Circulation pattern - the injection trend chart and the traffic-by-kind mix.
- Scope coverage - reach per project scope (global first), each row with its own reach rate and injection count.
- Knowledge pressure - the most-injected memories against quiet knowledge: active memories not updated, injected, or read in 90 days, mirroring the gardener's default staleness horizon. Unlike everything else on the page, the stale list is all-time, not windowed.
- Loop health - push versus pull: is what briefings push also what agents
pull? Demand rate is the share of briefed memories that were also pulled
by a query; waste share is the share of injected tokens spent on memories
with no query-gated demand, judged against a fixed trailing 30 days whatever
the window. Two miss stats sit side by side and measure different paths:
the recall-miss rate is ambient - prompts that matched no memory on the
<seam-recall>path - while agent search misses are deliberaterecallcalls that found nothing; recurring ones feed the gardener's memory-wanted pass. Funnel by surface splits the read-after-inject funnel by injection surface - session-start briefings versus subagent-start child injections - each with its injections, distinct memories, and the share pulled by a query-gated read within the following 24 hours. The zone closes with the dead weight panel: memories briefings kept injecting without a single recall hit, prompt match, or read in 30 days (constraints and stages exempt as pinned-by-design) - the evidence behind the gardener's dead-weight archive proposals.
Reachable from the Overview's retrieval-health card.
Gardener
/console/gardener
The review queue. Each pending proposal renders as a card showing exactly what it would do - the memory to archive and why (whether staleness, a dead stage, or dead weight flagged it), the pair to merge with their similarity score, the digest or consolidated memory with its body rendered, the reproject's source and destination, the rekind's from and to kinds, the split's children and shared parent, and the knowledge gap card with the queries agents kept searching for in vain - applying that one opens a task; nothing is written until someone writes the memory.
Split batches are grouped by plan and reviewed together, setup card first, with an apply-the-whole-plan action.
The actions are apply, dismiss, hide forever, retarget (reproject cards only), and apply plan. Dismissing answers the evidence in front of you - the pattern comes back if it recurs; hiding answers the pattern itself. Everything you decide lands in Recently decided with an Undo, and a hide is additionally listed under Hidden forever, where Unhide lifts the block without returning the proposal to the queue. Above them sits a single ask-in-words box, and it only ever produces more proposals for this same queue. A request recognized as a project split is planned as a split directly - the plan batch appears below like any other. When the split's source project cannot be matched, an inline follow-up asks you to pick the project and plans the split from there; nothing is retyped.
See The gardener for what each proposal type means.
Settings
/console/settings
A view of the running configuration - data dir, budgets, gardener settings, the registered projects, and the repo→project map - with editable blocks for optional features, the semantic index, briefing injection (including utility ranking), and project families.
Optional features
/console/settings#features
Optional features are the parts of Seamless you can switch on and off, and they ship off: a fresh install exposes none of them until you turn one on. The zone renders one card per feature - a toggle, an Enabled/Disabled pill, what the feature is, a generated line naming exactly what switching it off hides, and a live count of the data it holds either way ("Data kept: 12 trials across 3 labs").
There are three today:
Research labs & trials owns the Labs and Trials
screens, the trials search scope, and the lab_open, trial_record, and
trial_query MCP tools. Screens and tools move together on purpose, so an
agent never sees a tool for a screen you switched off.
Momentum is gentle progress cues woven into existing screens -- plan finish lines, capture streaks, knowledge payoffs, and project growth -- judged from real activity, never invented. It owns no screens or tools of its own; turning it on adds seven surfaces where you already look:
- Plan finish-line cards on the Overview attention strip: any plan at least 80% done gets one positive card naming the exact remaining steps ("seambench -- one step from shipped"), linking to the plan, with a thin progress bar drawn to the plan's exact done/total percent. The agent briefing's plan line carries the same emphasis, so agents are nudged to close it too.
- The capture calendar on Sessions: a year of daily activity, cell intensity from sessions per day, a distinct dot on days that captured knowledge (a session left findings, a memory, a note, or a trial), and two quiet numbers -- the current capture streak and the longest ever. The streak counts covered days, so it rewards capture, not raw usage; a streak of seven covered days or more earns a small flame beside the number, and the number itself stays verbatim. The grid is an instrument, not wallpaper: hovering a cell reads out its day, clicking one focuses the session map on exactly that day (a clearable chip names the focus; picking a time window widens back out), and the grid is keyboard-walkable -- arrows move a day or a week, Enter focuses, all without a page reload.
- Knowledge payoff moments: the first time a memory is read by a session other than the one that wrote it, the moment lands in the activity ledger ("gotcha chroma-boot-race just paid off for the first time") -- once per memory, ever -- and the Overview rail gains a Memory of the month panel naming the last 30 days' top-utility memory with the counts behind the claim.
- Maturity stages on the project board and detail header: each project earns a latched stage -- seedling, sprouting, established, deep-rooted -- from real thresholds over age, memories, event volume, and reach. Stages never regress, and the pill's tooltip states exactly what the next stage asks for.
- The plan-shipped settle on Plans: the task transition that closes a plan's last step -- wherever it lands, an agent shipping over MCP included -- mints a once-ever plan.shipped moment. The plan's row settles with a single wash when it happens while you are watching, the ledger renders it under its own flag icon, and the Plans header counts the local month: "Plans shipped this month: N".
- Milestone moments in the activity ledger: a short latched set of honest firsts and counts -- a project's 100th/500th/1000th memory, its 1000th answered recall, its first supersession, its first shipped plan, and its birthdays -- each minted once ever and rendered under an award glyph with the exact claim ("100 memories written in seamless"). Milestones accumulate in the ledger only; there is no trophy screen.
- Witnessed unlocks on Settings: the utility-activation table's armed note upgrades to the date each stage unlocked, so a latch that used to flip silently is witnessed.
Motion keeps one register: every animation plays once -- on a live arrival or
on first render -- and nothing loops except the existing pulse idioms. A moment
already on screen when the page loads renders plain; only a new arrival over
the live feed animates. prefers-reduced-motion stills all of it, and the
shipped-plan settle is the ceiling: no confetti and no toasts, which belong to
gamification's arcade on Now.
Momentum keeps the console's judged-numbers ethos: every number is real and verifiable, empty states say so honestly, and there are no punishment mechanics -- an inactive day is an empty cell, never a warning, and nothing nags or expires. Off (the default), none of it renders and none of it is computed: no latch moves and no moment is minted. Moments minted while it was on stay in the event history (nothing is ever deleted), though milestone rows leave the activity feeds until it returns.
Gamification is the arcade layer of the Now screen. Where momentum asks "is this knowledge practice building on itself?", gamification plays back "how hard is the fleet running right now?" - and an owner who enjoys one framing may find the other noisy, so they toggle separately. It owns no screens or tools of its own; turning it on adds four surfaces to Now:
- The day tape: today's judged output - tasks closed, memories and notes written, plans touched, sessions started - each cell against its trailing 7-day daily average.
- The personal-records rail: latched bests (most tasks closed in a day, most memories written in a day, most agents live at once). Records only ever move forward, like maturity stages; an unset record reads "today could be the day".
- The hot-streak pulse: the last ten minutes' event count beside the titlebar pulse, catching fire past a fixed floor. The count is shown verbatim either way, so the claim stays verifiable.
- Celebration moments: a record falling or a plan shipping its last step today renders a moment chip - and when one lands while you are watching, a toast and a brief confetti burst (skipped under reduced motion). A record crossing is minted into the event ledger at most once per record per day.
The same guardrails as momentum apply: every number is judged from recorded activity, never invented, and there are no punishment mechanics - a quiet day is an empty tape, never a warning. Off (the default), none of it is computed, no record latch is written, and the Now page carries zero trace of it.
Nothing is ever deleted. Switching a feature off gates exposure and nothing else: the trials stay in the database, its screens answer with a short "switched off" page that links back here (a JSON caller gets a 403), and switching it back on restores every surface exactly as it was. That is what the data-kept line on each card is for - it reports the feature's own rows whether it is on or off.
When the change lands has four different answers:
| Surface | When |
|---|---|
| The console | Immediately - nav entries, screens, the search scope, and the overview tiles appear or disappear on the next render. |
| A tool call | Immediately - a disabled feature's tool is refused as an unknown tool, whatever list the caller is holding. |
| The agent briefing | The next session start - momentum's finish-line emphasis reads the same stored override the console gates on. |
| A client's tool list | The next time it lists tools, in practice its next session. Seamless declares listChanged: false and sends no tool-list notification, so a connected client keeps the list it already has. |
| The client-side skill | The next seamlessd install-hooks run. The daemon does not reach into ~/.claude/skills on a toggle, so seamlessd doctor raises an info line while a skill for a disabled feature is still sitting in a client's skill home. |
Saving writes a stored override row in the database - the same layer the
briefing form uses. It wins over file and env, never touches your config file,
and holds until Reset to file + env clears it; reset means back to the
file/env configuration, which unless you set features: there is off.
One override can be in force without you having set it. Upgrading an installation that already holds trial data seeds the override with research on, so a feature that now ships off does not disappear from under data you were already using. That is why the zone states that a stored override is in force rather than crediting you with the choice - and reset clears it like any other.
Semantic index & storage
The embedding pipeline and the SQLite database, side by side. The embedder card shows the active provider and model - and when embeddings are off, the exact cause, with distinct copy for the owner off switch, the no-key lexical fallback, and a config error. The off/auto switch is a settings row read once at serve start, so the page flags a pending restart whenever the stored switch disagrees with the running process. Below it, the stored-vector counts: totals, the not-yet-embedded backlog, and a per-model table that badges models the running embedder no longer writes as stale - re-embed everything rewrites the corpus with the active model in the background. The database card shows the file path, size on disk including the WAL, and schema version.
Briefing injection
Saving writes a runtime override row in the DB. It layers over the file/env
values and wins until reset, and it applies from the next session start - no
daemon restart. It never touches your config file, so
seamless.yaml stays the thing you wrote. Reset clears the override and
reverts to file/env. The form validates: a non-numeric knob or a value that
fails Briefing.Validate() comes back as an error flash, not a
silently-dropped save.
The form's utility ranking group holds the closed-loop knobs -
utility_weight (utility's share of the briefing sort key; 0 restores pure
recency) and utility_mode (auto arms each project as its demand history
matures, on everywhere now, off never). The same group's utility
activation by scope table lists every scope with each readiness gate against
its threshold (demand events, memories touched, history age - met gates turn
green) and, for scopes still building, spells out exactly what remains before
auto arms ("needs 7 more events, 3d more history"). A per-scope force
overrides the latch in either direction; when the global mode is on or
off, the table says the per-scope state is dormant until auto returns. See
Sessions & briefings for
how the blended order behaves once active.
Project families
Create, rename, edit, or delete the named groupings that
seamlessd family manages from
the CLI - the same project_families setting, so a change on either surface
shows up on the other. Members are chosen from a closed picker of registered
projects; the CLI is the route for pre-registering a slug that does not exist
yet.
See Configuration for what each knob does, and Sessions & briefings for what they tune.
Event detail
/console/events/{id}
What a Recent-activity or timeline row links to: a compact event review workspace with the event's agent/session attribution, verbatim injected or transport content, surfaced memories resolved to their live index entries (or flagged missing), remaining payload fields, and raw JSON. The Interactions inspector uses the same content model in a side pane; opening the full page adds context without changing the underlying event.
Errors
A bad or stale URL renders a styled, layout-wrapped error page with a way back,
rather than dropping you on a bare 404 page not found. A 404 names the missing
entity and a 400 names the bad parameter and its valid values. A 500 stays
generic in the browser - the detail is in the log, not the response. Fragment
fetches (?peek=1) get a fragment-shaped error, since a full page injected into
the detail pane would nest the whole console inside itself.