> ## Documentation Index
> Fetch the complete documentation index at: https://docs.evermind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OSS Quickstart

> Five minutes from zero to adding a conversation, querying it back, and reading it as plain Markdown.

EverOS runs as a **service** — start the server, then call the HTTP API. There is no in-process library mode; an `everos` server is always in front of your agent.

## Prerequisites

* **Python 3.12+**
* **An OpenRouter API key** — covers the chat LLM (memory extraction) *and* the multimodal LLM (parsing image / pdf / audio content items) with a single key.
* **A DeepInfra API key** — for the embedding and rerank models that OpenRouter doesn't ship.

Two keys total. Any OpenAI-compatible endpoint plugs in via the matching `*__BASE_URL` env var if you'd rather use OpenAI directly, self-host vLLM, route to Ollama, etc.

## Steps

<Steps>
  <Step title="Install">
    ```bash theme={null}
    pip install everos
    # or:  uv pip install everos
    ```
  </Step>

  <Step title="Configure">
    Generate the starter config files and drop in your two keys:

    ```bash theme={null}
    everos init                       # writes ~/.everos/everos.toml + ome.toml (use --root to relocate)
    chmod 600 ~/.everos/everos.toml    # the file holds your API keys
    ```

    `everos init` generates two files in the memory root (`~/.everos` by default): `everos.toml` (provider and application settings) and `ome.toml` (offline strategy config, hot-reloaded). Open `everos.toml` and fill the `api_key` field in each provider section; only two distinct keys are needed:

    | Section        | `api_key` provider          |
    | -------------- | --------------------------- |
    | `[llm]`        | OpenRouter (chat LLM)       |
    | `[multimodal]` | OpenRouter (same key works) |
    | `[embedding]`  | DeepInfra                   |
    | `[rerank]`     | DeepInfra (same key works)  |

    The template ships model defaults for `[llm]` (`gpt-4.1-mini`) and `[multimodal]` (`google/gemini-3-flash-preview`). `[embedding]` and `[rerank]` ship no model default, so set their `model` and `base_url` yourself (for example DeepInfra's `Qwen/Qwen3-Embedding-4B` and `Qwen/Qwen3-Reranker-4B`). To use a different OpenAI-compatible endpoint for any provider, set that section's `base_url`.

    <Note>
      `everos init` and `everos server start` must use the same root: relocate with `everos init --root <path>` and start with the matching `--root <path>` (or set `EVEROS_ROOT`). The server reads `<root>/everos.toml` and exits with an error if it is missing. Any setting can also be overridden by an `EVEROS_*` environment variable (e.g. `EVEROS_LLM__API_KEY`), handy for containers and CI. Edits to `everos.toml` need a server restart; `ome.toml` hot-reloads within \~2s.
    </Note>
  </Step>

  <Step title="Start the server">
    ```bash theme={null}
    everos server start
    ```

    You should see:

    ```
    starting everos on 127.0.0.1:8000
    INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    ```

    <Note>
      The default bind is `127.0.0.1` (loopback only). To expose the API elsewhere, put your own auth/gateway in front first. The cascade index daemon runs **in the same process** as a FastAPI lifespan coroutine — you don't need a separate worker.
    </Note>

    The server runs in the foreground. **Open a second terminal** for the steps below, and use `Ctrl+C` to stop the server when you're done.

    In the second terminal, verify the server is up:

    ```bash theme={null}
    curl http://127.0.0.1:8000/health
    # {"status":"ok"}
    ```
  </Step>

  <Step title="Add a conversation">
    EverOS ingests memory at the **conversation level**: you POST a batch of `messages` tied to a `session_id`, and the server accumulates them until the boundary detector trips.

    ```bash theme={null}
    TS=$(($(date +%s)*1000))    # Unix epoch in milliseconds (v1 contract)
    curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
      -H 'Content-Type: application/json' \
      -d "{
        \"session_id\": \"demo-001\",
        \"app_id\": \"default\",
        \"project_id\": \"default\",
        \"messages\": [
          {\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $TS, \"content\": \"I love climbing in Yosemite every spring.\"},
          {\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $((TS+10000)), \"content\": \"My favorite coffee shop is Blue Bottle in SOMA.\"},
          {\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $((TS+20000)), \"content\": \"I bike to work most days.\"}
        ]
      }"
    ```

    Response:

    ```json theme={null}
    {
      "request_id": "bf86e4e857834eba804841f8bff29106",
      "data": {
        "message_count": 3,
        "status": "accumulated"
      }
    }
    ```

    `status: "accumulated"` means the messages are in the session buffer, but the boundary detector hasn't decided to extract a memory cell yet.
  </Step>

  <Step title="Force boundary extraction">
    For a quick demo, force extraction manually:

    ```bash theme={null}
    curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
      -H 'Content-Type: application/json' \
      -d '{"session_id":"demo-001","app_id":"default","project_id":"default"}'
    ```

    Response (this takes a few seconds — one LLM call for extraction):

    ```json theme={null}
    {
      "request_id": "ec0e7a00c3bd4b00bb21212a411b7763",
      "data": {
        "status": "extracted"
      }
    }
    ```

    `status: "extracted"` means at least one memory cell was carved out and written to disk and indexed.

    <Note>
      `/flush` is **OSS-only**. The cloud edition decides boundary timing server-side and does not expose this endpoint.
    </Note>
  </Step>

  <Step title="Search your memory">
    ```bash theme={null}
    curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
      -H 'Content-Type: application/json' \
      -d '{
        "user_id": "alice",
        "app_id": "default",
        "project_id": "default",
        "query": "Where do I like to climb?",
        "top_k": 5
      }'
    ```

    Response (trimmed):

    ```json theme={null}
    {
      "request_id": "b53a3a94a080472d97692c503c88afdf",
      "data": {
        "episodes": [
          {
            "id": "alice_ep_20260528_00000002",
            "user_id": "alice",
            "session_id": "demo-001",
            "summary": "On May 28, 2026 ... Alice shared that she loves climbing in Yosemite every spring ...",
            "score": 0.628,
            "atomic_facts": [
              {
                "id": "alice_af_20260528_00000016",
                "content": "Alice said she loves climbing in Yosemite every spring.",
                "score": 0.628
              }
            ]
          }
        ],
        "profiles": [],
        "agent_cases": [],
        "agent_skills": []
      }
    }
    ```

    The hybrid retrieval (BM25 + vector + scalar) returns the episode that contains the climbing fact, with the matching atomic fact nested under it. The other arrays (`profiles` / `agent_cases` / `agent_skills`) are always present for client-side symmetry and are populated only when the relevant memory type matches.
  </Step>
</Steps>

## Your memory is just Markdown

This is what makes EverOS different — your memory persists as plain Markdown files on disk:

```
~/.everos
├── everos.toml                        ← provider + application config (API keys)
├── ome.toml                           ← offline strategy config (hot-reloaded)
├── default_app/                       ← app_id  ("default" → "default_app")
│   └── default_project/               ← project_id ("default" → "default_project")
│       └── users/
│           └── alice/                  ← user_id
│               ├── episodes/
│               │   └── episode-2026-05-28.md
│               ├── .atomic_facts/
│               │   └── atomic_fact-2026-05-28.md
│               ├── .foresights/
│               │   └── foresight-2026-05-28.md
│               └── user.md             ← profile
└── .index/                             ← derived indexes (rebuildable from md)
    ├── sqlite/system.db
    └── lancedb/*.lance/
```

The `default` scope ID materialises as `default_app` / `default_project` on disk so the default space is visually distinct from any user-named space. Any other ID maps to itself (e.g. `app_id: "my-app"` → `my-app/`).

Top-level `.index/` holds SQLite + LanceDB **derived** indexes — wipe it and the cascade daemon rebuilds everything from the Markdown alone.

Every memory entry is a plain file you can `cat` / `grep` / `vim` directly, version with Git, or open in Obsidian (the dotfile directories stay hidden by default).

## Stopping the server

`Ctrl+C` in the server terminal. Uvicorn shuts each lifespan provider down in reverse order before exiting.

## Next steps

<CardGroup cols={2}>
  <Card title="Integrate into your agent" icon="code">
    Add `/add`, `/flush`, and `/search` to your agent loop. Any HTTP client works.
  </Card>

  <Card title="Scope your memory" icon="folder-tree">
    Use `app_id` and `project_id` to isolate memory for different apps or projects.
  </Card>

  <Card title="Multi-modal messages" icon="paperclip">
    Pass images, audio, PDFs, and documents alongside text. Install the multimodal extra to enable parsing.
  </Card>

  <Card title="Search modes" icon="filter">
    Choose from keyword, vector, hybrid, or agentic retrieval. Filter by user, session, or custom fields.
  </Card>
</CardGroup>
