← MSBAi Home

K-ai Governance & Reliability Change Log

Purpose: An append-only, audit-facing record of material changes to how K-ai operates — its safety guarantees, sensor/audit behavior, and outbound-reply reliability. The published /governance page reflects the live G-rule registry (content/behavior governance); this log captures the reliability / mechanism changes (the “R-track”) that the registry does not, plus any fix made in response to an incident.

Why this exists: the agent runtime lives in a separate repo (nanoclaw-msbai), so its git history is not visible from a governance audit of this knowledge base. This log bridges that gap: each entry says what changed, why, and where to find the code. Detailed root-cause + diffs live in the linked nanoclaw-msbai commit.

Format per entry: date — change · why · R-track mapping · commit.


2026-09-24 — Host no longer runs git or file writes as root in the agent checkout

What: The curriculum checkout that K-ai’s agent containers edit moved from /root/repos/msba-online to /srv/msba-online, owned by the same unprivileged user the containers run as. The host process now runs every git command there as that user, never as root, and every file it writes or reads there (audit log entries, sensor triage notes, email attachment extractions) goes through a short-lived child process with that user’s rights. The daily MindForum room refresh reads a snapshot of the separate root-only read mirror instead of the agent checkout. On the server, the ubuntu account (the same user id) lost passwordless sudo and its sudo and lxd group memberships.

Why it matters: The agent containers can change anything in that checkout, including git’s own hook scripts and settings. Because the host ran git there as root, a container turned by a malicious email or message could have planted a hook that ran with full control of the server, or redirected a root file write through a symbolic link. Now such a planted hook or link runs with no more rights than the container already has. No sign of misuse was found before the change (hooks, git settings, cron entries and key files were checked). Stated limit: the same class of issue in the host’s per-group message and session folders is being fixed separately.

R-track: agent sandbox integrity / host privilege boundary. Code: nanoclaw-msbai faf53e2a (deployed 2026-09-24 08:26 CDT, 17 seconds of downtime); reviewed over four Codex security rounds. Ops record: nanoclaw-msbai/ops/2026-09-24-host-hardening.md. Acceptance: the first audit commit after the move is pushed by the unprivileged user and leaves no root-owned files in the checkout.

2026-09-23 — Telegram uploads moved out of the knowledge base checkout

What: Documents sent to K-ai on Telegram are now saved, original plus extracted text, in a host-owned store outside this repository (data/telegram-uploads/<group>/ on the VPS). Only the Telegram agent group can read that store, and it cannot write to it. Before this change, office extractions were written into this repository’s discussions/uploads/, which every agent group can write to. Large documents now arrive as a bounded preview with an index of sections and line ranges and a pointer to the saved text, instead of being truncated at 8,000 characters; the agent is told not to claim it has read the whole file from the preview.

Why it matters: A file inside a checkout the agents can write to can be swapped or redirected by a compromised session (for example, a planted symbolic link turning a host write into an overwrite elsewhere). The new store refuses symbolic links, never replaces an existing file, and checks its own directory’s owner and permissions before every write. Uploads are also no longer visible to the email agent. Stated limit: all Telegram senders still share one agent group, so an upload from one Telegram user is readable by sessions started by another, the same boundary as their messages. That is the open “K-ai user privacy” question and this change does not widen it.

R-track: agent sandbox integrity / data-exposure boundary. Code: nanoclaw-msbai PR #58 (07e8e675, merged 2026-09-14, deployed 2026-09-23 14:28 CDT after review of the symlink, inline-limit and privacy findings); test portability fix c672cfe8. Acceptance: first live Telegram document upload shows “Save status: ready”.

2026-09-11 — Nightly digest deliverability and next-morning delivery check

What: The nightly Daily Digest (scheduled task 7af3lh, 20:00 CT) now follows format rules written into its prompt: no links, no phishing-trigger wording (password, login, verify, account, credential, urgent), a body under 40 plain-text lines, and capped sections. Cadence and subject are unchanged. Separately, K-ai’s host now records every email Resend accepts and the outcome Resend reports for it; a send with no outcome after 12 h is reported to Vishal during the 08:00 CT hour and written to the audit log as [DELIVERY PROBLEM] unconfirmed.

