Skip to content

Storage

All stateful data in Triggerfish flows through a unified StorageProvider abstraction. No module creates its own storage mechanism -- every component that needs persistence takes a StorageProvider as a dependency. This design makes backends swappable without touching business logic and keeps all tests fast and deterministic.

StorageProvider Interface

typescript
interface StorageProvider {
  /** Retrieve a value by key. Returns null if not found. */
  get(key: string): Promise<StorageValue | null>;

  /** Store a value at a key. Overwrites any existing value. */
  set(key: string, value: StorageValue): Promise<void>;

  /** Delete a key. No-op if key does not exist. */
  delete(key: string): Promise<void>;

  /** List all keys matching an optional prefix. */
  list(prefix?: string): Promise<string[]>;

  /** Delete all keys. Use with caution. */
  clear(): Promise<void>;
}

INFO

StorageValue is a string. All structured data (sessions, lineage records, configuration) is serialized to JSON before storage and deserialized on read. This keeps the interface simple and backend-agnostic.

Implementations

BackendUse CasePersistenceConfiguration
MemoryStorageProviderTesting, ephemeral sessionsNone (lost on restart)No configuration needed
SqliteStorageProviderDefault for personal tierSQLite WAL at ~/.triggerfish/data/triggerfish.dbZero configuration
Enterprise backendsEnterprise tierCustomer-managedPostgres, S3, or other backends

MemoryStorageProvider

Used in all tests for speed and determinism. Data exists only in memory and is lost when the process exits. Every test suite creates a fresh MemoryStorageProvider, ensuring tests are isolated and reproducible.

SqliteStorageProvider

The default for personal tier deployments. Uses SQLite in WAL (Write-Ahead Logging) mode for concurrent read access and crash safety. The database lives at:

~/.triggerfish/data/triggerfish.db

SQLite requires no configuration, no server process, and no network. A single file stores all Triggerfish state. The @db/sqlite Deno package provides the binding, which requires --allow-ffi permission.

TIP

SQLite WAL mode allows multiple readers to access the database concurrently with a single writer. This is important for the Gateway, which may read session state while the agent is writing tool results.

Enterprise Backends

Enterprise deployments can plug in external storage backends (Postgres, S3, etc.) without code changes. Any implementation of the StorageProvider interface works. The backend is configured in triggerfish.yaml.

Namespaced Keys

All keys in the storage system are namespaced with a prefix that identifies the data type. This prevents collisions and makes it possible to query, retain, and purge data by category.

NamespaceKey PatternDescription
sessions:sessions:sess_abc123Session state (conversation history, metadata)
taint:taint:sess_abc123Session taint level
lineage:lineage:lin_789xyzData lineage records (provenance tracking)
audit:audit:2025-01-29T10:23:45Z:hook_pre_outputAudit log entries
cron:cron:job_daily_reportCron job state and execution history
notifications:notifications:notif_456Notification queue
exec:exec:run_789Agent execution environment history
skills:skills:skill_weatherInstalled skill metadata
config:config:v3Configuration snapshots

Retention Policies

Each namespace has a default retention policy. Enterprise deployments can customize these.

NamespaceDefault RetentionRationale
sessions:30 daysConversation history ages out
taint:Matches session retentionTaint is meaningless without its session
lineage:90 daysCompliance-driven, audit trail
audit:1 yearCompliance-driven, legal and regulatory
cron:30 daysExecution history for debugging
notifications:Until delivered + 7 daysUndelivered notifications must persist
exec:30 daysExecution artifacts for debugging
skills:PermanentInstalled skill metadata should not expire
config:10 versionsRolling config history for rollback

Design Principles

All Modules Use StorageProvider

No module in Triggerfish creates its own storage mechanism. Session management, taint tracking, lineage recording, audit logging, cron state, notification queues, execution history, and configuration -- all flow through StorageProvider.

This means:

  • Swapping backends requires changing one dependency injection point
  • Tests use MemoryStorageProvider for speed -- no SQLite setup, no filesystem
  • There is exactly one place to implement encryption-at-rest, backup, or replication

Serialization

All structured data is serialized to JSON strings before storage. The serialize/deserialize layer handles:

  • Date objects (serialized as ISO 8601 strings via toISOString(), deserialized via new Date())
  • Branded types (serialized as their underlying string value)
  • Nested objects and arrays
typescript
// Storing a session
const session = { id: "sess_abc", taint: "CONFIDENTIAL", createdAt: new Date() };
await storage.set("sessions:sess_abc", JSON.stringify(session));

// Retrieving a session
const raw = await storage.get("sessions:sess_abc");
if (raw) {
  const session = JSON.parse(raw);
  session.createdAt = new Date(session.createdAt); // Restore Date
}

Immutability

Session operations are immutable. Reading a session, modifying it, and writing it back always produces a new object. Functions never mutate the stored object in place. This aligns with the broader Triggerfish principle that functions return new objects and never mutate.

Directory Structure

~/.triggerfish/
  config/          # Agent configuration, SPINE.md, TRIGGER.md
  data/            # triggerfish.db (SQLite)
  workspace/       # Agent exec environment
    <agent-id>/    # Per-agent workspace (persists)
    background/    # Background session workspaces
  skills/          # Installed skills
  logs/            # Audit logs
  secrets/         # Encrypted credential store

SECURITY

The secrets/ directory contains encrypted credentials managed by the OS keychain integration. Never store secrets in configuration files or in the StorageProvider. Use the OS keychain (personal tier) or vault integration (enterprise tier).

Released under the MIT License.