@polyweave/agent-loop (1.0.1)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/agent-loop@1.0.1"@polyweave/agent-loop": "1.0.1"About this package
agent-loop
Reusable agent->evaluator execution loop for LLM harnesses. Agent does work, evaluator judges, symbolic state propagates, retry until done.
Published as @polyweave/agent-loop.
Synopsis
import { createAgentLoop, createDefaultEvaluatorParser, NodeStatus } from '@polyweave/agent-loop';
const loop = createAgentLoop({
agentAdapter: myAgentAdapter,
evaluatorAdapter: myEvalAdapter,
buildAgentPrompt: (ctx) => ({ type: 'TEXT', streamId: 's1', content: { text: 'Do task: ' + ctx.task } }),
buildEvaluatorPrompt: (ctx, history) => ({ type: 'TEXT', streamId: 's2', content: { text: 'Judge this work' } }),
parseEvaluatorOutput: createDefaultEvaluatorParser(),
toolAdapters: { my_tool: myToolAdapter },
maxRetries: 5
});
loop.source.pipe(mySink);
loop.sink.write({ type: 'START' });
Install
npm install @polyweave/agent-loop
Description
The agent-loop package implements a reusable agent-evaluator execution loop over Polyweave push-stream frames. An LLM agent performs work using registered tools, then an evaluator LLM judges the output. Symbolic state propagates between iterations, and the loop retries until the evaluator signals SUCCESS or FAILURE, or the retry limit is reached.
The agent-loop handles lifecycle wrapping (max iterations, aborts), tool routing through @polyweave/tool-adapter, and frame collection from agent and evaluator streams. The loop emits PROGRESS frames for each phase transition so downstream observers can react to node_started, node_completed, node_failed, node_retry, and symbolic_asserted events.
Two loop variants are provided: createAgentLoop runs a single agent-evaluator cycle (with retries), and createLoopingAgentLoop chains multiple inner loops together, advancing through nodes provided by a nodeProvider while a shouldContinue predicate evaluates the shared context.
API
createAgentLoop(options)
Creates an agent-evaluator execution loop.
Parameters:
options.agentAdapter(required) — LLM adapter duplex{ source, sink }for the agent.options.evaluatorAdapter(required) — LLM adapter duplex{ source, sink }for the evaluator.options.buildAgentPrompt(ctx)(required) — Function returning a frame prompt for the agent given cycle context.options.buildEvaluatorPrompt(ctx, agentHistory)(required) — Function returning a frame prompt for the evaluator.options.parseEvaluatorOutput(frames)(required) — Function parsing evaluator frames into{ status, evidence, symbolicUpdates }.options.toolAdapters— Object mapping tool names to adapter duplexes.options.context— Initial context includingsymbolics,runtime,node.options.initialSymbolics— Initial values for the symbolic state.options.maxRetries— Maximum deterministic-check retries (default 5).options.maxIterations— Maximum agent-evaluator cycles (default 10).options.maxBufferedOutputFrames— Output buffer size cap.options.outputOverflowStrategy— Buffer overflow strategy ('drop-oldest').options.onProgress(type, data)— Progress callback.options.deterministicChecks(checksList, toolTrace)— Synchronous check function.options.checksList— Array of check strings (e.g.'file_exists:output.txt','symbolic:done=true','stdout_contains:OK','exit_code:0').options.toolTimeoutMs— Tool call timeout (default 30000).
Returns: { source, sink, symbolics, context, abort }
createLoopingAgentLoop(options)
Creates a loop that chains multiple inner agent-loop invocations.
Parameters:
options.innerLoopFactory(innerOpts)— Factory returning an agent loop (defaults tocreateAgentLoop).options.shouldContinue(context)— Predicate; when false, the loop completes.options.nodeProvider(context, index)— Returns the next node or null.options.maxIterations— Maximum total loop iterations (default 10).options.contextoroptions.initialContext— Shared context across inner loops.- Plus all options forwarded to each inner loop.
Returns: { source, sink, context, abort }
createSymbolicState(initial)
Creates a subscription-based symbolic key-value store.
Returns: { assertValue, retractValue, getValue, getAll, applyUpdate, applyUpdates, subscribe }
createDefaultDeterministicChecks(options)
Returns a function that evaluates check strings for deterministic verification.
Check formats:
file_exists:path— Checks file presence in workspace directory.symbolic:name=value— Checks symbolic state value.stdout_contains:pattern— Searches tool trace stdout.exit_code:N— Checks tool trace exit codes.
Returns: { allPassed: boolean, evidence: string }
createDefaultEvaluatorParser()
Returns a function that parses evaluator TEXT frames as JSON into { status, evidence, symbolicUpdates }. Expects JSON with status ('SUCCESS', 'FAILURE', 'RUNNING'), optional evidence, and optional symbolicUpdates array.
NodeStatus
Enum: { PENDING, RUNNING, SUCCESS, FAILURE }.
Examples
// With deterministic checks and file verification
const loop = createAgentLoop({
agentAdapter,
evaluatorAdapter,
toolAdapters: { bash: bashTool },
buildAgentPrompt: (ctx) => textFrame('Write file output.txt with "hello"'),
buildEvaluatorPrompt: () => textFrame('Did the agent write the file?'),
parseEvaluatorOutput: createDefaultEvaluatorParser(),
deterministicChecks: createDefaultDeterministicChecks({ workspaceDir: '/tmp' }),
checksList: ['file_exists:output.txt', 'symbolic:done=true'],
maxRetries: 3
});
// Looping variant with sequential nodes
const loop = createLoopingAgentLoop({
context: { symbolics: createSymbolicState() },
nodeProvider: (ctx, i) => nodes[i] || null,
shouldContinue: (ctx) => ctx.symbolics.getValue('done') !== true,
maxIterations: 5,
buildAgentPrompt: (ctx) => textFrame('Step for node: ' + ctx.node),
buildEvaluatorPrompt: (ctx, h) => textFrame('Review the step'),
parseEvaluatorOutput: createDefaultEvaluatorParser(),
agentAdapter,
evaluatorAdapter
});
Tests
npm test
Tests cover the agent-evaluator loop lifecycle, retry logic, deterministic check evaluation, symbolic state propagation, abort/cancel semantics, frame collection, and the looping agent loop variant.
Requirements
Node.js 18 or later. ESM only.
Caveats
- Both
agentAdapterandevaluatorAdaptermust implement the Polyweave frame duplex interface ({ source, sink }). - The loop is single-cycle-at-a-time internally (re-entrant via
executeFullCycle), but the sink interface serializes starts. - Deterministic checks run synchronously and block the agent cycle; for async checks, handle externally.
- Excessive
maxRetriescombined with largemaxIterationsmay produce many LLM calls.
License
MIT
Dependencies
Dependencies
| ID | Version |
|---|---|
| @polyweave/harness-base | * |
| @polyweave/tool-adapter | * |
| @polyweave/tools | * |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |