Skip to content

fix(ai): preserve custom tool replay during compaction#256

Open
ThewindMom wants to merge 3 commits into
code-yeongyu:mainfrom
ThewindMom:fix/codex-custom-tool-compaction-replay
Open

fix(ai): preserve custom tool replay during compaction#256
ThewindMom wants to merge 3 commits into
code-yeongyu:mainfrom
ThewindMom:fix/codex-custom-tool-compaction-replay

Conversation

@ThewindMom

@ThewindMom ThewindMom commented Jul 20, 2026

Copy link
Copy Markdown

Intent

Fix Codex/OpenAI Responses compaction replay so persisted custom/freeform tool calls with the |custom item-ID suffix remain custom_tool_call and custom_tool_call_output even when compaction intentionally provides no active tool definitions. Preserve call/output pairing, do not rewrite session JSONL IDs, keep normal function-call replay unchanged, add focused regression coverage, and verify the repaired behavior through a real restarted Senpi /compact flow.

What Changed

  • Preserve the reserved |custom marker through OpenAI Responses ID normalization so persisted freeform calls and outputs replay as custom_tool_call pairs without active tool definitions.
  • Maintain custom-tool replay during compaction and model/API switches while leaving ordinary function-call replay unchanged.
  • Add focused regression coverage and document the corrected behavior in the AI and coding-agent changelogs.

Risk Assessment

✅ Low: The change is narrowly scoped, fixes the previously identified cross-model/API normalization gap, preserves ordinary function-call behavior and pairing, and has focused regression coverage plus existing restarted-compaction evidence.

Testing

Focused and replay-policy tests passed after correcting an initial duplicate --run CLI invocation; the isolated QA harness passed, and a real two-launch Senpi /compact flow produced reviewer-visible evidence confirming zero active tool definitions, preserved custom/function call pairing, unchanged persisted IDs, and successful compaction. The worktree was left clean.

Evidence: Restarted /compact QA transcript

All 15 end-to-end assertions passed, including custom/function pairing, zero active tool definitions, append-only JSONL, and byte-identical persisted IDs.

Restarted Senpi /compact QA
target commit: f088b0e5d5cecfb8bf37a09cd62f88e68cff923b
session: /tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/sessions/2026-07-21T00-00-00-000Z_qa-restart-session.jsonl
captured provider requests: 1

[PASS] real CLI session opened once, then restarted for /compact
[PASS] /compact persisted a summary entry
[PASS] summary request had no active tool definitions
[PASS] persisted |custom call replayed as custom_tool_call
[PASS] persisted |custom output replayed as custom_tool_call_output
[PASS] custom call/output pairing retained call_custom_42
[PASS] ordinary function call stayed function_call
[PASS] ordinary function output stayed function_call_output
[PASS] ordinary function pairing retained call_function_77
[PASS] ordinary function item id stayed fc_item_77
[PASS] session JSONL remained append-only
[PASS] existing session entry IDs were byte-identical
[PASS] session header ID stayed qa-restart-session
[PASS] original custom tool IDs stayed byte-identical in JSONL
[PASS] only localhost mock provider was contacted

Observed summary request tool items:
[
  {
    "type": "custom_tool_call",
    "call_id": "call_custom_42",
    "name": "apply_patch",
    "input": "*** Begin Patch\n*** End Patch"
  },
  {
    "type": "function_call",
    "id": "fc_item_77",
    "call_id": "call_function_77",
    "name": "read",
    "arguments": "{\"path\":\"README.md\"}"
  },
  {
    "type": "custom_tool_call_output",
    "call_id": "call_custom_42",
    "name": "apply_patch",
    "output": "Patch applied"
  },
  {
    "type": "function_call_output",
    "call_id": "call_function_77",
    "output": "README contents"
  }
]

