DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Core Runtime·Chapter 05

Agent Objects, Registry, and Lifecycle

Creation, resumption, inboxes, state, and shutdown

VerifiedUpstream 47f943859bScope: Trace the Agent interface, registry, create/resume transactions, live handles, initiators, and cancellation semantics.

Conclusion: an Agent is a scoped live execution capability, not the session record itself

A Session holds durable, replayable facts. An Agent is the current process's driver, Inbox, scope, and cancellation boundary around that Session. An AgentHandle is the teardown capability held only by an owner.

These objects are not interchangeable. A Session can exist without a live Agent. The same persisted Session can become a fresh Agent in a later process lifecycle. Finding an Agent in the registry proves that it is currently published, not that the observer may shut it down.

1. The interface package deliberately knows nothing about the concrete loop

Package boundary

@deepseek-ai/dsh-agent defines the Agent interface, registry, Inbox, initiator scope, and the full agent/* live-event vocabulary. dsh-agent-loop supplies concrete construction and driving through a single AgentFactory slot. On every create/resume, the registry retraces the factory through the caller's context so lifecycle effects belong to the fiber that actually initiated the operation. AGENT-FACTORY-SEAM

Mechanism-level deduction

The UI, ACP bridge, subagent providers, and tests can depend only on ctx.agents without importing ReactLoopAgent. Replacing the loop requires implementing the same Agent/Factory lifecycle contract, but consumers do not need to change. This is the practical meaning of replaceability at the core-object layer.

2. Exact anatomy of an Agent

MemberSemanticsDurable?
idThe exact same SessionId as session.id, not a separate Agent UUIDIdentity comes from the Session
optionsProvider, model, and optional positive maxTokens for this instanceNot intrinsic history; resume may select a new route
sessionThe append-only event log and source of truth for derived historyYes
inboxA pending-work projection reconstructed from durable splice eventsEvents are durable; arrays are live projections
ctxThe Agent's Cordis scope; tools, prompt sections, and listeners unwind with itNo; rebuilt on resume
statusOnly idle or running; disposal means leaving the registry, not a third statusLive-only
Public contract

The interface also exposes send, followup, steer, inject, cancel, whenIdle, and runMaintenance. running describes the complete driver drain interval, not strictly “a turn is still open.” AGENT-PUBLIC-INTERFACE

3. The live phase is more precise than public status

Concrete implementation

ReactLoopAgent has three internal phases: idle(lastTurn), maintenance(abort, wakeRequested), and running(abort, turn, step, wakeRequested). The public layer maps both idle and maintenance to idle, and only running to running. It emits agent/status only when the mapped value changes. AGENT-RUNTIME-SCOPE

runMaintenance() synchronously acquires the truly idle phase without inventing a model turn. Waking input received during maintenance remains in the Inbox and starts a driver only after maintenance settles. whenIdle() repeatedly compares the identity of activityDone, so it follows replacement work scheduled before the observed activity retires instead of awaiting only one captured Promise.

Design assessment

The public state stays simple while internal scheduling retains the required precision. The cost is interpretive: an observer cannot read idle as “absolutely no background maintenance,” nor running as “the model is generating.” Turn and step events carry the precise semantics.

4. Every Agent owns a world that can be retracted as one unit

Production source

The constructor restores the last turn and Inbox from Session events, then calls createScope() with the Agent itself as the scope key and gives agent.ctx its own agent property. Tools, prompts, variables, restrictions, and listeners registered through that context are local to the Agent. AGENT-RUNTIME-SCOPE

Setup contract

Create/resume setup(agentCtx) runs while neither Session nor Agent is in a registry. It may asynchronously mount scoped plugins and return a synchronous commit() that revalidates mutable resources at the publication boundary. Setup composes but must not drive; throws, commit failures, and owner unload all roll back before publication. AGENT-SETUP-CONTRACT

“The Agent capability world is complete before the first model request” becomes a structural guarantee rather than a race in which an agent/created listener tries to finish installation.

5. Create and Resume converge on one atomic publication pipeline

1

Acquire SessionPreparation

Create validates and snapshots a fresh session's seed/meta; Resume prepares an existing Session through persistence. Neither is live in the store yet.

2

Prepare ReactLoopAgent

Create the scope, Inbox, and driver; fuse caller signal, owner fiber, and factory teardown before any resource exists.

3

Await setup

Call setup(agent.ctx) and let it finish asynchronously while registry lookups still return nothing.

4

Synchronous setup commit

Perform the final non-yielding validation/commit after every await and before the first publication edge.

5

Enter both registries

First sessions.enter(session), then agents.enter(agent, ownerCtx.agent). This is the authoritative same-ID arbitration boundary.

6

Announce in order

session/createdagent/created → non-vetoing agent/session-start, whose source is startup or resume.

7

Return AgentHandle

The creation-only signal no longer controls the live object; future lifecycle control belongs to the handle, Agent cancellation, and structural owners.

Production control flow

Fresh creation and persistence resume both call setupAndPublish(). Persistence loading is itself bounded by caller signal, owner unload, and factory teardown. AGENT-CREATE-RESUMEAGENT-PREPARE-PUBLISH

Behavioral proof

Tests hold async setup behind a gate and confirm that both registries remain empty, then verify the complete announcement order. Two same-ID creates may both enter setup, but exactly one publishes and the loser leaves no orphan Session. Aborting a creation signal after a handle returns does not stop the live Agent. AGENT-ATOMIC-TEST

6. Publication is split into enter and announce

Registry control flow

enter() enforces agent.id === agent.session.id, checks ID collisions, and inserts an exact entry with a stable carrier without emitting an event. announce() may run once only for the current exact entry and marks it announced before dispatch. AGENT-REGISTRY-LIFECYCLE

The split exists for atomic publication and reentrant teardown:

  • Session and Agent can first enter their stores together so listeners observe a coherent world.
  • If an agent/created listener synchronously requests detach, deletion waits until dispatch returns; every later listener still sees the same live entry.
  • The detach closure captures the entry object, not merely its ID. An old closure cannot delete a later replacement that reuses the ID.
  • Rolling back an entry that was never announced does not emit agent/disposed, avoiding an impossible lifecycle edge.
Reentrancy tests

Registry tests verify split publication, idempotent detach, isolation from stale capabilities, and stable visibility to every listener when a created listener requests detach. AGENT-REGISTRY-TEST

7. Teardown has several owners but one quiescence boundary

AuthorityWhy it owns the lifecycleWhat it can do
Consumer's AgentHandleIt explicitly created or resumed the AgentCall memoized dispose()
Caller fiberStructured concurrency: child resources cannot outlive the caller pluginUnload triggers the same disposal
AgentLoop factory fiberLive Agents depend on that provider implementation and service surfaceProvider HMR/unload stops every old instance
Actual reverse order

All owners race on one memoized Promise: cancel the driver with {kind:'disposed'}, await whenIdle(), dispose the Agent scope, precisely detach the Agent and then the Session, and finally release factory/owner bookkeeping. AGENT-PREPARE-PUBLISH

Reentrant-publication tests

Even when a session/created or agent/created listener immediately unloads the owner, other listeners still see both registries intact. The observable rollback order is scope disposal, then agent/disposed if it had been announced, and finally session/disposed. AGENT-TEARDOWN-TEST

8. Runtime owner, durable lineage, and initiator are three different relations

RelationMeaningStorageAuthorization?
Session parentSessionDurable fork/derivation lineageSession headerDoes not automatically authorize current-process operations
Registry ownerWhich live Agent's scoped context created this live entryProcess-local AgentEntrySupports only exact runtime-ownership queries
Current initiatorWhich driver async chain causally triggered this workAsyncLocalStorageExplicitly not proof of liveness or authorization

A persisted fork with parentSession may therefore become a runtime root when resumed independently. Conversely, a child created by a current owner cannot carry ambient initiator identity through a worker, HTTP request, process, or durable queue without explicit materialization and validation.

Initiator API

withInitiator() and withoutInitiator() isolate and restore identity across concurrent async chains while preserving the exact returned value or Promise. Service teardown rejects new boundaries, drains already returned Promise boundaries, then disables AsyncLocalStorage. A nested chain that starts its own owner unload is released from the drain to prevent self-wait. AGENT-INITIATOR-APIAGENT-INITIATOR-DRAIN

9. Inbox is an incremental projection of durable events

Reconstruction

Constructing an Inbox replays every agent/inbox/spliced event after seedLength. Invalid coordinates or duplicate identities fail recovery instead of being silently repaired. The Inbox has next-turn and next-step lists, but MessageId must be unique across their union. AGENT-INBOX-REPLAYAGENT-INBOX-COMMIT

OperationDurable factLive notification
append / prependagent/inbox/spliced with inserted messagesPer-message agent/inbox/inserted
replaceOne splice removes the old value and inserts the new oneDiscard old, then insert new
remove / clearDeletion splice with outcome:'canceled'Per-message discarded
claimPure deletion splice, not canceledPer-message claimed with the owning turn

The implementation calls session.append() before mutating the memory array. A synchronous session observer therefore sees the pre-splice projection and can reconstruct removed messages from normalized coordinates. One step claim takes every pending next-step item first, then one next-turn item when a turn boundary requires it.

Behavioral proof

Tests cover invalid persisted splices, replacement across both lists, duplicate MessageId rejection, and clear writing two canceled events in next-step then next-turn order. AGENT-INBOX-TEST

10. Follow-up, Steer, Inject, and Cancel are not synonyms

APITargetWakes?Earliest consumption boundary
followup(message)next-turnYesA new ordinary turn; one next-turn message at a time
steer(message)next-stepYesThe next step of a running Agent; opens a turn when idle
inject(message)next-stepNoThe next pre-step of an existing driver; parks while idle
cancel(cause)Clears both by defaultNoAborts current activity; idle cancellation does not arm future work
cancel(cause,{keepInbox:true})Preserves bothNoAborts only current activity; pending work awaits a later wake
Implementation mapping

send() first persists Inbox insertion and then drives according to the wake flag. If the current activity is already aborted, new waking input is reclassified to next-turn so it cannot join the converging failed turn. A disposal cause never latches a new wake. AGENT-RUNTIME-SCOPE

11. Benefits, costs, and limitations

ChoiceBenefitCost or limitation
Separate Agent interface and loop packagesConsumers and orchestration do not bind to one driverA replacement must reproduce a sophisticated lifecycle and event contract
Unpublished setup transactionThe first observer always sees a complete scoped worldSetup is trusted same-process code; “compose, never drive” is contractual
Two-stage enter / announceCoherent dual registries and reentrant teardownMore complex lifecycle code; partially delivered notifications still require paired disposal
Handle as teardown capabilityRead-only registry observers cannot stop an AgentEvery transport/provider must carry ownership explicitly
Durable Inbox projectionIdle injection, cancellation, and resume share one source of truthEvery queue mutation adds events; one MessageId carries exactly one source
Process-local initiatorConvenient, concurrency-isolated causal attribution in one processCannot cross workers/processes/wires and must never replace explicit authorization
Chapter assessment

DeepSeek Harness does not make Agent a “smart object” that owns every state. It separates durable facts, the live driver, scoped contributions, queued work, ownership capability, and causal attribution. The decomposition is a strong foundation for resume, per-Agent plugins, HMR, and multiple transports. The real cost is that the lifecycle protocol itself resembles a small transaction system; a new provider that bypasses prepare/publish/dispose ordering will break consistency.

Chapter verification checklist

  • Separated the Agent interface package from the concrete AgentLoop factory.
  • Verified id/session, options, scope, status, and public input/cancellation methods.
  • Traced create and resume from preparation through setup, commit, and all three publication events.
  • Verified same-ID arbitration, creation-only signals, and failure rollback.
  • Traced enter/announce, reentrant detach, and stale-capability protection.
  • Verified shared teardown ownership across consumer, caller fiber, and factory fiber.
  • Separated durable lineage, runtime owner, and process-local initiator.
  • Verified Inbox replay, commit order, claims, cancellation, and MessageId uniqueness.

My Learning Notes

Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.