Skip to content

feat: add reusable multi-turn agent kit#299

Open
khaliqgant wants to merge 4 commits into
mainfrom
feat/turn-kit
Open

feat: add reusable multi-turn agent kit#299
khaliqgant wants to merge 4 commits into
mainfrom
feat/turn-kit

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

  • add @agentworkforce/turn-kit, a transport-neutral lifecycle for multi-turn agents
  • centralize conversation identity, chronological memory, deterministic context providers, interim acknowledgements, receipt-gated actions/final delivery, and post-delivery persistence
  • reuse @agent-assistant/turn-context through the optional @agentworkforce/turn-kit/assistant subpath for canonical identity/context assembly, guardrails, provenance, and harness projection
  • align deterministic Workforce context blocks with the Agent Assistant prepared-context contract while keeping the base package lightweight
  • add a typed persona helper, runnable Relay example, package docs, and publish/verify wiring
  • keep transport parsing, domain mutations, exact pending/idempotency state, and grounding policy in application adapters

The 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.memory and callback responder surfaces.

Validation

  • pnpm --filter @agentworkforce/turn-kit test (13 tests)
  • pnpm run typecheck:examples
  • pnpm -r build
  • AGENT_WORKFORCE_HOME=<empty temp dir> pnpm check (full monorepo lint, typecheck, release tests, and package tests; 331 CLI tests)
  • package tarball verified with workspace dependencies rewritten to 4.1.34
  • base tarball import verified without installing the optional Agent Assistant peer

The 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.

Review in cubic

@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces @agentworkforce/turn-kit, a transport-neutral multi-turn runner with memory, deterministic context, delivery confirmation, persona validation, an assistant-context bridge, tests, a Relaycast example, documentation, and publishing workflow integration.

Changes

Turn Kit

Layer / File(s) Summary
Turn contracts, memory, and context
packages/turn-kit/src/types.ts, packages/turn-kit/src/memory.ts, packages/turn-kit/src/context.ts, packages/turn-kit/src/index.ts
Defines public turn contracts and implements conversation identity, chronological history recall/persistence, context normalization, optional-provider handling, and duplicate-ID validation.
Assistant context bridge
packages/turn-kit/src/assistant.ts, packages/turn-kit/src/assistant.test.ts
Builds canonical assistant turn context from history and metadata, merges caller blocks, validates IDs, and projects the assembled context to a harness shape.
Turn execution and confirmation
packages/turn-kit/src/runner.ts, packages/turn-kit/src/actions.ts, packages/turn-kit/src/persona.ts, packages/turn-kit/src/turn-kit.test.ts
Adds the recall → context → respond → deliver → confirmed-save lifecycle, confirmed actions, memory-enabled personas, delivery errors, response validation, and lifecycle tests.
Package surface and lifecycle documentation
packages/turn-kit/package.json, packages/turn-kit/tsconfig.json, packages/turn-kit/README.md, packages/turn-kit/CHANGELOG.md
Configures package exports and compilation, documents APIs and lifecycle guarantees, and records the unreleased feature set.
Relay multi-turn example
examples/turn-agent/*, examples/tsconfig.json
Adds a Relaycast agent and persona using workspace memory, conversation identity, LLM responses, delivery receipts, and delayed persistence.
Release and verification integration
.github/workflows/publish.yml, .github/workflows/verify-publish.yml, README.md
Adds turn-kit to publish targets, release-note ordering, manual verification choices, and the repository package list.

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
Loading

Possibly related PRs

Poem

A bunny hops through turns in a row,
With memory saved when receipts say so.
Context blocks gather, replies take flight,
Delivery confirms the history right.
“Turn-kit is ready!” the rabbit cheers—
Multi-turn magic for future ears!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding a reusable multi-turn agent kit.
Description check ✅ Passed The description directly matches the implemented turn-kit, assistant bridge, example, docs, and workflow changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/turn-kit

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +30 to +33
conversation: {
transport: 'relay',
id: conversationKey(event.channel, event.threadId)
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +43 to +45
const entries = normalizeTurnHistory(recalled, options.recallOrder ?? 'newest-first');
ctx.log?.('info', 'turn-kit.memory-recalled', { entries: entries.length });
return entries;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread packages/turn-kit/src/memory.ts Outdated
Comment on lines +11 to +14
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')}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@khaliqgant

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/turn-kit/src/memory.ts Outdated
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')}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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").

Comment on lines +71 to +74
if (entries.length > 0 && entries.every((entry) => validDate(entry.createdAt))) {
entries.sort((left, right) =>
String(left.createdAt).localeCompare(String(right.createdAt))
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7a1f3c0: timestamped entries now compare parsed epoch values, with a mixed-offset regression test.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8e10a7a and 7c68a82.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • .github/workflows/publish.yml
  • .github/workflows/verify-publish.yml
  • README.md
  • examples/tsconfig.json
  • examples/turn-agent/README.md
  • examples/turn-agent/agent.ts
  • examples/turn-agent/persona.ts
  • packages/turn-kit/CHANGELOG.md
  • packages/turn-kit/README.md
  • packages/turn-kit/package.json
  • packages/turn-kit/src/actions.ts
  • packages/turn-kit/src/assistant.test.ts
  • packages/turn-kit/src/assistant.ts
  • packages/turn-kit/src/context.ts
  • packages/turn-kit/src/index.ts
  • packages/turn-kit/src/memory.ts
  • packages/turn-kit/src/persona.ts
  • packages/turn-kit/src/runner.ts
  • packages/turn-kit/src/turn-kit.test.ts
  • packages/turn-kit/src/types.ts
  • packages/turn-kit/tsconfig.json

Comment thread packages/turn-kit/src/memory.ts
@khaliqgant

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 7a1f3c0351

ℹ️ 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".

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant