Give agents the context they need to act. Run agents, workflows and your own code beside the data with Particles. Memory, state, coordination and analytics in one durable engine.
$curl -fsSL https://get.particledb.ai | bash
ParticleDB 1.1.19-rc.17 — release candidate; not yet GA or for production data · Release status
An agent’s decisions depend on the data it receives. When context is scattered across systems, every task inherits the work of finding it, joining it and keeping it current.
ParticleDB brings relational facts, vector memory and live operational data into one engine. Prepare context with SQL. Apply your logic with Particles. Give the agent the information it needs for the next step.
db.execute("CREATE TABLE accounts (id BIGINT PRIMARY KEY, balance BIGINT)")db.execute("INSERT INTO accounts VALUES (1, 100), (2, 0)")with db.transaction() as tx: # commits on exit, rolls back on error tx.execute("UPDATE accounts SET balance = balance - $1 WHERE id = $2", [30, 1]) tx.execute("UPDATE accounts SET balance = balance + $1 WHERE id = $2", [30, 2])print(db.query("SELECT id, balance FROM accounts ORDER BY id"))
await db.execute('CREATE TABLE accounts (id BIGINT PRIMARY KEY, balance BIGINT)');await db.execute('INSERT INTO accounts VALUES (1, 100), (2, 0)');await db.transaction(async tx => { // commits on return, rolls back on throw await tx.execute('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [30, 1]); await tx.execute('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [30, 2]);});console.log((await db.query('SELECT id, balance FROM accounts ORDER BY id')).rows);
Uses db from the connect example. Inside main, with import com.particledb.ParticleDB.
db.execute("CREATE TABLE accounts (id BIGINT PRIMARY KEY, balance BIGINT)");db.execute("INSERT INTO accounts VALUES (1, 100), (2, 0)");db.transaction(tx -> { // returns normally: commit; throws: rollback tx.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", 30L, 1L); tx.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", 30L, 2L); return null;});System.out.println(db.query("SELECT id, balance FROM accounts ORDER BY id").getRows());
Output
[Row{id=1, balance=70}, Row{id=2, balance=30}]
Uses db from the connect example. Inside func main. Errors are ignored here for brevity; check them in real code.
db.Exec(ctx, "CREATE TABLE accounts (id BIGINT PRIMARY KEY, balance BIGINT)")db.Exec(ctx, "INSERT INTO accounts VALUES (1, 100), (2, 0)")err := particledb.BeginFunc(ctx, db, particledb.TxOptions{}, func(tx *particledb.Tx) error { if _, err := tx.Exec(ctx, "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 30, 1); err != nil { return err // an error rolls back } _, err := tx.Exec(ctx, "UPDATE accounts SET balance = balance + $1 WHERE id = $2", 30, 2) return err // nil commits})rows, _ := db.Query(ctx, "SELECT id, balance FROM accounts ORDER BY id")balances, _ := particledb.CollectRows(rows, particledb.RowToMap)fmt.Println(balances, err)
Output
[map[balance:70 id:1] map[balance:30 id:2]] <nil>
Uses db from the connect example. Inside fn main() -> particledb_sdk::Result<()>, with use particledb_sdk::{Config, ParticleDB, Value}.
db.execute("CREATE TABLE accounts (id BIGINT PRIMARY KEY, balance BIGINT)", &[])?;db.execute("INSERT INTO accounts VALUES (1, 100), (2, 0)", &[])?;db.transaction(|tx| { // Ok commits, Err rolls back tx.execute("UPDATE accounts SET balance = balance - 30 WHERE id = 1", &[])?; tx.execute("UPDATE accounts SET balance = balance + 30 WHERE id = 2", &[])?; Ok(())})?;for row in db.query("SELECT id, balance FROM accounts ORDER BY id", &[])?.rows { println!("{:?}", row.values());}
Output
[Int64(1), Int64(70)]
[Int64(2), Int64(30)]
CREATE TABLE accounts (id BIGINT PRIMARY KEY, balance BIGINT);INSERT INTO accounts VALUES (1, 100), (2, 0);BEGIN;UPDATE accounts SET balance = balance - 30 WHERE id = 1;UPDATE accounts SET balance = balance + 30 WHERE id = 2;COMMIT;SELECT id, balance FROM accounts ORDER BY id;
Output
1|70
2|30
Key-value
Use Redis-compatible key-value structures for queues, locks, counters and rate limits, backed by the same durable engine.
const hits = await db.kv.incr('rate:agent:1'); // 1, 2, 3 ... per callawait db.query('SELECT KV_EXPIRE($1, 60)', ['rate:agent:1']); // the window resets after 60 sconsole.log(hits);
Output
1
Uses db from the connect example. Inside main, with import com.particledb.ParticleDB.
long hits = db.kv().incr("rate:agent:1", 1); // 1, 2, 3 ... per calldb.kv().expire("rate:agent:1", 60); // the window resets after 60 sSystem.out.println(hits);
Output
1
Uses db from the connect example. Inside func main. Errors are ignored here for brevity; check them in real code.
hits, _ := db.KVIncr(ctx, []byte("rate:agent:1")) // 1, 2, 3 ... per calldb.Exec(ctx, "SELECT KV_EXPIRE($1, 60)", "rate:agent:1") // the window resets after 60 sfmt.Println(hits)
Output
1
Uses db from the connect example. Inside fn main() -> particledb_sdk::Result<()>, with use particledb_sdk::{Config, ParticleDB, Value}.
let key = Value::String("rate:agent:1".into());let hits = db.query_one("SELECT KV_INCR($1)", &[key.clone()])?.unwrap(); // 1, 2, 3 ... per calldb.query("SELECT KV_EXPIRE($1, 60)", &[key])?; // the window resets after 60 sprintln!("{}", hits.get::<i64>(0)?);
Output
1
SELECT KV_INCR('rate:agent:1'); -- 1, 2, 3 ... per callSELECT KV_EXPIRE('rate:agent:1', 60); -- the window resets after 60 s
Output
1
1
Vector search
Query vectors and relational facts together, with embeddings stored beside the data they describe.
db.execute("CREATE TABLE runs (agent TEXT, ms BIGINT)")db.execute("INSERT INTO runs VALUES ('planner', 120), ('planner', 90), ('critic', 200)")print(db.query("SELECT agent, COUNT(*) AS runs, SUM(ms)::BIGINT AS total_ms " "FROM runs GROUP BY agent ORDER BY agent"))
await db.execute("CREATE TABLE runs (agent TEXT, ms BIGINT)");await db.execute("INSERT INTO runs VALUES ('planner', 120), ('planner', 90), ('critic', 200)");console.log((await db.query(`SELECT agent, COUNT(*) AS runs, SUM(ms)::BIGINT AS total_ms FROM runs GROUP BY agent ORDER BY agent`)).rows);
Uses db from the connect example. Inside main, with import com.particledb.ParticleDB.
db.execute("CREATE TABLE runs (agent TEXT, ms BIGINT)");db.execute("INSERT INTO runs VALUES ('planner', 120), ('planner', 90), ('critic', 200)");System.out.println(db.query("SELECT agent, COUNT(*) AS runs, SUM(ms)::BIGINT AS total_ms " + "FROM runs GROUP BY agent ORDER BY agent").getRows());
Uses db from the connect example. Inside func main. Errors are ignored here for brevity; check them in real code.
db.Exec(ctx, "CREATE TABLE runs (agent TEXT, ms BIGINT)")db.Exec(ctx, "INSERT INTO runs VALUES ('planner', 120), ('planner', 90), ('critic', 200)")rows, _ := db.Query(ctx, `SELECT agent, COUNT(*) AS runs, SUM(ms)::BIGINT AS total_ms FROM runs GROUP BY agent ORDER BY agent`)report, _ := particledb.CollectRows(rows, particledb.RowToMap)fmt.Println(report)
Uses db from the connect example. Inside fn main() -> particledb_sdk::Result<()>, with use particledb_sdk::{Config, ParticleDB, Value}.
db.execute("CREATE TABLE runs (agent TEXT, ms BIGINT)", &[])?;db.execute("INSERT INTO runs VALUES ('planner', 120), ('planner', 90), ('critic', 200)", &[])?;for row in db.query("SELECT agent, COUNT(*) AS runs, SUM(ms)::BIGINT AS total_ms \ FROM runs GROUP BY agent ORDER BY agent", &[])?.rows { println!("{:?}", row.values());}
CREATE TABLE runs (agent TEXT, ms BIGINT);INSERT INTO runs VALUES ('planner', 120), ('planner', 90), ('critic', 200);SELECT agent, COUNT(*) AS runs, SUM(ms)::BIGINT AS total_msFROM runs GROUP BY agent ORDER BY agent;
Output
critic|1|200
planner|2|210
Particles
Bring your code beside the data with Particles: scoring, transforms and tools callable from SQL.
Add the flag to the quickstart start command. Included in the free edition. The server runs the module with the python3 on its PATH, which needs Python 3.11 or newer and pip install particledb; checked on the standard 1.1.19-rc.17 download. Requirements
A relational database for state. A key-value store for coordination. A vector database for memory. A warehouse for analytics. Plus sync jobs and dual writes.
One shared foundation.
Serve all four workloads from one durable engine, with Particles running your code beside the data.
built for your stack
Use the tools you already know.
PostgreSQL-compatible SQL for psql, JDBC and your ORM. Redis-compatible key-value access. Check the published clients and release versions for your application.