cloudflare / cloudflare/agents

Think: public accessor for the admitted turn's request id and trigger

Open
#2,218 0 comments 0 reactions 1 assignee Claimed by @threepointone View on GitHub
enhancement think
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

## What we read today, and why

We run a `Think` subclass (`@cloudflare/think` 0.17.0, `agents` 0.22.0) as a per-tenant site manager. Turns arrive from several doors at once: the WebSocket chat, an email channel, alarm-driven proactive turns, and `runTurn` calls from our own tools. Per-turn state (which channel is speaking, which actor, the trace collector, attachments) has to be keyed by the turn that is actually running, because a channel body can be waiting on the queue while a WebSocket chat turn runs ahead of it.

The one identity every hook and tool call inside a turn shares is the request id the `TurnQueue` is running. Today we reach it, and two neighbours, through private fields:

1. `this._turnQueue.activeRequestId`. `Think` declares `private _turnQueue`; the `TurnQueue` class from `agents/chat` exposes `activeRequestId` publicly (set before the admitted body, cleared in its `finally`), but the queue instance itself is private.
2. `this._turnQueue.enqueue(name, fn)`, to serialise our own post-publish work behind the chat turn (the detached-delivery pattern, inside `keepAliveWhile`).
3. `this._cf_sealMemoryLimitedRecovery()` (protected), to seal a still-live recovery incident when our own alarm strike counter trips.

A fourth value we cannot reach at all: the admitted turn's `trigger` (`ws-chat`, `rpc`, `programmatic`, `agent-tool`, `auto-continuation`, ...). It lives in the module-private `admittedTurnContext` `AsyncLocalStorage` in `think.js` and is not exposed on `TurnContext`, on `activeTurnMetadata` (empty on dashboard WebSocket turns), or on any hook argument. We need it to tell "the chat door's turn is running" from "our channel body's turn is running" when the two race.

We have wrapped all of these in one adapter module with a version pin that fails our typecheck on a bump, so nothing here is urgent for us. It would be better for everyone if the reads were public.

## Ask

A public, documented accessor on `Think` (or `Agent`) for the admitted turn, something like:

```ts
get activeTurn():
| { requestId: string; trigger: TurnTrigger; admission: "submit" | "wait"; channel?: string; continuation: boolean }
| undefined;
```

readable from `beforeTurn`, `prepareStep`, tool `execute` and `onChatResponse`, and `undefined` outside any turn. Exporting `TurnTrigger` from `@cloudflare/think` would help too. A public way to enqueue work behind the current turn (`enqueueTurn(name, fn)` or similar) would remove the second private read.

Happy to open a PR if the shape is agreed.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.