@polyweave/prompt-manager (1.0.6)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/prompt-manager@1.0.6"@polyweave/prompt-manager": "1.0.6"About this package
@polyweave/prompt-manager
AI persona management with LLM-based prompt optimization from exemplars — push-stream native.
Published as @polyweave/prompt-manager.
Status
PARTIALLY IMPLEMENTED — The push-stream prompt injection wrapper, Analyzer, Optimizer, repeated prompt builder, and all storage adapters (Memory, AtomStore, StateStore) are functional. The orchestrated PromptManager class that wires storage + analysis + optimization together is scaffolded but not yet built. The SQLiteAdapter is planned but not implemented. See TODO.md for the full implementation roadmap.
Synopsis
import { createPromptManagerWrapper, MemoryAdapter, Analyzer, Optimizer } from '@polyweave/prompt-manager';
import { createFrameDuplex, createTextFrame } from '@polyweave/core';
// Push-stream wrapper injects system prompts into scopes
const adapter = createFrameDuplex((frame, respond) => {
if (frame.type === 'TEXT') {
respond({ type: 'START' });
respond(createTextFrame('Echo: ' + frame.content.text));
respond({ type: 'END' });
}
});
const wrapper = createPromptManagerWrapper(adapter, { systemPrompt: 'You are a helpful assistant.' });
wrapper.sink.write({ type: 'START' });
wrapper.sink.write(createTextFrame('Hello'));
wrapper.sink.write({ type: 'END' });
// The adapter receives [START, TEXT('You are a helpful assistant.'), TEXT('Hello'), END]
// Dynamic prompt update via META frames
wrapper.sink.write({ type: 'START' });
wrapper.sink.write({ type: 'META', content: { key: 'type', value: 'system-prompt-update' } });
wrapper.sink.write(createTextFrame('Be concise and direct.', { metadata: { role: 'system' } }));
wrapper.sink.write({ type: 'END' });
// future scopes now prepend 'Be concise and direct.'
// Storage: in-memory persona CRUD
const storage = new MemoryAdapter();
storage.create({ id: 'tech-reporter', domain: 'journalism', traits: { style: 'analytical' } }, (err, persona) => {
if (err) return;
storage.get('tech-reporter', (err, record) => console.log(record));
});
// Exemplar analysis & optimization
const analyzer = new Analyzer({ run: myLLMRunner });
analyzer.analyzeExemplars(['sample article 1', 'sample article 2'], { style: 'analytical' }, {}, (err, analysis) => {
const optimizer = new Optimizer({ run: myLLMRunner });
optimizer.optimizePrompt(analysis, { style: 'analytical' }, {}, (err, prompt) => console.log(prompt));
});
// Repeated prompt template builder
import { createRepeatedPromptTemplate } from '@polyweave/prompt-manager';
const template = createRepeatedPromptTemplate({ coreTemplate: 'Rule: Do X.', endTemplate: 'Now respond.', repeatCount: 2 });
Install
npm install @polyweave/prompt-manager
Why
LLM prompt engineering is iterative. You start with exemplar documents, extract style patterns, generate a system prompt, test it, and repeat. This package provides the machinery to do that inside the Polyweave frame protocol: a bidirectional wrapper that injects system prompts into any downstream adapter, an Analyzer that extracts writing patterns from exemplar texts via an LLM, an Optimizer that generates system prompts from those patterns, and pluggable storage backends for persona versioning. The repeated prompt template builder creates "say it twice" patterns for LLM instruction-following reinforcement. Everything is callback-based with optional Promise support — no async/await required in the core API.
API
createPromptManagerWrapper(innerDuplex, options?)
Wraps a push-stream duplex to inject system prompts into every request scope.
Signature
function createPromptManagerWrapper(
innerDuplex: IFrameDuplex,
options?: {
systemPrompt?: string | null,
name?: string | null
}
): IFrameDuplex
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
innerDuplex |
IFrameDuplex |
Yes | — | The downstream duplex to wrap (e.g., an LLM adapter). |
options.systemPrompt |
string|null |
No | null |
Initial system prompt. If set, prepended as a TEXT frame with metadata.role: 'system' before user content in every scope. |
options.name |
string|null |
No | null |
Node name for topology. |
Return Value
Returns an IFrameDuplex that:
- Buffers all frames between
STARTandENDinto a scope. - On scope
END, emitsSTART→ PARAM frames (if any) → system prompt TEXT frame (if set) → content TEXT/IMAGE/AUDIO/VIDEO frames →ENDdownstream. - META frames with
{ content: { key: 'type', value: 'system-prompt-update' } }mark the current scope as a prompt update. When this scope ends, the first TEXT frame withmetadata.role: 'system'replacescurrentSystemPrompt. The scope itself is not forwarded. - Response frames pass through unmodified.
- Nested scopes (START before END of prior scope) flush the prior scope before beginning the new one.
Frame Protocol
| Inbound (sink) | Outbound (source) | Description |
|---|---|---|
START |
START |
Begins buffering a scope. Emits START downstream when scope flushes. |
END |
END |
Flushes the buffered scope downstream. If isSystemPromptUpdate, replaces the system prompt and emits nothing. |
TEXT, IMAGE, AUDIO, VIDEO |
TEXT, etc. |
Buffered within scope, then emitted in order after the system prompt. |
PARAM |
PARAM |
Emitted before the system prompt TEXT frame so params arrive before content. |
META (system-prompt-update) |
— | Marks scope as a prompt update. Consumed; no downstream output. |
| All other frames | Passthrough | Forwarded unmodified. |
Error Conditions
No explicit errors are thrown. All error handling is passive: if in a nested scope, the prior scope is flushed; if an incomplete scope is abandoned, it is simply dropped. META frames that do not match the system-prompt-update pattern pass through.
Analyzer
Analyzes exemplar texts to extract style patterns, structure, tone, and constraints via an LLM runner.
Constructor
new Analyzer(options?: {
run?: (input: { prompt: string, exemplars: string[], traits: object, options: object }) => any,
promptBuilder?: (exemplars: string[], traits: object, options: object) => string
})
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.run |
function |
No | null |
LLM runner function. Receives { prompt, exemplars, traits, options }. Returns a result or a Promise. If not provided, analysis falls back to defaultAnalysis. |
options.promptBuilder |
function |
No | buildAnalysisPrompt (internal) |
Custom prompt builder. Receives (exemplars, traits, options) and returns a prompt string. |
analyzeExemplars(exemplars, traits?, options?, done?)
analyzer.analyzeExemplars(
exemplars: any[],
traits?: object,
options?: { guidance?: string },
done?: (err: Error | null, result?: object) => void
): Promise<object> | void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
exemplars |
any[] |
No | [] |
Array of exemplar entries. Each entry is normalized via normalizeExemplar: strings pass through, objects use .text, .content, or JSON.stringify. |
traits |
object |
No | {} |
Trait key-value pairs to guide analysis. |
options.guidance |
string |
No | 'Extract style patterns, structure, tone, and constraints.' |
Guidance appended to the analysis prompt. |
done |
function |
No | — | Node-style callback (err, result). If omitted, returns a Promise. |
Return Value
If done is provided, calls done(null, result). If done is omitted, returns a Promise<object>.
The result object has shape:
{
summary: string, // analysis summary
style: object, // style patterns (traits if fallback)
constraints: string[], // formatting/structural constraints
do: string[], // recommended practices
dont: string[], // patterns to avoid
exemplarCount: number // number of exemplars analyzed
}
If options.run is not provided, returns a default analysis with summary: 'LLM analysis not configured. Returning defaults.' and the provided traits.
Optimizer
Generates optimized system prompts from analysis results via an LLM runner.
Constructor
new Optimizer(options?: {
run?: (input: { prompt: string, analysis: object, traits: object, options: object }) => any,
promptBuilder?: (analysis: object, traits: object, options: object) => string
})
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.run |
function |
No | null |
LLM runner function. Receives { prompt, analysis, traits, options }. Returns a result or a Promise. If not provided, optimization falls back to defaultPromptFromTraits. |
options.promptBuilder |
function |
No | buildOptimizationPrompt (internal) |
Custom prompt builder. Receives (analysis, traits, options) and returns a prompt string. |
optimizePrompt(analysis, traits?, options?, done?)
optimizer.optimizePrompt(
analysis: object | null,
traits?: object,
options?: { guidance?: string },
done?: (err: Error | null, result?: string) => void
): Promise<string> | void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
analysis |
object|null |
No | — | Analysis result (e.g., from Analyzer.analyzeExemplars). May be null/undefined. |
traits |
object |
No | {} |
Trait key-value pairs to embed in the prompt. |
options.guidance |
string |
No | 'Generate a system prompt that matches the analysis.' |
Guidance appended to the optimization prompt. |
done |
function |
No | — | Node-style callback (err, result). If omitted, returns a Promise. |
Return Value
If done is provided, calls done(null, promptString). If done is omitted, returns a Promise<string>.
If options.run is not provided, returns a default prompt built from traits: 'You are a helpful assistant. Adopt these traits: key1: value1, key2: value2.' or 'You are a helpful assistant.' if no traits.
createRepeatedPromptTemplate(options)
Creates a composed smart template that repeats a core template N times, useful for LLM instruction-following reinforcement.
Signature
function createRepeatedPromptTemplate(options: {
coreTemplate: string,
endTemplate: string,
repeatCount?: number,
separator?: string,
includeDisclaimer?: boolean,
disclaimerTemplate?: string
}): SmartTemplate
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.coreTemplate |
string |
Yes | — | The template repeated repeatCount times. |
options.endTemplate |
string |
Yes | — | The template appended once after all repeats. |
options.repeatCount |
number |
No | 2 |
How many times to repeat coreTemplate. Clamped to >= 1. |
options.separator |
string |
No | '\n\n' |
Separator between template segments. |
options.includeDisclaimer |
boolean |
No | true |
Whether to prepend the disclaimer template. |
options.disclaimerTemplate |
string |
No | 'I will repeat the instructions and content twice for clarity. Follow the output format exactly.' |
Disclaimer text prepended before repeats. |
Error Conditions
| Condition | Error Type | Message |
|---|---|---|
coreTemplate is falsy |
Error |
'createRepeatedPromptTemplate requires a coreTemplate.' |
endTemplate is falsy |
Error |
'createRepeatedPromptTemplate requires an endTemplate.' |
Return Value
Returns a composed SmartTemplate (from @polyweave/smart-templates) with segments: [disclaimer, coreTemplate × repeatCount, endTemplate].
createRepeatedPromptBuilder(options)
Creates a template string interpreter from a repeated prompt template.
function createRepeatedPromptBuilder(options: {
coreTemplate: string,
endTemplate: string,
repeatCount?: number,
separator?: string,
includeDisclaimer?: boolean,
disclaimerTemplate?: string
}): TemplateStringInterpreter
Same parameters as createRepeatedPromptTemplate. Returns a TemplateStringInterpreterConstructor result — a function that interprets template-strings with the repeated pattern.
Storage Adapters
All adapters extend BaseStorageAdapter and implement the same interface (callback-style). Operations that receive no callback will throw if the base class _notImplemented is reached, though all concrete adapters override every method.
BaseStorageAdapter
new BaseStorageAdapter(options?: object)
Abstract base class. All methods delegate to _notImplemented(method, done), which either calls done(new Error(...)) if a callback is provided, or throws. Concrete subclasses override all methods.
MemoryAdapter
new MemoryAdapter(options?: object)
In-memory storage using Map. All data is JSON-cloned on read/write.
| Method | Signature | Description |
|---|---|---|
create |
(persona: object, done: (err, record) => void) |
Creates a persona. Calls back with error if persona.id is missing or already exists. |
get |
(id: string, done: (err, record) => void) |
Retrieves a persona by id. Returns null if not found (no error). |
update |
(id: string, changes: object, done: (err, record) => void) |
Merges changes into the persona. Calls back with error if persona not found. |
delete |
(id: string, done: (err, result) => void) |
Deletes a persona and its versions. Always succeeds (idempotent). |
list |
(filters?: object, done: (err, records) => void) |
Lists personas. Filters: domain, ids (array), limit, offset. |
saveVersion |
(id: string, version: number, snapshot: object, done: (err, entry) => void) |
Saves a versioned snapshot for a persona. |
getVersion |
(id: string, version: number, done: (err, entry) => void) |
Retrieves a specific version. Returns null if not found. |
listVersions |
(id: string, done: (err, entries) => void) |
Lists all versions for a persona. Returns empty array if none. |
AtomStoreAdapter
new AtomStoreAdapter(atomStore: object, options?: {
personaType?: string,
versionType?: string
})
Delegates to any atom store that exposes create/insert/upsert/write, get/read/fetch, delete/remove, and list/query/search methods. Method dispatch probes for the first available method name from each group. Supports both callback-based and sync/Promise-based stores.
StateStoreAdapter
new StateStoreAdapter(options?: {
docStore?: DocumentStore,
docStoreOptions?: object,
personaPrefix?: string,
versionPrefix?: string,
now?: () => number
})
Stores personas and versions using @polyweave/state DocumentStore sections. If docStore is not provided, creates one via createDocumentStore(docStoreOptions). Persona data is stored in sections keyed '<personaPrefix>:<id>' and versions in '<versionPrefix>:<id>'.
Tests
npm test
Covers:
- index.test.js — Topology scan (self-report, assertions, bidirectional wrapping), system prompt injection (static via options, dynamic via META, passthrough without prompt), end-to-end flow with responding mock, mutable prompt update across requests.
- manager.test.js — Scope buffering and system prompt prepend, passthrough without prompt, PARAM frame ordering before injected system prompt.
- state-store-adapter.test.js — StateStoreAdapter create/get/saveVersion/listVersions round-trip with DocumentStore.
Requirements
- Node.js 22 or later.
- ESM only (
"type": "module"). - Dependencies:
@polyweave/core,@polyweave/state,@polyweave/smart-templates,uuid.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/errors | * |
| @polyweave/smart-templates | * |
| @polyweave/state | * |
| uuid | ^9.0.0 |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |