DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Execution Environment·Chapter 23

Code Mode, Self-Modification, and Dynamic Plugins

How the model orchestrates tools and can inspect or mount its own runtime

VerifiedUpstream 47f943859bScope: Analyze run_code transport, worker runtimes, serialized subcalls, Cordis inspect/define, VM boundaries, and disposal.

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

Most important security conclusion

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

modeTool schemas sent to the modelExecution boundary
nativeNative tools visible in the current scopeNative tools are directly callable; no CodeRuntime required
codeOnly run_code, plus a generated SDKA model-direct call to any other name gets UNKNOWN_TOOL; nested calls can reach visible tools
bothNative tools + run_code + SDKBoth 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

Static boundary: the language is not bound to one request

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

LimitDefaultWhat it actually constrains
computeMs60,000 msThe worker's own event-loop busy time; waiting on a slow tool is not charged
maxWallMs600,000 msA wall-clock ceiling that never pauses
maxOutputBytes67,108,864 bytesCombined JSON size of logs plus the final value or failure diagnostic
maxOldGenerationSizeMb512 MiBThe 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

Static observability boundary

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 layerWhat the program/model seesConvergence action
Nested tool rejection, policy denial, unknown toolA ToolCallError rejection in the program; it may catch and continueThe sub-call still completes post/log/context processing
Program exception / invalid outputOuter CODE_RUN_FAILED with kind, message, and captured logsAbort the run-scoped signal; drain started calls and log work
Busy/wall timeout, outer abort, OOM/worker exit, output limitA distinct failure kind rather than a generic exceptionTerminate the worker; discard late binding resolution
Runtime contract misuseA run() rejectionOuter 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 invariantHow the fixture proves it
native/code/both schemas, SDK, and code-only instruction agreeAssemble every mode and check prompt-section order
Safe calls overlap, exclusive calls form full barriers, and the cap holdsGated tools expose live/pending counts before release
Pre/post, contexts, and settlement follow submission order; run close drainsArtificially delay policy, post, and runtime settlement
Policy denial, lossy JSON, tool errors, and outer abort fail closedAssert program rejection, Session events, and final result
The worker survives forged port traffic, timeout, OOM, and late repliesInject 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

StageToolEffect
Discovercordis_inspect_listList Host/Client Inspect Providers, methods, and schemas
Querycordis_inspect_queryRun a provider-declared read-only query
Self-inspectcordis_inspect_selfProgressively read this Session's Plugins, Packages, source, and diagnostics
Definecordis_defineAppend an immutable Package; do not run it
Activatecordis_runStart run/update for one exact Package
Stopcordis_stopRetract live effects while retaining definitions, grants, and version pointers
Deletecordis_undefinePermanently 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

The most easily misread recovery semantic

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

FaceAccidental-misuse controlsWhat it cannot promise
HostFresh node:vm realm; process/Buffer absent; require/fetch/timer traps; declared-service ctx facadeHost-realm helpers permit escape; async bodies escape vmTimeoutMs
Clientnew Function closure parameters shadow ambient globals; React/styles/host.call are explicit; declared-service facadeNot a browser security realm; code is as trusted as the Host that accepted it
ServicesUndeclared properties, framework internals, and Context-valued returns are denied; tools expose controlled register/schema onlyAllowed 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

Control-plane isolation is not effect isolation

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

ProvenKey assertion
Define-only behavior, syntax precheck, Session ownershipBad source never enters the registry; another Session sees absence
Host-only path, approval, rejection, asynchronous Client failureState and identity stay exact; failure retracts only the run it started
Context/tool escape closurectx.root, Context returns, undeclared services, and raw execute are denied
Version failure semanticsCurrent is preserved, next/attempt remain diagnosable, and an attaching-page failure does not stop an existing Host run
Client replace/serialize/recover/disposePer-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

FindingClassImpact / recommendation
Prompt assembly and execution do not bind one runtime languageFuture multi-backend hot-swap windowBind runtime/generation to the request before a second backend ships
Binding resolutions are outside the outer output byte capMemory and transport capacity boundaryTools should paginate/reduce; consider a per-binding cap if needed
Asynchronous listeners can reorder nested settle-log appendsObservability boundaryPair by subCallId; do not infer completion order from event sequence
Host vmTimeoutMs covers synchronous work onlyTrust boundaryRun trusted code only; do not treat the VM as a hostile sandbox
Update failure preserves the old current pointer, but the old run is stoppedRecovery-semantic trapUI/Agent should state that an explicit rollback run is required
tool-cordis README still describes five old tools, old cordis_inspect, and ackTimeoutMsConfirmed documentation driftTreat 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

Architecture judgment

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.

Validation status

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.