Documentation/Persistent Instructions (NextGen Plugin)
v2.0.0-beta.1Snapshot bebdcd1Markdown ↗

Persistent Instructions (NextGen Plugin)

Persistent Instructions (NextGen Plugin)

⚠️ Deprecated in favour of MemoryPluginNextGen. This plugin keeps working unchanged for existing integrations — no breaking change — but new code should prefer the memory plugin, which replaces dumb KV with append-only facts + LLM-synthesised profiles that evolve from observations.

PersistentInstructionsPluginNextGen is a context plugin that stores agent-level custom instructions on disk as individually keyed entries. Unlike InContextMemory (volatile key-value pairs), persistent instructions survive process restarts and are automatically loaded when the agent starts.

Key Difference from InContextMemory

Feature InContextMemory Persistent Instructions
Storage In-memory (volatile) Disk (persistent JSON)
Survives restarts No Yes
Best for Session state, counters, flags Agent personality, learned rules
LLM can modify Yes (store_set("whiteboard", ...)) Yes (store_set/store_delete("instructions", ...))
Auto-loaded Via session restore Always on agent start
Default capacity 20 entries, 40,000 tokens 50 entries, 50,000 chars total

Quick Setup

typescript
import { Agent } from 'oneringai';

const agent = Agent.create({
  connector: 'openai',
  model: 'gpt-4.1',
  context: {
    model: 'gpt-4.1',
    agentId: 'my-assistant',  // Used for storage path
    features: {
      persistentInstructions: true,  // Enables PersistentInstructionsPluginNextGen
    },
  },
});

// Plugin is accessible via ctx.getPlugin('persistent_instructions')
// Instructions are automatically loaded from disk on first context prepare

Manual Setup

For more control, you can set up the plugin manually:

typescript
import { AgentContextNextGen, PersistentInstructionsPluginNextGen } from 'oneringai';

const ctx = AgentContextNextGen.create({ model: 'gpt-4.1' });

// Create and configure plugin
const plugin = new PersistentInstructionsPluginNextGen({
  agentId: 'my-assistant',
  maxTotalLength: 100000,  // Characters across all entries, default is 50000
  maxEntries: 50,          // Maximum number of keyed entries, default is 50
});

// Register with context
ctx.registerPlugin(plugin);

// Set instructions programmatically (keyed entries)
await plugin.set('personality', 'Always respond in a friendly tone.');
await plugin.set('formatting', 'Prefer bullet points for lists.');

Configuration Options

typescript
interface PersistentInstructionsConfig {
  /** Agent ID - used to determine storage path (required) */
  agentId: string;

  /** Custom storage implementation (default: FilePersistentInstructionsStorage) */
  storage?: IPersistentInstructionsStorage;

  /** Maximum total content length across all entries in characters (default: 50000) */
  maxTotalLength?: number;

  /** Maximum number of entries (default: 50) */
  maxEntries?: number;
}

Storage Path

Instructions are stored at:

  • Unix/macOS: ~/.oneringai/agents/<agentId>/custom_instructions.json
  • Windows: %APPDATA%/oneringai/agents/<agentId>/custom_instructions.json

The agent ID is sanitized to be filesystem-safe (lowercase, special chars replaced with underscores).

Available Tools

The LLM manages persistent instructions through the unified store_* tools with store="instructions":

store_set("instructions", ...)

Add or update a single instruction by key:

jsonc
// Tool call from LLM
{
  "name": "store_set",
  "arguments": {
    "store": "instructions",
    "key": "personality",
    "data": {
      "content": "Always be friendly and helpful. Use clear, simple language."
    }
  }
}
// Returns: { "success": true, "message": "Instruction 'personality' added", "key": "personality", "contentLength": 57 }

store_delete("instructions", ...)

Remove a single instruction by key:

jsonc
// Tool call from LLM
{
  "name": "store_delete",
  "arguments": {
    "store": "instructions",
    "key": "personality"
  }
}
// Returns: { "deleted": true, "key": "personality" }

store_list("instructions")

List all instructions with their keys and content:

jsonc
// Tool call from LLM
{
  "name": "store_list",
  "arguments": {
    "store": "instructions"
  }
}
// Returns: {
//   "total": 2,
//   "entries": [
//     { "key": "personality", "contentLength": 57, "createdAt": ..., "updatedAt": ... },
//     { "key": "formatting", "contentLength": 35, "createdAt": ..., "updatedAt": ... }
//   ]
// }

store_action("instructions", "clear", ...)

Remove all instructions (requires confirmation):

jsonc
// Tool call from LLM
{
  "name": "store_action",
  "arguments": {
    "store": "instructions",
    "action": "clear",
    "params": { "confirm": true }
  }
}
// Returns: { "success": true, "action": "clear", "message": "All custom instructions cleared" }

Direct API Access

The plugin provides a programmatic API for direct manipulation:

typescript
const plugin = ctx.getPlugin<PersistentInstructionsPluginNextGen>('persistent_instructions')!;

