npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

effect-harness

v0.13.0

Published

Durable AI conversations with Effect services, schema-backed state, and scoped local execution.

Readme


effect-harness is an agent loop you can kill at any moment. Every conversation, tool call and model response is committed to storage as it happens, so a crash, a deploy or a Cloudflare eviction picks up exactly where it stopped. It is built on Effect: tools are Schemas, implementations are Layers, and the client talks to the loop over typed RPC, in the same process or across the internet.

[!WARNING] effect-harness is still very unstable. I use it in my own projects and adjust it as I go: fixing bugs, reshaping the API, and sometimes breaking things between minor versions. Pin an exact version, and expect to read the changelog when you upgrade.

const client = yield* HarnessClient.HarnessClient
const root = yield* client.root()

const submission = yield* root.submit({ type: 'input', content: 'Fix the flaky login test' })
const settled = yield* submission.wait // survives restarts: reacquire it by ID later

Why effect-harness?

💾 Durable by default

Runs are durable tasks with checkpoints. Restart the process and unfinished generations, tool calls and compactions resume from storage. Memory, JSONL, SQLite and Postgres backends share one engine.

🧩 Schemas all the way down

Declare tools with Effect Schemas and implement them as Layers. Arguments are validated, results encoded, failures typed, and a missing implementation is a type error, not a runtime surprise.

🔌 Client and loop, split

HarnessClient speaks RPC to HarnessServer. Keep both in one process, put the loop behind a WebSocket, or host it in a Cloudflare Durable Object, and your calling code stays the same.

🤖 Real providers, real accounts

OpenAI, Anthropic, Codex through a ChatGPT sign-in, Claude subscriptions and any OpenAI-compatible server. Let people add accounts at run time, each with its own credentials.

🧠 A loop that manages itself

Steering and follow-up queues, automatic retries with backoff, context compaction before the window fills, deferred tool loading with tool_search, and per-conversation usage ledgers.

🛠️ Batteries included

read, write, edit and bash coding tools, a durable code mode that replays QuickJS scripts after a crash, image fitting for vision models, and a scripted Faux model for tests.

Install

bun add effect-harness@^0.7.0 [email protected]

Add optional peers only for the integrations you use:

| You want | Also install | | ----------------------------- | ----------------------------------------------------- | | effect-harness/openai | @effect/[email protected] | | effect-harness/anthropic | @effect/[email protected] | | effect-harness/coding/sharp | [email protected] | | effect-harness/cloudflare | [email protected] @effect/[email protected] |

[!NOTE] These docs describe the 0.7 API (lowercase subpaths such as effect-harness/client). 0.6 and earlier use a different layout. Until 0.7 is on npm, build from a checkout: bun install && bun run setup && bun run build.

Your first agent in 60 seconds

This runs offline. Faux is a scripted model: it asks for the tool, then answers.

import { Console, Effect, Layer, Schema } from 'effect'
import { HarnessClient, HarnessTransport } from 'effect-harness/client'
import { Extension, HarnessServer } from 'effect-harness/harness'
import { Faux, Model } from 'effect-harness/model'
import { Storage } from 'effect-harness/storage'
import { Tool, Toolkit } from 'effect-harness/tool'

// 1. Declare a tool: a name, a description, and Schemas. No code yet.
const Uppercase = Tool.make('uppercase', {
  description: 'Convert text to uppercase.',
  parameters: Schema.Struct({ text: Schema.String }),
  success: Schema.String,
})
const toolkit = Toolkit.make(Uppercase)

// 2. Implement it, as a Layer, where the loop runs.
const ToolsLive = toolkit.toLayer({
  uppercase: ({ text }) => Effect.succeed(text.toUpperCase()),
})

