Skip to content

Configuration

passageway.toml

Default location ~/.config/passageway/passageway.toml, or the path in PASSAGEWAY_CONFIG. One [[connector]] entry is one source with its own index file, so two servers of the same kind are two entries with the same command. Secrets are references to environment variables, never values. In Claude Code, /passageway-add-source from the plugin writes the entry for you.

[embedder]
min_similarity = 0.845          # optional: the relevance gate for the model in use

[[connector]]
name = "notes"                  # starts with a-z or 0-9, then a-z, 0-9, _ or -; it names the index file
command = ["/path/to/python", "-m", "passageway.testing.fake_connector"]
env = { FAKE_ITEMS = "/path/to/items.json" }
secrets = { TOKEN = "env:NOTES_TOKEN" }
timeout = 300                   # seconds per call
max_line_bytes = 4194304        # longest line a connector may print
max_output_bytes = 67108864     # most output one call may produce
max_delete_fraction = 0.3       # a reconcile that would delete more than this share is refused

A connector runs with an empty environment (only PATH and HOME, plus its env table and named secrets), so python on PATH is usually not the interpreter that has passageway installed. For the example above use the tool's own interpreter, $(uv tool dir)/passageway/bin/python.

See the connector protocol for what a connector prints and the Wikipedia connector for a bundled one.

The embedder

The embedder turns text into vectors. passageway has one built-in embedder (kind = "onnx") that runs a model on your CPU, with three model presets to choose from; any other embedder is a program that speaks the embedder protocol (kind = "command").

Without an [embedder] section passageway uses the built-in embedder with its default model, the multilingual e5-small, and fetches it once (pinned to a revision, every file verified by checksum) when it is missing. model picks another model preset:

[embedder]
model = "jina-v2-base-de"       # see `passageway model list`
device = "auto"                 # auto | cpu | coreml
download = true                 # fetch the pinned files when missing (false for an air-gapped machine)

To use another model, point [embedder] at a program that speaks the embedder protocol; the bundled OpenAI-compatible embedder is one, for Ollama, LM Studio, vLLM, OpenAI and Voyage:

[embedder]
kind = "command"                # "onnx" (the default) or "command"
command = ["/path/to/python", "/path/to/my_embedder.py"]
env = { MODEL = "bge-m3" }
secrets = { API_KEY = "env:EMBED_KEY" }
min_similarity = 0.80           # the relevance gate for this model
batch_size = 32                 # texts per request
timeout = 60                    # seconds per request
Key Kinds Meaning
kind both onnx is the built-in embedder, which runs a model preset; command runs your program as one long-lived process
min_similarity both The relevance gate, from 0 to 1. Similarity scales differ between models, so measure it with passageway eval --sweep; a command embedder without it gets the default model preset's value and a warning
model onnx A model preset of the built-in embedder: multilingual-e5-small (the default), multilingual-e5-base, jina-v2-base-de; passageway model list shows them with their measured relevance gate
device onnx auto (CPU) or cpu; coreml is opt-in on a Mac, measured four times slower on these int8 models and giving different vectors, so only for your own experiments
download onnx true fetches the preset's files from huggingface.co (or the mirror in PASSAGEWAY_MODEL_HUB) when they are missing or fail their checksum, once, before the server answers; false makes a missing model an error, with passageway model download <preset> as the step to run
model_dir onnx Where the preset's files are. Default: PASSAGEWAY_MODEL_DIR for the default preset (itself <data dir>/models/e5-small), else <data dir>/models/<preset>; passageway model uses the same rule. Set, it wins over PASSAGEWAY_MODEL_DIR and --model-dir
command, env, secrets command The program, its settings and named secrets (references to environment variables), run with an empty environment like a connector
batch_size, timeout command At most this many texts per request (1 to 1024); seconds per request, after which the process is killed. The first request, which loads the model, is under the same limit

A typo in a key is an error. Changing the embedder rebuilds every index at the next start, because vectors of different models cannot be compared.

To compare models, add [embedders.NAME] tables with the same keys. The server ignores them; passageway eval compare runs your questions with each:

[embedders.e5]
kind = "onnx"

[embedders.bge]
kind = "command"
command = ["/path/to/python", "/path/to/my_embedder.py"]
min_similarity = 0.80

Environment

Variable Default Meaning
PASSAGEWAY_LOGIN_PASSWORD required Static Bearer token, and the OAuth login page's password
PASSAGEWAY_LOGIN_PASSWORD_FILE unset A file holding the password, read when the variable is unset; passageway init writes one (see Run it)
PASSAGEWAY_INSTANCE unset Names one of several instances on this machine; it moves the default config and data folders to passageway-<name>. For a service; on the command line --instance NAME says the same and wins
PASSAGEWAY_CONFIG ~/.config/passageway/passageway.toml Connector file
PASSAGEWAY_BASE_URL unset Public https URL; set, it turns on OAuth for /mcp
PASSAGEWAY_DATA_DIR ~/Library/Application Support/passageway on macOS, ~/.local/share/passageway elsewhere Index files and OAuth state
PASSAGEWAY_MODEL_DIR <data dir>/models/e5-small The embedding model
PASSAGEWAY_HOST, PASSAGEWAY_PORT 127.0.0.1, 8082 Where to listen
PASSAGEWAY_POLL_SECONDS, PASSAGEWAY_RECONCILE_SECONDS 60, 1800 Change polling and full reconcile
PASSAGEWAY_TOOL_DESCRIPTION, PASSAGEWAY_INSTRUCTIONS neutral text What MCP clients read about the recall tool
PASSAGEWAY_LOG_LEVEL INFO
APP_VERSION the installed version Version string shown by /health and status; set it only to override the package's version

Several instances

Several Claude setups side by side (yours, your work's, a customer's) can each have their own passageway, with their own sources, index and password. Give each instance a name and a port of its own:

passageway --instance work init
PASSAGEWAY_PORT=8083 PASSAGEWAY_LOGIN_PASSWORD_FILE=~/.config/passageway-work/password passageway --instance work
passageway --instance work eval cases.toml

The instance reads ~/.config/passageway-work/passageway.toml and keeps its index in the data folder with -work appended (~/Library/Application Support/passageway-work on macOS). The model presets stay in the shared data folder, so they are downloaded once. /health names the instance. Every command takes --instance NAME, anywhere on the line; a service sets PASSAGEWAY_INSTANCE instead, and the argument wins when both are there. An instance must exist: --instance wrok is an error that lists the instances there are, and only init creates one. Without an instance nothing changes, unless the machine has only named instances: then a bare passageway or passageway eval asks you to pick one rather than run against an empty default. In Claude Code, /passageway-install asks for the name, picks a free port and runs each instance as its own service.

Each Claude setup connects to the instances you choose, one or several: their hook files and MCP registrations live in its own CLAUDE_CONFIG_DIR, so /passageway-connect run in that setup touches no other. The hook asks every connected instance at once and joins their passages; the recall tool of instance work is the MCP server passageway-work. This keeps sources, indexes and connections apart; it is not a security boundary. Under one operating-system user, a Claude with a shell can read another instance's password file and call its port, so which Claude connects to which instance is up to you. For a boundary that holds, run each instance as its own user or in its own container.

Certificates

Every HTTPS request passageway makes, from the core's model download and from the bundled connectors and embedder, checks the server's certificate against your operating system's trust store: the Keychain on macOS, the certificate store on Windows, /etc/ssl on Linux. A server whose certificate comes from a company or internal CA works as soon as that CA is trusted there, the way Safari and curl see it; no exported bundle in SSL_CERT_FILE is needed. Only the Confluence connector can turn checking off, with VERIFY_TLS = false.

Rebuilding a source

Each source is one file, <data dir>/<name>.db, and it can always be rebuilt from the connector: stop the server, delete the file, start it again. Do this after changing the embedding model, after removing a connector from the config (its items stay searchable until then), and when the deletion guard keeps refusing a legitimate bulk delete (it refuses to remove more than max_delete_fraction of a source, and always allows a single item; it also refuses to empty a source of two or more items).

Health

GET /health is open so a health check can use it. It reports each source's item count and whether it is failing; the error text itself is shown only when the request carries the Bearer password. The same report, errors included, is the MCP tool status, so a connected client asks the server itself instead of curl; a server started with a broken configuration answers {"status": "error", "error": ...} from both.