Skip to content

[Think] Add client projection hooks for server-only message context #1966

Open

Description

@c-w-xiaohei

Problem

Think Session is the canonical history used for model execution, tools, compaction, and recovery. Applications may need to persist server-owned, model-visible context with a turn, such as retrieval snapshots, system reminders, or internal tool details, without exposing it to browser clients.

Today, Think uses the same this.messages view for model assembly and client synchronization. Persisting internal parts therefore risks exposing them through broadcasts, /get-messages, reconnects, rollback, or resumable replay. beforeTurn is too late to persist context, while Agent lifecycle hooks do not cover every client output path. This forces applications to either split conversation state into sidecars or replace the built-in browser transport.

Proposal

Please consider stable hooks such as:

prepareMessageForSession(message, context): SessionMessage
projectMessagesForClient(messages, context): UIMessage[]
projectStreamChunkForClient(chunk, context): UIMessageChunk | null

The exact API may differ, but the important guarantees are:

  • server-only fields are created or validated before Session persistence;
  • model execution, compaction, and recovery use the complete canonical history;
  • every client-facing history, broadcast, reconnect, rollback, and replay path uses the client projection;
  • stream projection preserves complete tool-call lifecycles and terminal events;
  • clients cannot mark their own content as trusted/internal.

Why

A single canonical Session transcript should be able to support different audiences without requiring application-level transcript joins or a second chat protocol. This would provide one extensible boundary for retrieval snapshots, reminders, private server tools, and future model-only context.

Observed with @cloudflare/think@0.13.0 and agents@0.17.3.

(co-authored by gpt-5.6-sol)

Activity

  1. nim442 commented on Sep 11, 2026

    @nim442

    I have an implementation of the projectMessagesForClient half of this on a branch, against current main (Think 0.18.0 / agents 0.23.0): https://github.com/nim442/agents/tree/client-projection-hook — compare view: https://github.com/cloudflare/agents/compare/main...nim442:agents:client-projection-hook?expand=1. gh pr create is currently refused for my account on this repo, so the PR will follow as soon as that clears; the branch is complete (code, tests, docs, changesets) in the meantime.

    What it does:

    • @cloudflare/think: a protected projectMessagesForClient(messages: UIMessage[], context: { reason: "broadcast" | "connect" | "hydrate" | "rollback" }): UIMessage[] | Promise<UIMessage[]> whose default returns messages unchanged. All four emitters of cf_agent_chat_messages route through it: _broadcastMessages (every persist-then-broadcast caller), _buildIdleConnectMessages (the connect frame), the GET …/get-messages route, and _rollbackDroppedSubmit. The hook receives a snapshot of the list as it stood when the send was issued; a synchronous projection is sent inline (no behaviour change for existing subclasses), an asynchronous one is queued so a later broadcast cannot overtake an earlier snapshot. Model assembly, compaction, and recovery never see the projection.
    • agents/sessions: HistoryReadOptions.overlays (default true) — overlays: false on history() / historyBatches() / getHistory() reads the stored rows underneath a compaction overlay, so a projection can hand people the real conversation while the model keeps the summary.

    prepareMessageForSession and projectStreamChunkForClient are left for a follow-up; stream replay re-sends cf_agent_use_chat_response chunks and never a message list, so the history projection is self-contained.

  2. apoorvasheera commented on Sep 17, 2026

    @apoorvasheera

    Do we know if this fix is coming soon, or should we build around it in our implementations? @cjol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions