When a new user sends a message into an existing Slack thread, model-generated attributions (e.g. Action taken on behalf of) use the original thread starter's identity rather than the actual requester's.
Correct RCA
The runtime turn context is injected once at bootstrap (needsBootstrapContext = true on the first turn). It includes the full context block — skills, config, runtime metadata, and the requester's identity. For all subsequent user messages, no identity is injected at all.
This means: when user B sends a message in a thread originally started by user A, the only <requester> block in the model's history belongs to user A's bootstrap turn. The model uses that stale identity for any attributions it generates.
The symptom is the same whether the stale context comes from the projection or from a resumed session — the real problem is that individual user messages don't carry their sender's identity.
Correct fix
Inject a lightweight requester identity block into every user message as it is stored, not just at bootstrap. The first turn continues to receive the full runtime context (skills, config, etc.) as today. Each subsequent user message should include a minimal block identifying who sent it:
<runtime-turn-context>
<requester>
- user_name: user.beta
- user_id: U_BETA
</requester>
</runtime-turn-context>
This makes each message self-describing regardless of thread length, number of turns, or which user started the conversation.
Why the projection-strip in PR #560 is insufficient
Stripping the old context from the projection unblocks re-injection of a fresh full bootstrap context on the next turn, but that is treating a symptom. If the projection does not contain a context block, needsBootstrapContext fires and re-injects the current requester — but it also re-injects everything else (skills, config, etc.) unnecessarily. The real invariant should be: every user message carries the identity of whoever sent it.
Prior art investigation
Researched how other agent frameworks handle per-message identity in multi-user threads:
- OpenAI Chat Completions API: explicitly supported a
name field on each message object for multi-user and multi-persona flows. When the newer Responses API dropped the name field, the community-recommended workaround is inline sender prefixes (e.g. [Alice]: <message>) directly in the user message content. No native per-message identity in the new API.
- Anthropic: no dedicated per-message identity field. Their context engineering guidance emphasizes injecting the smallest high-signal token set at each inference step — directly supporting the "minimal identity sub-block per user message" approach over re-injecting the full bootstrap context.
- General pattern: embedding sender identity in message content (inline prefix or metadata block) before each user turn is the common approach across agents. No framework injects it automatically; it is a builder responsibility.
System prompt implication
The current <conversation> rule — "the requester is the person asking now, which may differ from the original reporter or subject" — asks the model to infer identity from context but gives it no reliable signal to work with once bootstrap context is stale.
Once per-message identity blocks are added, this rule should be updated to clarify that the <requester> block in the current user message is the authoritative identity for that turn, and should be used for attributions like Action taken on behalf of.
Action taken on behalf of Nelson Osacky.
When a new user sends a message into an existing Slack thread, model-generated attributions (e.g.
Action taken on behalf of) use the original thread starter's identity rather than the actual requester's.Correct RCA
The runtime turn context is injected once at bootstrap (
needsBootstrapContext = trueon the first turn). It includes the full context block — skills, config, runtime metadata, and the requester's identity. For all subsequent user messages, no identity is injected at all.This means: when user B sends a message in a thread originally started by user A, the only
<requester>block in the model's history belongs to user A's bootstrap turn. The model uses that stale identity for any attributions it generates.The symptom is the same whether the stale context comes from the projection or from a resumed session — the real problem is that individual user messages don't carry their sender's identity.
Correct fix
Inject a lightweight requester identity block into every user message as it is stored, not just at bootstrap. The first turn continues to receive the full runtime context (skills, config, etc.) as today. Each subsequent user message should include a minimal block identifying who sent it:
This makes each message self-describing regardless of thread length, number of turns, or which user started the conversation.
Why the projection-strip in PR #560 is insufficient
Stripping the old context from the projection unblocks re-injection of a fresh full bootstrap context on the next turn, but that is treating a symptom. If the projection does not contain a context block,
needsBootstrapContextfires and re-injects the current requester — but it also re-injects everything else (skills, config, etc.) unnecessarily. The real invariant should be: every user message carries the identity of whoever sent it.Prior art investigation
Researched how other agent frameworks handle per-message identity in multi-user threads:
namefield on each message object for multi-user and multi-persona flows. When the newer Responses API dropped thenamefield, the community-recommended workaround is inline sender prefixes (e.g.[Alice]: <message>) directly in the user message content. No native per-message identity in the new API.System prompt implication
The current
<conversation>rule — "the requester is the person asking now, which may differ from the original reporter or subject" — asks the model to infer identity from context but gives it no reliable signal to work with once bootstrap context is stale.Once per-message identity blocks are added, this rule should be updated to clarify that the
<requester>block in the current user message is the authoritative identity for that turn, and should be used for attributions likeAction taken on behalf of.Action taken on behalf of Nelson Osacky.