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
16 changes: 14 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ REMEMBERSTACK_SELFHOST_DEPLOYMENT_SLUG=local
REMEMBERSTACK_SELFHOST_DEPLOYMENT_NAME=Local memory
REMEMBERSTACK_SELFHOST_API_PORT=8000

# Required to start the provider adapter. The short Markdown smoke path makes no
# provider request; replace this value before processing a real corpus.
# Required to start the provider adapter. Replace this value before processing
# a corpus; the complete Compose pipeline makes extraction and embedding calls.
REMEMBERSTACK_OPENROUTER_API_KEY=replace-before-real-use
# Optional exact OpenRouter embedding-provider slug; unset keeps automatic routing.
# REMEMBERSTACK_OPENROUTER_EMBEDDING_PROVIDER=nebius
# Optional global reasoning effort for every chat-generation call. Allowed:
# none, minimal, low, medium, high, xhigh, max. Unset uses the model default;
# "none" reduces extraction latency but can reduce adjudication quality, and
# models that require reasoning may reject it.
# REMEMBERSTACK_OPENROUTER_REASONING_EFFORT=none

# Optional benchmark/deployment model overrides. Keep explicit model IDs for a
# reproducible run; do not use a rotating router such as openrouter/free.
# REMEMBERSTACK_E2_EXTRACT_MODEL=nvidia/nemotron-3-super-120b-a12b:free
# REMEMBERSTACK_E3_NORMALIZE_MODEL=nvidia/nemotron-3-super-120b-a12b:free
58 changes: 47 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,28 +53,60 @@ jobs:
raise RuntimeError("MinIO replaced an immutable object key")
PY

- name: Ingest and observe the E0 chain
- name: Prove the zero-cost full pipeline
run: |
curl --fail --data-binary $'# Hello\n\nCompose smoke.\n' \
'http://localhost:8000/ingest?filename=smoke.md&mime=text%2Fmarkdown'
running_services="$(
docker compose --env-file .env.example ps --status running --services
)"
for service in \
api worker-convert worker-structure worker-chunk worker-embed-chunk \
worker-extract-claims worker-normalize-relations \
worker-adjudicate-supersession worker-embed-claim worker-reconcile \
worker-label-relation; do
printf '%s\n' "$running_services" | grep -qx "$service"
done
ingested="$(
curl --fail --header 'Content-Type: application/octet-stream' \
--data-binary ' ' \
'http://localhost:8000/ingest?filename=empty.md&mime=text%2Fmarkdown'
)"
version_id="$(
printf '%s' "$ingested" |
docker compose --env-file .env.example exec -T api \
python -c 'import json, sys; print(json.load(sys.stdin)["version_id"])'
)"
observed=''
for attempt in {1..30}; do
for attempt in {1..60}; do
observed="$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
"SELECT count(*) FILTER (WHERE stage='convert' AND status='succeeded'), count(*) FILTER (WHERE stage='structure' AND status='succeeded'), count(*) FILTER (WHERE stage='chunk' AND status='pending') FROM processing_state")"
if [ "$observed" = '1|1|1' ]; then
"SELECT count(*), count(*) FILTER (WHERE status='succeeded'), count(DISTINCT stage) FROM processing_state WHERE target_id='$version_id'")"
if [ "$observed" = '10|10|10' ]; then
break
fi
sleep 1
done
test "$observed" = '1|1|1'
test "$observed" = '10|10|10'
test "$(docker compose --env-file .env.example exec -T postgres \
psql -U rememberstack -d rememberstack -Atc 'SELECT count(*) FROM cost_ledger')" = '0'
psql -U rememberstack -d rememberstack -Atc \
'SELECT count(*) FROM cost_ledger')" = '0'
docker compose --env-file .env.example --profile operations \
run --rm projections
readiness="$(
curl --fail --header 'Content-Type: application/json' \
--data "[\"$version_id\"]" \
'http://localhost:8000/readiness?require_projections=true'
)"
printf '%s' "$readiness" |
docker compose --env-file .env.example exec -T api python -c \
'import json, sys; report = json.load(sys.stdin); assert report["ready"]; assert all(item["ready"] for item in report["projections"])'

- name: Stage an upgrade from the prior schema
run: |
docker compose --env-file .env.example stop \
api worker-convert worker-structure
api worker-convert worker-structure worker-chunk worker-embed-chunk \
worker-extract-claims worker-normalize-relations \
worker-adjudicate-supersession worker-embed-claim worker-reconcile \
worker-label-relation
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 \
Expand Down Expand Up @@ -116,7 +148,7 @@ jobs:
docker compose "${compose_args[@]}" ps --status running --services
)"
test -z "$(printf '%s\n' "$running_services" \
| grep -E '^(api|worker-convert|worker-structure)$' || true)"
| grep -E '^(api|worker-(convert|structure|chunk|embed-chunk|extract-claims|normalize-relations|adjudicate-supersession|embed-claim|reconcile|label-relation))$' || true)"
test "$(docker compose "${compose_args[@]}" exec -T postgres \
psql -U rememberstack -d rememberstack -Atc \
'SELECT version_num FROM alembic_version')" = 'p7_02_0016'
Expand All @@ -133,7 +165,11 @@ jobs:
running_services="$(
docker compose "${compose_args[@]}" ps --status running --services
)"
for service in api worker-convert worker-structure; do
for service in \
api worker-convert worker-structure worker-chunk worker-embed-chunk \
worker-extract-claims worker-normalize-relations \
worker-adjudicate-supersession worker-embed-claim worker-reconcile \
worker-label-relation; do
printf '%s\n' "$running_services" | grep -qx "$service"
done
curl --fail http://localhost:8000/healthz
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ WORKDIR /app
COPY pyproject.toml uv.lock README.md LICENSE alembic.ini ./

