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
41 changes: 23 additions & 18 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -185,25 +185,30 @@ DEFAULT_MODEL=anthropic/claude-opus-4-6
# =============================================================================
# Hippocampus lets scope-based agents (scope, code_review, doc, supply_chain)
# consolidate their run into a ContextMemory and persist it as an Episode.
# Save-only for now (no loading). Disabled by default per-signature — see
# the per-signature MEMORY_* settings below.
# Episodes are stored in PostgreSQL using a relational schema with pgvector
# for semantic search.
#
# Episodes are written to:
# global/episodic/<repo>/<scope-subroot>/codespy-<task>-<timestamp>.json
# under MEMORY_ROOT (filesystem) or MEMORY_S3_BUCKET (s3).

# Storage backend: filesystem or s3 (default: filesystem)
# MEMORY_BACKEND=filesystem

# Filesystem backend
# MEMORY_ROOT=~/.cache/codespy/memory

# S3 backend (used when MEMORY_BACKEND=s3)
# MEMORY_S3_BUCKET=my-bucket
# Falls back to AWS_REGION if not set
# MEMORY_S3_REGION=us-east-1
# For MinIO / S3-compatible endpoints
# MEMORY_S3_ENDPOINT_URL=https://minio.example.com
# Production uses MEMORY_POSTGRES_HOST (+ credentials) to connect to an
# external PostgreSQL instance (AWS RDS, etc.). For local development,
# pg0-embedded is used if MEMORY_POSTGRES_HOST is not set.

# PostgreSQL connection — set for production (AWS RDS, etc.)
# When MEMORY_POSTGRES_HOST is unset, pg0-embedded auto-starts a local instance.
# MEMORY_POSTGRES_HOST=rds-host.amazonaws.com
# MEMORY_POSTGRES_PORT=5432
# MEMORY_POSTGRES_USER=myuser
# MEMORY_POSTGRES_PASSWORD=mypassword
# MEMORY_POSTGRES_DATABASE=codespy
# MEMORY_POSTGRES_SCHEMA=episodic # PostgreSQL search_path (default: episodic; each memory type uses its own)

# Bank ID — nickname, username, email, or agent name scoping all memory data.
# When unset, defaults to "codespy".
# MEMORY_BANK_ID=my-agent

# pg0 embedded settings (local dev only, ignored when MEMORY_POSTGRES_HOST is set)
# MEMORY_PG0_NAME=codespy
# MEMORY_PG0_PORT=5432
# MEMORY_PG0_DATA_DIR=~/.cache/codespy/pg0 # default; persists on Docker-mounted codespy-cache volume

# Reflection defaults — overridable per-signature via <SIGNATURE>_MEMORY_*
# MEMORY_DEFAULT_ENABLED=false
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@

## [Unreleased]

## [1.2.0] - 2026-09-11

