Backend Setup

SKILL.md

Backend Setup

Overview

Setting up an agent runtime backend involves:

  1. Configuring environment variables
  2. Implementing a PersistenceAdapter
  3. Creating the runtime with configuration
  4. Starting REST and WebSocket servers

Environment Variables

Required environment variables:

# Modal credentials (for sandbox creation)
MODAL_TOKEN_ID=your_modal_token_id
MODAL_TOKEN_SECRET=your_modal_token_secret

# Anthropic API key (for Claude agents)
ANTHROPIC_API_KEY=your_anthropic_api_key

Obtain Modal credentials from modal.com.

Minimal Server Example

import { createServer } from "http";
import { createAgentRuntime, type PersistenceAdapter } from "@hhopkins/agent-runtime";

// 1. Implement PersistenceAdapter (see references/types.md for full interface)
const persistence: PersistenceAdapter = {
  // Session operations
  listAllSessions: async () => [],
  loadSession: async (sessionId) => null,
  createSessionRecord: async (session) => {},
  updateSessionRecord: async (sessionId, updates) => {},

  // Storage operations
  saveTranscript: async (sessionId, rawTranscript) => {},
  saveWorkspaceFile: async (sessionId, file) => {},
  deleteSessionFile: async (sessionId, path) => {},

  // Agent profile operations
  listAgentProfiles: async () => [{ id: "default", name: "Default Agent" }],
  loadAgentProfile: async (agentProfileId) => ({
    id: "default",
    name: "Default Agent",
    systemPrompt: "You are a helpful assistant.",
    tools: ["Read", "Write", "Edit", "Bash"],
  }),
};

async function main() {
  // 2. Create runtime
  const runtime = await createAgentRuntime({
    persistence,
    modal: {
      tokenId: process.env.MODAL_TOKEN_ID!,
      tokenSecret: process.env.MODAL_TOKEN_SECRET!,
      appName: "my-agent-app",
    },
    idleTimeoutMs: 15 * 60 * 1000, // 15 minutes
    syncIntervalMs: 30 * 1000,      // 30 seconds
  });

  // 3. Start runtime (loads sessions, starts background jobs)
  await runtime.start();

  // 4. Create REST API server
  const restApp = runtime.createRestServer({
    apiKey: "your-api-key",
  });

  // 5. Create HTTP server and attach REST routes
  const httpServer = createServer(async (req, res) => {
    const response = await restApp.fetch(
      new Request(`http://${req.headers.host}${req.url}`, {
        method: req.method,
        headers: req.headers as any,
        body: req.method !== "GET" && req.method !== "HEAD"
          ? await getRequestBody(req)
          : undefined,
      })
    );

    res.statusCode = response.status;
    response.headers.forEach((value, key) => res.setHeader(key, value));
    res.end(await response.text());
  });

  // 6. Create WebSocket server on same HTTP server
  const wsServer = runtime.createWebSocketServer(httpServer);

  // 7. Start listening
  httpServer.listen(3001, () => {
    console.log("Server running on http://localhost:3001");
  });

  // 8. Graceful shutdown
  process.on("SIGTERM", async () => {
    httpServer.close();
    wsServer.close();
    await runtime.shutdown();
    process.exit(0);
  });
}

function getRequestBody(req: any): Promise<string> {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", (chunk: any) => body += chunk.toString());
    req.on("end", () => resolve(body));
    req.on("error", reject);
  });
}

main();

Runtime Configuration

const runtime = await createAgentRuntime({
  // Required: persistence adapter implementation
  persistence: PersistenceAdapter,

  // Required: Modal credentials
  modal: {
    tokenId: string,
    tokenSecret: string,
    appName: string,  // Modal app name for sandboxes
  },

  // Optional: idle timeout before session cleanup (default: 15 min)
  idleTimeoutMs: number,

  // Optional: sync interval for persisting state (default: 30 sec)
  syncIntervalMs: number,
});

REST API Endpoints

The runtime creates these REST endpoints:

Method Endpoint Description
POST /sessions/create Create new session
GET /sessions/:id Get session data
POST /sessions/:id/message Send message to agent
GET /sessions List all sessions
GET /agent-profiles List available agent profiles
GET /health Health check

Lazy Sandbox Pattern

Sandboxes are created lazily - not when a session is created, but when the first message is sent. This optimizes resource usage:

  1. POST /sessions/create - Creates session record, no sandbox yet
  2. POST /sessions/:id/message - First message triggers sandbox creation
  3. Subsequent messages reuse the running sandbox
  4. Idle timeout eventually terminates the sandbox

SessionManager Access

Access the session manager for advanced operations:

// Get all loaded sessions
const sessions = runtime.sessionManager.getLoadedSessions();

// Get specific session
const session = runtime.sessionManager.getSession(sessionId);

// Unload a session (terminates sandbox, syncs state)
await runtime.sessionManager.unloadSession(sessionId);

// Get session state
const state = session.getState();

WebSocket Events

The WebSocket server emits these events to connected clients:

Block streaming:

  • session:block:start - New block begins
  • session:block:delta - Incremental text update
  • session:block:update - Block metadata changes
  • session:block:complete - Block finishes

Session lifecycle:

  • session:status - Runtime state changes
  • session:metadata:update - Token/cost updates

Files:

  • session:file:created - New file in workspace
  • session:file:modified - File changed
  • session:file:deleted - File removed

Subagents:

  • session:subagent:discovered - New subagent started
  • session:subagent:completed - Subagent finished

Errors:

  • error - Error occurred

PersistenceAdapter

The PersistenceAdapter is the main integration point. Implement this interface to connect the runtime to your storage layer. See references/types.md for the full interface.

Common implementations:

  • In-memory - For development/testing
  • SQLite - For single-server deployments
  • PostgreSQL/MySQL - For production
  • Convex/Supabase - For serverless

Related Skills

  • overview - Understanding the runtime architecture
  • react-integration - Building React frontends
  • agent-design - Configuring agent profiles
Weekly Installs
0
First Seen
Jan 1, 1970