@pomerene/sdk
v0.1.1
Published
Framework-agnostic SDK for Pomerene. createCache + withCache wrap any agent function (Mastra, LangGraph, raw OpenAI Agents SDK, custom loops) with a deterministic trajectory cache.
Maintainers
Readme
@pomerene/sdk
Framework-agnostic SDK for Pomerene. Wraps any agent function — Mastra agent.generate.bind(agent), a LangGraph node, a raw OpenAI Agents SDK call, a custom loop — with a deterministic trajectory cache that returns cached outcomes when the new query is semantically similar to a cached one.
The cache hit path is zero-LLM by design. No plan adaptation, no parameter rewriting, no runtime LLM judgment.
Install
pnpm add @pomerene/sdk
# or: npm install @pomerene/sdkHosted cloud (createCloudCache)
Point the same withCache / wrapAgent call sites at the managed API
(api.pomerene.io) instead of an in-process store — no database to run:
import { createCloudCache, withCache } from '@pomerene/sdk'
const cache = createCloudCache({ apiKey: 'atc_live_…' }) // → api.pomerene.io
const cached = withCache(myAgent, { cache, extractGoal, extractContext, toOutput })Embeddings run on our key (bundled into the plan). lookup fails open (a cloud
outage → your agent just runs live), with retries + a circuit breaker. Check
usage with cache.stats() or at pomerene.io/usage.
Managed abstraction (no LLM key needed)
R10 goal abstraction (below) normally runs in your pipeline with your LLM
key. If your cloud key has managed abstraction enabled, the cloud abstracts
on its own key instead — at write time, and lazily on lookup (only after a
specific-path miss, so cache hits stay zero-LLM). You then need no LLM key for
Pomerene at all: just use withCache without an abstractor. Ask your
Pomerene contact to enable it on your key.
Other languages
The cloud API is plain HTTP/JSON, with official thin clients:
- Go —
go get github.com/kylemaa/pomerene-go/pomerene - Python —
pip install pomerene
No-wrap alternative: the local proxy (@pomerene/proxy)
withCache needs a function to wrap. For closed-loop agents you don't control
end-to-end (Claude Code, Cursor, a raw loop), there's no agentFn to pass — so
instead run the local proxy and point the agent at it with one env var. Both the
Anthropic and OpenAI wire protocols are supported:
OPENAI_API_KEY=… pnpm --filter @pomerene/proxy dev # listens on :8787
export ANTHROPIC_BASE_URL=http://localhost:8787 # Anthropic agents (Claude Code) → /v1/messages
export OPENAI_BASE_URL=http://localhost:8787/v1 # OpenAI agents (Cursor, …) → /v1/chat/completionsThe agent makes ordinary /v1/messages (or /v1/chat/completions) calls and never knows
Pomerene is there; the daemon runs locally so your provider key passes through and is never
stored. To make turns cacheable you have two options:
- Tag — have the agent emit a
[POM: family | step | taint_keys]tag (via its system prompt / CLAUDE.md) so deterministic structural steps become cacheable. - Auto-tag — run with
POM_PROXY_AUTOTAG=1to deterministically recognize untagged requests for the vetted workloads (zero tagging) and measure your would-be hit rate first; serving from auto-tags stays opt-in behindPOM_PROXY_AUTOTAG_SERVE=1.
Best for repetitive structured work (test-gen, codemods, scheduled agents) — see the
@pomerene/proxy README for the full taint/step model. The hit path stays zero-LLM.
Quickstart — minimal
For string-in / string-out agents, use wrapAgent:
import { createCache, wrapAgent } from '@pomerene/sdk'
const cache = await createCache({
apiKey: process.env.OPENAI_API_KEY,
storage: 'memory', // or 'libsql:file:./traj.db' for persistence
})
const cachedAgent = wrapAgent(myAgent, {
cache,
recordTrajectory: (input, output, durationMs) => ({
id: `entry-${Date.now()}`,
goal: input as string,
goalEmbedding: [],
contextSnapshot: [],
steps: [],
taintMap: [],
outcome: output as string,
cost: { promptTokens: 0, completionTokens: 0, totalTokens: 0, estimatedUsd: 0 },
replayAccuracy: null,
timestamp: new Date(),
metadata: { durationMs },
}),
})
// First call: miss → runs your agent → trajectory recorded
// Second call with similar input: L1 hit → cached outcome, no agent run
await cachedAgent('what time is it in tokyo?')wrapAgent auto-detects goal extraction from string inputs, input.goal, input.query, or input.instruction. For other shapes use withCache with explicit extractors (see below).
Quickstart — LangGraph
import { createCache, withLangGraphCache } from '@pomerene/sdk'
const cache = await createCache({ storage: 'libsql:file:./agent-cache.db' })
const cachedGraph = withLangGraphCache(myCompiledGraph, { cache })
await cachedGraph.invoke({ messages: [new HumanMessage('cancel my flight')] })Quickstart — full control (withCache)
import { createCache, withCache } from '@pomerene/sdk'
const cache = await createCache({ storage: 'memory' })
const cachedAgent = withCache(myAgent, {
cache,
extractGoal: (input) => input.question,
extractContext: (input) => ({ user_id: input.userId }),
toOutput: (cached) => ({ answer: cached.outcome }),
recordTrajectory: (input, output) => ({ /* ... */ }),
})Goal abstraction (R10) — for unique-goal workloads
For workloads where each task has unique slot values (different users, different orders) and verbatim goal repetition is rare, configure a goalAbstractor. The abstractor extracts an action template + slot map at write time; the cache uses lookupWithFallback (specific path first, abstract fallback) so paraphrased queries pick up specific-path hits and unique queries pick up cluster-level hits.
import { createCache, wrapAgent, openaiAbstractor } from '@pomerene/sdk'
import OpenAI from 'openai'
const openai = new OpenAI()
const abstractor = openaiAbstractor(openai, { model: 'gpt-4o-mini' })
const cache = await createCache({ storage: 'libsql:file:./traj.db', goalAbstractor: abstractor })
const cachedAgent = wrapAgent(myAgent, { cache, abstractor, recordTrajectory: /* ... */ })On the hosted cloud, managed abstraction does this server-side on our key (see Hosted cloud above) — you don't run an abstractor yourself.
Run the workload analyzer first to know whether your workload looks more like the airline benchmark (cross-user goal-type repetition; cascade lifts to 86% / 78%) or the retail benchmark (uniquely-specified per-user tasks; cascade trigger 13% but cross-user precision is workload-structurally bounded). See packages/evals/src/workload-analyzer.ts and the benchmark notes in the project README.
Storage shorthands
'memory'→ in-process HNSW + Map (no persistence)'libsql:file:./traj.db'→ LibSQL with separate columns + opt-in HNSW persistence'postgres://…'→ Postgres with JSONB + tenant scoping (production target){ kind: 'libsql', url, authToken? }→ structured config{ kind: 'postgres', connectionString, tableName? }→ structured config{ kind: 'custom', store }→ bring your ownTrajectoryStore
Architectural invariants (kept by the wrapper)
- Zero-LLM hit path. Embeddings are precomputed at write time. Cache lookups are deterministic. The optional
goalAbstractorruns in your pipeline before lookup — outside the cache itself. - Cache layer ≠ agent layer. On miss or partial-replay handoff, control returns to your agent. The cache never modifies prompts.
Documentation + benchmarks
- Project overview + live demo: pomerene.io
- Methodology + headline numbers: airline cascade 86% / 78%; retail 13% / 0% (workload-structural ceiling)
License
MIT
