Summary
GET /v1/nodes returns every historical node row in a workspace with no status filter, limit, cursor, or server-side filtering. Relay's default agent-relay fleet nodes call consequently downloads and deserializes the full history before hiding offline/non-fleet rows locally.
This is now a material orchestration/control-plane cost, not just cosmetic history.
Exact production measurement (2026-09-10)
On the chief campaign workspace:
- total rows returned: 6,298
- online rows: 10 at first measurement; only 4 available Fleet nodes in the later default view
- offline rows: 6,288
- historical
implicit,direct rows: 3,437
- serialized
fleet nodes --all output: 3,059,182 bytes (~1,026,940 tokens through the supervising harness)
- stale
active_agents values summed across returned history: 2,408
- default
fleet nodes still reported 6294 offline or non-fleet records hidden, proving it fetched history before filtering
- calls currently take ~2.3–2.7 seconds locally even though the useful default result is four nodes
This was a read-only measurement against a real long-lived multi-node qualification workspace; no synthetic rows were added.
Root cause
Current origin/main:
packages/engine/src/engine/node.ts:1997-2006 runs db.select().from(nodes).where(workspaceId) for the entire workspace, then applies even name and capability filters in JavaScript.
packages/engine/src/routes/node.ts:194-208 exposes only capability and name; no status, role/history selection, limit, or cursor.
packages/sdk-typescript/src/relay.ts:976-980 can send only capability and name and expects the entire array.
- Relay
packages/cli/src/cli/commands/fleet.ts:121-137 calls that unbounded list, then locally splits live/history and hides history unless --all.
Node deletion now exists through relaycast-cloud#101 / relaycast#380, but it does not make an unbounded read safe and cannot be used as implicit broad retention cleanup.
Impact
- Every ordinary Fleet node listing pays the cost of thousands of dead rows.
- Agent prompts/tool transcripts can accidentally receive multi-megabyte output.
- The extra DB/serialization load plausibly amplifies current workspace contention, though this is distinct from Relay #1743's write-retry bug until measured.
--all has no bounded or paged operational form.
- Offline history can present stale
active_agents counters as if they were current.
Related: AgentWorkforce/relay#1689.
Acceptance criteria
- Push
name, capability, and a new liveness/status/history selector into the SQL query.
- Give Relay's default
fleet nodes / fleet agent list a server-filtered live-Fleet path so it never downloads the full history.
- Add a stable, bounded pagination contract for explicit history (
--all) without silently truncating; preserve compatibility for existing SDK callers or add an explicitly paged SDK surface.
- Define offline/history
active_agents semantics; do not present stale counters as authoritative live occupancy.
- Add a regression with thousands of historical rows proving the default query reads/returns only the requested live subset and explicit history is paged.
- Measure response bytes/query time before and after on a representative roster.
Summary
GET /v1/nodesreturns every historical node row in a workspace with no status filter, limit, cursor, or server-side filtering. Relay's defaultagent-relay fleet nodescall consequently downloads and deserializes the full history before hiding offline/non-fleet rows locally.This is now a material orchestration/control-plane cost, not just cosmetic history.
Exact production measurement (2026-09-10)
On the chief campaign workspace:
implicit,directrows: 3,437fleet nodes --alloutput: 3,059,182 bytes (~1,026,940 tokens through the supervising harness)active_agentsvalues summed across returned history: 2,408fleet nodesstill reported6294 offline or non-fleet records hidden, proving it fetched history before filteringThis was a read-only measurement against a real long-lived multi-node qualification workspace; no synthetic rows were added.
Root cause
Current
origin/main:packages/engine/src/engine/node.ts:1997-2006runsdb.select().from(nodes).where(workspaceId)for the entire workspace, then applies evennameandcapabilityfilters in JavaScript.packages/engine/src/routes/node.ts:194-208exposes onlycapabilityandname; nostatus, role/history selection, limit, or cursor.packages/sdk-typescript/src/relay.ts:976-980can send onlycapabilityandnameand expects the entire array.packages/cli/src/cli/commands/fleet.ts:121-137calls that unbounded list, then locally splits live/history and hides history unless--all.Node deletion now exists through relaycast-cloud#101 / relaycast#380, but it does not make an unbounded read safe and cannot be used as implicit broad retention cleanup.
Impact
--allhas no bounded or paged operational form.active_agentscounters as if they were current.Related: AgentWorkforce/relay#1689.
Acceptance criteria
name,capability, and a new liveness/status/history selector into the SQL query.fleet nodes/fleet agent lista server-filtered live-Fleet path so it never downloads the full history.--all) without silently truncating; preserve compatibility for existing SDK callers or add an explicitly paged SDK surface.active_agentssemantics; do not present stale counters as authoritative live occupancy.