### Changed
- **BREAKING**: Memory storage backend migrated from filesystem/S3 to PostgreSQL
- `MemoryConfig` fields removed: `backend`, `root`, `s3_bucket`, `s3_region`, `s3_endpoint_url`
- New `PostgresConfig` sub-model (`memory.postgres.*`): `host`, `port`, `user`, `password`, `database`, `schema`
- New `Pg0Config` sub-model (`memory.pg0.*`): `name`, `port`, `data_dir` (local dev via pg0-embedded)
- New `bank_id` field on `MemoryConfig` scoping all memory data
- Env vars removed: `MEMORY_BACKEND`, `MEMORY_ROOT`, `MEMORY_S3_BUCKET`, `MEMORY_S3_REGION`, `MEMORY_S3_ENDPOINT_URL`
- Env vars added: `MEMORY_POSTGRES_HOST`, `MEMORY_POSTGRES_PORT`, `MEMORY_POSTGRES_USER`, `MEMORY_POSTGRES_PASSWORD`, `MEMORY_POSTGRES_DATABASE`, `MEMORY_POSTGRES_SCHEMA`, `MEMORY_PG0_NAME`, `MEMORY_PG0_PORT`, `MEMORY_PG0_DATA_DIR`, `MEMORY_BANK_ID`
- **BREAKING**: `Item` renamed to `Observation` throughout context memory
- `ItemTag` → `ObservationTag`
- `Operation.item_id` → `Operation.observation_id`
- `Mutation.item_id` → `Mutation.observation_id`
- `ContextMemory.all_items()` → `ContextMemory.all_observations()`
- `ContextMemory.find_item()` → `ContextMemory.find_observation()`
- `Hippocampus._update_item_scores()` → `Hippocampus._update_observation_scores()`
- **BREAKING**: `Topic` model gains required `type` field (e.g. `"project_scope"`, `"pull_request"`)
- **BREAKING**: `get_memory_store()` / `reset_memory_store()` renamed to `get_episode_store()` / `reset_episode_store()`
- **BREAKING**: `Hippocampus.end_episode()` signature: `store` type `Storage` → `EpisodeStore`; `dir` parameter removed
- **BREAKING**: `Hippocampus.save_episode()`, `Hippocampus.load_episode()` removed; use `EpisodeStore.save_episode()` directly
- **BREAKING**: `find_latest_episode()`, `save_episode()`, `load_episode()` removed from `episode.py`; replaced by `EpisodeStore.load_context()`
- **BREAKING**: `ContextMemory.merge()`, `.to_json()`, `.from_json()` removed; `EpisodeStore.load_context()` returns a merged view via SQL
- **BREAKING**: GitHub Action inputs replaced: `memory-backend`, `memory-root`, `memory-s3-*` → `memory-postgres-*`, `memory-bank-id`
- All review modules (code_reviewer, doc_reviewer, supply_chain_auditor, auditor, summarizer, scope_resolver) now use `EpisodeStore.load_context()` with topic-based queries instead of `find_latest_episode()` with filesystem path patterns
- `PRContext.repo_full_name` property added, stripping host prefix from `repo_slug`
- REPLACE on non-existent observation with valid section prefix now falls back to ADD instead of silently skipping
- REPLACE with topic-ID-shaped `observation_id` (no section prefix) logs warning and skips
- `Hippocampus._record_mutations()` handles REPLACE-to-ADD fallback to keep mutations aligned with `new_ids`
- `Episode.id` field added (caller-provided UUID); `Episode.timestamp` no longer auto-generated
- `reset_episode_store()` calls `store.close()` before clearing the cache
- SQL in `postgres.py` refactored from f-strings to `psycopg.sql.SQL().format()`

### Added
- `src/codespy/agents/memory/postgres.py` — `EpisodeStore`: relational episode storage with schema (banks, episodes, topics, observations, observation_topics, episode_topics, mutations)
- `src/codespy/agents/memory/pg0_manager.py` — pg0-embedded lifecycle (`get_pg0_uri()`, `stop_pg0()`) for zero-config local dev
- `psycopg` dependency (>=3.1, extras: binary + pool)
- `pg0-embedded` dependency (>=0.15)
- `libgssapi-krb5-2` in Dockerfile for Kerberos/GSSAPI PostgreSQL auth
- `_PREFIX_TO_SECTION` reverse mapping in `context_memory.py`

### Removed
- `ContextMemory.merge()`, `.to_json()`, `.from_json()`
- `find_latest_episode()`, `save_episode()`, `load_episode()` from `episode.py`
- `Hippocampus.save_episode()`, `.load_episode()`, `.episode_file_path()`, `_episode_index`
- `MemoryBackend` type alias
- Storage dependency (`codespy.tools.storage.base.Storage`, `codespy.tools.storage.models`) in hippocampus/episode modules

## [1.0.16] - 2026-09-01

### Changed
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
ripgrep \
libgssapi-krb5-2 \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -m -u 1000 codespy

Expand Down
57 changes: 33 additions & 24 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,25 +314,30 @@ inputs:
required: false
default: 'false'

memory-backend:
description: 'Memory storage backend: filesystem or s3. Note: filesystem is ephemeral in Docker; use s3 for persistence across runs.'
memory-postgres-host:
description: 'PostgreSQL host for memory storage (e.g., rds-host.amazonaws.com). When unset, pg0-embedded auto-starts.'
required: false
default: 'filesystem'

memory-root:
description: 'Filesystem path for memory storage (only used with filesystem backend; ephemeral in Docker)'
memory-postgres-port:
description: 'PostgreSQL port (default: 5432)'
required: false

memory-s3-bucket:
description: 'S3 bucket for memory storage (required when backend is s3). Requires aws-access-key-id/aws-secret-access-key inputs.'
default: '5432'
memory-postgres-user:
description: 'PostgreSQL user (default: postgres)'
required: false

memory-s3-region:
description: 'S3 region for memory bucket (defaults to aws-region input)'
memory-postgres-password:
description: 'PostgreSQL password'
required: false
memory-postgres-database:
description: 'PostgreSQL database name (default: codespy)'
required: false
default: 'codespy'
memory-postgres-schema:
description: 'PostgreSQL schema / search_path (default: episodic)'
required: false
default: 'episodic'

memory-s3-endpoint-url:
description: 'S3 endpoint URL for S3-compatible stores (e.g., MinIO)'
memory-bank-id:
description: 'Bank ID scoping all memory data (e.g., project name or agent name). Defaults to "codespy".'
required: false

memory-max-reflects:
Expand Down Expand Up @@ -541,11 +546,13 @@ runs:

# Memory (Hippocampus)
MEMORY_DEFAULT_ENABLED: ${{ inputs.memory-enabled }}
MEMORY_BACKEND: ${{ inputs.memory-backend }}
MEMORY_ROOT: ${{ inputs.memory-root }}
MEMORY_S3_BUCKET: ${{ inputs.memory-s3-bucket }}
MEMORY_S3_REGION: ${{ inputs.memory-s3-region }}
MEMORY_S3_ENDPOINT_URL: ${{ inputs.memory-s3-endpoint-url }}
MEMORY_POSTGRES_HOST: ${{ inputs.memory-postgres-host }}
MEMORY_POSTGRES_PORT: ${{ inputs.memory-postgres-port }}
MEMORY_POSTGRES_USER: ${{ inputs.memory-postgres-user }}
MEMORY_POSTGRES_PASSWORD: ${{ inputs.memory-postgres-password }}
MEMORY_POSTGRES_DATABASE: ${{ inputs.memory-postgres-database }}
MEMORY_POSTGRES_SCHEMA: ${{ inputs.memory-postgres-schema }}
MEMORY_BANK_ID: ${{ inputs.memory-bank-id }}
MEMORY_DEFAULT_MAX_REFLECTS: ${{ inputs.memory-max-reflects }}
MEMORY_DISTILLER_MODEL: ${{ inputs.memory-distiller-model }}
MEMORY_DISTILLER_REASONING_EFFORT: ${{ inputs.memory-distiller-reasoning-effort }}
Expand Down Expand Up @@ -649,11 +656,13 @@ runs:

