Problem
afx status renders the Architects section with rich runtime metadata per row (name (pid=<n> port=<n> terminal=<uuid>)) but the Builders section omits both pid and terminalId columns. The asymmetry isn't an oversight — it's a side-effect of Spec 1057's owner-aware builders table sourcing from state.db instead of Tower's live terminal list — but it costs the diagnostic workflow during builder recovery / debugging.
The gap is felt the moment something goes wrong with a builder: identifying which OS process to inspect, signal, or kill currently requires dropping to sqlite3 ~/.agent-farm/global.db (or ps -axo string-matching) to discover the PID by hand. Hit during the pir-832 recovery session this issue spun out of.
Source
packages/codev/src/agent-farm/commands/status.ts — the architect rendering reads from workspaceStatus.terminals (the Tower runtime view, which has term.pid / term.terminalId populated per Spec 786 Phase 5):
const pid = term.pid ? `pid=${term.pid}` : 'pid=?';
const port = term.port ? ` port=${term.port}` : '';
const termIdValue = term.terminalId ?? term.id;
const termId = ` terminal=${termIdValue}`;
logger.info(` ${chalk.cyan(name)} (${pid}${port}${termId})`);
The builder rendering immediately below it calls renderBuilders(builders, ownerFilter) with builders sourced from state.db.builders (Spec 1057, for the spawnedByArchitect column needed for owner-aware grouping). state.db.builders doesn't carry pid / terminalId — those are runtime fields Tower populates on its in-memory terminal_sessions view.
Proposed fix
Enrich each builder row in renderBuilders with a join against Tower's terminal list, additively. For each row from state.db, look up the matching terminal_sessions entry by role_id == builder-<id> and workspace_path == <workspace>, and append pid + terminalId as new columns.
Pseudocode:
const builderTerminals = workspaceStatus.terminals.filter(t => t.type === 'builder');
const tIndex = new Map(builderTerminals.map(t => [t.role_id ?? t.label, t]));
for (const b of builders) {
const t = tIndex.get(`builder-${b.id}`);
const pid = t?.pid ? `pid=${t.pid}` : '';
const termId = t?.terminalId ? `terminal=${t.terminalId}` : '';
// ... append to existing row format
}
The existing owner-aware grouping (Spec 1057's design point) is unchanged — the join just adds columns, doesn't change the row source or grouping axis.
Acceptance criteria
- The
afx status Builders section renders pid=<n> and terminal=<uuid> columns per row (when available from Tower's terminal list).
- Owner-aware grouping (
spawnedByArchitect column) is preserved unchanged.
- A builder row whose Tower terminal entry is missing (Tower not running, or terminal session row not yet replayed by reconcile) renders the row without
pid / terminal columns rather than pid=? — the empty case should be visually quiet, not noisy.
- The legacy Tower-not-running fallback path in
status.ts (which already explicitly says "PID/port not available") is untouched.
Out of scope
- Adding architect-style columns (port, name, etc.) for builders. Builders don't expose a port and their
role_id is already in the existing ID column.
- Changing the data-source ordering —
state.db stays the primary, Tower's terminal list is the secondary enrichment.
- Any change to architect rendering — already correct.
Protocol
AIR. Small mechanical wiring change with no architectural decision: one Map-build, one per-row lookup, additive column rendering. Test coverage: a unit test asserting that a builder row with a matching Tower terminal renders with pid=<n> and terminal=<uuid>, and a row without a matching terminal renders without those fields.
Related
- Spec 1057 — established the owner-aware Builders table sourced from state.db (the design this issue extends additively).
- Spec 786 Phase 5 — established the
TowerWorkspaceStatus rich-metadata extension that architect rendering already consumes.
Problem
afx statusrenders the Architects section with rich runtime metadata per row (name (pid=<n> port=<n> terminal=<uuid>)) but the Builders section omits bothpidandterminalIdcolumns. The asymmetry isn't an oversight — it's a side-effect of Spec 1057's owner-aware builders table sourcing fromstate.dbinstead of Tower's live terminal list — but it costs the diagnostic workflow during builder recovery / debugging.The gap is felt the moment something goes wrong with a builder: identifying which OS process to inspect, signal, or kill currently requires dropping to
sqlite3 ~/.agent-farm/global.db(orps -axostring-matching) to discover the PID by hand. Hit during the pir-832 recovery session this issue spun out of.Source
packages/codev/src/agent-farm/commands/status.ts— the architect rendering reads fromworkspaceStatus.terminals(the Tower runtime view, which hasterm.pid/term.terminalIdpopulated per Spec 786 Phase 5):The builder rendering immediately below it calls
renderBuilders(builders, ownerFilter)withbuilderssourced fromstate.db.builders(Spec 1057, for thespawnedByArchitectcolumn needed for owner-aware grouping).state.db.buildersdoesn't carrypid/terminalId— those are runtime fields Tower populates on its in-memoryterminal_sessionsview.Proposed fix
Enrich each builder row in
renderBuilderswith a join against Tower's terminal list, additively. For each row from state.db, look up the matchingterminal_sessionsentry byrole_id == builder-<id>andworkspace_path == <workspace>, and appendpid+terminalIdas new columns.Pseudocode:
The existing owner-aware grouping (Spec 1057's design point) is unchanged — the join just adds columns, doesn't change the row source or grouping axis.
Acceptance criteria
afx statusBuilders section renderspid=<n>andterminal=<uuid>columns per row (when available from Tower's terminal list).spawnedByArchitectcolumn) is preserved unchanged.pid/terminalcolumns rather thanpid=?— the empty case should be visually quiet, not noisy.status.ts(which already explicitly says "PID/port not available") is untouched.Out of scope
role_idis already in the existing ID column.state.dbstays the primary, Tower's terminal list is the secondary enrichment.Protocol
AIR. Small mechanical wiring change with no architectural decision: one Map-build, one per-row lookup, additive column rendering. Test coverage: a unit test asserting that a builder row with a matching Tower terminal renders with
pid=<n>andterminal=<uuid>, and a row without a matching terminal renders without those fields.Related
TowerWorkspaceStatusrich-metadata extension that architect rendering already consumes.