← Thinking Thinking

Laws Before Code: DeepSeek Harness and Composability-First Design

A theory-first distillation of composability: one meta-constraint, eight dimensions, twelve design theses, five families of portable laws. DSH does not…

2026-08-18Thinking55 min read

When any two plugins meet, who decides? Most frameworks answer: "check the registration order." DeepSeek Harness answers with a set of laws you can write into a code-review checklist.

This is the third piece in the DSH series. The first covered the competitive landscape; the second dissected the engineering structure of the source tree. This one answers a more fundamental question: when we call a framework "plugin-based," what exactly are we demanding? Agent systems fear this question more than traditional software does, because an agent's workload is composition-dense by nature—loops, tools, policies, memory, sandboxes, UI. Every layer invites outside hands, and every hand can step on another.

Prologue: Three Failure Modes of Compositional Runaway

Late on the night of August 13, DeepSeek open-sourced Harness under MIT. Within three days stars hit 39.7K. Beneath the noise, a quieter fact deserves more attention: in this 219-package repository, how any two third-party components compose has an actual answer. Most systems that claim to be plugin-based compose by luck.

Start with three common failure modes. These are not straw men—they are the walls almost every plugin-based system hits in its second year.

Failure mode one: hooks arm-wrestling. Two extensions both need to modify the same request: one injects retrieval results into the context, the other redacts sensitive information. The framework provides priority-ranked hooks, so the executed outcome depends on registration order. If redaction runs first, injected content slips past it; if injection runs first, users report "after redaction the model can no longer see key information." The maintainer's answer: "try adjusting the load order." This class of issue never finishes closing, because the conflict has no semantics, only timing. Switch machines, change one config, and the result flips. Whoever fixes the bug can only pray the reproduction environment doesn't change.

Failure mode two: deep-merge hell. Configuration has four layers: framework defaults, distribution presets, the user directory, and command-line arguments. After a deep merge, no one can mechanically answer which layer a field's final value came from. You changed it and nothing happened, because there was one more layer above your three; you changed nothing and it changed anyway, because an upstream default quietly shifted. Engineer hours spent debugging configuration grow linearly with layer count, linearly with field count—and the two multiply. Most platforms do not die of missing features; they die when configuration can no longer be reasoned about.

Failure mode three: nondeterministic teardown order. Plugin A takes a handle to plugin B during registration; at unload, B leaves first, and A's cleanup code hangs in mid-air. The more frequent the hot reloads, the more visible the leaks. Worse, it doesn't reproduce: timing-dependent problems never leave a stable scene, so the report can only say "leaks occasionally; restarting fixes it."

The three failure modes share one name: compositional runaway. Each plugin is individually correct; nobody owns the combination. In traditional software, compositional runaway is a maintenance-cost problem; in agent systems it is a security problem—the behavioral gaps that policy plugins, permission plugins, and tool plugins compose into are precisely the channels for privilege escalation.

Distinguish two words. Configurable means many switches; composable means the combination of any switches obeys laws. A hooks system is configurable: when two hooks conflict, the outcome depends on registration order and luck. Waterfall plus a monotonic guard is composable: under any combination of listeners, the outcome is still given by laws. This distinction is the key to reading DSH's architecture, and the dividing line between a real agent platform and a monolith with a shell.

Configurable versus composable
Configurable versus composable

One Meta-Constraint, Eight Dimensions

Every key design in the DSH source shares a single constraint:

The composition of arbitrary third-party components must remain predictable, without relying on "this particular combination having been designed by the core team."

Two roads lead away from this constraint. The traditional harness shrinks the composition space: a monolithic core plus limited hooks, and third parties who want to change core behavior go through review, PRs, and core-team arbitration. This works while the ecosystem is small; once it grows, the arbitration queue bursts first. Extenders stop waiting and simply fork, and the ecosystem fragments. DSH takes the other road: algebrize every composition dimension. Each dimension carries explicit composition laws, the semantics of any combination are computable, and the core team never vets combinations one by one.

Eight dimensions, one table to see them all:

