@elizaos/plugin-trajectory-logger 2.0.0-alpha.6 → 2.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
12
 
13
- // ../../../../node_modules/drizzle-orm/entity.js
13
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/entity.js
14
14
  function is(value, type) {
15
15
  if (!value || typeof value !== "object") {
16
16
  return false;
@@ -38,7 +38,7 @@ var init_entity = __esm(() => {
38
38
  hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
39
39
  });
40
40
 
41
- // ../../../../node_modules/drizzle-orm/column.js
41
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/column.js
42
42
  var Column;
43
43
  var init_column = __esm(() => {
44
44
  init_entity();
@@ -92,7 +92,7 @@ var init_column = __esm(() => {
92
92
  };
93
93
  });
94
94
 
95
- // ../../../../node_modules/drizzle-orm/column-builder.js
95
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/column-builder.js
96
96
  var ColumnBuilder;
97
97
  var init_column_builder = __esm(() => {
98
98
  init_entity();
@@ -152,19 +152,19 @@ var init_column_builder = __esm(() => {
152
152
  };
153
153
  });
154
154
 
155
- // ../../../../node_modules/drizzle-orm/table.utils.js
155
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/table.utils.js
156
156
  var TableName;
157
157
  var init_table_utils = __esm(() => {
158
158
  TableName = Symbol.for("drizzle:Name");
159
159
  });
160
160
 
161
- // ../../../../node_modules/drizzle-orm/tracing-utils.js
161
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/tracing-utils.js
162
162
  function iife(fn, ...args) {
163
163
  return fn(...args);
164
164
  }
165
165
  var init_tracing_utils = () => {};
166
166
 
167
- // ../../../../node_modules/drizzle-orm/pg-core/unique-constraint.js
167
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/pg-core/unique-constraint.js
168
168
  function uniqueKeyName(table, columns) {
169
169
  return `${table[TableName]}_${columns.join("_")}_unique`;
170
170
  }
@@ -172,7 +172,7 @@ var init_unique_constraint = __esm(() => {
172
172
  init_table_utils();
173
173
  });
174
174
 
175
- // ../../../../node_modules/drizzle-orm/pg-core/columns/common.js
175
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/pg-core/columns/common.js
176
176
  var PgColumn, ExtraConfigColumn;
177
177
  var init_common = __esm(() => {
178
178
  init_column();
@@ -226,7 +226,7 @@ var init_common = __esm(() => {
226
226
  };
227
227
  });
228
228
 
229
- // ../../../../node_modules/drizzle-orm/pg-core/columns/enum.js
229
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/pg-core/columns/enum.js
230
230
  function isPgEnum(obj) {
231
231
  return !!obj && typeof obj === "function" && isPgEnumSym in obj && obj[isPgEnumSym] === true;
232
232
  }
@@ -261,7 +261,7 @@ var init_enum = __esm(() => {
261
261
  };
262
262
  });
263
263
 
264
- // ../../../../node_modules/drizzle-orm/subquery.js
264
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/subquery.js
265
265
  var Subquery, WithSubquery;
266
266
  var init_subquery = __esm(() => {
267
267
  init_entity();
@@ -283,11 +283,11 @@ var init_subquery = __esm(() => {
283
283
  };
284
284
  });
285
285
 
286
- // ../../../../node_modules/drizzle-orm/version.js
286
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/version.js
287
287
  var version = "0.45.1";
288
288
  var init_version = () => {};
289
289
 
290
- // ../../../../node_modules/drizzle-orm/tracing.js
290
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/tracing.js
291
291
  var otel, rawTracer, tracer;
292
292
  var init_tracing = __esm(() => {
293
293
  init_tracing_utils();
@@ -317,13 +317,13 @@ var init_tracing = __esm(() => {
317
317
  };
318
318
  });
319
319
 
320
- // ../../../../node_modules/drizzle-orm/view-common.js
320
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/view-common.js
321
321
  var ViewBaseConfig;
322
322
  var init_view_common = __esm(() => {
323
323
  ViewBaseConfig = Symbol.for("drizzle:ViewBaseConfig");
324
324
  });
325
325
 
326
- // ../../../../node_modules/drizzle-orm/table.js
326
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/table.js
327
327
  function isTable(table) {
328
328
  return typeof table === "object" && table !== null && IsDrizzleTable in table;
329
329
  }
@@ -374,7 +374,7 @@ var init_table = __esm(() => {
374
374
  };
375
375
  });
376
376
 
377
- // ../../../../node_modules/drizzle-orm/sql/sql.js
377
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/sql.js
378
378
  function isSQLWrapper(value) {
379
379
  return value !== null && value !== undefined && typeof value.getSQL === "function";
380
380
  }
@@ -776,7 +776,7 @@ var init_sql = __esm(() => {
776
776
  };
777
777
  });
778
778
 
779
- // ../../../../node_modules/drizzle-orm/alias.js
779
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/alias.js
780
780
  function aliasedTable(table, tableAlias) {
781
781
  return new Proxy(table, new TableAliasProxyHandler(tableAlias, false));
782
782
  }
@@ -877,7 +877,7 @@ var init_alias = __esm(() => {
877
877
  };
878
878
  });
879
879
 
880
- // ../../../../node_modules/drizzle-orm/errors.js
880
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/errors.js
881
881
  var DrizzleError, DrizzleQueryError, TransactionRollbackError;
882
882
  var init_errors = __esm(() => {
883
883
  init_entity();
@@ -909,7 +909,7 @@ params: ${params}`);
909
909
  };
910
910
  });
911
911
 
912
- // ../../../../node_modules/drizzle-orm/logger.js
912
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/logger.js
913
913
  var ConsoleLogWriter, DefaultLogger, NoopLogger;
914
914
  var init_logger = __esm(() => {
915
915
  init_entity();
@@ -943,7 +943,7 @@ var init_logger = __esm(() => {
943
943
  };
944
944
  });
945
945
 
946
- // ../../../../node_modules/drizzle-orm/query-promise.js
946
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/query-promise.js
947
947
  var QueryPromise;
948
948
  var init_query_promise = __esm(() => {
949
949
  init_entity();
@@ -968,7 +968,7 @@ var init_query_promise = __esm(() => {
968
968
  };
969
969
  });
970
970
 
971
- // ../../../../node_modules/drizzle-orm/utils.js
971
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/utils.js
972
972
  function mapResultRow(columns, row, joinsNotNullableMap) {
973
973
  const nullifyMap = {};
974
974
  const result = columns.reduce((result2, { path, field }, columnIndex) => {
@@ -1134,7 +1134,7 @@ var init_utils = __esm(() => {
1134
1134
  textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder;
1135
1135
  });
1136
1136
 
1137
- // ../../../../node_modules/drizzle-orm/pg-core/table.js
1137
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/pg-core/table.js
1138
1138
  var InlineForeignKeys, EnableRLS, PgTable;
1139
1139
  var init_table2 = __esm(() => {
1140
1140
  init_entity();
@@ -1154,7 +1154,7 @@ var init_table2 = __esm(() => {
1154
1154
  };
1155
1155
  });
1156
1156
 
1157
- // ../../../../node_modules/drizzle-orm/pg-core/primary-keys.js
1157
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/pg-core/primary-keys.js
1158
1158
  var PrimaryKeyBuilder, PrimaryKey;
1159
1159
  var init_primary_keys = __esm(() => {
1160
1160
  init_entity();
@@ -1186,7 +1186,7 @@ var init_primary_keys = __esm(() => {
1186
1186
  };
1187
1187
  });
1188
1188
 
1189
- // ../../../../node_modules/drizzle-orm/sql/expressions/conditions.js
1189
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/expressions/conditions.js
1190
1190
  function bindIfParam(value, column) {
1191
1191
  if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
1192
1192
  return new Param(value, column);
@@ -1322,7 +1322,7 @@ var init_conditions = __esm(() => {
1322
1322
  init_sql();
1323
1323
  });
1324
1324
 
1325
- // ../../../../node_modules/drizzle-orm/sql/expressions/select.js
1325
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/expressions/select.js
1326
1326
  function asc(column) {
1327
1327
  return sql`${column} asc`;
1328
1328
  }
@@ -1333,13 +1333,13 @@ var init_select = __esm(() => {
1333
1333
  init_sql();
1334
1334
  });
1335
1335
 
1336
- // ../../../../node_modules/drizzle-orm/sql/expressions/index.js
1336
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/expressions/index.js
1337
1337
  var init_expressions = __esm(() => {
1338
1338
  init_conditions();
1339
1339
  init_select();
1340
1340
  });
1341
1341
 
1342
- // ../../../../node_modules/drizzle-orm/relations.js
1342
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/relations.js
1343
1343
  function getOperators() {
1344
1344
  return {
1345
1345
  and,
@@ -1571,7 +1571,7 @@ var init_relations = __esm(() => {
1571
1571
  };
1572
1572
  });
1573
1573
 
1574
- // ../../../../node_modules/drizzle-orm/sql/functions/aggregate.js
1574
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/functions/aggregate.js
1575
1575
  function count(expression) {
1576
1576
  return sql`count(${expression || sql.raw("*")})`.mapWith(Number);
1577
1577
  }
@@ -1602,7 +1602,7 @@ var init_aggregate = __esm(() => {
1602
1602
  init_sql();
1603
1603
  });
1604
1604
 
1605
- // ../../../../node_modules/drizzle-orm/sql/functions/vector.js
1605
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/functions/vector.js
1606
1606
  function toSql(value) {
1607
1607
  return JSON.stringify(value);
1608
1608
  }
@@ -1646,20 +1646,20 @@ var init_vector = __esm(() => {
1646
1646
  init_sql();
1647
1647
  });
1648
1648
 
1649
- // ../../../../node_modules/drizzle-orm/sql/functions/index.js
1649
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/functions/index.js
1650
1650
  var init_functions = __esm(() => {
1651
1651
  init_aggregate();
1652
1652
  init_vector();
1653
1653
  });
1654
1654
 
1655
- // ../../../../node_modules/drizzle-orm/sql/index.js
1655
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/sql/index.js
1656
1656
  var init_sql2 = __esm(() => {
1657
1657
  init_expressions();
1658
1658
  init_functions();
1659
1659
  init_sql();
1660
1660
  });
1661
1661
 
1662
- // ../../../../node_modules/drizzle-orm/index.js
1662
+ // ../../../node_modules/.bun/drizzle-orm@0.45.1/node_modules/drizzle-orm/index.js
1663
1663
  var exports_drizzle_orm = {};
1664
1664
  __export(exports_drizzle_orm, {
1665
1665
  textDecoder: () => textDecoder,
@@ -1845,7 +1845,7 @@ function sqlLiteral(v) {
1845
1845
  if (typeof v === "boolean")
1846
1846
  return v ? "TRUE" : "FALSE";
1847
1847
  if (typeof v === "object")
1848
- return `'${JSON.stringify(v).replace(/'/g, "''")}'::jsonb`;
1848
+ return `'${JSON.stringify(v).replace(/'/g, "''")}'`;
1849
1849
  return `'${String(v).replace(/'/g, "''")}'`;
1850
1850
  }
1851
1851
 
@@ -1854,9 +1854,9 @@ class TrajectoryLoggerService extends Service {
1854
1854
  capabilityDescription = "Captures and persists LLM calls, provider accesses, and full trajectories for debugging, analysis, and RL training";
1855
1855
  enabled = true;
1856
1856
  initialized = false;
1857
- activeTrajectories = new Map;
1858
1857
  activeStepIds = new Map;
1859
1858
  stepToTrajectory = new Map;
1859
+ writeQueues = new Map;
1860
1860
  static async start(runtime) {
1861
1861
  const service = new TrajectoryLoggerService(runtime);
1862
1862
  await service.initialize();
@@ -1897,9 +1897,92 @@ class TrajectoryLoggerService extends Service {
1897
1897
  return;
1898
1898
  }
1899
1899
  await this.ensureTablesExist();
1900
+ await this.backfillTrajectoriesMissingLlmCalls();
1900
1901
  this.initialized = true;
1901
1902
  logger2.info("[trajectory-logger] Trajectory logger service initialized");
1902
1903
  }
1904
+ async getTableColumnNames(tableName) {
1905
+ const names = new Set;
1906
+ try {
1907
+ const result = await this.executeRawSql(`
1908
+ SELECT column_name
1909
+ FROM information_schema.columns
1910
+ WHERE table_name = ${sqlLiteral(tableName)}
1911
+ AND table_schema NOT IN ('pg_catalog', 'information_schema')
1912
+ `);
1913
+ for (const row of result.rows) {
1914
+ const name2 = asString(pickCell(row, "column_name"));
1915
+ if (name2)
1916
+ names.add(name2);
1917
+ }
1918
+ if (names.size > 0)
1919
+ return names;
1920
+ } catch {}
1921
+ const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
1922
+ if (!safeTableName)
1923
+ return names;
1924
+ try {
1925
+ const pragma = await this.executeRawSql(`PRAGMA table_info(${safeTableName})`);
1926
+ for (const row of pragma.rows) {
1927
+ const name2 = asString(pickCell(row, "name"));
1928
+ if (name2)
1929
+ names.add(name2);
1930
+ }
1931
+ } catch {}
1932
+ return names;
1933
+ }
1934
+ async ensureTrajectoryColumnsExist() {
1935
+ const columns = await this.getTableColumnNames("trajectories");
1936
+ const requiredColumns = [
1937
+ ["scenario_id", "TEXT"],
1938
+ ["episode_id", "TEXT"],
1939
+ ["batch_id", "TEXT"],
1940
+ ["group_index", "INTEGER"],
1941
+ ["steps_json", "JSONB NOT NULL DEFAULT '[]'"],
1942
+ ["reward_components_json", "JSONB NOT NULL DEFAULT '{}'"],
1943
+ ["metrics_json", "JSONB NOT NULL DEFAULT '{}'"],
1944
+ ["metadata_json", "JSONB NOT NULL DEFAULT '{}'"],
1945
+ ["is_training_data", "BOOLEAN NOT NULL DEFAULT FALSE"],
1946
+ ["is_evaluation", "BOOLEAN NOT NULL DEFAULT FALSE"],
1947
+ ["used_in_training", "BOOLEAN NOT NULL DEFAULT FALSE"],
1948
+ ["judged_at", "TIMESTAMPTZ"]
1949
+ ];
1950
+ for (const [columnName, definition] of requiredColumns) {
1951
+ if (columns.has(columnName))
1952
+ continue;
1953
+ try {
1954
+ await this.executeRawSql(`
1955
+ ALTER TABLE trajectories
1956
+ ADD COLUMN IF NOT EXISTS ${columnName} ${definition}
1957
+ `);
1958
+ } catch (errWithIfExists) {
1959
+ try {
1960
+ await this.executeRawSql(`
1961
+ ALTER TABLE trajectories
1962
+ ADD COLUMN ${columnName} ${definition}
1963
+ `);
1964
+ } catch (errPlain) {
1965
+ const combinedMessage = `${errWithIfExists instanceof Error ? errWithIfExists.message : String(errWithIfExists)} | ${errPlain instanceof Error ? errPlain.message : String(errPlain)}`.toLowerCase();
1966
+ if (combinedMessage.includes("already exists") || combinedMessage.includes("duplicate column") || combinedMessage.includes("duplicate_column")) {
1967
+ continue;
1968
+ }
1969
+ logger2.warn(`[trajectory-logger] Failed to add trajectories.${columnName} (non-fatal): ${errPlain instanceof Error ? errPlain.message : String(errPlain)}`);
1970
+ }
1971
+ }
1972
+ }
1973
+ for (const statement of [
1974
+ `ALTER TABLE trajectories
1975
+ ALTER COLUMN start_time TYPE BIGINT USING start_time::BIGINT`,
1976
+ `ALTER TABLE trajectories
1977
+ ALTER COLUMN end_time TYPE BIGINT USING end_time::BIGINT`,
1978
+ `ALTER TABLE trajectories
1979
+ ALTER COLUMN duration_ms TYPE BIGINT USING duration_ms::BIGINT`
1980
+ ]) {
1981
+ try {
1982
+ await this.executeRawSql(statement);
1983
+ } catch {}
1984
+ }
1985
+ }
1903
1986
  async ensureTablesExist() {
1904
1987
  await this.executeRawSql(`
1905
1988
  CREATE TABLE IF NOT EXISTS trajectories (
@@ -1930,89 +2013,459 @@ class TrajectoryLoggerService extends Service {
1930
2013
  ai_judge_reward REAL,
1931
2014
  ai_judge_reasoning TEXT,
1932
2015
  judged_at TIMESTAMPTZ,
1933
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1934
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
2016
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
2017
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
2018
+ )
2019
+ `);
2020
+ await this.ensureTrajectoryColumnsExist();
2021
+ try {
2022
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_agent_id ON trajectories(agent_id)`);
2023
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_source ON trajectories(source)`);
2024
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_status ON trajectories(status)`);
2025
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_created_at ON trajectories(created_at)`);
2026
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_scenario_id ON trajectories(scenario_id)`);
2027
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_batch_id ON trajectories(batch_id)`);
2028
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_is_training ON trajectories(is_training_data)`);
2029
+ } catch (e) {
2030
+ logger2.warn(`[trajectory-logger] Failed to create indexes (non-fatal): ${e instanceof Error ? e.message : String(e)}`);
2031
+ }
2032
+ await this.executeRawSql(`
2033
+ CREATE TABLE IF NOT EXISTS trajectory_step_index (
2034
+ step_id TEXT PRIMARY KEY,
2035
+ trajectory_id TEXT NOT NULL REFERENCES trajectories(id) ON DELETE CASCADE,
2036
+ step_number INTEGER NOT NULL DEFAULT 0,
2037
+ is_active BOOLEAN NOT NULL DEFAULT FALSE,
2038
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
2039
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
2040
+ )
2041
+ `);
2042
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectory_step_index_trajectory_id ON trajectory_step_index(trajectory_id)`);
2043
+ await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectory_step_index_is_active ON trajectory_step_index(is_active)`);
2044
+ }
2045
+ normalizeTrajectoryStatus(value) {
2046
+ switch (value) {
2047
+ case "active":
2048
+ case "completed":
2049
+ case "error":
2050
+ case "timeout":
2051
+ case "terminated":
2052
+ return value;
2053
+ default:
2054
+ return "completed";
2055
+ }
2056
+ }
2057
+ async backfillTrajectoriesMissingLlmCalls() {
2058
+ const maxRows = 2000;
2059
+ let fixed = 0;
2060
+ try {
2061
+ const rows = await this.executeRawSql(`
2062
+ SELECT id, status
2063
+ FROM trajectories
2064
+ WHERE COALESCE(llm_call_count, 0) = 0
2065
+ AND status <> 'active'
2066
+ ORDER BY created_at ASC
2067
+ LIMIT ${maxRows}
2068
+ `);
2069
+ for (const row of rows.rows) {
2070
+ const trajectoryId = asString(pickCell(row, "id"));
2071
+ if (!trajectoryId)
2072
+ continue;
2073
+ const status = this.normalizeTrajectoryStatus(asString(pickCell(row, "status")));
2074
+ await this.withTrajectoryWriteLock(trajectoryId, async () => {
2075
+ const trajectory = await this.getTrajectoryById(trajectoryId);
2076
+ if (!trajectory)
2077
+ return;
2078
+ const inserted = this.ensureAtLeastOneLlmCall(trajectory, "backfill");
2079
+ if (!inserted)
2080
+ return;
2081
+ await this.persistTrajectory(trajectoryId, trajectory, status);
2082
+ fixed += 1;
2083
+ });
2084
+ }
2085
+ if (fixed > 0) {
2086
+ logger2.info(`[trajectory-logger] Backfilled ${fixed} completed trajectories with synthetic LLM calls`);
2087
+ }
2088
+ if (rows.rows.length >= maxRows) {
2089
+ logger2.warn(`[trajectory-logger] Backfill hit safety cap (${maxRows}); remaining older trajectories will be fixed lazily when touched`);
2090
+ }
2091
+ } catch (err) {
2092
+ logger2.warn(`[trajectory-logger] Failed to backfill trajectories missing LLM calls (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
2093
+ }
2094
+ }
2095
+ normalizePurpose(value) {
2096
+ switch (value) {
2097
+ case "action":
2098
+ case "reasoning":
2099
+ case "evaluation":
2100
+ case "response":
2101
+ case "other":
2102
+ return value;
2103
+ default:
2104
+ return "other";
2105
+ }
2106
+ }
2107
+ defaultEnvironmentState(timestamp = Date.now()) {
2108
+ return {
2109
+ timestamp,
2110
+ agentBalance: 0,
2111
+ agentPoints: 0,
2112
+ agentPnL: 0,
2113
+ openPositions: 0
2114
+ };
2115
+ }
2116
+ createPendingAction(stepTimestamp) {
2117
+ return {
2118
+ attemptId: "",
2119
+ timestamp: stepTimestamp,
2120
+ actionType: "pending",
2121
+ actionName: "pending",
2122
+ parameters: {},
2123
+ success: false
2124
+ };
2125
+ }
2126
+ createStep(stepId, stepNumber, envState) {
2127
+ const timestamp = envState.timestamp || Date.now();
2128
+ return {
2129
+ stepId,
2130
+ stepNumber,
2131
+ timestamp,
2132
+ environmentState: envState,
2133
+ observation: {},
2134
+ llmCalls: [],
2135
+ providerAccesses: [],
2136
+ action: this.createPendingAction(timestamp),
2137
+ reward: 0,
2138
+ done: false
2139
+ };
2140
+ }
2141
+ ensureAtLeastOneLlmCall(trajectory, source) {
2142
+ if (this.computeTotals(trajectory.steps).llmCallCount > 0) {
2143
+ return false;
2144
+ }
2145
+ const timestamp = Date.now();
2146
+ let step = trajectory.steps[trajectory.steps.length - 1];
2147
+ if (!step) {
2148
+ step = this.createStep(uuidv4(), 0, this.defaultEnvironmentState(timestamp));
2149
+ step.done = true;
2150
+ trajectory.steps.push(step);
2151
+ }
2152
+ if (!Array.isArray(step.llmCalls)) {
2153
+ step.llmCalls = [];
2154
+ }
2155
+ const syntheticCall = {
2156
+ callId: uuidv4(),
2157
+ timestamp,
2158
+ model: "milady/synthetic-trajectory-fallback",
2159
+ systemPrompt: "[synthetic] inserted by trajectory logger because no LLM calls were captured",
2160
+ userPrompt: "[synthetic] this trajectory completed without recorded model activity",
2161
+ response: "[synthetic] placeholder call inserted to enforce minimum llm_call_count=1",
2162
+ temperature: 0,
2163
+ maxTokens: 0,
2164
+ promptTokens: 0,
2165
+ completionTokens: 0,
2166
+ latencyMs: 0,
2167
+ purpose: "other",
2168
+ actionType: "TRAJECTORY_FALLBACK"
2169
+ };
2170
+ step.llmCalls.push(syntheticCall);
2171
+ if (!step.metadata)
2172
+ step.metadata = {};
2173
+ step.metadata.syntheticLlmCall = true;
2174
+ step.metadata.syntheticLlmCallSource = source;
2175
+ trajectory.metadata.syntheticLlmCall = true;
2176
+ trajectory.metadata.syntheticLlmCallSource = source;
2177
+ const existingFallbackCount = trajectory.metrics.syntheticLlmFallbackCount;
2178
+ trajectory.metrics.syntheticLlmFallbackCount = typeof existingFallbackCount === "number" && Number.isFinite(existingFallbackCount) ? existingFallbackCount + 1 : 1;
2179
+ return true;
2180
+ }
2181
+ computeTotals(steps) {
2182
+ let llmCallCount = 0;
2183
+ let providerAccessCount = 0;
2184
+ let totalPromptTokens = 0;
2185
+ let totalCompletionTokens = 0;
2186
+ for (const step of steps) {
2187
+ const llmCalls = Array.isArray(step.llmCalls) ? step.llmCalls : [];
2188
+ const providerAccesses = Array.isArray(step.providerAccesses) ? step.providerAccesses : [];
2189
+ llmCallCount += llmCalls.length;
2190
+ providerAccessCount += providerAccesses.length;
2191
+ for (const call of llmCalls) {
2192
+ totalPromptTokens += call.promptTokens ?? 0;
2193
+ totalCompletionTokens += call.completionTokens ?? 0;
2194
+ }
2195
+ }
2196
+ return {
2197
+ stepCount: steps.length,
2198
+ llmCallCount,
2199
+ providerAccessCount,
2200
+ totalPromptTokens,
2201
+ totalCompletionTokens
2202
+ };
2203
+ }
2204
+ async withTrajectoryWriteLock(trajectoryId, task) {
2205
+ const previous = this.writeQueues.get(trajectoryId) ?? Promise.resolve();
2206
+ const next = previous.catch(() => {}).then(task);
2207
+ this.writeQueues.set(trajectoryId, next);
2208
+ try {
2209
+ await next;
2210
+ } finally {
2211
+ if (this.writeQueues.get(trajectoryId) === next) {
2212
+ this.writeQueues.delete(trajectoryId);
2213
+ }
2214
+ }
2215
+ }
2216
+ async getTrajectoryById(trajectoryId) {
2217
+ const result = await this.executeRawSql(`SELECT * FROM trajectories WHERE id = ${sqlLiteral(trajectoryId)} LIMIT 1`);
2218
+ if (result.rows.length === 0)
2219
+ return null;
2220
+ return this.rowToTrajectory(result.rows[0]);
2221
+ }
2222
+ async getStepIndex(stepId) {
2223
+ const result = await this.executeRawSql(`SELECT trajectory_id, step_number, is_active FROM trajectory_step_index WHERE step_id = ${sqlLiteral(stepId)} LIMIT 1`);
2224
+ const row = result.rows[0];
2225
+ if (!row)
2226
+ return null;
2227
+ const trajectoryId = asString(pickCell(row, "trajectory_id"));
2228
+ if (!trajectoryId)
2229
+ return null;
2230
+ const stepNumber = asNumber(pickCell(row, "step_number")) ?? 0;
2231
+ const isActiveText = asString(pickCell(row, "is_active"));
2232
+ const isActive = isActiveText === "true" || isActiveText === "t" || pickCell(row, "is_active") === true;
2233
+ return { trajectoryId, stepNumber, isActive };
2234
+ }
2235
+ async setStepIndex(stepId, trajectoryId, stepNumber, isActive) {
2236
+ await this.executeRawSql(`
2237
+ INSERT INTO trajectory_step_index (
2238
+ step_id, trajectory_id, step_number, is_active, updated_at
2239
+ ) VALUES (
2240
+ ${sqlLiteral(stepId)},
2241
+ ${sqlLiteral(trajectoryId)},
2242
+ ${stepNumber},
2243
+ ${isActive ? "TRUE" : "FALSE"},
2244
+ CURRENT_TIMESTAMP
1935
2245
  )
2246
+ ON CONFLICT (step_id) DO UPDATE SET
2247
+ trajectory_id = EXCLUDED.trajectory_id,
2248
+ step_number = EXCLUDED.step_number,
2249
+ is_active = EXCLUDED.is_active,
2250
+ updated_at = CURRENT_TIMESTAMP
1936
2251
  `);
1937
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_agent_id ON trajectories(agent_id)`);
1938
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_source ON trajectories(source)`);
1939
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_status ON trajectories(status)`);
1940
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_created_at ON trajectories(created_at)`);
1941
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_scenario_id ON trajectories(scenario_id)`);
1942
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_batch_id ON trajectories(batch_id)`);
1943
- await this.executeRawSql(`CREATE INDEX IF NOT EXISTS idx_trajectories_is_training ON trajectories(is_training_data)`);
2252
+ }
2253
+ async markAllStepsInactive(trajectoryId) {
2254
+ await this.executeRawSql(`
2255
+ UPDATE trajectory_step_index
2256
+ SET is_active = FALSE, updated_at = CURRENT_TIMESTAMP
2257
+ WHERE trajectory_id = ${sqlLiteral(trajectoryId)}
2258
+ `);
2259
+ }
2260
+ async resolveTrajectoryId(stepIdOrTrajectoryId) {
2261
+ const cached = this.stepToTrajectory.get(stepIdOrTrajectoryId);
2262
+ if (cached)
2263
+ return cached;
2264
+ const byStep = await this.getStepIndex(stepIdOrTrajectoryId);
2265
+ if (byStep?.trajectoryId) {
2266
+ this.stepToTrajectory.set(stepIdOrTrajectoryId, byStep.trajectoryId);
2267
+ return byStep.trajectoryId;
2268
+ }
2269
+ const byId = await this.executeRawSql(`SELECT id FROM trajectories WHERE id = ${sqlLiteral(stepIdOrTrajectoryId)} LIMIT 1`);
2270
+ const row = byId.rows[0];
2271
+ const id = row ? asString(pickCell(row, "id")) : null;
2272
+ return id;
2273
+ }
2274
+ async getCurrentStepIdFromDb(trajectoryId) {
2275
+ const result = await this.executeRawSql(`
2276
+ SELECT step_id
2277
+ FROM trajectory_step_index
2278
+ WHERE trajectory_id = ${sqlLiteral(trajectoryId)} AND is_active = TRUE
2279
+ ORDER BY step_number DESC, updated_at DESC
2280
+ LIMIT 1
2281
+ `);
2282
+ const row = result.rows[0];
2283
+ return row ? asString(pickCell(row, "step_id")) : null;
2284
+ }
2285
+ async persistTrajectory(trajectoryId, trajectory, status = "active") {
2286
+ if (status !== "active") {
2287
+ this.ensureAtLeastOneLlmCall(trajectory, "finalize");
2288
+ }
2289
+ const totals = this.computeTotals(trajectory.steps);
2290
+ const isFinalStatus = status !== "active";
2291
+ const persistedEndTime = isFinalStatus ? trajectory.endTime : null;
2292
+ const persistedDuration = isFinalStatus ? trajectory.durationMs : null;
2293
+ const updatedAtIso = new Date().toISOString();
2294
+ try {
2295
+ await this.executeRawSql(`
2296
+ UPDATE trajectories SET
2297
+ status = ${sqlLiteral(status)},
2298
+ end_time = ${sqlLiteral(persistedEndTime)},
2299
+ duration_ms = ${sqlLiteral(persistedDuration)},
2300
+ step_count = ${totals.stepCount},
2301
+ llm_call_count = ${totals.llmCallCount},
2302
+ provider_access_count = ${totals.providerAccessCount},
2303
+ total_prompt_tokens = ${totals.totalPromptTokens},
2304
+ total_completion_tokens = ${totals.totalCompletionTokens},
2305
+ total_reward = ${trajectory.totalReward},
2306
+ steps_json = ${sqlLiteral(trajectory.steps)},
2307
+ reward_components_json = ${sqlLiteral(trajectory.rewardComponents)},
2308
+ metrics_json = ${sqlLiteral(trajectory.metrics)},
2309
+ metadata_json = ${sqlLiteral(trajectory.metadata)},
2310
+ updated_at = ${sqlLiteral(updatedAtIso)}
2311
+ WHERE id = ${sqlLiteral(trajectoryId)}
2312
+ `);
2313
+ } catch (modernErr) {
2314
+ await this.executeRawSql(`
2315
+ UPDATE trajectories SET
2316
+ status = ${sqlLiteral(status)},
2317
+ end_time = ${sqlLiteral(persistedEndTime)},
2318
+ duration_ms = ${sqlLiteral(persistedDuration)},
2319
+ step_count = ${totals.stepCount},
2320
+ llm_call_count = ${totals.llmCallCount},
2321
+ provider_access_count = ${totals.providerAccessCount},
2322
+ total_prompt_tokens = ${totals.totalPromptTokens},
2323
+ total_completion_tokens = ${totals.totalCompletionTokens},
2324
+ total_reward = ${trajectory.totalReward},
2325
+ steps_json = ${sqlLiteral(trajectory.steps)},
2326
+ metadata = ${sqlLiteral(trajectory.metadata)},
2327
+ updated_at = ${sqlLiteral(updatedAtIso)}
2328
+ WHERE id = ${sqlLiteral(trajectoryId)}
2329
+ `).catch((legacyErr) => {
2330
+ logger2.warn({ err: legacyErr, trajectoryId }, `[trajectory-logger] Failed to persist trajectory update after compatibility fallback: ${modernErr instanceof Error ? modernErr.message : String(modernErr)}`);
2331
+ throw legacyErr;
2332
+ });
2333
+ }
2334
+ }
2335
+ async ensureStepExists(trajectory, stepId) {
2336
+ let step = trajectory.steps.find((entry) => entry.stepId === stepId);
2337
+ if (step) {
2338
+ if (!Array.isArray(step.llmCalls))
2339
+ step.llmCalls = [];
2340
+ if (!Array.isArray(step.providerAccesses))
2341
+ step.providerAccesses = [];
2342
+ return step;
2343
+ }
2344
+ const index = await this.getStepIndex(stepId);
2345
+ const stepNumber = index?.stepNumber ?? trajectory.steps.length;
2346
+ step = this.createStep(stepId, stepNumber, this.defaultEnvironmentState());
2347
+ trajectory.steps.push(step);
2348
+ trajectory.steps.sort((a, b) => a.stepNumber - b.stepNumber);
2349
+ return step;
1944
2350
  }
