Running Agents Without HTTP — Agentuity Documentation

Running Agents Without HTTP

Execute agents programmatically for cron jobs, bots, CLI tools, and background workers

Sometimes your agent logic needs to run without an incoming HTTP request. createAgentContext() gives standalone code the same infrastructure that HTTP handlers get automatically: tracing, sessions, and storage access.

Basic Usage

import { createAgentContext } from '@agentuity/runtime';
import chatAgent from '@agent/chat/agent';
 
const ctx = createAgentContext();
const result = await ctx.run(chatAgent, { message: 'Hello' });

The run() method executes your agent with full infrastructure support: tracing, session management, and access to all storage services.

For agents that don't require input:

const result = await ctx.run(statusAgent);

Options

OptionTypeDescription
sessionIdstringCustom session ID. Auto-generated from trace context if not provided
triggerstringTrigger type for telemetry: 'discord', 'cron', 'websocket', 'manual'
parentContextContextParent OpenTelemetry context for distributed tracing

By default, each standalone invocation creates a fresh thread and restores or creates a session from the session ID. Pass sessionId when an external system needs a stable session identifier for a run.

External Cron Job Example

For scheduled tasks managed outside of Agentuity:

import { createApp, createAgentContext } from '@agentuity/runtime';
import cron from 'node-cron';
import cleanupAgent from '@agent/cleanup/agent';
 
await createApp();
 
// Run cleanup every hour
cron.schedule('0 * * * *', async () => {
  const ctx = createAgentContext({ trigger: 'cron' });
  await ctx.run(cleanupAgent, { task: 'expired-sessions' });
});

Multiple Agents in Sequence

Run multiple agents in sequence with the same context:

const ctx = createAgentContext();
 
// First agent analyzes the input
const analysis = await ctx.run(analyzeAgent, { text: userInput });
 
// Second agent generates response based on analysis
const response = await ctx.run(respondAgent, {
  analysis: analysis.summary,
  sentiment: analysis.sentiment,
});

Each ctx.run() call gets its own session and tracing span, while sharing the logger, tracer, and app state.

Reusing Contexts

Create a context once and reuse it for multiple invocations:

const ctx = createAgentContext({ trigger: 'websocket' });
 
// Each run gets its own session and tracing span
websocket.on('message', async (data) => {
  const result = await ctx.run(messageAgent, data);
  websocket.send(result);
});

Using invoke() Directly

ctx.run() is syntactic sugar over ctx.invoke(). Use invoke() when you need more control:

  • Run arbitrary async functions (not just agents) within agent context
  • Execute multiple agents in a single invocation with a shared session and tracing span
  • Set a custom OpenTelemetry span name

Running Arbitrary Functions

const ctx = createAgentContext({ trigger: 'cron' });
 
await ctx.invoke(async () => {
  // Any async code runs with full agent infrastructure
  const data = await ctx.kv.get('config', 'settings');
  await ctx.stream.create('audit', {
    contentType: 'text/plain',
  });
  ctx.logger.info('Cron task complete');
});

Multiple Agents in One Invocation

When agents share a session and tracing span:

const ctx = createAgentContext();
 
const result = await ctx.invoke(async () => {
  const analysis = await analyzeAgent.run({ text: userInput });
  const response = await respondAgent.run({
    analysis: analysis.summary,
  });
  return response;
});

Both agent calls share the same OpenTelemetry span, session, and thread. Compare this with calling ctx.run() twice, where each call gets its own session and span.

Custom Span Names

const result = await ctx.invoke(() => agent.run(input), { spanName: 'daily-cleanup' });

invoke() Options

OptionTypeDefaultDescription
spanNamestring'agent-invocation'Span name for this invocation

Invocation Lifecycle

Each call to ctx.run() or ctx.invoke() follows this lifecycle:

  1. Create tracing span: An OpenTelemetry span is created within the parent context. Trace state includes project, org, and deployment IDs for distributed tracing.
  2. Create conversation state: A fresh thread is created and the session is restored or created using the session ID.
  3. Fire session start event: A start event is sent to the session event provider with the trigger type, session ID, thread ID, and deployment metadata.
  4. Execute function: Your function runs within AsyncLocalStorage context, giving it access to ctx.kv, ctx.stream, ctx.vector, ctx.logger, and waitUntil.
  5. Handle background tasks: If ctx.waitUntil() callbacks are pending, they continue after your function returns.
  6. Save session and thread: Session and thread state are persisted via their providers. When waitUntil() tasks are pending, this happens after those tasks finish.
  7. Fire session complete event: A complete event is sent with the final status code, agent IDs that participated, and any user data. When waitUntil() tasks are pending, this happens after those tasks finish.

Errors during execution set the span status to ERROR and include the error message in the session complete event. Errors from waitUntil() tasks are recorded on the span and reported in the eventual session complete event.

What's Included

Detecting Context

Use inAgentContext() to check if code is running inside an agent handler with ambient context available:

import { inAgentContext, createAgentContext } from '@agentuity/runtime';
import myAgent from '@agent/my-agent/agent';
 
async function processRequest(data: unknown) {
  if (inAgentContext()) {
    // Inside an agent handler, ambient context is available
    // so agent.run() works directly without explicit context
    return myAgent.run(data);
  }
 
  // Outside agent handler, create context first
  const ctx = createAgentContext();
  return ctx.run(myAgent, data);
}

This is useful for writing utility functions that work both inside agent handlers and in standalone scripts.

To check if the Agentuity runtime is initialized (but not necessarily inside a handler), use isInsideAgentRuntime() instead.

Next Steps