2 unstable releases

new 0.6.1 Feb 10, 2026
0.4.0 Jan 30, 2026

#1274 in Database interfaces

MIT license

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

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 Database trait (async, ?Send for 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