Dimension Common industry practice DSH mechanism Composition law
Lifecycle init/dispose callback pairs effect stack Undo order mirrors registration order
State Mutable objects plus side-channel logs event log Session semantics = fold(log)
Interception Priority hooks waterfall Wrapping is associative; vetoes are absorbing
Policy Symmetric allow/deny chains monotonic guard deny is the absorbing element
Capability Direct module imports Definition/Provider/Consumer seam Provider replacement = a configuration change
Execution Hardcoded local execution world Tools are pure consumers of the world
Configuration Deep merge patch algebra Whole-row replacement; layer order is decidable
Trust Boundaries plus roles per-call capability Permissions attenuate per call

The table is an index; four rows deserve their mechanisms spelled out.

On the lifecycle row, the effect stack runs one layer deeper than "callback pairs": every registration a plugin performs in its context—subscribing to events, starting timers, spawning subprocesses—pushes the corresponding undo action onto the stack. Unloading is a stack rollback: reverse order, idempotent, with a well-defined intermediate state even when startup fails halfway. Failure mode three structurally cannot exist: teardown order is always the mirror of registration order, and there is no such thing as "A's cleanup dangling while it waits for B to leave."

On the state row, fold is a mathematical word with a plain meaning: current state is not stored in a mutable object but "replayed" out of the log from start to finish. Each appended event makes state the cumulative projection of all events. Want to edit history? That operation does not exist: appending new events is possible; mutating old ones has no entry point. It sounds heavy; in practice it is incremental—projection caches keep replay cost proportional to the number of new events alone.

On the policy row, the crux of the monotonic guard is that the verdict space holds exactly two values: keep the status quo, or deny. With multiple guards participating, the verdict equals "does a deny exist," independent of order, duplication, and count. The act of overriding a denial has no expression in this verdict space: it is not forbidden—it cannot even be expressed.

On the configuration row, patch algebra means configuration is not a tree merged and re-merged but a table of rows. Each layer has exactly two actions: insert a new row, or replace an existing row wholesale by id. The final state is the fold of the empty table in layer order. To know where a field's value comes from, find the last layer that touched the row; the answer is unique and mechanically decidable.

Being honest about the boundary: six of the eight dimensions are formalized into checkable laws, corresponding to the six E/L/W/G/S/P families in the source, each with implementation anchors. The execution and trust dimensions stop at the mechanism level—mechanisms exist, safe defaults exist, but formalized guarantees do not. That is not a defect; it is precision: review checklists can follow formalization wherever it reaches, and the parts that are not formalized do not pretend to carry guarantees.

One further proposition deserves separate mention. DSH's session state and model-visible input equal a function of four parameters: the activated plugin set, the patch sequence, the event-log prefix, and the scope tree. The external world—filesystem, network, credential resolution—is explicitly declared as free variables. The three kinds of noise that make traditional systems hard to debug are structurally eliminated in this design: no implicit global state (all state is a projection of the log); no unaccounted registration (to register is to push onto the stack, replayable); no out-of-order commits (tool results are committed in model order, and on abort, calls that never started get synthesized error results, so the log can always reconstruct what the model saw).

The way to test this proposition is equally unforgiving: resume and replay share the same validation path, and a session constructed from the log can necessarily be persisted again. You cannot build a session that "only lives in memory." For teams running evaluations and controlled experiments, the weight of that sentence is self-evident.

Twelve Theses, Four Axes

DSH's design positions condense into twelve theses. More important than the list is where they come from: each thesis is a trade-off taken along one stress axis, and agent platforms cannot escape four of them.

The time axis asks what changes and at what cadence. The state axis asks what must be auditable and replayable. The interaction axis asks whether order still counts when multiple parties participate. The trust axis asks who decides across boundaries, and what to do when you don't know. The twelve theses sit on the four axes: T1 the loop is a plugin, T7 execution is a world, and T11 capability through the seam, on the time axis; T2 facts are immutable, T3 registration is an effect, and T8 configuration is an algebra, on the state axis; T4 interception has exactly one verb, T5 policy must be monotonic, and T12 composition pruned by scope, on the interaction axis; T6 capability travels per call, T9 the type graph extends to the wire, and T10 honesty over completeness, on the trust axis.

