Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 8 additions & 29 deletions .docker/gbrain/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -280,35 +280,14 @@ gbrain config set dream.synthesize.link_manifest true >/dev/null
gbrain config set agent.use_gateway_loop true >/dev/null
echo "[gbrain-entrypoint] corpus checkout: $BRAIN_DIR (filesystem + Postgres index)"

# Route gbrain's OpenRouter reranker through the same Roomote credential
# gateway as embeddings and chat. Do this after initialization so exposing an
# OpenRouter-compatible endpoint does not change which provider gbrain chooses
# when it creates the Brain. An empty forwarded setting restores the default,
# including after a deployment previously selected another reranker.
GBRAIN_RERANKER_MODEL="${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite}"
case "$GBRAIN_RERANKER_MODEL" in
openrouter:*)
if [ -z "${OPENROUTER_BASE_URL:-}" ] && [ -n "${OPENAI_BASE_URL:-}" ]; then
OPENROUTER_BASE_URL="${OPENAI_BASE_URL%/}"
case "$OPENROUTER_BASE_URL" in
*/v1) ;;
*) OPENROUTER_BASE_URL="$OPENROUTER_BASE_URL/v1" ;;
esac
export OPENROUTER_BASE_URL
fi
if [ -z "${OPENROUTER_API_KEY:-}" ] && [ -n "${OPENAI_API_KEY:-}" ]; then
OPENROUTER_API_KEY="$OPENAI_API_KEY"
export OPENROUTER_API_KEY
fi
if [ -z "${OPENROUTER_BASE_URL:-}" ] || [ -z "${OPENROUTER_API_KEY:-}" ]; then
echo "[gbrain-entrypoint] WARNING: $GBRAIN_RERANKER_MODEL needs OPENROUTER_BASE_URL and OPENROUTER_API_KEY."
echo "[gbrain-entrypoint] WARNING: reranking will remain fail-open until the gateway is configured."
fi
;;
esac

gbrain config set search.reranker.model "$GBRAIN_RERANKER_MODEL" >/dev/null
echo "[gbrain-entrypoint] reranker: $GBRAIN_RERANKER_MODEL"
# The Brain does not use a reranker. gbrain's own init already writes
# `search.reranker.enabled false` for installs keyed the way ours are, but
# make the choice explicit so every brain — including ones created before
# this line and ones hit by upstream mode-bundle default flips — converges
# on the same shipped behavior. Retrieval is hybrid RRF; autocut no-ops
# without rerank scores by design.
gbrain config set search.reranker.enabled false >/dev/null
echo "[gbrain-entrypoint] reranker: disabled"

# Adding a key to a brain created without one is a first-class flow rather
# than an edge case: on hosts whose compose parser ignores `profiles` the
Expand Down
8 changes: 3 additions & 5 deletions .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,19 @@ DEFAULT_COMPUTE_PROVIDER=docker
# R_GITHUB_APP_SLUG=
# Optional comma-separated GitHub App slugs that are also trusted as Roomote-managed.
# R_GITHUB_ADDITIONAL_APP_SLUGS=
# Self-run Brain inference (embeddings/rerank stay on your hardware; chat
# Self-run Brain embeddings (embeddings stay on your hardware; chat
# synthesis keeps using the configured provider). With the bundled service,
# set COMPOSE_PROFILES=brain,local-inference and ALL of the settings below —
# the model names and dimensions must match what the inference server
# the model name and dimensions must match what the inference server
# serves, and the embedding pair is create-time: set everything BEFORE the
# Brain's first boot. gbrain's defaults (text-embedding-3-small, 1536) name
# models the bundled server does not serve, so the URLs alone are not a
# models the bundled server does not serve, so the URL alone is not a
# working configuration. Self-run model names pass through unchanged and
# must exactly match the ids served by the upstream.
# R_BRAIN_EMBEDDINGS_UPSTREAM_URL=http://infinity:7997
# R_BRAIN_RERANK_UPSTREAM_URL=http://infinity:7997
# R_BRAIN_INFERENCE_UPSTREAM_API_KEY=
# R_BRAIN_EMBEDDING_MODEL=BAAI/bge-m3
# R_BRAIN_EMBEDDING_DIMENSIONS=1024
# R_BRAIN_RERANKER_MODEL=BAAI/bge-reranker-v2-m3
# R_GITHUB_APP_ID=
# Raw GitHub App private-key PEM with newlines escaped as \n; do not base64 it.
# R_GITHUB_APP_PRIVATE_KEY=
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 14 additions & 29 deletions apps/api/src/handlers/brain-inference/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import type { Variables } from '../../types';
const LOG_PREFIX = '[Brain Inference]';

