preste docs

Agent SDK

@vauban-org/agent-sdk is the TypeScript library powering preste and all Vauban production agents. It provides a unified agent runtime with four interchangeable strategies, tiered conversation memory, a skill registry, and ports for Brain integration.

Installation

pnpm add @vauban-org/agent-sdk

Node.js requirement. Node.js 20+ (ESM). The SDK is published as type: "module".

Agent

The unified runtime. One class, four strategies. Each call to run(task) executes a full cycle and returns an AgentRunResult.

Basic usage

import { createAgent } from "@vauban-org/agent-sdk";

const agent = createAgent({
  agentId: "my-agent",
  strategy: "react",
  systemPrompt: "You are a helpful assistant.",
  tools: registry,
  llmFn: myLlmFn,
  maxSteps: 10,
});

const result = await agent.run("List files in the current directory");
console.log(result.finalMessage);

AgentConfig options

Option Type Description
agentId string Agent identifier used for telemetry and skill capture.
strategy AgentStrategyName "ooda" | "react" | "plan" | "one-shot". Default: "ooda".
systemPrompt string Base system prompt. Strategies may augment it.
tools ToolRegistry Tool registry exposed to the strategy.
llm LLMProviderPort LLM port used by the OODA strategy.
llmFn StrategyLLMCompletionFn Lower-level completion function for react / plan / one-shot strategies.
maxSteps number Maximum steps per cycle. Default: 10.
hooks AgentHooks Optional hooks: onStep, onPhaseComplete.
outcomeMapping (result) => OutcomeRecord | null Maps a cycle result to an outcome record for telemetry and skill capture.
skillCapture SkillCaptureOptions Enable Hermes Procedural Learning Loop: capture skills after successful cycles above quality threshold.

Strategies

Strategy Loop Use case
"ooda" Observe → Orient → Decide → Act → Reflect Full OODA ceremony, existing OODAAgentImpl consumers.
"react" Thought → Action → Observe Tool-heavy tasks: file ops, API calls, multi-step reasoning.
"plan" Plan → Execute → Synthesize Tasks that benefit from upfront decomposition before execution.
"one-shot" Single LLM call Simple classification, extraction, or generation with no tool use.

AgentRunResult

interface AgentRunResult {
  runId: string;
  agentId: string;
  strategy: AgentStrategyName;
  finalMessage: string;
  stopReason: "complete" | "max_steps" | "error";
  stepCount: number;
  tokensUsed: { in: number; out: number };
  durationMs: number;
  outcome?: OutcomeRecord;
  skillCaptured?: boolean;
}

Quality gates: computeQualityOrNull / isIdleCycle

Agents must distinguish productive cycles (work was attempted) from idle cycles (nothing to process). This distinction drives the L1→L2 promotion gate: only productive cycles count toward the 30-cycle, 0.7-quality threshold.

import { computeQualityOrNull, isIdleCycle } from "@vauban-org/agent-sdk";

const agent = createAgent({
  agentId: "release-broadcaster",
  strategy: "react",
  systemPrompt,
  tools,
  llmFn,
  outcomeMapping: (result) => {
    // No work available this cycle? Return null ; idle, excluded from quality avg.
    if (isIdleCycle({ result })) return null;

    return {
      quality: computeQualityOrNull({ result, successKeywords: ["published"] }) ?? 0,
      value_cents: 0,
    };
  },
});

ADR-ECO-052. Idle cycles must return null from computeQualityOrNull. The level gate filters WHERE outcome_quality IS NOT NULL before computing the promotion average. Event-driven agents (queue consumers, webhook listeners, scheduled pollers) are idle most of the time ; conflating idle and productive cycles would block legitimate L2 promotions.

ConversationContext

Tiered memory for multi-turn sessions. Two tiers: working memory (last N turns verbatim, default 6) and episodic memory (older turns replaced by LLM-generated summaries).

Constructor

import { ConversationContext } from "@vauban-org/agent-sdk";

const ctx = new ConversationContext({ workingMemorySize: 6 });

addTurn(role, content, meta?)

ctx.addTurn("user", "What is the capital of France?");
ctx.addTurn("assistant", "Paris.", {
  tokensIn: 12, tokensOut: 3, model: "qwen3-8b"
});

compact(llmFn, opts?)

Summarize turns older than workingMemorySize using an LLM function. Returns a CompactionReport with token estimates before/after.

const report = await ctx.compact(
  async (prompt) => llm.complete(prompt),
  { keepLast: 6 }
);
// report.turnsBefore, report.turnsAfter, report.tokensEstimatedBefore, ...

