Skip to content
PacSpace
Talk to us

Integration patterns

Three ways to lay out an agent's record: an entry per action, an entry per run, or a run that opens, records its actions, and closes. Which to pick and how to name the entries.

An agent's record is one record per run, and the question is how many entries a run gets. Three patterns cover most agents. They differ in how much the record says and how many writes it costs; they do not differ in what the check confirms.

Entry per action

Every action the agent takes is an entry, written as it happens. The record reads like the run: the task it was given, each tool call, the decision, the handoff to a person, the message it sent. This is the pattern for an agent that acts on other people's systems or money, and for an agent under evaluation, where the order of its actions and the moment it reached past its test are what a reader will ask about.

Entrykindpayloads
The run opensagent-runThe instruction, and the limits in force
The agent calls a tooltool-callThe call and its result, as the harness kept them
The agent decidesdecisionThe decision as written
The agent hands work to a personagent-handoffWhat it handed over
A person approvesapproval-givenThe approval, written by the tool the person used
The agent acts on the worldyour word for the actionThe action, as sent
The run closes (lifecycle: 'closed')agent-run-closeThe transcript or the summary

Cost: one write per action, up to 120 a minute per workspace. For an agent that takes hundreds of actions a minute, fold the small ones into one decision entry that carries several fingerprints, and keep the actions that touch the outside world as entries of their own. An agent in an evaluation and What an agent did are this pattern.

Entry per run

One entry, written when the run ends, carrying the fingerprints of everything that matters: the instruction, the transcript, the output. The record says the run happened, when, on whose instruction, and what it produced, and nothing about the order inside it. This is the pattern for scheduled jobs and for runs where no agent acts on the world, such as a nightly benchmark suite, where the run is the unit and the transcript is a file.

typescript
// refOf is the helper on Recording from an agent: it fingerprints a file and keeps its blinding file beside it.
await pac.records.emit({
  record: `suite-${jobId}`,
  title: `Nightly benchmark suite ${jobId} finished, ${today}`,
  kind: 'suite-run',
  occurredAt: finishedAt,
  actorId: 'suite-runner-2',
  instructedBy: 'nightly-schedule',
  payloads: [
    await refOf(`suites/${jobId}/config.json`),
    await refOf(`suites/${jobId}/transcript.jsonl`),
    await refOf(`suites/${jobId}/results.json`),
  ],
  idempotencyKey: `suite-${jobId}:finished`,
});

Cost: one write per run. The transcript itself stays with you, and the record carries its fingerprint, so a reader who is later handed the transcript and its blinding file can check it is the one that was recorded.

Open, act, close

The first pattern with a start and an end: the run opens with an entry that names it, actions are entries, and the run closes with an entry that carries the summary and lifecycle: 'closed'. After the close, anything that tries to write to the record is recorded as an attempt, marked so, and the record's state does not move. This is the pattern for an agent that must not keep acting after it was told to stop, because the record then shows both the stop and any attempt after it.

Naming

  • record is the run's id, made from something you already have: run-${runId}, eval-run-${runId}, suite-${jobId}. Never the clock.
  • The first entry's title is the record's name. Name the thing, with its date: "Agent-7 runtime decisions, 23 September".
  • kind is your vocabulary, in lower case letters, digits, and hyphens: tool-call, approval-given. A dot or an underscore is refused with RECORD_CONTENT_INVALID. Pick a short list and keep to it; a reader filters by it.
  • idempotencyKey is the run id joined to the step: run-${runId}:call-${callId}. It is never the record id alone, because every entry needs its own.
  • actorId is who acted, as you name them: the agent, a monitor, a person. instructedBy is the person or system that started the run, and it changes only when a person steps in, for example the person whose approval an action rests on.

References between records

When a run acts on something that has its own record, a model release, a document, another run, the entry can name that record's entry in references: [{ recordKey, entry }]. The referenced entry must already be committed in your workspace. A reader of the run's record can then open the thing it acted on, as it was at the time. A model release shows a run naming the build it runs.

What not to record

Record the fingerprints of the file, the transcript, and the prompt, and keep the things themselves, and their blinding files, with you. Keep secrets, keys, and tokens out of every field; everything under content is sealed, and a field you reveal is shown to whoever you share the record with. Do not record the same action twice under two keys. And do not write an entry for an action that has not happened yet.