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
7 changes: 7 additions & 0 deletions .github/compose/upgrade-gate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
setup:
entrypoint: ["/bin/sh", "-eu", "-c"]
command:
- |
until [ -f /tmp/rememberstack-upgrade-release ]; do sleep 0.1; done
exec python -m rememberstack.profiles.selfhost setup
78 changes: 77 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ jobs:
timeout-minutes: 25
permissions:
contents: read
env:
QUICKSTART_TARGET_SECONDS: "600"
steps:
- uses: actions/checkout@v4

- name: Validate Compose model
run: docker compose --env-file .env.example config --quiet

- name: Start fresh self-host deployment
run: docker compose --env-file .env.example up --build --detach --wait
run: |
started_at="$(date +%s)"
docker compose --env-file .env.example up --build --detach --wait
curl --fail http://localhost:8000/recipes
elapsed_seconds="$(($(date +%s) - started_at))"
echo "Quickstart cold start to first query: ${elapsed_seconds}s / ${QUICKSTART_TARGET_SECONDS}s target" \
| tee -a "$GITHUB_STEP_SUMMARY"
test "$elapsed_seconds" -le "$QUICKSTART_TARGET_SECONDS"

- name: Prove MinIO conditional create
run: |
Expand Down Expand Up @@ -62,6 +71,73 @@ jobs:
test "$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc 'SELECT count(*) FROM cost_ledger')" = '0'

- name: Stage an upgrade from the prior schema
run: |
docker compose --env-file .env.example stop \
api worker-convert worker-structure
docker compose --env-file .env.example run --rm --no-deps \
--entrypoint alembic setup downgrade p7_02_0016
test "$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
'SELECT version_num FROM alembic_version')" = 'p7_02_0016'
test "$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
'SELECT count(*) FROM deployments')" = '1'
docker compose --env-file .env.example rm --force setup

- name: Run migrations before application processes restart
run: |
compose_args=(
--env-file .env.example
--file compose.yaml
--file .github/compose/upgrade-gate.yaml
)
docker compose "${compose_args[@]}" up --detach --wait &
compose_pid="$!"
release_gate() {
docker compose "${compose_args[@]}" exec -T setup \
touch /tmp/rememberstack-upgrade-release
}
trap 'release_gate || true' EXIT

setup_running=''
for attempt in {1..30}; do
setup_running="$(
docker compose "${compose_args[@]}" ps --status running --services setup
)"
if [ "$setup_running" = 'setup' ]; then
break
fi
sleep 1
done
test "$setup_running" = 'setup'

running_services="$(
docker compose "${compose_args[@]}" ps --status running --services
)"
test -z "$(printf '%s\n' "$running_services" \
| grep -E '^(api|worker-convert|worker-structure)$' || true)"
test "$(docker compose "${compose_args[@]}" exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
'SELECT version_num FROM alembic_version')" = 'p7_02_0016'

release_gate
wait "$compose_pid"
trap - EXIT
test "$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
'SELECT version_num FROM alembic_version')" = 'p7_05_0017'
test "$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
'SELECT count(*) FROM deployments')" = '1'
running_services="$(
docker compose "${compose_args[@]}" ps --status running --services
)"
for service in api worker-convert worker-structure; do
printf '%s\n' "$running_services" | grep -qx "$service"
done
curl --fail http://localhost:8000/healthz

- name: Show service logs after failure
if: failure()
run: docker compose --env-file .env.example logs --no-color
Expand Down
142 changes: 142 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Release

on:
push:
tags: ["v*.*.*"]

permissions:
contents: read

jobs:
verify:
name: Verify release
runs-on: ubuntu-latest
timeout-minutes: 30
env:
REMEMBERSTACK_DATABASE_URL: postgresql+psycopg://rememberstack:rememberstack_test@localhost:5432/rememberstack_test
services:
postgres:
image: ghcr.io/dbsystel/postgresql-partman@sha256:bf9d2331aaeb3e33a4e6e73b4c024ca618d4d1ea050bbab978125ff63b83385a
env:
POSTGRES_USER: rememberstack
POSTGRES_PASSWORD: rememberstack_test
POSTGRES_DB: rememberstack_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U rememberstack -d rememberstack_test"
--health-interval 5s
--health-timeout 5s
--health-retries 20
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Set up Python
run: uv python install 3.14

