Quickstart

Build and deploy your first Tardie agent

Tardigrade is a TypeScript framework for building durable, composable agents around an immutable event log. By the end of this guide, you will know how to run a Tardigrade agent locally, inspect its trajectory, and deploy it to the cloud.

Prerequisites

  1. Bun 1.4 or above
  2. The latest Tardie CLI
  3. An LLM API key from a provider of your choice

For the fastest setup, create an OpenRouter API key before you begin. For deployment, install either the Wrangler CLI for Cloudflare or the Celld CLI for your own cloud.

Initialize your project

First, install the Tardie CLI.

bun add -g tardie@latest

Now, use the Tardie CLI to create a new tardie-agent directory and configure a language model provider.

tdg init tardie-agent --template quickstart

Initialization locks the selected model. Run tdg models lock to include additional configured models with complete context metadata.

After the setup, you should see the following files:

tardie-agent/
  • actor.ts Actor and atoms
  • services.ts Model and library implementations
  • generated/providers.ts Configured model implementations
  • server.ts Bun server entry
  • worker.ts Cloudflare and Celld Worker entry
  • wrangler.jsonc Cloudflare Worker configuration
  • celld.jsonc Self-hosted Celld configuration
  • models.lock.json Resolved model metadata
  • package.json Dependencies and module metadata

Move into the new project directory:

cd tardie-agent

Define your first actor

Hello world!

For this Quickstart, we'll implement a simple agent with a tool that returns dummy weather data for a given city. In another guide, we'll explore a more advanced setup by building an agent that can execute JavaScript code, call MCP servers, and run subagents within a sandbox.

As you follow along, you will see tip boxes that you can expand to learn what happens behind the scenes.

About these tips

These notes explain what Tardigrade is doing behind the scenes. You can skip them and still complete the guide.

Within actor.ts, you will see the following code:

ts
import { Effect, Schema } from "effect"
import { Rpc } from "effect/unstable/rpc"
import { atom, defineActor } from "tardie/core"
import { agentMethods, compact, infer, messages, tools as libraryTools } from "tardie/agent"
import { defineLibrary, MethodDescription, MethodHints } from "tardie/libraries"

const actorName = "tardie-agent"

const actorInstructions = `
Answer questions about the weather.
Use the weather tool for current conditions.
`.trim()

export const weather = defineLibrary({
  name: "weather",
  description: "Weather information",
  toolNames: { current: "get_weather" },
  methods: [Rpc.make("current", {
    payload: Schema.Struct({ city: Schema.String }),
    success: Schema.Struct({ city: Schema.String, temperature: Schema.Finite }),
  })
    .annotate(MethodDescription, "Get example weather data for a city")
    .annotate(MethodHints, { readOnlyHint: true, openWorldHint: false })],
})

export default defineActor(actorName, Effect.gen(function* () {
  const system = atom(actorInstructions)
  const tools = yield* libraryTools([weather])
  const context = yield* compact(messages)
  const agent = yield* infer(get => ({
    system: get(system),
    tools: get(tools),
    context: get(context),
  }))
  return { atom: agent, methods: agentMethods }
}))

messages derives message history from the event log. compact prepares model context, and infer proposes model calls. The actor composes these atoms and exposes agentMethods.

The weather library declares a typed method with Effect RPC. Its implementation belongs in services.ts:

ts
const tools = weather.implement({
  current: ({ city }) => Effect.succeed({ city, temperature: 21 }),
})

The example returns dummy weather data. Replace the handler with your weather service. toolActs([tools]) supplies the handler to the runtime; the actor sees its method schema and annotations.

Running it locally

Run the generated Bun API server directly:

bun run dev

The server runs on localhost:4242 by default. Set PORT to change it. Check curl localhost:4242/healthz from another shell to confirm it is ready. Edit server.ts to change how the actor and API are assembled.

Send your first message

Once you have the agent running, create a root thread named quickstart, then send it your first message. The creation command prints the assigned thread ID.

bash
tdg thread create --name quickstart
tdg call message '{"text":"What is the weather in Singapore?"}' --thread quickstart

The message method records a TurnRequested event. Model and tool results update durable atoms, and a TurnSettled event completes the call with { text: string }. Each call has an invocation ID. To safely retry a call, supply the same --id and the same input.

Inspecting the actor logs

After your call, you will see .tardigrade/data/. This directory holds one SQLite database for each actor instance. An event log is an immutable, append-only ledger of events sent to the actor and events generated by the actor.

.tardigrade/data/
  • <actor-instance>.sqlite Registry for the main actor instance
  • <actor-instance>.sqlite.threads/
    • <thread>.sqlite Event log for the quickstart thread

A simplified view of the message includes the following events. The log also records the model requests and responses.

  1. ThreadCreated { address: { ... } }
  2. TurnRequested { text: "What is the weather..." }
  3. ToolCalled { callId: "...", counted: true }
  4. ToolReturned { callId: "...", output: "...", error: null }
  5. TurnSettled { settlement: "completed", ... }

These events persist across runs. Each thread has a separate SQLite event database in the .sqlite.threads/ directory beside its actor instance database. Set TARDIGRADE_ACTOR_DATA to change the storage directory.

You can use the following command to view the events:

tdg events <thread>

If you used the command in this guide, the first message landed in a thread called quickstart. To view its events, run:

tdg events quickstart

Send a follow up message

To send a follow up message, simply send another message to the same thread:

tdg call message '{"text":"How about San Francisco?"}' --thread quickstart

Deploy your agent

Set your provider credential as a Worker secret before deploying. For example, with OpenRouter:

bunx wrangler secret put OPENROUTER_API_KEY

The generated Worker uses an ActorDO binding and the same actor, service layer, and model lock as the Bun server. Deploy it:

bun run deploy:cloudflare

To deploy it on your own cloud, use Celld:

bun run deploy:celld

Send a message to your cloud agent

Once your deployment is ready, create a root thread on the remote host, then invoke its message method. Set ACTOR_URL to your deployment URL.

bash
tdg thread create --name quickstart --url "$ACTOR_URL"
tdg call message '{"text":"What is the weather in Singapore?"}' --thread quickstart --url "$ACTOR_URL"

Going further

See the README for atom composition and the SDK reference for custom libraries. To migrate an existing event log at a settled boundary, use state initialisation.