Backend Setup
SKILL.md
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 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:
POST /sessions/create- Creates session record, no sandbox yetPOST /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:
// 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 beginssession:block:delta- Incremental text updatesession:block:update- Block metadata changessession:block:complete- Block finishes
Session lifecycle:
session:status- Runtime state changessession:metadata:update- Token/cost updates
Files:
session:file:created- New file in workspacesession:file:modified- File changedsession:file:deleted- File removed
Subagents:
session:subagent:discovered- New subagent startedsession: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