Seeding historical AEP records is awkward — no ergonomic timestamp override
Repo: packages/aep
Problem
We wanted to seed demo AEP data at service startup so integrators / demos see a realistic audit trail without first running the agent. The natural pattern is "make it look like this run happened 3 days ago at 23:17". Doing that today requires:
- Passing
timestamp_ms into every addAction() call
- Passing
createdAtMs into build() / emit()
- Manipulating the private
#actions array is impossible (readonly private field), so you can't fix timestamps post-hoc
There's no AEPEmitter.forRun(runStartMs, opts) or emitter.setStartTime(ms) helper.
Evidence
packages/aep/src/emitter.ts:
Constructor — emitter.ts:38-40. AEPEmitterOptions (lines 15-27) has no created_at_ms / start_ms field. Can't be set once and reused.
build() — emitter.ts:93, signature build(createdAtMs?: number): AEPRecord. So per-call override exists, but requires re-passing the same value on every call.
addAction() — emitter.ts:42-61:
addAction(
action: Omit<ActionEvidence, "action_id" | "timestamp_ms"> & {
action_id?: string;
timestamp_ms?: number;
}
): void {
this.#actions.push({
action_id: action.action_id ?? `action-${this.#actions.length}`,
timestamp_ms: action.timestamp_ms ?? Date.now(),
...
});
...
}
Each timestamp_ms must be supplied explicitly. Default is Date.now() — perfect at runtime, unhelpful for seeding.
#actions is private+readonly — emitter.ts:31: readonly #actions: ActionEvidence[] = []. So even if the caller wanted to walk it and adjust timestamps after adding, they can't.
Reproducer
Seeding a demo record for "3 days ago, 4 actions at 23:17 + 1s each":
// Current API — the caller has to compute each timestamp manually
const baseMs = threeDaysAgoAtLocal2317();
const e = new AEPEmitter({ run_id: 'demo-001', model_id: 'x' });
e.addAction({ tool_name: 'a', state_changing: false, timestamp_ms: baseMs + 1000 });
e.addAction({ tool_name: 'b', state_changing: false, timestamp_ms: baseMs + 2000 });
e.addAction({ tool_name: 'c', state_changing: true, timestamp_ms: baseMs + 3000 });
e.addAction({ tool_name: 'd', state_changing: true, timestamp_ms: baseMs + 4000 });
const rec = e.build(baseMs); // remember to pass createdAtMs, again
Every seed call carries the baseMs plumbing. In our project we seed 10 records covering multiple scenarios — the code got noisier than the actual scenario definitions.
What we ended up doing
Since we couldn't set the clock on the emitter, we ported it to Python and gave our Python AEPEmitter a private field we overwrite:
e = AEPEmitter(run_id=f'demo-seed-{i:03d}', ...)
e._created_at_ms = run_ts_ms # private field — feels hacky
e._start_ms = run_ts_ms
We'd rather do this via the public API of the TS emitter (since we'll eventually converge on it) but there isn't one.
Proposed fix
Simple option: add an AEPEmitterOptions.created_at_ms?: number and a now?: () => number clock injection:
export interface AEPEmitterOptions {
// ... existing fields
/** Fixed run start; if provided, build()/emit() use this by default. */
created_at_ms?: number;
/** Clock injection for addAction default timestamps and for build() default. */
now?: () => number;
}
Then:
const t0 = threeDaysAgoAtLocal2317();
let t = t0;
const e = new AEPEmitter({
run_id: 'demo-001',
created_at_ms: t0,
now: () => (t += 1000), // each addAction advances by 1s
});
e.addAction({ tool_name: 'a', state_changing: false });
e.addAction({ tool_name: 'b', state_changing: false });
// ...
const rec = e.build(); // uses created_at_ms from options
Cleaner for seeding, cleaner for tests, no impact on the runtime path (default now is Date.now).
Bonus: this makes the emitter easier to unit-test — deterministic clock.
Happy to send a PR.
Filed by: CATL Ariba Joule integration team. We seed 10 demo AEP records at service startup for the audit tab; the current API forced us to duplicate the clock plumbing in every scenario definition.
Seeding historical AEP records is awkward — no ergonomic timestamp override
Repo:
packages/aepProblem
We wanted to seed demo AEP data at service startup so integrators / demos see a realistic audit trail without first running the agent. The natural pattern is "make it look like this run happened 3 days ago at 23:17". Doing that today requires:
timestamp_msinto everyaddAction()callcreatedAtMsintobuild()/emit()#actionsarray is impossible (readonly private field), so you can't fix timestamps post-hocThere's no
AEPEmitter.forRun(runStartMs, opts)oremitter.setStartTime(ms)helper.Evidence
packages/aep/src/emitter.ts:Constructor —
emitter.ts:38-40.AEPEmitterOptions(lines 15-27) has nocreated_at_ms/start_msfield. Can't be set once and reused.build()—emitter.ts:93, signaturebuild(createdAtMs?: number): AEPRecord. So per-call override exists, but requires re-passing the same value on every call.addAction()—emitter.ts:42-61:Each
timestamp_msmust be supplied explicitly. Default isDate.now()— perfect at runtime, unhelpful for seeding.#actionsis private+readonly —emitter.ts:31: readonly #actions: ActionEvidence[] = []. So even if the caller wanted to walk it and adjust timestamps after adding, they can't.Reproducer
Seeding a demo record for "3 days ago, 4 actions at 23:17 + 1s each":
Every seed call carries the baseMs plumbing. In our project we seed 10 records covering multiple scenarios — the code got noisier than the actual scenario definitions.
What we ended up doing
Since we couldn't set the clock on the emitter, we ported it to Python and gave our Python
AEPEmittera private field we overwrite:We'd rather do this via the public API of the TS emitter (since we'll eventually converge on it) but there isn't one.
Proposed fix
Simple option: add an
AEPEmitterOptions.created_at_ms?: numberand anow?: () => numberclock injection:Then:
Cleaner for seeding, cleaner for tests, no impact on the runtime path (default
nowisDate.now).Bonus: this makes the emitter easier to unit-test — deterministic clock.
Happy to send a PR.
Filed by: CATL Ariba Joule integration team. We seed 10 demo AEP records at service startup for the audit tab; the current API forced us to duplicate the clock plumbing in every scenario definition.