What survives a run.
deja provides durable recall for agent systems.
It extracts structured memory from completed runs and stores it independently of any single execution. Memory in deja is explicit, reviewable, and optional.
Agents may consult deja. They are never required to.
- Post-run recall — derived from artifacts and outcomes
- Addressable and scoped — by user, agent, or session
- Designed to persist — longer than any single agent session
- Self-hosted — runs on your Cloudflare account
- A shared service
- Conversation history
- Implicit context
- Hidden state
Long-running systems repeat work unless memory is made explicit.
deja captures what mattered after execution, so future runs can begin informed rather than reactive.
All entries in deja are:
- Stored in your Cloudflare account
- Traceable to a source run
- Auditable
- Removable
- Scoped by intent
Memory persists by choice, not by accident.
Or deploy manually:
- Cloudflare account
- Wrangler CLI (
npm install -g wrangler) - Node.js 18+ or Bun
# Clone
git clone https://github.com/acoyfellow/deja
cd deja
# Install dependencies
bun install # or npm install
# Login to Cloudflare
wrangler login
# Create vectorize index for semantic search
wrangler vectorize create deja-embeddings --dimensions 384 --metric cosine
# Set your API key (you'll use this to authenticate requests)
wrangler secret put API_KEY
# Enter a secure random string when prompted
# Deploy
bun run deploy # or npm run deployAfter deploy, wrangler outputs your worker URL:
Published deja (1.0.0)
https://deja.<your-subdomain>.workers.dev
Edit wrangler.json before deploying:
{
"name": "deja",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
"workers_dev": true,
"durable_objects": {
"bindings": [
{ "name": "DEJA", "class_name": "DejaDO" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["DejaDO"] }
],
"vectorize": [
{ "binding": "VECTORIZE", "index_name": "deja-embeddings" }
],
"ai": {
"binding": "AI"
}
}Replace $DEJA_URL with your deployed worker URL.
Replace $API_KEY with the key you set during setup.
curl -X POST $DEJA_URL/learn \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trigger": "when deploying to cloudflare",
"learning": "run wrangler deploy --dry-run first",
"confidence": 0.9
}'curl -X POST $DEJA_URL/inject \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"context": "deploying a cloudflare worker",
"format": "prompt",
"limit": 5
}'Returns entries semantically relevant to the context.
Durable Object per user. Each user gets isolated storage. Isolation by architecture, not access control.
Your infrastructure
│
▼
┌──────────────────────────────────────┐
│ DejaDO │
│ ┌────────────────────────────────┐ │
│ │ SQLite (entries, secrets) │ │
│ └────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Vectorize │
│ (semantic retrieval) │
└──────────────────────────────────────┘
Entries are scoped:
| Scope | Visibility |
|---|---|
shared |
All agents for this user |
agent:<id> |
Specific agent |
session:<id> |
Specific session |
Callers declare which scopes they can access. deja filters accordingly.
Store an entry.
curl -X POST $DEJA_URL/learn \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trigger": "when this is relevant",
"learning": "what to recall",
"confidence": 0.9,
"scope": "shared"
}'Retrieve relevant entries for a context. Tracks hits.
curl -X POST $DEJA_URL/inject \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"context": "describe the task",
"scopes": ["shared", "agent:myagent"],
"limit": 5,
"format": "prompt"
}'Search entries without tracking hits.
curl -X POST $DEJA_URL/query \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "search term", "limit": 10}'List entries with optional filters.
curl "$DEJA_URL/learnings?scope=shared&limit=20" \
-H "Authorization: Bearer $API_KEY"Remove an entry.
curl -X DELETE $DEJA_URL/learning/<id> \
-H "Authorization: Bearer $API_KEY"Get memory statistics.
curl $DEJA_URL/stats \
-H "Authorization: Bearer $API_KEY"deja also stores secrets, scoped the same way as entries.
curl -X POST $DEJA_URL/secret \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "OPENAI_KEY", "value": "sk-...", "scope": "shared"}'curl $DEJA_URL/secret/OPENAI_KEY \
-H "Authorization: Bearer $API_KEY"curl -X DELETE $DEJA_URL/secret/OPENAI_KEY \
-H "Authorization: Bearer $API_KEY"If you're building on Cloudflare and want direct access without HTTP:
// In your wrangler.json, add:
"services": [
{ "binding": "DEJA", "service": "deja", "entrypoint": "DejaDO" }
]
// In your code
const deja = env.DEJA.get(env.DEJA.idFromName(userId));
// Entries
await deja.inject(scopes, context, limit);
await deja.learn(scope, trigger, learning, confidence, source);
await deja.query(scopes, text, limit);
await deja.getLearnings(filter);
await deja.deleteLearning(id);
// Secrets
await deja.getSecret(scopes, name);
await deja.setSecret(scope, name, value);
await deja.deleteSecret(scope, name);
// Stats
await deja.getStats();bun install
bun run dev # local dev server
bun run test # run tests
bun run deploy # deploy to Cloudflare- Cloudflare Workers + Durable Objects
- SQLite (DO storage)
- Vectorize (semantic retrieval)
- Workers AI (embeddings)
- Hono (HTTP routing)
Recall, by design.