Skip to content

Repository files navigation


SurrealDB filesystem


A filesystem-based memory layer for agents, with hybrid search, backed by SurrealDB.


SurrealDB   Python   Pydantic   NousResearch (Hermes)   OpenAI   Anthropic   Claude


Beta     X (formerly Twitter) Follow    

Warning

SurrealFS is in beta. The API, schema, and CLI may change in breaking ways between releases, and it is not yet recommended for production use. Bug reports and feedback are very welcome.

Give an agent somewhere durable to keep its work. SurrealFS is a file table plus the tools to hand it to a model: files and folders, full-text and semantic search, all queryable with SurrealQL because it is just a table.

The file browser

The file browser is a standalone tool that can be used to browse and edit files in a SurrealDB database.

Requirements

  • Python 3.12+
  • A SurrealDB 3.x server.

Install

pip install surrealfs                    # core, and the Hermes plugin
pip install "surrealfs[pydantic-ai]"     # + the pydantic-ai toolset
pip install "surrealfs[mcp]"             # + the MCP server
pip install "surrealfs[browser]"         # + the file browser

Quickstart

Start a database (just db), then:

from surrealdb import AsyncSurreal
from surrealfs import SurrealFs, apply_schema

db = AsyncSurreal("ws://localhost:8000/rpc")
await db.signin({"username": "root", "password": "root"})
await db.use("surrealfs", "demo")
await apply_schema(db)  # defines the `file` table; safe to re-run

fs = SurrealFs(db, user="alice")  # who you are; ROOT bypasses permissions
await fs.write_text("/notes/today.md", "# Today\n- ship the refactor")
await fs.edit("/notes/today.md", "ship", "shipped")
await fs.ls("/notes")
await fs.search_text("refactor")

You create and own the connection. SurrealFS never connects, signs in, or selects a namespace.

user is required and has no default. Files carry a unix owner and mode, and the database cannot tell SurrealFS who is asking (every credential is a system credential), so the identity comes from you. See Permissions.

The plain async API

SurrealFs returns structured FileEntry and SearchHit objects, not pre-formatted strings. Build whatever integration you like on top.

Read read_text read_bytes tail ls glob stat exists
Write write_text write_bytes edit touch mkdir
Organise mv cp rm chmod
Search search search_text search_semantic reindex_embeddings

Integrations

Six ways to hand it to an agent, each with its own README:

pydantic-ai a FunctionToolset, with recoverable mistakes raised as ModelRetry surrealfs[pydantic-ai]
Raw JSON tool schemas plain dicts in Anthropic or OpenAI shape, no framework core
Hermes the 14 surrealfs_* tools plus a bundled notes skill core
Hermes memory files every completed turn, and recalls context before each one core
MCP the 16 tools over stdio, for Claude, Cursor, Zed, Codex or your own surrealfs[mcp]
Claude that server as a plugin, bundled with the /brain skill surrealfs[mcp,agent-memory]

The four tool surfaces are generated from one registry in surrealfs/tools/, so they cannot drift apart. Tool descriptions are markdown in surrealfs/tools/docs/. Edit them as prose; they are prompt text. The memory provider is the odd one out: it exposes no tools of its own, because the Hermes plugin's already cover explicit reads and writes.

The file browser

Point the agents at a shared SurrealDB (a Cloud instance, say) and every person on the team can run the browser on their own machine to see and edit the same filesystem the agents are writing to:

pip install "surrealfs[browser]"
surrealfs-browser                # http://127.0.0.1:7933

Tree on the left, file on the right, chat with the note-taking agent on the far right. Text is editable and saves back to the table, markdown renders with a source toggle, agent-authored HTML renders in a sandboxed iframe, images display, and the search box is the same hybrid fs.search the agent's own tool calls. Conversations are files too, under /_sessions/: open one from the tree and it replays.

Credentials come from a .env in the working directory, or the environment directly, which wins over the file:

SURREALDB_URL=wss://….surreal.cloud/rpc
SURREALDB_USER=…
SURREALDB_PASS=…
SURREALDB_NAMESPACE=…
SURREALDB_DATABASE=…
SURREALDB_AUTH_LEVEL=database    # root (default) | namespace | database
OPENAI_API_KEY=…                 # optional: makes search hybrid
ANTHROPIC_API_KEY=…              # optional: enables the chat panel

