-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodex-map.yaml
More file actions
574 lines (562 loc) · 35.1 KB
/
Copy pathcodex-map.yaml
File metadata and controls
574 lines (562 loc) · 35.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# codex-map.yaml
#
# Provenance map: opencode-codex-memory is a TypeScript port of codex's Rust memory
# system. This file is the single source of truth for "which codex file does
# each of our files come from" and "which codex commit did we last audit".
#
# Update flow:
# 1. Point CODEX_REPO at a codex checkout.
# 2. Run scripts/check-codex-drift.sh
# 3. Port intentional upstream changes; record deliberate divergences in `note`.
# 4. Bump `codex_ref` / `codex_ref_date` to the commit you audited against.
#
# Design invariant (do not break without codex breaking it first):
# Memory is GLOBAL. Project/cwd separation exists ONLY as a soft routing hint
# inside the consolidation + read-path prompts, never as schema-level,
# read-path, or job-level partitioning. See consolidation.md mapping note.
# codex commit this map was last verified against.
codex_ref: ac192cd7937b0d73edc6dffe009940ae53782dd4
codex_ref_date: 2026-09-06
# Upstream directories/files to watch for ANY change since codex_ref.
# The drift script diffs these path prefixes between codex_ref..HEAD.
watch_paths:
- codex-rs/ext/memories
- codex-rs/memories
- codex-rs/state/src/runtime/memories.rs
- codex-rs/state/src/model/memories.rs
- codex-rs/state/memory_migrations
- codex-rs/protocol/src/memory_citation.rs
- codex-rs/core/src/memory_usage.rs
- codex-rs/core/src/stream_events_utils.rs
- codex-rs/git-utils/src/baseline.rs
- codex-rs/utils/output-truncation
- codex-rs/utils/home-dir/src/lib.rs
# Added after the 2026-07-04 parity audit — these are where past blind spots
# lived (config options, lifecycle triggers, pollution channels, dormant
# remote backend):
- codex-rs/config/src/types.rs
- codex-rs/core/src/session/session.rs
- codex-rs/core/src/mcp_tool_call.rs
- codex-rs/core/src/tools/registry.rs
- codex-rs/app-server/src/request_processors/turn_processor.rs
- codex-rs/codex-api/src/endpoint/memories.rs
# Added after the 2026-07-12 review audit — previously unwatched inputs the
# port depends on (redaction patterns, rollout persistence policy, feature
# stage, MCP pollution metadata):
- codex-rs/secrets/src/sanitizer.rs
- codex-rs/rollout/src/policy.rs
- codex-rs/features/src/lib.rs
- codex-rs/codex-mcp/src/server.rs
# Added after the 2026-07-21 audit — paginated-thread memory eligibility,
# captured MCP pollution metadata, and the optional external-memory importer
# also have inputs outside the core memory directories:
- codex-rs/rollout/src/metadata.rs
- codex-rs/rollout/src/state_db.rs
- codex-rs/thread-store/src/thread_metadata_sync.rs
- codex-rs/thread-store/src/local/update_thread_metadata.rs
- codex-rs/codex-mcp/src/binding.rs
- codex-rs/external-agent-migration/src/detect/memory.rs
- codex-rs/external-agent-migration/src/memory.rs
- codex-rs/external-agent-migration/src/memory_import.rs
- codex-rs/app-server/src/external_agent_migration/processor.rs
- codex-rs/app-server/src/external_agent_migration/protocol.rs
- codex-rs/app-server-protocol/src/protocol/v2/config.rs
# ours: path in this repo. theirs: path in the codex checkout.
# Every `theirs` is existence-checked by the drift script (catches renames like
# the read_path.md move from memories/read/ -> ext/memories/).
mappings:
# --- Read path (system-prompt injection + tools) ---
- ours: src/templates/read_path.md
theirs: codex-rs/ext/memories/templates/memories/read_path.md
note: >
Synced from codex_ref with platform adaptations (do NOT byte-sync):
citation block is <memory-citation>/<session_ids> with ses_* ids (codex:
<oai-mem-citation>/<rollout_ids> UUIDs) — citation.ts parses these tags;
quick-pass search + update-note guidance are {{ search_step }} /
{{ update_instructions }} placeholders filled by source.ts: with
dedicated_tools on they instruct the memory_search/memory_read/
memory_add_note tools incl. since/until (memory dir lives outside the
workspace), with dedicated_tools off they revert to codex's file-based
wording (agent reads/writes the FS directly, like codex);
rollout_summaries described as per-session .md recaps (codex: raw .jsonl
rollouts with session_meta/rollout_path).
Injects a SINGLE global memory_summary.md regardless of project.
- ours: src/source.ts
theirs: codex-rs/ext/memories/src/prompts.rs
note: >
builds the injected summary block; takes no cwd/project arg (codex takes
codex_home only). Extra dedicatedTools flag selects tool-based vs
codex-style file-based guidance snippets for read_path.md.
DELIBERATE EXTENSION (same as the memory tools' path guard): resolves
memory_summary.md component-by-component and refuses injection when the
root or summary file is a symlink, then reads through the same O_NOFOLLOW
file descriptor it verified to close the final-component race; codex
prompts.rs trusts the root and only rejects a symlinked root on clear.
- ours: tools/memory.ts
theirs: codex-rs/ext/memories/src/tools/search.rs
note: >
memory_search/memory_read/memory_list/memory_add_note. codex splits these
across tools/{search,read,list,ad_hoc_note}.rs and local/*.rs.
search ports local/search.rs fully: queries[], match modes
(any / all_on_same_line / all_within_lines with minimal-window pruning),
path scoping (file or dir), integer-cursor pagination with
next_cursor/truncated, context_lines, normalized (alphanumeric-only)
comparison, case-sensitive default, 200-result cap, (path,line)
ordering, binary-file skip. DIVERGENCES: match_mode is flattened for
the zod schema (enum + separate line_count instead of codex's
internally-tagged {type, line_count}); max_results is the arg name for
codex's max_results (was `limit` pre-0.2); queries[] is optional
because the port adds since/until time filtering with a no-query
chronological listing mode (no upstream equivalent); that listing
paginates with the same integer cursor as query matches.
read uses line_offset/max_lines like codex plus a 256KiB byte backstop
(codex: 20k-token budget); read on a directory returns a listing (codex:
NotFile error) and skips hidden/symlink names like list/search.
add_note generates the filename plugin-side (codex: model
supplies it) with codex's hyphen layout and append-only collision
handling; port caps note<=4000/title<=120 chars and prepends a
title/created/session header (codex: verbatim unlimited note body).
list supports integer cursor pagination and returns a single entry for
file paths, matching codex. Listing/search walkers use Dirent
file types (codex read_sorted_dir_entries entry.file_type()) so
symlinks are skipped without following.
Note writes reject symlinked path components, matching codex local/
ad_hoc_note.rs ensure_notes_dir.
- ours: src/paths.ts
theirs: codex-rs/utils/home-dir/src/lib.rs
note: >
Upstream data-root discovery used by the local memory-root mapping below.
- ours: src/paths.ts
theirs: codex-rs/ext/memories/src/local.rs
note: >
memory root = <data root>/memories, mirroring local.rs from_codex_home
(codex_home.join("memories")). DELIBERATE ADAPTATION: codex's data root is
find_codex_home (CODEX_HOME env, else ~/.codex); the opencode analog is
opencode's own data dir, path.join(xdgData, "opencode") computed with the
SAME xdg-basedir lib opencode uses (packages/core/src/global.ts), so the
root tracks opencode across platforms and XDG_DATA_HOME. opencode does not
expose this dir through the plugin API (/path returns
home/config/state/worktree/directory, not data), hence the recompute.
OPENCODE_CODEX_MEMORY_TEST_ROOT is the CODEX_HOME analog: an outright
override used by tests and the write-pipeline sandbox.
- ours: src/path-guard.ts
theirs: codex-rs/ext/memories/src/local/path.rs
note: >
safe path resolution that cannot escape the memory root: lexical
containment + per-component symlink rejection + dot-components hidden,
mirroring local.rs resolve_scoped_path. DELIBERATE EXTENSION: the root
itself is also rejected when symlinked (assertMemoryRootSafe, applied by
every memory tool; source.ts uses the full safeResolveMemoryPath for the
injected summary). Reads and overwrites open the final component with
O_NOFOLLOW, verify fstat against a post-open lstat, and truncate only
after verification, closing path-swap races in tools/workspace/baseline;
codex only enforces the root rule when clearing
(control.rs clear_memory_root_contents) and trusts the root elsewhere.
# --- Citations ---
- ours: src/citation.ts
theirs: codex-rs/memories/read/src/citations.rs
note: >
strip/parse <memory-citation><citation_entries>/<session_ids>. Also cross-check
codex-rs/protocol/src/memory_citation.rs for the wire schema.
Primary strip/record seam is opencode's experimental.text.complete hook
(fires before final part persistence), which matches codex's behavior of
stripping from the displayed/persisted message; the
messages.transform + message.part.updated paths remain as fallbacks for
older hosts and pre-existing history (codex keeps citations in raw
history — the port's persisted history is stripped instead, a remaining
divergence). Port requires a closing tag
(codex's stream parser auto-closes at EOF); port drops codex's legacy
<thread_ids> alias and instead accepts its own legacy comma-separated id
list.
# --- Storage / DB ---
- ours: src/db.ts
theirs: codex-rs/state/memory_migrations/0001_memories.sql
note: SQLite schema (stage1 outputs, jobs, session meta).
- ours: src/store.ts
theirs: codex-rs/state/src/runtime/memories.rs
note: >
claim/lease/retry job logic, phase2 input selection, usage recording,
pruning. The behavioral heart of the port; audit carefully on any change.
DIVERGENCES: stage-1 claim gates on the jobs watermark only (codex also
probes stage1_outputs.source_updated_at — diverges only in exotic DB
states); phase-2 success watermark = max(selected source_updated_at)
(codex additionally folds in the claim-time input_watermark;
informational column, nothing reads it); phase-2 selection pages ranked
candidates and probes session.get until maxRaw live rows are collected
(404 → deleteSessionMemory, same as session.deleted; timeout/error keeps
the row). Codex performs the equivalent live-thread check while paging.
Host adaptation (no live quota API): quota/rate-limit stage-1 failures
keep status=pending and do not decrement retry_remaining; exhausted
quota-failed rows are requeued without a newer session watermark. Codex
instead skips claiming via guard.rs before any job is started.
# --- Write path: phase 1 (extraction) ---
- ours: src/phase1.ts
theirs: codex-rs/memories/write/src/phase1.rs
note: >
transcript build + eligibility + stage1 extraction orchestration.
Transcript budget 600k chars, 50/50 head+tail split ~ codex's 150k-token
fallback (codex uses 70% of the model context window when known).
Reasoning parts are excluded like codex (rollout policy Reasoning=false).
DIVERGENCES: a first-time empty transcript is treated as
success-with-no-output (codex fails when a rollout cannot be loaded)
because empty opencode sessions are normal; if a prior extraction exists,
empty fails/retries so a transient empty API response cannot erase it.
Partial-empty stage-1 fields
(empty raw_memory OR empty rollout_summary) match codex → no-output.
Citation blocks are stripped from the transcript before extraction
(codex rollouts retain them; keeps the extractor from mining our own
citation markup). After an observed quota/rate-limit error, remaining
claimed jobs in the same pass are finalized without calling the model
(Codex guard.rs skips the whole startup instead).
- ours: src/capture.ts
theirs: codex-rs/memories/write/src/phase1.rs
note: >
transcript/session loading. In codex this lives inside phase1.rs (reads
rollout .jsonl files directly); the port uses opencode's official API
exclusively — session.messages for transcripts (session-scoped routing
covers cross-project sessions; errors propagate so the job retries
instead of finalizing a false no-output) and GET /experimental/session
(Session.listGlobal, roots=true) for global discovery via the host
client's hey-api transport (V1 SDK has no experimental namespace;
fail-safe: failures skip, never finalize). opencode.db is never read;
the only SQLite is the plugin's own memory.db (D5).
Codex now extracts from legacy and paginated threads alike; the port was
already history-mode agnostic because these APIs expose the session
transcript independently of Codex's rollout storage mode.
See D4 in ARCHITECTURE.md.
- ours: src/templates/stage_one_system.md
theirs: codex-rs/memories/write/templates/memories/stage_one_system.md
note: >
Byte-synced from codex_ref with title only retitled "Single Session"
(opencode UX); keeps codex "rollout" domain language for artifacts.
- ours: src/templates/stage_one_input.md
theirs: codex-rs/memories/write/templates/memories/stage_one_input.md
note: >
Platform-shaped: session_id/session_cwd/transcript placeholders instead of
codex rollout_path/rollout_cwd/rollout_contents (opencode has no rollout file).
# --- Write path: phase 2 (consolidation) ---
- ours: src/phase2.ts
theirs: codex-rs/memories/write/src/phase2.rs
note: >
consolidation orchestration + heartbeat/lease. Ports codex #32193
validate_consolidation_artifacts gating: early no-diff succeed only when
MEMORY.md exists and memory_summary.md starts with `v1`; post-agent
invalid artifacts fail the job without baseline reset. Like codex, the
consolidator edits the LIVE artifacts (no staging/promote step): a
failed or timed-out run can leave a partial summary until the retry
repairs it — reviewed 2026-07-12 and kept as-is for parity; do not add
staging unless codex does.
The opencode session.prompt transport can return HTTP 200 while the
assistant message carries an error; llm.ts treats that as failed_agent
so phase2 preserves the workspace diff, matching codex's requirement
that AgentStatus be Completed before validation/baseline reset.
Observed-quota stamp (src/ratelimit.ts) skips claiming phase 2 the same
way Codex start.rs skips both phases when guard.rs fails.
DIVERGENCE (codex_ref 1bbdb32, 2026-07-15 audit): codex now threads a
parent_permission_profile into phase2::run/agent::get_config so the
consolidation agent inherits the parent session's OS sandbox choice
(Disabled->Disabled, External->External, Managed->the old
WorkspaceWrite(memory root, no network)). The port has no equivalent:
the consolidator's sandbox is the opencode `memorize` agent allowlist
(D2), not a codex SandboxPolicy, and opencode plugins get no parent
permission profile. No behavior to port; consolidateViaSubagent stays.
DIVERGENCE (codex_ref ba42e68, 2026-07-30 audit): codex holds the lease
when the consolidation agent's shutdown fails, where "failed" is a thread
handle that did not terminate. The port has no thread handle: a failed
shutdown is a failed or timed-out session.delete call, deliberately NOT
an unconfirmed 404 — sessionDeletionConfirmed returns false on hosts without session.get
and would otherwise stall every job for a full lease.
- ours: src/workspace.ts
theirs: codex-rs/memories/write/src/storage.rs
note: >
rebuild raw_memories, write rollout summaries, prune extension resources,
validate_consolidation_artifacts (also codex workspace.rs).
DIVERGENCES: raw_memories.md entries are truncated at 10k chars each
(codex: unbounded; guards the consolidation prompt against giant
extractions); rollout summary headers add usage_count and drop
rollout_path/git_branch (metadata the port does not have); ensureLayout
pre-seeds placeholder MEMORY.md + empty memory_summary.md (codex: the
consolidator INITs them; outcome-equivalent, first diff shows M not A).
Claude project memory import is no longer a divergence: see
src/claude-import.ts. Codex-CLI consolidated-memory exchange remains in
src/codex-interop.ts.
- ours: src/claude-import.ts
theirs: codex-rs/external-agent-migration/src/memory_import.rs
note: >
Port of codex's Claude (external-agent) memory import into
extensions/external_agent_import/. Also tracks memory.rs for discovery.
Discover ~/.claude/projects/<key>/memory markdown (skip symlinks), resolve
project cwd from newest session *.jsonl with an absolute existing cwd
(project_cwd_from_sessions), whole-project replace with scope.json +
EXTENSION_INSTRUCTIONS (byte-equality change detection, source-gone
removes resources). DIVERGENCES: no migration UI / selected_memory list —
continuous phase-2 sync when claude_import.enabled (optional projects
allowlist; omit = all with cwd); unreachable Claude home is a quiet no-op
(not fatal); no state-DB enqueue (sync runs inside the claimed phase-2 job
after baseline, before diff, same ordering as codex-interop import);
project keys rejected if path-unsafe or hidden (path-guard); instructions
add optional [from claude] provenance tag. Default-off via claude_import
plugin option (codex: migration feature / include_memory detect gate).
- ours: src/codex-interop.ts
theirs: codex-rs/external-agent-migration/src/memory_import.rs
note: >
DELIBERATE ADAPTATION, not a 1:1 port: reuses codex's external-agent
memory-import pattern (extensions/<name>/instructions.md + resources/,
byte-equality change detection, instructions refreshed on constant
change, source-gone removes resources) but with per-file in-place
replace instead of codex's whole-dir delete+rewrite (fixed 2-artifact
set; non-regular files at target paths are replaced, never written
through), and the source is Codex's own GLOBAL consolidated memory
(MEMORY.md + memory_summary.md), not per-project Claude memories — so
no scope.json, no project selection surface, no state-DB gating.
Claude per-project import lives in src/claude-import.ts (same extension
name/layout as upstream). An unreachable codex home is NOT a deletion
signal (only an existing memories root with missing artifacts is),
diverging from codex's fatal-on-missing-home because the plugin must
degrade quietly. Codex syncs out-of-band and enqueues consolidation; the
port syncs inside the claimed phase-2 job (after baseline, before diff
capture) because its claim is time-gated and pre-claim workspace writes
could race a running consolidator's lease. Also adds an export direction
with no codex equivalent: our consolidated artifacts are written into
$CODEX_HOME/memories/extensions/opencode_import/ for codex's consolidator
to merge via its generic extensions contract (additive only; codex's
workspace is never bootstrapped and its state DB never touched).
Instructions constants adapt codex's EXTENSION_INSTRUCTIONS with
cross-system provenance tags ([from codex]/[from opencode]) as an echo
guard — needed because this exchange is two-way while codex's Claude
import is one-way and cannot echo. The guard is deliberately
instruction-level, matching codex's own all-instruction content rules
(see ARCHITECTURE.md D6). Default-off via the opencode-specific
codex_interop plugin option.
- ours: src/git-baseline.ts
theirs: codex-rs/git-utils/src/baseline.rs
note: >
git baseline diffing for phase 2. Port uses isomorphic-git instead of
libgit2; also see codex-rs/memories/write/src/workspace.rs for the diff
wiring. captureWorkspaceDiff throws on failure (ports codex
failed_workspace_status — an errored diff must not look like "no
changes"); ensureBaseline self-heals corrupt git metadata by destructive
re-init (ports reset_git_repository_sync-on-unusable).
DIVERGENCES: patches use the `diff` package's createPatch headers
(Index:/---/+++) instead of codex's git-style `diff --git` rendering; no
file-mode/symlink diff rendering; workdir content reads fail closed on
symlinks instead of including their targets in the generated diff;
hidden paths (dot components, e.g. Finder `.DS_Store`) are omitted from
staging and workspace diffs because path-guard reports them as not found
— including them failed phase 2. Codex's walker includes them and reads
bytes directly.
- ours: src/templates/consolidation.md
theirs: codex-rs/memories/write/templates/memories/consolidation.md
note: >
Synced from codex_ref with platform adaptations (do NOT byte-sync):
rollout annotations demand cwd/session_id/updated_at (codex:
cwd/rollout_path/thread_id — metadata our artifacts do not have);
thread-id terminology replaced with session ids throughout; shell-command
examples replaced with file-tool equivalents (codex's consolidator has a
sandboxed shell — wc -l / rg --files; our memorize agent has
read/edit/write/glob/grep only); the optional housekeeping step tells the
agent it cannot delete files (summary pruning is plugin-side keep-set
logic in workspace.ts, codex expects agent-driven deletion); secret
replacement token is [REDACTED:secret] (redact.ts style, codex:
[REDACTED_SECRET]); "managed by Codex" branding rewritten to the plugin.
{{ memory_extensions_* }} placeholders are rendered by
buildConsolidationPrompt in src/llm.ts, mirroring codex prompts.rs +
lib.rs prompt_blocks. INVARIANT: project/cwd scoping lives ONLY here,
as an LLM instruction to "organize the index first by cwd / project scope,
then by topic". Do not add hard scoping elsewhere unless codex does.
# --- Control / lifecycle ---
- ours: tools/control.ts
theirs: codex-rs/memories/write/src/control.rs
note: >
memory_reset/inspect/mode; clearMemoryRootsContents equivalent.
DELIBERATE DIVERGENCE: these are model-invocable tools, always registered
(codex exposes reset/mode only as client-side RPCs / CLI, never to the
model) — a plugin has no client-UI surface, and "reset my memory" in
plain language is core plugin UX; opencode's generic tool permissions are
the guard rail. memory_mode additionally accepts "polluted" (codex
protocol: enabled|disabled, polluted is set only automatically);
memory_inspect is a port addition (read-only status).
- ours: src/index.ts
theirs: codex-rs/core/src/stream_events_utils.rs
note: >
citation extraction from streamed assistant text + turn-start/idle
pipeline triggering.
Loosest mapping; the plugin hook wiring is opencode-specific.
DELIBERATE DIVERGENCES vs codex lifecycle: (1) codex kicks the write
pipeline at turn start (app-server turn_processor.rs -> start.rs); the
port pumps at the first chat.message of a session (same turn-start
semantics) AND on idle events — an extra trigger codex does not have.
(2) codex ships memories default-off behind Feature::MemoryTool with a
TUI consent prompt (the feature is Stable now, still default-off); the
port is on-by-default — installing the plugin is the consent (README
states this). memory_mode stamping happens at the first chat.message of
a session (closest plugin-visible moment to codex's
stamp-at-thread-creation, session.rs), with the idle path as fallback;
the same hook pumps phase 1 so extraction no longer depends on idle
events; pollution marking happens in tool.execute.before — at INVOCATION,
matching codex, which marks inside handle_approved_mcp_tool_call before
the call runs (mcp_tool_call.rs maybe_mark_thread_memory_mode_polluted)
and marks web search on the completed response item
(stream_events_utils.rs response_item_may_include_external_context:
WebSearchCall/ToolSearchCall/ToolSearchOutput). tool.execute.after is
deliberately NOT used: opencode does not guarantee it (session/tools.ts
awaits execute() with no ensuring/catchAll; an abort interrupts the
fiber), so a failed or cancelled call left the session unmarked.
DIVERGENCE: marking at invocation also marks permission-denied calls,
which codex does not — the safe direction for an opt-in guard, since the
alternative under-marks. Classification covers websearch/webfetch plus
every live MCP server via client.mcp.status(); server names use
opencode's own tool-prefix sanitization (codex marks all MCP servers
pollutes_memory=true). The live status request is aborted after 1s so a
stalled local endpoint cannot block tool execution. codex has a third
channel with no port:
registry.rs marks on output.contains_external_context() after a
successful builtin tool — opencode exposes no equivalent output flag. (3) the memorize / memorize-extract
sub-agents self-register through the plugin config hook (user-defined
agents of the same name win) — pure opencode packaging; codex ships its
memory agents inside the binary; injection appends an external_directory
allow for <memory root>/* to memorize because opencode gates file tools
outside the session's project and the global memory workspace is always
external (no codex equivalent — Seatbelt sandboxing is process-level);
the memorize prompt adds a
10k-char/2500-token summary-size instruction codex's consolidator does
not get (both sides also truncate at 2500 tokens on read).
(4) generate_memories=false turns off the whole write pipeline, agent
injection, and delete-triggered consolidation (README documents it as
the pipeline switch); codex only stamps NEW threads memory_mode=disabled
and keeps the pipeline running for previously-enabled ones.
# --- Prompt building ---
- ours: src/llm.ts
theirs: codex-rs/memories/write/src/prompts.rs
note: >
buildConsolidationPrompt ports build_consolidation_prompt including the
lib.rs prompt_blocks extension blocks (rendered only when
<memory_root>/extensions exists). The rest of llm.ts (sub-agent session
plumbing) is opencode-specific — see D3 in ARCHITECTURE.md.
D3-adjacent DIVERGENCES: stage-1 requests opencode's json_schema output
format (EXTRACTION_SCHEMA in src/llm.ts) — the platform equivalent of
codex's output_schema + output_schema_strict — and reads the result from
AssistantMessage.structured; validateExtraction rejects prompt-skeleton
echoes and treats empty fields as no-op. opencode enforces json_schema via
a forced StructuredOutput tool call (toolChoice: required), so the
otherwise tool-less memorize-extract agent allows that one
side-effect-free tool (opencode.json; tests/agents.test.ts enforces the
exception). Model config lookup is timeout-bounded and only successful
responses are cached, so transient host failures cannot pin missing model
config until reload. parseExtraction (brace-scrape, unknown-field tolerant)
stays as a fallback for hosts/models that don't populate structured. Per-request
timeouts mirror the 1h job leases (codex relies on the lease alone);
timed-out or ownership-cancelled prompts call the bound session.abort SDK
method with a 1s bound before session deletion; deletion is bounded to
codex's 10s shutdown timeout. Cleanup is awaited for consolidation because the phase-2 lease
depends on it (see the src/phase2.ts note), fire-and-forget for
extraction, which has no codex agent-shutdown counterpart and no write
tools. Dispose: extractViaSubagent defaults to pluginShutdownSignal();
consolidateViaSubagent takes the phase-2 AbortSignal (heartbeat can cancel
consolidator without full plugin shutdown). Both also session.abort for
host cleanup. plugin-created sessions carry a
durable metadata ownership marker, and startup cleanup requires both that
marker and an exact generated title because OpenCode copies metadata to
user forks. Exact legacy titles are temporarily reseeded but never
deleted; plugin startup awaits a bounded, title-filtered, cursor-paginated
host-wide list/reseed pass while stale deletions continue through a
concurrency- and time-bounded background batch and retain ownership
unless a follow-up session.get confirms 404 (OpenCode may return success
after an internally failed remove);
AssistantMessage.error is treated as a failed sub-agent turn even though
opencode returns it with HTTP 200;
reasoning effort is pinned via PromptInput.variant low/medium (codex:
Low extract / Medium consolidate). SDK types omit variant (same lag as
format). DELIBERATE DIVERGENCE: Codex only talks to Codex models and
hardcodes those efforts. This plugin looks up the model's OpenCode
variants and, if the pin is missing, selects the nearest listed effort
on none < minimal < low < medium < high < xhigh < max (ties → higher).
Empty variant lists omit the pin (host default). Catalog lookup failure
or a missing variants field keeps the Codex pin (unknown variant is a
host no-op). Custom picker names such as "fast xhigh" are ignored.
# --- Utilities ---
- ours: src/redact.ts
theirs: codex-rs/secrets/src/sanitizer.rs
note: >
secret redaction for stage-1 outputs (codex calls redact_secrets on
raw_memory/rollout_summary/slug and on the serialized rollout; the port
redacts the transcript lines and the extraction outputs).
DELIBERATE SUPERSET: codex ships 4 patterns (openai key, AKIA, bearer,
bare-key assignment); the port adds anthropic/github/slack/private-key
patterns, uses labeled [REDACTED:*] tokens vs codex's [REDACTED_SECRET],
allows quotes around the assignment KEY so JSON/YAML forms
like "password": "..." are caught — codex's SECRET_ASSIGNMENT_REGEX
misses those. An escape-aware value scanner preserves keys, separators,
comments, JSON trailing delimiters, and nested object/array boundaries;
unquoted values become quoted [REDACTED] strings, so redacting JSON/YAML
payloads remains parseable without leaking punctuation-bearing suffixes;
assignment-like content inside an enclosing quoted string is replaced
without adding another quote layer.
Bearer matches codex (word-boundary, space/tab only, 16+ body, trailing
=*, applied before key patterns). AKIA still omits codex's \b anchor.
isMemoryExcludedFragment lives here too (upstream: memories/write
phase1.rs is_memory_excluded_contextual_user_fragment) but is INERT on
opencode: AGENTS.md is joined into system[0] and skills are rendered as
an <available_skills> system catalog (skill/index.ts fmt), so neither
ever appears as a user text part. Kept for parity; the structural
`ignored`-part filter in capture.ts is what actually excludes
non-conversation content here.
- ours: src/token.ts
theirs: codex-rs/utils/output-truncation
note: token-limit truncation for the 2500-token summary cap (chars/4 estimate here).
- ours: src/ratelimit.ts
theirs: codex-rs/memories/write/src/guard.rs
note: >
DELIBERATE DIVERGENCE: codex gates on real provider rate-limit windows
(guard.rs, min_rate_limit_remaining_percent, fails open; called from
start.rs once per startup — a pure read with no process-local stamp).
Opencode does not expose provider rate limits, so the port keeps a phase-1
process-local anti-stampede (30s) that stamps only AFTER a stage-1 claim
succeeds; empty/no-claim passes do not burn the interval (matches codex
skipped_no_candidates free retry). After an observed quota/rate-limit
error, a 1h process stamp skips further claims for the same configured
model (or the same phase when no model is configured). Codex start.rs skips
both phases when its account-level live guard fails; model scoping avoids
blocking independently configured opencode providers. Phase 2 still has
no 30s timer — DB claim/cooldown only, like codex. Wire
min_rate_limit_remaining_percent when the host exposes provider quota.
# Platform-only modules (no codex theirs path — do not map with theirs: null,
# the drift script existence-checks every theirs value):
# src/lifecycle.ts — dispose/reload abort for consolidator + sub-sessions
# src/host-client.ts — OpenAPI type-lag cast adapters
# src/reasoning-variant.ts — nearest OpenCode effort when Codex low/medium is absent
# src/diagnostics.ts — inspect ring buffer (codex has OTEL metrics instead)
# --- Extensions (ad-hoc notes) ---
- ours: src/workspace.ts
theirs: codex-rs/memories/write/src/extensions
note: >
ad-hoc notes extension instructions + retention pruning. codex keeps this
in a dedicated extensions/ module; the port folds it into workspace.ts.
# --- Config options ---
- ours: src/index.ts
theirs: codex-rs/config/src/types.rs
note: >
Plugin options mirror codex MemoriesToml/MemoriesConfig 1:1 by NAME so
syncing stays trivial. Supported: generate_memories, use_memories,
dedicated_tools, disable_on_external_context, max_raw_memories_for_consolidation,
max_unused_days, max_rollout_age_days, max_rollouts_per_startup,
min_rollout_idle_hours, extract_model, consolidation_model. Defaults match
codex (max_rollout_age_days=10, min_rollout_idle_hours=6,
max_rollouts_per_startup=2, max_raw_memories_for_consolidation=256).
Numeric knobs are clamped to codex's ranges (types.rs From<MemoriesToml>):
(1,4096)/(0,365)/(0,90)/(1,128)/(1,48). Unknown option keys warn (codex
deny_unknown_fields errors). use_memories=false hides the memory tools
like codex's extension gating.
DIVERGENCES: (1) dedicated_tools defaults to true here (codex: false) so the
standalone plugin ships its memory tools out of the box; (2) codex's
min_rate_limit_remaining_percent is not implemented — see src/ratelimit.ts
stub; (3) unset extract_model/consolidation_model fall back to opencode's
configured small_model/model respectively (mirroring codex's
gpt-5.6-luna/gpt-5.6-terra cheap-vs-capable split), then to the session default
— opencode's automatic small-model pick (provider.getSmallModel) is not
exposed to plugins, only the explicit config keys are;
(4) the legacy alias no_memories_if_mcp_or_web_search is not accepted
(fresh plugin, no legacy configs); (5) wrong-TYPED values on known keys
warn via memory_inspect and fall back to the default (codex: serde
hard-fails the whole config parse — a plugin must not break the host on
bad options).