Skip to content

GET /v1/nodes is unbounded: Relay default fetches 6,298 rows / 3.06 MB to show four live nodes #422

Description

@khaliqgant

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions