Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: include agent metadata in workspace list responses - #27934

Merged
Emyrk merged 7 commits into
mainfrom
steven/workspaces-include-agent-metadata
Aug 10, 2026
Merged

feat: include agent metadata in workspace list responses#27934
Emyrk merged 7 commits into
mainfrom
steven/workspaces-include-agent-metadata

Conversation

@Emyrk

@Emyrk Emyrk commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes #27933. Related: #27897 (single-agent GET).

Agent metadata is only readable via a per-agent watch stream, so reading it across N workspaces costs N+1 requests. This adds a batch read to the list endpoint:

GET /api/v2/workspaces?q=param:"pool=demo" include_agent_metadata:task_status
  • New include_agent_metadata search key, repeatable and key-scoped. It expands the response, it does not filter workspaces.
  • GetWorkspaces aggregates the requested keys as JSON behind a CASE: without opt-in the response is unchanged and the subquery never runs. Runs only for the returned page, inside the same authorized query.
  • Agents in the response gain metadata ([]codersdk.WorkspaceAgentMetadata, omitempty), mapped by the workspace_agent_id each element carries. The collection script is omitted; it can be long.
  • codersdk.WorkspaceFilter gains IncludeAgentMetadata []string.
  • No wildcard, no schema change, no migration.

Authored by Coder Agents on behalf of @Emyrk.

Agent metadata could only be read by opening a watch stream per agent,
so a consumer inspecting N workspaces made N+1 requests per pass for
state coderd already stores. The workspaces list query now aggregates
the requested keys as JSON when the new include_agent_metadata search
key opts in, and the response attaches them to each agent as
metadata. The expansion is key-scoped and opt-in because values can be
64KiB each; without it the response is unchanged and the aggregate
subquery never runs.

Closes #27933
Comment thread coderd/workspaces.go Outdated
…data

The script is the collection command, not collected state; it can be
long and list consumers want values. The description's script field is
always empty on the workspaces list endpoint.
@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

PLAT-453

Emyrk added 4 commits August 6, 2026 22:05
…a aggregate

The latest_build lateral already resolves the provisioner job;
propagate it through the CTE chain as latest_build_provisioner_job_id
so the agent_metadata aggregate joins resources by job ID instead of
re-deriving the latest build with a max(build_number) lookup.
…base package

sqlc column overrides require tablename.colname against a real
relation, and agent_metadata is a query expression, so a types.go
Scanner cannot be wired to the generated row. Instead the row gains
ParseAgentMetadata, keeping the JSON handling in the database package
and the handler free of unmarshaling.
… ACL columns

AgentMetadataAggregate moves to types.go with Scan/Value, matching how
ConvertWorkspaceRows handles user_acl and group_acl; the handler scans
the row's raw JSON into the typed aggregate.
@Emyrk
Emyrk marked this pull request as ready for review August 6, 2026 22:31
@Emyrk
Emyrk requested a review from DanielleMaywood as a code owner August 6, 2026 22:31
Comment thread coderd/database/queries/workspaces.sql Outdated
Comment thread coderd/workspaces.go Outdated
@jscottmiller

Copy link
Copy Markdown
Contributor

Some small issues found with meat+agents. One potential timezone serialization issue depending on postgres defaults - probably worth fixing but I'll leave it to you. Agent note below.

BLOCKER (Priya) — collected_at produces unparseable JSON on non-UTC Postgres sessions. jsonb_build_object renders timestamptz in the session TimeZone; collected_at defaults to year 1 and is never set by InsertWorkspaceAgentMetadata, so a named non-UTC zone yields an LMT offset with seconds (0001-12-31T19:03:58-04:56:02 BC), which Go refuses. That 500s the entire list page. Coder never pins TimeZone in the DSN and initdb inherits the host zone. The new test always sets collected_at, so it structurally cannot catch this. Priya reproduced it end to end. Fix: to_char(... AT TIME ZONE 'UTC', ...) in SQL. Dana independently flagged the year-1 timestamp as a shape problem (age ≈ 6.4e10), which corroborates the underlying condition even though only Priya traced it to a 500.

@BobbyHo BobbyHo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice change — batching this instead of making N per-workspace watch-stream calls is a great optimization. The changes LGTM overall. I just have one question related to behavior at larger-scale deployments, but it’s non-blocking.

-- workspace_agent_id so multi-agent workspaces can map values onto
-- the right agent. Keys match case-insensitively because search
-- queries are lowercased.
CASE WHEN cardinality(@include_agent_metadata :: text[]) > 0 THEN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question (not a blocker):

if I understand correctly, this:

LIMIT
    CASE
        WHEN @limit_::integer > 0 THEN
            @limit_
    END

means no LIMIT is applied when limit_ <= 0 (the CASE evaluates to NULL), which matches ParsePagination's 0 = no limit convention used by other list endpoints.

agent_metadata's jsonb_agg also seems to be the first subquery here whose output size can grow with the amount of matching data, rather than collapsing to a fixed-size value like the other filters.

Do we feel comfortable extending the existing unbounded-query behavior with a per-row payload that can also grow unbounded? Or would it be worth running an EXPLAIN ANALYZE benchmark against a large, multi-thousand-workspace fixture with include_agent_metadata + limit=0 first, just to see whether we need a guardrail for larger deployments?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your reading of the LIMIT is right. On growth, two bounds apply:

  • Per-row payload is capped: the agent API truncates metadata values to 2048 bytes (maxValueLen in coderd/agentapi/metadata.go, errors likewise), and the expansion only aggregates the explicitly requested keys, so each agent contributes ~(keys × ~2.5KB) worst case, not "whatever the template defines".
  • Per-row cost is one PK probe: the aggregate runs post-pagination against workspace_agent_metadata's primary key (workspace_agent_id, key), once per returned row. The existing latest_build lateral is heavier and runs per candidate row, pre-limit.

So with limit=0 the response grows with row count, but that's already this endpoint's dominant behavior: after the SQL returns, the handler fetches builds/resources/agents/apps/scripts for every returned workspace, each of which outweighs the metadata increment. The expansion doesn't introduce a new unbounded dimension, it scales the same way the rest of the response does, with a smaller constant.

Happy to run an EXPLAIN ANALYZE against a multi-thousand-workspace fixture before this merges if you'd like the numbers on record, or to gate the expansion on an explicit limit, though no other response expansion on this endpoint does that today.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ That was AI.

I don't like the unbounded workspace query limit. We talked about fixing this in our standup. I can put some pressure to limit the limit sooner rather than later

…ent the search key

Review feedback: the stored key was lowercased but the requested array
was only lowercase by virtue of the search parser, which is surprising
for any other caller of the query; normalize both sides in SQL. The
list endpoint's q parameter doc now names include_agent_metadata.
@github-actions

Copy link
Copy Markdown
Contributor

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

@Emyrk
Emyrk merged commit 9a57dfa into main Aug 10, 2026
32 checks passed
@Emyrk
Emyrk deleted the steven/workspaces-include-agent-metadata branch August 10, 2026 13:13
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include agent metadata in workspace list responses

3 participants