Six of them, expanded.

T1: The loop is a plugin. Rationale: the biggest variable in the agent domain is the loop itself—reasoning paradigms iterate monthly, from single-step tool calls to multi-agent orchestration to probabilistic planning, and every paradigm shift rewrites the loop. Implementation: DSH makes the agent loop a plugin, and the official architecture doc states plainly that "changing the loop means updating the architecture doc"—loop replacement is a supported first-class operation, not a hack. The loop kernel reduces to a state-machine skeleton with all policy externalized. Cost: the framework tax comes due before product validation; for teams whose loop strategy has already converged, this abstraction is pure overhead. Signal: an 18-month-plus product horizon with an unconverged loop strategy makes it worth it; a one-off delivery does not.

T2: Facts are immutable; views are derivable. Rationale: the hardest problem in LLM systems is behavioral attribution—why the model did that. Attribution requires history that cannot be tampered with and views that can be redefined. Implementation: the session log is deep-frozen on append; context compression replaces only the "surface projection"—the view layer the model sees—while the raw record stays replayable forever. Audit, replay, fork, and comparison views are all guaranteed by this one structure, no extra machinery required. Cost: storage for full persistence, mitigated by zstd compression, and projection consistency that needs ongoing maintenance. Signal: adopt in systems that need audit, replay, or controlled experiments; purely forward-only conversations can degrade to mutable history plus backups.

T4: Interception has exactly one verb. Rationale: multiple interception verbs inevitably arm-wrestle when combined—the root of failure mode one. Implementation: waterfall is the sole interception mechanism; listeners wrap the next chain, not calling next is a veto, and a veto is a first-class behavior rather than an exception path. There are four interception points: before a step (rewrite or veto the step), before the request (modify the request), on the stream (wrap streaming output), and around tool execution (full lifecycle). The loop core carries zero policy branches. The payoff: when third parties need to change core behavior, the core team never arbitrates priorities. Cost: interception is less expressive than editing the core directly, and writing async waterfalls carries a mental cost. Signal: adopt when third-party extensions need to change core behavior; with first-party extensions only, hooks suffice.

T5: Policy must be monotonic. Rationale: multi-party decisions must be independent of participation order, and mathematically a lattice plus an absorbing element is the minimal order-independent construction. Implementation: before tool execution, a guard has exactly two choices—keep the status quo, or give a denial reason. The key is where this lives: the decision type has no allow constructor. That is not a discipline convention; a violation cannot even be expressed at the type level. How multiple guards' denial reasons are presented follows explicit rules, but a reason is merely the verdict's payload and never changes the verdict itself. The cost is just as concrete: this cannot express an override. What a security guard denies, a later user allow cannot reverse. Such needs must travel through new events—an approval chain produces new decided facts; the law is not amended. The law's boundary is itself designed. Signal: mandatory with three or more policy sources (user, team, security); optional with a single source.

T8: Configuration is an algebra, not a merge. Rationale: under deep merge, the provenance of final values is undecidable—the root of configuration hell. Implementation: patch has exactly two operations, insert a new row or replace a row wholesale by id, and system state is the fold of the empty table in layer order. "I changed it and it didn't take" becomes an answerable question: find the last layer that touched the row. Layer swaps in the loader are transactional—the new layer imports successfully before the old one is disposed, and failures roll back—so hot reload never leaves half-state. Cost: no partial-field inheritance; child layers must write whole rows. Signal: adopt at three or more configuration layers.

T10: Honesty over completeness. Rationale: trust boundaries are held up by "no pretending"—a partial masquerading as full is more dangerous than a missing feature. Implementation: sandbox backends self-report full or partial, and the Windows ACL sandbox is honestly labeled partial; approvals deny when nobody answers; capability facts drive the schema, so the tool surface the model sees is determined by capabilities that actually exist—if the sandbox isn't there, the model doesn't see sandbox parameters and never gets the chance to wish for parameters that don't exist. Cost: the feature list looks shorter. Signal: mandatory for production and compliance settings.

