Skip to content

[good first issue-platform adapt-OpenCode] Add retry-safe durable memory integration - #1181

Open
diqierjia wants to merge 18 commits into
TencentCloud:feat/server_teamfrom
diqierjia:codex/926-opencode-tool-memory-idempotency
Open

diqierjia wants to merge 18 commits into
TencentCloud:feat/server_teamfrom
diqierjia:codex/926-opencode-tool-memory-idempotency

Conversation

@diqierjia

@diqierjia diqierjia commented Aug 27, 2026 •

Copy link
Copy Markdown

Supersession and dependency

Supersedes #978.

This PR carries forward the native OpenCode adapter from #978 and closes the accepted-write / local-ack crash window identified during review.

It is currently stacked on the unmerged Gateway idempotency implementation from #1142. The dependency commits retain their original authorship and will be removed after #1142 merges and this branch is rebased onto the refreshed feat/server_team.

Expected merge order:

  1. Merge feat(gateway): add retry-safe conversation idempotency #1142.
  2. Rebase this PR onto the refreshed feat/server_team.
  3. Re-run the OpenCode and MemoryCore checks.
  4. Merge this PR; [good first issue-platform adapt-OpenCode] Add durable TencentDB Agent Memory integration #978 can then be closed as superseded.

Summary

  • Add deterministic Gateway idempotency to OpenCode L0 capture.
  • Preserve the durable outbox, cross-process claims, restart recovery, automatic recall/capture, and five native memory tools from [good first issue-platform adapt-OpenCode] Add durable TencentDB Agent Memory integration #978.
  • Add tenant/session-scoped idempotency for /v3/skill/conversation/add.
  • Reject same-key/different-payload replays with a conflict response.
  • Persist Skill archives at the exact registered key and scope deterministic task IDs by the complete session identity.
  • Recover interrupted Skill current/meta/receipt write-back without duplicate appends or stale buffered messages.
  • Serialize the complete Skill session read-modify-write path across Core replicas using the existing queue abstraction: Redis uses a token-checked renewable lease, while standalone mode uses the shared local queue.
  • Prevent cross-replica same-key conflicts from being accepted and prevent different-key concurrent requests from overwriting one session buffer.
  • Repair Redis Set/List partial-enqueue state so a retry cannot strand a durable Skill task.
  • Persist Skill task-registration receipts so retries do not recreate work that a worker already consumed, while failed enqueue attempts remain retryable.
  • Retry OpenCode without a key only for the exact unsupported-store 503; unrelated 503 responses remain retryable failures.
  • Recover pending Gateway outbox delivery without repeating L0, quota accounting, metadata registration, embeddings, or standalone JSONL mirroring.
  • Coalesce concurrent delivery of the same pending outbox event and preserve standalone/unkeyed compatibility.
  • Return retryable 503 while keyed pipeline notification or outbox acknowledgement remains pending, preserving the adapter's local retry state.
  • Fix the Skill isolation guard so repository-root Git paths cannot silently bypass its module-relative rules.

Pipeline delivery is recoverable at-least-once. A crash after notification but before outbox acknowledgement can cause redelivery, so downstream consumers should deduplicate by the stable task or event identity.

Refs #926, #978, #1087, and #1142.

Validation

  • OpenCode adapter: 9 files, 49 tests passed; typecheck and build passed.
  • MemoryCore idempotency/session-mutex tests: 5 files, 60 tests passed.
  • MemoryCore build:plugin: passed.
  • OpenCode pack:check: passed.
  • Skill isolation guard: positive run passed; a temporary forbidden node:fs probe was correctly rejected.
  • Real local Gateway/OpenCode E2E previously passed: 5 native tools, repeated idle deduplicated, cross-session recall passed. The adapter code was unchanged by the latest Core follow-up.
  • git diff --check: passed.
  • The latest review follow-up changes no package manifest or lockfile and adds no third-party dependency. The adapter itself uses the OpenCode host plugin as a peer dependency and pins only development tooling.

