Skip to content

Commit 6a4681e

Browse files
Refactor agent runtime and task handling
1 parent 8e5de51 commit 6a4681e

12 files changed

Lines changed: 1086 additions & 386 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@agentpond/duckdb": patch
3+
---
4+
5+
Speed up DuckDB sync by batching raw event writes and timestamp-ordered projection, and rename the cache class to AgentPondCache.

apps/cli/src/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
loadEnvFile,
1010
S3ObjectStore,
1111
} from "@agentpond/core";
12-
import { AgentPondDuckDb } from "@agentpond/duckdb";
12+
import { AgentPondCache } from "@agentpond/duckdb";
1313
import { manualTraceResourceSpans } from "./otel-trace.js";
1414
import {
1515
writeEventsAndSyncCache,
@@ -39,7 +39,7 @@ export async function main(argv = process.argv): Promise<void> {
3939
try {
4040
if (!resource || parsed.flags.help || parsed.flags.h) return printHelp();
4141
if (resource === "sync") {
42-
const db = new AgentPondDuckDb(config.dbPath);
42+
const db = new AgentPondCache(config.dbPath);
4343
const result = await db.syncFromStore({
4444
store: new S3ObjectStore(config.s3),
4545
projectId: config.projectId,
@@ -51,7 +51,7 @@ export async function main(argv = process.argv): Promise<void> {
5151
if (resource === "sql") {
5252
const query = rest.length > 0 ? [action, ...rest].join(" ") : action;
5353
if (!query) throw new CliError("Missing SQL query");
54-
const db = new AgentPondDuckDb(config.dbPath);
54+
const db = new AgentPondCache(config.dbPath);
5555
const rows = await db.query(query);
5656
await db.close();
5757
return print(rows, json);
@@ -63,7 +63,7 @@ export async function main(argv = process.argv): Promise<void> {
6363
return createTrace(parsed, config, json);
6464
}
6565

66-
const db = new AgentPondDuckDb(config.dbPath);
66+
const db = new AgentPondCache(config.dbPath);
6767
const rows = await runReadCommand(db, resource, action, rest, parsed);
6868
await db.close();
6969
return print(rows, json);
@@ -141,7 +141,7 @@ async function createTrace(
141141
}
142142

143143
async function runReadCommand(
144-
db: AgentPondDuckDb,
144+
db: AgentPondCache,
145145
resource: string,
146146
action: string | undefined,
147147
rest: string[],

apps/cli/src/sync-write.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type IngestionEvent,
66
type ObjectStore,
77
} from "@agentpond/core";
8-
import { AgentPondDuckDb } from "@agentpond/duckdb";
8+
import { AgentPondCache } from "@agentpond/duckdb";
99

1010
export async function writeOtelAndSyncCache(
1111
config: Pick<AgentPondConfig, "dbPath" | "projectId" | "s3">,
@@ -18,7 +18,7 @@ export async function writeOtelAndSyncCache(
1818
prefix: config.s3.prefix,
1919
});
2020
const object = await writer.writeOtelResourceSpans(resourceSpans);
21-
const db = new AgentPondDuckDb(config.dbPath);
21+
const db = new AgentPondCache(config.dbPath);
2222
try {
2323
await db.syncFromStore({
2424
store,
@@ -42,7 +42,7 @@ export async function writeEventsAndSyncCache(
4242
prefix: config.s3.prefix,
4343
});
4444
const manifest = await writer.writeAcceptedEvents(events);
45-
const db = new AgentPondDuckDb(config.dbPath);
45+
const db = new AgentPondCache(config.dbPath);
4646
try {
4747
await db.syncFromStore({
4848
store,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { mkdirSync } from "node:fs";
2+
import { dirname } from "node:path";
3+
import { type DuckDBConnection, DuckDBInstance } from "@duckdb/node-api";
4+
5+
type ProcessedTable = "processed_manifests" | "processed_objects";
6+
7+
export class DuckDbOperations {
8+
private instance?: DuckDBInstance;
9+
private connection?: DuckDBConnection;
10+
11+
constructor(readonly dbPath: string) {
12+
mkdirSync(dirname(dbPath), { recursive: true });
13+
}
14+
15+
async exec(sqlText: string): Promise<void> {
16+
await (await this.getConnection()).run(sqlText);
17+
}
18+
19+
async all<T = Record<string, unknown>>(sqlText: string): Promise<T[]> {
20+
const reader = await (await this.getConnection()).runAndReadAll(sqlText);
21+
return reader.getRowObjectsJS() as T[];
22+
}
23+
24+
sql(value: unknown): string {
25+
return sql(value);
26+
}
27+
28+
async processedKeyExists(
29+
table: ProcessedTable,
30+
key: string,
31+
): Promise<boolean> {
32+
const rows = await this.all(
33+
`SELECT key FROM ${table} WHERE key = ${this.sql(key)} LIMIT 1`,
34+
);
35+
return rows.length > 0;
36+
}
37+
38+
async insertProcessedKey(
39+
table: ProcessedTable,
40+
key: string,
41+
manifestKey?: string,
42+
): Promise<void> {
43+
if (table === "processed_objects") {
44+
await this.exec(
45+
`INSERT INTO processed_objects (key, manifest_key) VALUES (${this.sql(key)}, ${this.sql(manifestKey)})`,
46+
);
47+
return;
48+
}
49+
await this.exec(
50+
`INSERT INTO processed_manifests (key) VALUES (${this.sql(key)})`,
51+
);
52+
}
53+
54+
async close(): Promise<void> {
55+
if (this.connection) {
56+
const connection = this.connection;
57+
this.connection = undefined;
58+
connection.closeSync();
59+
}
60+
if (this.instance) {
61+
const instance = this.instance;
62+
this.instance = undefined;
63+
instance.closeSync();
64+
}
65+
}
66+
67+
private async getConnection(): Promise<DuckDBConnection> {
68+
if (!this.connection) {
69+
this.instance = await DuckDBInstance.create(this.dbPath);
70+
this.connection = await this.instance.connect();
71+
}
72+
return this.connection;
73+
}
74+
}
75+
76+
export function sql(value: unknown): string {
77+
if (value === null || value === undefined) return "NULL";
78+
return `'${String(value).replaceAll("'", "''")}'`;
79+
}

0 commit comments

Comments
 (0)