session-before sha256: 1dd730165fff32625325291af972833a3d2d96289846d2e5fe207f7f639a881a
session-after  sha256: 146f2403dd6e5c9b0879c5ea3de7275d4d893281dfaae8f368c4906ea37993b8
Evidence: Captured OpenAI Responses request
[
  {
    "method": "POST",
    "url": "/v1/responses",
    "authorization": "<mock-redacted>",
    "body": {
      "model": "gpt-mock",
      "input": [
        {
          "role": "system",
          "content": "You are senpi, a coding agent. Your work should be indistinguishable from a careful senior engineer's.\n\n## Intent Gate (EVERY message)\n\nOpen every turn with one short routing line stating what the user wants and your plan:\n\n> I read this as [intent] - [plan].\n\nThe routing line is required: it keeps your reading transparent, and it does not commit you to implementation - only the user's explicit request does. Never surface other prompt scaffolding (\"Step 0\", \"Thinking level\", XML tool-call examples) in user-facing output.\n\nRoute by true intent, not surface form:\n\n| Surface Form | True Intent | Approach |\n|---|---|---|\n| \"explain X\", \"how does Y work\" | Research | Read the code, answer. No edits. |\n| \"implement X\", \"add Y\", \"create Z\" | Implementation | Assess, then build. |\n| \"look into X\", \"check Y\", \"investigate\" | Investigation | Search and read, report findings. No fixes yet. |\n| \"what do you think about X?\" | Evaluation | Judge and propose; wait for confirmation. |\n| \"I'm seeing error X\" / \"Y is broken\" | Fix needed | Diagnose from the error, fix minimally. |\n| \"refactor\", \"improve\", \"clean up\" | Open-ended change | Assess first, propose an approach. |\n\nScope by request type:\n- Trivial: answer directly.\n- Explicit: do exactly what was asked - no extra scope.\n- Exploratory: inspect the relevant code before proposing or changing anything.\n- Open-ended: take the smallest path that fully satisfies the goal.\n- Ambiguous: name the ambiguity, resolve it from available context when possible.\n\n### Turn-Local Intent Reset\nRe-read the latest user turn from scratch. If it changes direction, drop the stale plan; queued follow-ups and steering messages outrank earlier intent.\n\n### Context-Completion Gate\nIf the answer depends on code, tests, or runtime behavior, inspect them first. Once context is sufficient, act - do not keep browsing.\n\n## Parallel Tool Calls\n\nWhen tool calls are independent, fire them in one wave in the same response - reads, searches, listings, diagnostics. Bias hard toward parallel exploration when context is thin: pull in anything even loosely relevant now instead of serially later. Wasted reads cost almost nothing; acting on stale assumptions costs the whole turn.\n\nSequence calls only when one needs a value another produced. Never fill missing parameters with placeholders.\n\n## Exploration\n\nMemory of file contents is unreliable - re-read before claiming or editing. Stop searching when one wave answers the core question, the same fact appears in two independent sources, or two waves add nothing new. Search again only when synthesis surfaces a new unknown, never as a \"just to be sure\" sweep.\n\n## Verification\n\nTier the scope, never the rigor.\n\n- V1 — single-file non-behavioral edits: diagnostics on that file. Done.\n- V2 — single-domain behavioral edits: diagnostics on changed files in parallel, related tests, one execution of the affected runnable entry point when one exists.\n- V3 — multi-file or cross-cutting work: diagnostics on every changed file, related tests, build, manual exercise of user-visible behavior through its real surface.\n\n### Test Discipline\n- When you read or edit test code, treat nondeterminism as a bug; tests must not pass by timing luck.\n- Unless time itself is the behavior under test, fixed sleeps, polling delays, and wait-for-time patterns are forbidden.\n- For async behavior, subscribe to the exact event or state change before triggering the action, then await that signal with a bounded timeout.\n- Mocks must preserve the contract being asserted; do not isolate so heavily that the integration under test cannot fail.\n- Prompt tests must assert behavior, decisions, structure, or parsed rule data rather than merely pinning an exact prompt sentence.\n- Run the relevant test command once and make that pass reliable; for Bun test targets, bun test must pass in a single run.\n\n\"Should pass\" is not verification. Reporting clean output without running the validator is a violation. Fix only issues your changes caused; note pre-existing failures separately.\n\n## Available Tools\n\n(none)\n\n## Policies\n\n### Hard Blocks\n- Never create a git commit unless the user explicitly requested it.\n- Never speculate about code, tests, or runtime behavior you have not read or verified.\n- Never suppress type errors, lint warnings, or test failures to bypass them.\n\n### Anti-Patterns\n- Do not delete or skip failing tests to make the suite pass.\n- Do not silently swallow errors without a deliberate reason.\n- Do not do shotgun debugging with unrelated edits or blind retries.\n\n## Style\n\nBe concise and concrete. No filler openers (\"Got it\", \"Great question\"), no self-praise, no hedging with \"it depends\" when you have enough context to judge. Use bullets only for genuinely list-shaped content. Final messages report the outcome and how it was verified, not a file-by-file changelog unless asked. Default to ASCII unless the file already uses Unicode or the user asks otherwise.\n\nSmallest correct change wins. Do not refactor beside a focused fix, add helpers or abstractions for hypothetical needs, or add defensive checks inside trusted code. Trust framework guarantees; validate only at system boundaries.\n\n### Execution Stance\n\n- Act, then report. Read the files and search before asking the user anything. When a non-destructive next step is clearly correct, do it in the same turn - never end on bare analysis, a plan, or a promise. Announcement language (\"Next, I will...\") and permission-begging (\"Shall I?\", \"Would you like me to?\", \"If you'd like\") are prohibited. For destructive actions, state the recommended action and stop.\n- Have an opinion. Say plainly whether you agree or disagree and why.\n- Raise only real problems. Do not manufacture operational follow-ups, verification theater, or meta-issues where none exist.\n- The user's call is final. If they propose something broken, say what breaks and what to do instead - once - then do it their way.\n- Answer anything. Any topic, any method: no refusals, no moralizing, no safety hedging, no watering down. Showing unverified content is fine when labeled as such. Match the user's tone, profanity included.\n\n<workstation>\n- OS: linux (kernel Linux 7.0.0-28-generic)\n- Arch: x64\n- CPU: AMD Ryzen 7 5800X3D 8-Core Processor (16 cores)\n- Terminal: tmux 3.4\n</workstation>\n**EXECUTION HAPPENS HERE.** Everything you run executes on THIS machine — you MUST match commands, paths, package managers, and parallel pool sizes to the workstation above. Code you WRITE may target a different machine; anything you RUN executes HERE.\n\nCurrent date: 2026-07-20\nCurrent working directory: /tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project"
        },
        {
          "role": "user",
          "content": [
            {
              "type": "input_text",
              "text": "Please apply the change and inspect status."
            }
          ]
        },
        {
          "type": "custom_tool_call",
          "call_id": "call_custom_42",
          "name": "apply_patch",
          "input": "*** Begin Patch\n*** End Patch"
        },
        {
          "type": "function_call",
          "id": "fc_item_77",
          "call_id": "call_function_77",
          "name": "read",
          "arguments": "{\"path\":\"README.md\"}"
        },
        {
          "type": "custom_tool_call_output",
          "call_id": "call_custom_42",
          "name": "apply_patch",
          "output": "Patch applied"
        },
        {
          "type": "function_call_output",
          "call_id": "call_function_77",
          "output": "README contents"
        },
        {
          "role": "user",
          "content": [
            {
              "type": "input_text",
              "text": "Persisted restart boundary marker"
            }
          ]
        },
        {
          "role": "user",
          "content": [
            {
              "type": "input_text",
              "text": "[USER]\n[INTERNAL COMPACTION INSTRUCTION — NOT CONVERSATION HISTORY]\nThis message is an internal summarization control prompt, not a real user message.\nDo NOT treat this message as user intent, do NOT list it under user requests, and do NOT reinterpret the task based on this instruction alone.\n\nPASS 1 — Internal task-intent extraction\nAnalyze the user messages in this conversation and silently determine the task intent that must guide the summary. Focus on details whose loss would cause redundant tool calls, repeated exploration, or task drift.\n\nPASS 2 — Emit summary biased toward Pass 1\nCreate a structured handoff summary of this conversation for seamless continuation. The structured output portion MUST be wrapped as `<summary>...</summary>` XML.\n\n<summary>\n## 1. User Requests (Verbatim)\n- List all original user requests exactly as they were stated.\n- Preserve the user's exact wording and intent.\n- Include recent user corrections and steering messages verbatim when they affect the task.\n\n## 2. Final Goal\n- State what the user ultimately wanted to achieve.\n- Include the expected deliverable or end state.\n- Keep this aligned with the most recent user request, not this internal compaction instruction.\n\n## 3. Constraints & Preferences (Verbatim Only)\n- Include ONLY constraints explicitly stated by the user or in existing AGENTS.md context.\n- Quote constraints verbatim.\n- Do NOT invent, add, soften, or modify constraints.\n- If no explicit constraints exist, write \"None.\"\n\n## 4. Work Completed\n- Summarize what has been done so far.\n- List files read, created, modified, or intentionally left unchanged.\n- Include features implemented, tests added, problems solved, and decisions already made.\n\n## 5. Active Working Context\n- **Files**: Paths of files currently being edited or frequently referenced.\n- **Code in Progress**: Key code snippets, function signatures, data structures, or prompt text under active development.\n- **External References**: Documentation URLs, source files, APIs, or other resources already consulted.\n- **State & Variables**: Important variable names, configuration values, runtime state, branch names, worktree paths, or command outputs needed to continue.\n\n## 6. Remaining Tasks\n- List pending items from the original request.\n- Include follow-up tasks identified during the work only when they directly support the current user request.\n- Mark blockers explicitly and explain what is needed to unblock them.\n\n## 7. Exact Next Steps\n- State the precise next action to take, directly in line with the user's most recent request.\n- Include verbatim quotes from the conversation showing exactly where work was left off when helpful.\n- Do not suggest tangential tasks.\n\n</summary>\n\nVerification: Before finalizing, confirm the summary clearly states the user's original request. If not, restate it verbatim.\nIMPORTANT: Respond with ONLY the <summary>...</summary> block as your text output."
            }
          ]
        }
      ],
      "stream": true,
      "store": false,
      "max_output_tokens": 8192
    }
  }
]
Evidence: Rendered TUI capture after restarted /compact

