> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xysq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 2026 06 17 reflect to recall skill synthesis design

# Deprecate reflect's synthesis, push reflection into recall + skills

Date: 2026-06-17
Status: Design approved, ready for implementation plan
Repos touched: `backend/`, `xysq-skills/`, `python_sdk/`

## Problem

`memory_reflect` calls Hindsight's `gpt-oss harmony` model to synthesize prose
answers. That model intermittently leaks raw scaffolding tokens
(`<|channel|>`, `to=functions`, `<|call|>`, or a bare `{"answer": ...}` JSON
blob) instead of prose. Observed live on 2026-06-17 (the `decisions` skill
returned scaffolding).

There is a partial guard already: `HindsightProvider.reflect()` in
`backend/memory/hindsight.py` detects poisoned text via
`_is_poisoned_reflect_text()` and raises, so the service returns
`{"status": "error"}` and skills fall back to recall. But the guard does not
catch every leak mode, and it is a patch over a deeper issue: we are running a
synthesis model in the request path whose only consumers are themselves LLMs
that re-articulate the result.

### Audit finding (decisive)

Every call site that consumes `reflect` is an LLM that re-synthesizes the
result. There are **zero direct-render consumers** - no UI component, REST
client, dashboard card, cron job, or CLI displays a reflect `answer` to a human
without an LLM in the loop. Consumers found:

* MCP tool `memory_reflect` -> calling agent (Claude Code, native chat, skills)
* REST `/memories/reflect` and `/api/sdk/memory/reflect` -> LLM/SDK callers
* `python_sdk` `memory.synthesize()` -> embedded into an LLM system prompt by
  `SynthesizeStrategy` / `BothStrategy`
* `backend/chat/wiring.py` `reflect()` -> fire-and-forget, result discarded (unused)

Because the synthesizer is always an LLM downstream, server-side synthesis is
redundant - and it is the sole source of the scaffolding leak.

## Principle

The MCP layer returns **facts**; the downstream LLM does the **synthesis**. We
stop running the harmony model in the request path. The "reflection" quality
moves into skill and docstring instructions that the calling agent applies,
ported from Hindsight's own reflect recipe.

## Design

### 1. Backend: re-point reflect at the recall path

* `services.memory.reflect()` stops calling `provider.reflect()` (the harmony
  model). It calls the same retrieval path recall uses and returns facts in a
  recall-shaped payload.
* `provider.reflect()` and the `_is_poisoned_reflect_text()` guard in
  `hindsight.py` are **left in place but unused by the tool**. Mark deprecated
  in a comment. Deleting the provider method is a separate later pass - it is
  still referenced by `backend/evals/` and the unused `chat/wiring.py`
  `reflect_background`. (Decision: re-point, keep provider.)
* The `memory_reflect` MCP tool keeps its name and signature so no caller
  breaks. Docstring changes from "Hindsight synthesizes; you consume prose" to
  "returns facts; synthesize them yourself using your skill's recipe."

### 2. Skills: port Hindsight's synthesis recipe

Hindsight's reflect is an agentic loop with four portable ingredients
(`hindsight-docs` `developer/reflect.md`). Each becomes a skill instruction the
downstream agent already knows how to follow:

| Hindsight ingredient                                                | Where it lands in our skills                                                                                                                                                          |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Hierarchical retrieval (mental models -> observations -> raw facts) | The skill's recall call chooses `types=["observation"]` for "what's true now", raw episodic for history. Made explicit per skill.                                                     |
| Mission / identity (per use-case prose)                             | Each skill gets a one-paragraph **mission line**: who is reasoning, what they care about.                                                                                             |
| Disposition (skepticism/literalism/empathy 1-5)                     | Baked into the mission as **one or two adjectives** - no numeric trait system. blockers/decisions lean skeptical+literal+direct; recap/prep lean neutral.                             |
| Grounding + validated citation                                      | Shared rule in `_shared/recall-recipe.md`: synthesize ONLY from returned facts; cite the `id`/`document_id` of facts actually used; if recall returns nothing, say so - never invent. |