maybeCompact(model, llmFn, threshold?)

Compact only when estimated fill ratio exceeds threshold (default: 0.70). Returns true if compaction was triggered.

const triggered = await ctx.maybeCompact(
  "qwen3-8b",
  llmFn,
  0.70
);

toMessages(strategy, systemPrompt?)

Return conversation history in the format required by the active strategy:

  • ooda ; returns a string suffix appended after the system prompt.
  • react / plan ; returns LLMMessage[] array with summaries as system prefix + user/assistant turns.
  • one-shot ; returns [] (no history).

Serialization: toJSON() / fromJSON()

Persist the context across process restarts via ConversationContextSnapshot:

// Serialize
const snapshot = ctx.toJSON();
await fs.writeFile("session.json", JSON.stringify(snapshot));

// Restore
const raw = JSON.parse(await fs.readFile("session.json", "utf8"));
const restored = ConversationContext.fromJSON(raw);

SkillRegistry

Register and execute SKILL.md skills at runtime. Skills are discovered from ~/.preste/skills/ and ./.preste/skills/.

import { SkillRegistry } from "@vauban-org/agent-sdk";

const registry = new SkillRegistry();

// Register a skill by directory path
await registry.register("~/.preste/skills/web-search");

// Look up by name
const skill = registry.lookup("web-search");

// Run a skill
const result = await skill.run({ task: "latest news on Starknet", llm });

SemanticMemoryPort

Interface for Brain integration. Implement this port to connect any semantic memory backend.

import type { SemanticMemoryPort } from "@vauban-org/agent-sdk";

interface SemanticMemoryPort {
  query(q: string, opts?: { tags?: string[]; limit?: number }): Promise<Entry[]>;
  archive(entry: {
    content: string;
    category: string;
    tags: string[];
    confidence: number;
  }): Promise<void>;
}

The built-in BrainHttpClient (CLI package) implements this port against the Brain MCP HTTP API. Supply your own implementation to connect a different backend.

BrainConversationConnector

Two functions that bridge ConversationContext with Brain for cross-session memory persistence.

createBrainCompactionLlmFn(semanticMemory, localLlmFn, sessionTag)

Wraps a local compaction LLM function to additionally archive each compaction summary to Brain (fail-soft: Brain unavailability never interrupts the session).

import { createBrainCompactionLlmFn } from "@vauban-org/agent-sdk";

const brainLlmFn = createBrainCompactionLlmFn(
  brainClient,   // SemanticMemoryPort
  localLlmFn,   // CompactionLLMFn ; local LLM (always called first)
  runId          // session tag (e.g. run ID)
);

await ctx.compact(brainLlmFn);

restoreSessionContext(semanticMemory, sessionTag, limit?)

Query Brain for prior compaction summaries tagged with sessionTag. Returns a formatted string suitable for injection as a system prompt prefix, or null if nothing is found. Always fail-soft.

import { restoreSessionContext } from "@vauban-org/agent-sdk";

const prior = await restoreSessionContext(
  brainClient,   // SemanticMemoryPort
  "my-project",  // session tag
  5              // max prior summaries (default: 5)
);

if (prior) {
  systemPrompt = prior + "\n\n" + systemPrompt;
}

Full example: multi-turn react agent with Brain memory

import {
  createAgent,
  ConversationContext,
  createBrainCompactionLlmFn,
  restoreSessionContext,
  computeQualityOrNull,
  isIdleCycle,
} from "@vauban-org/agent-sdk";

const ctx = new ConversationContext();
const brain = new BrainHttpClient(process.env.BRAIN_API_KEY);

// Restore prior session context from Brain on startup
const prior = await restoreSessionContext(brain, "my-agent");
let systemPrompt = "You are a helpful assistant.";
if (prior) systemPrompt = prior + "\n\n" + systemPrompt;

// Compaction LLM function that archives summaries to Brain
const compactFn = createBrainCompactionLlmFn(brain, localLlmFn, runId);

const agent = createAgent({
  agentId: "my-agent",
  strategy: "react",
  systemPrompt,
  tools: registry,
  llmFn: myLlmFn,
  outcomeMapping: (result) => {
    if (isIdleCycle({ result })) return null;
    return { quality: computeQualityOrNull({ result }) ?? 0, value_cents: 0 };
  },
});

// Per-turn loop
for (const userInput of inputs) {
  ctx.addTurn("user", userInput);
  await ctx.maybeCompact("qwen3-8b", compactFn);

  const result = await agent.run(userInput);
  ctx.addTurn("assistant", result.finalMessage);
}