Skip to content

feat(adapters): add Qoder session adapter - #305

Merged
samzong merged 5 commits into
mainfrom
feat/alibaba-agent-cli-adapter
Oct 7, 2026
Merged

samzong merged 5 commits into
mainfrom
feat/alibaba-agent-cli-adapter

Conversation

@samzong

@samzong samzong commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

What's changed?

  • Add a qoder source (label QD) for Qoder CLI transcripts under $QODER_CONFIG_DIR or ~/.qoder, and $QODERCN_CONFIG_DIR or ~/.qoder-cn. Each root uses its environment override before a home-directory fallback; a root with neither is skipped without error. China-edition session IDs are namespaced with cn:. Index main transcripts and <session>/subagents/agent-*.jsonl, with spawn links and distinct child IDs. Accept plugin-qualified and custom subagent filenames containing colons, dots, and spaces, while rejecting path separators.
  • Extract user and assistant text from message.content, cwd and RFC3339 timestamps. Keep tool payloads, images, reasoning, runtime injections, compaction summaries, and client-generated API/authentication errors out of searchable messages.
  • Record tool_use / tool_result blocks as session events. Preserve call IDs and result status, file evidence for Read / Edit / MultiEdit / Write / NotebookEdit, and command evidence for Bash / PowerShell. PowerShell command text is preserved, but shell-derived file evidence is explicitly unsupported. Rewind ancestry crosses compaction boundaries through logicalParentUuid, keeping pre-compaction tools active and discarded branches inactive.
  • Read cwd and inline titles from the transcript. Prefer the current worktree cwd (or worktree path), restore original cwd after worktree exit, then use relocation cwd and the latest message cwd. Derive Fork links from the first forkedFrom.sessionId, preserving the China-edition namespace. Do not read speculative sidecars: Qoder CLI 1.1.65 does not write the -session.json / fork_from format.
  • Record exposed, nonzero integer token counts, deduplicated by model-response ID within each transcript. Deduplicate stable assistant:<message.id> keys across Qoder sessions when aggregating usage and wrapped reports, so copied fork history is counted once. Keep per-session records intact and do not deduplicate assistant:line:<n> fallback keys. Qoder has its own seen set, separate from other sources. Split persisted cache-inclusive input into fresh input, cache read, and cache write, clamping fresh input to zero. Do not estimate hidden token counts from credits, context size, or token-stats. Provider remains unknown when absent from the transcript. Usage and event parser versions are 2; metadata parser version is 3 so unchanged transcripts can repair earlier index results.
  • Resume main sessions with qodercli --resume <id> (qoderclicn for China-edition sessions); start with qodercli <prompt>. Subagent transcripts are searchable but are not independent resume targets.
  • Add sanitized authenticated main/resume/fork transcripts alongside the synthetic usage fixture, sixteen adapter tests, and two Qoder usage regression tests. Qoder IDE session support is tracked separately in Add Qoder IDE conversation adapter #307.

Why

Qoder CLI sessions were not searchable or resumable in Recall. The published Qoder CLI 1.1.65 package confirms a Claude-shaped message.content envelope, while the existing Qwen parser expects message.parts. A dedicated Qoder parser uses the shared JSONL, timestamp, file-scan, event, and shell-evidence helpers. This keeps Qoder's authentication filtering and token redaction separate from Claude's provider accounting without changing either existing parser.

Qoder CLI 1.1.65 normalizes Anthropic, OpenAI Chat, and OpenAI Responses usage into OpenAI chunks before OM persists prompt_tokens as input_tokens. Anthropic normalization includes cache creation and cache read in that count. Subtracting both cache buckets matches Recall's disjoint usage fields and prevents cached requests from disappearing from usage reports.

buildForkEntries copies the original message ID and usage into fork transcripts. Aggregation-level deduplication preserves searchable fork history and usage when a fork is viewed alone, while preventing double counting when the parent and fork are reported together. Using the shared UsageDedup also keeps CLI/TUI usage and wrapped totals consistent; Qoder response IDs use a separate seen set from other sources.

Compatibility limit: an OpenAI-compatible service that returns top-level cache_creation_input_tokens without including them in prompt_tokens will cause fresh input to be undercounted. Authenticated custom-provider transcripts are still needed to establish whether such a service is used; a demonstrated non-inclusive provider format would require a reliable discriminator before changing accounting.

Verification

  • make check passes: cargo-audit 0.22.2 (matching CI), format check, workspace Clippy with --all-targets --features bench -- -D warnings, and workspace tests. The core suite passes 915 tests, including sixteen Qoder adapter tests and two Qoder usage tests. cargo test --lib qoder passes 18 tests; cargo test --lib usage passes 82 tests.
  • Explicit configuration roots work without a home directory, either individually or together. A missing root is skipped, and the home-directory fallback remains available. Qoder, Junie, and Zed registrations are retained.
  • Authenticated main/resume/fork captures cover visible messages, cwd, millisecond timestamps, titles, linked Read/Bash/Edit events, copied fork history, and zero native-provider usage. The native CLI successfully resumes the main session using Recall's generated command. Detailed commands and observations are recorded below.
  • Isolated synthetic-data verification covers sync, incremental sync, single-session sync, force sync, event-backfill preview, search, session show, usage, and export. The base fixture yields three messages, six tool events, and three usage events totaling 480 input / 47 output tokens. Parent plus copied fork still yields three usage events / 480 input / 47 output / 527 total; per-session usage remains intact, line-key usage is retained, and Qoder/Claude seen sets are separate. These token values are synthetic assertions, not measured consumption.
  • Cache-inclusive records expose 50 fresh input / 40 output / 3000 cache read / 200 cache write. Compaction-aware rewind keeps pre-compaction Read call/results active and discarded Edit call/results inactive. Parser versions support backfilling unchanged transcripts without --force.
  • Plugin namespaces, custom names containing spaces and dots, and names containing :agent- remain searchable and preserve their original parent with relation spawn. China-edition parent prefixes and fork links are covered by parser tests. Directory selection covers latest cwd, relocation, worktree entry, and restoration after worktree exit.

Test evidence

  • Tools: official native Qoder CLI 1.1.65, model Qwen3.8-Flash; branch-built Recall 0.6.3; cargo-audit 0.22.2 matching CI. OS: macOS 27.0.1, arm64. The published @qoder-ai/qodercli 1.1.65 bundle was also inspected for persistence and usage serialization. All Recall commands used a separate sandbox HOME and SQLite index; the normal user index was not touched.
  • Authenticated real validation on 2026-10-07 used exactly three short model tasks against a temporary project. The already authenticated isolated Qoder HOME was used without inspecting, copying, or changing its authentication file. No BYOK configuration, account settings, or login state was changed. The existing login-created session was preserved and indexed.
  • Main task: HOME=/tmp/recall-qoder-install/home /tmp/recall-qoder-install/home/.local/bin/qodercli -w /tmp/recall-qoder-probe -m Qwen3.8-Flash --session-id <main-id> --name "Recall probe main" --permission-mode bypass_permissions --tools Read Bash Edit --max-output-tokens 400 --max-model-request-retries 0 -p "Use Read to read sample.txt, use Bash to run wc -l sample.txt, then use Edit to append exactly one line gamma to sample.txt. Only work on this file. Do not delegate. Report the final line count in one short sentence." exited successfully. The actual file became alpha, beta, gamma, and the assistant returned The final line count is 3. The transcript contains Read, Bash, Edit, and two additional Bash calls, each with a result.
  • Resume: recall session resume --source qoder --source-id <main-id> --print-command --format json produced program qodercli and arguments ["--resume", "<main-id>"]. Those generated arguments were run through the isolated native executable with -w /tmp/recall-qoder-probe -m Qwen3.8-Flash --permission-mode bypass_permissions --tools "" --max-output-tokens 120 --max-model-request-retries 0 -p "Without using tools, what line did you append to sample.txt in this session? Answer with that word only." It returned gamma and appended the question and answer to the same transcript.
  • Fork: the same native resume arguments and short-task options were run with --fork-session --name "Recall probe fork" -p "Without using tools, answer FORK_OK only.". Qoder created a new transcript with copied history and per-record forkedFrom pointing to the main session. The source preserved the requested prompt but the model answered The final line count in sample.txt is 3. instead of the requested marker. Recall indexed that actual answer; the model response mismatch did not prevent fork ancestry or history validation.
  • Real Recall output: recall sync --source qoder --project all -v after resume/fork reported 1 new, 1 updated, 1 unchanged, 14 messages. session show --include metadata,messages,events,usage --format json reported 6 messages / 10 events for main, 8 messages / 10 events for fork, and 1 message / 0 events for the preserved pre-existing session. All visible message text, roles, timestamps, earliest-message start times, and cwd values were compared directly with the real JSONL records. Main/fork titles matched Recall probe main / Recall probe fork; the fork topology had relation fork to main. Cwd was /private/tmp/recall-qoder-probe. Tool-call IDs, all ten event timestamps per transcript, and Read/Edit file targets matched the original records and their results. The real results omit is_error, so Recall correctly leaves result status unset rather than inventing success.
  • Native-provider input, output, cache-read, and cache-write counts were all zero. Each real session had zero usage events; recall usage --source qoder --json reported 0 events / 0 total tokens before and after fork. This confirms hidden-count handling, but does not establish nonzero fork usage deduplication. recall search gamma --source qoder --project all --format json found main and fork. Repeated sync reported 3 unchanged, 0 messages. No subagents appeared in these tasks.
  • tests/fixtures/qoder/real-main.jsonl and real-fork.jsonl are sanitized captures from these authenticated sessions, not handwritten conversations. They retain only probe content and relevant envelope fields, replace session/record/response/tool identifiers consistently, normalize project paths, redact reasoning, and remove account/billing/runtime metadata. The original login conversation is not exported. Fixture provenance and limits are documented in tests/fixtures/qoder/README.md. Two new tests cover the real main/resume and copied-fork formats; all 18 Qoder-related tests pass. The full make check passes, including 915 core tests.
  • Isolated fixture replay with the current branch binary indexes seven transcripts: real-capture main/fork fixtures retain 6/8 messages and 10 events each, with zero usage and the expected fork link. Three plugin/custom subagents remain searchable with the correct spawn parent. Parent plus copied synthetic fork reports 3 usage events / 480 input / 47 output. Repeated sync reports 7 unchanged, 0 messages. This replay uses sanitized/synthetic fixtures and does not consume additional native-model requests.
  • Bundle/synthetic proof remains separate from measured data: original Ogi / nVA / OM and UQc functions normalize cache-inclusive input; keo provides compaction ancestry; buildForkEntries copies response IDs and usage; Ob permits plugin-qualified subagent names; cco selects worktree/relocation/latest cwd. Isolated synthetic Recall verification reports 3 usage events / 480 input / 47 output for parent plus fork, preserves pre-compaction active tools across rewind, and finds plugin subagents. These counts are assertions, not real consumption.
  • Still unverified with real data: BYOK nonzero token values and cache accounting, the non-inclusive provider compatibility case, China-edition executable behavior, IDE history, authenticated subagents/compaction/rewinds, worktree/relocation behavior (including cwd changes across rewind), and actual PowerShell/NotebookEdit execution. Recall's generated start command was not executed directly; the real initial task used native print mode.

Closes #279

Signed-off-by: samzong <samzong.lu@gmail.com>
@samzong

samzong commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T14:08:36.791264Z d0f1392 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: d0f1392303

ℹ️ 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 src/adapters/qoder.rs Outdated
Signed-off-by: samzong <samzong.lu@gmail.com>
…i-adapter

Signed-off-by: samzong <samzong.lu@gmail.com>
Signed-off-by: samzong <samzong.lu@gmail.com>
Signed-off-by: samzong <samzong.lu@gmail.com>
@samzong
samzong marked this pull request as ready for review October 7, 2026 16:22
@samzong
samzong merged commit fdce139 into main Oct 7, 2026
4 of 5 checks passed
@samzong
samzong deleted the feat/alibaba-agent-cli-adapter branch October 7, 2026 16:22
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.

Add Qoder session adapter (CLI and IDE)

1 participant