Code Mode, Self-Modification, and Dynamic Plugins
How the model orchestrates tools and can inspect or mount its own runtime
Conclusion: Code Mode is governed tool orchestration; dynamic Cordis is temporary runtime self-modification
DeepSeek Harness does not treat “write some code” as a shortcut to broader authority. Code Mode changes only how the model sees and invokes tools: the model submits one run_code, while every tools.* call inside the program returns to the original ToolRuntime and remains subject to the same agent visibility, approval, guards, tool sandbox, and result normalization. Dynamic Cordis is a separate, explicitly opt-in control plane: the model can inspect, define immutable Packages, request activation, and let a temporary Plugin register tools, services, events, or browser UI in the current process. The former is orchestration within one call; the latter is a process-local extension across turns. They are not the same kind of “self-modification.”CODEMODE-MODESCODEMODE-POLICYCODEMODE-CORDIS-PROMPT
Both paths explicitly refuse to present their execution substrate as a security boundary. The worker-thread runtime assigns model code bash-equivalent trust; the dynamic Host VM admits that host-realm helpers are an escape route. These restrictions provide lifecycle convergence, API discipline, and accidental-misuse reduction—not safe execution of hostile code.CODEMODE-WORKER-TRUSTCODEMODE-HOST-SANDBOX
1. Start with the shipped composition: CodeRuntime is mounted, but Code Mode and self-modification are still explicit choices
The headless bundle mounts the worker-thread CodeRuntime by default and maps tools.mode to DSH_TOOLS_MODE; the Web bundle uses the same temporary process-wide switch. When the environment variable is unset, the ToolRuntime schema still defaults to native. “A runtime exists” therefore does not mean “the model is using Code Mode.”CODEMODE-HEADLESS-COMPOSITIONCODEMODE-WEB-COMPOSITION
The model-facing self-modification surface is narrower still: the advanced headless overlay explicitly adds CodeRuntime, the Cordis Host runner, and tool-cordis together. Self-modification is not an automatic companion to Code Mode; the deployment must deliberately place that toolset in the composition.CODEMODE-SELF-MOD-OPTIN
2. Three presentation modes and two enforcement points
| mode | Tool schemas sent to the model | Execution boundary |
|---|---|---|
native | Native tools visible in the current scope | Native tools are directly callable; no CodeRuntime required |
code | Only run_code, plus a generated SDK | A model-direct call to any other name gets UNKNOWN_TOOL; nested calls can reach visible tools |
both | Native tools + run_code + SDK | Both call forms are legal |
The deployment default, a preset, or an agent scope can choose presentation; the nearest scope wins, and fiber disposal removes the declaration. run_code is also reserved transport outside the registration layers: restrictions cannot remove it and a scoped registration cannot shadow it.CODEMODE-SCOPECODEMODE-TRANSPORT
The security-critical property is not prompt prose but the same collapse at both wire and execution boundaries. code sends only the run_code schema; at execution, only a nested dispatch carrying the opaque parent token may cross the collapse. The generated SDK also excludes run_code, so a program cannot recursively create another transport layer.CODEMODE-WIRECODEMODE-EXECUTION
3. run_code is language-aware transport, not a promise of a general code interpreter
run_code takes two required strings: code, the body of an async function, and a non-empty description used as the UI title. It returns only ordered logs and an optional JSON result; an empty result receives a fixed placeholder.CODEMODE-RUN-SCHEMA
The tool layer has TypeScript and Python schema/SDK renderers, but the CodeRuntime seam explicitly states that only TypeScript has a published backend today. language and isolation are presentation/diagnostic descriptors, not compatibility or security proofs. A missing runtime or unknown language fails loudly during prompt assembly.CODEMODE-RUNTIME-DESCRIPTORSCODEMODE-WIRE
Assembly and execution read live ctx.codeRuntime independently. This makes no observable difference while only one published backend exists. If future HMR swaps to a different language between those reads, however, the model could write against the old SDK and execute on the new runtime. The source records this window explicitly and defers the fix until a second backend exists.CODEMODE-RUNTIME-READS
4. Worker runtime: a fresh worker per run, four budgets, and one terminal outcome
| Limit | Default | What it actually constrains |
|---|---|---|
computeMs | 60,000 ms | The worker's own event-loop busy time; waiting on a slow tool is not charged |
maxWallMs | 600,000 ms | A wall-clock ceiling that never pauses |
maxOutputBytes | 67,108,864 bytes | Combined JSON size of logs plus the final value or failure diagnostic |
maxOldGenerationSizeMb | 512 MiB | The worker's old-generation heap |
TypeScript first undergoes erasable type stripping on the Host. Syntax failure or non-erasable syntax such as enum starts no worker. The normal path creates a fresh worker with env:{}, execArgv:[], a heap resource limit, and captured stdout/stderr.CODEMODE-WORKER-LIMITSCODEMODE-WORKER-LIFECYCLE
The program runs as a strict AsyncFunction body whose explicit parameters are binding namespaces, their typed error classes, and console. Whether completion, timeout, abort, output overflow, worker error, or exit occurs, the Host permits only one terminal outcome and terminates the worker plus drains output pipes before resolving. Runtime disposal likewise aborts and awaits every live worker.CODEMODE-WORKER-BOOTCODEMODE-WORKER-LIFECYCLECODEMODE-WORKER-LIMITS
5. The data boundary is lossless JSON; the outer output cap does not cover intermediate tool results
The CodeRuntime seam requires binding arguments, binding resolutions, and program completion to be lossless JSON. The worker wire encodes values as a flat token stream and decodes them iteratively, so deep structures do not turn the JavaScript call stack into an implicit limit. Malformed, sparse, cyclic, exotic, non-finite, or incomplete values fail closed.CODEMODE-SEAMCODEMODE-FLAT-JSON
The worker is treated as a hostile peer. The Host revalidates message shape, performs own-property binding lookup, ignores duplicate IDs and junk messages, and snapshots binding resolutions again. The worker likewise snapshots arguments before posting a call. Neither side falls back to lossy “best-effort stringify.”CODEMODE-WORKER-BRIDGECODEMODE-WORKER-BOOT
6. Nested tools inherit the same runtime governance, not the worker's authority
model → run_code (outer ToolExecution)
→ worker program calls tools.read(...)
→ parent token + same agent + same run signal
→ ToolRuntime.prepare
→ pre-execute → approval → monotonic guards
→ ToolRuntime.dispatch
→ around wrapper → real tool body → its own sandbox/provider
→ ordered post/finalize → JSON value back to program
At program start, the bridge builds the tools namespace from the calling agent's currently visible schemas and excludes run_code. Each call then returns to staged ToolRuntime with the same agent, outer rootCallId, opaque parent token, and run-scoped signal.CODEMODE-BINDINGSCODEMODE-NESTED-DISPATCH
parent means only “this call came from inside the transport,” allowing it past the code model-direct collapse. It does not bypass restrictions, approval, guards, around wrappers, a tool's filesystem/shell sandbox, or post-policy. Nor does the worker itself “inherit a filesystem sandbox”: it can request only bindings, and real side effects occur in each tool's provider world.CODEMODE-EXECUTIONCODEMODE-POLICY
7. Every run_code owns a bounded scheduler
The default maxParallelSubCalls=10 applies to one program, not as a process-wide semaphore. The driver maintains a pending queue, in-flight set, commit queue, and exclusive flag. Concurrency-safe calls submitted by one program may overlap; an exclusive tool must wait for the pool to drain, run alone, and retain its barrier through post/commit.CODEMODE-MODESCODEMODE-SCHEDULER
Only around-dispatch/body work overlaps. Start events, pre-execute/approval, post/finalization, context deferral, and binding settlement all occupy one ordered lane, while the commit cursor advances in submission order. A tool replaced while queued is reclassified before starting. If it is unloaded after binding enumeration but before invocation, real dispatch resolves the live registry and returns an unknown-tool failure.CODEMODE-SCHEDULERCODEMODE-NESTED-DISPATCH
8. Two history layers: the model sees the curated outer result; the Session retains the nested trajectory
Program console output and the top-level return form the outer run_code result, and only that result enters later model history. Every internal call separately writes tool/code-dispatch-start and tool/code-dispatch, correlated by exact rootCallId, parentCallId, and subCallId, with an argument snapshot detached from the actual dispatch value.CODEMODE-HISTORYCODEMODE-NESTED-DISPATCH
A nested result's additionalContexts are deferred onto the outer execution during ordered commit. They can therefore survive even if the program later fails. A successful nested result's concludesTurn also propagates outward.CODEMODE-NESTED-DISPATCH
During ordered commit, the binding resolves to the program before asynchronous shapeDispatchLog → session.append work runs; all log work is guaranteed only to drain before the outer run closes. If an earlier listener is slower than a later one, settle-event append order can differ from binding submission order. Reconstruction should pair by subCallId, not treat nested event sequence as canonical body-completion order.CODEMODE-NESTED-DISPATCHCODEMODE-SCHEDULER
9. Failure and recovery: programs can catch tool errors; runtime failures close the outer call
| Failure layer | What the program/model sees | Convergence action |
|---|---|---|
| Nested tool rejection, policy denial, unknown tool | A ToolCallError rejection in the program; it may catch and continue | The sub-call still completes post/log/context processing |
| Program exception / invalid output | Outer CODE_RUN_FAILED with kind, message, and captured logs | Abort the run-scoped signal; drain started calls and log work |
| Busy/wall timeout, outer abort, OOM/worker exit, output limit | A distinct failure kind rather than a generic exception | Terminate the worker; discard late binding resolution |
| Runtime contract misuse | A run() rejection | Outer ToolRuntime still materializes a structured tool error |
The outer signal triggers the run-scoped abort. Once the program settles for any reason, the bridge rejects queued-unstarted calls, lets started bodies reach quiescence, and awaits durable nested-log work. The outer turn therefore does not leave background sub-calls that can continue producing side effects.CODEMODE-SEAMCODEMODE-BINDINGSCODEMODE-WORKER-BRIDGE
10. Code Mode test invariants
| Pinned invariant | How the fixture proves it |
|---|---|
| native/code/both schemas, SDK, and code-only instruction agree | Assemble every mode and check prompt-section order |
| Safe calls overlap, exclusive calls form full barriers, and the cap holds | Gated tools expose live/pending counts before release |
| Pre/post, contexts, and settlement follow submission order; run close drains | Artificially delay policy, post, and runtime settlement |
| Policy denial, lossy JSON, tool errors, and outer abort fail closed | Assert program rejection, Session events, and final result |
| The worker survives forged port traffic, timeout, OOM, and late replies | Inject hostile messages and resource failures directly |
CODEMODE-TEST-PRESENTATIONCODEMODE-TEST-SCHEDULERCODEMODE-TEST-POLICY-FAILURECODEMODE-TEST-WORKERCODEMODE-TEST-WORKER-HOSTILE
11. The actual dynamic Cordis model surface has seven tools
| Stage | Tool | Effect |
|---|---|---|
| Discover | cordis_inspect_list | List Host/Client Inspect Providers, methods, and schemas |
| Query | cordis_inspect_query | Run a provider-declared read-only query |
| Self-inspect | cordis_inspect_self | Progressively read this Session's Plugins, Packages, source, and diagnostics |
| Define | cordis_define | Append an immutable Package; do not run it |
| Activate | cordis_run | Start run/update for one exact Package |
| Stop | cordis_stop | Retract live effects while retaining definitions, grants, and version pointers |
| Delete | cordis_undefine | Permanently remove the Plugin and all Packages |
The surface deliberately separates inspect, define, and execute. cordis_define only validates parameters, ownership, and syntax, then stores source; it does not itself modify the repository, configuration, or disk, and it triggers no Plugin effects. Actual capability exists only after cordis_run and is determined by the live services the Package obtains.CODEMODE-CORDIS-INSPECTCODEMODE-CORDIS-DEFINECODEMODE-CORDIS-LIFECYCLE-TOOLS
12. Inspect is runtime schema discovery, not a business-API proxy
inspect_list returns the provider/method/schema directory; inspect_query calls only read-only methods declared there. A Host query validates input, executes locally, and validates output again. A Client query publishes an agent-identified request and waits for the first valid same-Session page response or cancellation. An invalid page response cannot claim the query.CODEMODE-CORDIS-INSPECTCODEMODE-INSPECT-REGISTRY
inspect_self uses progressive disclosure: no IDs returns only Plugin summaries; pluginId returns Packages, version pointers, and the latest run; only pluginId plus packageId returns exact immutable source and Host/Client diagnostics. The tool shape enforces “read current facts before repair” instead of leaving that discipline to prompt hope.CODEMODE-CORDIS-INSPECT
13. Three identities and two version pointers
Plugin (pluginId, session owner)
├─ Package A (packageId, immutable host/client source)
├─ Package B (packageId, immutable host/client source)
├─ currentPackageId → last fully successful Package
├─ nextPackageId → in-progress or most recently failed target
└─ active/latest Run (pluginRunId, mode, Host/Client diagnostics)
pluginId names the logical object that can evolve; packageId names an immutable source version; pluginRunId names one activation attempt and ties together approval, Host/Client loading, private handlers, and errors. The registry separately stores per-Package and future-version grants.CODEMODE-REGISTRYCODEMODE-CORDIS-PROMPT
Define-new mints an ID from a semantic prefix. Define-existing must find the same Session owner, then always mints a new packageId and appends it. Old source is never overwritten in place, and there is no hidden path that edits a running function body.CODEMODE-DEFINE-RUNNER
14. Host-only and Client-bearing Packages have different activation contracts
A Host-only Package starts within the same call and returns running on success. A Package with a Client half and no grant creates an approval request and immediately returns awaiting-approval; an authorized version returns starting without pretending browser activation has succeeded.CODEMODE-RUN-APPROVALCODEMODE-CORDIS-LIFECYCLE-TOOLS
The page strictly performs Host half → Client source for the exact active run → browser load → settlement. Request, Package, and Run identities must all match. The first valid response claims the pending request, while unknown, duplicate, or stale responses return accepted:false.CODEMODE-CLIENT-ORCHESTRATIONCODEMODE-APPROVAL-SETTLE
Final success, user rejection, or technical failure occurs after the original tool call and returns to the owning Agent through steering context. Failure carries current/next pointers and directs repair on the same Plugin; rejection explicitly forbids automatically asking for the same approval again.CODEMODE-OUTCOME-STEERING
15. “Preserve current after failure” is not automatic runtime rollback
An update first await retract(old run), then starts the target Package. Host-only success can commit immediately. A Client-bearing version remains client-pending until successful page settlement, and only commitActivation then points currentPackageId to the new version and deletes nextPackageId.CODEMODE-ACTIVATION
When the target fails, the old currentPackageId is indeed preserved for explicit rollback, but the old run was already stopped at update start and is not restarted automatically. Pointer rollback and live-effect rollback are different operations. Recovery must explicitly run current again or repair next and retry update.CODEMODE-CORDIS-PROMPTCODEMODE-TEST-VERSIONING
16. How a dynamic tool enters Code Mode: the next assembly and the next run snapshot
A Host Package cannot hand an arbitrary object to the registry. It must pass through harness.defineTool, which validates parameter schema, output renderer, and execute, then JSON-normalizes cross-realm schema/result/presentation data. registerTool accepts only definitions carrying the internal marker. Guarded ctx.tools.get returns only a schema view, never live execute that could bypass the policy pipeline.CODEMODE-DYNAMIC-TOOL-GUARD
After registration, the next prompt assembly rebuilds the SDK from the agent's live visible set; the next run_code builds a fixed tools namespace from the schemas visible at that moment. An already-running program does not gain a new property. Conversely, if a tool is stopped/unregistered after namespace enumeration but before invocation, the binding name remains, yet dispatch re-resolves the live registry and fails as unknown tool.CODEMODE-SDK-LIVECODEMODE-BINDINGSCODEMODE-TEST-SCHEDULER
17. A dynamic Package “sandbox” is a capability facade, not hostile-code isolation
| Face | Accidental-misuse controls | What it cannot promise |
|---|---|---|
| Host | Fresh node:vm realm; process/Buffer absent; require/fetch/timer traps; declared-service ctx facade | Host-realm helpers permit escape; async bodies escape vmTimeoutMs |
| Client | new Function closure parameters shadow ambient globals; React/styles/host.call are explicit; declared-service facade | Not a browser security realm; code is as trusted as the Host that accepted it |
| Services | Undeclared properties, framework internals, and Context-valued returns are denied; tools expose controlled register/schema only | Allowed services connect to the real runtime; authority depends on composition |
Host source receives compile-only prechecks at define time and executes as an async body at run time; vmTimeoutMs surrounds only synchronous evaluation. The Host facade preserves reversible lifecycle verbs and declared services while withholding framework internals such as root, fiber, and raw registries.CODEMODE-HOST-SANDBOXCODEMODE-HOST-CODECODEMODE-CONTEXT-GUARD
The Client half is not a module bundle either. It is a plain-JavaScript async body explicitly receiving React, console, styles, host.call, and trap functions. Its ctx twin also gates services through inject and adds ownership guards for slots/theme.CODEMODE-CLIENT-EVALCODEMODE-CLIENT-GUARD
18. The lifecycle invariant is not merely “can load,” but “reaches quiescence after stop”
Host retraction first removes the active run from the registry, unregisters package-private handlers, then await fiber.dispose(), and finally broadcasts a retract event carrying exact Plugin/Package/Run identity. Listeners, tools, services, timers, and other effects registered through fiber-aware APIs therefore unwind with their owner.CODEMODE-RETRACT
The browser runner serializes load/unload per pluginId. A new revision tears down the old entry first; exact-run retraction cannot delete a newer activation; one failure is contained in the queue tail so a later repair can continue; runner disposal unloads every page Package.CODEMODE-CLIENT-LIFECYCLE
Session ownership protects Plugin define/run/stop/inspect actions, but a Host Package hangs under a process-local root group and can use real services granted by the composition. Ownership prevents another Session from manipulating the record; it does not by itself prove every registration or service side effect is confined to the owning Session. Concrete scope still belongs to the capability being used.CODEMODE-REGISTRYCODEMODE-RETRACTCODEMODE-CONTEXT-GUARD
19. Self-modification lives only in process memory; @pluginId is not source persistence
The registry, ID counters, Package source, grants, current/next pointers, and active run all live in process memory. Definitions disappear on process restart. cordis_define writes no repository/configuration/disk state and performs no automatic promotion. A durable implementation still requires normal code and configuration changes.CODEMODE-CORDIS-PROMPTCODEMODE-REGISTRY
An exact @pluginId in a user message injects source-free identity/version context at pre-step and explicitly requires cordis_inspect_self with pluginId+packageId before appending a new Package. Non-user-source messages do not trigger it. A removed, cross-Session, or restart-lost ID becomes unavailable rather than silently creating a replacement Plugin.CODEMODE-REFERENCE
cordis_stop is reversible deactivation: it cancels pending approval and retracts the live run while retaining definitions, grants, and version pointers. cordis_undefine permanently removes the record. Neither operation creates anything recoverable at the next process start.CODEMODE-CORDIS-LIFECYCLE-TOOLSCODEMODE-RETRACT
20. Dynamic self-modification test invariants
| Proven | Key assertion |
|---|---|
| Define-only behavior, syntax precheck, Session ownership | Bad source never enters the registry; another Session sees absence |
| Host-only path, approval, rejection, asynchronous Client failure | State and identity stay exact; failure retracts only the run it started |
| Context/tool escape closure | ctx.root, Context returns, undeclared services, and raw execute are denied |
| Version failure semantics | Current is preserved, next/attempt remain diagnosable, and an attaching-page failure does not stop an existing Host run |
| Client replace/serialize/recover/dispose | Per-Plugin ordering, exact retract, and failed loads do not wedge the queue |
CODEMODE-TEST-DYNAMIC-RUNNERCODEMODE-TEST-DYNAMIC-GUARDCODEMODE-TEST-VERSIONINGCODEMODE-TEST-CLIENT-LIFECYCLE
21. Static findings and documentation drift
| Finding | Class | Impact / recommendation |
|---|---|---|
| Prompt assembly and execution do not bind one runtime language | Future multi-backend hot-swap window | Bind runtime/generation to the request before a second backend ships |
| Binding resolutions are outside the outer output byte cap | Memory and transport capacity boundary | Tools should paginate/reduce; consider a per-binding cap if needed |
| Asynchronous listeners can reorder nested settle-log appends | Observability boundary | Pair by subCallId; do not infer completion order from event sequence |
Host vmTimeoutMs covers synchronous work only | Trust boundary | Run trusted code only; do not treat the VM as a hostile sandbox |
| Update failure preserves the old current pointer, but the old run is stopped | Recovery-semantic trap | UI/Agent should state that an explicit rollback run is required |
tool-cordis README still describes five old tools, old cordis_inspect, and ackTimeoutMs | Confirmed documentation drift | Treat the seven-tool production source as authoritative and update README/generated links |
CODEMODE-RUNTIME-READSCODEMODE-SEAMCODEMODE-NESTED-DISPATCHCODEMODE-HOST-CODECODEMODE-ACTIVATIONCODEMODE-README-DRIFT
22. Final assessment
Code Mode's strongest design choice is reducing “the model writes a program” to a presentation and composition technique. The capability set, approvals, and tool sandboxes are neither copied into the worker nor bypassed; nested dispatch reuses the single ToolRuntime. Dynamic Cordis's strongest choice is decomposing self-modification into inspect → immutable define → identified activation → reversible stop, with browser approval and asynchronous failure represented as explicit state. Their shared weakness is naming that invites overestimating isolation: worker, node:vm, and browser closure all provide trusted-code containment/API discipline.
This chapter traces ToolRuntime, the run_code bridge, CodeRuntime seam, worker bootstrap/transport, Cordis inspect/registry/Host/Client runners, guards, composition, and tests line by line at the pinned upstream commit. Targeted Vitest covered 14 files, and all 376 tests passed. The run confirms the current presentation, scheduling, failure, approval, versioning, guard, and teardown invariants; the language-binding, nested-log ordering, binding-capacity, and asynchronous-VM boundaries above remain static source findings rather than claimed regression proofs.
My Learning Notes
Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.