// Add/update entry by key
await plugin.set('personality', 'Always be friendly and helpful.');
await plugin.set('formatting', 'Use bullet points for lists.');

// Get single entry by key
const entry = await plugin.get('personality');  // InstructionEntry | null
// entry = { id: 'personality', content: '...', createdAt: ..., updatedAt: ... }

// Get all entries (sorted by createdAt)
const all = await plugin.get();  // InstructionEntry[] | null

// List metadata for all entries
const list = await plugin.list();
// [{ key: 'personality', contentLength: 35, createdAt: ..., updatedAt: ... }, ...]

// Remove a single entry
await plugin.remove('formatting');  // true if found, false if not

// Clear all
await plugin.clear();

Context Output Format

When the LLM context is prepared, persistent instruction entries are rendered as markdown sections:

markdown
### personality
Always be friendly and helpful. Use clear, simple language.

### formatting
- Use bullet points for lists
- Keep responses concise

### user_preferences
The user prefers dark mode and verbose explanations.

Session Persistence

PersistentInstructionsPluginNextGen supports state serialization. The state format includes all entries:

typescript
// State includes all entries
const state = plugin.getState();
// state = { entries: [...], agentId: "my-assistant", version: 2 }

// Restore state (useful for in-memory state sync)
plugin.restoreState(state);
// Also handles legacy format: { content: string | null, agentId: string }

Use Cases

Ideal for:

  • Agent personality/behavior - Tone, style, expertise areas
  • User preferences - Formatting, verbosity, topics of interest
  • Learned rules - Patterns discovered during conversation
  • Tool usage guidelines - When to use specific tools
  • Custom instructions - Domain-specific knowledge

Example: Building a Learning Assistant

typescript
const agent = Agent.create({
  connector: 'openai',
  model: 'gpt-4.1',
  instructions: `You are a learning assistant. When the user expresses preferences or
gives feedback about your responses, use store_set("instructions", key, { content }) to remember them for
future sessions. Use descriptive keys like "user_preferences", "response_style", etc.
Review your instructions with store_list("instructions") at the start of each conversation.`,
  context: {
    model: 'gpt-4.1',
    agentId: 'learning-assistant',
    features: { persistentInstructions: true },
  },
});

// User: "I prefer when you explain things with analogies"
// Agent calls: store_set("instructions", "response_style", { content: "Explain concepts using analogies when possible" })
// Next session, agent sees this in context automatically

Best Practices

1. Use Descriptive Keys

typescript
// GOOD: Descriptive, categorical keys
await plugin.set('personality', 'Be friendly and approachable');
await plugin.set('formatting_rules', 'Use bullet points for lists');
await plugin.set('domain_knowledge', 'User works in fintech');

// AVOID: Generic or numbered keys
await plugin.set('rule1', '...');  // Not descriptive
await plugin.set('misc', '...');   // Too vague

2. One Concern Per Entry

typescript
// GOOD: Each entry covers one topic
await plugin.set('tone', 'Use formal language');
await plugin.set('code_style', 'Use TypeScript, follow existing patterns');
await plugin.set('response_length', 'Keep responses concise, 2-3 paragraphs max');

// AVOID: Mixing concerns in one entry
await plugin.set('rules', 'Be formal. Use TypeScript. Keep it short.');

3. Combine with InContextMemory

Use both systems for their strengths:

typescript
// Persistent instructions for long-term knowledge
// - Agent personality
// - User preferences
// - Learned rules

// InContextMemory for session-specific state
// - Current task progress
// - Temporary flags
// - Running totals

4. Set Reasonable Limits

typescript
// For simple agents
const agent = Agent.create({
  context: {
    features: { persistentInstructions: true },
    plugins: { persistentInstructions: { maxTotalLength: 10000, maxEntries: 20 } },
  },
});

// For complex agents with lots of learned rules
const agentVariant2 = Agent.create({
  context: {
    features: { persistentInstructions: true },
    plugins: { persistentInstructions: { maxTotalLength: 100000, maxEntries: 100 } },
  },
});

Upgrade Guide (from single-string to KVP)

If upgrading from the previous single-string persistent instructions:

  1. File storage: Auto-migrated. Legacy custom_instructions.md files are read as a single legacy_instructions entry and converted to custom_instructions.json on next save. No action needed.
  2. Custom storage backends: Update load() to return InstructionEntry[] | null and save() to accept InstructionEntry[] instead of string.
  3. Tool API: instructions_append is removed — use store_set("instructions", key, { content }) to add new entries. instructions_get is removed — use store_list("instructions") to see all entries.
  4. Programmatic API: plugin.set(content) → plugin.set(key, content). plugin.append(section) → plugin.set(newKey, section). plugin.get() now returns InstructionEntry[] | null (or a single InstructionEntry when called with a key).
  5. Session state: Existing saved sessions with old format ({ content: string | null }) are auto-migrated on restoreState().

Documentation snapshot · 2026-11-02Suggest a correction ↗

Search documentation

Type to search the v2 documentation.