Extensions / Guides

Observe JSONL journals

Use the terminal observation broker to find one bounded journal, prove it belongs to the foreground agent session, and hand its stream to jsonlSession().

The JSONL path

  1. Resolve a known journal below the selected terminal’s environment or home directory.
  2. Read and validate a bounded header record.
  3. Bind the provider’s stable session ID with an opaque file or process fingerprint.
  4. Follow the resulting opaque file handle and map each record to a lifecycle event.
TypeScript
async function observe(terminal: AgentTerminalContext) {
  const file = await terminal.observation.files.resolveHomeRelative(
    ".acme-agent/sessions/current.jsonl",
    {
      beneath: { homeRelative: ".acme-agent/sessions" },
      extension: ".jsonl",
      signal: terminal.signal,
    },
  );
  if (!file) return { state: "not-bound" };

  // Check one bounded record before treating a file as a session.
  const first = await terminal.observation.files.readJsonLine(file, {
    position: "first", maxBytes: 64 * 1024, signal: terminal.signal,
  });
  if (!isSessionHeader(first)) return { state: "not-bound" };

  const binding = await terminal.bindSession({
    providerSessionId: first.id,
    mappingVersion: "1",
    journal: file,
    fingerprint: { kind: "acme-root-journal", file },
  });

  return jsonlSession({
    binding,
    source: terminal.observation.files.follow(file, { signal: terminal.signal }),
    mapRecord(record, session) {
      if (isRecord(record) && record.type === "completed") {
        session.publish.done({ outcome: "success" });
      }
    },
  });
}

Use handles, not paths

Every resolver returns an opaque handle valid only for this terminal context. A path string from a process or journal is descriptive; it is never permission to read a local file.

NeedBroker method
A known file under agent homefiles.resolveHomeRelative()
A file below a declared CLI environment variablefiles.resolveRelativeToEnvironment()
One record for session prooffiles.readJsonLine()
Bounded stream of journal changesfiles.follow()
Discover bounded child journalsresolveHomeDirectory(), listDirectory(), or watchDirectory()

Pass terminal.signal to each operation. Terminay cancels it when the terminal observation is no longer valid.

Bind only a proven root

A binding ties a provider session ID and mapping version to the exact terminal. Use a file or process handle in its fingerprint—not a guessed newest file, a local absolute path, or a working directory. A child journal extends an existing root; it never creates another root binding.

Add subagent journals

If the CLI writes separate child journals, provide stable child IDs. The host owns replay, flow control, and de-duplication; your mapping only supplies verified terminal-scoped sources.

TypeScript
const directory = await terminal.observation.files.resolveHomeDirectory(
  ".acme-agent/subagents",
  { beneath: { homeRelative: ".acme-agent/subagents" }, signal: terminal.signal },
);

const listing = directory && await terminal.observation.files.listDirectory(directory, {
  extensions: [".jsonl"], maxDepth: 2, maxEntries: 32, maxBytes: 4 * 1024 * 1024,
  signal: terminal.signal,
});

const childSources = await Promise.all((listing?.entries ?? []).map(async (entry) => ({
  childId: entry.relativePath,
  journal: entry.handle,
  source: terminal.observation.files.follow(entry.handle, { signal: terminal.signal }),
})));

return jsonlSession({ binding, source, childSources, mapRecord });

Keep record mapping defensive

mapRecord() receives unknown. Validate record shape, bound text that you publish, and ignore malformed or unsupported records. JSONL replacement and truncation are handled as stream changes; do not assume a journal is append-only.

Next: publish lifecycle events.