On this page

On this page

Get started

Conversation transcript ownership

Conversation transcript ownership

Proposal, partly implemented. A chat's model-visible transcript is the log of what that chat showed. One owner writes that log for every outbound message. Scheduled jobs either run inside a chat or run in the background; they never do both. Step 1 shipped in #168996.

Problem

Users reply to messages they can see. Before #168996, many messages that a chat showed were not in that chat's model history, and some entries in model history were never shown:

  • Cron results that went to another chat reached the recipient only as a transcript-only mirror or a System: awareness note. Cron results that went to the creating chat used a separate canonical writer.
  • Messages that a job sends with the message tool are stored in the receiving chat as transcript-only mirrors. Provider replay skips them (#168683).
  • Failure alerts are sent but not written to any transcript (#168684).
  • A job with sessionTarget: "session:<key>" that delivers to a different chat adds its whole run to the <key> conversation, which showed nothing.

Each path had its own rules, so each edge case needed its own fix.

Decision

Rule 1: the transcript matches the chat

A message is in conversation C's model-visible history if and only if C showed it. It is written once, as the same message that was shown, after the channel confirms delivery.

Allowed exceptions, each recorded as a warn run diagnostic:

  • The destination chat is bound to a different agent. The message is sent; no agent's transcript gets it.
  • The payload is native-only (for example a Discord embed with no text). The message is sent; there is no honest text to write.

Rule 2: a job runs in a chat, or in the background

Mode User intent Run lives in Result shown in
In chat "Check in with me here daily" A normal turn in that chat The same chat only
Background "Send me a budget report daily" Its own job session (memory allowed) One destination chat

An in-chat job cannot deliver to another chat. A background job never writes its run into a chat's transcript; only its result reaches the destination through Rule 1. Today's targets map as follows: main is in chat; isolated and current are background (current adds a snapshot of the creating chat); session:<key> is in chat when it delivers to <key>, and must become a background job with its own session when it delivers elsewhere.

One owner

The outbound send owner writes the transcript entry for every confirmed visible send: automation results, failure alerts, message-tool sends, and subagent announcements. It skips the write only when the send is the reply of a turn that runs in the same conversation, because that turn already wrote it. That skip is the reason delivery mirrors became transcript-only in #99470: without it, replay showed every answer twice. Cron keeps no transcript code of its own.

Considered options

  • Opt-in continuation, as in Hermes Agent. Hermes runs every job in a fresh session and delivers fire-and-forget by default. cron.mirror_delivery or a per-job attach_to_session adds a labelled user message to the destination session. Rejected as the default: a reply to a delivered message should work without configuration, and a switch per job is a setting users must discover.
  • Always write to the creating chat too. Rejected: the creating chat would hold messages it never showed, so the agent can refer to things the user did not see. Run history already answers "what did that job send?".
  • Keep recipient mirrors and awareness notes. Rejected: several writers, best effort, no wait for an active turn, no idempotency, and the model sees a note about the message instead of its own message.

Status and order

  1. Done (#168996). Cron results go to the destination chat once, through the canonical writer (src/sessions/background-session-result.ts), after confirmed delivery. Mirrors and awareness notes are removed.
  2. Move the writer to the outbound send owner. Cron, the message tool, alerts, and subagent announcements use it; remove the cron-specific path. Closes #168683 and #168684.
  3. Two job modes. Doctor migrates existing jobs. A session:<key> job that delivers elsewhere becomes a background job with its own session. That job loses the <key> conversation as context, so Doctor reports each migrated job.
  4. Show the posting job to the model (#168685), so "stop this" on a delivered message needs no lookup.

Risks to check before step 2

  • Repeated assistant turns. A result appended after the chat's last assistant reply creates two adjacent assistant messages. This has shipped for current jobs since #126860 without a known failure, but Hermes broke on the same pattern (hermes-agent#2221). Add a provider replay test for each supported provider family.
  • Session key parity. The write must land in the exact session that an inbound reply uses. X has a known mismatch (#168686).
  • Reset of the creating chat. Implicitly routed results still fail closed after a reset (#169258). Under Rule 1 a reset chat is the same chat; this needs a product decision.
  • Prompt cache. Entries are appended at the end and never rewrite earlier history, so the cached prefix stays valid.
Was this useful?