The OpenCode adapter also retains the bilingual documentation, source-first installer, package checks, local Gateway contract test, and isolated real-host acceptance coverage documented in #978.

DCO note

All commits authored for this PR carry Signed-off-by trailers. The dependency commits retain their original #1142 authorship and will disappear from this PR after #1142 merges and this branch is rebased.

@Maxwell-Code07

Copy link
Copy Markdown
Collaborator

Thank you so much for your attention and contribution! We will arrange an internal review for this PR shortly, and all feedback will be shared right here in the discussion.

Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
@diqierjia
diqierjia force-pushed the codex/926-opencode-tool-memory-idempotency branch from dac9c22 to afe79a4 Compare August 30, 2026 08:37
@diqierjia diqierjia changed the title fix(adapters): make OpenCode and Skill capture retry-safe [good first issue-platform adapt-OpenCode] Add retry-safe durable memory integration Aug 30, 2026
@diqierjia

Copy link
Copy Markdown
Author

@Maxwell-Code07 Could a maintainer please approve the pending fork workflow for the refreshed head?

Latest run: https://github.com/TencentCloud/TencentDB-Agent-Memory/actions/runs/33302053004

This PR supersedes #978 and is currently stacked on #1142. The two commits authored in this PR now carry DCO Signed-off-by trailers; the 10 dependency commits retain their original #1142 authorship and will disappear after #1142 merges and this branch is rebased onto feat/server_team.

Local workflow-equivalent checks passed:

  • OpenCode adapter: 9 files / 46 tests
  • MemoryCore idempotency: 3 files / 35 tests
  • MemoryCore build:plugin
  • git diff --check

After #1142 merges, I will rebase this PR onto the refreshed feat/server_team and re-run the checks. Thank you.

@yangjj-iso yangjj-iso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

概述:为 OpenCode 新增原生 TypeScript 插件适配器(非 Proxy 架构),同时在 MemoryCore 核心层引入 idempotency_key 幂等机制。+6907/-78,约 50 个文件,涵盖核心存储层幂等性、网关路由层、OpenCode 适配器(含安装脚本、测试、文档)。

必须更改(P1/P2)
P1 — 幂等路径丢失向量 Embedding

v2-router.ts 中,带 idempotency_key 的请求走 store.claimConversationAdd() 路径,该方法在 sqlite.ts 的 writeL0ForConversationAdmission 中只写入 L0 metadata + FTS,不写入向量索引。而非幂等路径调用 store.upsertL0(record, emb) 包含向量。

证据:

// 幂等路径 — 无 embedding
const claim = await store.claimConversationAdd!({ scope, payloadDigest, records: acceptedRecords, pipelineRounds });
// 非幂等路径 — 有 embedding
const emb = await embedding.embed(record.messageText);
await store.upsertL0(record, emb);
影响:启用了 Embedding 的部署中,通过幂等路径写入的 L0 记录将无法被向量搜索召回,仅 BM25 可用。需在 claim 成功后补算 embedding 并更新向量,或在 ClaimConversationAddInput 中增加 embedding 参数。

P2 — macOS/Linux 安装路径缺失

SELF_INSTALL.md 和 install-from-source.ps1 仅支持 Windows PowerShell。README 声称"adapter runtime supports macOS/Linux",USER_GUIDE 也说"those platforms must not copy the task's Windows steps yet",但没有提供 macOS/Linux 的替代安装路径。bin/tdai-opencode.mjs 本身是跨平台的(install/doctor/uninstall),应在文档中补充 macOS/Linux 用户使用 tdai-opencode install 的说明。

P2 — add-handler.ts 中 sessionChains Map 无清理

private readonly sessionChains = new Map<string, Promise>();
每个 session 的 key 加入后永不移除。长时间运行 + 大量 session 的进程中会持续增长。coordinator.ts 中的 sessionChains 有同样问题。建议在 promise 完成后删除已 settle 的 entry,或加 LRU 上限。

