feat: implement memory and evidence administration with guided repairs
Publish documentation / publish (push) Successful in 1m27s

Add PostgreSQL-backed memory, editable evidence with source review and activation, and human-approved archive repairs across the harness, API, and UI. Include migrations, deployment support, regression coverage, and validation documentation.

Refresh permissions from validated session roles so existing administrator logins can access newly deployed archive management features.
This commit is contained in:
Codex
2026-09-10 10:31:34 +02:00
parent 8fe526dd6e
commit 82e2c91f42
168 changed files with 11914 additions and 1772 deletions
+30 -6
View File
@@ -50,6 +50,10 @@ def _evaluation_workspace_root(cfg) -> Path:
def _requires_candidate_evaluation(cfg) -> bool:
evidence = cfg.evidence
if evidence and evidence.local_archive_root and (
evidence.local_archive_root / "evidence/.local/state.yaml"
).exists():
return False
if evidence is None or evidence.schema_version != 2:
return False
return evidence.source_root is not None or any(
@@ -224,7 +228,8 @@ def _parse_dwh_steps(value: str) -> tuple[str, ...]:
return steps
def run_from_config(config: Path, *, dry_run: bool = False, resume: str | None = None):
def run_from_config(config: Path, *, dry_run: bool = False, resume: str | None = None,
local_snapshot: Path | None = None):
from tht.adapters.factory import build_vector_store
from tht.cli.schema_cmd import _load_config_or_exit
from tht.cli.vector_cmd import make_embedder
@@ -239,8 +244,11 @@ def run_from_config(config: Path, *, dry_run: bool = False, resume: str | None =
corpus_root = cfg.paths.artifacts.parent / "corpus"
vector_store = build_vector_store(cfg, require_write=True)
embedder = make_embedder(cfg.embeddings)
from tht.evidence.adapters import FilesystemEvidenceSource
sources = [FilesystemEvidenceSource(local_snapshot, patterns=("curated/**/*.md",))] \
if local_snapshot is not None else build_sources(cfg.evidence)
pipeline = build_preprocessing_pipeline(
store=CorpusStore(corpus_root), sources=build_sources(cfg.evidence),
store=CorpusStore(corpus_root), sources=sources,
embedder=embedder,
vector_store=vector_store,
embedding_id=cfg.embeddings.id or f"ollama/{cfg.embeddings.model}",
@@ -376,9 +384,17 @@ def evidence_cmd(
dry_run: bool = typer.Option(False, "--dry-run"),
resume: str | None = typer.Option(None, "--resume"),
json_output: bool = typer.Option(False, "--json"),
consolidate: bool = typer.Option(False, "--consolidate"),
) -> None:
if action is not None and action != "gc":
raise typer.BadParameter("only the optional 'gc' action is supported")
if consolidate and (dry_run or resume is not None or action is not None):
message = "Consolidation cannot be combined with dry-run, resume or gc"
if json_output:
typer.echo(json.dumps({"status": "failed", "code": "invalid_consolidation", "error": message}))
else:
typer.secho(message, fg=typer.colors.RED, err=True)
raise typer.Exit(code=2)
if action == "gc":
try:
payload = gc_from_config(config, dry_run=dry_run)
@@ -406,21 +422,29 @@ def evidence_cmd(
typer.secho("ERRORE: resume requires a preprocessing run id", fg=typer.colors.RED, err=True)
raise typer.Exit(code=2)
try:
result = run_from_config(config, dry_run=dry_run, resume=resume)
except Exception: # noqa: BLE001
if consolidate:
from tht.evidence.administration import consolidate_from_config
result = consolidate_from_config(config)
else:
result = run_from_config(config, dry_run=dry_run, resume=resume)
except Exception as error: # noqa: BLE001
from tht.evidence.administration import ConsolidationError
detail = str(error) if isinstance(error, ConsolidationError) else "preprocessing failed"
payload = {"status": "failed"}
if isinstance(error, ConsolidationError):
payload["saved"] = error.saved
if json_output:
typer.echo(json.dumps(
_evidence_json_payload(
cfg,
payload,
code="preprocessing_failed",
error="preprocessing failed",
error=detail,
),
sort_keys=True,
))
else:
typer.secho("ERRORE: preprocessing failed", fg=typer.colors.RED, err=True)
typer.secho(f"ERRORE: {detail}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1) from None
payload = result.model_dump(mode="json")
if payload.get("status") != "succeeded":