- name: Install dependencies
run: uv sync --locked --python 3.14

- name: Validate tag and shared artifact version
run: uv run python scripts/check_release_contract.py --tag "$GITHUB_REF_NAME"

- name: Verify architecture, style, and types
run: |
uv run lint-imports
uv run ruff check src/
uv run ruff format --check src/
uv run pyright src/ --pythonversion 3.14

- name: Run the release test suite
run: uv run pytest src/tests -q

- name: Build distributions
run: uv build

- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: python-distributions
path: dist/
if-no-files-found: error

publish-pypi:
name: Publish PyPI
needs: verify
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/rememberstack
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: python-distributions
path: dist/

- name: Publish through PyPI Trusted Publishing
uses: pypa/gh-action-pypi-publish@release/v1

publish-ghcr:
name: Publish GHCR
needs: verify
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Derive the semantic image tag
id: metadata
uses: docker/metadata-action@v5
with:
images: ghcr.io/writeitai/remember-stack
flavor: latest=false
tags: type=semver,pattern={{version}}

- name: Build and publish the shared API/worker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}

github-release:
name: Publish GitHub release
needs: [publish-pypi, publish-ghcr]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Download distributions
uses: actions/download-artifact@v4
with:
name: python-distributions
path: dist/

- name: Create the release with pinned deployment inputs
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
dist/*
compose.yaml
.env.example
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# RememberStack

[![CI](https://github.com/writeitai/rememberstack/actions/workflows/ci.yml/badge.svg)](https://github.com/writeitai/rememberstack/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/writeitai/rememberstack/python-coverage-comment-action-data/endpoint.json)](https://github.com/writeitai/rememberstack/tree/python-coverage-comment-action-data)
[![CI](https://github.com/writeitai/remember-stack/actions/workflows/ci.yml/badge.svg)](https://github.com/writeitai/remember-stack/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/writeitai/remember-stack/python-coverage-comment-action-data/endpoint.json)](https://github.com/writeitai/remember-stack/tree/python-coverage-comment-action-data)

A memory system for AI agents, designed to ingest **millions** of heterogeneous documents and
distill them into progressively more abstract, navigable knowledge — while keeping everything
auditable by humans. Scale is a requirement, not an aspiration: it is meant to still be useful
at a million documents.

> **Pre-release software.** Phases 0–6 and most of Phase 7 are implemented and tested, but no
> public package or container release exists yet. The fresh-deployment Docker Compose skeleton
> is documented under [Self-host deployment](website/src/app/docs/deployment/page.mdx); it proves
> PostgreSQL, MinIO, API ingestion, and the first two E0 worker stages, not a production rollout.
> The build follows [plan/plans/roadmap.md](plan/plans/roadmap.md).
> **Pre-release software.** Phases 0–6 and most of Phase 7 are implemented and tested. Release
> automation exists, but the owner-side legal, governance, and registry setup gates are not
> complete, so no public package or container release exists yet. The fresh-deployment Docker
> Compose skeleton is documented under
> [Self-host deployment](website/src/app/docs/deployment/page.mdx); it proves PostgreSQL, MinIO,
> API ingestion, and the first two E0 worker stages, not a production rollout. The build follows
> [plan/plans/roadmap.md](plan/plans/roadmap.md).

## TL;DR

Expand Down Expand Up @@ -166,3 +168,4 @@ the same PR, and the full-scope design intent stays here in `plan/`.
| [plan/designs/docs_site_design.md](plan/designs/docs_site_design.md) | Public docs site: in-repo Next.js/MDX static module + same-PR truthfulness contract (D66) |
| [decisions.md](decisions.md) | Architecture decision log (D1–D66) with rationale |
| [questions.md](questions.md) | Open questions to resolve before building |
| [RELEASING.md](RELEASING.md) | Maintainer setup, release procedure, and public-artifact verification |
96 changes: 96 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Releasing RememberStack

The `Release` workflow publishes one version to PyPI and GHCR, then creates a GitHub release
containing the Python distributions, the same version-pinned `compose.yaml`, and `.env.example`.
It accepts only tags exactly matching `vMAJOR.MINOR.PATCH`.

## One-time owner setup

Complete these steps in order:

1. Finish the focused name clearance and put the bounded CLA in place. These are governance
gates, not release-workflow features.
2. Confirm the GitHub repository is `writeitai/remember-stack`, then update each existing clone:

```bash
git remote set-url origin git@github.com:writeitai/remember-stack.git
```

The readable hyphen belongs only to repository and container URLs; the product remains
RememberStack and the Python distribution/import remain `rememberstack`. GitHub redirects
ordinary repository and Git traffic after a rename, but the final name must be in place before
configuring PyPI because the trusted identity includes the repository name.
3. In the GitHub repository, create an environment named `pypi`. Add yourself as a required
reviewer so a tag cannot publish to PyPI without an explicit approval.
4. Create a PyPI account, enable two-factor authentication, and add a
[pending Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
with these exact values:

| Field | Value |
|---|---|
| PyPI project name | `rememberstack` |
| GitHub owner | `writeitai` |
| Repository | `remember-stack` |
| Workflow | `release.yml` |
| Environment | `pypi` |

A pending publisher does not reserve the PyPI name. Configure it only after the repository
rename and publish promptly once the release gates are clear.
5. Protect tags matching `v*` so only maintainers can create or update release tags.

No PyPI password or long-lived API token belongs in GitHub secrets. The workflow requests a
short-lived OpenID Connect credential and grants `id-token: write` only to the PyPI job.

## Cutting a release

Prepare a normal pull request that updates both `project.version` in `pyproject.toml` and the
GHCR tag in `compose.yaml`. Update release-facing documentation in the same pull request. The
contract check rejects drift:

```bash
uv run python scripts/check_release_contract.py --tag v0.1.0
```

After that pull request is merged and `main` is green, tag its exact merge commit:

```bash
git switch main
git pull --ff-only
git tag -a v0.1.0 -m "RememberStack 0.1.0"
git push origin v0.1.0
```

The workflow validates the tag, runs the release test suite, builds the wheel and source
distribution, and publishes `rememberstack==0.1.0` plus
`ghcr.io/writeitai/remember-stack:0.1.0`. It creates the GitHub release only after both
registries accept their artifact.

PyPI and GHCR do not support an atomic cross-registry transaction. Never reuse a published
version after a partial failure: fix the cause, complete the missing publish when safe, or cut the
next patch version.

## First-release GHCR step

The first container package may be private. After the first successful image push, open the
`remember-stack` package settings in GitHub and change its visibility to **public** so anonymous
Compose pulls work. GitHub warns that a public package cannot be made private again.

The image carries standard OCI source labels generated from the repository metadata, which links
the package back to this repository. Docker Hub is intentionally not a second publication target.

## Verify the public artifacts

Run these checks from a clean machine or temporary directory:

```bash
uvx --from rememberstack==0.1.0 remember --version
docker pull ghcr.io/writeitai/remember-stack:0.1.0
gh release download v0.1.0 --repo writeitai/remember-stack \
--pattern compose.yaml --pattern .env.example
cp .env.example .env
docker compose --env-file .env up --no-build --pull always --detach --wait
curl --fail http://localhost:8000/healthz
docker compose --env-file .env down --volumes
```

The final command deletes the disposable verification deployment and its volumes.
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ x-app: &app
build:
context: .
dockerfile: Dockerfile
image: rememberstack:local
image: ghcr.io/writeitai/remember-stack:0.1.0
environment:
REMEMBERSTACK_DATABASE_URL: postgresql+psycopg://${REMEMBERSTACK_POSTGRES_USER}:${REMEMBERSTACK_POSTGRES_PASSWORD}@postgres:5432/${REMEMBERSTACK_POSTGRES_DB}
REMEMBERSTACK_MINIO_ENDPOINT_URL: http://minio:9000
Expand Down
Loading
Loading