RUN addgroup --system app \
&& adduser --system --ingroup app app \
&& adduser --system --ingroup app \
--home /var/lib/rememberstack --no-create-home app \
&& mkdir -p /var/lib/rememberstack/forget-manifests \
&& chown -R app:app /var/lib/rememberstack \
&& uv sync --locked --no-dev --extra server --no-install-project
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ at a million documents.
> **Pre-release software.** Phases 0–7 are implemented and tested. The public
> [`v0.1.0`](https://github.com/writeitai/remember-stack/releases/tag/v0.1.0) release is available
> from PyPI and GHCR; release automation, trusted publishing, tag protection, and bounded
> contributor-agreement enforcement are in place. The fresh-deployment Docker Compose skeleton
> contributor-agreement enforcement are in place. The fresh-deployment Docker Compose profile
> 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
> [Self-host deployment](website/src/app/docs/deployment/page.mdx); it composes PostgreSQL,
> MinIO, the API, all ten continuous E/P1 routes, and explicit P2/P3 publication for one
> deployment. It remains pre-release software, not a production rollout. The build follows
> [plan/plans/roadmap.md](plan/plans/roadmap.md).

## TL;DR
Expand Down
69 changes: 38 additions & 31 deletions benchmarks/locomo/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# RS-LoCoMo-v1 setup
# RS-LoCoMo-Full-v1 setup

This directory implements the reviewed `RS-LoCoMo-v1 J@30` protocol. It does not contain the
LoCoMo data and does not auto-download it. The data is CC BY-NC 4.0; confirm the intended use
before obtaining it from the
[official repository](https://github.com/snap-research/locomo/tree/3eb6f2c585f5e1699204e3c3bdf7adc5c28cb376/data).
This directory contains the unshipped full-system LoCoMo adapter. It does not vendor or
auto-download LoCoMo. Supply the exact pinned `locomo10.json` only after confirming its
CC BY-NC 4.0 terms.

Install the repository and the two deterministic-scorer dependencies:
Install the repository plus deterministic scorer dependencies:

```bash
uv sync --extra benchmark
```

The safe first command is local:
The safe first command is local and makes no API or model call:

```bash
uv run --extra benchmark python -m benchmarks.locomo prepare \
Expand All @@ -20,27 +19,35 @@ uv run --extra benchmark python -m benchmarks.locomo prepare \
--output .benchmark-runs/locomo-smoke
```

`prepare` checks the exact pinned SHA, validates the committed eight-question smoke manifest,
renders session Markdown, and writes a call/document plan. It does not contact RememberStack or
an evaluator.

Do not proceed to `ingest`, `answer`, or `judge` until the owner walkthrough in
[`locomo_benchmark_design.md`](../../plan/designs/locomo_benchmark_design.md#12-pre-run-checklist).
Those stages require `--execute` plus exact isolation/readiness acknowledgements and explicit
document, question, call, and shared evaluator-cost ceilings.

Question and call ceilings are run-absolute, not allowances for one sample invocation.
`--max-questions` must therefore cover the complete prepared tier (8 for smoke, 200 for
development, or 1,540 for publication), while reader and judge call ceilings include calls
already checkpointed for earlier samples. The evaluator-cost ceiling likewise covers the shared
reader-plus-judge ledger for the entire run.

The local ledger records provider usage attached to successfully parsed, checkpointed calls.
Provider-billed calls that fail before usable accounting is returned are not reconstructable, and
a process death after a response but before its atomic checkpoint may repeat that one call.
Provider/account hard limits are therefore the hard monetary boundary; the harness ceilings are
additional fail-closed operational guards.

The released Compose profile is not yet a valid target: it wires only `convert` and `structure`,
not the complete claim-indexing path. Use of a complete isolated deployment is a pre-run
prerequisite, not something this harness silently constructs.
The harness validates the pinned bytes, renders session documents, and fingerprints the
eight-question smoke plan. Do not run remote stages until reviewing
[`locomo_benchmark_design.md`](../../plan/designs/locomo_benchmark_design.md).

The stock Compose deployment now includes all ten continuous E/P1 workers. After ingesting one
isolated conversation and waiting for them to settle, publish the aggregate projections once:

```bash
docker compose --profile operations run --rm projections
```

The `answer` command then calls the public readiness endpoint. It refuses to run unless every
requested version completed the exact composed stage generations and both P2/P3 builds began
after that work completed. It also refuses a changed public recipe catalog. There is no manual “index ready”
acknowledgement.

Readiness also records the API process's current non-secret model configuration for operator
review. Those values are not processing-time provenance; freeze one Compose environment for the
run and retain the provider/cost artifacts.

The primary protocol uses a bounded answer agent over normal public recipes, not hard-coded claim
search. Limits are run-absolute: allow up to nine agent calls per selected question and one judge
call per answer. The shared evaluator-cost value is a reported-spend stop threshold: a completed
call can cross it, is recorded, and stops the run. Use the provider account cap as the hard
monetary boundary. If that leaves later questions unanswered, they remain visible as zero-scored
missing records; resuming them requires an explicitly higher threshold.

P3 is built and freshness-checked as part of the ordinary deployment, but the remote recipe
agent has no filesystem mount. This protocol therefore does not attribute answer quality to P3
navigation. A future mount-enabled protocol needs a new fingerprint and name.

No real benchmark has been run as part of the setup implementation.
23 changes: 12 additions & 11 deletions benchmarks/locomo/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Command line for the deliberately staged RS-LoCoMo-v1 harness."""
"""Command line for the deliberately staged full-system LoCoMo harness."""

from __future__ import annotations

Expand Down Expand Up @@ -51,10 +51,9 @@ def main(argv: list[str] | None = None) -> int:
run_dir=args.run,
sample_id=args.sample,
max_questions=args.max_questions,
max_reader_calls=args.max_reader_calls,
max_agent_calls=args.max_agent_calls,
max_evaluator_cost_usd=args.max_evaluator_cost_usd,
execute=args.execute,
index_ready_confirmation=args.confirm_index_ready,
client=client,
provider=provider,
)
Expand Down Expand Up @@ -89,7 +88,7 @@ def _provider() -> OpenRouterModelProvider:


def _positive_decimal(value: str) -> Decimal:
"""Parse one strictly positive CLI monetary ceiling."""
"""Parse one strictly positive reported-spend stop threshold."""
try:
parsed = Decimal(value)
except InvalidOperation as error:
Expand All @@ -104,7 +103,7 @@ def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="python -m benchmarks.locomo",
description=(
"RS-LoCoMo-v1: prepare is local; ingest/answer/judge require "
"RS-LoCoMo-Full-v1: prepare is local; ingest/answer/judge require "
"explicit execution acknowledgements"
),
)
Expand All @@ -128,7 +127,7 @@ def _parser() -> argparse.ArgumentParser:
ingest.add_argument("--confirm-isolated-deployment")

answer = commands.add_parser(
"answer", help="retrieve and call the frozen reader for one sample"
"answer", help="run the bounded public-recipe answer agent for one sample"
)
_run_and_sample(answer)
answer.add_argument(
Expand All @@ -138,19 +137,20 @@ def _parser() -> argparse.ArgumentParser:
help="run-absolute authorization; must cover the prepared tier item count",
)
answer.add_argument(
"--max-reader-calls",
"--max-agent-calls",
type=int,
required=True,
help="run-absolute ceiling over reader calls already recorded plus new calls",
help="run-absolute ceiling over answer-agent model calls; recipe calls"
" have a separate per-question cap",
)
answer.add_argument(
"--max-evaluator-cost-usd",
type=_positive_decimal,
required=True,
help="run-absolute shared reader-plus-judge reported-cost ceiling",
help="run-absolute shared reported-spend stop threshold; use a provider"
" account cap as the hard monetary boundary",
)
answer.add_argument("--execute", action="store_true")
answer.add_argument("--confirm-index-ready")

judge = commands.add_parser(
"judge", help="call the frozen judge for one sample's answers"
Expand All @@ -166,7 +166,8 @@ def _parser() -> argparse.ArgumentParser:
"--max-evaluator-cost-usd",
type=_positive_decimal,
required=True,
help="run-absolute shared reader-plus-judge reported-cost ceiling",
help="run-absolute shared reported-spend stop threshold; use a provider"
" account cap as the hard monetary boundary",
)
judge.add_argument("--execute", action="store_true")

Expand Down
Loading
Loading