Repository navigation
perf(sdk): cache tools.list by catalog + policy revision - #50
Merged
Merged
Conversation
aryasaatvik
added a commit
that referenced
this pull request
Jun 21, 2026
## Why
Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.
## What
- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.
## Safety
- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).
## Gate
tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.
Follow-up: a reindex on the deployed instance warms the new unified
variant.
<!-- stack:links:start -->
### [Stack](https://github.com/kitlangton/stack)
1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
aryasaatvik
changed the base branch from
perf/precompile-tool-schema-typescript
to
dev
June 21, 2026 21:52
aryasaatvik
force-pushed
the
feat/tools-list-revision-cache
branch
from
June 21, 2026 21:52
e20d74b to
2a50b8b
Compare
tools.list was an uncached full `tool` scan + `tool_policy` read on every call. produceConnectionTools now records a per-connection toolset_revision (a hash of the connection's manifest (path, index_fingerprint) pairs) in plugin_storage at every catalog-mutation site (produce, both clear branches, connection-remove, integration-remove). tools.list derives a per-owner catalogGen from those rows and a policyGen from tool_policy, and caches the DB-derived, policy-filtered list keyed on both. Static tools are unioned live; the `query` substring filter is applied post-cache so distinct queries share one cached set. The cache stays warm through token rotation (no produceConnectionTools call) and invalidates exactly when a connection's tool set, a tool's schema, or a policy changes -- including OpenAPI/MCP tool additions on refresh and removals.
Adds a "tools.list cache" suite: tool-set additions/removals across a refresh, a tool content change, a block policy busting the cache, the query filter applied post-cache (distinct queries share a cached set), and static tools unioned live on a hit.
The catalog-revision read was tenant-wide and the combine sorted only by the `integration:connection` key, so the all-owners list (filter.owner undefined) could surface same-key rows for different owners in nondeterministic order, destabilizing the cache key (it would never hit). Scope the read by filter.owner when set, and carry `owner` in the combine tuple + sort.
aryasaatvik
force-pushed
the
feat/tools-list-revision-cache
branch
from
June 21, 2026 21:53
2a50b8b to
ce07a7e
Compare
aryasaatvik
added a commit
that referenced
this pull request
Jun 23, 2026
## Why
Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.
## What
- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.
## Safety
- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).
## Gate
tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.
Follow-up: a reindex on the deployed instance warms the new unified
variant.
<!-- stack:links:start -->
### [Stack](https://github.com/kitlangton/stack)
1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
aryasaatvik
added a commit
that referenced
this pull request
Jun 23, 2026
**Phase 1** of Tier-2 tool-catalog caching — stacked on #49. Caches `executor.tools.list` (today a full `tool` scan + `tool_policy` read on every call) behind a **content-derived revision key**, so the list stays warm and only re-reads D1 when the catalog or policies actually change. No TTL. - `produceConnectionTools` writes a per-connection **`toolset_revision`** = `sha256` of the connection's manifest rows' `(path, index_fingerprint)`, in **`plugin_storage`** (owner-scoped, **no schema migration**), at all 5 catalog-mutation sites (produce, both clear branches, connection-remove, integration-remove). - `catalogGen` combines the in-scope connections' revisions; `policyGen` combines the `tool_policy` rows. Key = `sha256(owner, integration, connection, includeBlocked, catalogGen, policyGen)`. - **Token rotation does not bust** (never calls `produceConnectionTools`); add/remove/change a tool, or any policy edit, does. - Cached value = the DB-derived, policy-filtered list. **Static tools unioned live** (never cached); **`query` applied post-cache** so distinct queries share one cached set. - Hand-authored `ToolListItem` Schema (list projection only — no input/output schema); branded ids stored as strings, rebuilt on read. - `catalogGen`/`policyGen` reads are tenant-wide → per-tenant-coarse invalidation (a catalog change re-warms all owners' list caches). Correct, just coarse. tsgo (sdk) · `oxlint --deny-warnings` · oxfmt · vitest **sdk 350/350** (new cache suite: add/remove/content invalidation, policy bust, query post-cache, static-union-on-hit). Phase 2 (stacked) moves the UI list onto the compact manifest tier. <!-- stack:links:start --> 1. #49 2. **#50** 👈 current <!-- stack:links:end -->
aryasaatvik
added a commit
that referenced
this pull request
Jun 23, 2026
## Why
Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.
## What
- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.
## Safety
- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).
## Gate
tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.
Follow-up: a reindex on the deployed instance warms the new unified
variant.
<!-- stack:links:start -->
### [Stack](https://github.com/kitlangton/stack)
1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
aryasaatvik
added a commit
that referenced
this pull request
Jun 23, 2026
**Phase 1** of Tier-2 tool-catalog caching — stacked on #49. Caches `executor.tools.list` (today a full `tool` scan + `tool_policy` read on every call) behind a **content-derived revision key**, so the list stays warm and only re-reads D1 when the catalog or policies actually change. No TTL. - `produceConnectionTools` writes a per-connection **`toolset_revision`** = `sha256` of the connection's manifest rows' `(path, index_fingerprint)`, in **`plugin_storage`** (owner-scoped, **no schema migration**), at all 5 catalog-mutation sites (produce, both clear branches, connection-remove, integration-remove). - `catalogGen` combines the in-scope connections' revisions; `policyGen` combines the `tool_policy` rows. Key = `sha256(owner, integration, connection, includeBlocked, catalogGen, policyGen)`. - **Token rotation does not bust** (never calls `produceConnectionTools`); add/remove/change a tool, or any policy edit, does. - Cached value = the DB-derived, policy-filtered list. **Static tools unioned live** (never cached); **`query` applied post-cache** so distinct queries share one cached set. - Hand-authored `ToolListItem` Schema (list projection only — no input/output schema); branded ids stored as strings, rebuilt on read. - `catalogGen`/`policyGen` reads are tenant-wide → per-tenant-coarse invalidation (a catalog change re-warms all owners' list caches). Correct, just coarse. tsgo (sdk) · `oxlint --deny-warnings` · oxfmt · vitest **sdk 350/350** (new cache suite: add/remove/content invalidation, policy bust, query post-cache, static-union-on-hit). Phase 2 (stacked) moves the UI list onto the compact manifest tier. <!-- stack:links:start --> 1. #49 2. **#50** 👈 current <!-- stack:links:end -->
aryasaatvik
added a commit
that referenced
this pull request
Aug 18, 2026
Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.
- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.
- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).
tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.
Follow-up: a reindex on the deployed instance warms the new unified
variant.
<!-- stack:links:start -->
1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 of Tier-2 tool-catalog caching — stacked on #49.
Caches
executor.tools.list(today a fulltoolscan +tool_policyread on every call) behind a content-derived revision key, so the list stays warm and only re-reads D1 when the catalog or policies actually change. No TTL.Invalidation
produceConnectionToolswrites a per-connectiontoolset_revision=sha256of the connection's manifest rows'(path, index_fingerprint), inplugin_storage(owner-scoped, no schema migration), at all 5 catalog-mutation sites (produce, both clear branches, connection-remove, integration-remove).catalogGencombines the in-scope connections' revisions;policyGencombines thetool_policyrows. Key =sha256(owner, integration, connection, includeBlocked, catalogGen, policyGen).produceConnectionTools); add/remove/change a tool, or any policy edit, does.Scope
queryapplied post-cache so distinct queries share one cached set.ToolListItemSchema (list projection only — no input/output schema); branded ids stored as strings, rebuilt on read.Notes (intentional, single-tenant self-host)
catalogGen/policyGenreads are tenant-wide → per-tenant-coarse invalidation (a catalog change re-warms all owners' list caches). Correct, just coarse.Gate
tsgo (sdk) ·
oxlint --deny-warnings· oxfmt · vitest sdk 350/350 (new cache suite: add/remove/content invalidation, policy bust, query post-cache, static-union-on-hit).Phase 2 (stacked) moves the UI list onto the compact manifest tier.
Stack