Files
PaperClipAI/scripts/benchmark-chat-milestones.ts
T
DottaandPaperclip 475f7df2e7 perf(server): prefilter chat milestone run candidates (#17892)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Chat publications connect a run to its authorized originating
conversation.
> - The milestone selector evaluates detailed lineage and final-answer
predicates.
> - A local plan evaluated these predicates across many unrelated run
and conversation pairs.
> - This pull request materializes recent runs that already match an
active or waiting conversation.
> - The existing query still makes each publication decision.
> - The benefit is less JSON work when most run history is unrelated to
active chat.

## Linked Issues or Issue Description

**What happened?**

The milestone candidate query repeatedly reads large run context values
for unrelated conversations. A synthetic baseline discarded 60,962 run
and conversation pairs through the complex join filter.

**Expected behavior**

Apply the existing same-company conversation binding before expensive
lineage checks. Preserve every candidate column, order, publication
decision and continuation rule.

**Steps to reproduce**

Run the two synthetic PostgreSQL comparisons below against baseline
`db70c5acd92f50fa37fb4f0dea3da0e7685089a2`. They capture complete SQL
from the actual service implementations.

Related work: #17606 changes chat lifecycle scheduling. #15802 deferred
milestone cadence. This SQL-only change leaves scheduling, startup
reconciliation and recovery intact. It has no dependency on those PRs.

## What Changed

- Materialize recent runs with an active or waiting same-company
conversation, using the unchanged native and legacy binding expression.
- Retain every outer lineage, continuation, final-response, privacy,
cursor, ordering and idempotency check.
- Add a real PostgreSQL historical-binding regression and
selective/dense performance controls.
- Document the measured gain and the dense-history tradeoff.

## Verification

- Dedicated binding, publication reconciliation and app coordination
suites passed all 27 tests across three files with real PostgreSQL where
required.
- The selective fixture has 2,615 runs, 55 conversation issues and about
192 KiB of pseudorandom context padding per run. Five alternating
measured samples follow two warmups on PostgreSQL 17.11.
- Final-harness selective milestone median: 1,495.85 ms before, 130.96
ms after, 11.42 times faster. Ranges: 1,483–1,716 ms and 125.59–135.85
ms. All 60 complete ordered rows match, including 15 explicit
historical-binding cases.
- Dense control: 736.71 ms before, 809.08 ms after, with overlapping
ranges. All 200 complete ordered rows match. This is inconclusive
performance evidence with possible materialization overhead.
- The unchanged native progress control is neutral in both fixtures. It
returns 41 equal complete ordered rows.
- The added large-window control has 12,215 runs, including 12,000
recent native history rows. A caller limit of one produces the real
service page size of 25. First-page medians 762.65 to 800.63 ms overlap
and are inconclusive. The 90% cursor improves 234.84 to 54.36 ms; the
99.99% tail improves 352.10 to 12.88 ms; the finished cursor improves
340.00 to 58.58 ms. Complete ordered results match: 25, 25, seven and
zero rows. Same-run later-conversation rows remain present.
- Large-window CTE materialization itself takes about 4.6–10 ms with
zero temp spill. This does not establish a materialization regression or
justify a larger production query rewrite.
- Reproduce the added window control with `--cursor-window` and a
distinct report path.
- Reproduce selective: `pnpm --filter @paperclipai/server exec tsx
../scripts/benchmark-chat-milestones.ts
--baseline-ref=db70c5acd92f50fa37fb4f0dea3da0e7685089a2
--docker-postgres=17 --report=/tmp/chat-milestones.json`.
- Add `--dense-chat` and a distinct report path for the dense control.
The harness applies normal migrations to a disposable container and uses
no hosted database.
- The existing real-service test at
`server/src/__tests__/chat-channels.integration.test.ts:34584` calls
enqueue with limit=1 four times, crosses 30 resolver-rejected
candidates, expects admissions [1,1,1,0], and asserts the exact three
publication keys plus internal-run exclusion. It passed and supplies the
full composition/publication-set proof requested in review.
- Assembled chat integration passed 37 selected tests. Together with the
27 focused tests, 64 checks passed. The selector covers bounded
draining, exact continuations, private summaries, final-answer
precedence, lifecycle idempotency and native progress races.
- Final head `1b3a6f50c9e7ec712dcbaf82c7fc3df141ec3f28`: [full CI
passed](https://github.com/paperclipai/paperclip/actions/runs/40534934737),
with 55 successful checks, two skipped and one neutral. Greptile is 5/5
on this head; zero unresolved review threads. Paperclip Review is 4/5,
neutral, explicitly with no must-fix correctness or security defect. Its
remaining payload-memory/dense-overhead caution is retained here as a
measured residual tradeoff; an identity-only projection needs production
evidence and separate comparison.

- After the Drizzle metadata correction, both generated service queries
and every parameter remain byte-identical to the selective and dense
measured versions. The real PostgreSQL binding regression passed again.
- Full repository typecheck and build passed on the integrated final
source. The local full test command was stopped after native PostgreSQL
initialization failed repeatedly, producing 16 failures and skipped
database suites. It is incomplete, not a pass. Focused real-database
suites used disposable Docker PostgreSQL. Current-head CI runs the full
gates separately.

## Risks

- A materialized source can add overhead when most recent runs match
active conversations. The dense control does not establish a gain.
- Historical JSON comparisons retain string and whitespace semantics.
Test fixtures disable the normalization trigger only while inserting
retained historical shapes in disposable databases.
- Synthetic SQL measurements exclude provider delivery and do not
establish deployed latency, actual compute sleep or fleet-wide cost
reduction.
- No schema migration, index, startup job, polling change or deployment
is included.

## Model Used

OpenAI Codex, configured model `gpt-6.1-sol` with xhigh reasoning, tool
use and code execution. Independent blast-radius review also used
`gpt-6-astra` and `gpt-6.1-sol`. The runtime context window was not
exposed.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-27 11:22:25 -05:00

460 lines
28 KiB
TypeScript

import assert from "node:assert/strict";
import { createHash, randomUUID } from "node:crypto";
import { execFileSync } from "node:child_process";
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { companies, agents, issues, applyPendingMigrations, createDb, startEmbeddedPostgresTestDatabase, type Db } from "../packages/db/src/index.js";
import * as treatment from "../server/src/services/chat-run-publications.js";
type Query = { sql: string; params: unknown[] };
const dockerPostgres = process.argv.includes("--docker-postgres=17");
const correctnessOnly = process.argv.includes("--correctness-only");
const cursorWindow = process.argv.includes("--cursor-window");
const historyRuns = cursorWindow ? 12_000 : 2_400;
const denseChat = process.argv.includes("--dense-chat");
const baselineRef = process.argv.find(arg => arg.startsWith("--baseline-ref="))?.slice(15);
assert.ok(baselineRef, "Pass a pinned pre-fix --baseline-ref=<commit>");
const repositoryRoot = fileURLToPath(new URL("../", import.meta.url));
const baselineRevision = execFileSync("git", ["rev-parse", "--verify", `${baselineRef}^{commit}`], { encoding: "utf8" }).trim();
const ANCHOR_MS = Date.parse("2026-10-19T00:00:00.000Z");
const SINCE_MS = ANCHOR_MS - 24 * 60 * 6e4;
const PROMPT_KIB = cursorWindow ? 6 : 144;
function mulberry32(seed2: number) {
return () => {
seed2 |= 0;
seed2 = seed2 + 1831565813 | 0;
let t = Math.imul(seed2 ^ seed2 >>> 15, 1 | seed2);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
const rand = mulberry32(1346457664);
const hex = (n: number) => Array.from({ length: n }, () => "0123456789abcdef"[Math.floor(rand() * 16)]).join("");
const uuid = () => `${hex(8)}-${hex(4)}-4${hex(3)}-${"89ab"[Math.floor(rand() * 4)]}${hex(3)}-${hex(12)}`;
const prompt = Buffer.from(
Array.from({ length: PROMPT_KIB * 1024 }, () => Math.floor(rand() * 256))
).toString("base64");
const iso = (ms: number) => new Date(ms).toISOString();
async function seed(db: Db) {
const companyA = uuid();
const companyB = uuid();
const agentA = uuid();
const agentB = uuid();
const endpointA = uuid();
const endpointB = uuid();
await db.insert(companies).values([
{ id: companyA, name: "Synthetic chat polls A", issuePrefix: "BENCH" },
{ id: companyB, name: "Synthetic chat polls B", issuePrefix: "BNCH" }
]);
await db.insert(agents).values([
{ id: agentA, companyId: companyA, name: "Fixture A", adapterType: "codex_local" },
{ id: agentB, companyId: companyB, name: "Fixture B", adapterType: "codex_local" }
]);
const conversationIssueIds: string[] = [];
const issueIdsA: string[] = [];
for (let n = 0; n < 50; n++) {
const issueId = uuid();
if (n < 45) conversationIssueIds.push(issueId);
issueIdsA.push(issueId);
await db.insert(issues).values({ id: issueId, companyId: companyA, title: `Fixture issue A${n}` });
}
const conversationIssueIdsB: string[] = [];
for (let n = 0; n < 10; n++) {
const issueId = uuid();
conversationIssueIdsB.push(issueId);
await db.insert(issues).values({ id: issueId, companyId: companyB, title: `Fixture issue B${n}` });
}
for (const [companyId, endpointId, agentId, ids, label] of [
[companyA, endpointA, agentA, conversationIssueIds, "A"],
[companyB, endpointB, agentB, conversationIssueIdsB, "B"]
]) {
const applicationId = uuid();
const connectionId = uuid();
await db.$client.unsafe(
`INSERT INTO tool_applications
(id, company_id, name, type, status)
VALUES ($1::uuid, $2::uuid, $3, 'chat', 'active')`,
[applicationId, companyId, `fixture-app-${label}`]
);
await db.$client.unsafe(
`INSERT INTO tool_connections
(id, company_id, application_id, name, uid, connection_purpose, transport, status, enabled)
VALUES ($1::uuid, $2::uuid, $3::uuid, $4, $5, 'channel', 'chat_sdk', 'active', true)`,
[connectionId, companyId, applicationId, `fixture-connection-${label}`, `fixture-conn-${label}`]
);
await db.$client.unsafe(
`INSERT INTO chat_endpoints
(id, company_id, connection_id, provider, public_id, publication_mode, assigned_agent_id, status)
VALUES ($1::uuid, $2::uuid, $3::uuid, 'slack', $4, 'automatic', $5::uuid, 'active')`,
[endpointId, companyId, connectionId, `fixture-endpoint-${label}`, agentId]
);
await db.$client.unsafe(
`INSERT INTO chat_conversations
(id, company_id, endpoint_id, issue_id, external_conversation_id, external_label, state)
SELECT gen_random_uuid(), $1::uuid, $2::uuid, issue::uuid, 'conv-' || n, 'Conv ${label} ' || n,
CASE WHEN n % 9 = 0 THEN 'waiting' WHEN n % 11 = 0 THEN 'completed' ELSE 'active' END
FROM (SELECT issue, row_number() OVER () AS n FROM unnest($3::uuid[]) AS issue) sub`,
[companyId, endpointId, ids]
);
}
const nativeRunIds: string[] = [];
const convRowsA = conversationIssueIds.map((issue, n) => ({ issue, n }));
for (const { issue, n } of convRowsA) {
const nativeRunId = uuid();
nativeRunIds.push(nativeRunId);
const base = ANCHOR_MS - n % 40 * 6e4;
await db.$client.unsafe(
`INSERT INTO heartbeat_runs
(id, company_id, agent_id, scope_kind, issue_id, runtime_mode, status, error_code, created_at, updated_at, context_snapshot, result_json)
VALUES ($1::uuid, $2::uuid, $3::uuid, 'issue', $4::uuid, 'native', 'running', NULL, $5, $5,
jsonb_build_object('issueId', $4::text, 'source', 'chat:slack:fixture', 'prompt', $6::text), NULL)`,
[nativeRunId, companyA, agentA, issue, iso(base), prompt]
);
await db.$client.unsafe(
`INSERT INTO heartbeat_runs
(id, company_id, agent_id, scope_kind, runtime_mode, status, error_code, created_at, updated_at, context_snapshot, result_json)
VALUES ($1::uuid, $2::uuid, $3::uuid, 'company', 'legacy',
CASE WHEN $4 % 5 = 0 THEN 'failed' WHEN $4 % 7 = 0 THEN 'succeeded' ELSE 'running' END,
CASE WHEN $4 % 5 = 0 THEN 'execution_reconciliation_required' END, $5, $5,
jsonb_build_object('issueId', $6::text, 'source', 'chat:slack:fixture', 'prompt', $7::text),
CASE WHEN $4 % 7 = 0 THEN jsonb_build_object('presentationDecision', jsonb_build_object('kind', 'fixture')) END)`,
[uuid(), companyA, agentA, n, iso(base), issue, prompt]
);
await db.$client.unsafe(
`INSERT INTO chat_publications
(company_id, endpoint_id, conversation_id, issue_id, idempotency_key, payload, state, created_at)
VALUES ($1::uuid, $2::uuid,
(SELECT id FROM chat_conversations WHERE company_id = $1::uuid AND issue_id = $4::uuid LIMIT 1),
$4::uuid, 'run:' || $3::text || ':working:' || $2::text,
jsonb_build_object('classification', 'external', 'source', 'safe_milestone', 'text', 'working'),
'streaming', $5)`,
[companyA, endpointA, nativeRunId, issue, iso(base - 12e4)]
);
await db.$client.unsafe(
`INSERT INTO heartbeat_run_events
(company_id, run_id, agent_id, seq, event_type, message, created_at)
VALUES ($1::uuid, $2::uuid, $3::uuid, 1, 'workspace.ready', 'ready', $4),
($1::uuid, $2::uuid, $3::uuid, 2, 'tool.execution.started', 'tool', $5)`,
[companyA, nativeRunId, agentA, iso(base + 3e4), iso(base + 45e3)]
);
}
for (const [n, issue] of conversationIssueIdsB.entries()) {
const runId = uuid();
const base = ANCHOR_MS - n % 10 * 6e4;
await db.$client.unsafe(
`INSERT INTO heartbeat_runs
(id, company_id, agent_id, scope_kind, issue_id, runtime_mode, status, error_code, created_at, updated_at, context_snapshot)
VALUES ($1::uuid, $2::uuid, $3::uuid, 'issue', $4::uuid, 'native', 'running', NULL, $5, $5,
jsonb_build_object('issueId', $4::text, 'source', 'chat:slack:fixture', 'prompt', $6::text))`,
[runId, companyB, agentB, issue, iso(base), prompt]
);
}
for (let start = 0; start < historyRuns; start += 400) {
if (cursorWindow) {
await db.$client.unsafe(`insert into heartbeat_runs
(company_id, agent_id, scope_kind, issue_id, native_issue_id, runtime_mode, status, created_at, updated_at, context_snapshot)
select $1::uuid, $2::uuid, 'issue', $3::uuid, $3::uuid, 'native', 'running',
$4::timestamptz - (12001 - n) * interval '50 milliseconds',
$4::timestamptz - (12001 - n) * interval '50 milliseconds',
jsonb_build_object('issueId', $3::text, 'source', 'chat:slack:fixture', 'fixtureHistory', true, 'prompt', $5::text)
from generate_series(${start + 1}, ${start + 400}) n`,
[companyA, agentA, conversationIssueIds[0], iso(ANCHOR_MS), prompt]);
continue;
}
await db.$client.unsafe(
`INSERT INTO heartbeat_runs
(company_id, agent_id, scope_kind, status, error_code, created_at, updated_at, context_snapshot)
SELECT $1::uuid, $2::uuid, 'company',
(ARRAY['queued','running','succeeded','interrupted','failed','timed_out','cancelled'])[(n % 7) + 1],
NULL,
make_interval(hours => CASE WHEN n % 3 = 0 THEN -(30 + (n % 100)) ELSE -(n % 16) END, secs => -60) + $4::timestamptz,
make_interval(hours => CASE WHEN n % 3 = 0 THEN -(30 + (n % 100)) ELSE -(n % 16) END, secs => -60) + $4::timestamptz,
jsonb_build_object('issueId', gen_random_uuid()::text, 'source', 'chat:slack:fixture', 'prompt', $3::text)
FROM generate_series(${start + 1}, ${start + 400}) n`,
[companyA, agentA, prompt, iso(ANCHOR_MS)]
);
}
await db.$client.unsafe(
`INSERT INTO heartbeat_runs
(company_id, agent_id, scope_kind, status, error_code, created_at, updated_at, context_snapshot)
SELECT $1::uuid, $2::uuid, 'company',
(ARRAY['queued','running','succeeded','interrupted','failed','timed_out','cancelled'])[(n % 7) + 1],
NULL,
make_interval(hours => CASE WHEN n % 3 = 0 THEN -(30 + (n % 100)) ELSE -(n % 16) END, secs => -60) + $4::timestamptz,
make_interval(hours => CASE WHEN n % 3 = 0 THEN -(30 + (n % 100)) ELSE -(n % 16) END, secs => -60) + $4::timestamptz,
jsonb_build_object('issueId', gen_random_uuid()::text, 'source', 'chat:slack:fixture', 'prompt', $3::text)
FROM generate_series(1, 100) n`,
[companyB, agentB, prompt, iso(ANCHOR_MS)]
);
await db.$client.unsafe(
"UPDATE heartbeat_runs SET scope_kind = 'company', issue_id = NULL WHERE id = $1::uuid",
[nativeRunIds[0]]
);
await db.$client.unsafe("ANALYZE heartbeat_runs");
await db.$client.unsafe("ANALYZE chat_conversations");
await db.$client.unsafe("ANALYZE heartbeat_run_events");
await db.$client.unsafe("ANALYZE chat_publications");
return { companyA, companyB };
}
async function makeDenseNativeChatHistory(db: Db) {
// Bind the otherwise unrelated history through the first-class issue column.
// Preserve existing bound/native runs and their progress publications so the
// unchanged native progress query remains a nonempty control.
await db.$client.unsafe(`with bindings as (
select distinct on (company_id) company_id, issue_id
from chat_conversations where state = 'active' order by company_id, id
) update heartbeat_runs r set scope_kind = 'issue', issue_id = b.issue_id,
context_snapshot = jsonb_set(r.context_snapshot, '{issueId}', to_jsonb(b.issue_id::text))
from bindings b where r.company_id = b.company_id
and r.issue_id is null and r.native_issue_id is null and r.runtime_mode = 'legacy'`);
}
// Add real source comments so complete-result equality includes admitted rows,
// and conflicting native/legacy bindings cannot be hidden by an empty result.
async function seedSourceMessages(db: Db) {
const conversations = await db.$client.unsafe("select id, company_id, endpoint_id, issue_id from chat_conversations");
for (const conversation of conversations) {
const commentId = uuid();
await db.$client.unsafe("insert into issue_comments (id, company_id, issue_id, body) values ($1, $2, $3, 'synthetic inbound')", [commentId, conversation.company_id, conversation.issue_id]);
await db.$client.unsafe(`insert into chat_message_links (company_id, endpoint_id, conversation_id, comment_id, provider_message_id, direction)
values ($1, $2, $3, $4::uuid, $4::text, 'inbound')`, [conversation.company_id, conversation.endpoint_id, conversation.id, commentId]);
await db.$client.unsafe(`update heartbeat_runs set context_snapshot = context_snapshot || jsonb_build_object('wakeCommentId', $1::text)
|| case when $4::boolean then jsonb_build_object('wakeCommentIds', coalesce(context_snapshot->'wakeCommentIds', '[]'::jsonb) || to_jsonb($1::text)) else '{}'::jsonb end
where company_id = $2 and (issue_id = $3::uuid or context_snapshot->>'issueId' = $3::text)`, [commentId, conversation.company_id, conversation.issue_id, cursorWindow]);
}
await db.$client.unsafe("analyze heartbeat_runs");
await db.$client.unsafe("analyze chat_message_links");
}
async function seedBindingCases(db: Db) {
const [conversation] = await db.$client.unsafe(`select conversation.*, link.comment_id, endpoint.assigned_agent_id
from chat_conversations conversation
join chat_message_links link on link.conversation_id = conversation.id
join chat_endpoints endpoint on endpoint.id = conversation.endpoint_id
where conversation.state = 'active' order by conversation.id limit 1`);
const [otherIssue] = await db.$client.unsafe("select id from issues where company_id = $1 and id <> $2 order by id limit 1", [conversation.company_id, conversation.issue_id]);
const [foreignIssue] = await db.$client.unsafe("select id from issues where company_id <> $1 order by id limit 1", [conversation.company_id]);
const issueId = conversation.issue_id as string;
const context = { source: "chat:slack:fixture", issueId, wakeCommentId: conversation.comment_id };
const cases = [
{ name: "legacy-issue", context, matches: true },
{ name: "trimmed-issue", context: { ...context, issueId: ` ${issueId} ` }, matches: true },
{ name: "task-fallback", context: { ...context, issueId: " ", taskId: issueId }, matches: true },
{ name: "numeric-issue-fallback", context: { ...context, issueId: 2, taskId: issueId }, matches: true },
{ name: "object-issue-fallback", context: { ...context, issueId: { id: issueId }, taskId: issueId }, matches: true },
{ name: "array-issue-fallback", context: { ...context, issueId: [issueId], taskId: issueId }, matches: true },
{ name: "uppercase-legacy", context: { ...context, issueId: issueId.toUpperCase() }, matches: false },
{ name: "malformed-legacy", context: { ...context, issueId: "not-a-uuid", taskId: issueId }, matches: false },
{ name: "native-over-legacy", context: { ...context, issueId: "not-a-uuid" }, nativeIssueId: issueId, matches: true },
{ name: "issue-column-over-legacy", context: { ...context, issueId: "not-a-uuid" }, runIssueId: issueId, matches: true },
{ name: "native-over-issue-column", context, nativeIssueId: issueId, runIssueId: otherIssue.id, matches: true },
{ name: "foreign-native-over-local", context, nativeIssueId: foreignIssue.id, matches: false },
{ name: "null-context", context: null, matches: false },
{ name: "scalar-context", context: "chat:slack:fixture", matches: false },
{ name: "array-context", context: [context], matches: false },
];
const expected = [];
// Preserve stored legacy shapes: the insert trigger otherwise canonicalizes
// uppercase JSON identifiers into the first-class UUID privacy binding.
await db.$client.unsafe("alter table heartbeat_runs disable trigger heartbeat_runs_set_scope_kind");
try { for (const item of cases) {
const runId = uuid();
await db.$client.unsafe(`insert into heartbeat_runs
(id, company_id, agent_id, status, scope_kind, issue_id, native_issue_id, context_snapshot, created_at, updated_at)
values ($1, $2, $3, 'queued', $4, $5, $6, $7::jsonb, $8, $8)`,
[runId, conversation.company_id, conversation.assigned_agent_id, "runIssueId" in item ? "issue" : "company",
"runIssueId" in item ? item.runIssueId : null, "nativeIssueId" in item ? item.nativeIssueId : null,
JSON.stringify(item.context), iso(ANCHOR_MS - 1000)]);
expected.push({ name: item.name, runId, matches: item.matches });
}
} finally { await db.$client.unsafe("alter table heartbeat_runs enable trigger heartbeat_runs_set_scope_kind"); }
return expected;
}
async function capture(db: Db, module: typeof treatment): Promise<Record<string, Query>> {
const queries: Query[] = [];
const stop = new Error("captured");
const captureDb = new Proxy(db, { get(target, key) {
if (key !== "select") return Reflect.get(target, key);
return (...fields: Parameters<Db["select"]>) => {
const builder = target.select(...fields), from = builder.from.bind(builder);
builder.from = ((...args: Parameters<typeof from>) => {
const selection = from(...args);
selection.execute = async () => {
queries.push(selection.toSQL());
if (queries.length === 2) throw stop;
return [];
};
return selection;
}) as typeof builder.from;
return builder;
};
}});
try { await module.enqueueChatRunMilestones(captureDb, { since: new Date(SINCE_MS), limit: cursorWindow ? 1 : 200 }); }
catch (error) { if (error !== stop) throw error; }
assert.equal(queries.length, 2, "capture both real candidate queries before any publication writes");
return { milestone: queries[0], native: queries[1] };
}
type Cursor = { updatedAt: Date; runId: string; conversationId: string };
async function captureCursor(db: Db, module: typeof treatment, cursor: Cursor): Promise<Query> {
let query: Query | undefined;
let executions = 0;
const stop = new Error("captured cursor statement");
const captureDb = new Proxy(db, { get(target, key) {
if (key !== "select") return Reflect.get(target, key);
return (...fields: Parameters<Db["select"]>) => {
const builder = target.select(...fields), from = builder.from.bind(builder);
builder.from = ((...args: Parameters<typeof from>) => {
const selection = from(...args);
selection.execute = async () => {
if (++executions === 2) { query = selection.toSQL(); throw stop; }
// Advance only the real service's keyset loop. This non-publishable
// placeholder page avoids resolving origins or writing publications.
return Array.from({ length: 25 }, () => ({ runStatus: "benchmark_skip", runUpdatedAt: cursor.updatedAt,
runId: cursor.runId, conversationId: cursor.conversationId }));
};
return selection;
}) as typeof builder.from;
return builder;
};
}});
try { await module.enqueueChatRunMilestones(captureDb, { since: new Date(SINCE_MS), limit: 1 }); }
catch (error) { if (error !== stop) throw error; }
assert.ok(query, "real second-page service statement was not captured");
return query;
}
async function seedCursorSibling(db: Db) {
const [original] = await db.$client.unsafe(`select conversation.* from chat_conversations conversation
join heartbeat_runs run on run.company_id = conversation.company_id and run.issue_id = conversation.issue_id
where run.context_snapshot->>'fixtureHistory' = 'true' order by conversation.id limit 1`);
await db.$client.unsafe(`insert into chat_conversations
(id, company_id, endpoint_id, issue_id, external_conversation_id, external_label, state)
values ($1, $2, $3, $4, 'cursor-sibling', 'Cursor sibling', 'active')`,
[uuid(), original.company_id, original.endpoint_id, original.issue_id]);
}
async function selectLateCursor(db: Db, fraction = 0.9): Promise<Cursor> {
const [row] = await db.$client.unsafe(`select chosen.updated_at, chosen.id as run_id, conversation.id as conversation_id
from (select id, company_id, issue_id, updated_at from heartbeat_runs
where context_snapshot->>'fixtureHistory' = 'true'
order by updated_at, id offset ${Math.floor(historyRuns * fraction)} limit 1) chosen
join chat_conversations conversation on conversation.company_id = chosen.company_id and conversation.issue_id = chosen.issue_id
where conversation.state = 'active' order by conversation.id limit 1`);
assert.ok(row);
return { updatedAt: new Date(row.updated_at), runId: row.run_id, conversationId: row.conversation_id };
}
const median = (values: number[]) => values.slice().sort((a, b) => a - b)[Math.floor(values.length / 2)];
const keepAlive = setInterval(() => {}, 1000);
let database: Awaited<ReturnType<typeof startDatabase>> | undefined;
let db: Db | undefined;
let baselineDir: string | undefined;
async function startDatabase() {
if (!dockerPostgres) return startEmbeddedPostgresTestDatabase("chat-milestone-benchmark-");
const container = `paperclip-chat-milestones-${randomUUID()}`;
const docker = (...args: string[]) => execFileSync("docker", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
docker("run", "--detach", "--rm", "--name", container, "--label", "paperclip.benchmark=chat-milestones", "--publish", "127.0.0.1::5432",
"--env", "POSTGRES_USER=paperclip", "--env", "POSTGRES_PASSWORD=paperclip", "--env", "POSTGRES_DB=paperclip", "postgres:17");
const cleanup = async () => { docker("rm", "--force", container); };
try {
let ready = false;
for (let attempt = 0; attempt < 100; attempt++) {
try { docker("exec", container, "pg_isready", "-h", "127.0.0.1", "-U", "paperclip"); ready = true; break; }
catch { await new Promise(resolve => setTimeout(resolve, 100)); }
}
assert.ok(ready, "Disposable Docker PostgreSQL did not become ready");
const port = docker("port", container, "5432/tcp").split(":").at(-1);
const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`;
await applyPendingMigrations(connectionString);
return { connectionString, cleanup };
} catch (error) { await cleanup(); throw error; }
}
try {
database = await startDatabase();
db = createDb(database.connectionString);
const modulePath = "server/src/services/chat-run-publications.ts";
const moduleUrl = new URL(`../${modulePath}`, import.meta.url);
const oldSource = execFileSync("git", ["show", `${baselineRevision}:${modulePath}`], { encoding: "utf8" });
const rewritten = oldSource.replace(/from "(\.{1,2}\/[^\"]+)"/g,
(_match, specifier: string) => `from ${JSON.stringify(fileURLToPath(new URL(specifier, moduleUrl)))}`);
baselineDir = await fs.mkdtemp(path.join(fileURLToPath(new URL("../server/scripts/", import.meta.url)), ".chat-milestone-baseline-"));
const baselineFile = path.join(baselineDir, "chat-run-publications.ts");
await fs.writeFile(baselineFile, rewritten);
const baseline = await import(pathToFileURL(baselineFile).href) as typeof treatment;
await seed(db);
if (denseChat) await makeDenseNativeChatHistory(db);
if (cursorWindow) await seedCursorSibling(db);
await seedSourceMessages(db);
const bindingCases = await seedBindingCases(db);
const queries = { baseline: await capture(db, baseline), treatment: await capture(db, treatment) };
const lateCursor = cursorWindow ? await selectLateCursor(db) : undefined;
const nearTailCursor = cursorWindow ? await selectLateCursor(db, 0.9999) : undefined;
let emptyCursor: Cursor | undefined;
if (lateCursor) {
queries.baseline.cursor = await captureCursor(db, baseline, lateCursor);
queries.treatment.cursor = await captureCursor(db, treatment, lateCursor);
}
if (nearTailCursor) {
queries.baseline.tail = await captureCursor(db, baseline, nearTailCursor);
queries.treatment.tail = await captureCursor(db, treatment, nearTailCursor);
const finalPage = await db.$client.unsafe(queries.baseline.tail.sql, queries.baseline.tail.params as never[]).values();
assert.ok(finalPage.length > 0 && finalPage.length < 25, "tail cursor must produce a short final page");
const last = finalPage.at(-1)!;
emptyCursor = { updatedAt: new Date(last[3]), runId: last[0], conversationId: last[7] };
queries.baseline.empty = await captureCursor(db, baseline, emptyCursor);
queries.treatment.empty = await captureCursor(db, treatment, emptyCursor);
}
const results = [];
for (const kind of cursorWindow ? ["milestone", "cursor", "tail", "empty", "native"] : ["milestone", "native"]) {
const samples = { baseline: [] as number[], treatment: [] as number[] };
const rows = { baseline: [] as unknown[], treatment: [] as unknown[] };
for (let round = 0; round < (correctnessOnly ? 1 : 7); round++) {
const order = round % 2 === 0 ? ["baseline", "treatment"] as const : ["treatment", "baseline"] as const;
for (const side of order) {
const query = queries[side][kind], start = performance.now();
rows[side] = await db.$client.unsafe(query.sql, query.params as never[]).values();
if (round >= 2) samples[side].push(performance.now() - start);
}
assert.deepEqual(rows.treatment, rows.baseline, `${kind}: complete ordered positional rows differ`);
}
if (kind === "milestone" && !denseChat && !cursorWindow) {
const runIds = new Set(rows.treatment.map(row => (row as unknown[])[0]));
for (const item of bindingCases) assert.equal(runIds.has(item.runId), item.matches, `binding semantics: ${item.name}`);
}
const expectedCursor = kind === "cursor" ? lateCursor : kind === "tail" ? nearTailCursor : undefined;
if (expectedCursor) {
const first = rows.treatment[0] as unknown[];
assert.equal(first[0], expectedCursor.runId, "late page must preserve the other conversation for the same run");
assert.ok(String(first[7]) > expectedCursor.conversationId);
}
if (kind === "empty") assert.equal(rows.treatment.length, 0, "finished cursor must produce an empty page");
else assert.ok(rows.baseline.length > 0, `${kind}: equality must include actual candidates`);
const plans: Record<string, unknown> = {};
for (const side of ["baseline", "treatment"] as const) {
const query = queries[side][kind];
const [plan] = await db.$client.unsafe(`EXPLAIN (${correctnessOnly ? "" : "ANALYZE, BUFFERS, "}FORMAT JSON) ${query.sql}`, query.params as never[]);
plans[side] = plan["QUERY PLAN"];
}
results.push({ kind, equalRows: rows.baseline.length, baselineMedianMs: median(samples.baseline), treatmentMedianMs: median(samples.treatment),
speedup: median(samples.baseline) / median(samples.treatment), samples, plans });
}
const sourceHashes = Object.fromEntries(await Promise.all([
"server/src/services/chat-run-publications.ts", "server/src/services/chat-run-milestone-candidates.ts", "scripts/benchmark-chat-milestones.ts",
].map(async file => [file, createHash("sha256").update(await fs.readFile(path.join(repositoryRoot, file))).digest("hex")])));
const output = { sourceHashes, baselineRevision, treatmentRevision: execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim(),
candidateSource: await fs.readFile(new URL("../server/src/services/chat-run-milestone-candidates.ts", import.meta.url), "utf8"),
treatmentDiff: execFileSync("git", ["-C", repositoryRoot, "diff", "--", "server/src/services/chat-run-publications.ts"], { encoding: "utf8" }),
postgres: await db.$client.unsafe("select version(), current_setting('shared_buffers') as shared_buffers, current_setting('work_mem') as work_mem"),
fixture: { runs: historyRuns + 215, bindingCases, bindingCasesChecked: !denseChat && !cursorWindow, denseNativeChat: denseChat,
cursorWindow, historyRuns, requestedLimit: cursorWindow ? 1 : 200, lateCursor, nearTailCursor, emptyCursor,
conversationIssues: 55, promptKiB: PROMPT_KIB, windowHours: 24 }, queries, results };
const reportPath = path.resolve(process.argv.find(arg => arg.startsWith("--report="))?.slice(9) ?? ".scratch/chat-milestones-report.json");
await fs.mkdir(path.dirname(reportPath), { recursive: true });
await fs.writeFile(reportPath, JSON.stringify(output, null, 2));
console.log(JSON.stringify({ reportPath, ...output, queries: undefined, treatmentDiff: undefined, candidateSource: undefined, results: results.map(({ plans, ...result }) => result) }, null, 2));
} finally {
try {
await db?.$client.end({ timeout: 0 });
await database?.cleanup();
if (baselineDir) await fs.rm(baselineDir, { recursive: true, force: true });
} finally { clearInterval(keepAlive); }
}