1945
2351
  logLlmCall(params) {
1946
2352
  if (!this.enabled)
1947
2353
  return;
1948
- const trajectoryId = this.stepToTrajectory.get(params.stepId);
1949
- if (!trajectoryId) {
1950
- logger2.debug({ stepId: params.stepId }, "[trajectory-logger] No active trajectory for LLM call");
1951
- return;
1952
- }
1953
- const trajectory = this.activeTrajectories.get(trajectoryId);
1954
- if (!trajectory)
1955
- return;
1956
- const currentStepId = this.activeStepIds.get(trajectoryId);
1957
- const step = trajectory.steps.find((s) => s.stepId === currentStepId);
1958
- if (step) {
1959
- const llmCall = {
1960
- callId: uuidv4(),
1961
- timestamp: Date.now(),
1962
- model: params.model,
1963
- systemPrompt: params.systemPrompt,
1964
- userPrompt: params.userPrompt,
1965
- response: params.response,
1966
- temperature: params.temperature,
1967
- maxTokens: params.maxTokens,
1968
- purpose: params.purpose,
1969
- actionType: params.actionType,
1970
- promptTokens: params.promptTokens,
1971
- completionTokens: params.completionTokens,
1972
- latencyMs: params.latencyMs
1973
- };
1974
- step.llmCalls.push(llmCall);
1975
- }
1976
- this.updateTrajectoryCountsInMemory(trajectoryId, {
1977
- llmCallCount: 1,
1978
- promptTokens: params.promptTokens ?? 0,
1979
- completionTokens: params.completionTokens ?? 0
2354
+ (async () => {
2355
+ const trajectoryId = await this.resolveTrajectoryId(params.stepId);
2356
+ if (!trajectoryId) {
2357
+ logger2.debug({ stepId: params.stepId }, "[trajectory-logger] No trajectory mapping for LLM call");
2358
+ return;
2359
+ }
2360
+ await this.withTrajectoryWriteLock(trajectoryId, async () => {
2361
+ const trajectory = await this.getTrajectoryById(trajectoryId);
2362
+ if (!trajectory)
2363
+ return;
2364
+ const step = await this.ensureStepExists(trajectory, params.stepId);
2365
+ const llmCall = {
2366
+ callId: uuidv4(),
2367
+ timestamp: Date.now(),
2368
+ model: params.model,
2369
+ modelVersion: params.modelVersion,
2370
+ systemPrompt: params.systemPrompt,
2371
+ userPrompt: params.userPrompt,
2372
+ response: params.response,
2373
+ reasoning: params.reasoning,
2374
+ temperature: params.temperature,
2375
+ maxTokens: params.maxTokens,
2376
+ purpose: this.normalizePurpose(params.purpose),
2377
+ actionType: params.actionType,
2378
+ promptTokens: params.promptTokens,
2379
+ completionTokens: params.completionTokens,
2380
+ latencyMs: params.latencyMs
2381
+ };
2382
+ step.llmCalls.push(llmCall);
2383
+ await this.persistTrajectory(trajectoryId, trajectory, "active");
2384
+ });
2385
+ })().catch((err) => {
2386
+ logger2.warn({ err, stepId: params.stepId }, "[trajectory-logger] Failed to persist LLM call");
1980
2387
  });
1981
2388
  }
1982
- logProviderAccess(params) {
2389
+ logLLMCall(stepId, details) {
2390
+ this.logLlmCall({
2391
+ stepId,
2392
+ model: details.model,
2393
+ modelVersion: details.modelVersion,
2394
+ systemPrompt: details.systemPrompt,
2395
+ userPrompt: details.userPrompt,
2396
+ response: details.response,
2397
+ reasoning: details.reasoning,
2398
+ temperature: details.temperature,
2399
+ maxTokens: details.maxTokens,
2400
+ purpose: details.purpose,
2401
+ actionType: details.actionType ?? "",
2402
+ latencyMs: details.latencyMs ?? 0,
2403
+ promptTokens: details.promptTokens,
2404
+ completionTokens: details.completionTokens
2405
+ });
2406
+ }
2407
+ logProviderAccess(arg1, arg2) {
1983
2408
  if (!this.enabled)
1984
2409
  return;
1985
- const trajectoryId = this.stepToTrajectory.get(params.stepId);
1986
- if (!trajectoryId) {
1987
- logger2.debug({ stepId: params.stepId }, "[trajectory-logger] No active trajectory for provider access");
1988
- return;
1989
- }
1990
- const trajectory = this.activeTrajectories.get(trajectoryId);
1991
- if (!trajectory)
2410
+ const params = typeof arg1 === "string" ? {
2411
+ stepId: arg1,
2412
+ providerName: arg2?.providerName ?? "unknown",
2413
+ data: arg2?.data ?? {},
2414
+ purpose: arg2?.purpose ?? "other",
2415
+ query: arg2?.query
2416
+ } : arg1;
2417
+ (async () => {
2418
+ const trajectoryId = await this.resolveTrajectoryId(params.stepId);
2419
+ if (!trajectoryId) {
2420
+ logger2.debug({ stepId: params.stepId }, "[trajectory-logger] No trajectory mapping for provider access");
2421
+ return;
2422
+ }
2423
+ await this.withTrajectoryWriteLock(trajectoryId, async () => {
2424
+ const trajectory = await this.getTrajectoryById(trajectoryId);
2425
+ if (!trajectory)
2426
+ return;
2427
+ const step = await this.ensureStepExists(trajectory, params.stepId);
2428
+ const access = {
2429
+ providerId: uuidv4(),
2430
+ providerName: params.providerName,
2431
+ timestamp: Date.now(),
2432
+ data: params.data,
2433
+ query: params.query,
2434
+ purpose: params.purpose
2435
+ };
2436
+ step.providerAccesses.push(access);
2437
+ await this.persistTrajectory(trajectoryId, trajectory, "active");
2438
+ });
2439
+ })().catch((err) => {
2440
+ logger2.warn({ err, stepId: params.stepId }, "[trajectory-logger] Failed to persist provider access");
2441
+ });
2442
+ }
2443
+ logProviderAccessByTrajectoryId(trajectoryId, access) {
2444
+ const stepId = this.getCurrentStepId(trajectoryId);
2445
+ if (!stepId) {
2446
+ logger2.debug({ trajectoryId }, "[trajectory-logger] No active step for provider access by trajectory");
1992
2447
  return;
1993
- const currentStepId = this.activeStepIds.get(trajectoryId);
1994
- const step = trajectory.steps.find((s) => s.stepId === currentStepId);
1995
- if (step) {
1996
- const access = {
1997
- providerId: uuidv4(),
1998
- providerName: params.providerName,
1999
- timestamp: Date.now(),
2000
- data: params.data,
2001
- query: params.query,
2002
- purpose: params.purpose
2003
- };
2004
- step.providerAccesses.push(access);
2005
2448
  }
2449
+ this.logProviderAccess(stepId, access);
2006
2450
  }
2007
- updateTrajectoryCountsInMemory(trajectoryId, counts) {}
2008
- async startTrajectory(stepId, options) {
2451
+ async startTrajectory(stepIdOrAgentId, options = {}) {
2009
2452
  if (!this.enabled)
2010
- return stepId;
2453
+ return uuidv4();
2454
+ const legacyStepId = typeof options.agentId === "string" && options.agentId.length > 0 ? stepIdOrAgentId : null;
2455
+ const agentId = (typeof options.agentId === "string" && options.agentId.length > 0 ? options.agentId : stepIdOrAgentId) ?? stepIdOrAgentId;
2011
2456
  const trajectoryId = uuidv4();
2012
2457
  const now = Date.now();
2458
+ const timestampIso = new Date(now).toISOString();
2459
+ const metadata = {
2460
+ ...options.metadata ?? {}
2461
+ };
2462
+ if (options.roomId)
2463
+ metadata.roomId = options.roomId;
2464
+ if (options.entityId)
2465
+ metadata.entityId = options.entityId;
2013
2466
  const trajectory = {
2014
2467
  trajectoryId,
2015
- agentId: options.agentId,
2468
+ agentId,
2016
2469
  startTime: now,
2017
2470
  endTime: now,
2018
2471
  durationMs: 0,
@@ -2029,28 +2482,19 @@ class TrajectoryLoggerService extends Service {
2029
2482
  },
2030
2483
  metadata: {
2031
2484
  source: options.source ?? "chat",
2032
- roomId: options.roomId,
2033
- entityId: options.entityId,
2034
- ...options.metadata ?? {}
2485
+ ...metadata
2035
2486
  }
2036
2487
  };
2037
- this.activeTrajectories.set(trajectoryId, trajectory);
2038
- this.stepToTrajectory.set(stepId, trajectoryId);
2039
- this.startStep(trajectoryId, {
2040
- timestamp: now,
2041
- agentBalance: 0,
2042
- agentPoints: 0,
2043
- agentPnL: 0,
2044
- openPositions: 0
2045
- });
2488
+ let persistedStart = false;
2046
2489
  try {
2047
2490
  await this.executeRawSql(`
2048
2491
  INSERT INTO trajectories (
2049
2492
  id, agent_id, source, status, start_time, scenario_id, episode_id,
2050
- batch_id, group_index, metadata_json
2493
+ batch_id, group_index, metadata_json, steps_json, reward_components_json, metrics_json,
2494
+ created_at, updated_at
2051
2495
  ) VALUES (
2052
2496
  ${sqlLiteral(trajectoryId)},
2053
- ${sqlLiteral(options.agentId)},
2497
+ ${sqlLiteral(agentId)},
2054
2498
  ${sqlLiteral(options.source ?? "chat")},
2055
2499
  'active',
2056
2500
  ${now},
@@ -2058,124 +2502,139 @@ class TrajectoryLoggerService extends Service {
2058
2502
  ${sqlLiteral(options.episodeId ?? null)},
2059
2503
  ${sqlLiteral(options.batchId ?? null)},
2060
2504
  ${options.groupIndex ?? "NULL"},
2061
- ${sqlLiteral(trajectory.metadata)}
2505
+ ${sqlLiteral(trajectory.metadata)},
2506
+ ${sqlLiteral([])},
2507
+ ${sqlLiteral(trajectory.rewardComponents)},
2508
+ ${sqlLiteral(trajectory.metrics)},
2509
+ ${sqlLiteral(timestampIso)},
2510
+ ${sqlLiteral(timestampIso)}
2062
2511
  )
2063
2512
  `);
2513
+ persistedStart = true;
2064
2514
  } catch (err) {
2065
- logger2.warn({ err, trajectoryId }, "[trajectory-logger] Failed to persist trajectory start");
2515
+ try {
2516
+ await this.executeRawSql(`
2517
+ INSERT INTO trajectories (
2518
+ id, agent_id, source, status, start_time, steps_json, metadata, created_at, updated_at
2519
+ ) VALUES (
2520
+ ${sqlLiteral(trajectoryId)},
2521
+ ${sqlLiteral(agentId)},
2522
+ ${sqlLiteral(options.source ?? "chat")},
2523
+ 'active',
2524
+ ${now},
2525
+ ${sqlLiteral([])},
2526
+ ${sqlLiteral(trajectory.metadata)},
2527
+ ${sqlLiteral(timestampIso)},
2528
+ ${sqlLiteral(timestampIso)}
2529
+ )
2530
+ `);
2531
+ persistedStart = true;
2532
+ } catch (legacyErr) {
2533
+ logger2.warn({ err: legacyErr, trajectoryId }, "[trajectory-logger] Failed to persist trajectory start");
2534
+ }
2535
+ }
2536
+ if (persistedStart && legacyStepId) {
2537
+ this.stepToTrajectory.set(legacyStepId, trajectoryId);
2538
+ try {
2539
+ await this.setStepIndex(legacyStepId, trajectoryId, -1, false);
2540
+ } catch (indexErr) {
2541
+ logger2.warn({ err: indexErr, trajectoryId, stepId: legacyStepId }, "[trajectory-logger] Failed to persist step index for trajectory start");
2542
+ }
2066
2543
  }
2067
2544
  return trajectoryId;
2068
2545
  }
2069
2546
  startStep(trajectoryId, envState) {
2547
+ if (!this.enabled)
2548
+ return uuidv4();
2070
2549
  const stepId = uuidv4();
2071
- const trajectory = this.activeTrajectories.get(trajectoryId);
2072
- if (!trajectory) {
2073
- logger2.warn({ trajectoryId }, "[trajectory-logger] Trajectory not found for startStep");
2074
- return stepId;
2075
- }
2076
- const step = {
2077
- stepId,
2078
- stepNumber: trajectory.steps.length,
2079
- timestamp: envState.timestamp || Date.now(),
2080
- environmentState: envState,
2081
- observation: {},
2082
- llmCalls: [],
2083
- providerAccesses: [],
2084
- action: {
2085
- attemptId: "",
2086
- timestamp: 0,
2087
- actionType: "pending",
2088
- actionName: "pending",
2089
- parameters: {},
2090
- success: false
2091
- },
2092
- reward: 0,
2093
- done: false
2094
- };
2095
- trajectory.steps.push(step);
2096
2550
  this.activeStepIds.set(trajectoryId, stepId);
2551
+ this.stepToTrajectory.set(stepId, trajectoryId);
2552
+ this.withTrajectoryWriteLock(trajectoryId, async () => {
2553
+ const trajectory = await this.getTrajectoryById(trajectoryId);
2554
+ if (!trajectory) {
2555
+ logger2.warn({ trajectoryId }, "[trajectory-logger] Trajectory not found for startStep");
2556
+ return;
2557
+ }
2558
+ const step = this.createStep(stepId, trajectory.steps.length, envState);
2559
+ trajectory.steps.push(step);
2560
+ await this.markAllStepsInactive(trajectoryId);
2561
+ await this.setStepIndex(stepId, trajectoryId, step.stepNumber, true);
2562
+ await this.persistTrajectory(trajectoryId, trajectory, "active");
2563
+ }).catch((err) => {
2564
+ logger2.warn({ err, trajectoryId, stepId }, "[trajectory-logger] Failed to persist startStep");
2565
+ });
2097
2566
  return stepId;
2098
2567
  }
2099
- completeStep(trajectoryId, action, rewardInfo) {
2100
- const trajectory = this.activeTrajectories.get(trajectoryId);
2101
- if (!trajectory)
2568
+ completeStep(trajectoryId, actionOrStepId, actionOrReward, maybeReward) {
2569
+ if (!this.enabled)
2102
2570
  return;
2103
- const stepId = this.activeStepIds.get(trajectoryId);
2104
- const step = trajectory.steps.find((s) => s.stepId === stepId);
2105
- if (!step)
2571
+ const explicitStepId = typeof actionOrStepId === "string" ? actionOrStepId : null;
2572
+ const action = typeof actionOrStepId === "string" ? actionOrReward : actionOrStepId;
2573
+ const rewardInfo = typeof actionOrStepId === "string" ? maybeReward : actionOrReward;
2574
+ if (!action)
2106
2575
  return;
2107
- step.action = {
2108
- attemptId: uuidv4(),
2109
- timestamp: Date.now(),
2110
- ...action
2111
- };
2112
- if (rewardInfo?.reward !== undefined) {
2113
- step.reward = rewardInfo.reward;
2114
- trajectory.totalReward += rewardInfo.reward;
2115
- }
2116
- if (rewardInfo?.components) {
2117
- trajectory.rewardComponents = {
2118
- ...trajectory.rewardComponents,
2119
- ...rewardInfo.components
2576
+ this.withTrajectoryWriteLock(trajectoryId, async () => {
2577
+ const trajectory = await this.getTrajectoryById(trajectoryId);
2578
+ if (!trajectory)
2579
+ return;
2580
+ const stepId = explicitStepId ?? this.activeStepIds.get(trajectoryId) ?? await this.getCurrentStepIdFromDb(trajectoryId);
2581
+ if (!stepId)
2582
+ return;
2583
+ const step = await this.ensureStepExists(trajectory, stepId);
2584
+ step.action = {
2585
+ attemptId: uuidv4(),
2586
+ timestamp: Date.now(),
2587
+ ...action
2120
2588
  };
2121
- }
2122
- this.activeStepIds.delete(trajectoryId);
2589
+ step.done = true;
2590
+ if (rewardInfo?.reward !== undefined) {
2591
+ step.reward = rewardInfo.reward;
2592
+ trajectory.totalReward += rewardInfo.reward;
2593
+ }
2594
+ if (rewardInfo?.components) {
2595
+ trajectory.rewardComponents = {
2596
+ ...trajectory.rewardComponents,
2597
+ ...rewardInfo.components
2598
+ };
2599
+ }
2600
+ await this.setStepIndex(stepId, trajectoryId, step.stepNumber, false);
2601
+ this.activeStepIds.delete(trajectoryId);
2602
+ await this.persistTrajectory(trajectoryId, trajectory, "active");
2603
+ }).catch((err) => {
2604
+ logger2.warn({ err, trajectoryId }, "[trajectory-logger] Failed to complete step");
2605
+ });
2123
2606
  }
2124
- async endTrajectory(stepIdOrTrajectoryId, status = "completed") {
2607
+ async endTrajectory(stepIdOrTrajectoryId, status = "completed", finalMetrics) {
2125
2608
  if (!this.enabled)
2126
2609
  return;
2127
- let trajectoryId = this.stepToTrajectory.get(stepIdOrTrajectoryId);
2610
+ const trajectoryId = await this.resolveTrajectoryId(stepIdOrTrajectoryId);
2128
2611
  if (!trajectoryId) {
2129
- trajectoryId = stepIdOrTrajectoryId;
2130
- }
2131
- const trajectory = this.activeTrajectories.get(trajectoryId);
2132
- if (!trajectory) {
2133
- logger2.debug({ stepIdOrTrajectoryId }, "[trajectory-logger] No active trajectory to end");
2612
+ logger2.debug({ stepIdOrTrajectoryId }, "[trajectory-logger] No trajectory to end");
2134
2613
  return;
2135
2614
  }
2136
- const now = Date.now();
2137
- trajectory.endTime = now;
2138
- trajectory.durationMs = now - trajectory.startTime;
2139
- trajectory.metrics.finalStatus = status;
2140
- trajectory.metrics.episodeLength = trajectory.steps.length;
2141
- let totalLlmCalls = 0;
2142
- let totalProviderAccesses = 0;
2143
- let totalPromptTokens = 0;
2144
- let totalCompletionTokens = 0;
2145
- for (const step of trajectory.steps) {
2146
- totalLlmCalls += step.llmCalls.length;
2147
- totalProviderAccesses += step.providerAccesses.length;
2148
- for (const call of step.llmCalls) {
2149
- totalPromptTokens += call.promptTokens ?? 0;
2150
- totalCompletionTokens += call.completionTokens ?? 0;
2615
+ await this.withTrajectoryWriteLock(trajectoryId, async () => {
2616
+ const trajectory = await this.getTrajectoryById(trajectoryId);
2617
+ if (!trajectory) {
2618
+ logger2.debug({ trajectoryId }, "[trajectory-logger] Trajectory not found while ending");
2619
+ return;
2151
2620
  }
2152
- }
2153
- try {
2154
- await this.executeRawSql(`
2155
- UPDATE trajectories SET
2156
- status = ${sqlLiteral(status)},
2157
- end_time = ${now},
2158
- duration_ms = ${trajectory.durationMs},
2159
- step_count = ${trajectory.steps.length},
2160
- llm_call_count = ${totalLlmCalls},
2161
- provider_access_count = ${totalProviderAccesses},
2162
- total_prompt_tokens = ${totalPromptTokens},
2163
- total_completion_tokens = ${totalCompletionTokens},
2164
- total_reward = ${trajectory.totalReward},
2165
- steps_json = ${sqlLiteral(trajectory.steps)},
2166
- reward_components_json = ${sqlLiteral(trajectory.rewardComponents)},
2167
- metrics_json = ${sqlLiteral(trajectory.metrics)},
2168
- metadata_json = ${sqlLiteral(trajectory.metadata)},
2169
- updated_at = NOW()
2170
- WHERE id = ${sqlLiteral(trajectoryId)}
2171
- `);
2172
- } catch (err) {
2173
- logger2.warn({ err, trajectoryId }, "[trajectory-logger] Failed to persist trajectory end");
2174
- }
2175
- this.activeTrajectories.delete(trajectoryId);
2176
- this.activeStepIds.delete(trajectoryId);
2177
- for (const [stepId, trajId] of this.stepToTrajectory.entries()) {
2178
- if (trajId === trajectoryId) {
2621
+ const now = Date.now();
2622
+ trajectory.endTime = now;
2623
+ trajectory.durationMs = now - trajectory.startTime;
2624
+ trajectory.metrics.finalStatus = status;
2625
+ trajectory.metrics.episodeLength = trajectory.steps.length;
2626
+ if (finalMetrics) {
2627
+ trajectory.metrics = {
2628
+ ...trajectory.metrics,
2629
+ ...finalMetrics
2630
+ };
2631
+ }
2632
+ await this.markAllStepsInactive(trajectoryId);
2633
+ this.activeStepIds.delete(trajectoryId);
2634
+ await this.persistTrajectory(trajectoryId, trajectory, status);
2635
+ });
2636
+ for (const [stepId, mappedTrajectoryId] of this.stepToTrajectory.entries()) {
2637
+ if (mappedTrajectoryId === trajectoryId) {
2179
2638
  this.stepToTrajectory.delete(stepId);
2180
2639
  }
2181
2640
  }
@@ -2231,23 +2690,28 @@ class TrajectoryLoggerService extends Service {
2231
2690
  ORDER BY created_at DESC
2232
2691
  LIMIT ${limit} OFFSET ${offset}
2233
2692
  `);
2234
- const trajectories = rowsResult.rows.map((row) => ({
2235
- id: asString(pickCell(row, "id")) ?? "",
2236
- agentId: asString(pickCell(row, "agent_id")) ?? "",
2237
- source: asString(pickCell(row, "source")) ?? "chat",
2238
- status: asString(pickCell(row, "status")) ?? "completed",
2239
- startTime: asNumber(pickCell(row, "start_time")) ?? 0,
2240
- endTime: asNumber(pickCell(row, "end_time")),
2241
- durationMs: asNumber(pickCell(row, "duration_ms")),
2242
- stepCount: asNumber(pickCell(row, "step_count")) ?? 0,
2243
- llmCallCount: asNumber(pickCell(row, "llm_call_count")) ?? 0,
2244
- totalPromptTokens: asNumber(pickCell(row, "total_prompt_tokens")) ?? 0,
2245
- totalCompletionTokens: asNumber(pickCell(row, "total_completion_tokens")) ?? 0,
2246
- totalReward: asNumber(pickCell(row, "total_reward")) ?? 0,
2247
- scenarioId: asString(pickCell(row, "scenario_id")),
2248
- batchId: asString(pickCell(row, "batch_id")),
2249
- createdAt: asIsoString(pickCell(row, "created_at"))
2250
- }));
2693
+ const trajectories = rowsResult.rows.map((row) => {
2694
+ const status = asString(pickCell(row, "status")) ?? "completed";
2695
+ const rawLlmCallCount = asNumber(pickCell(row, "llm_call_count")) ?? 0;
2696
+ const llmCallCount = status === "active" ? rawLlmCallCount : Math.max(1, rawLlmCallCount);
2697
+ return {
2698
+ id: asString(pickCell(row, "id")) ?? "",
2699
+ agentId: asString(pickCell(row, "agent_id")) ?? "",
2700
+ source: asString(pickCell(row, "source")) ?? "chat",
2701
+ status,
2702
+ startTime: asNumber(pickCell(row, "start_time")) ?? 0,
2703
+ endTime: asNumber(pickCell(row, "end_time")),
2704
+ durationMs: asNumber(pickCell(row, "duration_ms")),
2705
+ stepCount: asNumber(pickCell(row, "step_count")) ?? 0,
2706
+ llmCallCount,
2707
+ totalPromptTokens: asNumber(pickCell(row, "total_prompt_tokens")) ?? 0,
2708
+ totalCompletionTokens: asNumber(pickCell(row, "total_completion_tokens")) ?? 0,
2709
+ totalReward: asNumber(pickCell(row, "total_reward")) ?? 0,
2710
+ scenarioId: asString(pickCell(row, "scenario_id")),
2711
+ batchId: asString(pickCell(row, "batch_id")),
2712
+ createdAt: asIsoString(pickCell(row, "created_at"))
2713
+ };
2714
+ });
2251
2715
  return { trajectories, total, offset, limit };
2252
2716
  }
2253
2717
  async getTrajectoryDetail(trajectoryId) {
@@ -2259,7 +2723,17 @@ class TrajectoryLoggerService extends Service {
2259
2723
  if (result.rows.length === 0)
2260
2724
  return null;
2261
2725
  const row = result.rows[0];
2262
- return this.rowToTrajectory(row);
2726
+ const trajectory = this.rowToTrajectory(row);
2727
+ const status = this.normalizeTrajectoryStatus(asString(pickCell(row, "status")));
2728
+ if (status !== "active") {
2729
+ const inserted = this.ensureAtLeastOneLlmCall(trajectory, "backfill");
2730
+ if (inserted) {
2731
+ await this.withTrajectoryWriteLock(trajectoryId, async () => {
2732
+ await this.persistTrajectory(trajectoryId, trajectory, status);
2733
+ });
2734
+ }
2735
+ }
2736
+ return trajectory;
2263
2737
  }
2264
2738
  async getStats() {
2265
2739
  const runtime = this.runtime;
@@ -2455,18 +2929,18 @@ class TrajectoryLoggerService extends Service {
2455
2929
  episodeId: asString(pickCell(row, "episode_id")) ?? undefined,
2456
2930
  batchId: asString(pickCell(row, "batch_id")) ?? undefined,
2457
2931
  groupIndex: asNumber(pickCell(row, "group_index")) ?? undefined,
2458
- steps: parseJson(pickCell(row, "steps_json"), []),
2932
+ steps: parseJson(pickCell(row, "steps_json", "steps"), []),
2459
2933
  totalReward: asNumber(pickCell(row, "total_reward")) ?? 0,
2460
- rewardComponents: parseJson(pickCell(row, "reward_components_json"), { environmentReward: 0 }),
2461
- metrics: parseJson(pickCell(row, "metrics_json"), {
2934
+ rewardComponents: parseJson(pickCell(row, "reward_components_json", "reward_components"), { environmentReward: 0 }),
2935
+ metrics: parseJson(pickCell(row, "metrics_json", "metrics"), {
2462
2936
  episodeLength: 0,
2463
2937
  finalStatus: "completed"
2464
2938
  }),
2465
- metadata: parseJson(pickCell(row, "metadata_json"), {})
2939
+ metadata: parseJson(pickCell(row, "metadata_json", "metadata"), {})
2466
2940
  };
2467
2941
  }
2468
2942
  getActiveTrajectory(trajectoryId) {
2469
- return this.activeTrajectories.get(trajectoryId) || null;
2943
+ return null;
2470
2944
  }
2471
2945
  getCurrentStepId(trajectoryId) {
2472
2946
  return this.activeStepIds.get(trajectoryId) || null;
@@ -2935,8 +3409,8 @@ async function buildGameStateFromDB(_trajectoryId) {
2935
3409
  async function recomputeTrajectoryRewards(_trajectoryIds) {}
2936
3410
  // integration.ts
2937
3411
  import { logger as logger4 } from "@elizaos/core";
2938
- function startAutonomousTick(trajectoryLogger, context) {
2939
- const trajectoryId = trajectoryLogger.startTrajectory(context.agentId, {
3412
+ async function startAutonomousTick(trajectoryLogger, context) {
3413
+ const trajectoryId = await trajectoryLogger.startTrajectory(context.agentId, {
2940
3414
  scenarioId: context.scenarioId,
2941
3415
  episodeId: context.episodeId,
2942
3416
  batchId: context.batchId,
@@ -3134,4 +3608,4 @@ export {
3134
3608
  RewardService
3135
3609
  };
3136
3610
 
3137
- //# debugId=61D715423871574264756E2164756E21
3611
+ //# debugId=16B8470362B4AC2364756E2164756E21