# RecallNest HTTP API Reference

> HTTP API 文档：所有端点的请求/响应格式。服务默认监听 `127.0.0.1:4318`。

Base URL: `http://localhost:4318`

All endpoints accept and return JSON. Set `Content-Type: application/json` for POST requests.

Workflow observation endpoints use a dedicated append-only store under `data/workflow-observations`. They do not write into the regular memory index or durable memory categories.

---

## Health Check

```
GET /v1/health
```

Returns server status.

**Response:**

```json
{
  "status": "ok",
  "version": "1.0.0",
  "totalMemories": 1247,
  "timestamp": "2026-03-15T10:00:00.000Z"
}
```

---

## Recall (Quick Search)

```
POST /v1/recall
```

Semantic search across scoped memories. Best for quick, conversational lookups when the caller already knows the active scope or session.

> 主动回忆：用关键词搜索相关记忆，返回按相关度排序的结果。默认需要显式 `scope`、`sessionId`，或环境里的默认 scope；只有 `allScopes=true` 才会跨 scope 搜索。

**Request:**

```json
{
  "query": "Docker bot debugging",
  "scope": "project:docker-bot",
  "limit": 5,
  "minScore": 0.5
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `query` | string | yes | — | Search query (2-3 key nouns work best) |
| `scope` | string | no | — | Explicit shared scope for the search |
| `sessionId` | string | no | — | Session identifier used to infer `session:{id}` scope |
| `allScopes` | boolean | no | `false` | Explicitly allow cross-scope search |
| `limit` | number | no | 5 | Max results (1-20) |
| `minScore` | number | no | 0 | Minimum relevance score (0-1). 0 = no filter |
| `category` | string | no | — | Filter: `profile`, `preferences`, `entities`, `events`, `cases`, `patterns` |
| `profile` | string | no | `"default"` | Retrieval profile: `default`, `writing`, `debug`, `fact-check` |

**Response:**

```json
{
  "results": [
    {
      "id": "a1b2c3d4",
      "text": "Docker bot crash troubleshooting: check logs first with docker logs...",
      "category": "cases",
      "tier": "core",
      "source": "cc",
      "scope": "cc:a1b2c3d4",
      "score": 0.87,
      "date": "2026-03-04",
      "boundary": {
        "layer": "evidence",
        "authority": "transcript-ingest",
        "conflictPolicy": "append-only",
        "originalCategory": "cases"
      },
      "canonicalKey": null,
      "promotedFrom": null
    }
  ],
  "query": "Docker bot debugging",
  "profile": "default",
  "totalMemories": 1247
}
```

---

## Auto Recall (Resume + Focused Search)

```
POST /v1/auto-recall
```

Compose startup continuity context and then run a focused scoped search in one call. Best for agent frameworks that want a single "recall at task start" hook without exposing extra MCP tools.

> 自动回忆：先做 `resume_context` 式的连续性恢复，再在同一 scope 下补一轮 focused search。适合 agent SDK / HTTP 集成在每次任务开始时直接调用。

**Request:**

```json
{
  "message": "RecallNest continuity",
  "scope": "project:recallnest",
  "limit": 3,
  "limitPerSection": 3,
  "includeLatestCheckpoint": true,
  "profile": "default"
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `message` | string | yes | — | Raw user message or task-start query |
| `task` | string | no | `message` | Optional broader task hint for resume composition |
| `scope` | string | no | — | Explicit shared scope |
| `sessionId` | string | no | — | Session identifier used to recover the latest checkpoint and infer scope |
| `allScopes` | boolean | no | `false` | Explicitly allow cross-scope focused search |
| `limit` | number | no | 5 | Max focused search results (1-20) |
| `limitPerSection` | number | no | 3 | Max items per resume section (1-6) |
| `includeLatestCheckpoint` | boolean | no | `true` | Whether to include the latest checkpoint in the resume payload |
| `category` | string | no | — | Optional focused search category filter |
| `profile` | string | no | `"default"` | Retrieval profile: `default`, `writing`, `debug`, `fact-check` |

**Response:**

```json
{
  "mode": "resume+search",
  "query": "RecallNest continuity",
  "profile": "default",
  "resolvedScope": "project:recallnest",
  "searchSkippedReason": null,
  "resume": {
    "summary": "Loaded 3 stable context item(s), 2 pattern(s), and 1 case(s). Latest checkpoint from codex-2026-03-16-001 on 2026-03-16: Implement startup continuity for fresh windows.",
    "resolvedScope": "project:recallnest",
    "stableContext": [
      "Entity: RecallNest continuity revolves around three primitives."
    ],
    "relevantPatterns": [
      "At task start, run search_memory before coding."
    ],
    "recentCases": [
      "Case: RecallNest scope fallback cleanup"
    ],
    "latestCheckpoint": {
      "sessionId": "codex-2026-03-16-001",
      "resolvedScope": "project:recallnest",
      "summary": "Implement startup continuity for fresh windows",
      "updatedAt": "2026-03-16T05:00:00.000Z"
    },
    "generatedAt": "2026-03-16T05:10:00.000Z"
  },
  "results": [
    {
      "id": "a1b2c3d4",
      "text": "RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, resume_context.",
      "category": "entities",
      "scope": "project:recallnest",
      "score": 0.91
    }
  ],
  "count": 1
}
```

If no explicit or inferred scope is available, `mode` becomes `resume-only`, `results` is empty, and `searchSkippedReason` explains why focused search was skipped.

---

## Store

```
POST /v1/store
```

Store a new durable memory entry.

> 存入长期记忆：适合 profile、preferences、entities、cases、patterns 这类跨窗口仍然有价值的信息。

**Request:**

```json
{
  "text": "User prefers code changes to be committed and pushed immediately",
  "category": "preferences",
  "source": "agent",
  "tags": ["workflow", "git"],
  "importance": 0.85
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `text` | string | yes | — | Memory content |
| `category` | string | no | `"events"` | One of: `profile`, `preferences`, `entities`, `events`, `cases`, `patterns` |
| `source` | string | no | `"manual"` | One of: `manual`, `agent`, `api` |
| `scope` | string | yes | — | Required explicit scope such as `project:recallnest` or `session:abc123` |
| `importance` | number | no | 0.7 | Importance score (0-1), affects decay and ranking |
| `tags` | string[] | no | `[]` | Optional tags, max 8 items |
| `canonicalKey` | string | no | derived from content | Optional stable key for dedupe/update semantics |

**Response:**

```json
{
  "id": "e5f6g7h8-...",
  "stored": true,
  "disposition": "stored",
  "storedAt": "2026-03-16T02:10:00.000Z",
  "category": "preferences",
  "scope": "memory:agent",
  "canonicalKey": "preferences:user-prefers-code-changes-to-be-committed-and-pushed-immediately"
}
```

If the same `canonicalKey` is already occupied by a different durable category, RecallNest returns `disposition = "conflict"` plus a `conflictId` instead of silently creating a second durable owner.

For slot-aware preferences, RecallNest now derives structured keys instead of collapsing everything into a broader same-topic key. Examples:

- atomic brand-item preference: `我喜欢吃麦当劳的麦辣鸡翅` -> `preferences:brand-item:麦当劳:麦辣鸡翅`
- reply-style preference: `User prefers concise, direct replies.` -> `preferences:reply-style:concise:direct`
- tool-choice preference: `Uses Bun over Node.` -> `preferences:tool-choice:bun:over:node`

---

## Capture (Structured Batch Write)

```
POST /v1/capture
```

Store multiple structured memories in one request.

> 批量结构化写入：适合上层 agent 一次性提炼多条 durable memory 后统一写入。

**Request:**

```json
{
  "scope": "agent:codex",
  "source": "agent",
  "defaultImportance": 0.7,
  "memories": [
    {
      "text": "RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI",
      "category": "entities"
    },
    {
      "text": "Use search_memory at task start",
      "category": "patterns",
      "importance": 0.9,
      "tags": ["workflow", "memory"]
    }
  ]
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `scope` | string | no | — | Default scope applied to items that do not specify one |
| `source` | string | no | `"agent"` | Default source for items that do not specify one |
| `defaultImportance` | number | no | 0.7 | Default importance for items that do not specify one |
| `memories` | array | yes | — | 1-20 structured memory items |

Each memory item supports:

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `text` | string | yes | — | Memory content |
| `category` | string | no | `"events"` | Durable memory category |
| `importance` | number | no | envelope default | Optional per-item override |
| `scope` | string | no | envelope scope | Optional per-item override; every stored memory still needs a scope via the envelope or item override |
| `source` | string | no | envelope source | Optional per-item override |
| `tags` | string[] | no | `[]` | Optional tags |
| `canonicalKey` | string | no | derived from content | Optional stable key for dedupe/update semantics |

**Response:**

```json
{
  "stored": 2,
  "memories": [
    {
      "id": "a1b2c3d4-...",
      "text": "RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI",
      "category": "entities",
      "scope": "agent:codex",
      "source": "agent",
      "storedAt": "2026-03-16T02:11:00.000Z",
      "disposition": "stored",
      "canonicalKey": "entities:recallnest-is-the-shared-memory-layer-for-claude-code-codex-and-gemini-cli"
    }
  ]
}
```

Each item may also carry `disposition = "conflict"` and a `conflictId` when batch input reuses an existing `canonicalKey` under a different durable category.

---

## Pattern (Structured Workflow Capture)

```
POST /v1/pattern
```

Store a reusable workflow as durable `patterns` memory.

> 专门写 workflow pattern：适合把“什么时候用、怎么做、做完得到什么”这种可复用流程沉淀成高质量 `patterns` 记忆。

**Request:**

```json
{
  "title": "Cross-window continuity handoff",
  "trigger": "When opening a fresh terminal window for the same project",
  "steps": [
    "Call resume_context before coding",
    "Review stable context and latest checkpoint",
    "Save checkpoint_session before leaving the window"
  ],
  "outcome": "The next window recovers decisions and next actions faster",
  "tools": ["resume_context", "checkpoint_session"],
  "importance": 0.9,
  "source": "agent",
  "tags": ["continuity", "handoff"]
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `title` | string | yes | — | Short workflow title |
| `trigger` | string | yes | — | When this workflow should be used |
| `steps` | string[] | yes | — | Ordered workflow steps, 1-8 items |
| `outcome` | string | no | — | Optional expected outcome |
| `tools` | string[] | no | `[]` | Optional tools, commands, or interfaces involved |
| `importance` | number | no | 0.82 | Importance score (0-1) |
| `scope` | string | yes | — | Required explicit scope such as `project:recallnest` or `session:abc123` |
| `source` | string | no | `"agent"` | One of: `manual`, `agent`, `api` |
| `tags` | string[] | no | `[]` | Optional tags; `workflow` and `pattern` are auto-added |
| `canonicalKey` | string | no | derived from title | Optional stable key for dedupe/update semantics |

**Response:**

```json
{
  "id": "f0e1d2c3-...",
  "stored": true,
  "category": "patterns",
  "title": "Cross-window continuity handoff",
  "scope": "memory:agent",
  "tags": ["continuity", "handoff", "workflow", "pattern"],
  "storedAt": "2026-03-16T08:10:00.000Z"
}
```

---

## Case (Structured Problem-Solution Capture)

```
POST /v1/case
```

Store a reusable problem-solution case as durable `cases` memory.

> 专门写 problem-solution case：适合把“遇到什么问题、怎么解决、最后效果怎样”这种可复用经验沉淀成高质量 `cases` 记忆。

**Request:**

```json
{
  "title": "RecallNest sparse startup context cleanup",
  "problem": "resume_context was returning noisy transcript fragments instead of a clean project handoff",
  "context": "This happened in a fresh RecallNest window after continuity setup was already in place",
  "solutionSteps": [
    "Filter low-signal transcript fragments from stable recall",
    "Backfill stable context from checkpoint focus, summary, and decisions",
    "Use a lightweight task focus fallback only when no checkpoint-backed context is available"
  ],
  "outcome": "Fresh windows now recover cleaner RecallNest continuity context",
  "tools": ["resume_context", "checkpoint_session"],
  "importance": 0.9,
  "source": "agent",
  "tags": ["continuity", "startup-context"]
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `title` | string | yes | — | Short case title |
| `problem` | string | yes | — | What problem happened |
| `context` | string | no | — | Optional context or preconditions |
| `solutionSteps` | string[] | yes | — | Ordered solution steps, 1-8 items |
| `outcome` | string | no | — | Optional result or resolution |
| `tools` | string[] | no | `[]` | Optional tools, commands, or interfaces involved |
| `importance` | number | no | 0.84 | Importance score (0-1) |
| `scope` | string | yes | — | Required explicit scope such as `project:recallnest` or `session:abc123` |
| `source` | string | no | `"agent"` | One of: `manual`, `agent`, `api` |
| `tags` | string[] | no | `[]` | Optional tags; `case` and `solution` are auto-added |
| `canonicalKey` | string | no | derived from title | Optional stable key for dedupe/update semantics |

---

## Promote (Evidence -> Durable)

```
POST /v1/promote
```

Promote an evidence memory into durable memory.

> 显式升级：把 transcript / import evidence 中的高价值片段升级成 durable memory，而不是让 raw ingest 直接冒充长期事实。

**Request:**

```json
{
  "memoryId": "12345678-1234-1234-1234-123456789abc",
  "text": "User prefers concise, direct replies.",
  "category": "preferences",
  "canonicalKey": "user-reply-style",
  "tags": ["writing"]
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `memoryId` | string | yes | — | Evidence memory ID or unique prefix |
| `text` | string | no | source text | Optional cleaned durable text |
| `category` | string | no | inferred from source | Target durable category |
| `importance` | number | no | 0.78 | Importance score (0-1) |
| `scope` | string | yes | — | Required explicit target scope such as `project:recallnest` or `session:abc123` |
| `source` | string | no | `"agent"` | Promotion source label |
| `tags` | string[] | no | `[]` | Optional tags |
| `canonicalKey` | string | no | derived from text | Optional stable key for dedupe/update semantics |

**Response:**

```json
{
  "id": "c0ffee00-...",
  "stored": true,
  "disposition": "promoted",
  "category": "preferences",
  "scope": "memory:agent",
  "sourceMemoryId": "12345678-1234-1234-1234-123456789abc",
  "sourceCategory": "events",
  "storedAt": "2026-03-16T02:20:00.000Z",
  "canonicalKey": "user-reply-style"
}
```

When a promotion would overwrite an existing durable memory with the same `canonicalKey`, RecallNest now creates an open conflict candidate instead of silently applying `latest-wins`.

The same slot-aware key inference also applies during promotion when `category = "preferences"` and the text resolves to a supported preference slot such as an atomic brand-item preference, a reply-style preference, or a tool-choice preference.

If the promotion resolves to the same preference slot as the existing durable owner, RecallNest now collapses it onto that owner instead of opening a conflict. This currently applies to slot-aware brand-item keys such as `preferences:brand-item:麦当劳:麦辣鸡翅`, reply-style keys such as `preferences:reply-style:concise:direct`, and tool-choice keys such as `preferences:tool-choice:bun:over:node`.

```json
{
  "id": "baddcafe-...",
  "stored": false,
  "disposition": "conflict",
  "category": "preferences",
  "scope": "memory:agent",
  "sourceMemoryId": "12345678-1234-1234-1234-123456789abc",
  "sourceCategory": "events",
  "storedAt": "2026-03-16T02:20:00.000Z",
  "canonicalKey": "user-reply-style",
  "conflictId": "4f71a1f1-..."
}
```

Notes:

- `disposition = "promoted"` means a new durable memory was written
- `disposition = "deduped"` means the durable text already existed under the same `canonicalKey`
- `disposition = "conflict"` means RecallNest kept the existing durable memory and opened a review item

---

## Conflicts

### List / Inspect Conflicts

```
GET /v1/conflicts
GET /v1/conflicts?status=open&canonicalKey=user-reply-style&limit=10
GET /v1/conflicts?attention=stale&limit=10
GET /v1/conflicts?groupBy=cluster&attention=resolved&limit=10
GET /v1/conflicts?conflictId=4f71a1f1-...
```

List recent conflict candidates or fetch a single conflict record.

**List response:**

```json
{
  "groupBy": "record",
  "conflicts": [
    {
      "conflictId": "4f71a1f1-...",
      "status": "open",
      "reason": "promotion_conflicts_with_existing_durable",
      "canonicalKey": "user-reply-style",
      "category": "preferences",
      "reopenCount": 1,
      "lastReopenedAt": "2026-03-16T05:20:00.000Z",
      "lifecycle": {
        "attention": "aging",
        "openAgeDays": 1,
        "reopenCount": 1,
        "isOpen": true,
        "needsAttention": false
      },
      "advice": {
        "suggestedResolution": "keep_existing",
        "confidence": "medium",
        "similarity": 0.84,
        "clusterKey": "user-reply-style::promotion_conflicts_with_existing_durable::preferences",
        "clusterLabel": "preferences / user-reply-style",
        "reasons": [
          "Existing durable text already looks like the tighter rewrite."
        ]
      },
      "existing": {
        "memoryId": "baddcafe-...",
        "text": "User prefers concise, direct replies."
      },
      "incoming": {
        "sourceMemoryId": "12345678-1234-1234-1234-123456789abc",
        "text": "User prefers colloquial writing that stays grounded and non-salesy."
      }
    }
  ],
  "count": 1
}
```

**Cluster response:**

```json
{
  "groupBy": "cluster",
  "clusters": [
    {
      "clusterKey": "user-reply-style::promotion_conflicts_with_existing_durable::preferences",
      "clusterLabel": "preferences / user-reply-style",
      "canonicalKey": "user-reply-style",
      "category": "preferences",
      "reason": "promotion_conflicts_with_existing_durable",
      "totalCount": 3,
      "openCount": 1,
      "latestUpdatedAt": "2026-03-16T05:20:00.000Z",
      "latestConflictId": "4f71a1f1-...",
      "attentionCounts": {
        "fresh": 0,
        "aging": 1,
        "stale": 0,
        "escalated": 0,
        "resolved": 2
      },
      "suggestedResolution": "keep_existing",
      "confidence": "medium"
    }
  ],
  "count": 1
}
```

Query parameters:

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Optional raw status filter: `open`, `accepted-incoming`, `kept-existing` |
| `attention` | string | Optional lifecycle filter: `fresh`, `aging`, `stale`, `escalated`, `resolved` |
| `groupBy` | string | Optional output mode: `record` (default) or `cluster` |
| `canonicalKey` | string | Optional canonical key filter |
| `limit` | number | Max results, default `20` |
| `conflictId` | string | Fetch a single conflict by full ID or unique prefix |

`advice.mergeSuggestion` is optional. It appears only when RecallNest can derive a conservative merged wording suggestion for `manual_review` conflicts.

**Single conflict response:**

```json
{
  "conflict": {
    "conflictId": "4f71a1f1-...",
    "status": "open",
    "canonicalKey": "user-reply-style",
    "category": "preferences",
    "lifecycle": {
      "attention": "aging",
      "openAgeDays": 1,
      "reopenCount": 1,
      "isOpen": true,
      "needsAttention": false
    },
    "advice": {
      "suggestedResolution": "keep_existing",
      "confidence": "medium",
      "similarity": 0.84,
      "clusterKey": "user-reply-style::promotion_conflicts_with_existing_durable::preferences",
      "clusterLabel": "preferences / user-reply-style",
      "reasons": [
        "Existing durable text already looks like the tighter rewrite."
      ]
    }
  }
}
```

### Resolve Conflict

```
POST /v1/conflicts/resolve
```

Resolve an open conflict candidate by keeping the existing durable memory, accepting the incoming promoted text, or merging the two into a conservative durable rewrite.

**Request:**

```json
{
  "conflictId": "4f71a1f1-...",
  "resolution": "accept_incoming",
  "notes": "Incoming wording is cleaner."
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `conflictId` | string | yes | Conflict candidate ID |
| `resolution` | string | yes | `accept_incoming`, `keep_existing`, or `merge` |
| `mergedText` | string | no | Optional merged durable wording when `resolution=merge`; otherwise RecallNest uses `advice.mergeSuggestion` when available |
| `notes` | string | no | Optional operator notes |

**Response:**

```json
{
  "conflictId": "4f71a1f1-...",
  "status": "accepted-incoming",
  "updatedAt": "2026-03-16T05:00:00.000Z",
  "resolvedAt": "2026-03-16T05:00:00.000Z",
  "updatedMemoryId": "baddcafe-..."
}
```

If you keep the existing durable memory, `updatedMemoryId` is omitted and the conflict status becomes `kept-existing`.
If you resolve with `merge`, the conflict status becomes `merged` and `updatedMemoryId` points to the rewritten durable memory that kept the same canonical owner.

### Conflict Audit

```
GET /v1/conflicts/audit
GET /v1/conflicts/audit?limit=100&top=5
GET /v1/conflicts/audit?canonicalKey=user-reply-style
```

Generate a terminal-friendly audit summary so stale or escalated clusters can be reviewed first.

**Response:**

```json
{
  "totalConflicts": 7,
  "totalClusters": 3,
  "openConflicts": 4,
  "openClusters": 2,
  "attentionCounts": {
    "fresh": 1,
    "aging": 1,
    "stale": 1,
    "escalated": 1,
    "resolved": 3
  },
  "priorityClusters": [
    {
      "clusterKey": "user-reply-style::promotion_conflicts_with_existing_durable::preferences",
      "clusterLabel": "preferences / user-reply-style",
      "canonicalKey": "user-reply-style",
      "category": "preferences",
      "reason": "promotion_conflicts_with_existing_durable",
      "totalCount": 4,
      "openCount": 2,
      "latestUpdatedAt": "2026-03-16T05:20:00.000Z",
      "latestConflictId": "4f71a1f1-...",
      "attention": "escalated",
      "suggestedResolution": "keep_existing",
      "confidence": "medium"
    }
  ],
  "suggestedActions": [
    "Resolve 1 escalated cluster(s) first."
  ]
}
```

Query parameters:

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Optional raw status filter: `open`, `accepted-incoming`, `kept-existing` |
| `canonicalKey` | string | Optional canonical key filter |
| `limit` | number | Max records to scan, default `100` |
| `top` | number | Max priority clusters to return, default `5` |

### Escalate Conflicts

```
POST /v1/conflicts/escalate
```

Preview or apply escalation metadata for `stale` / `escalated` open conflicts.

**Request:**

```json
{
  "attention": "stale",
  "limit": 100,
  "top": 10,
  "apply": false,
  "notes": "nightly escalation audit"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `attention` | string | no | `stale` or `escalated`, default `stale` |
| `canonicalKey` | string | no | Optional canonical key filter |
| `limit` | number | no | Max open conflicts to scan, default `100` |
| `top` | number | no | Max eligible conflicts to include, default `10` |
| `apply` | boolean | no | When `false`, preview only. When `true`, writes escalation metadata |
| `notes` | string | no | Optional operator note stored on escalated conflicts |

**Response:**

```json
{
  "apply": false,
  "scanned": 12,
  "eligible": 2,
  "escalated": 0,
  "skipped": 0,
  "items": [
    {
      "conflictId": "4f71a1f1-...",
      "canonicalKey": "user-reply-style",
      "attention": "stale",
      "openAgeDays": 4,
      "reopenCount": 1,
      "escalationCount": 0,
      "suggestedResolution": "manual_review",
      "confidence": "high",
      "action": "pending",
      "clusterKey": "user-reply-style::promotion_conflicts_with_existing_durable::preferences"
    }
  ]
}
```

---

## Checkpoint (Save Current Work State)

```
POST /v1/checkpoint
```

Store a session checkpoint outside the durable memory index.

> 保存当前工作状态：适合跨窗口延续任务，但不应该混入长期 durable memory。

**Request:**

```json
{
  "sessionId": "codex-2026-03-16-001",
  "scope": "agent:codex",
  "summary": "Implement session checkpoint storage",
  "decisions": ["Keep checkpoints out of LanceDB"],
  "openLoops": ["Need resume_context next"],
  "nextActions": ["Add checkpoint MCP tool"],
  "entities": ["RecallNest", "Codex"],
  "files": ["src/session-store.ts"]
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `sessionId` | string | yes | — | Current session identifier |
| `scope` | string | no | `session:{sessionId}` | Optional shared scope |
| `summary` | string | yes | — | Compact summary of the current work state |
| `task` | string | no | — | Optional task label |
| `decisions` | string[] | no | `[]` | Key decisions already made |
| `openLoops` | string[] | no | `[]` | Unresolved questions or pending items |
| `nextActions` | string[] | no | `[]` | Next actions to take |
| `entities` | string[] | no | `[]` | Relevant projects, tools, or people |
| `files` | string[] | no | `[]` | Relevant files or paths |
| `updatedAt` | string | no | `now()` | Optional ISO timestamp override |

RecallNest sanitizes repo-state text such as `git status`, modified-file lists, and untracked-file notes out of saved checkpoint content. Verify volatile repo state locally in the next window instead of relying on checkpoint text.
Managed MCP / HTTP checkpoint calls now also append a dedicated workflow observation automatically: normal saves record `success`, while repo-state sanitization records `corrected` with signal `repo-state-sanitized`.

**Response:**

```json
{
  "sessionId": "codex-2026-03-16-001",
  "scope": "agent:codex",
  "summary": "Implement session checkpoint storage",
  "decisions": ["Keep checkpoints out of LanceDB"],
  "openLoops": ["Need resume_context next"],
  "nextActions": ["Add checkpoint MCP tool"],
  "entities": ["RecallNest", "Codex"],
  "files": ["src/session-store.ts"],
  "updatedAt": "2026-03-16T09:30:00.000Z"
}
```

---

## Resume (Compose Startup Context)

```
POST /v1/resume
```

Compose startup context for a fresh window by combining stable durable memory, relevant patterns and cases, plus the latest checkpoint for a session or scope.

> 为新窗口编排上下文：返回的是组合后的连续性上下文，不是原始检索结果堆积。

Managed MCP / HTTP resume calls now also append a dedicated `resume_context` workflow observation with source `managed`; this stays in the workflow observation store and does not enter durable recall.

**Request:**

```json
{
  "task": "Implement startup continuity for fresh windows",
  "scope": "agent:codex",
  "sessionId": "codex-2026-03-16-001",
  "limitPerSection": 3,
  "includeLatestCheckpoint": true,
  "profile": "default"
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `task` | string | no | — | Optional current task or question to bias recall |
| `scope` | string | no | — | Optional shared scope for project or terminal continuity |
| `sessionId` | string | no | — | Optional session identifier used to recover the latest checkpoint |
| `limitPerSection` | number | no | 3 | Max items returned per section (1-6) |
| `includeLatestCheckpoint` | boolean | no | `true` | Whether to include the latest checkpoint summary |
| `profile` | string | no | `"default"` | Retrieval profile: `default`, `writing`, `debug`, `fact-check` |

**Response:**

```json
{
  "summary": "Loaded 3 stable context item(s), 2 pattern(s), and 1 case(s). Latest checkpoint from codex-2026-03-16-001 on 2026-03-16: Implement startup continuity for fresh windows.",
  "resolvedScope": "agent:codex",
  "stableContext": [
    "Profile: User builds local-first memory systems.",
    "Preference: User prefers concise technical replies.",
    "Entity: RecallNest is shared across Claude Code, Codex, and Gemini CLI."
  ],
  "relevantPatterns": [
    "At task start, run search_memory before coding."
  ],
  "recentCases": [
    "Keep session state in a checkpoint store instead of the durable index."
  ],
  "latestCheckpoint": {
    "sessionId": "codex-2026-03-16-001",
    "resolvedScope": "agent:codex",
    "summary": "Implement startup continuity for fresh windows",
    "updatedAt": "2026-03-16T05:00:00.000Z"
  },
  "generatedAt": "2026-03-16T05:10:00.000Z",
  "profile": "default"
}
```

Use `resolvedScope` as the default scope for follow-up `search_memory`, `brief_memory`, `pin_memory`, and similar recall calls when the client did not already provide a stricter shared scope.

---

## Latest Checkpoint

```
GET /v1/checkpoint/latest
```

Fetch the latest checkpoint for a given `sessionId` or `scope`.

> 获取最新 checkpoint：适合调试当前工作状态是否已写入，也可以和 `POST /v1/resume` 配合使用。

Query params:

| Param | Required | Description |
|-------|----------|-------------|
| `sessionId` | no | Filter by session ID |
| `scope` | no | Filter by shared scope |

**Response:**

```json
{
  "checkpoint": {
    "sessionId": "codex-2026-03-16-001",
    "summary": "Implement session checkpoint storage",
    "updatedAt": "2026-03-16T03:20:00.000Z",
    "checkpointId": "b1c2d3e4-...",
    "resolvedScope": "agent:codex"
  }
}
```

If nothing matches:

```json
{
  "checkpoint": null
}
```

---

## Workflow Observe

```
POST /v1/workflow-observe
```

Store one append-only workflow observation outside durable memory.

> 记录 workflow observation：适合追踪 `resume_context`、`checkpoint_session` 等 primitive 是成功、失败、被纠正，还是被漏掉。

**Request:**

```json
{
  "workflowId": "resume_context",
  "outcome": "missed",
  "summary": "Fresh window skipped continuity recovery before repo exploration.",
  "scope": "project:recallnest",
  "source": "smoke",
  "signal": "missed-startup-trigger",
  "task": "headless continuity smoke",
  "tags": ["continuity"],
  "tools": ["resume_context"]
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `workflowId` | string | yes | — | Workflow primitive id such as `resume_context` |
| `outcome` | string | no | `"success"` | One of: `success`, `failure`, `corrected`, `missed` |
| `summary` | string | yes | — | Short description of what happened |
| `scope` | string | no | `"global"` | Optional scope such as `project:recallnest` |
| `source` | string | no | `"manual"` | Source label such as `agent`, `managed`, `smoke`, `eval`, `manual` |
| `signal` | string | no | — | Optional failure/correction signal tag |
| `task` | string | no | — | Optional related task |
| `tags` | string[] | no | `[]` | Optional tags |
| `tools` | string[] | no | `[]` | Optional tools involved |
| `recordedAt` | string | no | `now()` | Optional ISO timestamp override |

**Response:**

```json
{
  "observationId": "resume_context-missed-2026-03-17T03-00-00-000Z",
  "workflowId": "resume_context",
  "outcome": "missed",
  "summary": "Fresh window skipped continuity recovery before repo exploration.",
  "scope": "project:recallnest",
  "resolvedScope": "project:recallnest",
  "source": "smoke",
  "signal": "missed-startup-trigger",
  "task": "headless continuity smoke",
  "tags": ["continuity"],
  "tools": ["resume_context"],
  "recordedAt": "2026-03-17T03:00:00.000Z"
}
```

---

## Workflow Health

```
GET /v1/workflow-health
GET /v1/workflow-health?workflowId=resume_context&scope=project:recallnest
```

Inspect one workflow primitive or return a dashboard of degraded workflows.

> 查看 workflow 健康度：支持单个 workflow 的 7 天 / 30 天汇总，也支持全局 dashboard。

Query params:

| Param | Required | Description |
|-------|----------|-------------|
| `workflowId` | no | Optional workflow primitive id |
| `scope` | no | Optional scope filter |
| `limit` | no | Dashboard max rows, default `10` |

**Workflow response:**

```json
{
  "workflowId": "resume_context",
  "scope": "project:recallnest",
  "status": "watch",
  "summary": "3 observations in the last 30d, success rate 33.3%, 2 issue observations.",
  "latestObservationAt": "2026-03-17T03:00:00.000Z",
  "windows": [
    {
      "days": 7,
      "total": 2,
      "successes": 1,
      "failures": 0,
      "corrected": 1,
      "missed": 0,
      "issueCount": 1,
      "successRate": 0.5,
      "issueRate": 0.5,
      "latestAt": "2026-03-17T03:00:00.000Z",
      "topSignals": [
        { "signal": "user-correction", "count": 1 }
      ]
    }
  ]
}
```

**Dashboard response:**

```json
{
  "dashboard": [
    {
      "workflowId": "checkpoint_session",
      "scope": "project:recallnest",
      "status": "critical",
      "total": 1,
      "issueCount": 1,
      "successRate": 0,
      "latestObservationAt": "2026-03-17T04:00:00.000Z",
      "summary": "1 observations in the last 30d, success rate 0.0%, 1 issue observations."
    }
  ]
}
```

---

## Workflow Evidence

```
GET /v1/workflow-evidence?workflowId=checkpoint_session&scope=project:recallnest&limit=5
```

Build an evidence pack for one workflow primitive from recent issue observations.

> 生成 workflow evidence pack：把最近问题 observation、top signals 和修复建议打包出来，方便做规则、prompt、测试收口。

Query params:

| Param | Required | Description |
|-------|----------|-------------|
| `workflowId` | yes | Workflow primitive id |
| `scope` | no | Optional scope filter |
| `limit` | no | Max recent issue observations, default `5` |

**Response:**

```json
{
  "workflowId": "checkpoint_session",
  "scope": "project:recallnest",
  "generatedAt": "2026-03-17T10:10:00.000Z",
  "summary": "1 issue observations in the last 30d for checkpoint_session.",
  "topSignals": [
    { "signal": "repo-state-contamination", "count": 1 }
  ],
  "recentIssues": [
    {
      "observationId": "checkpoint_session-failure-2026-03-17T04-00-00-000Z",
      "outcome": "failure",
      "summary": "Checkpoint still carried repo-state text before the product-side guard landed.",
      "signal": "repo-state-contamination",
      "recordedAt": "2026-03-17T04:00:00.000Z",
      "task": "headless continuity smoke"
    }
  ],
  "suggestions": [
    "Keep volatile repo-state text out of saved checkpoints and handoff summaries unless this window verified it."
  ]
}
```

---

## Search (Advanced)

```
POST /v1/search
```

Advanced search with full metadata, retrieval path details, and scope filtering.

> 高级搜索：返回完整元数据、检索路径、重要度等详情。

**Request:**

```json
{
  "query": "API authentication patterns",
  "limit": 5,
  "category": "patterns",
  "profile": "fact-check"
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `query` | string | yes | — | Search query |
| `limit` | number | no | 5 | Max results (1-20) |
| `category` | string | no | — | Filter: `profile`, `preferences`, `entities`, `events`, `cases`, `patterns` |
| `profile` | string | no | `"default"` | Retrieval profile: `default`, `writing`, `debug`, `fact-check` |
| `scope` | string | no | — | Filter by source scope (e.g. `cc`, `codex`, `api:my-agent`) |
| `minScore` | number | no | 0 | Minimum relevance score (0-1) |

**Response:**

```json
{
  "results": [
    {
      "id": "a1b2c3d4-...",
      "text": "Authentication pattern: use JWT with...",
      "category": "patterns",
      "scope": "cc:abc12345",
      "score": 0.82,
      "importance": 0.8,
      "timestamp": 1741219200000,
      "date": "2026-03-06",
      "metadata": { "source": "cc", "tier": "working", "file": "..." },
      "boundary": {
        "layer": "durable",
        "authority": "structured-memory",
        "conflictPolicy": "latest-wins",
        "originalCategory": "patterns"
      },
      "canonicalKey": "patterns:cross-window-continuity-handoff",
      "promotedFrom": null,
      "sources": { "vector": { "score": 0.85, "rank": 1 }, "bm25": { "score": 0.7, "rank": 3 } }
    }
  ],
  "query": "API authentication patterns",
  "profile": "fact-check",
  "count": 1
}
```

Derived provenance fields:

| Field | Type | Description |
|-------|------|-------------|
| `boundary` | object \| null | Parsed boundary metadata: `layer`, `authority`, `conflictPolicy`, and optional `originalCategory` / `downgradedFrom` |
| `canonicalKey` | string \| null | Stable dedupe/update key if the memory has one |
| `promotedFrom` | object \| null | Present when a durable memory was explicitly promoted from evidence; includes source `memoryId`, `scope`, `category`, `source`, and source `boundary` |
| `provenanceHistory` | array | Observed evidence trail for the durable memory. Falls back to `[promotedFrom]` when no explicit history has been materialized yet |
| `provenanceHistoryCount` | number | Total provenance-history observations tracked for the durable memory |

---

## Stats

```
GET /v1/stats
```

Memory index statistics.

**Response:**

```json
{
  "totalMemories": 35176,
  "byScope": {
    "cc:abc12345": 773,
    "memory": 974,
    "codex:019ccbe4": 277
  },
  "byCategory": {
    "fact": 31582,
    "events": 1546,
    "cases": 1337,
    "entities": 455,
    "patterns": 156,
    "preferences": 65,
    "profile": 26
  }
}
```

> Note: `byScope` shows raw scope keys. Use `byCategory` for the 6-category distribution.

---

## Consolidate (Phase 3 — not yet implemented)

```
POST /v1/consolidate
```

> This endpoint is planned for Phase 3 (self-evolution). See [docs/self-evolution.md](self-evolution.md) for the design.

---

## Gaps (Phase 3 — not yet implemented)

```
GET /v1/gaps
```

> This endpoint is planned for Phase 3 (self-evolution). See [docs/self-evolution.md](self-evolution.md) for the design.

---

## Error Responses

All endpoints return errors in this format:

```json
{
  "error": "Description of what went wrong"
}
```

| HTTP Status | Meaning |
|-------------|---------|
| 400 | Bad request (missing required fields, invalid values) |
| 404 | Endpoint not found |
| 500 | Internal server error |
| 503 | Service unavailable (health check failed) |

---

## Quick Test

```bash
# Health check
curl http://localhost:4318/v1/health

# Recall
curl -X POST http://localhost:4318/v1/recall \
  -H "Content-Type: application/json" \
  -d '{"query": "Docker debugging", "limit": 3}'

# Store
curl -X POST http://localhost:4318/v1/store \
  -H "Content-Type: application/json" \
  -d '{"text": "Test memory entry", "category": "events"}'

# Advanced search with category filter
curl -X POST http://localhost:4318/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "debugging", "category": "cases", "profile": "debug"}'

# Stats
curl http://localhost:4318/v1/stats
```
