Event-driven Agent-Based Modeling framework for TypeScript
npm install simullmEvent-driven Agent-Based Modeling framework for TypeScript.
- Event-driven architecture: Agents respond to actions/messages in a reactive system
- Type-safe: Full TypeScript support with generic types for state and actions
- Flexible state management: Both global state and agent internal state
- Exit conditions: Required termination logic prevents infinite loops
- Async support: Handles both synchronous and asynchronous agent actions
- Action cascading: Agents can dispatch new actions during processing
- Memory management: Agents can maintain internal state across turns
``bash`
npm install simullmor
bun add simullm
`typescript
import { createSimulation, createAgent } from 'simullm';
// Define your state and action types
interface CounterState {
value: number;
}
type CounterAction =
| { type: "INCREMENT"; amount: number }
| { type: "RESET" };
// Create agents
const counterAgent = createAgent
"counter",
(action, context) => {
if (action.type === "INCREMENT") {
context.updateGlobalState(state => ({
value: state.value + action.amount
}));
} else if (action.type === "RESET") {
context.updateGlobalState(state => ({ value: 0 }));
}
}
);
// Create simulation with required exit condition
const simulation = createSimulation
initialGlobalState: { value: 0 },
agents: [counterAgent],
shouldExit: ({ actionCount, globalState }) =>
actionCount >= 10 || globalState.value >= 100
});
// Run simulation
await simulation.dispatch({ type: "INCREMENT", amount: 5 });
await simulation.dispatch({ type: "INCREMENT", amount: 15 });
console.log(simulation.getGlobalState()); // { value: 20 }
console.log(simulation.getActionCount()); // 2
`
$3
Actions are messages passed through the system. All agents receive every action and can choose to respond.$3
Every simulation must define when to stop via the shouldExit function. You have access to:
- globalState: Current global state
- agentStates: All agent internal states
- lastAction: The action that was just processed
- actionCount: Total number of processed actionsExamples
$3
`typescript
const simulation = createSimulation({
initialGlobalState: { value: 0 },
agents: [counterAgent],
shouldExit: ({ actionCount }) => actionCount >= 5
});
`$3
`typescript
const simulation = createSimulation({
initialGlobalState: { temperature: 20 },
agents: [heatingAgent, coolingAgent],
shouldExit: ({ globalState }) =>
globalState.temperature >= 100 || globalState.temperature <= 0
});
`$3
`typescript
const simulation = createSimulation({
initialGlobalState: { score: 0 },
agents: [learningAgent],
shouldExit: ({ agentStates }) =>
agentStates["learner"]?.experience >= 1000
});
`API Reference
$3
Creates a new simulation instance.
Config:
-
initialGlobalState: TGlobalState - Starting global state
- agents: Agent[] - Array of agents to participate
- shouldExit: (context: ExitContext) => boolean - Required exit conditionReturns:
EventSimulation$3
Creates a new agent.
Parameters:
-
id: string - Unique agent identifier
- onAction: (action, context) => void | Promise - Action handler
- initialInternalState?: TInternalState - Optional internal state$3
-
dispatch(action) - Dispatch an action to all agents
- exit() - Returns a promise that resolves when simulation exits
- getGlobalState() - Get current global state
- getAgentInternalState(agentId) - Get agent's internal state
- getAllAgentStates() - Get all agent internal states
- getActionCount() - Get total processed actions
- hasSimulationExited() - Check if simulation has terminatedAdvanced Usage
$3
Each agent receives a context object with:
-
globalState - Current global state (read-only)
- internalState - Agent's private state (read-only)
- allAgents - Array of all agents with their IDs and internal states
- dispatch(action) - Dispatch new actions
- updateGlobalState(updater) - Modify global state
- updateInternalState(updater) - Modify agent's internal state$3
Use
context.allAgents to coordinate between agents:`typescript
const coordinator = createAgent("coordinator", (action, context) => {
// Find available workers
const availableWorkers = context.allAgents
.filter(agent => agent.id !== "coordinator" && !agent.internalState?.busy)
.map(agent => agent.id);
// Assign tasks to available workers
availableWorkers.forEach(workerId => {
context.dispatch({ type: "ASSIGN_TASK", agentId: workerId });
});
});
`$3
Use the
exit() method to wait for simulations to complete:`typescript
// Start simulation
simulation.dispatch({ type: "START" });// Wait for completion
await simulation.exit();
console.log("Simulation completed!");
console.log("Final state:", simulation.getGlobalState());
`$3
See the
/experiments directory for complete examples:
- Counter simulation with turn-based agents
- Predator-prey ecosystem simulation
- Market trading simulation with LLM agentsMigration from v0.1.x
Breaking Change: The
shouldExit property is now required in SimulationConfig.Before (v0.1.x):
`typescript
const simulation = createSimulation({
initialGlobalState: { value: 0 },
agents: [myAgent]
// Could run infinitely
});
`After (v0.2.x):
`typescript
const simulation = createSimulation({
initialGlobalState: { value: 0 },
agents: [myAgent],
shouldExit: ({ actionCount }) => actionCount >= 10 // Required
});
`Development
$3
Use the automated release script:
`bash
bun run release
`This handles:
- Version bumping (patch/minor/major)
- Changelog updates
- Git commit and tagging
- Pushing to remote
- npm publishing
See
scripts/README.md` for details.MIT