Problem Statement
I run agent-tty across many Homes — the default ~/.agent-tty, throwaway mktemp -d directories for isolated runs, and per-project AGENT_TTY_HOME overrides. But agent-tty can't see across them. list and the Session Dashboard only ever show the single Home resolved for that invocation, and there's no way to enumerate Homes at all (the only cross-Home artifact is a one-way sha256(home) hash under /tmp). So I can't discover which Homes exist, I have to remember and re-type every --home path, and a Home I've used effectively disappears from view the moment I move on — even though its Event Logs are still inspectable.
Solution
agent-tty remembers every Home that has hosted a Session in a per-machine Home Registry, and the Session Dashboard gains a Home picker so I can switch Homes and inspect each one's Sessions without leaving the dashboard. A new home list gives the same view on the CLI, comparable to list for Sessions. The registry stays tidy on its own: dead Homes (deleted temp dirs, emptied Homes) are hidden from listings immediately and pruned by garbage collection, which now sweeps across all known Homes by default. Everything is read-only observation; browsing never changes a Session.
User Stories
- As a developer, I want agent-tty to remember each Home I've created a Session in, so that I don't have to track
--home paths myself.
- As a developer, I want
home list to show the Homes that currently have Active Sessions, so that I can see where my live work is.
- As a developer, I want
home list --all to also show Homes whose Sessions are all Terminal, so that I can find a finished project's Home to replay its Sessions.
- As a developer, I want
home list --json, so that I can script over the set of Homes.
- As a developer, I want each
home list row to show the Home path, a count of Active/total Sessions, and when it was last used, so that I can tell Homes apart at a glance.
- As a developer, I want
home list sorted by most-recently-used first, so that the Home I care about is at the top.
- As a developer, I want to open the Session Dashboard and press a key to bring up a Home picker, so that I can switch which Home's Sessions I observe without restarting the dashboard.
- As a developer, I want the dashboard to open on the Home I launched it with (or the default), so that the picker is additive and nothing about today's behavior changes.
- As a developer, I want the picker to default to Active Homes and let me widen to all registered Homes, mirroring the dashboard's existing
active/all scope for Sessions.
- As a developer, when I select a Home in the picker, I want the dashboard to list that Home's Sessions and let me open a Live View, so that inspecting another Home works exactly like inspecting the current one.
- As a developer, I want browsing Homes in the picker to never change any Session's state, so that merely looking around can't reconcile or kill anything.
- As a developer, I want a Home added to the registry automatically the first time it hosts a Session, so that I never register Homes by hand.
- As a developer who uses throwaway
mktemp -d Homes, I don't want the registry to balloon with dead entries, so that the picker and home list stay useful.
- As a developer, I want listings to immediately hide a Home whose directory or Sessions are gone, so that I never see a stale Home even before garbage collection runs.
- As a developer, I want
gc to sweep all registered Homes by default and prune dead ones, so that one periodic command keeps the registry from growing without bound.
- As a developer, I want
gc --home <path> to scope collection to a single Home, so that I can still target one Home when I need to.
- As a developer, I want garbage collection to deregister a Home once it has no Sessions left, so that emptied Homes drop out of the picker.
- As a developer, I want garbage collection to never delete a Home directory, so that agent-tty can't remove a directory I own and might still want.
- As a developer, I want a deregistered Home to reappear automatically if I create a Session in it again, so that deregistration is never destructive or sticky.
- As a developer, I want
home forget <path> to remove a Home from the registry without touching disk, so that I can clear a Home from the picker immediately.
- As an AI coding agent, I want
home list --json, so that I can discover which Homes are available and choose one to inspect.
- As a developer, I want the registry to be per-machine, so that another machine's Homes never appear in or interfere with mine.
- As a developer using Coder workspaces, I want each workspace to keep its own registry, so that Homes don't leak between workspaces.
- As a developer, I want the registry stored at
${XDG_STATE_HOME:-~/.local/state}/agent-tty/homes.json, so that it sits in the conventional machine-local state location and is easy to find or clear.
- As a developer, if I delete the registry file, I want Homes to re-register on next use, so that losing the file is harmless.
- As a developer, I want the dashboard picker and
home list to show the same Homes under the same scope, so that the two surfaces never disagree.
- As a developer, I want
home commands and the picker to use the term "Home" consistently and never conflate it with my OS $HOME, so that the vocabulary is unambiguous.
- As a developer, when I enter a selected Home, I want its Sessions reconciled (accurate statuses) exactly as
list/the dashboard do today for a single Home, so that entry reflects the truth.
- As a developer running parallel agent workflows, I want concurrent
creates to update the registry safely without corrupting it, so that the file survives heavy use.
- As a developer, I want
home list to never mutate Session state across Homes just to display counts, so that listing stays cheap and safe.
Implementation Decisions
- New
HomeRegistry store module under src/storage/ (sibling to sessionPaths.ts/home.ts): a per-machine, advisory JSON index. The Home directories remain the source of truth; the registry is reconciled against them, never the reverse. (See docs/adr/0008-home-registry-advisory-per-machine.md.)
- Location:
${XDG_STATE_HOME:-~/.local/state}/agent-tty/homes.json, derived from the OS user and independent of AGENT_TTY_HOME — the registry spans Homes, so it cannot live inside one. Resolved by a pure function, mirroring resolveHome.
- Entry shape:
{ path, lastSeenAt } only. No cached Session counts or statuses; those are derived live by scanning the Home at read time.
- Store interface with injected fs + clock (mirroring
GcDependencies): upsert(path) (register / refresh lastSeenAt), list() (applies prune-on-read), forget(path), and a prune/sweep operation. Writes are atomic (temp file + rename) to tolerate concurrent creates.
- Auto-registration: the
create path upserts the resolved Home after a Session is created.
- Prune-on-read:
list() stats each entry and omits Homes whose directory or sessions/ tree is gone; it does not rewrite the file on every read (durable compaction is gc's job).
- Scope: an Active Home is a Home with ≥1 Active Session. Default scope of both
home list and the picker is Active Homes; --all (and the picker's existing all toggle) include Terminal-only Homes. Active-ness is determined by a read-only manifest scan with no reconciliation, so listing/browsing never mutates state.
- New
home command group (mirroring skills): home list [--all] [--json] and home forget <path>. home list reuses the existing emitSuccess/list-result envelope; rows show path, derived active/total counts, and last-seen; sorted newest-first.
gc becomes cross-Home by default: with no explicit Home selection it sweeps every registered Home (collect Collectable Session directories in each existing Home, reconcile within the local boundary, deregister gone/emptied Homes). With --home or an explicit AGENT_TTY_HOME it scopes to that single Home as today. This requires the command context to expose whether a Home was explicitly selected vs defaulted. gc never deletes a Home directory.
- Local-only reconciliation: reconcile/kill use local PIDs and manifests carry no machine identity, so a Home shared across machines is unsupported (ADR 0008). No machine-id guard in this PRD.
- Session Dashboard becomes Home-aware:
home lifts into dashboard state; the Focus state machine gains a home pane; selecting a Home re-runs the already-Home-parameterized session-listing. The dashboard opens on the launched/resolved Home (additive picker); full reconcile happens only on Home entry, never while browsing. --home/AGENT_TTY_HOME sets only the initial Home and does not restrict navigation. Picker logic lives in pure functions; the Ink component stays thin.
Testing Decisions
- Good tests assert external behavior, not implementation: given a registry file + Home directories on disk (or an injected store), assert what
home list/gc/the scope functions produce and persist, not how.
- HomeRegistry store —
test/unit/storage/homeRegistry.test.ts (prior art: sessionPaths.test.ts, artifactStorage.test.ts): register+dedupe (same path refreshes lastSeenAt), prune-on-read omits gone Homes, atomic write survives concurrent upserts, newest-first ordering, XDG_STATE_HOME honored with ~/.local/state fallback, location independent of AGENT_TTY_HOME.
- Scope/picker pure functions —
test/unit/dashboard/ (prior art: sessionScope.test.ts, sessionListLayout.test.ts): Active Home = ≥1 Active Session; default vs all membership; ordering; and a guard test injecting a reconcile spy to prove the scope scan performs zero reconciliation.
home list / home forget commands — test/unit/commands/ (prior art: dashboard.test.ts, skills-list.test.ts, gc.test.ts): --json envelope shape + human lines; forget removes an entry without touching disk; empty-registry behavior.
- Cross-Home gc — extend
test/unit/commands/gc.test.ts: default sweep prunes dead/emptied Homes; --home scopes to one; deregister-on-empty; gc never rms a Home directory (assert the Home dir still exists, only session dirs + registry entry removed); --dry-run/--stale-only/--older-than still honored per-Home within the sweep.
- Auto-registration on create — extend
test/unit/commands/create.test.ts: creating a Session upserts the resolved Home (injected store / temp state dir).
- Result-schema sweep —
gc's result shape changes and a new home list result is added, so grep the suite for stale .result assertions (typecheck + sandbox unit runs miss these; CI integration/e2e catch them) and cover new/changed envelopes in golden-envelopes.test.ts.
- The Ink
app.tsx gets no new dedicated unit test (consistent with today); its behavior is exercised through the pure scope/layout functions.
Out of Scope
- Cross-machine / remote Home aggregation — the registry is per-machine and local-only; remote Event Log Follow transport and shared/NFS Homes are out of scope (ADR 0008).
- Machine-identity guard on reconcile — deferred follow-up; this PRD relies on the per-machine boundary instead.
- Deleting Home directories — gc deregisters and collects Sessions but never removes a Home directory.
- User-assigned Home names/labels — entries are
{path, lastSeenAt} only.
- Cached Session counts in the registry — always derived live.
- Making the dashboard agent-aware (grouping/filtering by client identity) — unchanged; still gated on a separate domain extension per
CONTEXT.md.
- A gc daemon / automatic periodic sweep — gc stays user/automation-invoked; "periodic" means the user or their harness schedules it.
Further Notes
- Domain terms and relationships are already captured in
CONTEXT.md (Home, Home Registry, Active Home; the Session Dashboard redefined as Home-aware) and the rationale in docs/adr/0008-home-registry-advisory-per-machine.md — both on branch feat/home-registry.
- Backward-incompatible CLI change: plain
gc changes from "collect the default Home" to "sweep all registered Homes." Call this out in the changelog; automation relying on the old behavior must pass --home.
- ADR numbering:
0008 currently collides with the unpushed feat/workflow-hero-demo branch's 0008-carry-two-parallel-hero-demos; whichever merges second must renumber.
- The registry is advisory: losing
homes.json is non-fatal (Homes re-register on next create; only Terminal-only Homes vanish from the picker until reused).
Problem Statement
I run
agent-ttyacross many Homes — the default~/.agent-tty, throwawaymktemp -ddirectories for isolated runs, and per-projectAGENT_TTY_HOMEoverrides. But agent-tty can't see across them.listand the Session Dashboard only ever show the single Home resolved for that invocation, and there's no way to enumerate Homes at all (the only cross-Home artifact is a one-waysha256(home)hash under/tmp). So I can't discover which Homes exist, I have to remember and re-type every--homepath, and a Home I've used effectively disappears from view the moment I move on — even though its Event Logs are still inspectable.Solution
agent-tty remembers every Home that has hosted a Session in a per-machine Home Registry, and the Session Dashboard gains a Home picker so I can switch Homes and inspect each one's Sessions without leaving the dashboard. A new
home listgives the same view on the CLI, comparable tolistfor Sessions. The registry stays tidy on its own: dead Homes (deleted temp dirs, emptied Homes) are hidden from listings immediately and pruned by garbage collection, which now sweeps across all known Homes by default. Everything is read-only observation; browsing never changes a Session.User Stories
--homepaths myself.home listto show the Homes that currently have Active Sessions, so that I can see where my live work is.home list --allto also show Homes whose Sessions are all Terminal, so that I can find a finished project's Home to replay its Sessions.home list --json, so that I can script over the set of Homes.home listrow to show the Home path, a count of Active/total Sessions, and when it was last used, so that I can tell Homes apart at a glance.home listsorted by most-recently-used first, so that the Home I care about is at the top.active/allscope for Sessions.mktemp -dHomes, I don't want the registry to balloon with dead entries, so that the picker andhome liststay useful.gcto sweep all registered Homes by default and prune dead ones, so that one periodic command keeps the registry from growing without bound.gc --home <path>to scope collection to a single Home, so that I can still target one Home when I need to.home forget <path>to remove a Home from the registry without touching disk, so that I can clear a Home from the picker immediately.home list --json, so that I can discover which Homes are available and choose one to inspect.${XDG_STATE_HOME:-~/.local/state}/agent-tty/homes.json, so that it sits in the conventional machine-local state location and is easy to find or clear.home listto show the same Homes under the same scope, so that the two surfaces never disagree.homecommands and the picker to use the term "Home" consistently and never conflate it with my OS$HOME, so that the vocabulary is unambiguous.list/the dashboard do today for a single Home, so that entry reflects the truth.creates to update the registry safely without corrupting it, so that the file survives heavy use.home listto never mutate Session state across Homes just to display counts, so that listing stays cheap and safe.Implementation Decisions
HomeRegistrystore module undersrc/storage/(sibling tosessionPaths.ts/home.ts): a per-machine, advisory JSON index. The Home directories remain the source of truth; the registry is reconciled against them, never the reverse. (Seedocs/adr/0008-home-registry-advisory-per-machine.md.)${XDG_STATE_HOME:-~/.local/state}/agent-tty/homes.json, derived from the OS user and independent ofAGENT_TTY_HOME— the registry spans Homes, so it cannot live inside one. Resolved by a pure function, mirroringresolveHome.{ path, lastSeenAt }only. No cached Session counts or statuses; those are derived live by scanning the Home at read time.GcDependencies):upsert(path)(register / refreshlastSeenAt),list()(applies prune-on-read),forget(path), and a prune/sweep operation. Writes are atomic (temp file + rename) to tolerate concurrentcreates.createpath upserts the resolved Home after a Session is created.list()stats each entry and omits Homes whose directory orsessions/tree is gone; it does not rewrite the file on every read (durable compaction is gc's job).home listand the picker is Active Homes;--all(and the picker's existingalltoggle) include Terminal-only Homes. Active-ness is determined by a read-only manifest scan with no reconciliation, so listing/browsing never mutates state.homecommand group (mirroringskills):home list [--all] [--json]andhome forget <path>.home listreuses the existingemitSuccess/list-result envelope; rows show path, derived active/total counts, and last-seen; sorted newest-first.gcbecomes cross-Home by default: with no explicit Home selection it sweeps every registered Home (collect Collectable Session directories in each existing Home, reconcile within the local boundary, deregister gone/emptied Homes). With--homeor an explicitAGENT_TTY_HOMEit scopes to that single Home as today. This requires the command context to expose whether a Home was explicitly selected vs defaulted. gc never deletes a Home directory.homelifts into dashboard state; theFocusstate machine gains ahomepane; selecting a Home re-runs the already-Home-parameterized session-listing. The dashboard opens on the launched/resolved Home (additive picker); full reconcile happens only on Home entry, never while browsing.--home/AGENT_TTY_HOMEsets only the initial Home and does not restrict navigation. Picker logic lives in pure functions; the Ink component stays thin.Testing Decisions
home list/gc/the scope functions produce and persist, not how.test/unit/storage/homeRegistry.test.ts(prior art:sessionPaths.test.ts,artifactStorage.test.ts): register+dedupe (same path refresheslastSeenAt), prune-on-read omits gone Homes, atomic write survives concurrent upserts, newest-first ordering,XDG_STATE_HOMEhonored with~/.local/statefallback, location independent ofAGENT_TTY_HOME.test/unit/dashboard/(prior art:sessionScope.test.ts,sessionListLayout.test.ts): Active Home = ≥1 Active Session; default vsallmembership; ordering; and a guard test injecting a reconcile spy to prove the scope scan performs zero reconciliation.home list/home forgetcommands —test/unit/commands/(prior art:dashboard.test.ts,skills-list.test.ts,gc.test.ts):--jsonenvelope shape + human lines;forgetremoves an entry without touching disk; empty-registry behavior.test/unit/commands/gc.test.ts: default sweep prunes dead/emptied Homes;--homescopes to one; deregister-on-empty; gc neverrms a Home directory (assert the Home dir still exists, only session dirs + registry entry removed);--dry-run/--stale-only/--older-thanstill honored per-Home within the sweep.test/unit/commands/create.test.ts: creating a Session upserts the resolved Home (injected store / temp state dir).gc's result shape changes and a newhome listresult is added, so grep the suite for stale.resultassertions (typecheck + sandbox unit runs miss these; CI integration/e2e catch them) and cover new/changed envelopes ingolden-envelopes.test.ts.app.tsxgets no new dedicated unit test (consistent with today); its behavior is exercised through the pure scope/layout functions.Out of Scope
{path, lastSeenAt}only.CONTEXT.md.Further Notes
CONTEXT.md(Home, Home Registry, Active Home; the Session Dashboard redefined as Home-aware) and the rationale indocs/adr/0008-home-registry-advisory-per-machine.md— both on branchfeat/home-registry.gcchanges from "collect the default Home" to "sweep all registered Homes." Call this out in the changelog; automation relying on the old behavior must pass--home.0008currently collides with the unpushedfeat/workflow-hero-demobranch's0008-carry-two-parallel-hero-demos; whichever merges second must renumber.homes.jsonis non-fatal (Homes re-register on nextcreate; only Terminal-only Homes vanish from the picker until reused).