/**
* The Brain's whole inference surface: embeddings for recall, reranking for
* precision, and chat for sourced synthesis and query expansion. Deliberately
* narrower than the task-sandbox gateway's allowlist, because this credential
* is a static deployment secret rather than a short-lived run token.
* The Brain's whole inference surface: embeddings for recall and chat for
* sourced synthesis and query expansion. Deliberately narrower than the
* task-sandbox gateway's allowlist, because this credential is a static
* deployment secret rather than a short-lived run token. Reranking is not
* part of the Brain: retrieval is hybrid RRF, and the reranker is disabled
* per-brain by the gbrain entrypoint.
*/
const BRAIN_ALLOWED_PATHS = new Set([
'/v1/embeddings',
'/v1/rerank',
'/v1/chat/completions',
'/v1/responses',
]);
Expand Down Expand Up @@ -119,23 +120,21 @@ async function rewriteBody(
}

/**
* A self-run inference upstream for one gateway path. Embeddings and rerank
* are the Brain's bulk data paths (memory text in, vectors/scores out), so
* they are the ones a deployment may want on its own hardware; chat synthesis
* stays with the configured model provider. Model names pass through
* unrewritten — the upstream owns its own model registry, and every Brain is
* locked to its embedding model at creation, so the name must mean exactly
* one thing forever.
* A self-run inference upstream for one gateway path. Embeddings are the
* Brain's bulk data path (memory text in, vectors out), so they are the one
* a deployment may want on its own hardware; chat synthesis stays with the
* configured model provider. Model names pass through unrewritten — the
* upstream owns its own model registry, and every Brain is locked to its
* embedding model at creation, so the name must mean exactly one thing
* forever.
*/
function resolveLocalUpstream(
upstreamPath: string,
): { baseUrl: string; apiKey?: string } | null {
const baseUrl =
upstreamPath === '/v1/embeddings'
? Env.R_BRAIN_EMBEDDINGS_UPSTREAM_URL
: upstreamPath === '/v1/rerank'
? Env.R_BRAIN_RERANK_UPSTREAM_URL
: undefined;
: undefined;

if (!baseUrl?.trim()) {
return null;
Expand Down Expand Up @@ -266,20 +265,6 @@ brainInference.post('/*', async (c) => {
);
}

// gbrain's OpenRouter reranker speaks the same authenticated gateway
// contract as embeddings and chat, but OpenAI itself has no compatible
// rerank endpoint. Fail explicitly instead of forwarding a doomed request
// to api.openai.com and obscuring the missing capability as a 404.
if (upstreamPath === '/v1/rerank' && resolved.providerId !== 'openrouter') {
return c.json(
{
error:
'Brain reranking requires an OpenRouter provider configured in Settings, or a local rerank upstream (R_BRAIN_RERANK_UPSTREAM_URL).',
},
503,
);
}

const provider = getInferenceGatewayProvider(resolved.providerId);

if (!provider?.authHeader) {
Expand Down
6 changes: 2 additions & 4 deletions apps/docs/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,13 @@ as per-task auth tokens or workspace paths.
| `OPENAI_COMPATIBLE_<NAME>_LABEL` | Optional | Display label stored with a named OpenAI-compatible connection. |
| `VLLM_BASE_URL` | vLLM | vLLM OpenAI-compatible endpoint URL, usually including its `/v1` path. |
| `VLLM_API_KEY` | Optional | Bearer API key for a vLLM endpoint that requires authentication. |
| `R_BRAIN_OPENROUTER_API_KEY` | Memory provider | OpenRouter key that enables Memory embeddings, reranking, and synthesis. |
| `R_BRAIN_OPENROUTER_API_KEY` | Memory provider | OpenRouter key that enables Memory embeddings and synthesis. |
| `R_BRAIN_OPENAI_API_KEY` | Memory provider | OpenAI key that enables Memory embeddings and synthesis. |
| `R_BRAIN_MODEL` | Optional | Memory synthesis model in the configured provider's naming. Changes apply immediately. |
| `R_BRAIN_EMBEDDING_MODEL` | Before first Memory boot | Embedding model id that sizes Memory's vector storage. Changing it later requires re-embedding. |
| `R_BRAIN_EMBEDDING_DIMENSIONS` | Before first Memory boot | Output width for `R_BRAIN_EMBEDDING_MODEL`; it must match the served model. |
| `R_BRAIN_RERANKER_MODEL` | Optional | Memory reranker model. Use OpenRouter's model id for OpenRouter, or the exact bare model id served by a self-run rerank upstream. |
| `R_BRAIN_EMBEDDINGS_UPSTREAM_URL` | Optional | OpenAI-compatible embeddings endpoint used instead of the Memory provider. |
| `R_BRAIN_RERANK_UPSTREAM_URL` | Optional | OpenAI-compatible rerank endpoint used instead of OpenRouter. |
| `R_BRAIN_INFERENCE_UPSTREAM_API_KEY` | Optional | Bearer key shared by the self-run embeddings and rerank upstreams; omit it for a trusted private-network service. |
| `R_BRAIN_INFERENCE_UPSTREAM_API_KEY` | Optional | Bearer key for the self-run embeddings upstream; omit it for a trusted private-network service. |

### Sandbox providers

Expand Down
36 changes: 14 additions & 22 deletions apps/docs/memory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,18 @@ Changing that key later takes effect on Memory's next request, with no
redeploy.

OpenRouter and OpenAI both support Memory's embedding and synthesis calls.
Search reranking requires OpenRouter unless a self-run rerank upstream is
configured.

### Run embeddings and reranking locally
### Run embeddings locally

Self-hosted Compose deployments can keep embeddings and reranking on their own
hardware while continuing to send chat synthesis to the configured Memory
provider. Enable both services and point Memory at the bundled inference server:
Self-hosted Compose deployments can keep embeddings on their own hardware
while continuing to send chat synthesis to the configured Memory provider.
Enable both services and point Memory at the bundled inference server:

```sh
COMPOSE_PROFILES=brain,local-inference
R_BRAIN_EMBEDDINGS_UPSTREAM_URL=http://infinity:7997
R_BRAIN_RERANK_UPSTREAM_URL=http://infinity:7997
R_BRAIN_EMBEDDING_MODEL=BAAI/bge-m3
R_BRAIN_EMBEDDING_DIMENSIONS=1024
R_BRAIN_RERANKER_MODEL=BAAI/bge-reranker-v2-m3
```

The bundled CPU service uses multilingual models so recall can cross languages.
Expand All @@ -125,11 +121,11 @@ dimensions is a lighter embedding alternative. Choose the embedding model and
dimensions before Memory's first boot; changing that pair later requires
re-embedding the corpus.

The two upstream URLs can instead target any OpenAI-compatible embedding and
rerank server. Set `R_BRAIN_INFERENCE_UPSTREAM_API_KEY` when that server requires
a bearer key. Roomote forwards model names unchanged to self-run upstreams, so
`R_BRAIN_EMBEDDING_MODEL` and `R_BRAIN_RERANKER_MODEL` must exactly match the
models that server exposes, without a provider prefix.
The upstream URL can instead target any OpenAI-compatible embedding server.
Set `R_BRAIN_INFERENCE_UPSTREAM_API_KEY` when that server requires a bearer
key. Roomote forwards model names unchanged to self-run upstreams, so
`R_BRAIN_EMBEDDING_MODEL` must exactly match a model that server exposes,
without a provider prefix.

Without a Memory key, Memory stays inert. Agents are not told it exists,
and nothing is ingested.
Expand Down Expand Up @@ -229,23 +225,19 @@ in staging is distinguishable from one written against production.

## Choosing models

Three settings pick Memory's models:
Two settings pick Memory's models:

| Variable | What it does | Written as | Changeable |
| ----------------------------- | ----------------- | --------------------------------- | --------------------- |
| `R_BRAIN_MODEL` | Sourced synthesis | your provider's naming | any time |
| `R_BRAIN_EMBEDDING_MODEL` | Semantic recall | a plain model id | before the first boot |
| `R_BRAIN_RERANKER_MODEL` | Search precision | provider or upstream naming | after a restart |

Leave the first two unset and Memory uses OpenAI's `gpt-5.6-luna` and
Leave them unset and Memory uses OpenAI's `gpt-5.6-luna` and
`text-embedding-3-small` through whichever provider you configured.

The reranker defaults to OpenRouter's `voyageai/rerank-2.5-lite`. Set
`R_BRAIN_RERANKER_MODEL` to choose another model from
OpenRouter's reranker catalog. Reranking requires an OpenRouter key; with only
OpenAI configured, gbrain keeps the unreranked results instead of failing the
search. When `R_BRAIN_RERANK_UPSTREAM_URL` is set, use the exact bare model id
served by that upstream instead.
Memory search does not use a cross-encoder reranker: retrieval is hybrid
(vector + keyword fusion), which keeps search latency flat and provider
requirements minimal.

The synthesis model is applied by Roomote when it forwards the call and passed
to the provider as written, so use that provider's naming
Expand Down
9 changes: 2 additions & 7 deletions deploy/compose/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@ x-roomote-base-env: &roomote-base-env
R_BRAIN_MODEL: ${R_BRAIN_MODEL:-}
R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-}
R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-}
R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-}
# Optional self-run inference: point embeddings/rerank at the bundled
# Optional self-run inference: point embeddings at the bundled
# `infinity` service (profile local-inference) or any OpenAI-compatible
# server. Chat synthesis keeps flowing to the configured model provider.
R_BRAIN_EMBEDDINGS_UPSTREAM_URL: ${R_BRAIN_EMBEDDINGS_UPSTREAM_URL:-}
R_BRAIN_RERANK_UPSTREAM_URL: ${R_BRAIN_RERANK_UPSTREAM_URL:-}
R_BRAIN_INFERENCE_UPSTREAM_API_KEY: ${R_BRAIN_INFERENCE_UPSTREAM_API_KEY:-}
R_GBRAIN_URL: ${R_GBRAIN_URL:-http://gbrain:8931}
R_GBRAIN_ADMIN_TOKEN_FILE: /gbrain-data/admin-bootstrap-token
Expand Down Expand Up @@ -542,7 +540,6 @@ services:
# provider key below instead makes the Brain call the provider directly.
OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference}
OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-}
GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-}
# Create-time, and it has to be the container that receives these: the
# embedding model and its width are decided when the Brain is created,
# and the gateway must never substitute a different one afterwards.
Expand All @@ -555,7 +552,7 @@ services:
security_opt:
- no-new-privileges:true

# Self-run embedding/reranking models for the Brain (opt-in, CPU).
# Self-run embedding model for the Brain (opt-in, CPU).
# Enable with the `local-inference` compose profile. The upstream URLs are
# NOT sufficient on their own: R_BRAIN_EMBEDDING_MODEL and
# R_BRAIN_EMBEDDING_DIMENSIONS must name what this server serves (gbrain's
Expand Down Expand Up @@ -590,8 +587,6 @@ services:
# Brain's first boot; the embedding choice is create-time.
- --model-id
- ${INFINITY_EMBEDDING_MODEL:-BAAI/bge-m3}
- --model-id
- ${INFINITY_RERANKER_MODEL:-BAAI/bge-reranker-v2-m3}
volumes:
- infinity_cache:/app/.cache
security_opt:
Expand Down
Loading
Loading