feat: add reusable multi-turn agent kit#299
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
📝 WalkthroughWalkthroughIntroduces ChangesTurn Kit
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Relaycast
participant TurnAgent
participant createTurnRunner
participant Memory
participant Model
participant RelaycastDelivery
Relaycast->>TurnAgent: message event
TurnAgent->>createTurnRunner: run conversation turn
createTurnRunner->>Memory: recall history
createTurnRunner->>Model: respond with history and input
createTurnRunner->>RelaycastDelivery: deliver reply
RelaycastDelivery-->>createTurnRunner: receipt.ok
createTurnRunner->>Memory: save confirmed turn
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 641e6b78db
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| conversation: { | ||
| transport: 'relay', | ||
| id: conversationKey(event.channel, event.threadId) | ||
| }, |
There was a problem hiding this comment.
Include the direct-message sender in the conversation key
For unthreaded @self Relay inbox messages, this produces the same relay:dm conversation id for every sender: the runtime maps a missing channel to dm and leaves threadId absent. Since this example uses workspace-scoped memory, a later direct message from another user recalls the prior user's input/reply and places it in that user's model prompt. Derive the key from the inbound peer/sender (or require a per-peer thread id) before invoking the runner.
Useful? React with 👍 / 👎.
| const entries = normalizeTurnHistory(recalled, options.recallOrder ?? 'newest-first'); | ||
| ctx.log?.('info', 'turn-kit.memory-recalled', { entries: entries.length }); | ||
| return entries; |
There was a problem hiding this comment.
Make required memory recall fail closed
With the standard runtime context, recall failures do not reject: its cloud memory adapter converts network and HTTP failures to []. Consequently this path normalizes that empty array and continues through response and delivery even when memory.required is true, despite the public option promising that a recall failure fails the turn. Surface a failure signal from the memory adapter or otherwise distinguish an unavailable recall from a legitimate empty history.
Useful? React with 👍 / 👎.
| export function conversationKey(root: string, thread?: string | number): string { | ||
| const base = requiredIdentityPart(root, 'conversation root'); | ||
| if (thread === undefined || thread === null || String(thread).trim() === '') return base; | ||
| return `${base}:${requiredIdentityPart(String(thread), 'conversation thread')}`; |
There was a problem hiding this comment.
Encode conversation-key components before joining them
A colon in a transport-local root makes distinct conversations collide: conversationKey('room:topic') and conversationKey('room', 'topic') both return room:topic. conversationTag subsequently encodes only that already-ambiguous value, so their memory tags are identical and their histories are mixed. Encode or length-prefix each component before concatenating them.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c68a82337
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export function conversationKey(root: string, thread?: string | number): string { | ||
| const base = requiredIdentityPart(root, 'conversation root'); | ||
| if (thread === undefined || thread === null || String(thread).trim() === '') return base; | ||
| return `${base}:${requiredIdentityPart(String(thread), 'conversation thread')}`; |
There was a problem hiding this comment.
Make conversation key composition unambiguous
When a transport's root or thread identifier contains :, distinct conversations can produce the same key—for example, conversationKey('a:b') and conversationKey('a', 'b') both return a:b. That key is subsequently embedded in the memory tag, so these otherwise separate conversations recall and persist into the same history, potentially exposing one conversation's context to another. Encode or length-prefix the components before combining them.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7a1f3c0: each root/thread component is now encoded before joining, with a regression proving conversationKey("room:topic") cannot collide with conversationKey("room", "topic").
| if (entries.length > 0 && entries.every((entry) => validDate(entry.createdAt))) { | ||
| entries.sort((left, right) => | ||
| String(left.createdAt).localeCompare(String(right.createdAt)) | ||
| ); |
There was a problem hiding this comment.
Sort timestamps by their parsed instant
This lexicographically sorts timestamp strings rather than the instants validated by Date.parse. Valid ISO timestamps with different offsets are therefore ordered incorrectly; for example, 2026-07-24T10:30:00Z sorts before 2026-07-24T12:00:00+02:00, even though the latter is 10:00 UTC. Since this history is presented as oldest-first to responders, use parsed epoch values for the comparator.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7a1f3c0: timestamped entries now compare parsed epoch values, with a mixed-offset regression test.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/turn-kit/src/memory.ts`:
- Around line 11-15: Update conversationKey to encode each non-empty identity
component before joining them, matching conversationTag’s collision-safe
behavior. Preserve requiredIdentityPart validation and the base return for an
absent thread, while ensuring distinct root/thread combinations cannot produce
the same key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9d13dea9-e262-4288-aa88-dfed8f300544
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (21)
.github/workflows/publish.yml.github/workflows/verify-publish.ymlREADME.mdexamples/tsconfig.jsonexamples/turn-agent/README.mdexamples/turn-agent/agent.tsexamples/turn-agent/persona.tspackages/turn-kit/CHANGELOG.mdpackages/turn-kit/README.mdpackages/turn-kit/package.jsonpackages/turn-kit/src/actions.tspackages/turn-kit/src/assistant.test.tspackages/turn-kit/src/assistant.tspackages/turn-kit/src/context.tspackages/turn-kit/src/index.tspackages/turn-kit/src/memory.tspackages/turn-kit/src/persona.tspackages/turn-kit/src/runner.tspackages/turn-kit/src/turn-kit.test.tspackages/turn-kit/src/types.tspackages/turn-kit/tsconfig.json
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
@agentworkforce/turn-kit, a transport-neutral lifecycle for multi-turn agents@agent-assistant/turn-contextthrough the optional@agentworkforce/turn-kit/assistantsubpath for canonical identity/context assembly, guardrails, provenance, and harness projectionThe abstraction is based on the shared lifecycle in life-agent and joke-bot, while preserving hn-monitor's important boundary: exact digest/thread grounding belongs in deterministic state/context providers, not semantic conversation memory. Agent Assistant sessions, memory stores, and continuations remain explicit opt-ins because they are richer contracts than Workforce's current managed
ctx.memoryand callback responder surfaces.Validation
pnpm --filter @agentworkforce/turn-kit test(13 tests)pnpm run typecheck:examplespnpm -r buildAGENT_WORKFORCE_HOME=<empty temp dir> pnpm check(full monorepo lint, typecheck, release tests, and package tests; 331 CLI tests)4.1.34The unisolated CLI test run can pick up the developer machine's personal Workforce source config; the full validation above used an isolated temporary
AGENT_WORKFORCE_HOME.