Persistent State

Tools can remember progress, cache results, and resume work between executions.

What is Persistent State?

When you create a tool in your personal library, it gets access to persistent storage that survives between executions. This lets you build tools that:

  • Track progress across multiple runs
  • Cache expensive computations
  • Resume interrupted work
  • Build up datasets over time

How It Works

Each tool in your personal library automatically gets access to toolState—a simple key-value store that persists between executions. The state is stored in a Durable Object, so it's fast, atomic, and reliable.

Note: Persistent state is only available for tools in your personal library (user tools), not for public/demo tools.

API Reference

The toolState object is available globally in your tool code:

toolState.get(key)

Retrieve a persisted value by key. Returns null if the key doesn't exist.

const count = await toolState.get('counter') || 0;

toolState.set(key, value)

Save a value by key. The value persists between executions.

await toolState.set('counter', count + 1);

Examples

Counter Tool

A simple counter that increments each time it's called:

const count = await toolState.get('counter') || 0;
const newCount = count + 1;
await toolState.set('counter', newCount);
result = `Count: ${newCount}`;

Progress Tracker

Track progress through a long-running task:

const lastRow = await toolState.get('lastProcessedRow') || 0;
const batch = processRows(startingAt: lastRow, count: 100);
await toolState.set('lastProcessedRow', lastRow + batch.length);
result = `Processed ${batch.length} rows, total: ${lastRow + batch.length}`;

Result Caching

Cache expensive computations:

const cacheKey = `result_${parsed.input}`;
const cached = await toolState.get(cacheKey);
if (cached) {
  result = cached;
} else {
  const computed = expensiveComputation(parsed.input);
  await toolState.set(cacheKey, computed);
  result = computed;
}

Limitations

  • State is scoped per tool—each tool has its own isolated state
  • Storage limit: ~128MB per tool (Durable Object limit)
  • Only available for user tools (not public/demo tools)
  • Values are serialized as JSON (objects, arrays, primitives supported)

Best Practices

  • Use descriptive keys (e.g., 'progress' not 'p')
  • Handle null values when reading (use || defaultValue)
  • Store structured data as JSON objects
  • Clean up old state if needed (set to null to delete)

SQL Database

In addition to key-value state storage, each tool also has access to its own isolated SQLite database via toolSQL. This is perfect for structured data, complex queries, and relational data.

toolSQL.exec(query, ...params)

Execute SQL queries and return rows. Supports parameterized queries for safety.

// Create a table
await toolSQL.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');

// Insert data
await toolSQL.exec('INSERT INTO users (name, email) VALUES (?, ?)', 'John', 'john@example.com');

// Query data
const users = await toolSQL.exec('SELECT * FROM users WHERE email = ?', 'john@example.com');
result = JSON.stringify(users);

Tip: Use the Database tab in the web dashboard to view and query your tool's SQL database directly. You can see all tables and execute custom queries.

When to Use SQL vs. State

  • Use toolState for: Simple key-value data, progress tracking, caching, configuration
  • Use toolSQL for: Structured data, complex queries, relational data, aggregations, sorting/filtering

Related

  • Web Dashboard - Create and manage tools with persistent state and SQL database
  • Writing Prompts - Learn how to describe tools that use state and SQL