Research positioning
The companion vault (../sms-research, see wiki/thesis.md) argues that safe live
self-modification needs three things no shipped system combined: a substrate with revertible effects and
reactive dependencies (Cordis / Spatiotemporal Composability), a judge outside the agent’s write set
(the lesson of the Darwin Gödel Machine’s objective hacking), and persistence with an audit trail (what
DeepSeek’s dsh deliberately skipped — “scratch, not memory”). sms-baseline is the attempt to have all three,
packaged so that ordinary applications can adopt it.
The four questions, answered for sms-baseline
The vault compares systems along what / who / when / judge and the lifecycle Bootstrap → Propose → Evaluate → Commit → Serve. Our row:
| sms-baseline | |
|---|---|
| What is modified | the running application, as plugins: keys provided, extension-point contributions (routes, collections, fields, hooks, tools, prompts), and agent code. The unit of change is the fiber — exactly what the substrate can swap atomically (not functions like SelfEvolve, not whole containers like MOSS). |
| Who proposes | the model, through plugin_define; humans through the same judge API |
| When | live, in the same conversation; Commit = the plugin/installed event + content-addressed blob; Serve = the fiber is active and reachable through http/agent/collections |
| Judge | the judge system plugin: gates (manifest, trust, source-lint, load-in-throwaway-executor) → policy (sensitive scopes/points → human) → mount → invariants; rollback restores the previous version from log + blobs |
| Unreachability of the judge | structural, not hidden: judge key and gate/invariant points require system trust checked in the kernel on every access; MountAuthorization minted only via KernelApi, which agent contexts never receive; system ids reserved; the executor bridge re-checks inject and trust per call |
| Boundary | QuickJS-in-WASM executor: own heap, memory limit, CPU deadline, JSON-only bridge. The Node executor is labelled steering, not a security boundary, as dsh labels node:vm. |
| Persistence | event-sourced (plugin/* fold into the target view), blobs by hash; SQLite, Postgres, D1, Durable Objects (drivers/store/*); files on S3 / R2 (drivers/objects/*) |
| Tenancy | kernel-per-tenant; the substrate never sees two tenants |
Concepts from the vault and where they live here
| Concept | Here |
|---|---|
revertible-effects | closed vocabulary provide/contribute; AppliedEffect.undo derived by the kernel; LIFO unwind (kernel.ts undoAll) |
spatiotemporal-composability (Thm 63, provider outlives consumers) | deactivate() unloads dependents first, settle() re-activates what became satisfiable |
plugin-architecture five requirements | closed effects, per-fiber isolation, reactive re-wiring, judge outside the write set, persistence as a separate axis |
project-trust-gate (load-time trust is not enough) | gates run at propose time and invariants run after every mount; trust is re-checked at every effect |
transactional-no-regression | mount = checkpoint (previous version in log) → act (activate) → judge (invariants) → commit or full undo. Batch size K = one plugin activation. |
objective-hacking / belief 6 | the judge’s inputs (gate, invariant) are system-only; but see Open questions on indirect contamination |
dynamic-plugin-runtime (dsh) | plugin_define → judge → kernel instead of a file loader; approval is a policy (sensitivePoints), not baked in |
agent-operating-system (process boundary vs self-modification) | one traditional Worker + DO per tenant; QuickJS inside; Dynamic Workers rejected for v1 |
event-sourced-session-log | chat/*, judge/*, fiber/* events are audit; plugin/* are state |
evaluator-co-evolution | invariants are owned by system plugins; they can evolve only through a code release, never at run time |
Threat model over the extension points (MLAS-style pass)
Stages: Propose (model calls plugin_define), Evaluate (gates/human), Commit (log + blob),
Serve (fiber active).
| Surface | Attack | Where it is stopped |
|---|---|---|
inject: ['judge'] / provide: ['judge'] | reach or replace the judge | trust gate (E) and kernel trust check (S); test agent plugins cannot reach system-only keys |
contributes.gate / invariant | weaken the evaluator | minContributorTrust: system (E, S) |
id collision (judge, http, …) | replace a system fiber | RESERVED_ID (E, S); judge requires agent. prefix |
| forged / replayed authorization | mount without the judge | nonce bound to id+version+code hash, single-use (C) |
code escapes (process, fetch, import) | I/O from agent code | source-lint (E); QuickJS has none of them (S); Node executor strips them (steering only) |
| runaway CPU / memory | denial of service inside the process | QuickJS sliceMs interrupt + memoryLimitBytes; Node executor resourceLimits |
route contribution shadowing /api/* | hijack admin routes | routes match in contribution order (system first); add route to sensitivePoints if agents may add routes; the admin routes need the bearer token regardless |
collection-hook with side effects | emissions outside the effect vocabulary (belief 8) | not undoable by design; hooks run inside collections which catches and logs; keep external effects in system plugins |
prompt contribution | steer the model’s later proposals (prompt injection from a plugin) | recorded and visible in /api/catalog; treat prompt as sensitive if plugins come from untrusted tenants’ agents |
| indirect judge contamination | an agent-contributed collection/tool changes what an invariant reads | invariants should read kernel state (kernel.has, fibers), not collection data — documented rule, not enforced |
| cross-tenant | one tenant’s agent plugin affects another | kernel-per-tenant, separate stores; no shared executor instances |
Open questions (deliberately not solved here)
- Emissions.
activate()code cannot do I/O in QuickJS, but a system-provided service itinvokes might. The vocabulary covers registry effects only; side effects through services are the plugin owner’s problem. A “compensating action” contract for services is future work. - Evaluator co-evolution. Invariants are frozen per release. The vault’s RQGM answer (freeze per epoch, promote against held-out ground truth) would need a second, offline judge.
- The human gate at scale.
needs-humanis a policy on scopes/points. Per-tenant approvers, quorum, and expiry are host concerns to build on/api/approvals. - Regulatory. A self-modifying production system is a substantial modification generator in EU AI Act terms; the event log is the evidence trail. Nothing here claims conformity.
Status (2026-08-28)
Implemented and tested: kernel guarantees (15 tests), both executors incl. interrupt/memory limits, judge
gates/approval/rollback, collections contract on SQLite / Postgres (PGlite) / D1- and Durable-Object-shaped storage,
object storage (S3, R2), node/fastify/cloudflare hosts, the system plugins (admin-api, agent-loop, audit, auth,
automation, collections, http, judge, meta, orm, rbac, rpc, scheduler), createBaseline + createTenantPool, end-to-end prompt → judge → mount →
restart → undo, and a real pnpm add git+…#tag&path: install of the built packages from a scratch clone.
Not run by pnpm test: wrangler/workerd (Cloudflare pieces are tested against structural fakes) and a
network Postgres (PGlite stands in), and the CLI/HTTP model drivers against live providers
(SMS_LIVE_MODELS=1) or live buckets (SMS_LIVE_OBJECTS=1).