Stop shipping megabytes of skill prompts in every snapshot refresh

The whole app turned to molasses because snapshot.load() shipped the
entire skill_runtime_snapshots table - unbounded, compiled_prompt
included - over IPC on every refresh. A real profile carried 142 rows
totaling 144MB (1.1MB each from the pre-cap CEO compile-all era), so
every coalesced refresh serialized ~144MB of JSON. The forced-humanizer
rework made it worse by adding a row per run for every agent.

- Drop skillRuntimeSnapshots, skillRevisions, skillTests, and
  skillBindings from the renderer snapshot: no renderer surface reads
  them; skill internals are main-process state. Only the lightweight
  skillDefinitions catalogue still ships.
- Project provider_requests without raw_response_json (4.1MB across the
  newest 100 rows; RunsPage only reads responseText)
- Migration 051 purges the bug-era compiled prompts from existing DBs
- resolveRuntimeSnapshot is now idempotent per run: a run re-queued by
  startup recovery reuses its frozen snapshot instead of dying on the
  uniqueness guard and burning real retry budget
- Regression test pins the contract: a 2MB database row must not add
  bytes to the snapshot (payload asserted under 400KB)
This commit is contained in:
ZacharyZhang-NY committed 2026-07-08 23:20:56 -04:00
1 parent 4ab20e679c
commit 1f89ea7089
10 files changed
+117 -34

No files matched your search

+4 -1
View File
@@ -178,9 +178,12 @@ export class SkillService {
* compiled prompt instead of silently overflowing the model's context.
*/
resolveRuntimeSnapshot(input: ResolveAgentSkillsInput): string {
// Idempotent per run: a run interrupted after resolving (crash, quit) is
// re-queued by startup recovery and must reuse its frozen skill set, not
// die on a uniqueness error that burns real retry budget.
const existingSnapshot = this.db.prepare("select id from skill_runtime_snapshots where agent_id = ? and run_id = ?")
.get(input.agentId, input.runId) as { id: string } | undefined;
if (existingSnapshot) throw new Error("Skill runtime snapshot already exists for this run.");
if (existingSnapshot) return existingSnapshot.id;
const forcedRows = this.db.prepare(`
select
sd.id as skill_id,
+9 -7
View File
@@ -62,8 +62,6 @@ export class SnapshotService {
);
const providerUsageWindows = list<ProviderUsageWindowRow>(this.db, "provider_usage_windows", "window_start desc");
const providerRateLimits = buildProviderRateLimits(providers, providerUsageWindows);
const skillBindings = list<AppSnapshot["skillBindings"][number]>(this.db, "skill_bindings", "priority asc")
.map((binding) => ({ ...binding, enabled: toBool(binding.enabled) }));
const files = list<AppSnapshot["files"][number]>(this.db, "file_objects", "created_at desc")
.map((file) => ({ ...file, pinned: toBool(file.pinned) }));
const legalHolds = list<AppSnapshot["legalHolds"][number]>(this.db, "legal_holds", "created_at desc")
@@ -115,14 +113,18 @@ export class SnapshotService {
providerAuthProfiles,
providerAuthSessions,
providerCatalog: PROVIDER_CATALOG,
providerRequests: recentList(this.db, "provider_requests", "created_at desc", 100),
// Explicit projection: raw_response_json alone weighs megabytes across
// 100 rows and no renderer surface reads it.
providerRequests: all<Row>(this.db.prepare(`
select id, org_id, provider_id, provider_key, workspace_id, model, prompt_sha256,
response_text, state, error_message, input_char_count, output_char_count,
execution_mode, policy_decision, external_response_id, stream_event_count,
cancelled_at, created_at, completed_at
from provider_requests order by created_at desc limit 100
`)).map((row) => camel(row) as unknown as AppSnapshot["providerRequests"][number]),
providerRateLimits,
providerRateLimitObservations,
skillDefinitions: list(this.db, "skill_definitions", "created_at asc"),
skillRevisions: list(this.db, "skill_revisions", "published_at desc"),
skillBindings,
skillRuntimeSnapshots: list(this.db, "skill_runtime_snapshots", "created_at desc"),
skillTests: list(this.db, "skill_tests", "last_run_at desc, id asc"),
socialAccounts: list(this.db, "social_accounts", "created_at asc"),
socialAccountGrants: list(this.db, "social_account_grants", "created_at asc"),
browserSessionContainers: list<AppSnapshot["browserSessionContainers"][number]>(this.db, "browser_session_containers", "created_at asc")