ai-agent

작성자: rivet-dev

지속적 메모리를 갖춘 AI 에이전트 백엔드 구축: 대화당 하나의 Rivet Actor, 대기열 메시지 처리, 실시간 이벤트로 스트리밍되는 LLM 응답.

npx skills add https://github.com/rivet-dev/skills --skill ai-agent

AI Agent

IMPORTANT: Before doing anything, you MUST read BASE_SKILL.md in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.

Working Examples

If you need a reference implementation, read the raw working example code in these templates:

Patterns for building AI agent backends with RivetKit, where each conversation is one Rivet Actor that owns its memory, its message queue, and its streaming output.

Starter Code

Start with one of the working examples on GitHub and adapt it. The sections below describe the flagship ai-agent example unless a variant is called out explicitly.

VariantStarter CodeUse When
Queue-driven AI SDK agentGitHubYou want a streaming chat agent where each conversation keeps its own persistent memory and processes one message at a time.
Sandbox coding agentGitHubThe agent should run a coding agent (Codex by default) inside an isolated sandbox via Docker, Daytona, or E2B.
Durable streams agent (experimental)GitHubYou want replayable, restart-safe prompt and response delivery through durable streams instead of actor state and events.

Conversation Memory

Use one actor per conversation, keyed by a conversation or agent id (see Actor Keys). The agent actor's persistent state is the conversation memory: in the ai-agent example, messages and status live in JSON actor state and survive sleep and restarts with no external database. Every model call rebuilds the prompt from c.state.messages plus a system prompt, so memory and inference input are the same data.

VariantWhere Memory LivesPersisted State Fields
ai-agentJSON actor statemessages, status
sandbox-coding-agentJSON actor state plus the sandbox ACP sessionmessages, status, sessionId
experimental-durable-streams-ai-agentDurable streams; the actor stores only its conversation id and a read cursorconversationId, promptStreamOffset

Message Handling

In the ai-agent example, the client pushes user input onto the agent's message queue with agent.connection.send("message", { text, sender }). This is a queue push, not an action call. The actor's run hook (see Lifecycle) consumes the queue serially with for await (const queued of c.queue.iter()).

Serial queue consumption is the per-conversation concurrency guarantee: at most one in-flight model call per actor, with no extra locking. The status field (thinking while a model call is in flight) is UI signal only; the run loop is the actual lock. The loop also checks c.aborted inside the token stream so shutdown exits gracefully.

VariantMessage IngressSerialization Guarantee
ai-agentmessage queue pushed via connection.sendrun hook pops one queued message at a time with c.queue.iter().
sandbox-coding-agentsendMessage action, no queueEach call awaits the sandbox round trip before broadcasting the result.
experimental-durable-streams-ai-agentDurable prompt stream long-polled from onWakepromptStreamOffset is persisted per chunk, so restarts resume without reprocessing prompts.

Streaming Responses

The ai-agent actor broadcasts a response event for every model text delta. The payload carries messageId, the per-token delta, the cumulative content, and a done flag (plus error on failure), so clients can either append deltas or idempotently replace the message by messageId using content. The example frontend replaces by messageId, which tolerates dropped events. The terminal broadcast has an empty delta, the full content, and done: true.

Because the assistant message object lives in c.state.messages and is mutated in place during streaming, partial content persists if the actor restarts mid-stream. The example broadcasts once per AI SDK delta with no throttling; batching or throttling deltas is a recommended extension for high-traffic deployments, not something the example implements.

Variant differences: sandbox-coding-agent sends a single response broadcast with done: true after the sandbox finishes (no incremental streaming), and experimental-durable-streams-ai-agent appends per-token chunks to a durable response stream, then broadcasts responseComplete or responseError.

Architecture

TopicSummary
TopologyagentManager["primary"] singleton directory plus one agent[agentId] actor per conversation.
IngressClient pushes AgentQueueMessage payloads onto the agent's message queue with connection.send.
StreamingOne response broadcast per model delta, terminal broadcast with done: true.
MemoryFull transcript and status in JSON actor state; no external database.

The manager creates AgentInfo records and warms each agent through actor-to-actor communication: createAgent calls c.client<typeof registry>(), then client.agent.getOrCreate([info.id]) and awaits getStatus() so the conversation actor exists before the client connects. The sandbox variant extends this topology with a codingSandbox actor that shares the agent's key (codingSandbox.getOrCreate([c.key[0]])), so the agent-to-sandbox mapping is implicit in the key space.

Actors

  • Key: agentManager["primary"]

  • Responsibility: Directory actor. Creates AgentInfo records, lists agents, and warms each agent actor via c.client().

  • Actions

    • createAgent
    • listAgents
  • Queues

    • None
  • State

    • JSON
    • agents
  • Key: agent[agentId]

  • Responsibility: One actor per conversation. Holds the full message history and status, consumes queued user messages in its run loop, calls the model via the AI SDK, and broadcasts streaming deltas.

  • Actions

    • getHistory
    • getStatus
  • Queues

    • message
  • Events

    • messageAdded
    • status
    • response
  • State

    • JSON
    • messages
    • status

Lifecycle

