Warning
Tardigrade is under active development. APIs may change.
Tardigrade is a typescript framework for building composable agents around an immutable event log. It is built on Effect TS and takes a functional approach to managing agent state and effects, drawing inspiration from Elm, and Jotai.
If you use the legacy component API, see the migration guide for moving existing state to atoms.
bunx tardie init meeseeks --template quickstartFor coding agents, use the Tardigrade skill.
bun add tardieUse atoms to store values for other atoms to use.
import { atom } from "tardie/core"
const role = atom("You are a research assistant.")
const style = atom("Cite your sources and keep answers concise.")Derive values from atoms using get.
const system = atom(get => `${get(role)}\n${get(style)}`)Durable atoms reduce an immutable event log into state.
import { Schema } from "effect"
import { durableAtom } from "tardie/core"
import { Event, TrajectoryState, reduceTrajectory } from "tardie/agent"
const history = durableAtom({
name: "researcher.history",
input: Event,
schema: TrajectoryState,
initial: { entries: [], models: [] },
reduce: reduceTrajectory,
})
const messages = atom(get => get(history).entries.map(entry => entry.message))Effect atoms derive views and propose actions.
Simplified compact:
const compact = messages => effectAtom(get => {
const history = get(messages)
const state = get(compactionState)
const context = prepareMessages(history, state)
const shouldSummarize = !state.pending && exceedsThreshold(context)
return {
view: { messages: context, compacting: Boolean(state.pending) },
events: {},
acts: shouldSummarize ? { summarize: summarizeRequest(context, state) } : {},
}
})Recorded results update durable compaction state.
Compose the atoms into an agent actor.
import { Effect } from "effect"
import { atom, defineActor } from "tardie/core"
import { workspace, fetch } from "tardie/libraries"
import { agentMethods, codeMode, compact, infer, messages } from "tardie/agent"
const researcher = defineActor("researcher", Effect.gen(function* () {
const system = atom("You are a careful research assistant.")
const tools = yield* codeMode([workspace(), fetch()])
const context = yield* compact(messages, {
triggerRatio: 0.8,
retainRatio: 0.5,
})
const agent = yield* infer(get => ({
system: get(system),
tools: get(tools),
context: get(context),
}))
return { atom: agent, methods: agentMethods }
}))event log -> history -> messages -> compact --+
|
system ---+-> infer -> researcher
|
tools ---+
Host each actor instance with an ActorDO for its supervisor directory and a separate SQLite-backed ThreadDO for each thread on Cloudflare or a self-hosted Celld fleet. The same Worker entrypoint runs on both.
import { actorContext } from "tardie/agent"
import { createActorWorker } from "tardie/platform/cloudflare"
import { researcher } from "./actor"
import { services } from "./services"
const worker = createActorWorker({
actor: researcher,
actorContext,
services: () => services(),
})
export const ActorDO = worker.ActorObject
export const ThreadDO = worker.ThreadObject
export default workercreateActorWorker routes requests and provides the ActorDO and ThreadDO classes.
ActorDO allocates and routes to threads; each ThreadDO owns a separate database with its state and journal. Each thread records its parent, through which we derive the logical lineage, here A -> B -> C.
ActorWorker -> ActorDO [supervisor DB]
└── directory
├── A [ThreadDO, thread DB]
├── B [ThreadDO, thread DB, parent: A]
└── C [ThreadDO, thread DB, parent: B]
The Quickstart generates services and deployment configs with both DO bindings. Configure your model and credentials, then run or deploy.
Run locally with bunx wrangler dev; see local setup.
bunx wrangler deployOr deploy to a Celld fleet, using its storage bucket:
celld deploy --config celld.jsonc --bucket s3://actorsSee the Cloudflare and Celld guides for configuration and credentials. For a Bun process, see the Bun example and service wiring.
See CONTRIBUTING.md.
