Read-only audit · production untouched · 2026-08-16

Fourteen things worth fixing before anything net new.

A full read-only sweep across every layer of the OiMy engine — routing, memory, model calls, the governance gate, delivery, the founder-only canary code, config, and concurrency — looking for defects with the same character as the request_ctx bug already found. Fourteen surfaced. This page ranks them, shows the evidence, and proposes a sequencing so fixes don't collide with each other.

Engine rev 976b318 Method live read-only inspection + isolated reproductions against temp DBs, no writes to prod Auditor GPT-5.6-Sol via Codex, independent pass

Two of these are safety-relevant and should go first. The already-known request_ctx crash (#1) and a separate, previously-unknown gap where a third-person self-harm statement about a family member doesn't reliably reach a synchronous crisis response (#2) — first-person crisis language is handled better than third-person concern. Everything else is correctness or reliability, ranked by blast radius, not urgency.

Nothing on this page has been applied. Production is untouched.

·Ranked summary

#FindingSeverityConfidence
1Undefined request_ctx disables the crisis/escalation fallback, silently swallowedsafetyexecution
2Third-person self-harm can reach the ordinary companion/bridge before any synchronous crisis respondersafetyexecution + reading
3Expired outbox messages remain claimable and can still be marked deliveredsafety (meds)reading
4Regenerated responses can be accepted with the same governance violation; bridge responses skip the gatesafety / correctnessreading
5OpenRouter fallback can recurse and drops system_additions on every retryreliabilityreading
6Session ID slides mid-conversation once history exceeds 24 stored rows (~12 exchanges)correctnessexecution
7Phase-4's advertised worker deadline doesn't actually cancel or bound the workreliabilityreading
8SQLite connection discipline is inconsistent — InferenceEngine shares one connection across all threadsreliabilityreading
9H30's legacy session_constraints never expire; first message double-countedcorrectnessreading
10Phase-3 context requests can get stuck "processing" forever; writes precede validation; anchor reservations can racereliabilityreading
11Hybrid bridge has check-then-set races on shared state; write-backs are unbounded daemon threadsreliabilityreading
12Four live routing regexes contain a literal backspace byte instead of \b — never match real inputcorrectnessbyte-level reading
13Entity/vector memory data-quality bugs: null ages stored as text, duplicate facts, embedding-cache collisionscorrectnessreading
14Config drift: COACH_MODEL silently unused, an embedded DeepSeek key fallback in source, fragile systemd layeringreliability / securitylive runtime + reading

·Recommended sequencing

Grouped by how safe each fix is to make in isolation — not by finding number. The goal is stabilizing without a fix in one batch quietly breaking something the next batch depends on.

1 · Now, isolated
Safety-critical, small, no interaction with anything else. Fix and deploy independently, don't batch with anything below. #1 request_ctx · #12 backspace-byte regexes
2 · Safety design
Safety-relevant but needs an actual decision, not just a patch — where the crisis responder plugs into routing, how the governance gate treats a failed regeneration. Design first, then implement together since they touch adjacent logic (both are about "what happens when the safe path isn't taken"). #2 third-person crisis routing · #4 governance regen gate
3 · Delivery integrity
Both touch the outbox/turn-ledger. Fix together, one migration, one test pass — doing them separately risks two people editing the same file with different assumptions about row state. #3 outbox expiry · #9 H30 session-constraint expiry (same "state never expires" root cause)
4 · Concurrency, needs testing
Each fix is well-scoped on paper, but all four touch shared state under concurrent load — the actual risk is two of these interacting in ways a single-threaded read-through won't catch. Fix behind a flag, load-test against a copied production DB before flipping it on for everyone, not just for the canary account. #5 OpenRouter fallback recursion · #6 session-ID slide · #8 InferenceEngine shared connection · #11 bridge state races
5 · Canary-only, lower urgency
Only affects the founder's own account today (Phase 3/4 are single-account canaries) — real, worth fixing before wider rollout, but not before the batches above. #7 Phase-4 deadline enforcement · #10 Phase-3 context-protocol races
6 · Hygiene, anytime
Data-quality and config cleanup. Low blast radius, no urgency, but cheap to fix and worth doing in the same pass as #14's credential rotation. #13 entity/vector data quality · #14 config drift + embedded key rotation

Batch 4 is the one place a "well thought out, not myopic" fix actually matters most — #5, #6, #8, and #11 all touch request-scoped state under concurrency, and a fix to one written without checking the others risks papering over a symptom instead of the shared cause.

·Findings, in detail

01Undefined request_ctx disables the crisis/escalation fallback

_execute() takes five arguments and has no local request_ctx. api.py:6042 When a crisis/escalation-veto changes an action workflow to chat, the fallback call passes request_ctx=request_ctx anyway. api.py:6112, 6120 The NameError is caught by a broad exception handler and replaced with static text. Introduced by commit 080094a, 2026-07-11.

Reproduced directly:

[safety-gate] family-tasks (action) vetoed — crisis/escalation signal → companion
chat-fallback companion call failed: name 'request_ctx' is not defined
REQUEST_CTX_FALLBACK chat I'm here for you! What would you like to talk about?

Blast radius

Every action misroute correctly vetoed by _detect_escalation_signal. Parent first-person self-harm gets a crisis-specific static fallback anyway (_fb_crisis); third-person escalation concern gets the generic reply with no safety content at all.

Fix

-def _execute(self, skill_id, workflow_type, user_input, user_id, profile):
+def _execute(self, skill_id, workflow_type, user_input, user_id, profile,
+             request_ctx=None):

Thread request_ctx through both call sites (api.py:3093 and api.py:3153). Defaults to None, so existing five-argument callers/tests stay compatible.

safetyconfirmed by executionNot verified: exact model output post-fix; whether deployed tests exercise this branch.
02Third-person self-harm doesn't reliably reach a synchronous crisis responder

The router correctly classifies phrases like "kill himself" as emergency-family, safety class 4. chat.py:48, 61, 164 But emergency-family is declared workflow_type: coach, emergency-family.yaml:128 and the chat-light companion fast path runs and can return before coach/bridge dispatch. api.py:2825, 2950 The companion safety alert only checks _detect_parent_crisis, which is mostly first-person language. _detect_escalation_signal is only used to veto actions — it never adds safety instructions to an ordinary companion call. safety_preempts is imported but never called. api.py:53

Isolated route test for "My son said he wants to kill himself tonight":

ROUTING emergency-family coach 4
[...ordinary companion attempted first, deliberately failed for the test...]
IMMEDIATE emergency-family bridge_active BRIDGE_WITHOUT_SYNCHRONOUS_CRISIS_RESPONSE

The bridge prompt explicitly prohibits advice or next steps and contains no crisis instruction. bridge_prompt_v2.txt:17, 113

Blast radius

Explicit third-person crisis statements about a child, spouse, or family member. First-person parent self-harm is handled better. Medical-emergency short circuit is unaffected.

Fix (needs a decision, sketch below)

+ safety_class = (routing_decision or {}).get("safety_class")
+ if safety_class == 4 and skill_id in {"emergency-family", "symptom-triage"}:
+     return self._execute_synchronous_safety_response(
+         skill_id, user_input, user_id, profile, request_ctx=request_ctx
+     )

Needs to distinguish immediate self-harm/violence/hazard language (synchronous safety response) from an ordinary request to draft or send a family alert (still needs normal recipient/action confirmation) — that distinction is a product decision, not just a patch.

safetyconfirmed by execution + readingNot verified: real live-model response (deliberately not requested — would write prod conversation state); crisis-number localization.
03Expired outbox messages can still be delivered

due_outbox checks status, not_before, and next_attempt_at — not expires_at. turn_ledger.py:724 begin_attempt claims a row without checking expiry either. turn_ledger.py:736 Expiry is only evaluated after a provider failure — an accepted provider result wins before the expiry check runs. turn_ledger.py:758, 799 Medication occurrences carry an explicit late_send_deadline; turn_ledger.py:201 Phase-3 write-backs enqueue with a two-hour expiry. context_protocol.py:577

Blast radius

Medication reminders arriving past their allowed late-send window; stale proactive messages; Phase-3 responses delivered hours after the context that produced them.

Fix

 SELECT ...
  WHERE status IN ('pending','retry')
    AND not_before <= ?
    AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
+   AND (expires_at IS NULL OR expires_at > ?)

Recheck expiry under BEGIN IMMEDIATE inside begin_attempt and mark expired-but-claimed rows terminal instead of leaving them pending. Separately: startup resets every sending row to retry, turn_ledger.py:839 so a process crash between provider-accept and commit can double-send — the ledger dedupes inbound but doesn't guarantee exactly-once external delivery.

safety (meds)confirmed by readingNot verified: whether expired-pending rows currently exist in prod; provider idempotency-key support.
04A regenerated response can still ship with the violation it was regenerated for

The correction loop is bounded to one regeneration attempt (good), but the accept condition only checks that the violation count didn't increase — not that it reached zero. api.py:5126–5141 Opener repetition isn't rechecked on the second candidate at all, and the second candidate skips the deterministic stripping pass the first one got. Separately, initial bridge output returns with no governance wrapping, api.py:3131, 3137 and hold-mode responses skip the normal post-generation checks entirely.

Blast radius

Any turn that triggers a correction. Highest-risk case: a regenerated suggestion that still violates a ledger exclusion — e.g. a food allergy — ships anyway because the count merely "didn't get worse." Bridge-mode users bypass more of the gate than direct-companion users.

Fix

+ def _evaluate_candidate(message):
+     return {"opener": ..., "roster": ..., "ledger": ...,
+             "clinical": ..., "advice": ..., "recycle": ...}

- if _after_n <= _before_n:
-     msg = _msg1
+ regenerated = deterministic_strip(_msg1)
+ after = _evaluate_candidate(regenerated)
+ if not any(after.values()):
+     msg = regenerated
+ else:
+     msg = original_sanitized_candidate

Run the same evaluator over bridge/hold/blended responses too — bridge brevity rules can stay separate, but safety/roster/ledger checks shouldn't be skipped for that path.

safety / correctnessconfirmed by readingNot verified: embedding-bank behavior under production numerics (audit environment lacked the prod embedding stack).
05OpenRouter fallback can recurse and silently drops context

On a missing API key, _call_openrouter_companion recursively calls itself with the fallback model even though the key is still absent. api.py:5424–5438 On a provider exception, the fallback comparison checks against the configured primary model, not the current call's model — so once already running on the fallback, a second failure recurses into the same fallback again. api.py:5648 Both recursive calls omit system_additions and request_ctx, silently dropping turn-mode instructions, thread/topic context, constraint-ledger additions, and correction instructions. Production currently has a live fallback model configured, so this path is real, not theoretical.

Fix

-def _call_openrouter_companion(...):
+def _call_openrouter_companion(..., allow_fallback=True):
 ...
- if _fb and _fb != os.environ.get("COMPANION_MODEL", ""):
-     return self._call_openrouter_companion(_fb, ..., original_query)
+ if allow_fallback and _fb and _fb != model:
+     return self._call_openrouter_companion(
+         _fb, user_input, entity_context=entity_context,
+         profile_context=profile_context, honcho_insight=honcho_insight,
+         conversation_history=conversation_history, is_bridge_mode=is_bridge_mode,
+         original_query=original_query, system_additions=system_additions,
+         request_ctx=request_ctx, allow_fallback=False,
+     )

If the key is absent, go straight to the non-OpenRouter fallback instead of recursing into another OpenRouter call.

reliabilityconfirmed by readingSafety-relevant when the dropped additions carry constraints. Not verified: a real provider outage wasn't induced against prod.
06Session identity slides mid-conversation

ContextFactory._history fetches only the latest 24 rows. request_context.py:204 Since rows include both user and assistant turns, that's roughly 12 exchanges, not 24. The oldest row in that truncated window is used as started_at when no 30-minute gap is found — so once a conversation passes the cap, every new message pushes the window forward and changes the hash-derived session_id. request_context.py:242, 249 The legacy fallback path has the identical LIMIT 24 pattern, api.py:2186 so this isn't specific to the request-context canary.

Reproduced against a temp DB: adding one row to a continuous 26-row history flips the session ID.

SESSION_SLIDES True
2026-08-16T15:01:57...  →  2026-08-16T15:02:17...

Fix

Don't derive persistent session identity from a capped sliding window. Either persist the active session ID in a per-user table, updated atomically on a real 30-minute gap, or page backwards until the actual boundary is found. Raising the row limit only delays the defect.

correctnessconfirmed by executionNot verified: how many live sessions currently exceed 12 exchanges.
07Phase-4's worker deadline doesn't actually cancel anything

Bulkhead.run wraps a synchronous worker in fail_after, bounded_server.py:138, 157 but doesn't enable cancellable/abandoned worker behavior — a normal synchronous worker ignores cancellation until it returns on its own. /chat, /context, and the compatibility proxy all run inside these workers.

Fix — no clean one-liner

Abandoning the thread lets it keep mutating memory after the client already got a 504. The real fix needs every downstream network/DB call to honor a passed-in deadline, plus capacity accounting that counts still-running abandoned threads against the bulkhead — not just a wrapper change.

reliabilityconfirmed by readingCanary-account-only today. Not verified: a completed load/stall test against the deployed unit.
08Inconsistent SQLite connection discipline

H30 uses a thread-local connection helper, and so do planner/chat routing. InferenceEngine instead constructs one connection with check_same_thread=False and stores it on the singleton, inference.py:201 shared with DeepProfilePipeline. inference.py:218 Every routed request calls record_signal, which commits through that shared connection. api.py:2399, inference.py:267 check_same_thread=False disables Python's ownership check — it does not serialize transactions, so concurrent request threads can interleave statements on the same connection. Separately, even where connections are correctly thread-local, H30 and profile updates do read-modify-write on JSON blobs across separate calls, which is a same-user lost-update risk independent of the connection-sharing issue.

Fix

- self._conn = sqlite3.connect(db_path, check_same_thread=False)
+ self.db_path = db_path
+ @property
+ def _conn(self):
+     return get_thread_local_connection(self.db_path)

DeepProfilePipeline needs a connection factory/path, not one shared connection object. For the read-modify-write risk: wrap in BEGIN IMMEDIATE, reread after acquiring the write lock, merge, then update — thread-local alone doesn't fix that part.

reliability / correctnessconfirmed by readingNot verified: sustained concurrent load against a production-sized DB, including WAL lock latency.
09H30's "session" constraints never actually expire

Legacy constraint extraction stores an un-timestamped JSON list; h30.py:378–409 get_session_constraints returns it with no expiry check at all. h30.py:413 Both companion paths inject it on every future turn. api.py:5199, 5461 The newer, correctly-timestamped constraint ledger exists alongside it, not instead of it. The reset endpoint clears session_suggestions but not session_constraints. api.py:6765 Separately: on a user's first tick, the current message gets recorded twice (once at insert, once on the common append path). h30.py:151, 158

Fix

- ["sticker chart", "reward system"]
+ [{"text": "sticker chart", "at": 1786890000.0}]

Add since_ts to get_session_constraints, pass the current session start from both callers, and treat un-timestamped legacy rows as expired during migration. For the double-count: initialize new rows with an empty list and let the shared append path add the message exactly once.

correctnessconfirmed by readingNot verified: how many live H30 rows carry stale legacy constraints today.
10Phase-3 context protocol: stuck turns, early writes, anchor races

get_grounding_context durably accepts a turn before routing/H30/anchors/response assembly run. context_protocol.py:254, 267 If anything fails before the response is inserted, a retry sees the already-accepted turn and returns "processing" forever — no lease or reclaim path. context_protocol.py:277 Separately, writeback updates H30/Honcho before delivery-channel and anchor validation, context_protocol.py:497, 511 so an invalid delivery block can 400 after memory already changed. And anchor selection excludes only delivered anchors, not reserved ones, context_protocol.py:335, 390 so two concurrent reads can reserve the same anchor for the same user/session.

Fix

 WHERE ... status='delivered'IN ('reserved','delivered')

That predicate change alone still races — it needs to sit inside the same BEGIN IMMEDIATE transaction as the reservation insert. Also needs a processing-status lease/reclaim path, and full payload validation (delivery, model metadata, reservations) before any H30/Honcho write.

reliability / correctnessconfirmed by readingCurrently scoped to the Phase-3 allowlist (founder account). Not verified: real concurrent traffic or interrupted-call recovery.
11Hybrid bridge: state races and unbounded write-back threads

_kimi_jobs is a process-wide dict. api.py:1825 The bridge gate does an unlocked existence check, api.py:3038 substantial work, then an unlocked assignment. api.py:3106 Two concurrent heavy requests for the same user can both see no job, both start work, and one registration overwrites the other. Delivery does an unlocked delete, api.py:2220 and pending-turn counters do an unlocked read-modify-write. api.py:2329 Separately, every normal write-back spawns a new daemon thread with no bounded pool, queue, or join. api.py:2123, 2163

Good news found in the same pass: the previously-documented bridge memory-write-back regression (both return paths skipping the memory write) is already fixed in current code, and DISABLE_BRIDGE is genuinely read now — it's just unset, so the bridge is active. api.py:2305, 2383, 3131, 3035 The older architecture brief's claim that both paths lose memory is stale.

Fix

+ self._kimi_jobs_lock = threading.RLock()
+ with self._kimi_jobs_lock:
+     if user_id in self._kimi_jobs: ...
+     self._kimi_jobs[user_id] = _job

Same lock for delivery/deletion/held_turns. Replace per-turn daemon spawning with a bounded executor and a finite queue with an explicit rejected/deferred status.

reliabilityconfirmed by readingNot verified: a same-user concurrent bridge stress test.
12Four live routing regexes contain a literal backspace byte

Four active regex strings contain byte 0x08 where \b (word boundary) was clearly intended — meal-planning→reminders, appointments→calendar, and two shopping-list continuation guards. api.py:2699, 2711, 2729, 2733 These patterns require an actual backspace character in user input, so ordinary text never matches — the corrections they're meant to make simply never fire. The same corruption exists in the (currently inactive) trust-guard patterns. Present since the initial snapshot 00ac059 — this has never worked.

Fix

- r"<0x08>(remind|reminder|...)"
+ r"\b(remind|reminder|...)\b"

Trivial, zero-risk, worth doing immediately alongside #1. Add a source-level check rejecting stray control bytes so this class of typo can't recur silently.

correctnessconfirmed by byte-level readingNot verified: production frequency of the resulting misclassifications.
13Entity/vector memory data-quality bugs

Null age becomes text "None": age = str(child.get("age", "")) turns a JSON null into the truthy string "None", which can render as "Child X (age None)". entity_extraction.py:131

Duplicate facts accumulate: _store_fact always inserts, never updates existing rows or last_seen_at; entity_extraction.py:163 should_run accepts but doesn't actually use its min_messages_since_last parameter. entity_extraction.py:281 Also: the context renderer only surfaces children/barrier/health/preference/pet facts — extracted adult/schedule/motivator/location facts are silently dead for the prompt path. entity_extraction.py:249

Embedding cache collides: the cache key is only the first 200 characters of text, while the provider embeds up to 8,000 — two distinct long messages sharing a prefix get the same cached embedding. vector_memory.py:92, 103

Fix

- age = str(child.get("age", ""))
+ raw_age = child.get("age")
+ age = str(raw_age) if raw_age is not None else ""

- cache_key = text.strip()[:200]
+ cache_key = hashlib.sha256(text.strip()[:8000].encode()).digest()

For duplicates: add a unique key on (user_id, fact_type, subject, value), upsert instead of insert, update last_seen_at.

correctnessconfirmed by readingNot verified: duplicate/null counts in the live database.
14Config drift and an embedded credential fallback

COACH_MODEL is set live but never read. Coach configuration actually uses TALL_MODEL, api.py:1822, 1841 which is unset — so the live COACH_MODEL=x-ai/grok-4.3 has zero effect; the code default applies instead. Whoever set that env var believes it's doing something it isn't.

An embedded DeepSeek API key exists as a hardcoded default in the coach scaffold. coach.py:935, 938 Not currently active (the real env var is set), but it's a live credential sitting in the repo, and would silently activate if the env config ever disappeared.

Fragile systemd layering: multiple lexically-ordered drop-ins, later z...-prefixed files winning — already bit the team once (the July 3 incident where an eval driver's config leaked into production for 3 hours). Static drop-ins beat systemctl set-environment, so a manager-level change can silently not apply.

Fix

- api_key = os.environ.get("DEEPSEEK_API_KEY", "<embedded>")
+ api_key = os.environ.get("DEEPSEEK_API_KEY", "")
+ if not api_key:
+     return None

Rotate the embedded key regardless. Decide deliberately whether COACH_MODEL should become a real alias for TALL_MODEL or just get removed — don't silently wire it up without deciding whether Grok-as-coach is actually intended. Consolidate config into one authoritative source and verify deploys against /proc/$MainPID/environ, not the unit file.

reliability / securityconfirmed from live runtime + readingNot verified: credential validity; whether vector_memory.db (computed via a fragile path-string replace, landing outside the engine's data dir) is included in current backups — worth checking separately.

·Also worth knowing

·Coverage — what got a full pass vs. a light one

AreaCoverage
Request lifecycle / routing / archetypesFull static audit + targeted execution
Memory / context / H30Full static audit
Model call layerFull static audit; no real provider outage induced
Post-generation governanceFull code-path audit; no live embedding-stack execution
Safety / crisisFull static + isolated end-to-end execution
Delivery / outboxFull static audit; no real message sent
Phase 1/3/4 canaryFull static; light dynamic (one defect reproduced, no live authenticated traffic generated)
Config / env layeringFull live runtime audit, no restart, no secrets printed
ConcurrencyFull static audit; no sustained production-scale stress test

No requested area went entirely unreached. The weakest evidence across the board is dynamic concurrency behavior under real load — those findings are code-confirmed but still want a controlled stress test against a copied production database before final tuning of timeouts, queues, and lock strategies.

Read-only audit against production source and live runtime state. Reproductions ran against temporary databases and monkeypatched provider calls — nothing was written to the production database, and no service was restarted.