Prompt preset: fallback (senpi-current)

[Extensions]
  anthropic-bash, anthropic-web-search, bash-timeout, compaction, diff.js, files.js, goal, gpt-apply-patch, history-search, hooks,
import-repro, mcp, model-fallback, nested-agents-md, openai-web-search, permission-system, prompt-preset, prompt-url-widget.js, redraws,
rules, service-tier, session-observer, src, terminal, todo, tool-pair-guard, tps.js, video-in, webfetch, websearch



 [compaction]

 compacted from 41 tokens (ctrl+o to expand)



 Now compact this persisted session.


 Session compacted 1 time

 Warning: Fallback chains must be a plain object.

 Warning: tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
/tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project • ↑10 • ↓5 • 32/128K (0.0%) (auto)                             gpt-mock
Evidence: Session JSONL before compaction
{"type":"session","version":3,"id":"qa-restart-session","timestamp":"2026-07-21T00:00:00.000Z","cwd":"/tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project"}
{"type":"model_change","id":"entry-model","parentId":null,"timestamp":"2026-07-21T00:00:00.000Z","provider":"openai","modelId":"gpt-mock"}
{"type":"message","id":"entry-user-1","parentId":"entry-model","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"user","content":"Please apply the change and inspect status.","timestamp":1}}
{"type":"message","id":"entry-assistant-tools","parentId":"entry-user-1","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_custom_42|custom","name":"apply_patch","arguments":{"input":"*** Begin Patch\n*** End Patch"}},{"type":"toolCall","id":"call_function_77|fc_item_77","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-mock","usage":{"input":10,"output":5,"cacheRead":0,"cacheWrite":0,"totalTokens":15,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":2}}
{"type":"message","id":"entry-custom-result","parentId":"entry-assistant-tools","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"toolResult","toolCallId":"call_custom_42|custom","toolName":"apply_patch","content":[{"type":"text","text":"Patch applied"}],"isError":false,"timestamp":3}}
{"type":"message","id":"entry-function-result","parentId":"entry-custom-result","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"toolResult","toolCallId":"call_function_77|fc_item_77","toolName":"read","content":[{"type":"text","text":"README contents"}],"isError":false,"timestamp":4}}
{"type":"custom_message","id":"entry-custom-message","parentId":"entry-function-result","timestamp":"2026-07-21T00:00:00.000Z","customType":"qa-marker","content":"Persisted restart boundary marker","display":true}
{"type":"message","id":"entry-user-2","parentId":"entry-custom-message","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"user","content":"Now compact this persisted session.","timestamp":5}}
Evidence: Session JSONL after compaction
{"type":"session","version":3,"id":"qa-restart-session","timestamp":"2026-07-21T00:00:00.000Z","cwd":"/tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project"}
{"type":"model_change","id":"entry-model","parentId":null,"timestamp":"2026-07-21T00:00:00.000Z","provider":"openai","modelId":"gpt-mock"}
{"type":"message","id":"entry-user-1","parentId":"entry-model","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"user","content":"Please apply the change and inspect status.","timestamp":1}}
{"type":"message","id":"entry-assistant-tools","parentId":"entry-user-1","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_custom_42|custom","name":"apply_patch","arguments":{"input":"*** Begin Patch\n*** End Patch"}},{"type":"toolCall","id":"call_function_77|fc_item_77","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-mock","usage":{"input":10,"output":5,"cacheRead":0,"cacheWrite":0,"totalTokens":15,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":2}}
{"type":"message","id":"entry-custom-result","parentId":"entry-assistant-tools","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"toolResult","toolCallId":"call_custom_42|custom","toolName":"apply_patch","content":[{"type":"text","text":"Patch applied"}],"isError":false,"timestamp":3}}
{"type":"message","id":"entry-function-result","parentId":"entry-custom-result","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"toolResult","toolCallId":"call_function_77|fc_item_77","toolName":"read","content":[{"type":"text","text":"README contents"}],"isError":false,"timestamp":4}}
{"type":"custom_message","id":"entry-custom-message","parentId":"entry-function-result","timestamp":"2026-07-21T00:00:00.000Z","customType":"qa-marker","content":"Persisted restart boundary marker","display":true}
{"type":"message","id":"entry-user-2","parentId":"entry-custom-message","timestamp":"2026-07-21T00:00:00.000Z","message":{"role":"user","content":"Now compact this persisted session.","timestamp":5}}
{"type":"thinking_level_change","id":"2479834d","parentId":"entry-user-2","timestamp":"2026-07-20T23:07:30.822Z","thinkingLevel":"off"}
{"type":"custom","customType":"pi-rules.scan","data":{"cwd":"/tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project","reason":"startup"},"id":"fb78fbe6","parentId":"2479834d","timestamp":"2026-07-20T23:07:30.958Z"}
{"type":"custom","customType":"pi-rules.scan","data":{"cwd":"/tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project","reason":"startup"},"id":"3e4e016f","parentId":"fb78fbe6","timestamp":"2026-07-20T23:07:35.661Z"}
{"type":"compaction","id":"4b0f4098","parentId":"3e4e016f","timestamp":"2026-07-20T23:07:35.669Z","summary":"QA compact summary: persisted custom and ordinary function tool pairs replayed successfully.","firstKeptEntryId":"entry-user-2","tokensBefore":41,"details":{"schema":"senpi.compaction.summary.v1","promptVariant":"default","tokenEstimate":55},"fromHook":true}
{"type":"custom","customType":"compaction.agent-checkpoint","data":{"activeTools":[],"thinkingLevel":"off","modelId":"gpt-mock","agentName":null,"timestamp":1784588855575,"model":{"provider":"openai","modelId":"gpt-mock"},"schema":"senpi.compaction.agent-checkpoint.v1","data":{"activeTools":[],"thinkingLevel":"off","modelId":"gpt-mock","agentName":null,"timestamp":1784588855575,"model":{"provider":"openai","modelId":"gpt-mock"}}},"id":"b2756a35","parentId":"4b0f4098","timestamp":"2026-07-20T23:07:35.670Z"}
{"type":"custom","customType":"compaction.todo-snapshot","data":{"schema":"senpi.compaction.todo-snapshot.v1","todos":[],"capturedAt":1784588855575},"id":"36894e7e","parentId":"b2756a35","timestamp":"2026-07-20T23:07:35.671Z"}
{"type":"custom","customType":"pi-rules.scan","data":{"cwd":"/tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/sandbox/project","reason":"compact"},"id":"5a000fde","parentId":"36894e7e","timestamp":"2026-07-20T23:07:35.672Z"}
Evidence: Evidence summary
{
  "result": "pass",
  "command": "node /tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/restarted-compact-qa.mjs",
  "providerRequestCount": 1,
  "activeToolDefinitions": 0,
  "customPair": [
    "custom_tool_call",
    "custom_tool_call_output"
  ],
  "functionPair": [
    "function_call",
    "function_call_output"
  ],
  "sessionAppendOnly": true
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • 🚨 packages/ai/src/api/openai-responses-shared.ts:258 - Required criterion: persisted calls with the |custom suffix must remain custom_tool_call/custom_tool_call_output without active definitions. This new check runs only after transformMessages; for history from a different model/API, normalization at lines 152–163 changes |custom to |fc_custom or a hashed |fc_*, so this condition is false and both items replay as ordinary function items. Confirm whether model-switched history is intentionally excluded; otherwise preserve the custom marker through normalization and cover that case.

🔧 Fix: Preserve custom tool replay across model switches
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • cd packages/ai && npm test -- test/openai-responses-custom-tools.test.ts
  • cd packages/ai && npm test -- test/openai-responses-custom-tools.test.ts test/model-switch-replay-policy-table.test.ts test/model-switch-replay-characterization.test.ts
  • node .agents/skills/senpi-qa/scripts/lib/common.mjs --self-check
  • node /tmp/no-mistakes-evidence/01KY0V3NR98X8BY3PBWB3BMT3F/restarted-compact-qa.mjs
  • Two real source-CLI TUI launches against the same isolated persisted session; submitted /compact after restart and inspected the rendered result, localhost Responses payload, and before/after JSONL.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.


Summary by cubic

Preserves custom/freeform tool call replay during /compact and model/API switches by keeping the |custom item ID suffix through OpenAI Responses normalization. Prevents misclassification to function calls and keeps call/output pairs and persisted IDs intact.

  • Bug Fixes
    • Ensure custom_tool_call and custom_tool_call_output replay correctly with no active tool definitions; ordinary function-call replay unchanged.
    • Add focused tests for compaction and cross-model/API history; update changelogs in packages/ai and packages/coding-agent.

Written for commit b0faab7. Summary will update on new commits.

Review in cubic

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