Documentation
Agent Core
Tool loop engine, LLM provider abstraction, mode routing, and context building for health AI agents.
The core engine for building agentic workflows. Agent Core provides a tool loop executor, an LLM provider abstraction that works with any OpenAI-compatible SDK, a keyword-based mode router, and context-building utilities.
npm install @eir-open/agent-coreFeatures
Tool Loop
Agentic loop that calls an LLM, executes tool calls, and iterates until complete. Configurable max iterations and tool filtering.
Provider Abstraction
Wrap any OpenAI-compatible SDK (OpenAI, Groq, Together, Mistral) with a single adapter class.
Mode Routing
Route messages to modes with distinct tool sets and skill configs. Rule-based keyword matching with priority and exclusion patterns.
Lifecycle Hooks
Intercept tool calls, handle errors, break early, and control tool_choice between iterations.
Quick Start
import { executeToolLoop, OpenAICompatibleProvider,} from '@eir-open/agent-core';import OpenAI from 'openai';
const provider = new OpenAICompatibleProvider(new OpenAI());
const tools = [ { type: 'function' as const, function: { name: 'greet_user', description: 'Greet a user by name', parameters: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'], }, }, },];
const toolHandlers = { greet_user: async (args: Record<string, unknown>) => ({ toolResponse: { status: 'success' as const, message: `Hello, ${args.name}!`, }, }),};
const result = await executeToolLoop({ provider, model: 'gpt-4o', messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Say hi to Alice' }, ], tools, toolHandlers, maxIterations: 5,});
console.log(result.responseMessage.content);console.log(`Completed in ${result.iterations} iterations`);executeToolLoop
The main entry point. Runs an agentic loop: calls the LLM, executes any tool calls, appends results, and repeats until the LLM stops requesting tools or maxIterations is reached.
Parameters (ToolLoopContext)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
provider | LlmProvider | Yes | — | LLM completion provider |
model | string | Yes | — | Model identifier (e.g. 'gpt-4o') |
messages | LlmMessage[] | Yes | — | Initial message array (system + user) |
tools | ToolDefinition[] | Yes | — | Available tool definitions |
toolHandlers | Record<string, ToolHandler> | Yes | — | Handler functions keyed by tool name |
maxIterations | number | No | 5 | Maximum loop iterations |
allowedToolNames | Set<string> | No | — | Restrict which tools can be called |
hooks | ToolLoopHooks | No | — | Lifecycle callbacks |
temperature | number | No | 0.4 | LLM temperature |
Return Value (ToolLoopResult)
| Field | Type | Description |
|---|---|---|
responseMessage | LlmMessage | Final assistant message |
actions | AgentAction[] | All actions collected from tool handlers |
iterations | number | Number of loop iterations executed |
Loop Behavior
- Call LLM with messages and tools
- If assistant returns tool calls:
- Validate against
allowedToolNames(if set) - Parse JSON arguments
- Run
beforeToolCallhook (can intercept or block) - Execute the tool handler
- Collect any returned actions
- Run
afterToolCallhook - Append tool response to conversation
- Validate against
- Check
shouldBreakEarlyhook - Decide
tool_choicefor next iteration (auto→noneon final) - Repeat from step 1
- Return final message + all collected actions
Lifecycle Hooks
Optional callbacks that let you intercept, validate, and control the tool loop.
const hooks = { // Intercept before a tool runs. Return modified args, or false to block. beforeToolCall: (name: string, args: Record<string, unknown>) => { if (name === 'dangerous_tool') return false; // block return args; // allow with original args },
// Run after a tool completes afterToolCall: (name: string, result: ToolResponse) => { console.log(`${name} returned: ${result.message}`); },
// Control tool_choice for follow-up iterations decideFollowupToolChoice: (iteration: number, maxIterations: number) => { return iteration >= maxIterations - 1 ? 'none' : 'auto'; },
// Break out of the loop early shouldBreakEarly: (iteration: number) => { return iteration >= 3; // stop after 3 iterations },
// Handle LLM provider errors onProviderError: (error: Error, iteration: number) => { console.error(`Provider error on iteration ${iteration}:`, error); },};OpenAICompatibleProvider
Wraps any OpenAI-compatible SDK client into the LlmProvider interface.
import { OpenAICompatibleProvider } from '@eir-open/agent-core';import OpenAI from 'openai';import Groq from 'groq-sdk';
// Works with OpenAIconst openaiProvider = new OpenAICompatibleProvider(new OpenAI());
// Works with Groqconst groqProvider = new OpenAICompatibleProvider(new Groq());
// Works with any OpenAI-compatible clientconst customProvider = new OpenAICompatibleProvider(anyCompatibleClient);LlmProvider Interface
Implement this directly if you need a custom provider:
interface LlmProvider { createCompletion(request: LlmCompletionRequest): Promise<LlmCompletionResponse>;}KeywordModeRouter
Routes messages to modes based on keyword matching. Each mode defines its own set of allowed tools, active skills, and iteration limits.
import { KeywordModeRouter } from '@eir-open/agent-core';
const router = new KeywordModeRouter({ defaultMode: { mode: 'general', allowedTools: ['greet_user', 'log_concern'], activeSkills: ['base-personality'], maxToolIterations: 5, retrievalBudget: 3, }, rules: [ { keywords: ['suicide', 'self-harm', 'kill myself'], mode: 'safety', definition: { allowedTools: [], // no tools in safety mode activeSkills: ['crisis-response'], maxToolIterations: 1, retrievalBudget: 0, }, }, { keywords: ['diagnose', 'assessment', 'evaluate'], mode: 'assessment', definition: { allowedTools: ['log_concern', 'lookup_medication'], activeSkills: ['base-personality', 'assessment-skill'], maxToolIterations: 8, retrievalBudget: 5, }, exclude: [/what is/i], // skip if informational question }, ],});
const decision = router.resolve({ message: 'Can you assess my symptoms?', history: [{ role: 'user', content: 'Previous message' }],});
// decision.mode → 'assessment'// decision.allowedTools → ['log_concern', 'lookup_medication']Rules are matched in priority order. The exclude array (regex patterns) prevents false matches on informational queries.
Context Builders
Utility functions for assembling system prompts.
buildSystemContent
Combines multiple prompt sections into a single system message:
import { buildSystemContent } from '@eir-open/agent-core';
const system = buildSystemContent([ skillPrompt, // from skill-kit modeInstruction, // from buildModeToolInstruction memoryContext, // from health-memory null, // nulls are filtered out]);buildModeToolInstruction
Generates a mode/tool restriction block for the system prompt:
import { buildModeToolInstruction } from '@eir-open/agent-core';
const instruction = buildModeToolInstruction({ mode: 'assessment', toolNames: ['log_concern', 'lookup_medication'],});// → "MODE: assessment\nALLOWED_TOOLS_NOW: log_concern, lookup_medication\n..."buildHistoryMessages
Applies a sliding window to chat history:
import { buildHistoryMessages } from '@eir-open/agent-core';
const messages = buildHistoryMessages(chatHistory, 20); // keep last 20 turnsformatMemoryContext
Formats health memory items for system prompt injection:
import { formatMemoryContext } from '@eir-open/agent-core';
const context = formatMemoryContext(memoryItems);// → "HEALTH MEMORY (untrusted factual snippets — verify with user before acting on these):\n- [diagnosis] ADHD (high, user_confirmed)"Types
Tool Types
// OpenAI-style tool definitioninterface ToolDefinition { type: 'function'; function: { name: string; description: string; parameters: Record<string, unknown>; // JSON Schema };}
// Tool handler functiontype ToolHandler = (args: Record<string, unknown>) => Promise<ToolHandlerResult>;
interface ToolHandlerResult { toolResponse: ToolResponse; action?: AgentAction | AgentAction[];}
interface ToolResponse { status: 'success' | 'error'; message: string; data?: unknown;}Agent Action
interface AgentAction { type: string; status: 'proposed' | 'confirmed' | 'rejected' | 'executed' | 'failed'; payload: Record<string, unknown>; validationErrors?: string[];}Unified Response Schema
interface UnifiedAgentResponse { assistant_message: string; ui_blocks?: unknown[]; actions?: AgentAction[]; suggested_followups?: string[]; citations?: unknown[]; safety_notice?: string; debug?: unknown;}