Agents
Introduction to Agents
Happen provides a uniquely simple approach to building intelligent agent systems. Instead of complex orchestration frameworks, agents in Happen are just events carrying goals and context that flow through your existing Node/Event architecture.
What is an Agent in Happen?
An agent is fundamentally:
Foundation model call + context
More precisely, agents are events that carry specialized context to achieve specific goals. They're not persistent objects you manage - they're ephemeral requests that flow through your system, gather the right context, and produce results.
Your First Agent
Let's start with the simplest possible example:
import { createNode } from 'happen'
import { createAgentNode } from 'happen-agents'
// Create a node that can handle agent requests
const assistant = createAgentNode({
llm: openai, // your LLM provider
name: 'assistant'
})
// Send an agent event - this IS the agent
assistant.broadcast({
type: 'agent.help',
payload: {
goal: "explain quantum computing in simple terms"
}
})
// The assistant automatically handles the pattern:
// 1. Receives the goal
// 2. Calls the LLM with context
// 3. Emits the result as eventsThat's it! You've just created and used an agent. No configuration, no lifecycle management, no complex setup.
Adding Context
The real power comes from providing context that helps the agent understand your domain:
// Create context that can be composed dynamically
const techContext = createContextNode({
compose: {
audience: () => "software developers",
style: () => "practical examples with code",
expertise: () => "distributed systems and web technologies"
}
})
// Agent events can reference context
system.broadcast({
type: 'agent.explain',
payload: {
goal: "explain how NATS messaging works",
contexts: [techContext.id]
}
})The agent automatically pulls together the context when processing your goal, giving much better results than a generic LLM call.
The Agent Event Pattern
Here's the key insight: agents are events, not objects. When you need intelligent behavior, you emit an agent event:
// This entire object IS the agent
{
type: 'agent.research',
payload: {
goal: "find the top 5 Node.js testing frameworks",
contexts: ['developer-context', 'current-project']
}
}Any node in your system can handle agent events. Multiple nodes can process the same agent request. Agents naturally coordinate through Happen's event system.
Intent Generation
You only need to specify what you want - the system figures out how to do it:
// You provide the goal
system.broadcast({
type: 'agent.analyze',
payload: {
goal: "analyze our API performance bottlenecks"
}
})
// The agent handler automatically:
// 1. Generates an approach based on available context
// 2. Executes the analysis using that approach
// 3. Formats results appropriatelyThe LLM generates its own methodology and intent based on your goal and available context.
Tools as Agents
In Happen, tools are just agents with specialized context. Instead of agents "calling tools," you have agent events triggering other agent events:
// Research agent triggers search agent
system.broadcast({
type: 'agent.websearch',
payload: {
goal: "find recent papers on vector databases",
contexts: ['search-optimization-context']
}
})
// Which might trigger a summarization agent
system.broadcast({
type: 'agent.summarize',
payload: {
goal: "create executive summary of search results",
contexts: ['executive-communication-context']
}
})This creates a uniform system where everything is just specialized context applied to specific functions.
Why This Approach?
Simplicity: No agent lifecycles, orchestrators, or complex state machines. Just events.
Composability: Agents work with any Happen system since they're just events.
Observability: Full causality tracking through Happen's event system.
Flexibility: Same pattern works for simple tasks or complex multi-agent coordination.
Natural: Builds on patterns you already know if you're using Happen.
Context Sources
Context can come from anywhere in your system:
const dynamicContext = createContextNode({
compose: {
// Pull from system state
currentProject: () => system.state.get('active-project'),
// Pull from recent events
recentIssues: () => system.events.recent('bug.*').slice(-5),
// Pull from other nodes
teamStatus: () => teamNode.state.get('availability'),
// Pull from external APIs
marketData: async () => await fetch('/api/market-conditions')
}
})The context stays fresh and relevant because it's composed dynamically when the agent runs.
Happen Agents: Intelligence Without Persistence
Why Are Agents Events?
Think of it this way: What is an agent, really? It's a moment of intelligent reasoning about "what should I do next?"
In traditional systems, we create persistent objects to hold this intelligence:
const agent = new Agent(); // Create a "thing" to do the thinking
agent.think(); // Ask the "thing" to reasonBut in Happen, everything is events. So reasoning is also an event:
system.broadcast({
type: 'agent.reasoning', // This IS the moment of thinking
payload: { query: "What should I do next?" },
context: { /* everything needed to reason intelligently */ }
});An agent isn't a "thing that thinks" - it's the "thinking itself."
Just like you don't need to create a persistent "Decision Maker" object to make a decision, you don't need a persistent "Agent" object to have intelligent reasoning. The reasoning happens in the moment, with full context, then moves on.
The intelligence isn't lost - it flows into the system's event history and influences future reasoning events.
What Are Agents in Happen?
Traditional Agent Systems
In most frameworks, agents are persistent objects with predefined capabilities:
class CustomerAgent {
private tools: Tool[];
private memory: ConversationMemory;
private personality: string;
constructor() {
this.tools = [new EmailTool(), new DatabaseTool(), new KnowledgeBaseTool()];
this.memory = new ConversationMemory();
this.personality = "helpful customer service rep";
}
processRequest(request: string): Response {
// Agent has fixed set of tools it can use
// Agent manages its own state and decision-making
return this.reasonAndRespond(request);
}
}Happen Agents
In Happen, agents are intelligent events that understand full system capabilities:
// Agent = reasoning event with complete system awareness
system.broadcast({
type: 'agent.reasoning',
payload: {
query: "Customer is upset about billing error"
},
context: {
// Rich situational context
customer: { id: 12345, tier: 'premium', history: [...] },
systemState: { billing: {...}, support: {...} },
// FULL SYSTEM CAPABILITIES available right now
capabilities: {
'customer-db': ['lookup', 'update', 'get-history'],
'billing-system': ['refund', 'adjust', 'get-statements'],
'email-service': ['send', 'template', 'schedule'],
'knowledge-base': ['search', 'suggest-articles'],
'escalation-system': ['create-ticket', 'assign-manager']
}
}
});Dynamic System Capability Awareness
The foundation model sees everything the system can do and orchestrates intelligently:
// System automatically discovers current capabilities
function discoverCapabilities() {
return {
// Database nodes
'user-db': userDbNode.getExposedFunctions(),
'billing-db': billingDbNode.getExposedFunctions(),
// Service nodes
'email': emailNode.getCapabilities(),
'sms': smsNode.getCapabilities(),
// Integration nodes
'slack': slackNode.getAvailableChannels(),
'jira': jiraNode.getProjects(),
// Even other agent reasoning capabilities
'specialist-agents': ['legal-analysis', 'technical-support', 'sales-optimization']
};
}
// LLM becomes system conductor that sees everything and decides what to do next
const context = await composeContext(query, {
systemState: getCurrentState(),
documents: searchRelevantDocs(query),
capabilities: discoverCapabilities(), // Full system topology
eventHistory: getRecentEvents()
});"But Are They Really Agents?" - Addressing the Persistence Misconception
When people hear that Happen agents are ephemeral events, a common reaction is: "But that's not a real agent! Agents need to persist and learn over time!"
This confuses where intelligence lives with how intelligence behaves.
Traditional Thinking: Intelligence Lives Inside Objects
class Agent {
private memory: Interaction[] = [];
private knowledge: Record<string, any> = {};
private personality: AgentPersonality = {};
respond(input: string): string {
// Agent uses its accumulated state to be "intelligent"
return this.reasonWithMemoryAndKnowledge(input);
}
}Happen Thinking: Intelligence Emerges from System Context
// No persistent agent object, but intelligence is preserved and enhanced
system.broadcast({
type: 'agent.reasoning',
payload: { query: "How should we handle this customer complaint?" },
context: {
conversationHistory: system.events.recent('customer.interaction.*'),
customerProfile: getCustomerContext(customerId),
pastResolutions: system.events.matching('complaint.resolved'),
currentCapabilities: getAvailableNodes(),
systemLearnings: getPatternAnalysis()
}
});Learning Still Happens - It's Just Distributed Across the System
Every interaction creates permanent learning in the system:
// When agent reasoning completes, outcomes become system knowledge
system.on('agent.response', (event) => {
// Store successful reasoning patterns
system.state.set(state => ({
...state,
successfulPatterns: [
...state.successfulPatterns,
{
query: event.context.originalQuery,
context: event.context,
outcome: event.payload,
effectiveness: calculateEffectiveness(event)
}
]
}));
});
// Future agents automatically benefit from past learnings (pseudo properties)
const enhancedContext = await composeContext(query, {
includePastPatterns: true, // Learn from successful interactions
includeSystemEvolution: true // Understand how system has changed
});Real-Time Learning from Live System Behavior
// Every successful resolution becomes immediate learning
system.on('complaint.resolved', (event) => {
// Resolution pattern is immediately available to future agents
updateContextPatterns(event.payload.resolution);
// System learns what works
system.state.set(state => ({
...state,
resolutionPatterns: [
...state.resolutionPatterns,
{
problemType: event.payload.problemType,
resolution: event.payload.resolution,
customerSatisfaction: event.payload.rating,
timestamp: event.timestamp
}
]
}));
});Intelligence Is Preserved: Agents Make Smart Decisions
The agent still makes intelligent decisions about what to do next:
// Agent reasoning: "I need more context about this customer"
system.broadcast({
type: 'context.customer_request',
payload: { customerId: event.payload.customerId },
context: event.context
});
// Agent reasoning: "This looks like a billing issue, I should check payment history"
system.broadcast({
type: 'tool.payment_lookup',
payload: { customerId: event.payload.customerId },
context: event.context
});
// Agent reasoning: "I have enough information now to provide resolution"
system.broadcast({
type: 'agent.response',
payload: { resolution: synthesizeResolution(allGatheredContext) },
context: event.context
});Superior Intelligence Through Multiple System Perspectives
Happen agents access collective intelligence instead of isolated knowledge:
context: {
// Perfect Memory: Complete causal history
customerHistory: system.events.matching(`customer.${customerId}.*`),
similarCases: system.events.matching('complaint.*'),
systemPatterns: getLearnedPatterns(),
// Collective Intelligence: What every part of system knows right now
salesInsights: salesNode.getInsights(),
supportPatterns: supportNode.getPatterns(),
customerBehavior: analyticsNode.getCurrentTrends(),
marketConditions: marketNode.getLatestData(),
// Dynamic Capabilities: What system can do right now
availableActions: discoverCapabilities()
}The Result: Better Agents Through Non-Persistence
Happen agents are more intelligent precisely because they don't persist:
Fresh context every time - no stale cached information
System-wide learning - not limited to individual agent experience
Perfect causality - trace exactly why every decision was made
Distributed intelligence - leverage entire system's knowledge
Real-time adaptation - learn from events as they happen
Dynamic capabilities - see what's possible right now, not what was configured at startup
// Every agent reasoning event gets the smartest possible context
const nextAgentEvent = {
type: 'agent.reasoning',
payload: { query: userQuery },
context: {
// All system knowledge up to this moment
systemWisdom: aggregateAllSystemLearnings(),
// Current system state and capabilities
currentCapabilities: discoverCapabilities(),
// Relevant historical context
relevantHistory: selectRelevantEvents(userQuery),
// Fresh data from all connected systems
liveContext: composeLiveSystemContext()
}
};The agent event gets smarter context, makes better decisions, and contributes to system-wide learning, all without needing to persist between interactions.
Dynamic Agency: Runtime and Teleportation
A core concept that emerges from Happen's "agent-as-event" model is that all agency is dynamic. This leads to two powerful capabilities: the ability to create intelligence on-the-fly and the elimination of agents as a processing bottleneck.
Dynamic by Default: Runtime Agency
In many traditional frameworks, agents, tools, and teams must be pre-defined, configured, and managed as persistent, stateful objects. This creates a rigid structure where intelligence is a "thing" you must build and maintain.
Happen's philosophy is the opposite: intelligence is an ephemeral event, not a persistent object. An agent isn't a thing that thinks—it's the event of thinking itself. This simple but profound shift means that your system's intelligent capabilities can be created, composed, and deployed "on the fly" at runtime.
Creating Agents On-the-Fly
Because an agent is an event, "creating an agent" is as simple as broadcasting an event. You don't need to instantiate a class or configure a new object; you simply describe the goal and its context, and the system handles it.
This means you can spawn a new "agent" from anywhere, at any time, in response to any other event.
// A 'payment.failed' event might trigger a new agent to investigate
paymentNode.on('payment.failed', (event) => {
console.log('Payment failed. Spawning an agent to investigate...');
// This broadcast IS the creation of the agent.
// It's a "just-in-time" moment of reasoning.
paymentNode.broadcast({
type: 'agent.investigation',
payload: {
goal: "Determine why this payment failed and what to do next.",
contexts: [
'billing-policy-context',
{
// Provide an ad-hoc, on-the-fly context
customerHistory: event.payload.customerHistory,
failedEvent: event
}
]
}
});
});Creating Tools On-the-Fly
Happen's philosophy extends this concept to "tools." A tool is not a static function registered with an agent; a tool is just a more specialized agent event.
This means "creating a new tool" is also just a matter of broadcasting a new event. An agent handler (createAgentNode) can "use a tool" by simply emitting another, more specific agent event.
// An agent handler for 'agent.investigation'
const investigatorAgent = createAgentNode({ llm, name: 'investigator' });
// When the agent 'thinks', its reasoning might be to 'use a tool'.
// In Happen, this means it emits a new, more specific agent event.
investigatorAgent.on('agent.complete', (event) => {
// Let's say the agent's reasoning (event.payload.result)
// decided it needs to perform a web search.
if (event.payload.result.nextStep === 'web-search') {
console.log('Agent is using "web-search" tool on the fly...');
// It "creates" the tool by broadcasting the event for it.
investigatorAgent.broadcast({
type: 'agent.websearch', // This is the "tool"
payload: {
goal: "Find recent news on payment processor outages",
contexts: ['search-optimization-context']
},
// Causality is maintained automatically
causationId: event.id
});
}
});Creating Teams On-the-Fly
This pattern becomes incredibly powerful when composing "teams." A team is not a pre-defined group of agents; it is an emergent pattern of event-driven collaboration.
You can create an entire, multi-step agent team at runtime to handle a single, specific task. This team can be dynamically assembled, execute its workflow, and then disappear, leaving only the causal event chain as a record of its existence.
Imagine you need a "team" to write a market analysis report. You can create this entire team inside an event handler.
// A single event triggers the creation of an entire ad-hoc "team"
apiNode.on('request.market-analysis', (event) => {
// 1. Create the "team" of agent handlers at runtime
const analystAgent = createAgentNode({
llm,
name: 'analyst',
contexts: ['financial-analysis-context']
});
const writerAgent = createAgentNode({
llm,
name: 'writer',
contexts: ['c-suite-comms-context']
});
// 2. Define the "team's" collaborative workflow
// The Analyst's completion event feeds the Writer
analystAgent.on('agent.complete', (analysisEvent) => {
writerAgent.broadcast({
type: 'agent.write',
payload: {
goal: 'Write an executive summary of the following analysis.',
contexts: [{ analysis: analysisEvent.payload.result }]
},
causationId: analysisEvent.id
});
});
// The Writer's completion event is the final response
writerAgent.on('agent.complete', (writerEvent) => {
// This is the final result from the "team"
console.log('Ad-hoc team finished:', writerEvent.payload.result);
// Send the final report back
apiNode.send(event.sender, {
type: 'response.market-analysis',
payload: { report: writerEvent.payload.result },
causationId: event.id
});
});
// 3. Kick off the team with the initial goal
analystAgent.broadcast({
type: 'agent.analyze',
payload: {
goal: 'Analyze Q3 market data for our sector.',
contexts: [{ rawData: event.payload.marketData }]
},
causationId: event.id
});
});🚀 Teleportation: Agents Without Blockers
This "agent-as-event" model has a powerful side effect, which we call "teleportation."
In traditional systems, an agent is a persistent object. It has a "location"—a specific process on a specific server. This creates a fundamental problem: the agent itself can become a blocker.
What if the agent's process is busy? All other requests must wait.
What if the agent object crashes? It must be restarted, and its internal state may be lost.
What if another service needs the agent? It must make a network call to the agent's specific location.
Happen's agents don't have this problem. Because an agent is just an ephemeral event, it has no fixed location.
An agent isn't a "thing" that can get in the way. It's a "thought" that can't be blocked.
When you broadcast an agent event, you are not sending a task to a specific object. You are emitting a goal into the unified event space. Thanks to the NATS backbone, this event "teleports" instantly, becoming available to any node that can handle it, no matter where it is deployed.
This means:
No Busy-Waiting: Firing an agent event is instantaneous. Your code is not blocked, waiting for an "agent object" to become free. You just send the event and move on.
No Location-Based Failure: The agent's "intelligence" (the node handler) can be scaled horizontally. If one
createAgentNodehandler is slow or fails, new agent events can be processed by other, healthy handlers without issue.No Single Point of Failure: The agent itself (the event) is never the blocker. The event is safely persisted in JetStream. If a handler fails while processing, the event is not lost and can be retried, ensuring the "thought" is never lost, even if the "thinker" (the node) momentarily fails.
The agent never becomes a blocker because it doesn't really "exist" in the traditional sense. It's a goal that teleports to the part of your system best equipped to handle it, and it can never get "stuck in a wall" because it's not a physical object—it's just information.
Emergent Agent Workflows: The Agent as a Flow
This pattern is one of the most powerful concepts that emerges from composing Happen's core primitives. By combining the Agent-as-Event model with the Event Continuum, you can solve one of the most difficult problems in agent-based systems: the "long-running agent."
The "Long-Running Agent" Problem
In traditional frameworks, a "long-running agent" is a single, stateful process that:
Is Stateful: It must internally manage its own plan, its memory, and what step it's currently on (e.g., "I'm on step 3 of 10").
Is Blocking: The agent's process is "busy" for the entire duration of the multi-step task, making it a bottleneck that cannot handle other requests.
Is Fragile: If that single, stateful process crashes one hour into a two-hour task, its internal state is lost. The entire task must be restarted from scratch.
The Happen Solution: The Agent as a Flow
Happen's architecture deconstructs this problem entirely. The "long-running task" is no longer a single, monolithic process. Instead, the "long-running agent" is just a flow in the Event Continuum.
In this emergent pattern:
The Agent (LLM) is not the worker; it's the just-in-time planner that only decides the very next step.
The Event Continuum is the executor that runs the steps.
The
contextobject is the agent's short-term memory, which flows between each function in the continuum.
The agent node effectively "writes its own next function" by providing a plan, and your handler code simply returns the function that matches that plan.
This model solves all three core problems:
Solves "Stateful": The agent node (
createAgentNode) remains completely stateless. It receives a goal and context, provides a single step, and is done. The "memory" of the workflow lives in thecontextobject and the Causal Event Web, which is exactly what they are designed for.Solves "Blocking": The agent node is not "busy" for two hours. It's busy for the few seconds it takes to generate "Step 1." It then returns the next function in the continuum and is immediately free to handle a different task from a different user. The "long-running" part is just the continuum
await-ing an async "tool" function.Solves "Fragile": The workflow is inherently resilient. Because the entire task is just a chain of causal events, if a node crashes, the flow can be resumed from the last successfully completed event, using its Causal ID and context to re-hydrate the flow.
Last updated