sequenceDiagram
	participant C as Client
	participant AM as agentManager
	participant A as agent
	participant LLM as Model API

	C->>AM: createAgent(name)
	AM->>A: getOrCreate([info.id]) + getStatus()
	AM-->>C: AgentInfo
	C->>A: connection.send("message", {text, sender})
	Note over A: run loop pops queue via c.queue.iter()
	A-->>C: messageAdded (user message)
	A-->>C: messageAdded (assistant placeholder)
	A-->>C: status (thinking)
	A->>LLM: streamText(system prompt + history)
	loop each text delta
		LLM-->>A: delta
		A-->>C: response {messageId, delta, content, done: false}
	end
	A-->>C: response {delta: "", content, done: true}
	A-->>C: status (idle)

Security Checklist

The examples ship without auth so they stay minimal. Apply this baseline before exposing an agent backend.

  • API keys stay server-side: OPENAI_API_KEY (or ANTHROPIC_API_KEY) is read by the AI SDK inside the actor process. The key never reaches the browser; clients only talk to the actor over RivetKit. The sandbox variant forwards keys into the sandbox env, never to the client.
  • Add authentication: The examples have no auth, so anyone who reaches the server can create agents, list them, and message any agent whose key they can guess. Add onBeforeConnect or createConnState checks with scoped tokens as a recommended extension. See Authentication.
  • Validate and rate-limit queue payloads: The example only skips bodies without a string text. Enforce payload size limits, schema validation, and per-connection rate limits as a recommended extension.
  • Derive sender identity server-side: The example trusts the client-supplied sender field verbatim. Bind sender identity to the authenticated connection instead.
  • Cap or trim message history: The example sends the full transcript on every model call with no cap. Trim or summarize old messages as a recommended extension so prompts and state stay bounded.
  • Set cost ceilings per conversation: Add per-agent token budgets and quotas as a recommended extension. The sandbox variant runs real compute, so also enforce per-user sandbox quotas and restrict sandbox network egress.

Reference Map

Actors

Cli

Clients

Cookbook

Deploy

General

Self Hosting

rivet-dev의 다른 스킬

ai-agent-workspace
rivet-dev
모든 AI 에이전트에게 자신만의 컴퓨터를 부여하세요: 경량 인프로세스 상에서 파일 시스템, 프로세스, 셸, 네트워킹 및 에이전트 세션을 갖춘 영구 작업 공간입니다…
official
chat-room
rivet-dev
Rivet Actors로 실시간 채팅방 백엔드 구축: 방마다 하나의 액터, SQLite 기반 메시지 기록, 모든 연결된 클라이언트에 WebSocket 브로드캐스트.
official
collaborative-text-editor
rivet-dev
Yjs CRDT와 Rivet Actors를 사용하여 협업 텍스트 편집기 백엔드를 구축합니다: 문서별 액터가 동기화 및 인식 업데이트를 중계하고 스냅샷을 유지합니다.
official
cron-jobs
rivet-dev
Rivet Actors를 사용한 내구성 있는 크론 작업: schedule.after 및 schedule.at 타이머는 재시작과 충돌에도 유지되며, 반복 작업 재설정 및 멱등성 핸들러를 지원합니다.
official
live-cursors
rivet-dev
Rivet Actors를 사용한 라이브 커서 및 멀티플레이어 프레즌스: 연결별 커서 상태, 이벤트 또는 원시 WebSocket을 통한 실시간 업데이트, 스로틀링.
official
per-tenant-database
rivet-dev
멀티 테넌트 데이터 격리를 위해 테넌트당 하나의 Rivet 액터를 사용합니다. 액터 키는 테넌트 ID이므로 각 테넌트는 자체 격리된 데이터셋과 마이그레이션을 갖습니다.
official
rivetkit-client-javascript
rivet-dev
JavaScript 클라이언트로, 무상태 또는 상태 저장 연결을 통해 Rivet Actors에 연결합니다. 브라우저, Node.js 및 Bun 환경을 지원하며, 환경 변수 또는 명시적 구성을 통한 자동 엔드포인트 감지 기능을 제공합니다. 독립적인 요청을 위한 무상태 액션 호출과 실시간 이벤트 구독이 가능한 상태 저장 연결의 두 가지 상호작용 모드를 제공합니다. onRequest 또는 onWebSocket 핸들러를 구현하는 액터를 위한 저수준 HTTP 및 WebSocket 액세스를 포함하며, 복합 배열 기반...
official
rivetkit-client-react
rivet-dev
React 클라이언트로, 훅과 실시간 상태 관리를 통해 Rivet Actors에 연결합니다. createRivetKit()으로 타입이 지정된 훅을 생성하고, useActor()로 키와 선택적 매개변수를 사용하여 액터 인스턴스에 연결합니다. useEvent()로 액터 이벤트를 구독하고, connStatus와 error 상태를 통해 연결 수명 주기를 모니터링합니다. createClient()를 사용하여 상태 비저장 단일 호출, 액터 검색 메서드(get, getOrCreate, create, getForId), 그리고 저수준 HTTP/WebSocket 액세스를 수행합니다. 복합 배열 키를 지원합니다...
official