# Memory (Hippocampus)
[ -n "$MEMORY_DEFAULT_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DEFAULT_ENABLED"
[ -n "$MEMORY_BACKEND" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_BACKEND"
[ -n "$MEMORY_ROOT" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_ROOT"
[ -n "$MEMORY_S3_BUCKET" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_S3_BUCKET"
[ -n "$MEMORY_S3_REGION" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_S3_REGION"
[ -n "$MEMORY_S3_ENDPOINT_URL" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_S3_ENDPOINT_URL"
[ -n "$MEMORY_POSTGRES_HOST" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_POSTGRES_HOST"
[ -n "$MEMORY_POSTGRES_PORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_POSTGRES_PORT"
[ -n "$MEMORY_POSTGRES_USER" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_POSTGRES_USER"
[ -n "$MEMORY_POSTGRES_PASSWORD" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_POSTGRES_PASSWORD"
[ -n "$MEMORY_POSTGRES_DATABASE" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_POSTGRES_DATABASE"
[ -n "$MEMORY_POSTGRES_SCHEMA" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_POSTGRES_SCHEMA"
[ -n "$MEMORY_BANK_ID" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_BANK_ID"
[ -n "$MEMORY_DEFAULT_MAX_REFLECTS" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DEFAULT_MAX_REFLECTS"
[ -n "$MEMORY_DISTILLER_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DISTILLER_MODEL"
[ -n "$MEMORY_DISTILLER_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DISTILLER_REASONING_EFFORT"
Expand Down
51 changes: 22 additions & 29 deletions codespy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,36 +74,29 @@ gitlab:
# default globally; enabled by default for summary — see `memory:`
# blocks under each signature below.

#
# Episodes are written to:
# global/episodic/<repo>/<scope-subroot>/codespy-<task>-<timestamp>.json
# under `root` (filesystem) or `s3_bucket` (s3).
# Episodes are persisted in PostgreSQL. When postgres.host is unset,
# pg0-embedded auto-starts a local PostgreSQL instance (zero config).
# For production, set postgres.host to an external PostgreSQL (AWS RDS, etc.).
memory:
backend: filesystem # MEMORY_BACKEND (filesystem | s3)

# Filesystem backend
root: ~/.cache/codespy/memory # MEMORY_ROOT

# S3 backend (used when backend: s3)
# ---------------------------------------------------------------------------
# Use this to store memory episodes in S3-compatible object storage instead of
# the local filesystem. Useful for:
# - Shared memory across multiple codespy instances (CI/CD, distributed setups)
# - Centralized episode storage
# - Integration with MinIO, AWS S3, or other S3-compatible services
# ---------------------------------------------------------------------------
s3_bucket: null # MEMORY_S3_BUCKET - The bucket name
# Example: "codespy-memory" or "mycompany-codespy"

s3_region: null # MEMORY_S3_REGION - AWS region for the bucket
# Falls back to aws_region (from llm.* section above)
# Example: "us-east-1", "eu-west-1"

s3_endpoint_url: null # MEMORY_S3_ENDPOINT_URL - Custom endpoint
# Only needed for MinIO or non-AWS S3-compatible storage
# Leave as null for AWS S3
# Example: "http://localhost:9000" (MinIO local)
# Example: "https://minio.example.com" (MinIO remote)
# External PostgreSQL (production, AWS RDS, etc.).
# When postgres.host is unset, pg0-embedded auto-starts a local instance.
postgres:
host: null # MEMORY_POSTGRES_HOST
port: 5432 # MEMORY_POSTGRES_PORT
user: null # MEMORY_POSTGRES_USER (default: postgres)
password: null # MEMORY_POSTGRES_PASSWORD
database: codespy # MEMORY_POSTGRES_DATABASE
schema: episodic # MEMORY_POSTGRES_SCHEMA (search_path per memory type; null = public)

# pg0-embedded settings (local dev only, ignored when postgres.host is set)
pg0:
name: codespy # MEMORY_PG0_NAME
port: null # MEMORY_PG0_PORT (auto-detected if unset)
data_dir: null # MEMORY_PG0_DATA_DIR (default: ~/.cache/codespy/pg0)

# Bank ID — scopes all memory data. Can be a nickname, username, email,
# or agent name. When unset, auto-generated from hostname.
bank_id: null # MEMORY_BANK_ID

# Reflection defaults — overridable per-signature via signatures.<name>.memory
default_enabled: false # MEMORY_DEFAULT_ENABLED
Expand Down
23 changes: 16 additions & 7 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,11 @@ AUTO_DISCOVER_GEMINI=false
|---------|---------|---------|-------------|
| Model | `DEFAULT_MODEL` | `anthropic/claude-opus-4-6` | Primary model for all signatures |
| Reasoning effort | `DEFAULT_REASONING_EFFORT` | `medium` | Provider reasoning budget: `minimal`, `low`, `medium`, `high` |
| Max tokens | `DEFAULT_MAX_TOKENS` | `64000` | Output token budget per completion (reasoning tokens included) |
| Max tokens | `DEFAULT_MAX_TOKENS` | `32000` | Output token budget per completion (reasoning tokens included) |
| Temperature | `DEFAULT_TEMPERATURE` | `0.2` | Default temperature for LLM calls |
| Max iterations | `DEFAULT_MAX_ITERS` | `5` | Maximum ReAct iterations for tool-using agents |
| Prompt caching | `ENABLE_PROMPT_CACHING` | `true` | Provider-side prompt caching (Anthropic, OpenAI, Bedrock) |
| Compact patches | `COMPACT_PATCHES` | `false` | Expand diff hunks to full function bodies using Tree-sitter |
| RLM fallback | `RLM_FALLBACK_ENABLED` | `true` | Proactive RLM fallback for context rot prevention |
| RLM react threshold | `RLM_FALLBACK_REACT_THRESHOLD` | `0.30` | Context ratio triggering RLM for ReAct modules |
| RLM CoT threshold | `RLM_FALLBACK_CHAIN_OF_THOUGHT_THRESHOLD` | `0.40` | Context ratio triggering RLM for ChainOfThought modules |
Expand Down Expand Up @@ -171,14 +172,22 @@ Brief overview:

| Setting | Env Var | Default | Description |
|---------|---------|---------|-------------|
| Backend | `MEMORY_BACKEND` | `filesystem` | Storage: `filesystem` or `s3` |
| Root path | `MEMORY_ROOT` | `~/.cache/codespy/memory` | Filesystem storage location |
| PostgreSQL host | `MEMORY_POSTGRES_HOST` | — | External PostgreSQL host |
| PostgreSQL port | `MEMORY_POSTGRES_PORT` | `5432` | External PostgreSQL port |
| PostgreSQL user | `MEMORY_POSTGRES_USER` | `postgres` | External PostgreSQL user |
| PostgreSQL password | `MEMORY_POSTGRES_PASSWORD` | — | External PostgreSQL password |
| PostgreSQL database | `MEMORY_POSTGRES_DATABASE` | `codespy` | External PostgreSQL database |
| PostgreSQL schema | `MEMORY_POSTGRES_SCHEMA` | `episodic` | PostgreSQL schema / search_path per memory type |
| Bank ID | `MEMORY_BANK_ID` | `codespy` | Scopes all memory data |
| pg0 name | `MEMORY_PG0_NAME` | `codespy` | pg0-embedded database name (local dev) |
| pg0 port | `MEMORY_PG0_PORT` | auto | pg0-embedded port (local dev) |
| pg0 data dir | `MEMORY_PG0_DATA_DIR` | — | pg0-embedded data directory (local dev) |
| Default enabled | `MEMORY_DEFAULT_ENABLED` | `false` | Enable memory globally |
| Max reflects | `MEMORY_DEFAULT_MAX_REFLECTS` | `0` | Reflection iterations (0 = once at end) |
| Context memory tokens | `MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS` | `16384` | Max tokens for persisted context memory |
| Item tokens | `MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS` | `512` | Soft per-item token limit |
| Trajectory tokens | `MEMORY_DEFAULT_MAX_TRAJECTORY_TOKENS` | `16384` | Cap on trajectory fed to Distiller |
| Question tokens | `MEMORY_DEFAULT_MAX_QUESTION_TOKENS` | `8192` | Cap on serialized reflection inputs |
| Context memory tokens | `MEMORY_MAX_CONTEXT_MEMORY_TOKENS` | `16384` | Ceiling on persisted context memory |
| Observation tokens | `MEMORY_MAX_CONTEXT_ITEM_TOKENS` | `512` | Soft per-observation token limit |
| Trajectory tokens | `MEMORY_MAX_TRAJECTORY_TOKENS` | `16384` | Cap on trajectory fed to Distiller |
| Question tokens | `MEMORY_MAX_QUESTION_TOKENS` | `8192` | Cap on serialized reflection inputs |

See [Memory System](memory.md) for full memory configuration details.

Expand Down
4 changes: 3 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ src/codespy/
│ ├── cost_tracker.py # Token/cost tracking
│ ├── dspy_config.py # DSPy runtime config
│ ├── memory/ # Hippocampus memory system
│ │ └── hippocampus/ # Episode persistence, context memory, budget
│ │ ├── hippocampus/ # Episode persistence, context memory, budget
│ │ ├── pg0_manager.py # pg0-embedded PostgreSQL lifecycle
│ │ └── postgres.py # PostgreSQL episode persistence
│ └── reviewer/ # Review pipeline
│ ├── models.py # Review data models
│ ├── reviewer.py # Main review orchestrator
Expand Down
Loading
Loading