docs: rebalance prose cleanup and add trimming skill
This commit is contained in:
@@ -48,7 +48,8 @@ export interface PreparedReactLoopAgent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an unpublished concrete agent with instance-bound lifecycle controls.
|
||||
* Construct an unpublished concrete agent with instance-bound lifecycle
|
||||
* controls. Only those paired controls can publish or start this instance.
|
||||
* @param ctx - the agent-loop service context used for driving and events.
|
||||
* @param id - the concrete agent identity.
|
||||
* @param options - loop options for the agent.
|
||||
@@ -253,7 +254,8 @@ export class ReactLoopAgent implements Agent {
|
||||
// Decide the durability checkpoint from the log: an accepted one-shot
|
||||
// turn must be flushed even when its message append was the failing step.
|
||||
const turnRecorded = this.session.events.some(e => e.type === 'turn/start' && e.data.turn === turn)
|
||||
// Track the asynchronous checkpoint so disposal drains it; contain errors.
|
||||
// Keep inject() synchronous: report checkpoint failures live instead of
|
||||
// rejecting the caller, and track the task so disposal still drains it.
|
||||
if (turnRecorded) {
|
||||
// Through the store's flush (the carrier owner), never a raw parallel.
|
||||
const flush = this.loopCtx.sessions.flush(this.session).catch((error: unknown) => {
|
||||
@@ -290,7 +292,11 @@ export class ReactLoopAgent implements Agent {
|
||||
this.currentAbort?.abort(reason ?? 'cancelled')
|
||||
}
|
||||
|
||||
/** Resolve at idle, or after driver exit when disposed. */
|
||||
/**
|
||||
* Resolve immediately when idle with no queued work, on the next quiescent
|
||||
* idle transition otherwise, or after driver exit when already disposed.
|
||||
* This observes quiescence; it does not own teardown.
|
||||
*/
|
||||
whenIdle(): Promise<void> {
|
||||
if (this._status === 'disposed') return this.done
|
||||
if (this._status !== 'running' && !this.#inbox.hasQueued) return Promise.resolve()
|
||||
@@ -330,7 +336,7 @@ export class ReactLoopAgent implements Agent {
|
||||
isCancelled: () => this.cancelRequested,
|
||||
cancelReason: () => this.cancelReason,
|
||||
clearCancel: () => { this.cancelRequested = false },
|
||||
// Pre-step cancellation re-parks without a status transition.
|
||||
// Pre-step cancellation re-parks without emitting a status transition.
|
||||
settleIdle: () => { this.settleIdleWaiters() },
|
||||
})
|
||||
}
|
||||
@@ -370,7 +376,8 @@ export class ReactLoopAgent implements Agent {
|
||||
// cleanup. The normal loop contains turn failures itself; allSettled is the
|
||||
// final lifecycle backstop for anything outside those boundaries.
|
||||
await Promise.allSettled([this.done])
|
||||
// Repeat because settled flushes retire in adjacent promise reactions.
|
||||
// Repeat because settled flushes retire in adjacent promise reactions;
|
||||
// allSettled keeps reporting failures from skipping ownership teardown.
|
||||
while (this.pendingIdleFlushes.size > 0) {
|
||||
await Promise.allSettled([...this.pendingIdleFlushes])
|
||||
}
|
||||
|
||||
@@ -73,7 +73,11 @@ function signalAbortError(id: AgentId, signal: AbortSignal): Error {
|
||||
return new Error(`agent "${id}" creation aborted`, { cause: signal.reason })
|
||||
}
|
||||
|
||||
/** Caller-owned create/resume transaction through publication and teardown. */
|
||||
/**
|
||||
* Caller-owned create/resume transaction through rollback-covered publication
|
||||
* and quiescent teardown. Resources remain private until the final registry
|
||||
* entry arbitrates identity.
|
||||
*/
|
||||
class AgentCreationTransaction {
|
||||
private active = true
|
||||
private failure: Error | undefined
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/** Agent loop driver with turn-level error containment. @module dsh-agent-loop/loop */
|
||||
/**
|
||||
* Drives one agent across queued durable turns. Turn failures are contained so
|
||||
* later work can run; the session log, not this driver, owns conversation state.
|
||||
* See docs/rfc/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md.
|
||||
* @module dsh-agent-loop/loop
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { FinishReason, GenerateOptions, LlmCallConfig, Message } from '@deepseek-ai/dsh-llm'
|
||||
@@ -78,7 +83,7 @@ export interface LoopHandle {
|
||||
cancelReason(): string
|
||||
/** Clear the cancel marker (called once per iteration after the turn returns). */
|
||||
clearCancel(): void
|
||||
/** Settle idle waiters when a cancelled turn is skipped without a status transition. */
|
||||
/** Settle idle waiters when pre-running cancellation skips a turn, without emitting `agent/status`. */
|
||||
settleIdle(): void
|
||||
}
|
||||
|
||||
@@ -267,7 +272,9 @@ async function runTurn(
|
||||
break
|
||||
}
|
||||
|
||||
// Compose, detach, and freeze the per-instance prefix before pressure checks.
|
||||
// Compose the request-only prefix once per loop instance before pressure
|
||||
// checks. It precedes all derived history and is recorded only in the
|
||||
// request header, not as session history.
|
||||
if (transmission.sessionPrefix === undefined) {
|
||||
const emptyPrefix: Message[] = deepFreeze([])
|
||||
const composed = await events.waterfall(
|
||||
@@ -294,7 +301,8 @@ async function runTurn(
|
||||
break
|
||||
}
|
||||
|
||||
// Snapshot the exact log prefix before step/start: the reconstruction boundary.
|
||||
// Snapshot the exact log prefix before step/start: the reconstruction
|
||||
// boundary. Appends after this synchronous snapshot join the next request.
|
||||
const boundaryMessages = session.deriveMessages()
|
||||
|
||||
session.append('step/start', { turn, step })
|
||||
@@ -444,13 +452,12 @@ function drainSteering(agent: ReactLoopAgent, inbox: Inbox, turn: number): boole
|
||||
return messages.length > 0
|
||||
}
|
||||
|
||||
/** One step: build the request from the boundary snapshot + the step's
|
||||
* header → compose the session prefix if this instance has none yet → log
|
||||
* the header event the request owes → stream model → record → execute
|
||||
* tools. The caller assembles the
|
||||
* system prompt, fires the `agent/pre-step` seam, snapshots the derivation,
|
||||
* and opens the step BEFORE calling this, so `boundaryMessages` is exactly
|
||||
* the surface prefix at step/start and already reflects any compaction. */
|
||||
/**
|
||||
* Run one committed step: transform call config, log the request header, build
|
||||
* the request from the cached prefix plus the step-boundary snapshot, stream and
|
||||
* record the response, then execute tools. The caller has already assembled the
|
||||
* prompt, run `agent/pre-step`, snapshotted history, and opened the step.
|
||||
*/
|
||||
async function runStep(
|
||||
ctx: Context,
|
||||
events: AgentEventDispatch,
|
||||
@@ -560,7 +567,8 @@ async function runStep(
|
||||
} catch {
|
||||
parsedArguments = call.arguments
|
||||
}
|
||||
// TODO(pre-tool-input-rewrite): A rewrite must keep logged history and live presentation aligned.
|
||||
// TODO(pre-tool-input-rewrite): Keep logged history and live presentation aligned;
|
||||
// see docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md.
|
||||
const result = await ctx.tools.execute({
|
||||
callId: call.id,
|
||||
name: call.name,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Per-loop-instance transmission bookkeeping for the reconstructability contract: which header
|
||||
* event to append before a request so the session log always explains the request (the
|
||||
* reconstructability RFC).
|
||||
* Per-loop-instance request-header bookkeeping for reconstructability. The
|
||||
* comparison baseline is the header folded from the session log, so a fresh
|
||||
* loop instance needs no special resume or fork state.
|
||||
* @module dsh-agent-loop/request-log
|
||||
*/
|
||||
|
||||
@@ -32,8 +32,10 @@ export function createTransmissionLog(): TransmissionLog {
|
||||
}
|
||||
|
||||
/**
|
||||
* Append whatever header event this request owes the log, so folding the log reproduces the
|
||||
* header the request was built under. Exactly one of four things happens.
|
||||
* Append whatever header event makes the log reproduce this request's header.
|
||||
* The first request from an instance always records a full `initial` or `resume`
|
||||
* snapshot. Later requests record nothing when unchanged, a round-tripping
|
||||
* delta when expressible, or a full `fallback` snapshot otherwise.
|
||||
*
|
||||
* @param session - the session whose log explains the request.
|
||||
* @param state - this loop instance's bookkeeping (mutated on first log).
|
||||
|
||||
Reference in New Issue
Block a user