Why it matters: The 09-08 and 09-09 digests were rejected by the university mail filter as “Suspected Phishing”; the hand-shortened 09-10 digest was delivered, so the shorter format is now permanent instead of a one-off. The digest is Vishal’s heartbeat signal, so a missing one has to surface. Stated limit: the 09-07 digest was reported delivered by Resend and still never reached the inbox (accepted, then held inside the university mail system). The new check cannot see that case; only a recipient-side arrival check could.

R-track: outbound delivery truthfulness / heartbeat integrity. Code: nanoclaw-msbai 94f6059a (2026-09-11, deployed 08:24 CDT). The prompt change is DB-only; the previous prompt is backed up on the VPS. Acceptance: the 2026-09-11 digest arrives.

2026-09-08 — Inbound email outage 09-02 → 09-08 (expired GitHub token in the email-inbox Worker)

What: The fine-grained GitHub token the Cloudflare email-inbox Worker uses to commit inbound mail to this repo expired 2026-09-02 00:10 CT. The Worker commits before it posts the webhook, and that commit is its one uncaught failure, so every email to msbai@illinihunt.org from then until the fix on 2026-09-08 10:29 CT was refused with a Cloudflare 421 and bounced back to the sender after five days. Nothing reached K-ai; the KV replay buffer never engaged because it only covers webhook-delivery failures. Outbound mail (digests, Monday alerts) kept flowing, which is why the outage looked like silence rather than an error.

Fix: token regenerated (new expiry 2026-12-07) and uploaded as the Worker secret; one-shot Telegram reminder task-github-pat-expiry-2026-12 fires 2026-11-30 (registry: nanoclaw-msbai/docs/reminder-registry.md). Senders whose mail bounced in the window must resend; the only reported case is Amber’s 9/2 post-meeting summary.

Open follow-up: make the Worker buffer mail when the GitHub commit fails instead of bouncing it, and route GitHub expiry notices to an address that is watched.

R-track: reliability / ingestion. Registry commit: nanoclaw-msbai@9c523340.


2026-07-29 — Weekly lint gains production-status ground truth (Box-sync corpus wired into check 6)

What: The Box→KB auto-sync writes per-item production status into courses/*/sync/activity-roster.md (Defined → Scripted → Recorded → Edited → Final) and was running correctly — 64 status rows for BADM 554, synced 2026-07-29 from Box, launchd sentinel healthy. But the weekly lint’s check 6 (premise validity, G37 v2) enumerated only EMAIL_ALLOWLIST.md and Confirmed DECISIONS.md entries as evidence sources. Grepping the live task prompt confirmed the gap deterministically: sync ×0, roster ×0, Box ×0, Status: ×0. Check 2 (“stale action items”) had no external corpus at all — it judged progress from each item’s own annotation text. Fix: added a production/recording bullet to check 6 pointing at the roster, with two explicit cautions — bundled items get narrowed, not closed; and absence of a roster row is not evidence of incompletion (the roster is LD-owned and does not track every artifact type). Live scheduler DB, task rowid 2; DB backed up to messages.db.bak-lintprompt-20260729.

Why it matters: same class of gap the person-name check closed, one layer up — a canonical record existed, was current, and no check consulted it. Concretely: ACTION_ITEMS.md carried “Draft scripts and plan wrapper content” as open with a 2026-06-20 “recording in progress” marker, while the roster showed the course intro (Course Welcome) already Recorded 2026-06-11 — nine days before the marker that was supposedly reporting progress on it. The item is now narrowed to the pieces the roster genuinely cannot confirm (bio video, module overviews) with 19 days to the BADM 554 launch. Generalizes the G37 v2 pattern: a premise check is only as good as the corpora it is told to read, and each new sync mechanism must be wired in explicitly when it ships.

R-track: R-lint / premise-validity evidence sources · scheduler DB prompt (not git-tracked; see docs/reminder-registry.md convention).


2026-07-29 — Sensor gains people-facts ground truth (person-name-mismatch pre-check)

What: K-ai’s 2026-07-28 course-projects reply expanded first-name-only KB references into FOUR wrong surnames (“Xing Huang”, “Mathias Kruttli”, “Gautam Ray”, “Ashish Agarwal” — all real academics elsewhere; ours are Gao, Kronlund, Pant, Khandelwal). No sensor ground-truth file carries instructor surnames, so the reply passed. Fix (nanoclaw 3e442d92, deployed): a deterministic pre-check against program/EMAIL_ALLOWLIST.md — the canonical people record; deliberately NO new people doc (one fact, one place) — flags any “First Last” pair whose unique allowlist first name is paired with a surname the allowlist doesn’t back (person-name-mismatch, FLAG never FAIL; middle-name drops and shared first names never flag). Free: string check, no metered call.

Why it matters: the check proved itself before it shipped — replaying the incident reply during acceptance surfaced the two additional wrong names (Ray, Agarwal) that the human-written first correction had missed; a completing correction went out the same hour (Resend e0344e0b). Error class named: surname hallucination substitutes a plausible real person from the wider world for the local one — exactly the failure mode a local canonical record refutes and a general model cannot.

2026-07-29 — Entry gate promoted to semantic judgment; read path gains premise checks (G37 v2, executing G38’s escalation clause)

What: The kb-entry-gate (G37) guarding ACTION_ITEMS.md and OPEN_QUESTIONS.md was lexical-only — live probes (2026-07-29) showed a reworded duplicate and a KB-satisfied item (“onboard Kate + Myranda”, both already allowlisted) passing clean, and the 2026-07-28 incident (surfacing “onboard Xing/Mathias/Gautam” as an open gap, all three long since allowlisted) happened on the read path, which no gate covered at all. G38 had anticipated exactly this: its registry row was recorded convention-tier deliberately, with the clause “promote to a kb-entry-gate v2 skill if the monthly review shows leaks.” The probes are that leak evidence; this change executes the promotion.

The v2 mechanism, four pieces: (1) the script becomes a multi-file candidate pre-filter (--emit-candidates: low-bar tracking twins + entity-driven KB snippets — allowlist rows, decision entries) and a skeptical subagent renders the verdict on three axes — redundant / already-satisfied / conflicting — with hard rules: blocks require verified file+line evidence, conflicts hold for humans (G8), ambiguity allows; (2) a surfacing-time playbook rule — before presenting an existing open item as a gap, re-validate its premise against KB state; (3) weekly-lint check 6 lists premise-dead open items (report-only; closing stays on the G38 human path) — an extension of the existing Wed lint per G30, not a new monitor; (4) a deterministic sensor FLAG (premise-dead-item-surfaced) catches onboarding-gap claims about already-allowlisted people post-hoc — deterministic because the sensor bills the metered Messages API, while the subagent judgment runs in containers on the flat-rate subscription. Cost structure decided placement.

Eval-first: the behavioural spec was written before the skill prompt — container/skills/kb-entry-gate/evals/golden.jsonl, 18 cases including four adversarial over-blocking traps (entity overlap without premise overlap, a new downstream question that merely cites a decision) and one expected-fail capability probe. Sensor FLAGs and kb-triage misses feed new cases before they’re fixed — the online loop reuses surfaces that already exist.

Why it matters: first tier promotion executed through a rule’s own pre-authorized escalation clause — governance that classified its incident instead of improvising. Full review (mechanism map, probe transcripts, design rationale): governance review 2026-07 · interactive gate simulator.

2026-07-28 — Wednesday KB update recipients expanded 19 → 25 (faculty roster completed + admissions)

2026-07-28 — Wednesday KB update recipients expanded 15 → 19 (T&L team)

2026-07-22 — Short-reply guard + large-document reply-tag guardrail (Amber audit incident)

2026-07-17 — Governance v9 wave implemented (G45/G46/G47/G51)

2026-06-19

2026-06-18

2026-07-15 — Sensor citation-path resolution fix (R-series)

What: 2 of 5 July sensor FAILs were false positives: K-ai cited repo files relative to discussions/ (e.g. kb-triage/2026-07-08-conflicts.md) but the sensor’s exact-path existence check reported them MISSING, and the prompt hard-FAILs any MISSING citation. citationExists() in nanoclaw-msbai/src/behaviour-sensor.ts now resolves the path as written, then discussions/-prefixed. Regression tests reproduce both July cases (nanoclaw-msbai 7c3f04f1, deployed 2026-07-15).

Why it matters: keeps FAIL verdicts high-signal — after the R1 ground-truth fixes cut false FAILs from ~36% to <5%, the citation-path class was the dominant remaining noise source.

Supersedes: the 2026-06-13 sensor-ground-truth-broadening inbox item (moved to done/) — broadening was the wrong shape; the June false-positive class was already eliminated by G40/G41 + rosters, and July’s residue was mechanical path resolution.

2026-07-15 — Outward reminder audience expanded: 12 → 15 pilot recipients

What: Xing Gao, Mathias Kronlund, and Gautam Pant — allowlisted as read-write faculty on 2026-06-24 (a5533d7) — were finally onboarded: the Vishal-approved welcome emails from 06-24 (which had stalled unsent) went out 2026-07-15 (Resend 2712d3ac, 874fd240; see discussions/audit-log/2026-07-15.md), and both outward reminders now include them: the Wed 14:00 weekly KB update (recipient list hardcoded in the scheduler task prompt, updated 12 → 15) and the Mon 08:00 action-item alert (resolves owners from EMAIL_ALLOWLIST.md, so pickup is automatic).

Why it matters: per the reminder-registry guardrail, any outward-recipient change requires admin approval (given by Vishal in this session) and a registry + changelog update. This closes a three-week gap where three allowlisted faculty had K-ai access but had never been told, and FIN 550 had no informed, allowlisted faculty contact. Registry updated in nanoclaw-msbai/docs/reminder-registry.md.

2026-07-17 — Outbound email reply gate: scaffolding can no longer ship as a reply body

What: Two pilot-visible incidents (2026-07-15 and 2026-07-16, both to Amber Glynn) delivered K-ai’s internal narration (“Mode 2 — …”, “Reply sent and pilot log committed…”) as the actual email body. Root cause: the April 2026 fix for the same failure (Jason Mock incident) added <reply>-block extraction but only wired it into the scheduler send path — the real inbound-reply path sent the session output verbatim, and even well-formed replies shipped with literal <reply> tags on the wire. nanoclaw-msbai@c7a2a7d7 adds a hard gate: email replies with a missing/empty <reply> block or scaffolding-signature bodies are refused, the session is retried (up to 5x), and exhaustion fires the dead-letter Telegram alert to Vishal. A second check at the Resend send choke point covers scheduler/IPC callers and malformed tags. Verified end-to-end with a synthetic test email (clean reply, sensor: pass, audit-log 2026-07-17 02:52).

Why it matters: stakeholder-facing correctness — internal agent state must never reach a pilot user as a reply. Failure mode is now “no reply + loud alert” instead of “confusing leak”, and the audit log records exactly what was sent.

2026-07-17 — Read-only access granted: Jill Moore-Reynen + Shambhavi Joshi (T&L project management)

What: Jill Moore-Reynen (j-moore6, Associate Director of Project Management, T&L) and Shambhavi Joshi (ssjoshi4, Associate PM) added to program/EMAIL_ALLOWLIST.md as read-only senders, at Jill’s request on the FIN 550 production-tracking thread (2026-07-15) and approved by Vishal. Same soft-enforcement model as the research observers: K-ai answers their questions but never commits KB changes on their behalf; any updates they send route through Jason Mock, who retains T&L update rights. Email-group prompt guardrail updated (nanoclaw-msbai groups/msbai-email/CLAUDE.md) to name all current read-only senders and their routing.

Why it matters: first T&L project-management access to K-ai — gives Jill/Shambhavi direct visibility into course-production tracking (Box roster → KB sync) without expanding write scope or connecting to Asana (explicitly deferred by Vishal on the same thread). Scope stays: allowlist entry = Q&A access only; they are not added to outward reminders (weekly update / Monday alert).

2026-07-28 — G42 in effect: two-tier outbound email reaches the runtime, bounded to the main group

What: G42 (two-tier outbound email authorization) was approved 2026-06-12 and activated in the registry 2026-07-22, but until now it existed only on paper — the playbook mirror never landed, and groups/msbai-email/CLAUDE.md actively told K-ai the opposite (“Tier-2 relay, when implemented, will restore immediate nudges”). nanoclaw-msbai PR #25 (89a3c0dd, deployed, /health 200) adds an §Outbound Email Authorization (G42) section to the shared playbook with Tier 1/Tier 2 and all eight Tier-2 conditions, and removes the negation clause. Registry updated to v9.3.

Implementation surfaced a limit the rule text never drew: Tier 2 splits in half. The G37 logging obligation works everywhere; the send obligation is main-group (Telegram) only. send_email is gated to the main group per G36, and non-main calls are dropped silently (src/ipc.ts — warn + break, no error returned to the container). Without a scope caveat, the email and web-chat agents would read “Tier 2 is pre-authorized” as a granted capability, attempt a relay that no-ops, and could then claim they nudged an owner. The playbook now names where Tier 2 is actionable and forbids claiming a relay elsewhere.

Why it matters: this is the G42 lesson closing on itself. G42’s six-week dormancy is why the registry now distinguishes “approved” from “in effect” and marks unimplemented rows ⚠ — and the same rule nearly shipped a second, subtler version of the same failure: text that grants permission in a context that cannot act on it. A rule granting permission never grants a tool; where the two diverge, the playbook must say so, because the failure is silent on both sides (no send, no error, and a plausible-sounding claim in the reply).

Not built: email-group relay needs a host-side relay-proposal path — agent composes a structured proposal, host validates against the allowlist, enforces the ≤2-relay cap, writes the parent-authorization audit entry, sends — i.e. the outbound-email.ts / processOutreachResult compose-then-host-send pattern in a new shape. In-container grants remain off the table (2026-06-10 R2 review, 8 rounds: every scheme leaks through a group-shared writable surface). Deferred: the Ron Guymon case that motivated activation arrived on Telegram, where Tier 2 already works end-to-end.

2026-07-28 — Aged open questions now surface in the weekly digest (G55)

What: OPEN_QUESTIONS.md was never scanned by the weekly exceptions digest, so a question could sit unanswered indefinitely with nothing surfacing it — and G42’s newly-activated coordination relay logs routed questions there with no reply-chase. RC-012 is now G55: every open - [ ] question carries an (Added: YYYY-MM-DD) stamp, and the digest reports questions crossing a 14-day threshold, the standing aged count, and any question missing the stamp. Shipped in nanoclaw-msbai (collectAgedOpenQuestions in src/exceptions-report.ts, stamping in container/skills/kb-entry-gate/), reporting into the existing digest per G30 — no new notification path.

Two design calls worth recording, both made after checking the data rather than the ticket:

Dates come from git, not from memory. RC-012 offered “parse an Added: field” or “adopt a date-stamp naming convention”. The file had zero Added: fields across 105 open questions, so either option meant a ~105-item hand-backfill plus a new field K-ai must remember on every future write — and a convention living only in prose gets skipped under context pressure, which would make the monitor silently under-report. Instead the backfill was derived by git blame -w -M -C (move/whitespace-aware, so the file’s reorganisations don’t date every question to a reformat commit), the kb-entry-gate skill now stamps new questions deterministically, and the digest reports anything unstamped — so a skipped gate becomes visible rather than silent.

Flag-once, not “everything currently old”. Real-data validation before shipping: 99 of 105 open questions are already past 14 days. A plain threshold would have opened the first digest with 99 one-liners and trained its reader to skip the section. The monitor instead enumerates only questions crossing the threshold inside the reporting window — the same flag-once semantics as the G47 auto-close scanner, and for the same reason: no suppression state to maintain, and a question Vishal consciously left open is not re-nagged every Monday. The standing backlog remains visible as one number, which deliberately never wakes the digest by itself.

Why it matters: this is the second monitor in two weeks to land with flag-once semantics, and the pattern is now the house style for anything scanning a long-lived backlog — report the transition, count the state. Blame-derived dating is likewise reusable: where the repo already records when something happened, deriving beats maintaining.

2026-07-28 — Outbound choke point made true: every email send now passes one gated host path (G22/G46)

What: The G22 registry row claimed all outbound email enforcement lived in nanoclaw-msbai/src/outbound-email.ts — but ordinary email replies and the G17 access-closed courtesy reply called the Resend API directly from webhook.ts, bypassing the choke point entirely. The two highest-volume send paths ran without G46 shortfall reporting, and the courtesy reply wrote no audit record at all (invisible to G23). Found by a Codex Architect consult during the 2026-07-28 Azure pre-migration review, verified in code, consolidated in nanoclaw-msbai PR #28 (278f8798, deployed).

Four adversarial review rounds hardened the consolidation itself before merge, including a real find: the courtesy reply’s narrow allowlist exemption combined with comma-splitting of the To field would have relayed pilot mail to arbitrary external addresses given a spoofed multi-address From header. Now refused at two independent layers (single bare @illinois.edu addr-spec required; the choke point separately refuses any exemption send with more than one recipient). Also: reply failures now fire the G46 owner alert naming the recipient (previously server-log-only); the courtesy reply is audit-logged; replies are judged by the exact allowlist snapshot that admitted the inbound message; and no send path can trigger a synchronous git fetch on the shared event loop.

Why it matters: G22’s enforcement claim is now accurate rather than aspirational — there is exactly one way out, and it carries the allowlist, delivery-completeness, audit, and (future) provider logic. This is also the Azure migration’s mail prerequisite: swapping Resend for Graph (plan §4) is now a change to one file instead of a hunt across send sites.

2026-08-04 — Roster drift closed: stakeholders.md derived from the allowlist (G57)

What: Onboarding Brook Corwin exposed that program/stakeholders.md — the file K-ai uses to tailor responses by role — had silently drifted from program/EMAIL_ALLOWLIST.md: seven July-2026 adds missing, Kacie Jones still listed six days after her access was retired, and a wrong email for Ashish Khandelwal (akhandelwal@ vs the confirmed ashishk@). RC-015 is now G57: the sender roster in stakeholders.md is a generated block (scripts/sync-stakeholders.py, markers in the file), only non-sender contacts and Open Question Ownership stay hand-maintained, and the existing deploy CI runs --check on every push — failing when the block is stale, a retired sender lingers anywhere in the file, or an allowlisted sender is duplicated in a manual table row.

Why it matters: the allowlist called itself the single source of truth, but nothing bound the second file to it — so every onboarding depended on a human remembering an undocumented second step, and across six onboarding events in July none did. The fix follows the standing pattern: derive instead of maintain (the shared content now cannot drift), and put the residual check in CI next to G34/G43 rather than the weekly lint, because it is deterministic set-comparison needing no LLM judgment. The onboarding procedure itself is now documented in the allowlist’s Notes — the one file anyone editing membership is already looking at.

2026-08-06 — Replies now reach the whole thread, not just the sender (G58)

What: When someone emails K-ai directly (K-ai in the real To: line) with colleagues on the thread, K-ai’s reply now CCs those colleagues — but only addresses already on program/EMAIL_ALLOWLIST.md. Anyone not on the list is left off, and the reply itself says so in a closing note naming who was not copied and why. Dropped addresses are also recorded on the outbound audit entry. Mail where K-ai is only Cc’d is unchanged (G56: filed silently, no reply).

Why it matters: Since 2026-07 the reply recipient was hardcoded to the sender, so a question asked “in front of” a dean or a colleague got an answer only the sender saw — the sender’s own choice of witnesses was silently defeated (the FIN 550 incident was this failure from the composing side). The 2026-08-01 “no reply-all, ever” rule was written before real To:/Cc: headers were recoverable; with G56’s header fidelity in place, the allowlist becomes the spam boundary: K-ai still cannot email anyone a human didn’t both put on the thread and previously authorize. The gate runs fail-closed even when inbound processing is in permissive fallback, so an unreadable allowlist narrows reach rather than widening it.

2026-08-10 — Monday reminders follow onboarding and current ownership

What: The Monday action-item task still contained the original 12-person recipient list even though the reminder registry said it followed the current allowlist. The host now builds the audience from the current action-item owners and the onboarding policy on every run, and refuses to send a partial or extra batch. Read-write users are automatically eligible when they own open items; observers and agent principals are excluded; read-only onboarding asks whether the person can own action items and records the answer as an explicit monday-reminder: yes or monday-reminder: no decision from Vishal. The existing operating-team read-only users are recorded as yes, while the two research observers are recorded as no. The onboarding checklist and generated stakeholder roster now carry the same policy.

Why it matters: Adding someone to K-ai no longer leaves a separate hidden Monday-recipient list to update. Access controls who may change the knowledge base; Monday reminders follow accountability instead. The explicit read-only decision preserves the difference between operational collaborators and visibility-only users, and a missing decision stops the batch instead of silently omitting someone. On the 2026-08-10 tracker this produces 20 messages: Vishal’s digest plus 19 owner check-ins. No catch-up batch was sent automatically.

2026-08-13 — Monday reminders: every allowlist member is eligible

What: The tiered eligibility rules from 2026-08-10 (read-write automatic, observers excluded, read-only decided case by case with monday-reminder: yes/no markers) are replaced by one uniform rule: every allowlist member is eligible to own action items and receives a Monday reminder whenever they own open items. The per-row monday-reminder: markers are removed from the allowlist, the onboarding checklist no longer asks the per-person question, and the research observers are eligible like everyone else. The only exclusion is the agent principal (an automated principal is not sent reminder email).

Why it matters: Eligibility is inert unless a person owns open items, so the case-by-case decision bought nothing except an extra onboarding step and a marker field the batch could fail on. One rule means adding someone to K-ai is the whole story: if they later own action items, they hear about them on Monday. Decided by Vishal, 2026-08-13.

2026-08-15 — G11/G39 promoted to code-tier: protected-path write guard, push tripwire, allowlist author-gate

What: The two convention-tier rules flagged by the 2026-08-03 governance audit are now enforced in code (nanoclaw-msbai PR #46, merge 24638836), in three layers. (1) A PreToolUse hook in every agent session — delivery-repair included — denies Write/Edit/NotebookEdit against the mounted program/curriculum.md, program/EMAIL_ALLOWLIST.md, and program/roles.md, telling the agent to flag the change for Vishal instead. (2) The GitHub push webhook trips on any non-admin-authored commit touching a protected path — including forced updates and pushes whose 20-commit payload truncates the change — writing a durable [TRIPWIRE] audit-log entry before attempting the Telegram owner alert (G51). (3) EMAIL_ALLOWLIST.md reads are pinned to the newest admin-authored revision: a bot- or third-party-authored allowlist commit is rejected fail-closed and the last admin revision keeps serving, so the inbound spam boundary cannot be widened by anything but an admin host commit.

Why it matters: until now these guarantees were prompt text. A confused or prompt-injected agent session could have edited the curriculum source of truth or added itself an authorized sender; the 2026-08-13 sensor FAIL (a fabricated CURRICULUM.md citation) showed the trust-the-prompt posture aging badly. Known limits, accepted and documented in the PR: container Bash writes bypass layer 1 (layers 2–3 are the backstop), and the identity signal is the git author email, which is forgeable — the proposed hard fix is a server-side GitHub push ruleset restricting program/*, staged as a recommendation for Vishal. Five independent review rounds (29 findings dispositioned, one attack candidate empirically refuted) preceded the merge.

R-track: hardening of the G51 audit spine (tripwire entries) + G39/G11 enforcement mechanism. Commit: nanoclaw-msbai 24638836; registry rows G11 + G39 flipped to code-tier same day.

2026-08-15 — Anonymous web chat retired (channel count 3 → 2)

What: K-ai’s web chat is shut down at both ends. Site-side, the /chat page is deleted and every entry point removed — the “Ask K-ai” nav item and footer link in _layouts/default.html, the home-page and FAQ cards, the knowledge-base invitation, and the embedded ask-K-ai panel on /governance (its per-rule “ask about this rule” hook went with it; the rule registry and its search are untouched). Host-side, WEBCHAT_SECRET is commented out of the VPS .env, which makes the channel self-disable at startup — the /chat route is never registered and now returns 404, verified with the real credential from both inside the VPS and the public internet. No code change or redeploy was needed; the channel code stays in nanoclaw-msbai, dormant.

Why it matters: the channel had no identity and permanent retention — the worst pairing. Usage was 48 messages over ~5 months, declining month over month (14/11/12/5/4/2) to no traffic at all after 2026-08-04, and every session was exactly one message: nobody ever held a conversation. Because nothing identified the sender, there was no way to establish who was asking, while the questions themselves were retained verbatim in the git-committed audit log and the message database. Email and Telegram cover the same need with identity plus allowlist gating, so retiring the channel removes an unauthenticated public-ish surface without losing capability. It also retires the proposed Entra/NetID sign-in work (_agent-inbox/2026-08-15-webchat-entra-oauth.md, now shelved) — that would have cost a dedicated app registration, four SPA redirect URIs per host, and a trip through UIUC’s admin-consent wall to authenticate a channel nobody was using.

Data: nothing was deleted. Existing transcripts stay under the same retention policy as email (Vishal, 2026-08-15). Note for whoever defines that policy: 37 of the 48 web-chat messages predate audit-logging and exist only in store/messages.db, so a retention rule scoped to discussions/audit-log/ would miss most of the web-chat history.

Residual: the retired page may still be served from Cloudflare’s edge cache for a few days (s-maxage=604800); it is inert, since the endpoint it posts to is gone. The old chat credential was published in page source and remains in git history — it is now dead, and re-enabling the channel must mint a fresh secret rather than restore it.

R-track: channel surface reduction / unauthenticated-access removal. Commits: msba-online 8114fab + 82a9314, nanoclaw-msbai 7651a15e. Supersedes the 2026-05-15 “Web chat channel: read-only discovery and FAQ” decision.

2026-09-02 — Delivery truth: the agent can no longer narrate a send the host refused

What: Two layers now keep K-ai’s account of an email consistent with what the host actually sent. At tool time, send_email checks every To and CC address against a pinned copy of the allowlist that the host writes into the container at start, and its result names any recipient the host will refuse. On the next turn, the host prepends a delivery record of every terminal send outcome (dropped CC, refused send, permanent provider error, dead letter, queue overflow, or an address that became allowed after the session began), acknowledged only once that run has produced a usable reply. The shared agent playbook gained the rule that addresses come only from the allowlist and a dropped CC is reported as not sent. Filed as RC-018, approved as G59 the same day.

Why it matters: On 2026-09-01 K-ai invented a CC address from Vishal’s surname, the gate dropped it, and K-ai then told Vishal the CC had gone to a wrong address. That was the one genuine error among 34 judged replies in the first clean post-deployment sensor window (issue #38). The fix removes the information gap that made the false narration possible rather than relying on prose alone.

R-track: delivery truthfulness / recipient boundary. Candidate: RC-018. Code: nanoclaw-msbai (2026-09-02), six Codex review rounds; the sensor replay corpus gained the incident as a genuine-FAIL control.

2026-08-18 — Sensor precision replay and unreviewed-alert Daily Digest

What: The behaviour sensor now receives the triggering user request as context, applies a stricter direct-contradiction test, and skips generic infrastructure/scaling answers unless they contain a concrete program fact. A side-effect-free evaluator supports a 15-case historical replay: each case runs three times and requires a two-of-three allowed-verdict majority. The Daily Digest now presents sensor FAILs as automated, unreviewed alerts; it no longer invents root-cause groupings and shows recorded human dispositions.

Why it matters: Manual review of the 2026-08-12 through 2026-08-18 window found one genuine K-ai error among eleven sensor FAILs. The false positives confused user-provided material with K-ai claims, soft launch with official start, internal course parts with separate registrations, and the mention of stale data with endorsement. The replay keeps those cases closed while retaining the genuine Linear Algebra fabrication and three adversarial negative controls.

R-track: sensor precision / evaluation reproducibility / digest truth in labeling. Governance rows: G24, G25, G40, G41. Code and replay corpus: nanoclaw-msbai sensor-precision repair (2026-08-18).