建议改进(P3)
add-handler.ts 第 297 行存在 pre-existing bug:instance_id: input.instance_id, instance_id: input.instance_id 重复字段。非本 PR 引入,建议顺手修复。
v2-schemas.ts 的 buildConversationIdempotencyScope() 是纯恒等函数(return { ...scope }),可移除或内联。
conversationAddRequestSchema 在 v2-schemas.ts 和 skill-schemas.ts 中重复定义且都加了相同的 idempotency_key regex,建议提取为共享常量。
capture.ts 的 completedTurns 对每个 index 调用 completedTurnAt,最坏 O(n²)。对长 session 可优化为单遍扫描,但典型场景可接受。
优点
幂等设计扎实:receipt + outbox + marker 三层恢复机制,SQLite 单事务原子保证(BEGIN IMMEDIATE),legacy processing 状态的 reclaim 逻辑非常严谨(校验 L0/FTS/vec/outbox scope 一致后才清理)。
测试覆盖全面:sqlite-idempotency.test.ts 519 行覆盖了 claim/replay/conflict/回滚/legacy recovery/FTS 清理/跨 scope 拒绝等边界;v2-router-idempotency.test.ts 覆盖了 503/409/pipeline 失败/unkeyed 保留等路由契约;adapter 侧有并发去重、跨进程 claim、重启恢复、e2e 等测试。
安全实践强:sanitize.ts 脱敏私有 key/bearer/credential URL/local path;format.ts 防止 recalled-block 注入;config 校验拒绝远程明文 HTTP 和 URL 内嵌凭证;installer 拒绝覆盖无关插件、切换 origin 时清除旧凭证。
fail-open 设计:Memory 故障不阻断 OpenCode 对话,失败写入保留在本地 outbox 等待恢复。
文档质量高:中英双语完整对称,含架构图、配置表、故障排查表、安全边界说明。

Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
@diqierjia

diqierjia commented Sep 1, 2026 •

Copy link
Copy Markdown
Author

@yangjj-iso Thank you for the detailed review. I pushed commits f306db3 and 4071b6b and addressed all requested P1/P2 items as well as the P3 suggestions.

Required changes

  • P1 — Embeddings on the idempotent path

    • The Gateway now computes embeddings before the atomic keyed admission and passes them through ClaimConversationAddInput.
    • SQLite persists the vectors in the same transaction as the L0 metadata, FTS rows, receipt, and outbox event.
    • Added router- and store-level regression tests verifying that keyed L0 records remain eligible for vector recall.
  • P2 — macOS/Linux installation

    • Added bilingual macOS/Linux source-install instructions using the cross-platform tdai-opencode install and doctor commands.
    • Documented the XDG configuration path, local file: dependency, Gateway configuration, remote credentials, and verification steps.
  • P2 — sessionChains cleanup

    • Both SkillConversationAddHandler and TurnCoordinator now remove settled entries.
    • Cleanup is ownership-checked so an older operation cannot delete a newer queued tail.
    • Added regression assertions for concurrent success and error paths.

Suggested improvements

  • Removed the duplicate instance_id logger fields.
  • Removed the identity-only buildConversationIdempotencyScope() helper.
  • Extracted the shared conversation idempotency-key Zod schema.
  • Rewrote completedTurns() as a single transcript pass and added an interleaved-parent regression test.

Validation

  • OpenCode adapter: typecheck, build, and 47/47 tests passed
  • MemoryCore idempotency suites on Node.js 22.22.2: 37/37 tests passed
  • MemoryCore build:plugin: passed
  • npm pack dry run: passed
  • git diff --check: passed

The live-Gateway E2E was not rerun because no Gateway was available at the local test endpoint. The fork workflow may still require maintainer approval.

This PR remains stacked on #1142. Once #1142 is merged, I will rebase onto the latest feat/server_team and rerun the full validation.

Could you please take another look when convenient? Thank you.

@yangjj-iso yangjj-iso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

