Skip to main content
Memory consolidation distills recent journal entries into durable learned-*.md rules. One endpoint forces an immediate run of the consolidation worker; the rest drive the human-in-the-loop review of proposed rule merges — preview a diff, then approve, reject, or explain each proposal. See the Consolidate guide. The scheduled runner ticks every 6h regardless; the trigger endpoint exists so operators can force an immediate pass after curating a crew or auditing new rules.
All endpoints require authentication and workspace scope. The trigger, approve, and reject endpoints require OWNER or ADMIN; the read-only diff and explain endpoints accept MEMBER or higher.

Endpoints


Triggering runs

Force an immediate consolidation pass instead of waiting for the 6h scheduler.

Trigger run

Auth: OWNER or ADMIN only (403 otherwise). Request body (optional):
Body may be empty; the defaults run consolidation across every non-deleted crew in the workspace for the last 24h. Response: 202 Accepted — run started:
Response: 202 Accepted — no summarizer configured, run skipped:
In both cases the journal records a system.consolidation_triggered + system.consolidation_completed pair so the audit trail is complete. Errors:

Proposal review (HITL)

Preview, approve, reject, or explain the rule-merge proposals a consolidation run produces.

Preview proposal diff

Auth: MEMBER or higher (matches Explain — no write authority needed to preview). Request: id is the required proposal path parameter. Workspace context is supplied by the optional X-Workspace-ID header (or the authenticated session). No request body. Returns a 3-line-context unified diff between the current canonical learned-YYYY-MM-DD.md and the file an approve of {id} would land on disk. The post-merge half of the diff is byte-identical to what POST /api/v1/consolidate/proposed/{id}/approve would write (modulo the per-instant Approved at HH:MM:SS line, which races the wall clock between preview and approve by definition). That equality is the load-bearing UX promise — a reviewer can read the diff, click approve, and trust that the committed bytes match what they saw.
Response: 200 OK:
Errors:

Approve proposal

Auth: OWNER or ADMIN only (403 otherwise); workspace context required. The proposal id + caller identity are the entire state machine — any request body is ignored. The handler does a read-only ExplainProposal lookup first; a cross-workspace probe surfaces as 404 (no existence leak) and an already-decided row short-circuits with 409 before any state changes. On success the proposal’s rendered body is appended to the canonical learned-YYYY-MM-DD.md for today (UTC), the row flips to status='approved' with decided_at + decided_by_user_id, the matching inbox row resolves with action='approved', and memory.consolidated lands on the journal. When BlobRoot is wired (see Memory observability), a memory_versions audit row is also recorded against the post-merge canonical file.
Response: 200 OK:
Errors:

Reject proposal

Auth: OWNER or ADMIN only (403 otherwise); workspace context required. Same workspace boundary pattern as approve — the lookup runs first, a cross-workspace probe is 404, an already-decided row is 409, and only then does the row flip to status='rejected'. The inbox item resolves with action='rejected' and the reason is logged at notice level so audit reviews don’t have to JOIN through memory_proposals for the “why”. The .proposed/proposal-*.md file stays on disk for audit; a separate retention sweep removes it. No memory.consolidated journal entry is emitted on reject — the inbox audit trail and the original memory.consolidation_proposed from the seed run carry the lineage. Request body (optional):
Response: 200 OK:
Errors:

Explain proposal

Auth: MEMBER or higher inside the workspace — read-only, no write authority needed to review what was proposed. Request: id is the required proposal path parameter. Workspace context is supplied by the optional X-Workspace-ID header (or the authenticated session). No request body. Returns the full proposal row plus the evidence the summarizer LLM looked at, so the HITL review UI can show “why this rule, given which journal entries”. Cross-workspace probes return 404 (no existence leak). The endpoint is safe to call on already-decided proposals; status, decided_at, and decided_by_user_id carry the resolution. scores is the per-rule ScoreResult map populated by the proposal writer once scoring is wired — proposals from before that landed return a literal {} so clients can blindly Unmarshal without nil-checks.
Response: 200 OK:
Errors:

Side effects

A successful run:
  1. Loads recent journal entries per crew (filtered to candidate types: peer.escalation, summary.generated, keeper.decision, mission.status_change, eval.regression_detected).
  2. Calls the summarizer LLM (Ollama in production).
  3. Appends extracted rules to {memoryRoot}/{crewSlug}/topics/learned-YYYY-MM-DD.md.
  4. Emits memory.consolidated per crew with rules_count and output path in the payload.
  5. Emits system.consolidation_completed once with aggregate crews_run and rules_appended.

Scheduling

Background scheduler: every 6h. Compactor: daily at 03:00 UTC. Both wired at server startup via consolidate.StartBackground.