Agent patterns
When to reach for walkindb, and concrete recipes for the workloads it's good at.
When to use walkindb
- Agent scratchpad memory. A planning agent needs to remember the tool calls it already made this run, the partial results, the decisions, the open questions. SQL tables with a few joins are a better data structure for this than rolling context windows.
- Tool-use state across a multi-step task. The agent browsed three URLs, called a shell tool twice, and got partial results from each. Store them in
tool_calls,tool_results, andartifactstables. Query them as context for the next step. - RAG chunk staging. You retrieved 40 chunks from your vector store for a single user question. Load them into
chunks(id, source, score, content), run SQL to dedupe / re-rank / filter by source, then return the top-10 to the model. - One-shot analyses. The user dropped a CSV into the agent context. Load it into a walk-in, run
GROUP BY/WINDOWqueries, return a natural-language summary. - Throwaway notebooks, teaching, demos. You want to show a SQL technique to a student. They don't have SQLite installed.
curl https://api.walkindb.com/sql ...works from any shell.
When NOT to use walkindb
- Anything durable. walk-ins are deleted after ~10 minutes. There is no paid tier.
- PII or regulated data. See the AUP.
- Anything you'd put in a transaction log. No WAL replication, no backups, no point-in-time recovery, no warnings before deletion.
- Very large datasets. 10 MB per walk-in. If you need 100 MB, reach for Turso, Neon, Postgres, or self-host walkindb with the cap raised.
- Cross-session shared state between different users. Every walk-in is strictly isolated; there's no concept of "the users table shared across all sessions". Use a real database.
Recipe: agent scratchpad memory
A planning agent that wants structured memory across tool calls in a single run. Use walkindb from the agent's Python tool runtime.
from walkindb import Client db = Client() # Schema for the agent's working memory. db.execute(""" CREATE TABLE plans( id INTEGER PRIMARY KEY AUTOINCREMENT, goal TEXT NOT NULL, created_at INTEGER DEFAULT (unixepoch()), status TEXT DEFAULT 'active' ); CREATE TABLE steps( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER REFERENCES plans(id), kind TEXT, -- 'thought' | 'tool_call' | 'observation' content TEXT, created_at INTEGER DEFAULT (unixepoch()) ); CREATE INDEX idx_steps_plan ON steps(plan_id, created_at); """) # The agent records each step as it goes. def remember(plan_id: int, kind: str, content: str): db.execute(f"INSERT INTO steps(plan_id, kind, content) VALUES({plan_id}, '{kind}', '{content.replace(chr(39), chr(39)*2)}')") # Later, the agent asks itself: "what have I done so far on this plan?" result = db.execute(""" SELECT kind, content FROM steps WHERE plan_id = 1 ORDER BY created_at """) for kind, content in result.rows: print(f"[{kind}] {content}")
Recipe: RAG chunk staging and re-ranking
You pulled 40 candidate chunks from your vector store. Load them into a walk-in, run SQL to filter/dedupe/rerank, return the top 10.
from walkindb import Client import json db = Client() db.execute(""" CREATE TABLE chunks( id INTEGER PRIMARY KEY, source TEXT, score REAL, content TEXT ); CREATE INDEX idx_chunks_src ON chunks(source); CREATE INDEX idx_chunks_score ON chunks(score DESC); """) # Bulk-insert 40 chunks in one statement (under the 8 KB body cap typically # fits ~20-30 short chunks per call; split if you have more). for c in candidate_chunks: body = c['content'].replace("'", "''") db.execute(f"INSERT INTO chunks(id, source, score, content) VALUES({c['id']}, '{c['source']}', {c['score']}, '{body}')") # Dedupe and rerank: prefer high-score chunks, cap per-source to 3 chunks, # return top 10 overall. result = db.execute(""" WITH ranked AS ( SELECT id, source, score, content, ROW_NUMBER() OVER (PARTITION BY source ORDER BY score DESC) AS rn FROM chunks ) SELECT id, source, score, content FROM ranked WHERE rn <= 3 ORDER BY score DESC LIMIT 10 """) # Feed these back to the model as context. top_chunks = [ {"source": r[1], "score": r[2], "content": r[3]} for r in result.rows ]
This is a classic case where SQL is the right tool: window functions + filtering are much cleaner in 8 lines of SQL than in 40 lines of Python.
Recipe: CSV analysis one-shot
from walkindb import Client import csv, io db = Client() db.execute("CREATE TABLE t(date TEXT, category TEXT, amount REAL)") # Load rows. For a 100-row CSV this is fine; for larger data, split into # batches that fit under the 8 KB request body cap. rows = list(csv.reader(io.StringIO(user_csv)))[1:] # skip header for date, category, amount in rows: db.execute(f"INSERT INTO t VALUES('{date}', '{category}', {amount})") # Analyses monthly = db.execute(""" SELECT strftime('%Y-%m', date) AS month, category, SUM(amount) AS total FROM t GROUP BY month, category ORDER BY month DESC, total DESC """) biggest = db.execute(""" SELECT category, SUM(amount) AS total FROM t GROUP BY category ORDER BY total DESC LIMIT 3 """) # Feed monthly.rows and biggest.rows back to the model for summary.
Recipe: MCP-style tool for an agent runtime
If you're writing an agent runtime (LangChain, Semantic Kernel, your own loop, etc.), here's the minimal wrapper you'd expose as a tool:
from walkindb import Client, WalkinDBError # Create one Client per agent run (not per tool call). _db = Client() def sql_tool(sql: str) -> dict: """Run SQL against a private, ephemeral SQLite database. The database is automatically created on the first call and deleted after ~10 minutes. It is isolated from every other agent's database. Use for: planning memory, RAG chunk staging, CSV analysis, scratch tables. Do NOT use for: PII, long-term storage, anything you can't afford to lose. """ try: r = _db.execute(sql) return { "columns": r.columns, "rows": r.rows, "rows_affected": r.rows_affected, "truncated": r.truncated, } except WalkinDBError as e: return {"error": e.error, "status": e.status}
Register this function as a tool in your agent framework. The docstring is the prompt the model sees — keep it informative about what's good and what's not.
Thinking about quotas
walkindb has generous-but-real limits. Two patterns to avoid:
Don't spin up a new walk-in per tool call
The Client instance reuses the session across calls. Make one Client() per agent run, not per tool call. Otherwise you'll burn through the 10 new-instance-creations/min/IP budget and start seeing 429s.
Don't put 50 separate INSERTs in a loop when you can use one multi-row INSERT
# Less good: 50 calls, 50 round trips for row in rows: db.execute(f"INSERT INTO t VALUES({row})") # Better: one call, one round trip, as long as the SQL fits in 8 KB values = ", ".join(f"({row})" for row in rows) db.execute(f"INSERT INTO t VALUES {values}")