# 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]