The remaining six close out in one table; the formula is the same throughout: rationale, implementation, cost, signal.

Thesis One line Adoption signal
T3 Registration is an effect Rollback under partial failure must be well-defined You need hot reload or dynamic loading
T6 Capability travels per call Permission attenuation follows the call chain, not the principal Agent chains cross permission domains
T7 Execution is a world Going remote collapses to N+M, not N×M You run execution both locally and remotely
T9 The type graph extends to the wire Interface, types, and wire protocol are one Multiple clients share one host
T11 Capability through the seam Partition modules around "what will change" Two or more candidate implementations exist
T12 Composition pruned by scope Inheritance and isolation solved with one primitive Multiple agents in one process

The four axes also work as a generator. Faced with a new design question, first ask which axis it presses on and which thesis it pushes against from behind. Example: "the approval flow needs majority voting." Majority voting is a non-monotonic aggregation and conflicts with T5. Either fold the vote into a single decided event (DSH's approach), or know clearly that you are leaving T5's zone of protection. Conversely, the spots on the axes where no thesis sits are the new blanks: budget and cost constraints lie outside the four axes, and DSH has no corresponding thesis either. That blank returns in the next piece.

The Laws Are a Portable Asset

The most transplantable thing in DSH is not the code; it is the laws. Eight law families—six formalized into checkable laws, plus two companion families for scope and scheduling—each eliminating one class of ordering bug. Five representatives, three things each: what the law says, which mechanism makes it hold, what it eliminates.

E1, the reverse-order law. Undo order mirrors registration order: if A depends on B, B registers first and A must tear down first. The mechanism is the effect stack—registration pushes, unload rolls the stack back—idempotent, with well-defined state under partial failure. It eliminates nondeterministic teardown order: the antidote to failure mode three.

W3, veto absorption. When a listener does not call next, the whole chain's outcome is "did not happen." A veto is a first-class, explicit behavior—not an exception path, and never in need of priority arbitration. The core implementation is about ten lines. It eliminates priority arm-wrestling among scattered interceptors: the antidote to failure mode one.

G1, the absorption law. The verdict of multiple guards depends only on "does a deny exist"—independent of order, repetition, and count. The guarantee lives at the type level: the decision type has no allow constructor. It eliminates policy registration order changing verdicts.

P1, decidable ownership. Any config field's final value equals "the whole row of the last layer that touched it." Provenance is mechanically decidable; deep-merge ambiguity structurally cannot exist. It eliminates configuration hell: the antidote to failure mode two.

L1, the denotation law. Session semantics are uniquely determined by the event sequence, with no hidden state beyond it. All "current state" is a fold over the log; all "what the model saw" is derived from the projection. It eliminates implicit global state and "mutating history breaks replay."

Collected into one table:

Mechanism Ordering bug eliminated
Effect stack in reverse order Nondeterministic teardown order
Model-order commits Tool completion order ≠ causal order
Monotonic guard Policy registration order changing verdicts
Patch whole-row replacement Deep-merge ambiguity
Waterfall as the only verb Priority arm-wrestling among scattered interceptors
Surface projection Mutating history breaking replay
Scope upward admission Event visibility leaking to uninvolved agents
Per-call capability Low-privilege call chains touching high-privilege resources

These laws do not depend on DSH's infrastructure. The guard monotonicity law needs only a type without an allow; the waterfall core is about ten lines; the patch algebra is a loader of about one hundred lines. The unit of harvest is a law plus its minimal implementation, not a package.

How do laws turn into design? Walk "move execution to remote" through the five-step method—a migration that actually happened in this repository.

Requirement: move the agent's entire execution surface—files, processes, terminal, LSP—from local to a remote E2B sandbox, without touching tool code. Step one, count: N tool classes times M execution environments equals N×M sets of remote adapters; assume twenty tool classes and four sandboxes and you get eighty adapter sets—unsustainable. Step two, find the thesis: T7 says execution is a world and tools are pure consumers of the world; bind file-path resolution and executable-lookup semantics to the "world" parameter and the combinatorics collapse to N+M. Step three, the operational path: swap in the two providers fs-e2b and subprocess-e2b, and Bash, PTY, and LSP change nothing. The abstraction's boundary is explicitly documented too: decisions that "must live in the same world as execution" (for instance, the LSP language-server process lives with the code) stay inside the world—you learn it from the doc, not the hard way. Step four, find the law: T8's patch algebra guarantees the migration is a configuration change; the --dump-config output matches the live run, reviewable and rollback-able. Step five, reason backward: without the world abstraction, one move to remote equals rewriting every tool once—and even after the move, the next execution-environment swap repeats the whole exercise.

Step five is the one most often skipped, and the most valuable. The backward-reasoning paragraph is exactly the "alternatives not adopted" section of a design document; rejected alternatives fall out of it mechanically.

One last note on use: a law being unable to express something and a law being violated are two different things. G1 cannot express an override—that is a design boundary, and override needs go through new events; an allow constructor slipping into the guard type is a violation of the law—a bug. Conflating the two breeds two errors: letting a bug pass as a boundary, or forcing a fix on a boundary as if it were a bug.

A Thirty-Year Lineage, an Itemized Cost List

DSH did not appear out of nowhere. Stretch the timeline and it stands at the end of a thirty-year bloodline: in 1968, THE's layered system proposed hierarchical decomposition; in 1985, the Mach microkernel turned "what enters the kernel" into an explicit question; in 2000, OSGi brought Java a service registry and bundle lifecycle; in 2004, Eclipse RCP built the extension-point system; in 2015, the VS Code extension host gave the plugin model a soft landing on the product side; in 2020, Koishi/Cordis turned this machinery into a TypeScript-native, effect-stack plugin framework for the chatbot domain; in 2026, DSH transplanted the full set into the agent loop.

Thirty-year lineage
Thirty-year lineage

Against its predecessors, DSH advances three things substantively. The loop itself is swappable: VS Code never made the editor's core loop replaceable; DSH made the agent loop a plugin. Configuration composition is an explicit algebra: OSGi's config-admin never achieved a layer-order law without deep merging. Extensions are typed: declaration merging plus type generation gives the services and events in the registry compile-time types, which OSGi-era registries never had.

There is one counter-conventional choice in timing. VS Code shipped the product first and opened a stable extension API about a year later; DSH does framework-ization before product maturity. The rationale matches T1: the loop still iterates monthly, and hardcoding equals rewriting at every shift. The other side of the bet: the concept tax comes due before revenue.

Seen from another angle, the agent domain is re-running platform history in compression: 2023–2025 were monolithic CLI products; 2025 brought restricted extension via hooks and skills; 2026 brought full framework-ization. The debates that OS and platforms took thirty years to settle, agent harnesses settled in three. The difference is cadence: platform history began platformizing after products matured; the agent domain is platformizing and validating products at the same time. That is both the opportunity and the risk—the opportunity is that the landscape is still unsettled; the risk is that nobody can afford the cost of waiting.

History's mirror is Mach versus Linux. Mach's concepts—microkernel, capabilities, client-server—all won in the long run; Linux won the market at hand. Hold a double expectation for DSH: if the loop paradigm converges and hardens, the microkernel's marginal value declines; if iteration continues, this architecture is a moat.

The cost list deserves equal airing. The concept inventory is about 45 first-class concepts; a competent TS engineer needs an estimated two to four weeks to go from running it to modifying core plugins, and one to two months to independently design seams and patch layers. The hot path carries constant-factor costs: one JSON snapshot plus deep freeze per assistant chunk, and full event persistence. Algorithmic complexity is not the issue—incremental caches are everywhere, and incremental projections always equal full recomputation. These constants are what correctness-first buys you.

There is also a list of over-engineering candidates, each defensible and each with a chronic ailment: a per-file 100% coverage hard gate is right for a framework team and a luxury for a product team; dual JSONL and SQLite persistence backends mean long-term double maintenance, and the community will eventually converge on one; multi-client type generation is over-investment in a single-client scenario; the Windows ACL sandbox's honest partial label is a plus, but its SID, DACL, and hard-link maintenance surface is out of proportion to its user share. The governance ledger: 219 packages, a bilingual documentation gate, and an estimated two to four core maintainers. A small team copying this process wholesale will be dragged down by the process itself.

What You Can Take Away

Not every team should fork. Four questions walk the decision tree: Is your product horizon 18 months or more with an unconverged loop strategy? Is the team 5 or more people who can keep 1–2 dedicated to the framework layer? Can you accept rc-stage breaking changes—pinning a commit and owning the migration yourself? Do you need audit and replay? Yes on the first three: build on. Stuck on the third: harvest P1 and watch upstream. No on the first question but you need audit: harvest the first two P1 steps. Neither: harvest the foundations and stop there.

Decision tree: Harvest versus Build-on
Decision tree: Harvest versus Build-on

Harvest: import not a line of DSH code; transplant the laws. The one-week-scale P0 list has six items:

Pattern Minimal implementation Bug eliminated
Monotonic guard One type plus one fold Policy-order roulette
Waterfall as the unified interceptor ~10 lines plus discipline If-spaghetti at interception points
Patch whole-row replacement A ~100-line loader Deep-merge hell
Model-order commits A committed pointer in the scheduler Causal confusion in tool results
fail-closed defaults Discipline Silent privilege downgrade
Capability-fact-driven schema Getters plus conditional fields The model wishing for capabilities that don't exist

This fits 95% of teams. Immediate payoff, zero risk; the one thing to guard against is translational drift—bring the laws whole. A guard without the type constraint is just a convention, and conventions lose to deadlines; a waterfall without the "one verb" discipline degenerates into new hooks, and the arm-wrestling resumes somewhere else.

Harvest has a second tier. Above the P0 sits a quarter-scale four-piece set, and the order matters: an effect-stack plugin kernel, an event-sourced session, the Definition/Provider/Consumer seam, a tool pipeline—each piece is the foundation of the next. Without the effect stack, hot reload stays unreliable; without event sourcing, pipeline decisions stay unauditable. Teams that reach this tier are holding not just patterns but a maintainable platform layer.

Build-on: pin a commit or fork the core five-piece set—the session, loop, and tool core packages plus the vendored cordis and include—and build your product on top. Prerequisites: 5 or more people, a horizon of 18 months or more, dedicated framework-layer maintenance, and tolerance for rc-stage turbulence. The fork's lifeline is the modification log of the vendored Cordis: every local patch on record, each with a reason; when upstream evolves, that log is what holds the merge window open. Maintain it as a contract, not as a README.

Watch: teams with an existing in-house stack should wait until the session format's version rises above 1 and the API carries a stability commitment before re-evaluating. rc-stage formats carry no migration promise; building a production plugin ecosystem on one right now means renting your foundation from someone else.

The one-line version: horizon under 18 months or a converged loop—harvest the P0 and build your own loop; a platform team that accepts turbulence—build on; everyone else—watch.

Coda: It Can Execute; It Cannot Learn

Spread DSH's event log out and count the downstream consumers—seven: resume, fork, transcript, telemetry, titles, statistics, plus snapshot replay during testing. Every behavior is replayable, every decision auditable, every model-visible input reconstructible. Put another way, all the raw feedstock for an experience collector is already in place, and its storage bill is already being paid.

The one thing missing is an eighth consumer: a learner that distills experience from history and feeds it back into the system. No policy optimization, no prompt evolution, no tool-pruning suggestions. The architecture has paved the road up to the door; the learning loop itself is the blank.

For those building the next generation of platforms, this blank is worth more than what has been implemented. The next piece lays out a complete reference design: how the eighth consumer enters; why every adaptation it makes must travel the supervised pipeline of "propose, approve, land, observe"; and why DSH's monotonic policy semilattice is precisely the safety foundation a self-improving system needs.


Declaration: This article was written from a source reading of the DeepSeek Harness open-source repository (github.com/deepseek-ai/deepseek-harness, v0.1.0-rc.5, analysis baseline commit 47f9438, re-verified 2026-08-17 with no drift); the first two pieces of the series are on this site. The judgments in this piece are the author's own views and do not constitute investment advice. Data as of August 17, 2026.