@msm-core/mini
v0.4.0
Published
Portable AI agent execution loop — brain-agnostic, zero embedded databases
Maintainers
Readme
@msm-core/mini
Portable AI agent execution loop — brain-agnostic, stateless, zero embedded databases.
@msm-core/mini is the execution engine extracted from msm-agent. It owns only the loop — brain calls, tool dispatch, guards, and session state in Redis. Everything else (memory, RAG, channels) belongs to your application.
Install
npm install @msm-core/mini
# peer deps — install only the ones you use:
npm install openai # OpenAI / compatible (Gemini, etc.)
npm install @anthropic-ai/sdk # Anthropic
npm install @google/generative-ai # Google Gemini (native)
npm install ioredis # required at runtimeQuick Start
import {
createAgent,
createGeminiBrain,
parseDefinition,
} from "@msm-core/mini";
const brain = createGeminiBrain({ model: "gemini-2.5-flash" });
const agent = createAgent({
definition: "./agent.md", // or a parsed AgentDefinition object
brain,
redis: { url: process.env.REDIS_URL! },
tools: [
{
name: "search_kb",
description: "Search the knowledge base",
parameters: {
query: { type: "string", description: "Search query", required: true },
},
async execute(args) {
const results = await myKb.search(String(args.query));
return { status: "ok", result: { results } };
},
},
],
});
const outcome = await agent.handle({
sessionId: "user-123",
message: "What is the market size for solar in Egypt?",
context: {
memories: assembledContext, // from @msm-core/context
userProfile: { country: "EG", language: "ar" },
},
});
console.log(outcome.text);Agent Definition File
Create a Markdown file (agent.md) that the parser reads at startup:
# Feasibility Analyst
domain: feasibility
language: ar
## Persona
name: Jarvis
style: professional, concise
## Brain
provider: gemini
model: gemini-2.5-flash
## Capabilities
- Conduct investment feasibility studies
- Analyze financial projections
- Retrieve and synthesize research documents
## Limits
maxIterations: 40
costCapPerTask: 1.50
timeoutMs: 600000
## Sections
- Executive Summary
- Market Analysis
- Financial Projections
- Risk Assessment
- RecommendationsSupported providers: openai | anthropic | gemini | ollama
Context Injection
The agent never queries external stores — your app assembles context before each call and passes it in AgentEvent.context. Use @msm-core/context to do this automatically.
import { createContextAssembler } from "@msm-core/context";
import { QdrantAdapter } from "@msm-core/context/adapters";
const assembler = createContextAssembler({
tiers: [{ name: "kb", priority: 1, adapters: [qdrantAdapter] }],
budget: { maxTokens: 6000 },
});
// In your request handler:
const ctx = await assembler.build({ text: userMessage, sessionId });
const outcome = await agent.handle({
sessionId,
message: userMessage,
context: {
memories: ctx.results.map((r) => ({
source: r.source,
content: r.content,
score: r.score,
})),
},
});Brains
| Factory | Provider | Env var |
| ---------------------------- | --------------------------- | ------------------- |
| createGeminiBrain(opts) | Google Gemini | GEMINI_API_KEY |
| createOpenAIBrain(opts) | OpenAI / compatible | OPENAI_API_KEY |
| createAnthropicBrain(opts) | Anthropic Claude | ANTHROPIC_API_KEY |
| createOllamaBrain(opts) | Ollama (local) | — |
| buildBrain(def) | Auto-select from definition | — |
buildBrain(definition) reads the ## Brain section of an AgentDefinition and returns the correct brain automatically.
HTTP Server
Wrap any agent with a minimal HTTP server for internal service-to-service calls:
import { createAgentServer } from "@msm-core/mini";
const server = createAgentServer(agent, { port: 4001 });
await server.start();
// GET /health → { ok: true, uptime: N }
// POST /v1/event → { sessionId, message } → LoopOutcome JSONGuards
Guards are hard limits that terminate the loop and return a forced response. Configure in the agent definition ## Limits section or override in code:
| Guard | Default | Description |
| ------------------------ | ---------- | --------------------------------------- |
| maxIterations | 60 | Max brain-call iterations per task |
| costCapPerTask | $2.50 | Max spend per task (USD) |
| timeoutMs | 900 000 ms | Wall-clock timeout |
| maxToolCallsPerTask | 200 | Max tool invocations |
| maxConsecutiveFailures | 3 | Abort after N consecutive tool failures |
Redis Schema
session:{prefix}:{id}:history # Message history (TTL: 24h)
session:{prefix}:{id}:metadata # Iteration count, cost accumulator
session:{prefix}:{id}:tools:dedup # Tool-call dedup cache (TTL: 5min)
agent:{prefix}:control:{id} # Kill/pause signalNo long-term storage — Redis is ephemeral session cache only. Your app owns persistence.
Architecture
Your App
│
├── assembles context (via @msm-core/context)
├── calls agent.handle({ sessionId, message, context })
│
└── @msm-core/mini
├── gates (ack, business hours)
├── session lock (Redis distributed)
├── context builder (persona + memories + tools + history)
├── brain call (Gemini / OpenAI / Anthropic / Ollama)
├── guards (iterations, cost, timeout, failures)
└── tool executor (validate → dedup check → execute → record)
│
└── Your Tools (passed explicitly — no registries)API Reference
createAgent(config)
interface AgentConfig {
definition: string | AgentDefinition; // .md file path or parsed object
brain: Brain; // from createGeminiBrain() etc.
redis: {
url: string;
prefix?: string; // key namespace (default: "msm")
ttl?: { history?: number; document?: number };
};
tools: Tool[];
guards?: Partial<GuardConfig>;
gates?: GateConfig;
hooks?: AgentHooks; // lifecycle callbacks
}agent.handle(event)
interface AgentEvent {
sessionId: string;
message: string;
context?: {
systemPrompt?: string;
memories?: MemoryEntry[];
userProfile?: Record<string, unknown>;
customFields?: Record<string, unknown>;
};
}
// Returns: Promise<LoopOutcome>LoopOutcome
interface LoopOutcome {
type:
| "response"
| "tool_calls"
| "error"
| "clarify"
| "escalate"
| "suppressed";
sessionId: string;
taskId: string;
text?: string;
metrics: {
iterations: number;
totalCostUsd: number;
durationMs: number;
totalToolCalls: number;
};
terminatedBy?: string; // guard type that ended the loop, if any
}Migrating from msm-agent
| msm-agent | @msm-core/mini |
| ------------------------------- | ----------------------------------------------- |
| loadAgent(path) | parseDefinition(path) |
| SQLiteMemoryAdapter | ❌ removed — app owns DB |
| MongoMemoryAdapter | ❌ removed — app owns DB |
| QdrantKnowledgeAdapter | Use @msm-core/context + QdrantAdapter |
| SkillRegistry.register(skill) | Pass tools directly to createAgent({ tools }) |
| createAgentHub(...) | ❌ removed |
| createMcpServer(...) | ❌ removed |
| ManualEventAdapter | Call agent.handle() directly |
License
MIT