const program = Effect.gen(function* () {
  // 3. A scripted model: call the tool, then answer. Swap in a real provider later.
  const faux = yield* Faux.make()
  yield* faux.setResponses([
    Faux.message(Faux.toolCall('uppercase', { text: 'hello' }), { stopReason: 'toolUse' }),
    Faux.message('It says HELLO.'),
  ])

  // 4. The loop: extensions, settings, storage and a model.
  const ServerLive = HarnessServer.layer({
    extensions: [Extension.make('text', { toolkit })],
    settings: { model: faux.ref() },
  }).pipe(Layer.provide([ToolsLive, Model.layer([faux.provider]), Storage.layerMemory]))

  // 5. Loop and client share one in-memory transport.
  const AppLive = Layer.merge(ServerLive, HarnessClient.layer).pipe(
    Layer.provide(HarnessTransport.layerMemory),
  )

  yield* Effect.gen(function* () {
    const client = yield* HarnessClient.HarnessClient
    const root = yield* client.root()
    const submission = yield* root.submit({ type: 'input', content: 'Shout "hello" for me.' })
    const settled = yield* submission.wait
    yield* Console.log(`status: ${settled.status}`)

    const { items } = yield* root.entries({ order: 'ascending' }, 50)
    for (const entry of items) yield* Console.log(entry.kind)
  }).pipe(Effect.provide(AppLive))
})

Effect.runPromise(Effect.scoped(program))
status: done
$user
$system
$assistant
$tool-result
$assistant

That transcript is the whole story: your message, the system prompt, the model asking for uppercase, the tool's result, and the final answer. Getting started walks through it line by line and then swaps in a real model and a database.

How it fits together

flowchart LR
  subgraph app["Your app (anywhere)"]
    C["HarnessClient"]
  end
  subgraph loop["The loop (HarnessServer)"]
    H["Harness<br/>conversations · submissions · runs"]
    S["Scheduler<br/>durable tasks"]
    X["Extensions<br/>tools · sections · hooks"]
  end
  C <-- "RPC: memory · WebSocket · Durable Object" --> H
  H --> S
  S -- "$generation" --> M(["Model<br/>OpenAI · Anthropic · Codex · Faux"])
  S -- "$tool" --> T["Tool handlers<br/>(your Layers)"]
  X -. selected per conversation .-> S
  H --> ST[("Storage<br/>memory · JSONL · SQL")]

A tool declaration holds Schemas only, so the client can import it. Toolkit.toLayer supplies the implementation where the loop runs. An extension bundles tools with system-prompt sections and hooks, and each conversation picks which extensions it uses. HarnessServer.layer builds the loop and serves it; HarnessClient.layer is what your code calls. Read Concepts for the full picture.

Documentation

| Guide | What you'll learn | | --------------------------------------------- | ------------------------------------------------------------------------------------ | | 🚀 Getting started | Run a conversation, then switch to a real model and persistent storage. | | 🧭 Concepts | Conversations, submissions, runs, entries, documents and tasks, and how they relate. | | 💬 Conversations | Submitting, steering, forking, resetting, compacting and watching live events. | | 🛠️ Tools | Declaring tools, config, replay safety, rich results, nested calls, stock tools. | | 🧩 Extensions | System-prompt sections, hooks, wrappers and per-conversation selection. | | 🤖 Providers & accounts | OpenAI, Anthropic, Codex and Claude sign-in, credentials and run-time accounts. | | 💾 Persistence | Storage backends, recovery, your own durable documents and named inits. | | 🌍 Deployment | WebSocket servers, browsers, and Cloudflare Durable Objects. | | 📚 Reference | Every subpath, settings and their defaults, built-in kinds. |

Examples

  • Cloudflare: one harness per Durable Object, with accounts added at run time and a command-line chat client over WebSockets.

Develop

bun install --frozen-lockfile
bun run setup        # patch the local compiler and linter for Effect diagnostics
bun run build
bun run check        # format, lint, typecheck
bun run test
bun run test:package # pack, install into an isolated consumer, run under Node 24+

Tests live next to the code, in each module's test/ folder, with fixtures several modules share in src/test/. Every PR carries a changeset (bun run changeset); see Releasing for the publishing workflow.

Credits

The harness architecture is nearly all Pi Durable's. The durable session and storage model, the task scheduler, conversation turns and recovery, compaction, the coding tools and the provider sign-in flows all follow Pi's design, and many modules are direct ports of it.

My work has mostly been porting it to Effect and making it take advantage of Effect's composability and ecosystem: Schemas for every tool, record and setting; Layers for handlers, storage and providers; typed RPC between client and loop; and Effect's platform, SQL and AI packages underneath.

Source module comments identify the Pi component each one models or ports; code mode uses @earendil-works/pi-codemode. The distributed NOTICE retains the upstream attribution and MIT license.