# OreCloud docs (/docs) OreCloud is a managed memory store for agentic apps. Each end user of your app gets their own store — their own instance of [LodeDB](https://github.com/Egoist-Machines/LodeDB), the open-source embedded vector database — created automatically on its first write. Embedding runs server-side, so there is no vector infrastructure to run. ```python import orecloud client = orecloud.Client() memory = client.store("user-42") memory.add("likes hiking near Seattle") context = memory.context_block("plan my weekend") ``` ## For LLMs [#for-llms] These docs are also served in machine-friendly renderings: [`/llms.txt`](/llms.txt) (index), [`/llms-full.txt`](/llms-full.txt) (everything as one markdown document), and any page as raw markdown by appending `.md` to its URL. # Keys & environments (/docs/keys) ## Tenancy lives in the credential [#tenancy-lives-in-the-credential] API keys are minted bound to one **environment** — every org gets the fixed `production`/`testing` pair. `orecloud.Client()` reads the key and binds itself to that environment — the same code serves testing and production by swapping the key. Nothing about tenancy is hardcoded in application code: ```python client = orecloud.Client() # org + environment come from ORECLOUD_TOKEN ``` ## Key kinds [#key-kinds] * **Secret keys** (`ore_sk_…`) — server-side use. Scopes chosen at mint. * **Publishable keys** (`ore_pk_…`) — safe for lower-trust surfaces: can rank results but can never read stored text. * **Personal tokens** — your own login (via `orecloud login`), unscoped to an environment; pass `org=`/`environment=` explicitly. Mint and revoke via the console, or: ```bash orecloud tokens mint --kind secret --scope write --scope read:search orecloud tokens list orecloud tokens revoke ``` The secret is shown once at mint. ## Text access is double-gated [#text-access-is-double-gated] Reading stored text back (`get`, `context_block`, `include_text=True`) requires **both**: 1. the key to carry the `read:text` scope, **and** 2. the store to have its `expose_text` flag on (off by default). A key alone is never enough — the store owner's flag has to agree. This is enforced server-side, not a client convention. Telemetry and audit logs are metrics-only: counts, ids, latency — never document text, queries, or embeddings. # Hosted MCP (/docs/mcp) Every managed store is also a remote MCP server. An agent connects with one command — no application code: ```bash claude mcp add --transport http orecloud \ "https:///mcp///" \ --header "Authorization: Bearer " ``` `orecloud mcp install` prints ready-to-paste configs for Claude Code, Cursor, and VS Code; the console's Connect panel shows them prefilled. ## Scopes decide the toolset [#scopes-decide-the-toolset] The key's scopes decide which tools the agent sees: | Key scope | Tools | | --------------------------------------- | ------------------------------------------ | | `read:search` | `lodedb_search`, `lodedb_stats` | | + `read:text` (on an expose-text store) | + `lodedb_get`, inline text in search hits | | + `write` (on a cloud-writer store) | + `lodedb_add`, `lodedb_remove` | With a write-scoped key, an agent can bootstrap and maintain its own memory through MCP alone. Append `?read_only=true` to the endpoint URL to force a read-only toolset regardless of the key. ## Same tools locally [#same-tools-locally] Tool names and schemas are contract-tested equal to the local `lodedb mcp` stdio server, so an agent moving between a local LodeDB directory and its hosted copy sees the same tools. # Working with memory (/docs/memory) ## Writing [#writing] ```python memory = client.store("user-42") memory.add("likes hiking near Seattle") memory.add("vegetarian", metadata={"topic": "diet"}) memory.add_many([ {"text": "prefers morning meetings"}, {"text": "timezone is US/Pacific", "id": "tz"}, ]) ``` `add` returns when the write is **accepted**: durable and ordered, embedded server-side. Visibility follows within seconds. Writes are concurrent — any number of agents can write to the same store with no writer contention. To block until a write is queryable, use `wait_for`: ```python memory.add("draft saved") memory.wait_for(memory.last_write_id) ``` ### TTLs — memory that expires [#ttls--memory-that-expires] ```python memory.add("cart contains 3 items", ttl_seconds=3600) ``` An expired memory is hidden from reads, not destroyed (hide-not-delete). ### Provenance — which agent, which session [#provenance--which-agent-which-session] ```python memory.add("user asked about pricing", agent_id="sales-bot", run_id="run-8842") ``` Reads and deletes can narrow to one agent's or one session's memories via the same parameters. ## Reading [#reading] Three read shapes, in increasing order of "do the work for me": ```python hits = memory.search("dietary restrictions", k=5) # top-k, engine-scored hits = memory.recall("what should I cook for them?") # raw message in, fused ranking out block = memory.context_block("what should I cook?") # prompt-ready text block ``` * `search` takes a query string. `filter=` narrows on metadata. * `recall` takes the **raw user message** — the server derives sub-queries and fuses the rankings. Each hit's `matched` attribute names the sub-queries that surfaced it. * `context_block` returns one prompt-ready string of the most recent plus most relevant memories, capped by `max_chars`. Paste it into your system prompt. Requires text access (see [Keys & environments](/docs/keys)). To read stored text by id: `memory.get(id)`; to page through everything a store holds: `memory.browse(limit=25)`. ## Forgetting [#forgetting] ```python memory.delete_memories(agent_id="sales-bot") # one agent's memories memory.delete_memories(run_id="run-8842") # one session's memories memory.delete_memories() # everything in the store client.delete_store("user-42") # forget the user entirely ``` Deleting the store deletes the user's own database — isolation is structural, so "forget this user" is not a filtered query, it's removing their data as a unit. Soft-deleted stores stay restorable for a grace period, then are gone. # Portability (/docs/portability) A store **is** the open-source LodeDB on-disk format — the cloud and the embedded engine share one implementation of it. There is no export step and no proprietary conversion: pull the committed store down and open it with the embedded engine. ```bash pip install lodedb orecloud pull orecloud:////user-42 ./user-42 ``` ```python import lodedb db = lodedb.LodeDB(path="./user-42") db.search("hiking", k=5) ``` `pull` verifies every artifact's checksum against the manifest and proves the copy opens through the engine before it reports success. ## The read API is identical [#the-read-api-is-identical] The cloud handle duck-types the local `lodedb.LodeDB` read surface — hits are `(score, id, metadata)` rows either way — so RAG adapters and MCP tool bodies written against a local index work against the hosted copy unmodified, and vice versa. ## Redaction posture [#redaction-posture] What leaves a machine is explicit. Pushes of local stores are redacted by default (no raw text ships unless you pass `--include-text`), and cloud stores only return text under the double gate described in [Keys & environments](/docs/keys). # Quickstart (/docs/quickstart) ## Install [#install] ```bash pip install orecloud ``` ## Sign in and create a store [#sign-in-and-create-a-store] ```bash orecloud login --host https:// orecloud store create user-42 --expose-text ``` `store create` registers a cloud-writer store (server-side embeddings, the `minilm` preset by default), mints a secret key scoped to it, and prints a ready-to-run snippet. `--expose-text` lets reads return stored text — you want it for memory workloads, since `context_block` and `get` return text. Without it, keys can rank results but never read payload back. Put the printed credentials in your environment: ```bash export ORECLOUD_HOST="https://" export ORECLOUD_TOKEN="ore_sk_..." ``` ## Store and recall [#store-and-recall] ```python import orecloud client = orecloud.Client() # org + environment come from the key memory = client.store("user-42") # one store per end user memory.add("likes hiking near Seattle") context = memory.context_block("plan my weekend") print(context) ``` A few things happened implicitly: * **No create step per user.** `client.store(name)` makes no HTTP call, and a store that doesn't exist yet is provisioned by its first write. A new end user is just a new store name. * **The write is durable when `add` returns.** Writes are accepted asynchronously (durable and ordered), then folded into the index within seconds. * **Read-your-writes.** A search from this handle waits for this handle's own writes to become visible, so the `context_block` call already reflects the `add` above. ## Search [#search] `search` is classic top-k retrieval; `recall` takes a whole raw user message and lets the server derive sub-queries: ```python hits = memory.search("outdoor plans", k=5) for score, id, metadata in hits: print(score, id, metadata) hits = memory.recall("what should I do this weekend?") ``` ## Next steps [#next-steps]