RAG backend and model orchestration for CMU GPT
  • Python 98.6%
  • Nix 1.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jesse Chen 5613c94feb
Some checks failed
kennel/deploy service 'agent' failed healthcheck within startup grace period
CI / check-1 (push) Successful in 1m38s
CI / check (push) Successful in 0s
kennel/build build succeeded
CI / build (push) Successful in 1m27s
CI / test-agent (push) Successful in 48s
Merge pull request 'fix: model-owned map decisions and a dedicated memory schema' (#18) from memory-moderation-optimization into main
Reviewed-on: #18
2026-08-13 21:20:49 +00:00
.forgejo/workflows Merge remote-tracking branch 'origin/main' into memory-moderation-optimization 2026-08-13 09:20:48 -07:00
agent fix: make memory schema creation static sql 2026-08-13 14:17:18 -07:00
ci_test fix: keep memory ci tests offline and update map prompt 2026-08-13 10:42:38 -07:00
src Merge remote-tracking branch 'origin/optimization-token-limit' into persistent-user-memory 2026-08-12 20:50:57 -07:00
tools fix: ci and adjusting to pipeline 2026-08-02 18:43:15 -04:00
.editorconfig Initial commit 2025-09-21 23:39:21 -04:00
.envrc feat: nix 2026-05-30 15:30:06 -04:00
.gitignore feat: nix 2026-05-30 15:30:06 -04:00
.pre-commit-config.yaml fix: ci and adjusting to pipeline 2026-08-02 18:43:15 -04:00
.python-version Initial commit 2025-09-21 23:39:21 -04:00
buildings.json feat: data-driven building catalog and better map directions handeling 2026-06-18 19:41:43 -07:00
devenv.lock fix: devenv location 2026-08-02 17:33:02 -04:00
devenv.nix fix: harden persistent memory and update comments 2026-08-05 21:15:42 -07:00
devenv.yaml chore: update kennel deployment 2026-08-12 21:31:00 -07:00
flake.lock fix: flake and build 2026-08-02 19:20:22 -04:00
flake.nix chore: permissions issue 2026-08-02 20:19:59 -04:00
LICENSE Initial commit 2025-09-21 23:39:21 -04:00
Procfile feat: persist authenticated user memories with pgvector 2026-07-15 22:06:34 -07:00
pyproject.toml Merge remote-tracking branch 'origin/main' into persistent-user-memory 2026-08-05 20:02:28 -07:00
README.md Merge remote-tracking branch 'origin/main' into persistent-user-memory 2026-08-05 20:02:28 -07:00
requirements.txt Merge remote-tracking branch 'origin/main' into persistent-user-memory 2026-08-05 20:02:28 -07:00
secretspec.toml Merge remote-tracking branch 'origin/main' into memory-moderation-optimization 2026-08-13 09:20:48 -07:00
uv.lock Merge remote-tracking branch 'origin/main' into persistent-user-memory 2026-08-05 20:02:28 -07:00

Python Template

This project makes use of several excellent tools from Astral, including uv, ruff, and ty.

Setup

  1. Once you have installed uv, install dependencies with
uv sync

Create a .env file with OPENROUTER_API_KEY, MCP_SERVER_URL, OPENAI_API_KEY, AGENT_SHARED_SECRET, and DATABASE_URL. For durable user memory, create a PostgreSQL database with pgvector and point DATABASE_URL at it (for example postgresql:///cmugpt_agent?host=/tmp for a local unix-socket server):

createdb cmugpt_agent
psql -d cmugpt_agent -c 'CREATE EXTENSION IF NOT EXISTS vector;'

OPENROUTER_API_KEY powers chat and memory extraction. OPENAI_API_KEY is a real OpenAI key used for text-embedding-3-large semantic search. The AGENT_SHARED_SECRET is a random application-to-application bearer token shared only with the Surface server; generate one with openssl rand -hex 32 and never put it in browser-visible configuration.

The embedding model uses a pgvector halfvec(3072) HNSW index. Startup verifies that an existing store_vectors table matches this shape and refuses to start against a database initialized for a different embedding model; in that case rebuild the store_vectors and vector_migrations tables and re-index any memory you need to retain. A fresh database needs no preparation beyond CREATE EXTENSION vector.

Long-term memory stores only durable facts: facts distilled from chats and facts the user explicitly asks CMUGPT to remember. Raw user/assistant turns are not stored or recalled as memory. The clear-memory endpoint also purges the legacy episode namespace so data written by older deployments can still be removed.

  1. Install the pre-commit hooks using
uv run pre-commit autoupdate
uv run pre-commit install --install-hooks
  1. VS Code will prompt you to install the recommended extensions, which you should accept. If you mistakenly closed it, you can find them in .vscode/extensions.json.

Usage

  • Format: uv run ruff format
  • Typecheck: uv run ty check
  • Lint: uv run ruff check

To run the FastAPI app locally with uv (the project uses uv for task execution), run:

uv run python src/main.py

You can set the PORT environment variable to change the listening port (defaults to 5000):

PORT=8080 uv run python src/main.py

Verify that memory is actually durable:

curl -s http://localhost:5000/api/health

The response must report memory.backend as postgres, memory.ready as true, and memory.semantic_search as true. An in-memory backend is only a local-development fallback and resets on process restart.

Deployment (Kennel)

Production runs on Kennel via devenv and secretspec. Pushes to Codeberg main trigger deploys (GitHub mirror pushes do not).

URLs:

Validate locally before pushing:

SECRETSPEC_PROVIDER=dotenv://.env devenv build scottylabs.kennel.config
nix build .#packages.x86_64-linux.agent

Set production secrets (requires cmugpt-agent-admins group and bao login -method=oidc):

secretspec set -P prod OPENROUTER_API_KEY
secretspec set -P prod OPENAI_API_KEY
secretspec set -P prod MCP_SERVER_URL
secretspec set -P prod AGENT_SHARED_SECRET
secretspec check -P prod

DATABASE_URL is not an OpenBao secret: Kennel injects it into the process environment from its platform-managed Postgres, so it is deliberately not declared in secretspec.toml. The database in devenv.nix fills the same role for local development.

Production must set AGENT_ENV=production (the Procfile already does). The agent refuses to start in production if DATABASE_URL or AGENT_SHARED_SECRET is missing from the environment.

Guidelines

You should not globally disable rules enforced by ruff or ty. If absolutely necessary, you can ignore them on a line-by-line basis:

For ty, use ignore directives in the following order of precedence, based on what is strictly necessary.

  1. # ty: ignore[<rule>] for ignoring single rules
  2. # ty: ignore[rule1, rule2, ...] for ignoring multiple rules
  3. # type: ignore or # type: ignore[<rule>] for ignoring all violations on that line (even if a rule is specified!)
  4. The decorator @typing.no_type_check to suppress all violations inside a function

For ruff, follow the same pattern.

  1. # noqa: <rule> for ignoring single rules
  2. # noqa: rule1, rule2, ... for ignoring multiple rules
  3. # noqa for ignoring all violations on that line
  4. # ruff: noqa: <rule> for ignoring a specific rule across an entire file
  5. # ruff: noqa for ignoring all violations across an entire file