Skip to content

Add attribute discovery and filtering to world.analytics#2903

Merged
karthikscale3 merged 7 commits into
mainfrom
karthik/analytics-attribute-filters
Jul 13, 2026
Merged

Add attribute discovery and filtering to world.analytics#2903
karthikscale3 merged 7 commits into
mainfrom
karthik/analytics-attribute-filters

Conversation

@karthikscale3

@karthikscale3 karthikscale3 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the optional world.analytics namespace with attribute APIs:

  • analytics.attributes.list(params?) — lists the distinct attribute keys observed on runs in the window, with runCount, firstSeenAt, lastSeenAt. Optional workflowName filter and startTime/endTime window; alphabetical keyset pagination. Hits GET /v2/analytics/attributes.
  • analytics.runs.list({ attributes: { key: value } }) — restricts the runs listing to runs whose latest attribute snapshot matches every provided pair (AND semantics, up to 8 pairs). Serialized as a single JSON-encoded attributes query param because attribute keys/values are arbitrary user strings that may contain = or ,. Reserved $-prefixed keys are allowed in read filters (they're rejected only on user writes).

Docs

The world.analytics namespace was previously undocumented. This PR adds a World SDK reference page (docs/.../world/analytics.mdx) covering the whole namespace — runs, attributes, steps/events/hooks/waits, lookback windows, and pageInfo — wires it into the World SDK index, and replaces the stale "in the future you'll be able to search and filter runs by attributes" line in the attributes guide with a filtering example. The docs-typecheck ambient world global gains the analytics namespace so snippets typecheck.

Notes

  • analytics remains optional on World and only world-vercel implements it, so no changes to local/postgres worlds.
  • New AnalyticsAttributeKeySchema exported from @workflow/world alongside the existing analytics schemas.
  • Plan-window semantics carry over unchanged: explicit windows older than the plan's observability lookback fail with observability-upgrade-required.
  • A listValues (distinct values per key) API was considered and dropped — not a derived requirement.

Testing

  • New packages/world-vercel/src/analytics.test.ts covering the JSON filter serialization (incl. empty-object omission) and keys-listing query params.
  • pnpm --filter @workflow/world-vercel test, packages/world vitest, full pnpm build, pnpm test:docs (1020 passed), and the docs link lint all green.

🤖 Generated with Claude Code

@karthikscale3
karthikscale3 requested review from a team and ijjk as code owners July 13, 2026 18:09
@changeset-bot

changeset-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 716b6ee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@workflow/world Minor
@workflow/world-vercel Minor
@workflow/cli Patch
@workflow/core Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/world-testing Patch
@workflow/builders Patch
workflow Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/nuxt Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jul 13, 2026 10:10pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 13, 2026 10:10pm
example-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-express-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-nitro-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 13, 2026 10:10pm
workflow-swc-playground Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workflow-tarballs Ready Ready Preview, Comment Jul 13, 2026 10:10pm
workflow-web Ready Ready Preview, Comment Jul 13, 2026 10:10pm

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1453 0 230 1683
✅ 💻 Local Development 1617 0 219 1836
✅ 📦 Local Production 1617 0 219 1836
✅ 🐘 Local Postgres 1617 0 219 1836
✅ 🪟 Windows 153 0 0 153
✅ 📋 Other 894 0 177 1071
✅ vercel-multi-region 0 0 0 0
Total 7351 0 1064 8415

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 126 0 27
✅ example 126 0 27
✅ express 126 0 27
✅ fastify 126 0 27
✅ hono 126 0 27
✅ nextjs-turbopack 150 0 3
✅ nextjs-webpack 150 0 3
✅ nitro 126 0 27
✅ nuxt 126 0 27
✅ sveltekit 145 0 8
✅ vite 126 0 27
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 128 0 25
✅ express-stable 128 0 25
✅ fastify-stable 128 0 25
✅ hono-stable 128 0 25
✅ nextjs-turbopack-canary 134 0 19
✅ nextjs-turbopack-stable 153 0 0
✅ nextjs-webpack-canary 134 0 19
✅ nextjs-webpack-stable 153 0 0
✅ nitro-stable 128 0 25
✅ nuxt-stable 128 0 25
✅ sveltekit-stable 147 0 6
✅ vite-stable 128 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 128 0 25
✅ express-stable 128 0 25
✅ fastify-stable 128 0 25
✅ hono-stable 128 0 25
✅ nextjs-turbopack-canary 134 0 19
✅ nextjs-turbopack-stable 153 0 0
✅ nextjs-webpack-canary 134 0 19
✅ nextjs-webpack-stable 153 0 0
✅ nitro-stable 128 0 25
✅ nuxt-stable 128 0 25
✅ sveltekit-stable 147 0 6
✅ vite-stable 128 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 128 0 25
✅ express-stable 128 0 25
✅ fastify-stable 128 0 25
✅ hono-stable 128 0 25
✅ nextjs-turbopack-canary 134 0 19
✅ nextjs-turbopack-stable 153 0 0
✅ nextjs-webpack-canary 134 0 19
✅ nextjs-webpack-stable 153 0 0
✅ nitro-stable 128 0 25
✅ nuxt-stable 128 0 25
✅ sveltekit-stable 147 0 6
✅ vite-stable 128 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 153 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 128 0 25
✅ e2e-local-dev-tanstack-start- 128 0 25
✅ e2e-local-postgres-nest-stable 128 0 25
✅ e2e-local-postgres-tanstack-start- 128 0 25
✅ e2e-local-prod-nest-stable 128 0 25
✅ e2e-local-prod-tanstack-start- 128 0 25
✅ e2e-vercel-prod-tanstack-start 126 0 27
✅ vercel-multi-region
App Passed Failed Skipped
✅ nextjs-turbopack 0 0 0

📋 View full workflow run

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 716b6ee · Mon, 13 Jul 2026 22:25:25 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1114 (-5.7%) 1587 🔴 1637 🔴 2040 🔴 30
TTFS hook + stream 1579 (+9.1%) 1891 🔴 1938 🔴 2038 🔴 30
STSO 1020 steps (1-20) 310 (+12%) 380 🔴 418 🔴 476 🔴 19
STSO 1020 steps (101-120) 404 (-7.4%) 424 🔴 513 🔴 548 🔴 19
STSO 1020 steps (1001-1020) 873 (+2.6%) 929 🔴 972 🔴 981 🔴 19
WO stream 1114 (-5.7%) 1587 1637 2040 30
WO hook + stream 1579 (+9.1%) 1891 1938 2038 30
SL stream 4654 (+3.7%) 5706 🔴 5829 🔴 6461 🔴 30
SL hook + stream 4616 (-3.6%) 5370 🔴 5495 🔴 5681 🔴 30
📜 Previous results (5)

1f47722

Mon, 13 Jul 2026 20:41:46 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1344 (+25%) 1649 🔴 1684 🔴 2186 🔴 30
TTFS hook + stream 1575 (+14%) 1913 🔴 2098 🔴 2303 🔴 30
STSO 1020 steps (1-20) 311 (+19%) 313 🔴 507 🔴 905 🔴 19
STSO 1020 steps (101-120) 446 (+12%) 478 🔴 538 🔴 593 🔴 19
STSO 1020 steps (1001-1020) 912 (-3.8%) 951 🔴 1029 🔴 1233 🔴 19
WO stream 1344 (+25%) 1649 1684 2186 30
WO hook + stream 1575 (+14%) 1913 2098 2303 30
SL stream 4772 (-2.1%) 5093 🔴 5800 🔴 7085 🔴 30
SL hook + stream 4894 (-0.8%) 5558 🔴 5633 🔴 5918 🔴 30

fe54d70

Mon, 13 Jul 2026 20:18:59 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1225 (+14%) 1752 🔴 1851 🔴 1978 🔴 30
TTFS hook + stream 1636 (+18%) 2102 🔴 2223 🔴 2453 🔴 30
STSO 1020 steps (1-20) 309 (+18%) 348 🔴 428 🔴 639 🔴 19
STSO 1020 steps (101-120) 420 (+5.7%) 484 🔴 506 🔴 512 🔴 19
STSO 1020 steps (1001-1020) 844 (-11%) 893 🔴 967 🔴 994 🔴 19
WO stream 1225 (+14%) 1752 1851 1978 30
WO hook + stream 1636 (+18%) 2102 2223 2453 30
SL stream 4410 (-9.5%) 5845 🔴 5949 🔴 5977 🔴 30
SL hook + stream 4824 (-2.2%) 5536 🔴 5705 🔴 5863 🔴 30

e19f7b3

Mon, 13 Jul 2026 19:45:11 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1224 (+14%) 1703 🔴 1778 🔴 1881 🔴 30
TTFS hook + stream 1591 (+15%) 1960 🔴 2058 🔴 2185 🔴 30
STSO 1020 steps (1-20) 284 (+8.7%) 315 🔴 453 🔴 480 🔴 19
STSO 1020 steps (101-120) 438 (+10%) 489 🔴 540 🔴 554 🔴 19
STSO 1020 steps (1001-1020) 858 (-9.5%) 895 🔴 928 🔴 1331 🔴 19
WO stream 1224 (+14%) 1703 1778 1881 30
WO hook + stream 1591 (+15%) 1960 2058 2185 30
SL stream 4275 (-12%) 5743 🔴 5824 🔴 6248 🔴 30
SL hook + stream 5094 (+3.3%) 5743 🔴 5891 🔴 6043 🔴 30

369fe23

Mon, 13 Jul 2026 19:22:38 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1247 (+16%) 1607 🔴 1767 🔴 1887 🔴 30
TTFS hook + stream 1608 (+16%) 1982 🔴 2128 🔴 2300 🔴 30
STSO 1020 steps (1-20) 282 (+7.9%) 304 🔴 354 🔴 518 🔴 19
STSO 1020 steps (101-120) 449 (+13%) 494 🔴 556 🔴 656 🔴 19
STSO 1020 steps (1001-1020) 1026 (+8.3%) 1087 🔴 1327 🔴 1355 🔴 19
WO stream 1247 (+16%) 1607 1767 1887 30
WO hook + stream 1608 (+16%) 1982 2128 2300 30
SL stream 4654 (-4.5%) 5515 🔴 5684 🔴 5760 🔴 30
SL hook + stream 4985 (+1.1%) 5546 🔴 5757 🔴 6887 🔴 30

9d11b05

Mon, 13 Jul 2026 18:39:32 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1166 (+8.7%) 1589 🔴 1648 🔴 1767 🔴 30
TTFS hook + stream 1644 (+19%) 1925 🔴 2010 🔴 2051 🔴 30
STSO 1020 steps (1-20) 294 (+13%) 311 🔴 487 🔴 626 🔴 19
STSO 1020 steps (101-120) 421 (+6.1%) 427 🔴 573 🔴 711 🔴 19
STSO 1020 steps (1001-1020) 872 (-8.0%) 930 🔴 1010 🔴 1242 🔴 19
WO stream 1166 (+8.7%) 1589 1648 1767 30
WO hook + stream 1644 (+19%) 1925 2010 2051 30
SL stream 4138 (-15%) 4989 🔴 5652 🔴 5745 🔴 30
SL hook + stream 4779 (-3.1%) 5219 🔴 5565 🔴 5725 🔴 30

Avg deltas compare against the most recent benchmark run on main at the time of this run.

Metrics — TTFS: time to first step body execution · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (time outside step bodies, client start → last step body exit) · SL: stream latency (first chunk write → visible to the reader)

Scenarios — stream: one step that streams chunks back to the client; no hooks, so the run stays in turbo mode · hook + stream: registers a hook before the same streaming step, which exits turbo mode · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges

🟢/🔴 mark percentiles within/above target. Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · STSO (1-20) 20/30/60 · STSO (101-120) 30/45/90 · STSO (1001-1020) 40/60/120

TTFS/WO compare client vs deployment clocks and SL compares the step runner’s clock vs the client’s (NTP-synced in CI). WO ends at the last step body exit, the closest observable proxy for the final step-completion request.

@karthikscale3
karthikscale3 marked this pull request as draft July 13, 2026 18:11
Not a derived requirement: the agent-runs UI filters by known constant
values and reads per-run values via batch attribute fetches; it never
enumerates distinct values for a key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
const world = await getWorld();

// Which attribute keys exist, and on how many runs?
const keys = await world.analytics.attributes.list();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

World.analytics is optional, but this snippet dereferences it without the runtime check claimed by the @skip-typecheck annotation. It will throw in local/Postgres/custom Worlds where analytics is absent. Please feature-detect here (as the new Analytics reference page does) before calling attributes.list() or runs.list().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e19f7b3. The snippet now feature-detects: if (!world.analytics) { throw new Error(...) } before any analytics call, and the prose above it states the namespace is optional and absent on local/Postgres/custom Worlds. Since the guard makes the block genuinely type-safe, I also removed the @skip-typecheck annotation — the block is now typechecked by test:docs (skipped count dropped by one, all green).

The snippet dereferenced the optional analytics namespace without the
runtime check its skip-typecheck annotation claimed, and would throw on
local/Postgres/custom Worlds. Guard it and drop the annotation — the
block is now genuinely typechecked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
karthikscale3 and others added 2 commits July 13, 2026 12:59
The Behavior list still claimed a query API was only planned, directly
contradicting the Searching and filtering section above it. State what
is implemented: attributes are readable on run objects everywhere, and
key discovery / key=value run filtering are available through the
optional Analytics API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Analytics page sits under workflow/runtime > World SDK, but neither
the API reference index card, the workflow/runtime World SDK card, nor
the World SDK overview mentioned analytics — making the new reference
effectively undiscoverable from /docs/api-reference. Mention it at each
level of the path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@TooTallNate TooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the API surface, wire encoding, schemas, and docs; ran the suites and the docs typecheck. Approving, with one small ask below.

What I verified:

  • Wire encoding: the JSON-encoded attributes param is the right call (keys/values are arbitrary user strings that can contain =/,), the empty-object omission is handled and tested, and the both-or-neither window contract matches the existing runs.list convention. The new AnalyticsAttributeKeySchema coercions (z.coerce.number/z.coerce.date) match the backend's stringly-typed row output.
  • Semantics documented honestly: latest-write-wins matching (a run whose attribute moved v1v2 no longer matches v1) and the reserved-$-keys-readable-but-not-writable asymmetry are both spelled out in the interface docs and the guide, and both match the backend behavior.
  • Docs: the new world/analytics.mdx reference page is accurate against the implementation (8-pair cap, return shapes, plan-window/pageInfo semantics), and it closes a real gap — the namespace was previously undocumented while the stale "in the future…" line sat in the attributes guide. Docs typecheck passes (1021) with the ambient world global gaining the namespace.
  • Tests: world-vercel 238/238, world 73/73 locally; CI fully green.

One ask before merge: bump the changeset to minor. This adds new public API surface (analytics.attributes.list(), the attributes filter param, a new exported schema/type) — that's a feature, not a fix, and per the repo's changeset conventions the bump type is what carries semantic meaning through to any future backport. Trivial edit.

One note, no action needed: the Analytics interface gains attributes as a required property, which is a compile-time break for any out-of-tree implementer of the namespace. In-tree only world-vercel implements it and analytics stays optional on World, so this is fine on the beta line — just flagging that once the interface is considered stable, additions like this should probably land optional-first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for f2be954 (AI decision).

This commit extends the world.analytics namespace, which does not exist on stable at all — packages/world/src/analytics.ts, packages/world-vercel/src/analytics.ts, and the entire analytics concept are absent there (zero analytics references in the world packages). The docs additions likewise reference an analytics reference page and the v5 observability/attributes.mdx guide that don't exist on stable, so the change builds entirely on main-only APIs.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

f2be954bb7fee078bc4b78118edaa157130fa362

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants