[host-scope, out of plugin] mcode team hooks for governance parity
⚠️ This issue is NOT for the plugin maintainer. It is a
coordination artifact for the mcode team. Plugin-side work
is in issues 1–4 of the same series. If @Wzdhehe is not also the
mcode maintainer, please forward this issue (or its content) to
the mcode team. The plugin-side PRs in issues 1–4 will reference
this issue's mcode-side PRs as a dependency before merging — we
will not start a "combined audit view" UI until mcode ships
event:audit + event:anomaly.
Author: @modacker
Source review: docs/REVIEW-sihankor-baselines-2026-08-28.md
Part 5.
Audience: mcode team (not the mcode-webui maintainer). This
issue is filed in the plugin repo as a coordination artifact; the
actual implementation lives in the mcode agent runtime.
Note: this issue is informational. The mcode-webui plugin
maintainer cannot implement these hooks; they exist to make the
host/plugin boundary explicit so the mcode team can plan.
Why
The mcode-webui plugin is bounded: it observes what mcode emits,
relays commands, and renders state. It does not (and cannot)
intercept mcode's internal writes to runtime-state.sqlite, its
internal audit trail, or its slash-command surface. Several
SiHankor engineering baselines require cooperation from the mcode
host to fully close:
- Baseline 1 (LLM is symbol generator, not governor): the LLM
in mcode can today trigger governance-flavored slash commands by
parsing user input. A permission:request hook in mcode is the
only way for the plugin to enforce that destructive operations
require user confirmation.
- Baseline 4 (audit trail): today, mcode's internal writes to
its own sqlite are silent. The plugin's events.ndjson (see
Stage 1 issue) is plugin-scope only. A mcode-side
event:audit + event:anomaly SSE channel would let the plugin
forward into a single combined audit view.
- Prohibition 1 (LLM must not directly modify knowledge/intent):
the plugin can guard webui-internal writes, but mcode's own
state changes (session metadata, plan state, etc.) happen
without any webui gating. A requires_confirmation: true flag
on mcode's slash commands would let the plugin enforce this in
the UI without rewriting the host.
What (in mcode, not in the plugin)
1. event:audit SSE channel
mcode emits structured events for every internal state change:
{ "kind": "session.create" | "session.update" | "plan.commit"
| "tool.exec" | "tool.result" | "permission.ask"
| "permission.resolve",
"ts": ..., "actor": "user"|"mcode"|"system", "data": { ... } }
The plugin forwards these into its own events.ndjson (see
Stage 1 issue), prefixed with a clear src: "mcode" marker.
2. event:anomaly SSE channel
mcode emits anomaly events that the plugin surfaces through its
anomaly bell (see Stage 1 issue). Examples:
- Repeated tool failures from the same call site
- Permission escalation attempts
- Plan / execution divergence (mcode committed a step that the
approved plan didn't include)
- Token-bucket near-exhaustion
The plugin renders these as a top-bar toast; the user sees
"⚠️ mcode reported 3 consecutive tool failures at "
without the plugin needing to know what a "tool failure" is.
3. permission:request hook on slash commands
mcode's slash command surface (today: a flat list parsed by mcode
itself) gains a requires_confirmation: boolean flag. The plugin
wires the flag to its authorize() helper (see Stage 1 issue):
when mcode is about to execute a requires_confirmation: true
command, the plugin intercepts, shows a confirmation modal, and
only lets mcode proceed when the user clicks Allow.
This is the cleanest way to enforce SiHankor prohibition 1
("LLM must not directly modify knowledge/intent") without
rewriting the host's command dispatch.
4. goal:status and plan:status events
mcode emits structured events when the goal/plan subsystem
transitions. The plugin renders these as the chat-side banner
status (e.g., "📋 Plan mode — 3/5 steps approved") without
needing to poll mcode's internal state.
Acceptance criteria
(Marked per-implementer; the plugin cannot test these directly.
The plugin team can test by stubbing the mcode-side events in
acp-client.js.)
Out of scope
- Plugin-side implementation of any of these — that's the Stage
1/2/3 issues
- mcode's internal sqlite schema changes — out of plugin scope
- Telemetry / observability beyond the audit trail — explicitly
rejected in SECURITY-NOTES.md
[host-scope, out of plugin] mcode team hooks for governance parity
Why
The mcode-webui plugin is bounded: it observes what mcode emits,
relays commands, and renders state. It does not (and cannot)
intercept mcode's internal writes to
runtime-state.sqlite, itsinternal audit trail, or its slash-command surface. Several
SiHankor engineering baselines require cooperation from the mcode
host to fully close:
in mcode can today trigger governance-flavored slash commands by
parsing user input. A
permission:requesthook in mcode is theonly way for the plugin to enforce that destructive operations
require user confirmation.
its own sqlite are silent. The plugin's
events.ndjson(seeStage 1 issue) is plugin-scope only. A mcode-side
event:audit+event:anomalySSE channel would let the pluginforward into a single combined audit view.
the plugin can guard webui-internal writes, but mcode's own
state changes (session metadata, plan state, etc.) happen
without any webui gating. A
requires_confirmation: trueflagon mcode's slash commands would let the plugin enforce this in
the UI without rewriting the host.
What (in mcode, not in the plugin)
1.
event:auditSSE channelmcode emits structured events for every internal state change:
The plugin forwards these into its own
events.ndjson(seeStage 1 issue), prefixed with a clear
src: "mcode"marker.2.
event:anomalySSE channelmcode emits anomaly events that the plugin surfaces through its
anomaly bell (see Stage 1 issue). Examples:
approved plan didn't include)
The plugin renders these as a top-bar toast; the user sees⚠️ mcode reported 3 consecutive tool failures at "
"
without the plugin needing to know what a "tool failure" is.
3.
permission:requesthook on slash commandsmcode's slash command surface (today: a flat list parsed by mcode
itself) gains a
requires_confirmation: booleanflag. The pluginwires the flag to its
authorize()helper (see Stage 1 issue):when mcode is about to execute a
requires_confirmation: truecommand, the plugin intercepts, shows a confirmation modal, and
only lets mcode proceed when the user clicks Allow.
This is the cleanest way to enforce SiHankor prohibition 1
("LLM must not directly modify knowledge/intent") without
rewriting the host's command dispatch.
4.
goal:statusandplan:statuseventsmcode emits structured events when the goal/plan subsystem
transitions. The plugin renders these as the chat-side banner
status (e.g., "📋 Plan mode — 3/5 steps approved") without
needing to poll mcode's internal state.
Acceptance criteria
(Marked per-implementer; the plugin cannot test these directly.
The plugin team can test by stubbing the mcode-side events in
acp-client.js.)event:auditfor everysessions.write,plan.commit,tool.exec,tool.resultevent:anomalyfor the 4 categories aboverequires_confirmation: booleanfield on each commandgoal:statusandplan:statusevents ontransition
events) renders in the top-bar "view" affordance without
the user needing to read raw logs
Out of scope
1/2/3 issues
rejected in
SECURITY-NOTES.md