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

@sediment/sediment

v0.2.5

Published

TypeScript framework for building composable AI systems

Readme

Sediment

A TypeScript framework for building composable AI systems. It handles the boring parts (logging, retries, concurrency, caching) so you can focus on the interesting parts (prompts, workflows, tools).

Why Sediment?

Sediment is built around four core values that make AI systems sustainable over time:

Composability

Avoid the "goto" anti-pattern. Monolithic agents where "anything can call anything" are like writing programs with jumps and gotos—the allure of generalization produces pipelines that are hard to understand and steer.

Instead, Sediment encourages higher-level tasks that orchestrate lower-level tasks rather than calling tools directly. This provides:

  • High parallelism: After planning, many subtasks can fire off concurrently
  • Clean context: Lower-level tasks filter and process results before returning to orchestrators
  • Steerability without sacrificing autonomy: A higher-level model steers at a high level of abstraction rather than contending with individual tool details

Observability

Design systems to be observable, explainable, and debuggable. You should be able to root cause and reproduce failures from single production examples.

Sediment can log the entire payload to/from inference servers when you opt into full logging. By default, sensitive fields are redacted. Each log record includes:

  • Metadata (which step, identifiers)
  • Complete request payload
  • Complete response stream (all tokens when logChunks: true)

For 400+ step pipelines, you can replay every completion call and identify exactly where non-determinism caused a failure.

Statelessness

The core pipeline is a pure function: input → output.

Actions as Nouns, Not Verbs: Actions that the model wants to take are not verbs (executed immediately). They're nouns (data structures describing intended effects). The caller validates and executes effects after the pipeline completes.

When actions are nouns, you get powerful behavior for free:

  • Models can review, rank, and correct actions (they're just JSON payloads)
  • You can generate draft candidates of many possible actions and vote across them
  • Persistence for human review becomes trivial
  • Eval environments are simpler—you examine drafted actions rather than final world state

Changeability

Maintain disciplined interface boundaries between layers that change at different rates:

  1. Core APIs (slowest changing) - Step API, adapters, log types. Like the stud system that makes Lego blocks fit together.
  2. Lower-level tasks (medium) - Entity resolution, answer generation, context gathering. The building blocks themselves.
  3. Product surfaces (fastest) - Interactive research, background jobs, specific workflows. Full Lego sets assembled from building blocks.

This separation means vendor API changes are insulated from the rest of the system, and new UI features can be added without affecting your building blocks.


Installation

Sediment is published as the public scoped npm package @sediment/sediment.

Local Development

Install with your package manager of choice:

pnpm add @sediment/sediment

Import the eval helpers from the eval subpath:

import { createEvalRunner } from '@sediment/sediment/eval';

GitHub Actions (Consumer Repos)

Use the ready template:

  • docs/examples/consumer-github-actions.yml

It installs @sediment/sediment from public npm without a Sediment-specific npm token. Add npm auth only if the consumer repo also depends on other private packages.

Vercel and Cloudflare

For deploy platforms that install dependencies during builds, no NPM_TOKEN is required for @sediment/sediment. Add npm auth only for other private dependencies.

Publishing from this Repo

Do the first publish manually from this repo after npm login.

After that, configure npm trusted publishing for this repo and future releases can come from tags. Once trusted publishing is configured, pushing a vX.Y.Z tag makes GitHub Actions:

  1. validate the package
  2. pack the exact tarball consumers install
  3. publish @sediment/sediment to npm
  4. attach the tarball to a GitHub Release

The detailed npm setup, first publish steps, and consumer install patterns live in docs/platform-distribution.md.

Quick Start

import { Runtime, createAnthropicAdapter, defineTask } from '@sediment/sediment';

// Create a runtime with your API key
const runtime = Runtime.create({
  completions: createAnthropicAdapter({
    apiKey: process.env.ANTHROPIC_API_KEY
  }),
});

// Define a task
const summarize = defineTask<string, string>(
  'summarize',
  async function* (text, deps) {
    for await (const chunk of deps.completions.complete({
      model: 'claude-sonnet-4-5-20250929',
      messages: [
        { role: 'user', content: `Summarize this: ${text}` }
      ],
      maxTokens: 200,
    })) {
      if (chunk.type === 'token') {
        yield chunk.content;
      }
    }
  }
);

// Run it with ExecutionResult
const result = await runtime.runTask(summarize, 'Your text here...');
console.log(result.outputs.join(''));
console.log(`Cost: $${result.estimatedCost.toFixed(4)}`);

Core Concepts

Tasks

Tasks are typed functions that call models and tools. They stream outputs and don't execute side effects directly.

const myTask = defineTask<Input, Output>('name', async function* (input, deps) {
  // Use deps.completions for model calls
  // Use deps.tools for tool execution
  // Use deps.effects to collect side effects
  // Use deps.citations to track sources
  yield output;
});

Runtime

The runtime provides dependencies to tasks. It handles logging, concurrency, and caching automatically.

const runtime = Runtime.create({
  completions: createAnthropicAdapter({ apiKey }),  // or createOpenAIAdapter
  logger: createConsoleLogger(),                     // optional
  pricing: MY_PRICING_TABLE,                         // optional, for cost tracking
  logChunks: true,                                   // enables replay capability
});

ExecutionResult

When you run a task through the Runtime, you get back an ExecutionResult that bundles outputs with all collected stores and metadata:

const result = await runtime.runTask(myTask, input);

// All outputs yielded by the task
console.log(result.outputs);

// Context ID for correlating with logs
console.log(result.contextId);

// Access collected effects (side effects as data)
const effects = result.effectStore.getAll();
for (const effect of effects) {
  if (effect.type === 'send_email') {
    await sendEmail(effect.payload);  // Execute after validation
  }
}

// Access citations for attribution
const citations = result.citationStore.getAll();

// Estimated cost in USD
console.log(`Cost: $${result.estimatedCost.toFixed(4)}`);

You can also use Runtime.createReplayable() to create a runtime with full replay capability enabled:

// Enables logChunks: true with unlimited capture
const runtime = Runtime.createReplayable({
  completions: adapter,
  pricing: MY_PRICING,
});

Effects

Instead of executing side effects (sending emails, updating databases), tasks collect them as data. You decide when to execute.

deps.effects.add({
  type: 'send_email',
  payload: { to: '[email protected]', subject: 'Hello', body: '...' }
});

// Later, inspect and execute
const effects = deps.effects.getAll();

Citations

Track where information came from:

deps.citations.add({
  id: 'cite_1',
  source: 'https://example.com',
  content: 'The relevant quote...',
});

Step DSL

Sediment provides a fluent DSL for building typed pipelines declaratively:

import { sediment, sequence, parallel } from '@sediment/sediment';

// Basic pipeline
const pipeline = sediment
  .input<string>()
  .completion('analyze', {
    model: 'claude-sonnet-4-5-20250929',
    renderPrompt: (q) => [{ role: 'user', content: q }],
  })
  .parse({ type: 'json' })
  .toTask('analysis_pipeline');

Step Methods

| Method | Description | |--------|-------------| | .completion(name, config) | Call a model with the given config | | .parse(schema) | Parse output (json, capture, regex, lines) | | .map(fn) | Transform each output | | .filter(predicate) | Filter outputs | | .collect() | Collect all outputs into an array | | .branch(branches) | Run multiple steps in parallel on the same input | | .loop(condition, body, maxIterations) | Loop while condition is true | | .conditional(predicate, ifTrue, ifFalse) | Conditional branching | | .effect(type, payloadFn) | Add an effect to the effect store | | .dict(schemas) | Apply multiple parse schemas to the same output | | .debug(label, options) | Log step execution for debugging | | .tap(fn) | Side effect without transforming output | | .take(n) | Take only first N outputs | | .tool(name, mapper) | Execute a tool | | .toTask(name) | Convert Step to a named Task for use with Runtime | | .merge(combiner) | Combine results from branch operations |

Parse Schemas

// JSON parsing
.parse({ type: 'json' })

// Capture between delimiters
.parse({ type: 'capture', start: '```json', end: '```' })

// Regex extraction
.parse({ type: 'regex', pattern: /\d+/ })

// Split into lines
.parse({ type: 'lines', filter: line => line.trim().length > 0 })

Dict: Multiple Parse Schemas

Extract multiple values from the same output:

const step = sediment.input<string>()
  .completion('generate', config)
  .dict({
    urls: sediment.capture('```urls', '```').collect(),
    search: sediment.capture('```search', '```').collect(),
  });
// Returns { urls: string, search: string }

Debug: Observability Integration

Log step execution while passing output through unchanged:

const step = sediment.input<string>()
  .completion('analyze', config)
  .debug('analyze_step', { rawChunks: true, truncateLength: 1000 });

Global Combinators

// Sequential composition of Tasks
const pipeline = pipe(task1, task2, task3);

// Parallel execution of Tasks on same input
const fanOut = parallel(searchWebTask, searchInternalTask, searchCacheTask);

Note: The pipe and parallel combinators work with Tasks (created via defineTask or .toTask()). For composing Steps before converting to a Task, use the fluent .branch() method, or use sequence/parallelSteps from the Step module.


Cost Tracking

Sediment tracks token usage and calculates cost estimates. You provide a pricing table with your models:

import type { PricingTable } from '@sediment/sediment';

const MY_PRICING: PricingTable = {
  'claude-sonnet-4-5': { inputTokenCostPer1m: 3, outputTokenCostPer1m: 15 },
  'claude-haiku-4-5': { inputTokenCostPer1m: 1, outputTokenCostPer1m: 5 },
  'gpt-4o': { inputTokenCostPer1m: 2.5, outputTokenCostPer1m: 10 },
};

const runtime = Runtime.create({
  completions: adapter,
  pricing: MY_PRICING,
});

const result = await runtime.runTask(myTask, input);
console.log(`Estimated cost: $${result.estimatedCost.toFixed(4)}`);

Pricing lookup supports prefix matching for versioned models—claude-sonnet-4-5-20250929 matches the claude-sonnet-4-5 entry.


Debugging Workflow

Sediment enables a tight debugging loop from production failures:

1. Capture Request ID

Every runtime instance has a request ID that links all traces:

const runtime = Runtime.create({ completions: adapter });
const requestId = runtime.getRequestId();  // Pass to UI for feedback capture

2. Query Logs

const logger = runtime.getLogger();
const records = logger.getRecords();

// Filter by context path for parallel execution traces
const branch0 = records.filter(r => r.contextPath?.startsWith('root/parallel[0]'));

// Each record contains:
// - params: the CompletionParams sent
// - response: { content, toolCalls, usage, chunks (if logChunks enabled) }
// - durationMs, timestamp, contextPath, cost

3. Replay Locally

With logChunks: true, you can replay the exact sequence of chunks:

const runtime = Runtime.createReplayable({
  completions: createCachedAdapter(
    createAnthropicAdapter({ apiKey }),
    { storage: createFileCacheStorage('.cache/completions') }
  ),
});

4. Fix and Add to Eval Suite

// Same task definitions, same runtime with caching
await runner.runAll([
  {
    name: 'regression_case_123',
    input: capturedInput,
    assertions: [{ type: 'contains', value: 'expected' }],
  },
]);

Building on Top

Sediment has four layers:

| Layer | What It Is | Examples | |-------|------------|----------| | 0 | Platform primitives | Model completions, tool execution (this package) | | 1 | Building blocks | Entity resolution, search, answer generation | | 2 | Composite tasks | Research loops, multi-step workflows | | 3 | Products | Chat assistants, alerting systems, report generators |

You build upward. Define Layer 1 tasks, compose them into Layer 2, wire Layer 2 into products.

Example: Building Block Task

// Layer 1: A reusable search task
const searchWeb = defineTask<string, SearchResult[]>(
  'search_web',
  async function* (query, deps) {
    const results = await deps.tools.execute('web_search', { query });

    // Register citations for each result
    for (const result of results) {
      deps.citations.add({
        id: `web_${result.id}`,
        source: result.url,
        content: result.snippet,
      });
    }

    yield results;
  }
);

Example: Composite Task

// Layer 2: Compose building blocks
const researchQuestion = defineTask<string, ResearchResult>(
  'research_question',
  async function* (question, deps) {
    // Run searches in parallel
    const [webResults, internalResults] = await Promise.all([
      runTask(searchWeb, question, deps),
      runTask(searchInternal, question, deps),
    ]);

    // Synthesize an answer
    const answer = await runTask(generateAnswer, {
      question,
      context: [...webResults, ...internalResults],
    }, deps);

    yield answer;
  }
);

Context Paths (Trace Hierarchy)

When running parallel tasks, Sediment tracks the execution hierarchy for visualization:

import { parallel, pipe } from '@sediment/sediment';

// Nested parallel execution using Task combinators
// (These are Tasks, not Steps - use .toTask() to convert Steps first)
const pipeline = pipe(
  taskA,
  parallel(
    pipe(taskB1, taskB2),
    pipe(taskC1, taskC2),
  ),
  taskD
);

// Each completion/tool record includes contextPath:
// - taskA: "root"
// - taskB1, taskB2: "root/parallel[0]"
// - taskC1, taskC2: "root/parallel[1]"
// - taskD: "root"

Adapter Reference

This is the menu of what's available. Build tasks by combining these capabilities.

Completions Adapters

All completions adapters share the same interface. Pass CompletionParams, get back streaming CompletionChunks.

CompletionParams

{
  model: string;                              // Required: model identifier
  messages: Message[];                        // Required: conversation history
  temperature?: number;                       // 0.0 - 1.0, controls randomness
  maxTokens?: number;                         // Max tokens to generate
  tools?: ToolDefinition[];                   // Tools the model can call
  toolChoice?: 'auto' | 'none' | 'required' | { type: 'function'; function: { name: string } };  // How the model should use tools
  stopSequences?: string[];                   // Custom stop sequences
  adapterExtras?: AdapterExtras;              // Vendor-specific parameters
}

CompletionChunks (What You Get Back)

// Text content streaming
{ type: 'token'; content: string }

// Model wants to call a tool
{ type: 'tool_call'; toolCall: { id: string; name: string; arguments: string } }

// Extended thinking content (Anthropic only)
{ type: 'thinking'; thinking: string; signature?: string }

// Reasoning summary content (provider-neutral, not raw chain-of-thought)
{ type: 'reasoning_summary'; content: string; done?: boolean }

// Stream complete
{ type: 'done'; usage?: { inputTokens: number; outputTokens: number } }

Anthropic Adapter

import { createAnthropicAdapter } from '@sediment/sediment';

const adapter = createAnthropicAdapter({
  apiKey: string;           // Required
  baseUrl?: string;         // Default: https://api.anthropic.com/v1/messages
  maxConcurrency?: number;  // Default: 5
  maxRetries?: number;      // Default: 3
  timeoutMs?: number;       // Default: 60000
});

This adapter uses the official Anthropic SDK (@anthropic-ai/sdk) with typed streaming and automatic retry handling.

Supported Models: claude-sonnet-4-5-20250929, claude-opus-4-5-20251101, claude-haiku-4-5-20251001, etc.

Unique Features:

  • Extended Thinking: Claude can show its reasoning process before answering.
const chunks = deps.completions.complete({
  model: 'claude-sonnet-4-5-20250929',
  messages: [{ role: 'user', content: 'Solve this step by step...' }],
  maxTokens: 16000,
  adapterExtras: {
    anthropic: { thinking: { type: 'enabled', budgetTokens: 8000 } }
  },
});

for await (const chunk of chunks) {
  if (chunk.type === 'thinking') {
    console.log('[Thinking]', chunk.thinking);
  }
  if (chunk.type === 'token') {
    process.stdout.write(chunk.content);
  }
}

OpenAI Adapter

import { createOpenAIAdapter } from '@sediment/sediment';

const adapter = createOpenAIAdapter({
  apiKey: string;           // Required
  baseUrl?: string;         // Default: https://api.openai.com/v1
  maxConcurrency?: number;  // Default: 5
  maxRetries?: number;      // Default: 3
  timeoutMs?: number;       // Default: 60000
});

This adapter uses the official OpenAI SDK (openai) and the Responses API streaming interface.

OpenAI reasoning summaries are opt-in through adapterExtras.openai.reasoningSummary. The adapter streams them as provider-neutral reasoning_summary chunks and does not expose raw chain-of-thought.

for await (const chunk of deps.completions.complete({
  model: 'gpt-5.4',
  messages: [{ role: 'user', content: 'Plan and answer.' }],
  adapterExtras: {
    openai: { reasoningEffort: 'low', reasoningSummary: 'auto' },
  },
})) {
  if (chunk.type === 'reasoning_summary') {
    updateWorkingState(chunk.content, { done: chunk.done });
  }
}

Cached Adapter

Wraps any completions adapter to add caching. Essential for evals (deterministic replay) and development (avoid repeated API calls).

import { createCachedAdapter, createInMemoryCacheStorage, createFileCacheStorage } from '@sediment/sediment';

// In-memory cache (for tests)
const cached = createCachedAdapter(
  createAnthropicAdapter({ apiKey }),
  { storage: createInMemoryCacheStorage() }
);

// File-based cache (for development/evals)
const cached = createCachedAdapter(
  createAnthropicAdapter({ apiKey }),
  { storage: createFileCacheStorage('.cache/completions') }
);

Configuration:

{
  storage: CacheStorage;           // Required: where to store cached completions
  keyFn?: (params) => string;      // Custom cache key function
  excludeFields?: string[];        // Fields to exclude from cache key
}

Storage Implementations:

| Storage | Use Case | Persistence | |---------|----------|-------------| | createInMemoryCacheStorage() | Unit tests | None (in-process only) | | createFileCacheStorage(dir) | Development, evals | Filesystem |


Tools Adapter

Register and execute tools that models can call.

import { createToolsAdapter } from '@sediment/sediment';

const tools = createToolsAdapter();

// Register a tool
tools.register(
  {
    name: 'get_weather',
    description: 'Get current weather for a city',
    parameters: {
      type: 'object',
      properties: {
        city: { type: 'string', description: 'City name' },
        units: { type: 'string', enum: ['celsius', 'fahrenheit'] },
      },
      required: ['city'],
    },
  },
  async (args) => {
    // Your implementation
    return JSON.stringify({ temp: 72, conditions: 'sunny' });
  }
);

// Pass to runtime
const runtime = Runtime.create({
  completions: createAnthropicAdapter({ apiKey }),
  tools,
});

// Tools are automatically available to models
const deps = runtime.getDeps();
for await (const chunk of deps.completions.complete({
  model: 'claude-sonnet-4-5-20250929',
  messages: [{ role: 'user', content: 'What is the weather in NYC?' }],
  tools: deps.tools.list(),  // Pass registered tools
  toolChoice: 'auto',
})) {
  if (chunk.type === 'tool_call') {
    // Execute the tool
    const result = await deps.tools.execute(
      chunk.toolCall.name,
      JSON.parse(chunk.toolCall.arguments)
    );
    console.log('Tool result:', result);
  }
}

ToolsAdapter Methods:

| Method | Description | |--------|-------------| | register(definition, handler) | Register a tool with its handler function | | execute(name, args) | Execute a tool, returns ToolResult | | list() | Get all registered tool definitions | | has(name) | Check if a tool is registered |


Stores

EffectStore

Collect side effects as data instead of executing them immediately.

deps.effects.add({ type: 'send_email', payload: { to, subject, body } });
deps.effects.add({ type: 'update_db', payload: { table, record } });

// Later, review and execute
const effects = deps.effects.getAll();
for (const effect of effects) {
  if (effect.type === 'send_email') {
    await sendEmail(effect.payload);
  }
}

CitationStore

Track information sources for attribution.

// Add a citation with explicit id
deps.citations.add({
  id: 'cite_1',
  source: 'https://example.com/article',
  content: 'The relevant quote...',
});

// Or use the create() helper to auto-generate an id
const citationId = deps.citations.create(
  'https://example.com/article',
  'The relevant quote...'
);

// Retrieve citations
const fetched = deps.citations.get('cite_1');
const allCitations = deps.citations.getAll();

LogStore

Full request/response logging with context path tracking.

const logger = runtime.getLogger();

// Get all completion records
const records = logger.getRecords();

// Filter by context path (for parallel execution traces)
const branch0 = records.filter(r => r.contextPath?.startsWith('root/parallel[0]'));

// Each record contains:
// - params: the CompletionParams sent
// - response: { content, toolCalls, usage }
// - durationMs, timestamp, contextPath

Eval

Test runner with deterministic caching for reproducible evaluations.

import { createEvalRunner } from '@sediment/sediment/eval';
import { createFileCacheStorage, createCachedAdapter, createAnthropicAdapter } from '@sediment/sediment';

const runner = createEvalRunner(summarizeTask, {
  runtimeConfig: {
    completions: createCachedAdapter(
      createAnthropicAdapter({ apiKey }),
      { storage: createFileCacheStorage('.cache/eval') }
    ),
  },
});

// Define test cases
await runner.runAll([
  {
    id: 'summarization_quality',
    input: 'Long article text...',
    assertions: [
      { type: 'contains', value: 'key point' },
      { type: 'token_count', max: 200, countType: 'output' },
    ],
  },
]);

Versioning and Upgrades

  • SemVer is enforced via git tags, npm releases, and GitHub Releases.
  • Breaking changes ship only in major versions and include migration notes.
  • New features ship in minor versions.
  • Fixes and internal hardening ship in patch versions.

Deprecation Policy

  • Deprecated APIs remain available for at least one major cycle.
  • Use CI dependency update PRs (Renovate/Dependabot) for controlled upgrades.

Docs

  • docs/platform-distribution.md - public npm package setup, publishing, and consumption.
  • docs/security-controls.md - enforced and manual security controls.
  • docs/polymer_spec.md - framework architecture and design goals.