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
feat(brainbar): optional self-reported caller_model on served brain_store (Refs #655, Part B) - #1643
Draft, partial. Refs #655, not Fixes. This PR covers #655Part B only: an optional, self-reported caller_model on the brain_store tool that BrainBar actually serves. Part A (transcript ingestion) is in draft #1639. Neither PR is merged, and this one changes none of #1639's files (ingest/codex.py, pipeline/classify.py, tests/test_author_model_provenance.py).
Actual base, fetched inside the cloud container before editing: d6b82f04f6baa00081c9dc38afca2ada9d114c78 (same commit)
Head: claude/opus-5-5-spend-down-oz80dz @ e535019a5d6472ce767f88f78e6820e577210dfe. It has 5 commits: tests (fail-first), the feature, a compile fix, and two boot-budget fixes.
Trace on the served surface (Swift BrainBar, not the retired Python MCP)
Schema.MCPRouter.toolDefinitions gets a brain_store.inputSchema.properties.caller_model of type string. stringMaxLengths["caller_model"] = BrainDatabase.callerModelMaxLength (128), so limitedInputSchema advertises maxLength: 128 and validate(arguments:) enforces it. It stays optional (required is still ["content"]). The compact core boot palette does not advertise it (fullPaletteOnlyProperties in compactCoreToolDefinition). Core sessions still have it validated against the full schema and stored; see the budget note below.
handleBrainStore. Reads args["caller_model"] as? String and passes it down all four exits: storeOrQueueWithinBudget (direct), and queueBrainStore for BACKUP_SNAPSHOT, DB_NOT_OPEN (no handle) and DB_NOT_OPEN (closed handle).
Direct write.storeOrQueueWithinBudget passes metadata: callerModelMetadata(callerModel) into the existing store(metadata:). On the BUSY fallback it passes callerModel into queuePendingStore.
Durable queue.PendingStoreItem gains an optional caller_model JSON key. scrubbed() and pendingStoreReplayLine (the rewrite of a line a flush skipped) carry it. Old records without the key decode unchanged. The decode is lenient on purpose: a non-string caller_model on a queued line becomes nil, so a bad model never strands the memory.
Replay.flushPendingStores stores with item.replayMetadata, which is the existing scrub metadata plus the same model metadata a direct store writes. brainbar_queue_id stays server-owned.
What is persisted.chunks.metadata.model = <trimmed model> and chunks.metadata.model_source = "caller_self_reported", and only when a valid value was supplied. Nothing is inferred from agent_id, tags, the session or the conversation. Nothing writes enrichment_model or enrichment_backend. The keys match fix(ingest): persist per-turn authoring model for Claude Code and Codex chunks (Refs #655) #1639's metadata.model, and model_source distinguishes self-reported values from transcript-derived ones.
Retrieval: traced, not changed (see Unresolved acceptance). BrainBar's search, recall and expand formatters do not read chunks.metadata. The value can be queried with json_extract(metadata,'$.model') / '$.model_source' but is not shown to agents yet.
Validation (BrainDatabase.normalizedCallerModel)
Input
Result
absent, or JSON null
nothing recorded (no model and no model_source key)
non-string (number, array, object)
REJECTED by the existing schema validator; nothing stored, nothing queued, and the reason names caller_model
over 128 characters
REJECTED by the existing schema validator (advertised maxLength)
control, format (zero-width, bidi override), line/paragraph separator, private-use or unassigned scalars
dropped; still STORED
more than 128 Unicode scalars that pass the 128-grapheme schema check (combining marks)
dropped; still STORED
anything SecretScrubber.scrub would change or quarantine
dropped; still STORED. A secret is never written as a model.
surrounding whitespace
trimmed
Idempotence: a same-session retry of identical content returns DUPLICATE with the same chunk_id and one row, and the first model is kept. A retry that claims a different model does not rewrite it.
Commands and observed output
There is no Swift toolchain in this Linux container, so all native proof comes from the hosted CI swift (macos-15) job, which runs swift build -c release and then python3 Tests/BrainBarTests/Scripts/run_swift_tests.py.
Fail-first (old production path). The head was the test-only commit 4e268cc, so the new tests ran against unchanged main source. The tests drive only the wire surface (brain_store args, pending-stores.jsonl, chunks.metadata), so they compile on main, where caller_model is silently ignored.
Run 41964129893, job 127037340646: Executed 1549 tests, with 23 tests skipped and 40 failures, all 40 inside StoreCallerModelTests (Executed 11 tests, with 40 failures). Every other suite was green.
Visible failures included: testDirectStoreRecordsSelfReportedModel (("nil") is not equal to ("Optional("fixture-model-a")")), testBusyQueuedStoreCarriesModelThroughDurableQueueAndReplay, testDirectAndReplayedRowsRecordIdenticalProvenance, testLineSkippedByAFlushKeepsItsModelWhenRewritten, testQueuedRecordsReplayWithValidatedModelOnly, testRetryInTheSameSessionIsIdempotentAndKeepsTheFirstModel, and testMalformedOrOversizedCallerModelIsRejectedAndStoresNothing (main answered ✔ STORED for caller_model: 42 and stored 4 rows where 0 were expected).
Controls that passed on main, as expected: testUntrustworthyCallerModelIsDroppedButMemoryIsStored (main stores without a model) and the absent-model control.
After the fix:
cd4bcab failed to compile: a stray } closed BrainDatabase early (cannot find type 'PendingStoreItem' in scope). Fixed in b635097.
b635097, run 41967358548, job 127047874424: all 11 StoreCallerModelTests passed. Two existing guards failed: testCoreToolsListStaysWithinBootBudget and testCoreDescriptionsAreUntruncatedOverSocket, both ("1647") is greater than ("1600"). The core palette has an owner-ratified 1600-byte boot budget. I did not raise the cap.
fbc7c20 shortened the description. Run 41970292796, job 127057264784 still measured 1647: the core palette strips property descriptions, so the property alone, "caller_model":{"maxLength":128,"type":"string"},, costs 49 bytes, and main already sits at about 1598 bytes. That run: Executed 1549 tests, with 23 tests skipped and 2 failures, exactly the two budget guards, with StoreCallerModelTests passing. The third assertion failure in the b635097 run did not recur and was not visible in the truncated log; I record it here as unexplained.
e535019 hides caller_model from the compact core schema only. It adds testCorePaletteHidesCallerModelButStillAcceptsIt: core does not list the property, a core-session store with caller_model still records it, and a 129-character value is still rejected.
Exact head e535019a5d6472ce767f88f78e6820e577210dfe, run 41974216215, job 127069830921: swift (macos-15) SUCCESS.Executed 1550 tests, with 23 tests skipped and 0 failures. All 12 StoreCallerModelTests passed, including testCorePaletteHidesCallerModelButStillAcceptsIt. Core boot payload W24 tools/list bytes=1598, so both 1600 guards pass with the cap untouched. BrainBar suite-owned temp gate: before=0 new=0. On the same head, test (3.11), lint, installed wheel imports, BrainBar renders no enrichment status, global model retirement and ratchet table were green. test (3.12) and test (3.13) were still running at 06:29Z.
Python (Linux, Python 3.13, editable venv in a scratch directory): I ran the suites that read Swift sources or docs/mcp-tools.md: test_atomic_brick_schema test_brainbar_label_parity test_chunk_write_contract test_cli_direct_sqlite test_lexical_defense test_retirement_inventory test_retirement_reference_docs test_retirement_report test_retirement_source test_tool_count_drift test_job_alerts, giving 876 passed, 1 xfailed. The diff touches no Python source.
Mutant controls: UNRUN as separate native runs. Each one would need its own roughly 15-minute macOS CI round and a revert commit on this branch. Coverage by reasoning, with the test expected to catch each mutant:
not passing callerModel into queuePendingStore from the BUSY path: testBusyQueued…
dropping it in pendingStoreReplayLine: testLineSkippedByAFlush…
strict decode instead of lenient: testQueuedRecordsReplay… (the numeric line would strand)
scrubMetadata instead of replayMetadata: testBusyQueued…, testBackupDeferred…, …Parity
removing the maxLength entry: the schema test and testMalformedOrOversized…
Fixtures are synthetic: a scratch DB under the suite-owned TMPDIR, made-up model strings (fixture-model-a/b), and synthetic sk-000… tokens. No real DB, no network, no model loads.
Retrieval surfacing. No BrainBar formatter shows metadata.model yet. Showing it in search, recall or expand is a formatter change with its own budget questions, and is outside this caller_model write path.
Discoverability vs. budget (owner decision). Core sessions, which is the default boot palette, do not see caller_model until expand_palette or BRAINLAYER_MCP_PROFILE=full. Advertising it in core means ratifying roughly 1600 → 1650 bytes, which the test comment says must be a deliberate decision. To advertise it, remove brain_store from fullPaletteOnlyProperties and raise both 1600 guards (MCPRouterTests.swift:243 and SocketIntegrationTests.swift:630).
Python mcp/store_handler.py (the retired, unserved path named in the issue) is unchanged on purpose.
Native, installed or live proof is UNRUN. There was no Mac, no installed BrainBar and no production DB. The proof is source plus hosted CI (macOS 15 runner). It is not an installed app.
Risks
Rejecting a non-string or over-128 caller_model rejects the whole store. That matches how every other bounded brain_store field already behaves, and the reason names the field, but a client that sends a junk model loses that write (loudly, never silently).
SecretScrubber quarantine of high-entropy strings could drop an unusually random-looking model ID. It is dropped, never stored raw, and the memory is still stored.
The pending-store JSON gains one optional key. An older BrainBar replaying a newer queue file ignores it, so the row is stored without a model.
The core boot payload is unchanged by this PR (about 1598 of 1600). fullPaletteOnlyProperties is a new mechanism: a property can be accepted while not advertised in core. It is used only for caller_model.
Owner handoff, @orc (routes while the BrainLayer lead is snoozed)
swift (macos-15) is green on the exact head e535019. Re-check test (3.12) and test (3.13), which were still running at handoff.
Decide items 2 and 3 above (retrieval surfacing; whether to spend about 49 boot bytes advertising caller_model in core).
Before merge, run the pre-merge live check against a copy of the real DB (AGENTS.md). It is UNRUN here. Suggested: point a dev BrainBar at the copy, call brain_store with and without caller_model, force one BUSY/queued write, flush, then run SELECT json_extract(metadata,'$.model'), json_extract(metadata,'$.model_source'), count(*) FROM chunks WHERE source='mcp' GROUP BY 1,2.
Report target: orchestrator/docs.local/plans/2026-11-04-claude-cloud-batch/collab.md. It is not mounted in the cloud, so this body is the handoff for the dispatcher to import.
Implementer and harness: Claude Code cloud worker (Anthropic Claude, effort high, fast mode off). No subagents, no extra sessions, no paid API calls. A cost meter was not available in-session. Work ran from 03:45Z to about 06:31Z, inside the 07:00Z bound.
Add optional self-reported caller_model to brain_store tool
brain_store accepts an optional caller_model string (max 128 Unicode scalars) and records it as provenance metadata (model and model_source: caller_self_reported) on stored chunks. The value is never derived from agent_id or session state.
BrainDatabase.normalizedCallerModel trims and validates the value. Untrustworthy values (blank, <synthetic> placeholder, control/format/bidi characters, secret-shaped, over limit) are dropped but the memory is still stored; non-string or over-length arguments reject the whole request.
The model survives the busy/backup-deferred paths: it is serialized into the pending-store JSONL queue and replayed by flushPendingStores with the same provenance as direct writes. Queue lines rewritten during a flush keep the value.
Core-profile tools/list omits caller_model from the advertised schema, but core calls still accept and validate it. Docs updated in mcp-tools.md.
Behavioral Change: MCPRouter core-tool schema compactor now strips configured full-palette-only properties from advertised schemas; decoding of legacy pending-store records tolerates missing or malformed caller_model without dropping the queued memory.
We reviewed changes in f435cd1...6dfe9d4 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.
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.
Implicitly unwrapped optionals should be avoided when possible
Implicitly unwrapped optionals (IUOs) are a type of optional in Swift that are automatically unwrapped when used. While they can be convenient in some cases, they can also lead to unexpected crashes if the value is nil. This is because
The reason will be displayed to describe this comment to others. Learn more.
Implicitly unwrapped optionals should be avoided when possible
Implicitly unwrapped optionals (IUOs) are a type of optional in Swift that are automatically unwrapped when used. While they can be convenient in some cases, they can also lead to unexpected crashes if the value is nil. This is because
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 6dfe9d4b62ee == PR head · checkout 6dfe9d4b62ee
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 42075573584 · main 8c4e7d7ba255 · 2026-11-05T17:32:35Z)
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 6dfe9d4b62ee == 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 6dfe9d4b62ee650e783ee3969922637fea616493. 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
⚪ n/a
n/a — macOS native probe trigger-gated: no affected path or ratchet:heartbeat label
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 6dfe9d4b62ee · PR head 6dfe9d4b62ee · checkout 6dfe9d4b62ee · run · updated 2026-11-05 18:19:18 UTC
The reason will be displayed to describe this comment to others. Learn more.
nil coalescing operator is only evaluated if the lhs is nil, coalescing operator with nil as rhs is redundant
Using the nil coalescing operator (??) in Swift can be helpful to provide default values when dealing with optional types. However, it can be easy to fall into the trap of using it unnecessarily, leading to redundant code that
… and replayed paths (Refs #655)
Fail-first: drives only the served wire surface (brain_store args, the
durable pending-stores.jsonl, chunks.metadata), so it compiles and runs
against main, where caller_model is ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZHFsmZCdvgmMpoxesP
…efs #655)
Thread an optional caller_model through the served BrainBar brain_store:
inputSchema (+128 maxLength), direct store, busy/backup/db-not-open
queue, durable pending-store JSON and replay. Persisted as
metadata.model + metadata.model_source='caller_self_reported' only when
supplied and valid; never inferred, never the enrichment columns. Old
queue lines without the field replay unchanged; a malformed queued model
drops the model, never the memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZHFsmZCdvgmMpoxesP
Compile fix for the caller_model commit (CI swift job: 'cannot find type
PendingStoreItem in scope'); also simplify the lenient decode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZHFsmZCdvgmMpoxesP
… core boot budget
testCoreToolsListStaysWithinBootBudget and the socket twin measured 1647
bytes with the longer description; 'Your model ID' lands at ~1592. The
1600 cap itself is unchanged (owner-ratified).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZHFsmZCdvgmMpoxesP
The core boot tools/list strips property descriptions, so caller_model
costs 49 bytes there regardless of wording, and main already sits at
~1598 of the ratified 1600. Keep the cap: hide the property from the
compact core schema only. Core sessions still have it validated against
the full schema and stored. Advertising it in core is an owner decision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZHFsmZCdvgmMpoxesP
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.
TASK MARKER:
cloud-credit-20261105-brainlayer-spend-655-part-bDraft, partial. Refs #655, not Fixes. This PR covers #655 Part B only: an optional, self-reported
caller_modelon the brain_store tool that BrainBar actually serves. Part A (transcript ingestion) is in draft #1639. Neither PR is merged, and this one changes none of #1639's files (ingest/codex.py,pipeline/classify.py,tests/test_author_model_provenance.py).Change and evidence
Base and head
origin/maind6b82f04f6baa00081c9dc38afca2ada9d114c78d6b82f04f6baa00081c9dc38afca2ada9d114c78(same commit)claude/opus-5-5-spend-down-oz80dz@e535019a5d6472ce767f88f78e6820e577210dfe. It has 5 commits: tests (fail-first), the feature, a compile fix, and two boot-budget fixes.Trace on the served surface (Swift BrainBar, not the retired Python MCP)
MCPRouter.toolDefinitionsgets abrain_store.inputSchema.properties.caller_modelof type string.stringMaxLengths["caller_model"] = BrainDatabase.callerModelMaxLength(128), solimitedInputSchemaadvertisesmaxLength: 128andvalidate(arguments:)enforces it. It stays optional (requiredis still["content"]). The compact core boot palette does not advertise it (fullPaletteOnlyPropertiesincompactCoreToolDefinition). Core sessions still have it validated against the full schema and stored; see the budget note below.handleBrainStore. Readsargs["caller_model"] as? Stringand passes it down all four exits:storeOrQueueWithinBudget(direct), andqueueBrainStoreforBACKUP_SNAPSHOT,DB_NOT_OPEN(no handle) andDB_NOT_OPEN(closed handle).storeOrQueueWithinBudgetpassesmetadata: callerModelMetadata(callerModel)into the existingstore(metadata:). On the BUSY fallback it passescallerModelintoqueuePendingStore.PendingStoreItemgains an optionalcaller_modelJSON key.scrubbed()andpendingStoreReplayLine(the rewrite of a line a flush skipped) carry it. Old records without the key decode unchanged. The decode is lenient on purpose: a non-stringcaller_modelon a queued line becomes nil, so a bad model never strands the memory.flushPendingStoresstores withitem.replayMetadata, which is the existing scrub metadata plus the same model metadata a direct store writes.brainbar_queue_idstays server-owned.chunks.metadata.model = <trimmed model>andchunks.metadata.model_source = "caller_self_reported", and only when a valid value was supplied. Nothing is inferred fromagent_id, tags, the session or the conversation. Nothing writesenrichment_modelorenrichment_backend. The keys match fix(ingest): persist per-turn authoring model for Claude Code and Codex chunks (Refs #655) #1639'smetadata.model, andmodel_sourcedistinguishes self-reported values from transcript-derived ones.chunks.metadata. The value can be queried withjson_extract(metadata,'$.model')/'$.model_source'but is not shown to agents yet.Validation (
BrainDatabase.normalizedCallerModel)modeland nomodel_sourcekey)caller_modelmaxLength)<synthetic>placeholder (same rule as #1639)SecretScrubber.scrubwould change or quarantineIdempotence: a same-session retry of identical content returns
DUPLICATEwith the samechunk_idand one row, and the first model is kept. A retry that claims a different model does not rewrite it.Commands and observed output
There is no Swift toolchain in this Linux container, so all native proof comes from the hosted CI
swift (macos-15)job, which runsswift build -c releaseand thenpython3 Tests/BrainBarTests/Scripts/run_swift_tests.py.Fail-first (old production path). The head was the test-only commit
4e268cc, so the new tests ran against unchangedmainsource. The tests drive only the wire surface (brain_store args,pending-stores.jsonl,chunks.metadata), so they compile onmain, wherecaller_modelis silently ignored.Executed 1549 tests, with 23 tests skipped and 40 failures, all 40 insideStoreCallerModelTests(Executed 11 tests, with 40 failures). Every other suite was green.testDirectStoreRecordsSelfReportedModel(("nil") is not equal to ("Optional("fixture-model-a")")),testBusyQueuedStoreCarriesModelThroughDurableQueueAndReplay,testDirectAndReplayedRowsRecordIdenticalProvenance,testLineSkippedByAFlushKeepsItsModelWhenRewritten,testQueuedRecordsReplayWithValidatedModelOnly,testRetryInTheSameSessionIsIdempotentAndKeepsTheFirstModel, andtestMalformedOrOversizedCallerModelIsRejectedAndStoresNothing(main answered✔ STOREDforcaller_model: 42and stored 4 rows where 0 were expected).testUntrustworthyCallerModelIsDroppedButMemoryIsStored(main stores without a model) and the absent-model control.After the fix:
cd4bcabfailed to compile: a stray}closedBrainDatabaseearly (cannot find type 'PendingStoreItem' in scope). Fixed inb635097.b635097, run 41967358548, job 127047874424: all 11StoreCallerModelTestspassed. Two existing guards failed:testCoreToolsListStaysWithinBootBudgetandtestCoreDescriptionsAreUntruncatedOverSocket, both("1647") is greater than ("1600"). The core palette has an owner-ratified 1600-byte boot budget. I did not raise the cap.fbc7c20shortened the description. Run 41970292796, job 127057264784 still measured 1647: the core palette strips property descriptions, so the property alone,"caller_model":{"maxLength":128,"type":"string"},, costs 49 bytes, andmainalready sits at about 1598 bytes. That run:Executed 1549 tests, with 23 tests skipped and 2 failures, exactly the two budget guards, withStoreCallerModelTestspassing. The third assertion failure in theb635097run did not recur and was not visible in the truncated log; I record it here as unexplained.e535019hidescaller_modelfrom the compact core schema only. It addstestCorePaletteHidesCallerModelButStillAcceptsIt: core does not list the property, a core-session store withcaller_modelstill records it, and a 129-character value is still rejected.e535019a5d6472ce767f88f78e6820e577210dfe, run 41974216215, job 127069830921:swift (macos-15)SUCCESS.Executed 1550 tests, with 23 tests skipped and 0 failures. All 12StoreCallerModelTestspassed, includingtestCorePaletteHidesCallerModelButStillAcceptsIt. Core boot payloadW24 tools/list bytes=1598, so both 1600 guards pass with the cap untouched.BrainBar suite-owned temp gate: before=0 new=0. On the same head,test (3.11),lint,installed wheel imports,BrainBar renders no enrichment status,global model retirementandratchet tablewere green.test (3.12)andtest (3.13)were still running at 06:29Z.Python (Linux, Python 3.13, editable venv in a scratch directory): I ran the suites that read Swift sources or
docs/mcp-tools.md:test_atomic_brick_schema test_brainbar_label_parity test_chunk_write_contract test_cli_direct_sqlite test_lexical_defense test_retirement_inventory test_retirement_reference_docs test_retirement_report test_retirement_source test_tool_count_drift test_job_alerts, giving 876 passed, 1 xfailed. The diff touches no Python source.Mutant controls: UNRUN as separate native runs. Each one would need its own roughly 15-minute macOS CI round and a revert commit on this branch. Coverage by reasoning, with the test expected to catch each mutant:
callerModelintoqueuePendingStorefrom the BUSY path:testBusyQueued…pendingStoreReplayLine:testLineSkippedByAFlush…testQueuedRecordsReplay…(the numeric line would strand)scrubMetadatainstead ofreplayMetadata:testBusyQueued…,testBackupDeferred…,…ParitymaxLengthentry: the schema test andtestMalformedOrOversized…Fixtures are synthetic: a scratch DB under the suite-owned TMPDIR, made-up model strings (
fixture-model-a/b), and syntheticsk-000…tokens. No real DB, no network, no model loads.Unresolved acceptance (why this is Refs #655)
metadata.modelyet. Showing it in search, recall or expand is a formatter change with its own budget questions, and is outside this caller_model write path.caller_modeluntilexpand_paletteorBRAINLAYER_MCP_PROFILE=full. Advertising it in core means ratifying roughly 1600 → 1650 bytes, which the test comment says must be a deliberate decision. To advertise it, removebrain_storefromfullPaletteOnlyPropertiesand raise both 1600 guards (MCPRouterTests.swift:243andSocketIntegrationTests.swift:630).mcp/store_handler.py(the retired, unserved path named in the issue) is unchanged on purpose.Risks
caller_modelrejects the whole store. That matches how every other bounded brain_store field already behaves, and the reason names the field, but a client that sends a junk model loses that write (loudly, never silently).SecretScrubberquarantine of high-entropy strings could drop an unusually random-looking model ID. It is dropped, never stored raw, and the memory is still stored.fullPaletteOnlyPropertiesis a new mechanism: a property can be accepted while not advertised in core. It is used only forcaller_model.Owner handoff,
@orc(routes while the BrainLayer lead is snoozed)swift (macos-15)is green on the exact heade535019. Re-checktest (3.12)andtest (3.13), which were still running at handoff.caller_modelin core).brain_storewith and withoutcaller_model, force one BUSY/queued write, flush, then runSELECT json_extract(metadata,'$.model'), json_extract(metadata,'$.model_source'), count(*) FROM chunks WHERE source='mcp' GROUP BY 1,2.orchestrator/docs.local/plans/2026-11-04-claude-cloud-batch/collab.md. It is not mounted in the cloud, so this body is the handoff for the dispatcher to import.Implementer and harness: Claude Code cloud worker (Anthropic Claude, effort high, fast mode off). No subagents, no extra sessions, no paid API calls. A cost meter was not available in-session. Work ran from 03:45Z to about 06:31Z, inside the 07:00Z bound.
Loop-record: implementer=anthropic/claude-code-cloud/high; reviewer=none; rounds=0; defects=3 (self-found: compile brace, boot budget x2)
🤖 Generated with Claude Code
https://claude.ai/code/session_01L3NoZHFsmZCdvgmMpoxesP
Note
Add optional self-reported
caller_modeltobrain_storetoolbrain_storeaccepts an optionalcaller_modelstring (max 128 Unicode scalars) and records it as provenance metadata (modelandmodel_source: caller_self_reported) on stored chunks. The value is never derived fromagent_idor session state.BrainDatabase.normalizedCallerModeltrims and validates the value. Untrustworthy values (blank,<synthetic>placeholder, control/format/bidi characters, secret-shaped, over limit) are dropped but the memory is still stored; non-string or over-length arguments reject the whole request.flushPendingStoreswith the same provenance as direct writes. Queue lines rewritten during a flush keep the value.tools/listomitscaller_modelfrom the advertised schema, but core calls still accept and validate it. Docs updated in mcp-tools.md.MCPRoutercore-tool schema compactor now strips configured full-palette-only properties from advertised schemas; decoding of legacy pending-store records tolerates missing or malformedcaller_modelwithout dropping the queued memory.Macroscope summarized 6dfe9d4.