GitHub - clavia-labs/tardigrade: The TypeScript framework for building composable actors around an immutable event log. · GitHub
Skip to content

About

The TypeScript framework for building composable actors around an immutable event log.

Topics

Resources

Contributing

Stars

439 stars

Watchers

0 watching

Forks

Latest commit

 

History

645 Commits

Folders and files

Repository files navigation


Tardie, the Tardigrade mascot

npm version Join Discord

Warning

Tardigrade is under active development. APIs may change.

Tardigrade

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.

{ view, effects } = f(event log)

Quickstart

If you use the legacy component API, see the migration guide for moving existing state to atoms.

bunx tardie init meeseeks --template quickstart

For coding agents, use the Tardigrade skill.

Atoms hold state

bun add tardie

Use 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 state from event log

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 propose actions

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.

It's atoms all the way

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 ---+

Hosting

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 worker

createActorWorker 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.

Cloudflare:

bunx wrangler deploy

Or deploy to a Celld fleet, using its storage bucket:

celld deploy --config celld.jsonc --bucket s3://actors

See the Cloudflare and Celld guides for configuration and credentials. For a Bun process, see the Bun example and service wiring.

Contributing

See CONTRIBUTING.md.

About

The TypeScript framework for building composable actors around an immutable event log.

Topics

Resources

Contributing

Stars

439 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages