@stackstackstack/dsh-agent-loop 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ import { isAgentLoopRequest } from "@stackstackstack/dsh-llm";
2
+ import { foldRequestHeader } from "@stackstackstack/dsh-session";
3
+ //#region lib/types/invariant.js
4
+ /**
5
+ * Package-owned request-reconstruction invariant for loop-built LLM calls.
6
+ * @module @stackstackstack/dsh-agent-loop/invariant
7
+ */
8
+ const PACKAGE_NAME = "@stackstackstack/dsh-agent-loop";
9
+ /** Cordis companion plugin name. */
10
+ const name = "agent-loop-invariant";
11
+ /** Service required before the companion can reserve package ownership. */
12
+ const inject = ["invariants"];
13
+ /** Install the request-reconstruction contribution into its child registration fiber. */
14
+ const install = Object.assign((ctx, fail) => {
15
+ ctx.on("llm/stream", (options, next) => {
16
+ if (!isAgentLoopRequest(options)) return next();
17
+ if (!Object.isFrozen(options)) fail("a loop-built request must be frozen");
18
+ if (options.sessionId === void 0) fail("a loop-built request must carry a session id");
19
+ const session = ctx.sessions.get(options.sessionId);
20
+ if (!session) fail(`a loop-built request must carry a live session id, got "${String(options.sessionId)}"`);
21
+ if (!Object.isFrozen(options.messages)) fail("a loop-built request must carry a frozen messages array");
22
+ const events = session.events;
23
+ if (!events.some((event) => event.type === "step/start")) return fail("a loop-built request with no step/start in its session log");
24
+ const header = foldRequestHeader(events);
25
+ if (header === void 0) return fail("a loop-built request with no request/header event in its session log");
26
+ const expected = session.deriveMessages();
27
+ if (JSON.stringify(options.messages) !== JSON.stringify(expected)) fail(`llm request for session "${String(session.id)}" diverges from the dispatch-time durable derivation (log-reconstruction desync)`);
28
+ if (!(options.model === header.config.model && options.system === header.system && options.temperature === header.config.temperature && options.maxTokens === header.config.maxTokens && JSON.stringify(options.stop) === JSON.stringify(header.config.stop) && JSON.stringify(options.tools ?? []) === JSON.stringify(header.tools ?? []))) fail(`llm request for session "${String(session.id)}" diverges from the folded request header`);
29
+ return next();
30
+ }, {
31
+ global: true,
32
+ prepend: true
33
+ });
34
+ }, { inject: ["sessions"] });
35
+ /**
36
+ * Register the agent-loop invariant companion.
37
+ * @param ctx - Cordis context carrying the invariant service.
38
+ * @returns the installed registration's disposer after setup succeeds.
39
+ */
40
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
41
+ //#endregion
42
+ export { apply, inject, name };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Default Agent driver over queued turns and step-boundary input. Every request
3
+ * is derived from the session log.
4
+ * @module dsh-agent-loop/agent
5
+ */
6
+ import type { Agent, AgentCancelCause, AgentOptions, AgentStatus, CancelOptions, InboxTarget } from '@stackstackstack/dsh-agent';
7
+ import { Inbox } from '@stackstackstack/dsh-agent';
8
+ import type { Scope } from '@stackstackstack/dsh-scope';
9
+ import type { Session, SessionId, UserMessage } from '@stackstackstack/dsh-session';
10
+ import type { Context } from '@deepseek-ai/cordis';
11
+ /** Drives one session through turn and step boundaries. */
12
+ export declare class ReactLoopAgent implements Agent {
13
+ private loopCtx;
14
+ readonly id: SessionId;
15
+ readonly options: AgentOptions;
16
+ readonly session: Session;
17
+ readonly inbox: Inbox;
18
+ private phase;
19
+ private activityDone;
20
+ /** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
21
+ readonly scope: Scope;
22
+ readonly ctx: Context;
23
+ /** Fused dispatcher, built once in the constructor so hot-path dispatches never allocate. */
24
+ private readonly dispatch;
25
+ /** Whether this loop instance has appended its initial/resume request anchor. */
26
+ private requestHeaderLogged;
27
+ private readonly runtimeContext;
28
+ constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session);
29
+ get status(): AgentStatus;
30
+ /** Commit a phase and publish its externally visible status transition. */
31
+ private setPhase;
32
+ send(message: UserMessage, target: InboxTarget, wakeup: boolean): void;
33
+ followup(input: UserMessage): void;
34
+ steer(input: UserMessage): void;
35
+ inject(input: UserMessage): void;
36
+ cancel(cause: AgentCancelCause, options?: CancelOptions): void;
37
+ runMaintenance<T>(job: (signal: AbortSignal) => Promise<T>): Promise<T>;
38
+ /**
39
+ * Start one driver, or latch its wake behind maintenance or an aborted
40
+ * activity. A wake sent while idle always opens its turn boundary, even
41
+ * when its message was cleared; only a latched replay is suppressed when
42
+ * the queue no longer holds the wake.
43
+ * @param wakeAfterAbort - the {@link send} classification, captured before
44
+ * the inbox insertion so a reentrant cancel cannot reclassify it.
45
+ */
46
+ private wakeDriver;
47
+ whenIdle(): Promise<void>;
48
+ /** Report one failure at its live boundary, then preserve it for driver containment. */
49
+ private throwError;
50
+ private kick;
51
+ private preStep;
52
+ /** Open one turn before claiming its first proposed step. */
53
+ private turn;
54
+ private step;
55
+ /**
56
+ * Compose one frozen request and bind it to the adapter registration that
57
+ * resolved its exact-model defaults.
58
+ */
59
+ private buildRequest;
60
+ }
61
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Shared agent-loop scheduler defaults.
2
+ * @module dsh-agent-loop/constants
3
+ */
4
+ /** Default maximum in-flight parallel-safe calls per agent step. */
5
+ export declare const DEFAULT_MAX_PARALLEL_TOOL_CALLS = 10;
6
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Concrete agent-loop plugin: creates scoped ReactLoopAgents, publishes them
3
+ * through the agent/session registries, and owns their ordered teardown.
4
+ *
5
+ * @module @stackstackstack/dsh-agent-loop
6
+ */
7
+ import { Context, Service } from '@deepseek-ai/cordis';
8
+ import z from '@deepseek-ai/schemastery';
9
+ import type { Agent, AgentFactory, AgentHandle, AgentOptions, CreateAgentOptions, ResumeAgentOptions } from '@stackstackstack/dsh-agent';
10
+ import { SessionId } from '@stackstackstack/dsh-session';
11
+ import type { SessionHeader } from '@stackstackstack/dsh-session';
12
+ import { DEFAULT_MAX_PARALLEL_TOOL_CALLS } from './constants.ts';
13
+ declare module '@deepseek-ai/cordis' {
14
+ interface Context {
15
+ agentLoop: AgentLoop;
16
+ /**
17
+ * Launcher-owned exact session identities for configured agents, keyed by
18
+ * the agent's config `id` and set with `ctx.provide()` before any Loader
19
+ * entry mounts (see {@link CONFIGURED_AGENT_IDENTITIES_KEY}). A launcher
20
+ * owns identity because only it knows whether the session already exists,
21
+ * while the `cordis.yml` row keeps the model route as ordinary patchable
22
+ * config. An entry with no matching key keeps its configured identity.
23
+ */
24
+ configuredAgentIdentities?: ConfiguredAgentIdentities;
25
+ }
26
+ interface Events {
27
+ /**
28
+ * A declarative agent entry failed before it could publish a live agent.
29
+ * Consumers that buffer work for the configured identity use this
30
+ * transient signal to reject that work instead of waiting forever. Normal
31
+ * factory teardown suppresses failures from the cancelled startup attempt.
32
+ * @param payload.sessionId - exact shared agent/session identity that failed startup.
33
+ * @param payload.error - persistence, setup, or publication failure.
34
+ * @mode emit
35
+ */
36
+ 'agent-loop/config-start-failed'(payload: {
37
+ sessionId: SessionId;
38
+ error: unknown;
39
+ }): void;
40
+ }
41
+ }
42
+ export { DEFAULT_MAX_PARALLEL_TOOL_CALLS };
43
+ /**
44
+ * One launcher-selected session identity for a configured agent. `resume`
45
+ * distinguishes rehydrating existing persisted history from creating the
46
+ * session fresh under that exact id, which the two config keys express as
47
+ * `resumeSessionId` and `sessionId`.
48
+ */
49
+ export interface LauncherAgentIdentity {
50
+ /** Exact session id to create fresh or resume. */
51
+ id: SessionId;
52
+ /** Resume existing persisted history instead of creating the session fresh. */
53
+ resume: boolean;
54
+ }
55
+ /** Launcher-selected identities keyed by the configured agent's `id`. */
56
+ export interface ConfiguredAgentIdentities extends Readonly<Record<string, LauncherAgentIdentity>> {
57
+ }
58
+ /**
59
+ * Context key a launcher sets before any Loader entry mounts
60
+ * (`ctx.provide(CONFIGURED_AGENT_IDENTITIES_KEY, identities)`) to fix
61
+ * configured agents' session identities without a config key, so an overlay
62
+ * repointing the row's model route cannot drop them.
63
+ */
64
+ export declare const CONFIGURED_AGENT_IDENTITIES_KEY = "configuredAgentIdentities";
65
+ /** Settings namespace carrying the tool-call parallelism a user owns. */
66
+ export declare const AGENT_LOOP_SETTINGS_NAMESPACE: import("@stackstackstack/dsh-settings").SettingsNamespace;
67
+ /**
68
+ * The agent-loop fields a user owns. Deliberately a strict subset of
69
+ * {@link Config}: `agents` is a boot-time composition array consumed once when
70
+ * the service starts, so a stored change could only look like it had an effect.
71
+ */
72
+ export interface AgentLoopSettings {
73
+ /** Maximum parallel-safe calls in flight per agent step. */
74
+ maxParallelToolCalls: number;
75
+ }
76
+ /** Schema of the agent-loop settings section. */
77
+ export declare const AGENT_LOOP_SETTINGS_SCHEMA: z<AgentLoopSettings>;
78
+ /** Agent-loop plugin configuration. */
79
+ export interface Config {
80
+ /**
81
+ * Maximum parallel-safe calls in flight per agent step. `1` is serial;
82
+ * omission defaults to {@link DEFAULT_MAX_PARALLEL_TOOL_CALLS}.
83
+ */
84
+ maxParallelToolCalls?: number;
85
+ /** Agents created or resumed at plugin startup. */
86
+ agents: (AgentOptions & {
87
+ /** Stable config label used in logs and as the fresh combined-id prefix. */
88
+ id: string;
89
+ /** Optional stable identity; remounts resume its materialized history, while first use creates it fresh. */
90
+ sessionId?: SessionId;
91
+ /** Optional workspace for a fresh session. */
92
+ cwd?: string;
93
+ /** Persisted session to resume instead of creating a fresh session. */
94
+ resumeSessionId?: SessionId;
95
+ })[];
96
+ }
97
+ /** Agent-loop configuration after defaults and load-time validation. */
98
+ type ResolvedConfig = Config & {
99
+ maxParallelToolCalls: number;
100
+ };
101
+ /** Concrete agent factory and driver service. */
102
+ export declare class AgentLoop extends Service implements AgentFactory {
103
+ static inject: string[];
104
+ /** Runtime schema for declarative agents. */
105
+ static Config: z<Config>;
106
+ /** Validated configuration owned by the agent-loop service. */
107
+ readonly config: ResolvedConfig;
108
+ private readonly ownership;
109
+ /** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
110
+ private readonly runtime;
111
+ constructor(ctx: Context, config: Config);
112
+ /** Report a contained declarative-start failure to identity-bound consumers. */
113
+ private reportConfiguredStartupFailure;
114
+ /** Restore a materialized exact config identity on remount, or create it on first use. */
115
+ private restoreOrCreateConfigured;
116
+ /** Wait for a draining same-id lifecycle to finish registry teardown. */
117
+ private waitForDrainingConfiguredIdentity;
118
+ /**
119
+ * Construct the driver, scope, and one memoized reverse teardown for a new
120
+ * agent. The teardown is registered with the factory and the owner fiber
121
+ * BEFORE publication, so a mid-setup unload rolls everything back; `signal`
122
+ * fuses caller cancellation with lifecycle teardown for setup awaits.
123
+ */
124
+ private prepare;
125
+ /**
126
+ * Create an agent and session under one caller-supplied identity, owned by
127
+ * the accessing fiber. Constructor-driven config calls mint a fresh combined
128
+ * id before entering this boundary.
129
+ * @param id - shared agent/session identity.
130
+ * @param options - concrete loop options.
131
+ * @param meta - optional fresh-session workspace metadata.
132
+ * @returns the published running agent.
133
+ */
134
+ create(id: SessionId, options?: AgentOptions, meta?: Pick<SessionHeader, 'cwd'>): Agent;
135
+ /**
136
+ * Create an owned agent on a caller-supplied session id.
137
+ * @param ownerCtx - caller context that structurally owns the lifecycle.
138
+ * @param options - identities, session seed/metadata, loop options, setup, and cancellation.
139
+ * @returns the published handle.
140
+ */
141
+ createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
142
+ /** Prepare one Agent around an acquired Session, run setup, and publish it. */
143
+ private setupAndPublish;
144
+ /**
145
+ * Resume an owned agent from the configured persistence service.
146
+ * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
147
+ * @param options - persisted identity, loop options, setup, and cancellation.
148
+ * @returns the published handle.
149
+ */
150
+ resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
151
+ /** Resume through an explicit persistence handle used by the deferred config path. */
152
+ private resumeWith;
153
+ }
154
+ export default AgentLoop;
155
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned request-reconstruction invariant for loop-built LLM calls.
3
+ * @module @stackstackstack/dsh-agent-loop/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "agent-loop-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register the agent-loop invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Durable projection state for dynamic runtime context.
3
+ * @module @stackstackstack/dsh-agent-loop/runtime-context
4
+ */
5
+ import type { ContextSnapshotSection } from '@stackstackstack/dsh-llm';
6
+ import type { Session, UserMessage } from '@stackstackstack/dsh-session';
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ /** Tracks the last retained runtime-context snapshot without owning its commit. */
9
+ export declare class RuntimeContextProjection {
10
+ /** `undefined` means no snapshot ever existed; `null` means none is retained. */
11
+ private retained;
12
+ /**
13
+ * Restore projection state once, then follow authoritative session events.
14
+ * @param ctx - agent-scoped event context.
15
+ * @param session - session receiving projected messages.
16
+ */
17
+ constructor(ctx: Context, session: Session);
18
+ /**
19
+ * Create an uncommitted snapshot only when the retained value differs.
20
+ * @param current - fully rendered dynamic context.
21
+ * @param sections - named contributions that formed the current snapshot.
22
+ * @returns a candidate user message, or `undefined` when no update is needed.
23
+ */
24
+ project(current: string, sections: readonly ContextSnapshotSection[]): UserMessage | undefined;
25
+ }
26
+ //# sourceMappingURL=runtime-context.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Schedules one assistant step's tool calls. Exclusive calls form barriers;
3
+ * parallel calls use a bounded rolling pool and are reclassified before start.
4
+ * Dispatch may overlap, while policy, results, and result context remain
5
+ * model-ordered. Abort or an internal scheduler failure stops replenishment
6
+ * and drains started calls.
7
+ *
8
+ * Abort records synthetic error results for skipped calls so replay stays
9
+ * valid. A terminal scheduler failure preserves already-recorded `tool/call`
10
+ * events without fabricating results.
11
+ * @module dsh-agent-loop/tool-calls
12
+ */
13
+ import type { Context } from '@deepseek-ai/cordis';
14
+ import { type ToolCallBlock } from '@stackstackstack/dsh-llm';
15
+ import type { UserMessage } from '@stackstackstack/dsh-session';
16
+ /**
17
+ * Schedule one assistant step's tool calls by their live concurrency mode.
18
+ * Ordinary completion and abort commit started-call results in order. Abort
19
+ * drains them, records synthetic results for unstarted calls, and returns with
20
+ * the signal still aborted after accepting started-call context through the
21
+ * caller-supplied acceptor (the machine stages it in its next-step inbox for the
22
+ * step boundary). An internal scheduler failure stops new dispatches, drains
23
+ * already-started dispatches, and rejects with the first failure without
24
+ * fabricating tool results.
25
+ * The committed step's AgentLoop driver boundary supplies the initiating Agent
26
+ * that becomes each explicit {@link ToolExecutionInput.agent}.
27
+ *
28
+ * @param ctx - loop context that owns the tool registry and carries the initiating Agent.
29
+ * @param turn - current turn number.
30
+ * @param step - current step number.
31
+ * @param toolCalls - assistant calls in model order.
32
+ * @param signal - abort signal shared by the step.
33
+ * @param acceptContext - accepts committed result context for the next step boundary.
34
+ */
35
+ export declare function executeToolCalls(ctx: Context, turn: number, step: number, toolCalls: ToolCallBlock[], signal: AbortSignal, acceptContext: (context: UserMessage) => void): Promise<{
36
+ concluded: boolean;
37
+ }>;
38
+ //# sourceMappingURL=tool-calls.d.ts.map
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@stackstackstack/dsh-agent-loop",
3
+ "description": "The concrete agent loop plugin for the DeepSeek Harness",
4
+ "version": "0.1.5",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/core/agent-loop"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib/index.js",
29
+ "lib/invariant.js",
30
+ "lib/types/**/*.d.ts"
31
+ ],
32
+ "license": "MIT",
33
+ "peerDependencies": {
34
+ "@stackstackstack/dsh-agent": "^0.1.5",
35
+ "@stackstackstack/dsh-invariants": "^0.1.5",
36
+ "@stackstackstack/dsh-scope": "^0.1.5",
37
+ "@stackstackstack/dsh-llm": "^0.1.5",
38
+ "@deepseek-ai/cordis": "^4.0.1",
39
+ "@stackstackstack/dsh-session": "^0.1.5",
40
+ "@stackstackstack/dsh-system-prompt": "^0.1.5",
41
+ "@stackstackstack/dsh-settings": "^0.1.5",
42
+ "@stackstackstack/dsh-tools": "^0.1.5",
43
+ "@stackstackstack/dsh-session-persistence": "^0.1.5"
44
+ },
45
+ "dependencies": {
46
+ "@deepseek-ai/schemastery": "^3.18.1"
47
+ },
48
+ "devDependencies": {
49
+ "@stackstackstack/dsh-agent": "^0.1.5",
50
+ "@stackstackstack/dsh-invariants": "^0.1.5",
51
+ "@stackstackstack/dsh-llm": "^0.1.5",
52
+ "@stackstackstack/dsh-session": "^0.1.5",
53
+ "@stackstackstack/dsh-scope": "^0.1.5",
54
+ "@stackstackstack/dsh-session-persistence": "^0.1.5",
55
+ "@stackstackstack/dsh-system-prompt": "^0.1.5",
56
+ "@stackstackstack/dsh-tools": "^0.1.5",
57
+ "@stackstackstack/dsh-settings": "^0.1.5",
58
+ "@deepseek-ai/cordis": "^4.0.1",
59
+ "@stackstackstack/dsh-session-persistence-jsonl": "^0.1.5"
60
+ }
61
+ }