Each skill body becomes: **mission line** -> **scope step** (3a below) ->
**retrieval directive** (recall call + tags + observation-vs-episodic) ->
**synthesis + citation rules** (shared) -> **output format** (already exists).

The five skills (`recap`, `decisions`, `actionables`, `blockers`, `prep`) and
`_shared/recall-recipe.md` are updated. The obsolete "if reflect fails, fall
back to recall" contract in `recall-recipe.md` is **removed** - there is no
harmony failure mode left, because reflect is now recall.

Example - `decisions`:

> **Mission:** You are surfacing decisions the user made. Be precise and
> literal: report the choice and its stated rationale, nothing inferred. If a
> decision has no clear rationale in the facts, present it as "rationale
> unknown" rather than inventing one.
> **Scope:** (see 3a)
> **Retrieve:** `memory_recall` with `tags: ["memory_kind:decision"]`,
> `types: ["observation"]` (so superseded decisions resolve to the current one).
> **Synthesize:** \[shared grounding + citation rules]
> **Format:** \[existing decisions format]

### 3a. Scope: parameter, not a hardcoded default

Skills currently hardcode `personal_only: true`. Since reflect becomes
recall-backed, it inherits recall's fan-out (personal + recall-enabled teams) -
strictly more capable than the old single-vault reflect. The recipe changes
from a constant to an explicit scope step:

> **Scope:** Default to the user's personal vault. If the request names a team
> (or the active context is a team), pass that team's scope instead of
> `personal_only`. If genuinely ambiguous and permitted, recall across personal
>
> * recall-enabled teams and label each fact by its `source`
>   (`personal` / `team:<id>`) in the output.

The `source` field already returns on every recall fact, so the agent can
attribute team vs personal. **Constraint:** this must respect existing scope
guards - native chat stays personal-only, and the org iron wall hard-blocks
cross-scope at the backend. The recipe *instructs*; the backend *authorizes*.
Recipe wording: "if permitted."

### 3b. python\_sdk: facts digest in `answer`

`synthesize()` returns recall facts joined into a readable block in the
`SynthesizeResult.answer` field, `citations` from fact ids, `confidence`
derived from result count/score. **No new LLM call in the SDK.** Every
`synthesize()` consumer is itself an LLM that re-articulates, so a facts digest
is exactly as useful as the old prose and cannot leak scaffolding.
`SynthesizeStrategy` and `BothStrategy` continue to embed `answer` into the
system prompt - now grounded facts instead of harmony prose. (Decision: facts
digest, not client-side synthesis, not a breaking deprecation.)

### 3c. response\_schema: accept-but-ignore, deprecate

Keep the `response_schema` param on the tool/route so no caller breaks. Stop
producing server-side `structured_output`; the downstream agent builds the
structure itself (it conforms to a schema better than the harmony model did).
Document the param as deprecated; remove next version.

## What we explicitly do NOT do

* Do not delete `provider.reflect()` or the harmony guard this pass (later cleanup).
* Do not add numeric disposition traits to skills - adjectives in the mission only.
* Do not make a new LLM call inside the SDK.
* Do not break the SDK `synthesize()` shape or the `response_schema` signature.
* Do not let the skill recipe override backend scope authorization.

## Success criteria

1. `memory_reflect` (MCP), `/memories/reflect`, `/api/sdk/memory/reflect`, and
   `python_sdk` `synthesize()` all return without ever invoking the harmony
   model - verified by no call to `provider.reflect()` in the request path.
2. The scaffolding-leak class (`<|channel|>`, `to=functions`, `<|call|>`, bare
   `{"answer"}`) is impossible from these paths because no harmony prose is
   generated. A test asserts reflect output contains only fact-shaped data.
3. The 5 skills synthesize grounded, cited answers from recall facts; the
   "fall back to recall" contract is gone from `recall-recipe.md`.
4. Skills default to personal scope but can target a team vault when named and
   permitted; backend scope guards still enforce.
5. No existing caller breaks: tool/route signatures and `SynthesizeResult`
   shape are unchanged.