有 3 个阻塞问题:

  1. Skill 归档文件名不一致:返回 data-idem-*.jsonl,实际写入 data-.jsonl,任务会被当作 ghost 丢弃。
  2. OpenCode 始终发送 idempotency_key,但默认 TCVDB Store 不支持事务 claim,远程写入会直接返回 503。
  3. pending receipt 缺少恢复机制;通知失败或进程崩溃后,重试会直接返回成功,但不会重新通知 pipeline。
    另外,Skill task ID 未包含 session,同一 idempotency key 跨 session 会冲突。请修复并补充对应回归测试后再合并。

@yangjj-iso
yangjj-iso self-requested a review September 5, 2026 13:45
Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
@diqierjia

diqierjia commented Sep 6, 2026 •

Copy link
Copy Markdown
Author

已修复审查指出的 3 个阻塞问题。第 3 项原文还附带了 Skill task ID 跨 session 冲突,因此是 3 个编号、4 个修复点:

  1. Skill 归档文件名不一致

    • 按任务注册的准确 data-idem-*.jsonl key 写入归档。
    • 使用真实 LocalStorageBackend 验证任务引用的文件可读。
  2. 默认 TCVDB Store 不支持事务 claim

    • OpenCode 仅在 Gateway 明确返回“不支持事务幂等”的 503 时去掉 key 重试一次。
    • 其他 503 不降级,继续保留为可重试失败。
  3. pending receipt 恢复,以及 Skill task ID 跨 session 冲突

    • pending receipt 重试复用持久化 outbox,再次通知并 ACK;失败继续返回可重试 503。
    • 重试不重复执行 L0、quota、metadata、embedding 或 standalone JSONL 副作用。
    • Skill task ID 包含完整 session 身份。

进一步按审查者视角复查后,又修复了这些恢复边界:

  • Skill archive/current/meta/receipt 部分落盘时,不重复追加或误留旧 current。
  • 多个待恢复请求的 marker 不再互相覆盖。
  • worker 已消费确定性任务后,handler 重试不会重新创建任务;首次入队失败仍可恢复重试。
  • 同一 pending outbox event 的并发通知在单进程内合并;ACK 竞争会回读 durable receipt。
  • standalone 无 pipeline 和 unkeyed 通知失败保持原有成功语义。
  • Redis SADD 成功但 LPUSH 失败时,重试会检测并修复缺失的 List 条目,避免持久化任务永久卡住。

验证结果:

  • OpenCode adapter:9 个测试文件,49 个测试通过;typecheck 和 build 通过。
  • MemoryCore idempotency:4 个测试文件,54 个测试通过。
  • MemoryCore build:plugin 通过。
  • OpenCode pack:check 通过。
  • Skill 隔离红线按脚本等价规则复核通过。
  • git diff --check 通过。
  • 本次补丁无 package manifest、lockfile 或新增第三方依赖变化。

最新修复提交:8c369f2

Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
Signed-off-by: diqierjia <jiahongcheng61@gmail.com>
@diqierjia

diqierjia commented Sep 6, 2026 •

Copy link
Copy Markdown
Author

@yangjj-iso I completed another reviewer-style pass after the previous approval and found one additional blocking concurrency gap plus one CI validation defect. Both are fixed in e5b24e4.

  • Skill session serialization was only in-process. Separate Core replicas could accept the same idempotency key with different payloads, or overwrite data-current for different keys in one session.
  • Added a session-scoped mutex through the existing local/Redis queue abstraction. Redis leases are token-checked, renewed during long operations, and released safely.
  • Added cross-handler conflict/lost-update regressions plus mutex serialization, exception-release, session-isolation, and lease-renewal coverage.
  • Fixed check-skill-queue-isolation.sh: Git returned repository-root paths while its rules matched module-relative paths, so the guard could silently miss violations. Positive and negative-probe checks now pass.

Validation on the new head:

  • MemoryCore: 5 files / 60 tests
  • MemoryCore build:plugin
  • OpenCode: 9 files / 49 tests, typecheck and build
  • OpenCode pack:check
  • Skill isolation positive run and forbidden-import negative probe
  • git diff --check

No package or lockfile changes and no new dependency in this follow-up. Could you please re-review the updated head when convenient?

This branch has not been deployed

No deployments
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.

4 participants