SURREALDB_AUTH_LEVEL picks which kind of user the credentials are, since the server infers that from the signin payload: a database-scoped Cloud credential needs database. All three are system users, which is why SurrealFS enforces permissions itself rather than leaning on the database; SURREALFS_USER (or --user) picks who the browser acts as, defaulting to root, which sees every private home. Every variable has a command-line form too (surrealfs-browser --help).

The server binds loopback. --host 0.0.0.0 exposes it, and then anyone who can reach the port has whatever access those credentials do. The page has no login of its own.

The page is a React app built on the SurrealDB UI Kit. Its source is in surrealfs/browser/ui/, and the build output it serves is gitignored, so working on this repo needs bun:

just ui        # build the page into surrealfs/browser/static
just ui-dev    # vite dev server, proxying the API to `just browser`

just browser builds it for you. Nothing else in the project needs bun -- just check stays Python-only.

Semantic search

fs.search runs a full-text arm and a vector arm and fuses them by rank. SurrealFS never calls an embedding model: you pass the embedder, so you pick the provider, and without one search stays full-text only. An OpenAI embedder and a daemon that keeps the vectors current ship in surrealfs.embed:

just embed              # poll every 5s; --once for a single pass

The queries are custom SurrealQL functions in the schema, not Python, so any client gets the same ranked, permission-filtered retrieval:

SELECT path, rrf_score FROM fn::sfs_hybrid_search("how do I get paid", $qvec, 5, NONE, 'alice');

fn::sfs_search_text and fn::sfs_search_semantic are the single arms. The last argument is who is asking: a system credential passes it, a record credential overrides it. SurrealFs calls these too, so there is only one definition.

See Semantic search for the ranking, match="all", the SurrealQL surface, and wiring it into the agent toolset.

Permissions

Files carry a unix owner and mode, /home/<user> is private, everything else is shared, and chmod moves things between the two. SurrealFs enforces it. See Permissions for the rules and the reasoning.

Letting the database enforce it too (optional)

The above is a real boundary for an agent, since no tool exposes raw SurrealQL, but not one against anyone holding the database credential. If several agents share a database, opt in:

python -m surrealfs.schema --record-auth   # adds the `user` table + record access
python -m surrealfs.users add alice        # creates alice and /home/alice (0700)
SURREALDB_AUTH_LEVEL=record SURREALDB_USER=alice SURREALDB_PASS=…

Now SurrealDB itself refuses to return or modify anything in another user's home, whatever query it is asked. Nothing else changes: the tools, the modes and chmod behave the same, and SurrealFs picks its identity up from the credential instead of the environment. Deployments that do not opt in are unaffected: the permissions clause is inert for system credentials.

The schema

surrealfs/schema/file.surql is the whole data model, and it is worth reading. The key idea: a file's parent is a record link and path is a computed field derived from the parent chain. Nothing stores a path, so renaming or moving a folder is a single UPDATE and every descendant's path follows for free.

DEFINE FIELD path ON file COMPUTED (
    ($this.parent.path || '') + '/' + <string>$this.filename
) TYPE string;

A folder is just a row with no content, no file bytes, and no symlink, also computed, as is_folder. hash is maintained by an event, and there are HNSW and BM25 indexes for the two search modes.

owner, mode and the computed gate are the permission model. See Permissions below and docs/permissions.md.

You can apply it without writing any code:

python -m surrealfs.schema                     # uses SURREALDB_* env vars
python -m surrealfs.schema --print             # dump the DDL, connect to nothing

Development

just db        # start SurrealDB in one terminal
just schema    # apply the file table to it
just test      # run the suite (starts its own server if needed)
just check     # lint + test
just browser   # the file browser on :7933
just agent     # the note-taking chat agent on :7932
just loop ""  # the framework-free Anthropic tool-use loop
just mcp       # the MCP server over stdio, as Claude Desktop launches it

Tests start a throwaway surreal server; set SURREALFS_TEST_URL to reuse one you already have.

About

Virtual file system for AI agents built on SurrealDB

Resources

Stars

24 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages