2 unstable releases
| new 0.6.1 | Feb 10, 2026 |
|---|---|
| 0.4.0 | Jan 30, 2026 |
#1274 in Database interfaces
88KB
2K
SLoC
Veta
Memory and knowledge base for agents, on the command line or as a Cloudflare worker
$ veta add --title "User prefers dark mode" --tags "preferences" --body "Always use dark theme in code examples"
Added note 1
$ veta add --title "Auth uses JWT" --tags "architecture" --body "Tokens expire after 15 minutes"
Added note 2
$ veta grep "dark"
1: User prefers dark mode (2026-02-01 14:30) -- Always use dark theme in code examples
$ veta tags
architecture (1 note)
preferences (1 note)
Veta is a database of notes that an agent can use to keep persistent memory. Notes are organized in tags, each note can have multiple tags.
A note has an autogenerated sequential ID, a title, a body, a list of tags, optional references (source code paths, URLs, documentation links, etc.), and a last modified date.
Veta runs both as a CLI (using local files with symlinks) and as a Cloudflare Worker (using D1). Both share the same core logic written in Rust.
Table of contents
- Installation
- Agent skill
- CLI usage
- Worker deployment
- HTTP API
- Example: Agents SDK chat app
- Architecture
Installation
Via Homebrew (macOS/Linux)
brew install andreasjansson/tap/veta
Via Cargo
cargo install veta
Pre-built binaries
Download from the releases page. Binaries are available for:
- Linux (x86_64, ARM64)
- macOS (Intel, Apple Silicon)
- Windows (x86_64, ARM64)
From source
git clone https://github.com/andreasjansson/veta.git
cd veta
cargo install --path crates/veta
Agent skill
Veta includes a skill file that teaches AI coding agents (like Claude Code) how to use Veta as persistent memory. The skill encourages proactive memory writes - documenting decisions, gotchas, and preferences before they're forgotten.
# Claude Code - personal (available across all your projects):
cp -r skills/veta ~/.claude/skills/
# Claude Code - project-specific (commit to version control):
cp -r skills/veta .claude/skills/
# OpenCode:
cp -r skills/veta ~/.config/opencode/skill/
CLI usage
Initialize
Before using Veta, initialize a database in your project directory:
$ veta init
Initialized veta database in .veta
This creates a .veta directory with notes stored as JSON files and tags organized via symlinks:
.veta/
notes/
1.json
2.json
tags/
architecture/
1.json → ../notes/1.json
debugging/
2.json → ../notes/2.json
Veta commands work from this directory and any subdirectory (it searches up the tree for .veta).
Add a note
# For long content, pipe stdin to `veta add`
$ echo "my body content..." | veta add --title "My title" --tags "comma,separated,tags"
Added note 1
# For short notes, use `--body`
$ veta add --title "My title" --tags "comma,separated,tags" --body "Short body"
Added note 2
# Add references to source code, URLs, documentation, etc.
$ veta add --title "Auth bug fix" --tags "debugging" --body "Fixed JWT expiry" \
--references "src/auth.rs:42,https://jwt.io/introduction"
Added note 3
References are optional pointers to external resources like source code locations, URLs, or documentation links that provide context for the note.
List all tags
$ veta tags
architecture (7 notes)
implementation-notes (3 notes)
design-decisions (1 note)
testing (11 notes)
deployment (4 notes)
debugging (9 notes)
List notes within a tag
# List notes within one or more tags
$ veta ls testing
78: Second issue note (2026-01-28 10:35) -- Second testing body truncated...
41: First testing note (2026-01-26 22:30) -- First testing body truncated...
# List all notes
$ veta ls
# List notes within a time range (SQLite datetime format)
$ veta ls --from "2026-01-01 00:00:00" --to "2026-01-20 00:00:00"
# Human-readable dates are supported
$ veta ls debugging --from "2 days ago"
$ veta ls --from "yesterday" --to "today"
$ veta ls gotchas --from "1 week ago" --to "now"
# `--from` and `--to` can be used together or individually
To avoid token explosions, we only show the latest 100 notes by default. If there are more, a message like [Showing the latest 100/250 notes] is displayed. Use --head/-n to change the limit, where 0 shows all notes.
Show a note
$ veta show 24
# My note about something
body line 1
body line 2
[...]
---
Last modified: 2026-01-07 11:41
Tags: testing,implementation-notes
References:
- src/auth.rs:42
- https://jwt.io/introduction
References are only shown if the note has any.
Show multiple notes at once:
$ veta show 1,2,3
Use --head/-n to show only the first n lines of each note body:
$ veta show 24 -n 5
Edit a note
# For long content, pipe stdin to `veta edit`
$ echo "my new body content..." | veta edit 71
Edited note 71: Updated body
# For short notes, use `--body`. You can also edit tags, title, and references
$ veta edit 71 --title "My new title" --tags "comma,separated,tags" --body "Short body"
Edited note 71: Updated title, tags, body
# Update references
$ veta edit 71 --references "src/new_file.rs,https://docs.example.com"
Edited note 71: Updated references
Delete a note
$ veta rm 45
Deleted note 45
# Delete multiple notes at once
$ veta rm 1,2,3
Deleted note 1
Deleted note 2
Deleted note 3
Search notes
veta grep searches title and body
$ veta grep "test"
# case-sensitive
$ veta grep -C "Claude"
# regular expressions
$ veta grep "claude|openai"
# grep within one or more tags
$ veta grep "cloudflare" --tags deployment,testing
Worker deployment
Veta publishes a pre-built WASM worker to npm as veta. This can be deployed standalone or integrated into an existing multi-worker Cloudflare project.
Standalone worker
For a standalone Veta deployment:
# Create a new directory
mkdir my-veta && cd my-veta
# Initialize with npm
npm init -y
npm install veta wrangler
# Create wrangler.toml
cat > wrangler.toml << 'EOF'
name = "my-veta"
main = "node_modules/veta/build/worker/shim.mjs"
compatibility_date = "2025-01-01"
[[d1_databases]]
binding = "VETA_DB"
database_name = "my-veta-db"
EOF
# Deploy (D1 database is auto-provisioned, migrations run automatically)
npx wrangler deploy
Multi-worker architecture (service bindings)
For projects with multiple workers (like a monorepo with UI, API, and agent workers), Veta can be added as a service that other workers call via service bindings.
1. Add the Veta worker to your project:
Create src/veta/index.ts:
export { default } from 'veta/worker';
Create wrangler.veta.toml:
name = "my-project-veta"
main = "src/veta/index.ts"
compatibility_date = "2025-01-01"
[[d1_databases]]
binding = "VETA_DB"
database_name = "my-project-veta-db"
Migrations run automatically on first request - no manual setup needed.
2. Add service binding to consuming workers:
In the worker that needs to call Veta (e.g., an agent worker), add the service binding:
# wrangler.agent.toml
[[services]]
binding = "VETA"
service = "my-project-veta"
[[env.production.services]]
binding = "VETA"
service = "my-project-veta"
3. Update your dev script to run all workers:
{
"scripts": {
"dev": "wrangler dev -c wrangler.toml -c wrangler.veta.toml -c wrangler.agent.toml --port 8787"
}
}
4. Call Veta from your worker:
// In your agent worker
interface Env {
VETA: Fetcher;
}
// Add a note
const response = await env.VETA.fetch(
new Request('http://veta/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Meeting notes',
body: 'Discussed project timeline...',
tags: ['meetings', 'project-x'],
}),
})
);
const { id } = await response.json();
// Search notes
const searchResponse = await env.VETA.fetch(
new Request('http://veta/grep?q=timeline&tags=meetings')
);
const notes = await searchResponse.json();
HTTP API
The worker exposes a RESTful HTTP API:
| Method | Path | Description |
|---|---|---|
POST |
/notes |
Create a note. Body: {title, body, tags, references?} |
GET |
/notes |
List notes. Query: ?tags=a,b&limit=20 |
GET |
/notes/:id |
Get a single note |
PATCH |
/notes/:id |
Update a note. Body: {title?, body?, tags?, references?} |
DELETE |
/notes/:id |
Delete a note |
GET |
/tags |
List all tags with note counts |
GET |
/grep |
Search notes. Query: ?q=pattern&tags=a,b&case_sensitive=true |
Example: Agents SDK chat app
The examples/agents-sdk/ directory contains a complete example of an AI chat agent with persistent memory using Veta and Cloudflare's Agents SDK.
https://github.com/user-attachments/assets/97949dec-c726-4162-8b33-136ad7c2116c
To run it locally:
cd examples/agents-sdk
npm install
echo "OPENAI_API_KEY=your-key-here" > .dev.vars
npm run dev
Then open http://localhost:8787. The agent remembers information across conversations - try telling it your name, starting a new chat, and asking "what's my name?"
See examples/agents-sdk/README.md for more details.
Architecture
Veta is written in Rust and compiles to both native (CLI) and WASM (Worker) targets. The architecture maximizes code sharing between the two.
veta/
├── crates/
│ ├── veta-core/ # Shared: types, validation, business logic
│ ├── veta-files/ # Native: file-based storage with symlinks
│ ├── veta-d1/ # WASM: D1 Database implementation
│ ├── veta/ # Native: CLI binary
│ └── veta-worker/ # WASM: Cloudflare Worker
└── schema/
└── migrations/ # SQL migrations for D1
Crates
veta-core — Pure business logic with no I/O dependencies. Defines:
- Data types (
Note,Tag,NoteQuery, etc.) - The
Databasetrait (async,?Sendfor WASM compatibility) VetaService<D: Database>containing all business logic
veta-files — Implements Database trait using local files. Notes are stored as JSON files in .veta/notes/, with tags organized via symlinks in .veta/tags/. Uses file locking for safe concurrent access.
veta-d1 — Implements Database trait using Cloudflare's D1 via workers-rs. Only compiled for wasm32-unknown-unknown.
veta — The veta command-line tool. Uses veta-core + veta-files.
veta-worker — The Cloudflare Worker entry point. Uses veta-core + veta-d1. Exposes the HTTP API via workers-rs Router.
Build targets
| Crate | Native | WASM |
|---|---|---|
veta-core |
✓ | ✓ |
veta-files |
✓ | ✗ |
veta-d1 |
✗ | ✓ |
veta |
✓ | ✗ |
veta-worker |
✗ | ✓ |
Dependencies
~39MB
~698K SLoC