| 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:
- Configuring environment variables
- Implementing a PersistenceAdapter
- Creating the runtime with configuration
- Starting REST and WebSocket servers
Environment Variables
Required environment variables:
MODAL_TOKEN_ID=your_modal_token_id
MODAL_TOKEN_SECRET=your_modal_token_secret
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";
const persistence: PersistenceAdapter = {
listAllSessions: async () => [],
loadSession: async (sessionId) => null,
createSessionRecord: async (session) => {},
updateSessionRecord: async (sessionId, updates) => {},
saveTranscript: async (sessionId, rawTranscript) => {},
saveWorkspaceFile: async (sessionId, file) => {},
deleteSessionFile: async (sessionId, path) => {},
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() {
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,
syncIntervalMs: 30 * 1000,
});
await runtime.start();
const restApp = runtime.createRestServer({
apiKey: "your-api-key",
});
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());
});
const wsServer = runtime.createWebSocketServer(httpServer);
httpServer.listen(3001, () => {
console.log("Server running on http://localhost:3001");
});
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({
persistence: PersistenceAdapter,
modal: {
tokenId: string,
tokenSecret: string,
appName: string,
},
idleTimeoutMs: number,
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:
POST /sessions/create - Creates session record, no sandbox yet
POST /sessions/:id/message - First message triggers sandbox creation
- Subsequent messages reuse the running sandbox
- Idle timeout eventually terminates the sandbox
SessionManager Access
Access the session manager for advanced operations:
const sessions = runtime.sessionManager.getLoadedSessions();
const session = runtime.sessionManager.getSession(sessionId);
await runtime.sessionManager.unloadSession(sessionId);
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:
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