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 events

That'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 appropriately

The 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 reason

But 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 createAgentNode handler 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:

  1. 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").

  2. 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.

  3. 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

It should be noted that it will be possible to have the model return functions that we can then evaluate as the next step so that it writes it's own next steps.

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 context object 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:

  1. 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 the context object and the Causal Event Web, which is exactly what they are designed for.

  2. 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.

  3. 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