Polyweave

@polyweave/prompt-manager (1.0.6)

Published 2026-07-11 15:30:09 +00:00 by Dvorak

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 START and END into a scope.
  • On scope END, emits START → PARAM frames (if any) → system prompt TEXT frame (if set) → content TEXT/IMAGE/AUDIO/VIDEO frames → END downstream.
  • 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 with metadata.role: 'system' replaces currentSystemPrompt. 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 *

Keywords

ai personas prompts llm optimization polyweave
Details
npm
2026-07-11 15:30:09 +00:00
66
John Dvorak
SEE LICENSE IN LICENSE
latest
11 KiB
Assets (1)
Versions (4) View all
1.0.6 2026-07-11
1.0.3 2026-07-11
1.0.2 2026-07-11
1.0.1 2026-07-10