You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(watcher): carry Codex turn_context author model across realtime reads (Refs #655, stacked on #1639) - #1651
Draft. Stacked on PartA #1639 (claude/orc-final-credit-batch-z2uwd0 @ a03bca7), not main. Do not merge, mark ready, or queue it. The owning lead reviews it.
Task marker: cloud-credit-20261105-brainlayer-hammer-codex-author-state. Refs #655. This is not a Fixes: PartA has to land first, and the gaps listed under Limitations are still open.
What was broken
PartA (a03bca7) records the per-turn author model in the batch Codex parser and in Claude's message.model. The realtime watcher got none of it for Codex:
normalize_provider_entry returns None for turn_context and session_meta, so the model and provider were thrown away before classification. On a03bca7, no realtime Codex chunk carries model or model_provider. The new tests confirm this (old run below).
A one-line fix that just remembered "the last model seen" would still be wrong. After a restart, a replay of confirmed offsets, a rewind, a replaced inode, or quarantined bytes, the reader starts somewhere the remembered value does not describe. It would then stamp a stale model on the turn, or no model at all.
What changed (watcher normalization / per-file context only)
src/brainlayer/watcher.py
CodexTurnContext(inode, offset, model, model_provider) is stored on each JSONLTailer, so every file has its own. It is trusted only at the exact (inode, offset) it describes.
_annotate_codex_author_context runs in poll_once before normalization. If the read starts anywhere other than the stored (inode, offset) (restart, replay after a failed read, rewind to 0, new inode, skipped quarantine bytes), the context is rebuilt by scan_codex_turn_context from the file's own bytes before the read start. The scan only parses lines that mention turn_context or session_meta. It returns unknown if the inode it opened is not the one being read. An unparseable turn_context resets the model to unknown instead of keeping the older one.
fold_codex_turn_context follows PartA's batch semantics. Every turn_context replaces the model, and an absent model or <synthetic> becomes unknown (via PartA's transcript_author_model). session_meta sets the provider.
The stamp (_author_model, _author_model_source, _author_model_provider) is written onto the entry itself. Deferred entries (T3 linkage unresolved) and retried entries therefore keep the author they were read with. Nothing recomputes it later.
_normalize_lines puts message.model only on assistant entries. User entries get the provider and no model.
src/brainlayer/watcher_bridge.py (+6 lines): sets metadata.model_source = "codex_turn_context" only when the chunk has a model, and sets metadata.model_provider when it is known. The Claude path is unchanged: Claude chunks get model from PartA and no model_source.
The offset registry format is unchanged. No new keys, no migration. A legacy {offset, inode, mtime} entry with no generation resumes correctly, because the context comes from the file and not from the registry.
assistant with no / unusable / unparseable turn_context before it
absent (unknown)
absent
from session_meta if present
user
absent
absent
from session_meta if present
One difference from PartA's batch parser: batch defaults a missing session_meta.model_provider to "openai". Realtime leaves it unknown. Unknown stays unknown, so the lead should decide whether batch keeps that default.
Proof (synthetic fixtures only; Linux cloud container)
Requested base: main@d6b82f04. Actual base: PartA head a03bca748a74dd2836b4b613a1fd21d8e15d888b, fetched inside the cloud session; main is its parent. Branch: claude/draft-pr-review-c7c8n9, head 6479098.
All 11 tests in tests/test_codex_realtime_author_model.py drive the production path: JSONLWatcher.poll_once → indexer → create_flush_callback → a synthetic scratch SQLite DB, or with arbitrated=True the queue JSONL. Restart tests build a fresh JSONLWatcher on the same offsets file.
scenario
test
mixed-model turns in one read; user turn not attributed
test_mixed_model_turns_in_one_read…
restart mid-file → latest model, not the first
test_resume_mid_file_after_restart…
legacy offset entry (no generation)
test_resume_from_a_legacy_offset_entry…
replay after a failed read doesn't reuse the end-of-read context
test_replay_after_a_failed_read…
truncation rewind drops context from removed bytes
test_truncation_rewind…
replaced inode (file grew, so only the inode tells)
a03bca7 source with the new tests: 10 failed, 1 passed. The one that passes is the truncation test, because old code never attributes at all. The always-reuse mutant below fails it.
This branch: 11 passed.
Mutants (each applied alone, then reverted):
mutant
result
always reuse the stored context (no offset/inode continuity check)
killed (replay, truncation)
no prefix scan (an unknown context on resume)
killed (5 tests)
first turn_context wins
killed (5 tests)
keep the old model when a turn_context is unparseable
killed (scan test)
scan skips its inode check
killed (scan test)
drop the context's inode comparison, keep only the offset
survived. This one is equivalent today: an inode change always builds a new JSONLTailer (offset 0, empty context) in _ensure_tailer, so the comparison is defence in depth.
Neighbouring suites:test_codex_realtime_author_model, test_author_model_provenance, test_jsonl_watcher, test_watcher, test_watcher_bridge, test_t3_app_provenance, test_ingest_denylist: 277 passed. ruff check and ruff format --check are clean on the touched files. Scoped scripts/run_tests.sh (BRAINLAYER_PREPUSH_SCOPE=changed-only, the 3 changed files): gate passed, exit 0. That covers the unit suite (176 passed: the new tests, test_jsonl_watcher 112, test_watcher_bridge 53), MCP registration (3), isolated eval/hook routing (40), bun, and the FTS5 shell regression. A first attempt timed out at my 900 s cap because I passed the files space-separated instead of comma-separated; it measured nothing.
Limitations / UNRUN
Not installed, not deployed, not live. Nothing ran on a Mac or against a real DB or real sessions. The full 4.3k-test suite was not run (the scoped run above was). There is no retrieval proof: no test runs a brain_search that filters or ranks by model. The only proof is that the metadata is persisted, in the DB row and in the queue event.
Arbitrated proof covers the queue event metadata. It does not include a drain-to-DB step for that event.
The prefix scan is a one-time cost per file per process: it runs on the first read of a resumed Codex file, and again after any replay, rewind, or quarantine skip. It is a line-by-line read with a substring filter, and only matching lines are JSON-parsed. It has not been measured on large real rollouts.
An in-place rewrite of the same inode that ends up larger than the stored offset is not detectable by the watcher. The bytes and the context are both stale there; that was already true before this change.
Size: +123 source lines and +309 test lines (432 handwritten in total, about 8% over the 400-line budget). All of the overrun is test fixtures and scenarios; the source change is small.
Batch/realtime provider-default divergence: see the note under the metadata table.
Risks
Codex reads now open the file a second time when the context has to be rebuilt. That read is read-only and uses no locks. It does not touch the DB, the registry, or checkpoint logic, and offsets and confirmation are unchanged.
The new entry keys ride the existing indexer/flush/deferral path. Unknown keys are ignored downstream.
To decide: (1) whether the batch parser keeps the "openai" provider default; (2) whether model_source should also be set for Claude rows (message.model) for symmetry. I left Claude rows alone to stay in scope.
Before calling this live, still needed: macOS CI, a pre-merge check against a DB copy per AGENTS.md, and a retrieval-level check.
Collab report file is not mounted in the cloud. This section is the addressed handoff for the dispatcher.
Track Codex turn-context author model and provider across realtime watcher reads
Adds CodexTurnContext to track the active model and provider per Codex session file in watcher.py.
turn-context records update the model; session-metadata records update the provider. New lines are annotated with the context in effect at their position, and each tailer caches its context at the current offset.
On restart, legacy offsets, or cache mismatches, scan_codex_turn_context rebuilds the context from the file prefix, with inode and record-boundary checks. Malformed turn-context lines yield an unknown model.
poll_once annotates new Codex lines before normalization, and _normalize_lines carries the model, model source, and provider onto entries. Chunks can now store model_source and model_provider in metadata via watcher_bridge.py.
Covers resume, replay after failed reads, truncation rewind, file replacement, and per-file isolation in test_codex_realtime_author_model.py.
Behavioral Change: assistant entries without a usable turn-context model now store no model instead of a stale one; check _annotate_codex_author_context and fold_codex_turn_context.
…ex chunks (#655)
Claude Code transcripts record message.model on every assistant turn, but
_extract_entry_metadata dropped it, so realtime-watcher and batch-index rows
could not answer 'which model wrote this?'. Read it into chunk metadata
['model'] for assistant entries only, verbatim, and leave it unknown when it
is absent, non-string, malformed, or Claude Code's '<synthetic>' placeholder.
The Codex parser, cited as the working path, actually stored model_provider
('openai') under 'model' and kept the first value for the whole session.
It now takes each turn_context model as the author of the following turn,
records model_provider separately, and stops attributing user and tool
chunks to a model.
Authoring provenance stays distinct from enrichment_model/enrichment_backend.
Refs #655
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZDshC2wAT1TQqUTtFa
…eads (#655)
The realtime watcher dropped Codex turn_context records, so no Codex
realtime chunk ever carried an author model, and a resumed reader would
have nothing to recover it from. Keep a per-file author context on the
tailer, valid only at the exact (inode, offset) it describes; any other
read start (restart, replay, rewind, replaced inode) rebuilds it from the
file prefix. The stamp rides on the entry, so deferred and retried
entries keep their author. Assistant chunks get metadata model and
model_source=codex_turn_context; all Codex chunks get model_provider
from session_meta. Unknown stays unknown. Offset registry format is
unchanged.
Refs #655
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZFsx8C4pmVN9vJ8gUZ
… under old code (#655)
Import the prefix-scan helper inside its two unit tests so the
watcher-path tests run, and fail on attribution, against the PartA
source instead of failing at import. Apply ruff format.
Refs #655
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZFsx8C4pmVN9vJ8gUZ
We reviewed changes in a03bca7...6479098 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
The reason will be displayed to describe this comment to others. Learn more.
External variable 'filepath' used in file path
Python's open() function can take in a relative or absolute path and read its file contents. If a user is provided direct access to the path that is opened, it can have serious security risks.
Every Value below was measured by this run. A row this machine cannot measure says n/a — <reason> instead of a number; baselines in Notes name their own machine, method and date and were not measured here.
Hosted success gives no verdict on mapped bytes, search p50/p95, idle CPU, hotlane backlog CPU, or fallback replay debt.
Row
Status
Value (measured by this run)
Method
Notes
commit provenance
🟢 GREEN
measured 647909895e69 == PR head · checkout 647909895e69
commit graph + live PR head · in-process · runner
Which commit this whole table is about. On a pull_request event the checkout is GitHub's synthetic merge ref, whose sha is not on the PR — #759's table printed 13fa724278bf while that PR's head was 4632f979 — so this row names the PR-head parent instead, the sha a reviewer can actually see. The comparison sha is read live from repos/{owner}/{repo}/pulls/{n} when the table is collected, not taken from the event payload, because the payload cannot know the run has been overtaken. Residual window, stated rather than papered over: a push landing between that read and the comment being posted is not caught here — the run for that push refreshes the table.
baseline attestation
🟢 GREEN
baseline f421d1a7c5e6 matches the main attestation (run 41876229562 · main d6b82f04f6ba · 2026-11-04T17:18:27Z)
main attestation artifact via Actions API · in-process · runner
What every comparison is measured AGAINST, and who says so. The baseline fields of tests/fixtures/sprint_gate/corpus.json (queries, latency_baseline_ms, thresholds) are compared to the ratchet-attestation artifact of the latest successful push or (no-input) workflow_dispatch run of ratchet-attest.yml on main, fetched through the Actions API — a PR run cannot write to another run's artifacts. A field that differs is RED unless that main run measured the new value. The calibrated socket collector can license p50/p95; every absent measured path stays locked, so missing collection never passes as permission for a hand edit. Boundary: the comparator is this PR's checkout of ci_ratchet_table.py, diff-reviewable, not tamper-proof.
provenance
🟢 GREEN
stamped 647909895e69 == HEAD, tree clean
wheel stamp · in-process · runner
Sha half of #749 keg-mode provenance: a keg built from this wheel can answer __build_sha__. The helper-age and served-process predicates need a running BrainBar and are measured only by scripts/sprint_gate.py on an installed Mac. The sha here is the checkout's — the explicit PR head in ratchet.yml — because that is what publish.yml stamps at release time; the PR-head sha this table describes is the one in commit provenance above.
fallback replay debt
⚪ n/a
n/a — no fallback queue on this machine: the pending memories live in ~/Gits/*/docs.local/decisions, and docs.local/ is gitignored, so a runner checkout has no copy of them to count
docs.local walk · machine with the fallback queue
local-only; not enforced by hosted CI. intended_brain_store: true with no chunk_id means a memory reached disk and never reached the DB, so it answers no brain_search. Budget: 0. Any pending or unparseable file is a finding, never a band -- 122 of these sat from 2026-06-28 to 2026-09-05 because nothing counted them where a reader would look. Measured by walking the tree, so it is only ever measured on a machine that HAS the tree.
pre-push: delete-only push runs no suite
🟢 GREEN
0 suite starts · exit 0
real checkout hook · delete-only stdin · sentinel suite runner
Bug SHA: 44ecbf5ba6f1ada3b965797559eee90c94b45595 (RED replay, 2026-10-06). Fix: #1084; measured at 647909895e6949c689fbe045dbb3391cb458ac89. Ceiling: 0 suite starts; hook must exit 0 and announce the delete-only skip.
mapped bytes
⚪ n/a
n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would
socket · installed Mac
local-only; not enforced by hosted CI. Baseline 26.2 GB — installed Mac, socket, 2026-09-03, after R2 drained 15,070 → 0. Up from 16.8 GB because the drain left more vectors mapped under the same cap: the change is the drain, not a leak. Not measured by this run.
search p50/p95
⚪ n/a
n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would
socket · installed Mac
local-only; not enforced by hosted CI. Margin p50: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin p95: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Calibrated on MacBook-Pro.local at 2026-09-01T08:42:22Z under active_sprint_load (tests/fixtures/sprint_gate/corpus.json). Not measured by this run.
idle CPU
⚪ n/a
n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would
ps sampling · installed Mac
local-only; not enforced by hosted CI. Ceiling: average CPU < 30% over a 60 s window (resource_budget in scripts/sprint_gate.py), ratified and kept as a hard budget. Margin daemon: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin helper: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin watcher: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Needs the BrainBar daemon, helper and watcher actually running. Not measured by this run.
hotlane backlog CPU
⚪ n/a
n/a — live hotlane CPU evidence requires an installed Mac
CPU/wall for backlog-encoding cycles · rolling 600 s · installed Mac
local-only; not enforced by hosted CI. Default budget 60% of one core; BRAINLAYER_HOTLANE_CPU_BUDGET_PERCENT overrides. At least 30s of active backlog cycles required; yielded time excluded. Measures installed code.
signature_valid
⚪ n/a
n/a — the macOS signature-parity job is trigger-gated and did not run on this PR: it touches no release or signing path (pyproject.toml, scripts/release-*, scripts/brainlayer-version-check.sh, publish.yml, ratchet.yml) and carries no ratchet:signatures label — a GitHub macOS runner bills at ~10× Linux minutes and rebuilds the keg venv from source
codesign · installed keg
scripts/release-verify-signatures.sh <keg> codesign-verifies every *.so/*.dylib under libexec/venv. The macOS parity job installs the published tap formula (etanhey/layers/brainlayer), so this row measures the release path — formula, published sdist and Homebrew's relocation — and not this PR's tree. Release-time baseline for the same keg on a different machine: 442 valid / 0 invalid — installed Mac (M4 Max), brew --prefix brainlayer 1.5.11, 2026-09-03.
BrainBar renders no enrichment status
🟢 GREEN
19 actual-view captures; active pending-store/replay retained
macOS DEBUG binary · AppKit actual views · Vision OCR + icon RGBA comparison · private synthetic state
Missing render capability/evidence is RED. Historical data is retained; this is source-build proof, not installed-app proof.
Watcher heartbeat freshness and recovery
🟢 GREEN
13 cases; polls 1 -> 2
private Python producer -> compiled Swift consumer
Preserves genuine silence; independent agent stores cannot revive the watcher. Not a load or installed-UI measurement.
maintenance quiesce: services stay down under a reviving watchdog
⚪ n/a
n/a — macOS replay trigger-gated: no affected path or ratchet:quiesce label
real launchd · GitHub macOS runner · real maintenance + fleet-watchdog
Fix: #1076 (bc6d690f4331387485678d0247e19e14e6ef8d97). Ceiling: 0 revivals; supervisor held, both resumed, no hold marker.
Synthetic private fixtures; SDK absence gated on merged R10b. Exact-head candidate proof, not deployment.
🟢 GREEN measured, within budget · 🔴 RED measured, out of budget — a finding to clear before merge · ⚪ n/a not measurable on this machine, never guessed.
No RED rows.
Measured on Linux/x86_64 · measured 647909895e69 · PR head 647909895e69 · checkout 647909895e69 · run · updated 2026-11-05 06:02:08 UTC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft. Stacked on PartA #1639 (
claude/orc-final-credit-batch-z2uwd0@a03bca7), notmain. Do not merge, mark ready, or queue it. The owning lead reviews it.Task marker:
cloud-credit-20261105-brainlayer-hammer-codex-author-state. Refs #655. This is not aFixes: PartA has to land first, and the gaps listed under Limitations are still open.What was broken
PartA (
a03bca7) records the per-turn author model in the batch Codex parser and in Claude'smessage.model. The realtime watcher got none of it for Codex:normalize_provider_entryreturnsNoneforturn_contextandsession_meta, so the model and provider were thrown away before classification. Ona03bca7, no realtime Codex chunk carriesmodelormodel_provider. The new tests confirm this (old run below).What changed (watcher normalization / per-file context only)
src/brainlayer/watcher.pyCodexTurnContext(inode, offset, model, model_provider)is stored on eachJSONLTailer, so every file has its own. It is trusted only at the exact(inode, offset)it describes._annotate_codex_author_contextruns inpoll_oncebefore normalization. If the read starts anywhere other than the stored(inode, offset)(restart, replay after a failed read, rewind to 0, new inode, skipped quarantine bytes), the context is rebuilt byscan_codex_turn_contextfrom the file's own bytes before the read start. The scan only parses lines that mentionturn_contextorsession_meta. It returns unknown if the inode it opened is not the one being read. An unparseableturn_contextresets the model to unknown instead of keeping the older one.fold_codex_turn_contextfollows PartA's batch semantics. Everyturn_contextreplaces the model, and an absent model or<synthetic>becomes unknown (via PartA'stranscript_author_model).session_metasets the provider._author_model,_author_model_source,_author_model_provider) is written onto the entry itself. Deferred entries (T3 linkage unresolved) and retried entries therefore keep the author they were read with. Nothing recomputes it later._normalize_linesputsmessage.modelonly on assistant entries. User entries get the provider and no model.src/brainlayer/watcher_bridge.py(+6 lines): setsmetadata.model_source = "codex_turn_context"only when the chunk has a model, and setsmetadata.model_providerwhen it is known. The Claude path is unchanged: Claude chunks getmodelfrom PartA and nomodel_source.{offset, inode, mtime}entry with nogenerationresumes correctly, because the context comes from the file and not from the registry.classify.py/ingest/codex.py, and the PartB Swiftcaller_modelwork (feat(brainbar): optional self-reported caller_model on served brain_store (Refs #655, Part B) #1643).Metadata contract (realtime Codex)
modelmodel_sourcemodel_providerturn_contextcodex_turn_contextsession_metaif presentturn_contextbefore itsession_metaif presentsession_metaif presentOne difference from PartA's batch parser: batch defaults a missing
session_meta.model_providerto"openai". Realtime leaves it unknown. Unknown stays unknown, so the lead should decide whether batch keeps that default.Proof (synthetic fixtures only; Linux cloud container)
Requested base:
main@d6b82f04. Actual base: PartA heada03bca748a74dd2836b4b613a1fd21d8e15d888b, fetched inside the cloud session;mainis its parent. Branch:claude/draft-pr-review-c7c8n9, head6479098.All 11 tests in
tests/test_codex_realtime_author_model.pydrive the production path:JSONLWatcher.poll_once→ indexer →create_flush_callback→ a synthetic scratch SQLite DB, or witharbitrated=Truethe queue JSONL. Restart tests build a freshJSONLWatcheron the same offsets file.test_mixed_model_turns_in_one_read…test_resume_mid_file_after_restart…generation)test_resume_from_a_legacy_offset_entry…test_replay_after_a_failed_read…test_truncation_rewind…test_replaced_inode…test_context_is_per_file<synthetic>turn_context → unknown, not previoustest_turn_context_without_a_usable_model…test_prefix_scan_*(2)test_arbitrated_queue_carries_the_same_attributionOld vs new:
a03bca7source with the new tests: 10 failed, 1 passed. The one that passes is the truncation test, because old code never attributes at all. The always-reuse mutant below fails it.Mutants (each applied alone, then reverted):
turn_contextwinsJSONLTailer(offset 0, empty context) in_ensure_tailer, so the comparison is defence in depth.Neighbouring suites:
test_codex_realtime_author_model,test_author_model_provenance,test_jsonl_watcher,test_watcher,test_watcher_bridge,test_t3_app_provenance,test_ingest_denylist: 277 passed.ruff checkandruff format --checkare clean on the touched files. Scopedscripts/run_tests.sh(BRAINLAYER_PREPUSH_SCOPE=changed-only, the 3 changed files): gate passed, exit 0. That covers the unit suite (176 passed: the new tests,test_jsonl_watcher112,test_watcher_bridge53), MCP registration (3), isolated eval/hook routing (40), bun, and the FTS5 shell regression. A first attempt timed out at my 900 s cap because I passed the files space-separated instead of comma-separated; it measured nothing.Limitations / UNRUN
brain_searchthat filters or ranks bymodel. The only proof is that the metadata is persisted, in the DB row and in the queue event.Risks
Handoff →
@orc(BrainLayer lead snoozed)main."openai"provider default; (2) whethermodel_sourceshould also be set for Claude rows (message.model) for symmetry. I left Claude rows alone to stay in scope.Generated by Claude Code
Note
Track Codex turn-context author model and provider across realtime watcher reads
Adds
CodexTurnContextto track the active model and provider per Codex session file in watcher.py.turn-contextrecords update the model;session-metadatarecords update the provider. New lines are annotated with the context in effect at their position, and each tailer caches its context at the current offset.scan_codex_turn_contextrebuilds the context from the file prefix, with inode and record-boundary checks. Malformedturn-contextlines yield an unknown model.poll_onceannotates new Codex lines before normalization, and_normalize_linescarries the model, model source, and provider onto entries. Chunks can now storemodel_sourceandmodel_providerin metadata via watcher_bridge.py._annotate_codex_author_contextandfold_codex_turn_context.Macroscope summarized 6479098.