@polyweave/feedback-descent (1.0.5)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/feedback-descent@1.0.5"@polyweave/feedback-descent": "1.0.5"About this package
@polyweave/feedback-descent
Feedback Descent optimization loop as a bt-harness duplex tool. Iteratively improves text artifacts through propose→evaluate cycles with accumulated critique.
Published as @polyweave/feedback-descent.
Synopsis
import { createFeedbackDescentTool } from '@polyweave/feedback-descent';
const tool = createFeedbackDescentTool({
apiKey: process.env.API_KEY,
model: 'deepseek-v4-flash',
initialArtifact: 'Draft version of text...',
maxIterations: 10,
patience: 3
});
tool.sink.write({ type: 'TOOL_CALL', content: {} });
tool.source.pipe(mySink);
Install
npm install @polyweave/feedback-descent
Why
LLM-generated artifacts (code, prose, configuration) often need iterative refinement beyond a single generation pass. Feedback Descent gives you a structured optimize-evaluate loop: an agent proposes an improved version, an evaluator judges which is better, and the best artifact is promoted. Critique accumulates across iterations, so the agent sees what was wrong before. This avoids prompt-engineering one-shots and instead applies the proven gradient-free optimization pattern of propose→evaluate→promote with stagnation-based termination.
API
createFeedbackDescentTool(options)
Creates a Polyweave frame duplex tool implementing the Feedback Descent loop. The tool is driven by TOOL_CALL initialization and TICK frames for step-by-step advancement. Under the hood, it creates a bt-harness with the FD_SPEC behavior tree and an in-memory document store.
createFeedbackDescentTool(options?: Object): IFrameDuplex
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.apiKey |
string |
No | — | API key for LLM adapter calls |
options.model |
string |
No | 'deepseek-v4-flash' |
Model name for agent and evaluator |
options.initialArtifact |
string |
No | '' |
Starting artifact text |
options.maxIterations |
number |
No | 10 |
Maximum promote cycles |
options.patience |
number |
No | 3 |
Stagnation threshold before termination |
options.createAdapter |
Function |
No | — | Custom adapter factory function |
options.adapterConfig |
Object |
No | — | Configuration passed to adapter factory |
options.debug |
boolean |
No | false |
Enable debug output (stored as _debug) |
options.name |
string |
No | null |
Adapter name |
Returns: A IFrameDuplex with additional properties:
source— push-stream source (emits TOOL_RESULT, ERROR, DESCRIPTION)sink— push-stream sink (see frame protocol below)_toolName—'feedback_descent'getBest()— Returns the current best artifact text synchronously from the document storeclose()— Closes the internal document storename— string, adapter name
Frame Protocol
Input frames (sink receives):
| Frame Type | Description |
|---|---|
TOOL_CALL |
Initiates the optimization loop. Content may include { initialArtifact, maxIterations, patience } to override constructor options. Rejects with ERROR if already running. |
TICK |
Advances the bt-harness by one tick. Forwarded to the current harness. |
CANCEL |
Calls cancel() on the current harness, marks not-running, clears harness reference |
ABORT |
Calls abort() on the current harness, marks not-running, clears harness reference |
DESCRIBE |
Responds with DESCRIPTION frame containing tool metadata, capabilities ['write', 'execute'], schema |
Output frames (source emits):
| Frame Type | Description |
|---|---|
TOOL_RESULT |
Optimization completed successfully. Content: { artifact: string, iterations: number } |
TOOL_RESULT |
Optimization failed (bt:tree_failed). Content: { artifact: string, iterations: 0, failed: true } |
ERROR |
Error during execution. Content: { message: string }. Emitted for: harness validation errors (spec parse failure), bt:error events, or "Already running" rejections |
DESCRIPTION |
Tool description response (capabilities, schema) |
Internal tool calls exposed to agents (in the bt-harness):
| Tool | Arguments | Description |
|---|---|---|
fd_read |
{ sectionId } |
Reads a document section. Default section is 'fd/best' |
fd_propose |
{ text, sectionId } |
Stores a proposal. Default section is 'fd/candidate'. Rejects with { error: 'Missing text' } if text is empty or missing |
fd_evaluate |
{ preferred, critique } |
Records evaluation. preferred is boolean. critique is stored in 'fd/critique'. Updates symbolic state @preferred |
Error Conditions
The tool emits ERROR frames (never throws) for:
- Harness validation failure — when
parseBtSpec(FD_SPEC)fails or harness reports!valid; message includes joinedharness.errors - Already running — when TOOL_CALL received while a previous loop is in progress; message:
'Already running' - bt:error — when the behavior tree emits a
bt:errorframe; message fromframe.content.messageor'fd error'
FD_SPEC
The behavior tree specification string (exported for inspection or modification before passing to parseBtSpec).
Symbols:
@iteration(number, starts 0) — increments on each evaluate action@stagnant(number, starts 0) — increments when candidate does not improve@preferred(boolean, starts false) — set byfd_evaluate@done(boolean, starts false) — set after evaluate completes, cleared after promote/increment
Postcondition: @stagnant >= @patience
Tree structure:
root = sequence {
propose (fd_read + fd_propose, runs when @done == false)
evaluate (fd_read + fd_evaluate, sets @done = true, @iteration++)
selector {
sequence { condition improved (@preferred == true), promote_best }
sequence { condition stagnating (@preferred == false), increment_stagnant (@stagnant++) }
}
}
Examples
// With custom initial artifact and patience
const tool = createFeedbackDescentTool({
apiKey: process.env.API_KEY,
initialArtifact: 'function add(a, b) { return a + b; }',
maxIterations: 20,
patience: 5
});
tool.sink.write({ type: 'TOOL_CALL', content: { maxIterations: 15, patience: 3 } });
const sink = createFrameSink((frame) => {
if (frame.type === 'TOOL_RESULT') {
console.log('Final artifact:', frame.content.artifact);
}
});
tool.source.pipe(sink);
// Tick-driven operation
tool.sink.write({ type: 'TOOL_CALL', content: {} });
tool.sink.write({ type: 'TICK' }); // advance one step
tool.sink.write({ type: 'CANCEL' }); // cancel
// Inspect the FD_SPEC
import { FD_SPEC, parseBtSpec } from '@polyweave/bt-harness';
const spec = parseBtSpec(FD_SPEC);
console.log(spec.actions.length); // 4 actions
Tests
npm test
Runs: node --test test/*.test.js
Covers: tool creation with various options, propose-evaluate-promote cycle, stagnation-driven termination, custom maxIterations/patience from TOOL_CALL args, getBest() during and after execution, CANCEL/ABORT handling, error propagation, and harness validation failure.
Requirements
- Node.js >= 22.0.0
- ESM only (
"type": "module") - Peer dependencies:
@polyweave/core - Dependencies:
@polyweave/bt-harness,@polyweave/state,@polyweave/deepseek
Caveats
- Requires a valid
apiKeyorcreateAdapterfor LLM model access. Without both, the harness will fail to initialize. - The behavior tree uses
maxTreeLoops: iterLimit + 5and relies on the postcondition (@stagnant >= @patience) to terminate. - Results are stored in an in-memory document store and are not persisted across restarts.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @polyweave/bt-harness | * |
| @polyweave/harness-base | * |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |