Back to list
hhopkins95

backend-setup

by hhopkins95

Service for running CLI agent (Claude Code, Gemini CLI, etc...) sessions in cloud sandboxes

1🍴 0📅 Dec 31, 2025

SKILL.md


name: Backend Setup description: This skill should be used when the user asks to "create an agent runtime server", "set up agent runtime backend", "configure Modal sandbox", "implement PersistenceAdapter", "start WebSocket server", "create REST API for agents", or needs to build a Node.js backend using @hhopkins/agent-runtime.

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:

MethodEndpointDescription
POST/sessions/createCreate new session
GET/sessions/:idGet session data
POST/sessions/:id/messageSend message to agent
GET/sessionsList all sessions
GET/agent-profilesList available agent profiles
GET/healthHealth 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
  • overview - Understanding the runtime architecture
  • react-integration - Building React frontends
  • agent-design - Configuring agent profiles

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon