Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions docs/benchmarks/cloudflare-multiturn/memory-plateau.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Gates and honesty notes for Streamable HTTP memory under sessionful load.

## Two separate problems

| # | Symptom | Cause class | Status |
| ----------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **1. Climb** | Anonymous ~200→~3444 MB over ~30k same-session JSON executes | MCP SDK JSON mode orphans `_streamMapping` after `resolveJson` ([typescript-sdk#2559](https://github.com/modelcontextprotocol/typescript-sdk/issues/2559)) | **Fixed in host** via `pruneOrphanJsonStreamMappings` after each POST; PR gate + nightly + unit bound test |
| **2. Lean floor** | Lean listen probe ~130 MB (Oct 15) → ~170–313 MB without lazy listen | Eager `clawql-payments` / memory-ONNX at Ready | **Fixed on tip** — lazy listen + SQLite catalog OOP + `clawql-inference/keys`; Bun `--smol` lean ~**136 MB** anon (N=2k) |
| # | Symptom | Cause class | Status |
| ----------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **1. Climb** | Anonymous ~200→~3444 MB over ~30k same-session JSON executes | MCP SDK JSON mode orphans `_streamMapping` after `resolveJson` ([typescript-sdk#2559](https://github.com/modelcontextprotocol/typescript-sdk/issues/2559)) | **Fixed in host** via `pruneOrphanJsonStreamMappings` after each POST; PR gate + nightly + unit bound test |
| **2. Lean floor** | Lean listen probe ~130 MB (Oct 15) → ~170–313 MB without lazy listen | Eager `clawql-payments` / memory-ONNX at Ready | **Fixed on tip** — lazy listen + catalog OOP + inference/keys + clawql-api grpc/graphql/swagger defer; Bun `--smol` lean ~**128 MB** anon (N=2k) |

Climb (#1) and lean (#2) are both on tip after `#1390` + lazy-listen restore. Peer gap (~99–102 MB MCPJungle/agentgateway) remains a further Bun density climb, not a Rust dataplane path (ADR 0016 Rejected).

Expand Down Expand Up @@ -71,7 +71,8 @@ Separate from `_streamMapping` climb:
- **Tip without lazy listen:** ~**170–313 MB** (eager payments + pgvector/ONNX at Ready).
- **Tip after lazy listen + catalog OOP (#1398):** Bun `--smol` equal-arm ~**138 MB** anon ([catalog-on N=2k](./heap-vs-anon-bun-smol-catalog-on-n2k.json) on remasure branch; ~145 MB catalog-off).
- **Tip after `clawql-inference/keys` (Ready skips full inference barrel):** Bun `--smol` equal-arm ~**136.2 MB** anon median ([keys + catalog-on](./heap-vs-anon-bun-smol-keys-catalog-on-n2k.json); [catalog-off](./heap-vs-anon-bun-smol-keys-catalog-off-n2k.json) ~134 MB — N=2k noise band). ESM `keys` entry is an 8 KB chunk (no payments/openai/routing).
- **Tip after clawql-api defer (`@grpc/grpc-js` / `graphql` / `swagger2openapi` dynamic-only):** Bun `--smol` equal-arm ~**128.0 MB** anon median ([api-lazy + catalog-on](./heap-vs-anon-bun-smol-api-lazy-catalog-on-n2k.json); [catalog-off](./heap-vs-anon-bun-smol-api-lazy-catalog-off-n2k.json) ~132 MB). ~8 MB vs keys tip.
- **Remasure:** `BUN_RUNTIME=1 BUN_SMOL=1 N=2000 npm run benchmark:heap-vs-anon-plateau`
- **Next density:** Deeper `clawql-api` lazy graph (grpc/graphql/swagger off Ready) + thin tools barrel to close ~34–37 MB toward peers (~99–102 MB).
- **Next density:** Thin host `tools.ts` / residual `clawql-api` + `clawql-auth` to close ~26–29 MB toward peers (~99–102 MB).

See also vault notes: [[Bun JSON streamMapping memory climb fixed]], [[Bun smol lean board 130MB anon plateau config]], [[Density baseline post-prune stalls and lean floor]], [[Bun density peer climb tip levers after 1390]].
14 changes: 8 additions & 6 deletions packages/clawql-api/src/execute/execute-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import { preferRestOpenApiExecuteEffect } from "./openapi-execute-path.js";
import { serializeExecuteResultEffect } from "./result-truncation.js";
import { unknownOperationIdErrorEffect } from "./suggest-operation-ids.js";
import { shapeExecuteDataEffect, WhereFilterError } from "./where-filter.js";
import { executeNativeGraphQL } from "./native-graphql.js";
import { executeNativeGrpc } from "./native-grpc.js";
import { executeNativeMcp } from "./native-mcp.js";
import { executeNativeCli } from "./native-cli.js";
import { executeNativeWebmcp } from "./native-webmcp.js";
Expand Down Expand Up @@ -307,9 +305,10 @@ export function executeClawqlOperationEffect(
const selectedFields = outputFields?.length
? outputFields.join("\n ")
: "__typename";
const exec = yield* fromPromise(() =>
executeNativeGraphQL(op as Operation, args, selectedFields)
);
const exec = yield* fromPromise(async () => {
const { executeNativeGraphQL } = await import("./native-graphql.js");
return executeNativeGraphQL(op as Operation, args, selectedFields);
});
if (!exec.ok) {
return yield* textContentEffect(
JSON.stringify({
Expand All @@ -328,7 +327,10 @@ export function executeClawqlOperationEffect(
}

if (op.protocolKind === "grpc" && op.nativeGrpc) {
const exec = yield* fromPromise(() => executeNativeGrpc(op as Operation, args));
const exec = yield* fromPromise(async () => {
const { executeNativeGrpc } = await import("./native-grpc.js");
return executeNativeGrpc(op as Operation, args);
});
if (!exec.ok) {
return yield* textContentEffect(
JSON.stringify({
Expand Down
3 changes: 1 addition & 2 deletions packages/clawql-api/src/execute/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ export * from "./openapi-execute-path.js";
export * from "./where-filter.js";
export * from "./result-truncation.js";
export * from "./suggest-operation-ids.js";
export * from "./native-grpc.js";
export * from "./native-graphql.js";
// native-grpc / native-graphql: dynamic-import from execute-core only (Ready RSS).
export * from "./rest-operation.js";
export * from "./types.js";
export { mandateArgsMatchEffect, type MandateArgsMatch } from "../proofs/mandate-args-match.js";
8 changes: 5 additions & 3 deletions packages/clawql-api/src/spec/custom-sources-merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@

import { writeFile } from "node:fs/promises";
import { resolve, sep } from "node:path";
import { convertObj } from "swagger2openapi";
import type { LoadedSpec } from "./spec-loader.js";
import { loadOpenAPIFromAbsolutePath } from "./spec-loader.js";
import type { Operation } from "./operation-types.js";
import { loadCliSourceOperations } from "./cli-source-loader.js";
import { loadMcpSourceOperations } from "./mcp-source-loader.js";
import { loadWebmcpSourceOperations } from "./webmcp-source-loader.js";
import { loadGraphqlNativeOperationsFromConfigs } from "./graphql-native-loader.js";
import { loadGrpcNativeOperationsFromConfigs } from "./grpc-native-loader.js";
import {
readCustomSourcesFile,
resolveClawqlHome,
Expand Down Expand Up @@ -127,11 +124,13 @@ async function loadOperationsForCustomSourceEntryImpl(
if (entry.kind === "graphql") {
const cfg = toGraphqlConfig(entry, home);
if (!cfg) return [];
const { loadGraphqlNativeOperationsFromConfigs } = await import("./graphql-native-loader.js");
return loadGraphqlNativeOperationsFromConfigs([cfg]);
}
if (entry.kind === "grpc") {
const cfg = toGrpcConfig(entry, home);
if (!cfg) return [];
const { loadGrpcNativeOperationsFromConfigs } = await import("./grpc-native-loader.js");
return loadGrpcNativeOperationsFromConfigs([cfg]);
}
if (entry.kind === "mcp") {
Expand Down Expand Up @@ -192,6 +191,7 @@ async function mergeCustomSourceOperationsImpl(loaded: LoadedSpec): Promise<Load
.map((e) => toGraphqlConfig(e, home))
.filter((c): c is GraphQLSourceConfig => c !== null);
if (gqlConfigs.length) {
const { loadGraphqlNativeOperationsFromConfigs } = await import("./graphql-native-loader.js");
const gqlOps = await loadGraphqlNativeOperationsFromConfigs(gqlConfigs);
operations = mergeOps(operations, gqlOps);
}
Expand All @@ -201,6 +201,7 @@ async function mergeCustomSourceOperationsImpl(loaded: LoadedSpec): Promise<Load
.map((e) => toGrpcConfig(e, home))
.filter((c): c is GrpcSourceConfig => c !== null);
if (grpcConfigs.length) {
const { loadGrpcNativeOperationsFromConfigs } = await import("./grpc-native-loader.js");
const grpcOps = await loadGrpcNativeOperationsFromConfigs(grpcConfigs);
operations = mergeOps(operations, grpcOps);
}
Expand Down Expand Up @@ -277,6 +278,7 @@ async function cacheCustomSourceBodyImpl(

let toWrite = bodyText;
if (entry.kind === "openapi" && bodyText.includes('"swagger": "2.0"')) {
const { convertObj } = await import("swagger2openapi");
const { openapi } = await convertObj(JSON.parse(bodyText) as object, {
patch: true,
warnOnly: true,
Expand Down
7 changes: 5 additions & 2 deletions packages/clawql-api/src/spec/graphql-native-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ vi.mock("node-fetch", () => ({
}));

import fetch from "node-fetch";
import { introspectionJsonToQuery, loadGraphqlNativeOperationsFromConfigs } from "clawql-api";
import { resetNativeProtocolRegistry } from "clawql-api";
import {
introspectionJsonToQuery,
loadGraphqlNativeOperationsFromConfigs,
} from "./graphql-native-loader.js";
import { resetNativeProtocolRegistry } from "./native-protocol-registry.js";

describe("graphql-native-loader", () => {
beforeEach(() => {
Expand Down
3 changes: 1 addition & 2 deletions packages/clawql-api/src/spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ export * from "./native-protocol-registry.js";
export * from "./native-protocol-merge.js";
export * from "./native-protocol-metrics.js";
export * from "./native-protocol-prometheus.js";
export * from "./graphql-native-loader.js";
export * from "./grpc-native-loader.js";
// graphql-native-loader / grpc-native-loader: dynamic-import only (Ready RSS).
export * from "./custom-sources-types.js";
export * from "./custom-sources-store.js";
export * from "./custom-sources-security.js";
Expand Down
10 changes: 7 additions & 3 deletions packages/clawql-api/src/spec/native-protocol-merge.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* Merge native GraphQL / gRPC operations into a loaded OpenAPI/Discovery spec (ADR 0002).
*
* Loaders are dynamic-imported so Ready/LOAD=db never evaluates `graphql` /
* `@grpc/grpc-js` / swagger at module init.
*/

import { loadGraphqlNativeOperations } from "./graphql-native-loader.js";
import { loadGrpcNativeOperations } from "./grpc-native-loader.js";
import { mergeCustomSourceOperations } from "./custom-sources-merge.js";
import { recordNativeMergeFromOperations } from "./native-protocol-metrics.js";
import type { LoadedSpec } from "./spec-loader.js";
import type { Operation } from "./operation-types.js";
Expand All @@ -14,11 +14,13 @@ async function mergeNativeProtocolOperationsImpl(loaded: LoadedSpec): Promise<Lo
let gql: Operation[] = [];
let grpc: Operation[] = [];
try {
const { loadGraphqlNativeOperations } = await import("./graphql-native-loader.js");
gql = await loadGraphqlNativeOperations();
} catch (e: unknown) {
console.error("[spec-loader] Native GraphQL merge failed:", e);
}
try {
const { loadGrpcNativeOperations } = await import("./grpc-native-loader.js");
grpc = await loadGrpcNativeOperations();
} catch (e: unknown) {
console.error("[spec-loader] Native gRPC merge failed:", e);
Expand All @@ -27,6 +29,8 @@ async function mergeNativeProtocolOperationsImpl(loaded: LoadedSpec): Promise<Lo
recordNativeMergeFromOperations(gql, grpc);
const extra = gql.concat(grpc);

const { mergeCustomSourceOperations } = await import("./custom-sources-merge.js");

if (extra.length === 0) {
// Still merge ~/.ClawQL/sources.json (CLI / MCP / OpenAPI custom sources).
return mergeCustomSourceOperations(loaded);
Expand Down
4 changes: 2 additions & 2 deletions packages/clawql-api/src/spec/spec-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import { Effect } from "effect";
import fetch from "node-fetch";
import { parse as parseYaml } from "yaml";
import { isAwsSpecLabelEffect, resolveAwsApiBaseUrlEffect } from "../auth/aws-auth.js";
import { convertObj } from "swagger2openapi";
import type { Operation, ParameterInfo } from "./operation-types.js";
import { loadGraphqlNativeOperationsFromConfigs } from "./graphql-native-loader.js";
import { mergeNativeProtocolOperations } from "./native-protocol-merge.js";
import {
shouldLoadCustomProvidersOnly,
Expand Down Expand Up @@ -477,6 +475,7 @@ async function loadRawDocument(source: SpecSource): Promise<unknown> {

async function normalizeToOpenAPI(raw: unknown): Promise<unknown> {
if (isSwagger2(raw)) {
const { convertObj } = await import("swagger2openapi");
const { openapi } = await convertObj(raw as object, {
patch: true,
/** e.g. Slack Web API spec uses JSON Schema unions Slack’s own generator emits. */
Expand Down Expand Up @@ -550,6 +549,7 @@ async function loadBundledGraphqlOperationsFromDiskOrFallback(opts: {
schemaAbs: string;
fallbackUrl: string;
}): Promise<Operation[]> {
const { loadGraphqlNativeOperationsFromConfigs } = await import("./graphql-native-loader.js");
try {
await readFile(opts.schemaAbs, "utf-8");
return loadGraphqlNativeOperationsFromConfigs([
Expand Down
Loading