# MCP reference

81 tools, 4 prompts, 8 resources — one local MCP server, both transports.

## Connect your AI

Heartwood's local MCP server is reachable over two transports — pick whichever your client supports. Both are loopback-only: nothing leaves your machine except to the model you personally chose.

### stdio (recommended — no port or token to manage)

Your client launches `heartwood mcp` as a subprocess; it auto-starts the daemon if needed and forwards stdio transparently.

```json
{
  "mcpServers": {
    "heartwood": {
      "command": "heartwood",
      "args": ["mcp"]
    }
  }
}
```

| Client | Where this config goes |
|---|---|
| Claude Desktop | `claude_desktop_config.json` (Settings -> Developer -> Edit Config) — or use the in-app "Connect" button, which writes this for you. |
| Claude Code | project-local `.mcp.json`, or run `claude mcp add heartwood -- heartwood mcp` |
| Cursor | project-local `.cursor/mcp.json`, or global `~/.cursor/mcp.json` |

### Streamable-HTTP (loopback)

The daemon binds `127.0.0.1:0` (an OS-assigned port, never fixed) and writes a discovery file, `discovery.json`, inside its data directory, carrying the port and a bearer token:

```json
{
  "port": 51234,
  "token": "..."
}
```

Point your client at `http://127.0.0.1:<port>/mcp` with that discovered token as a bearer credential. (The daemon also mints a separate human-only credential for the app's own UI — it is never documented as a client credential and will not authenticate an MCP call; the discovery-file token above is the only one your AI assistant ever needs.)

```json
{
  "mcpServers": {
    "heartwood": {
      "url": "http://127.0.0.1:<port>/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}
```

Non-technical walkthrough (one paste, no understanding required): [Connect your AI](../getting-started-ai.md).

## Tools

### Trees

<!-- doc-md:tool:tree.list -->
### `tree.list` (Preview)

List the trees known to this daemon (id, name, created-at, archived flag). Archived trees are omitted unless 'include_archived' is set.

**Parameters**

object
  - `include_archived`: boolean — optional

**Output shape**

object
  - `active_tree_id`: string — required
  - `items`: array of — required
    - `TreeView`: object
      - `archived`: boolean — required
      - `created_at_ms`: integer (uint64) — required
      - `id`: string — required
      - `name`: string — required

**Example prompts**

- “What trees exist in this daemon right now?”
- “List all my trees, including archived ones.”

### People

<!-- doc-md:tool:person.create -->
### `person.create` (Preview)

Conclude a new person over a set of personas — the identity conclusion that turns raw evidence into a named individual on the tree. A persona already concluded over by another live person is refused (cite proof.argument.record's output as the optional proof_argument). Human actors apply directly; agent actors always file a proposal for human approval — minting an identity is human judgment.

**Parameters**

object
  - `certainty`: string [enum: proved, probable, possible, disproved] — required
  - `personas`: array of — required
    string (uuid)
  - `proof_argument`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `applied`: boolean — required
  - `message`: string — required
  - `person_id`: string (nullable) — optional
  - `proposal_id`: string (nullable) — optional

**Example prompts**

- “I've got a persona from the 1900 census that isn't linked to anyone yet — conclude it as a new person, Arthur Fernwood.”
- “Turn this unmatched census entry into a real person in the tree so I can start attaching relatives to her.”

<!-- doc-md:tool:person.living_status.set -->
### `person.living_status.set` (Preview)

Set an explicit living/deceased override for a person, overriding the automatic likely-living guess. This gates whether the person is suppressed from a GEDCOM (family file) export by default — set it whenever the automatic guess is wrong. use_heuristic clears the override and resumes the automatic guess.

**Parameters**

object
  - `person`: string (uuid) — required
  - `status`: string [enum: living, deceased, use_heuristic] — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “Mark Arthur as deceased so he stops being suppressed from the family file I export.”
- “I don't actually know whether this person is still living — clear my earlier override and go back to the automatic guess.”

<!-- doc-md:tool:person.merge -->
### `person.merge` (Preview)

Merge two persons into one: concludes a new person over the union of both persons' personas, then retracts both originals. Refused if the two persons share a persona. Reversible with person.split using the two original persona sets, which the merge event itself records.

**Parameters**

object
  - `left`: string (uuid) — required
  - `right`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `person_id`: string (uuid) — required

**Example prompts**

- “I think 'Arthur Fernwood' and 'Art Fernwood' are the same man entered twice — merge them into one person.”
- “These two entries are clearly duplicates from two different census years. Combine them so the tree only shows one person.”

<!-- doc-md:tool:person.retract -->
### `person.retract` (Preview)

Undo a mistaken person.create: removes the person's conclusion entirely (state-removal, not a status flag). The underlying personas are untouched — they remain available to conclude over again with person.create.

**Parameters**

object
  - `person`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “I created a person by mistake while testing — undo that, but keep whatever source appearances I'd already linked to her.”
- “That last person I added was a typo, not a real conclusion. Take it back.”

<!-- doc-md:tool:person.split -->
### `person.split` (Preview)

Split a person into two, partitioning its persona set across caller-supplied left/right groups (every persona must land in exactly one side). This is how a merge is undone, and also how a mistaken person.create over unrelated personas is corrected.

**Parameters**

object
  - `left`: array of — required
    string (uuid)
  - `person`: string (uuid) — required
  - `right`: array of — required
    string (uuid)
  - `tree_id`: string — optional

**Output shape**

object
  - `left`: string (uuid) — required
  - `right`: string (uuid) — required

**Example prompts**

- “I merged two people together and it turns out they were actually different individuals — split them back apart.”
- “This person record is conflating two different Arthur Fernwoods. Separate the census appearances back into the two original people.”

<!-- doc-md:tool:persona.create -->
### `persona.create` (Preview)

Mint a new persona against an existing source — the raw evidence-layer appearance ("John Smith, age 34" as this record names them), not yet an identity conclusion. Plain write, ungated for both human and agent actors: minting a persona commits to nothing about who the person is. Use person.create afterward to conclude an identity over one or more personas. 'label' is the record's own wording and nothing parses a name out of it: pass 'name' (with 'research_context') to state the given/surname pieces. Without it the person concluded here has no surname at all — none is guessed from the label.

**Parameters**

object
  - `label`: string — required
  - `name`: object (optional) — optional
    - `display`: string (nullable) — optional
    - `given`: array of — optional
      string
    - `surname`: array of — optional
      string
  - `research_context`: object (optional) — optional
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `persona_id`: string (uuid) — required

**Example prompts**

- “This 1900 census page names a household member who isn't in the tree at all yet — mint a persona for her: Clara Fernwood, age 8.”
- “Create a new persona against this source for the witness named in the marriage record, before I try to conclude who they are.”

### Relationships

<!-- doc-md:tool:relationship.attach -->
### `relationship.attach` (Preview)

Attach a parent or spouse edge between two persons. 'kind' is "parent" (relative becomes person's parent), "child" (relative becomes person's child — the reversed edge), or "spouse" (person and relative become spouses). Refuses self-relationships and ancestry cycles before writing anything. Idempotent: attaching an edge that already exists returns it unchanged with 'created: false'. Requires research_context, same as assertion.capture.

**Parameters**

object
  - `kind`: string — required
  - `person`: string (uuid) — required
  - `relative`: string (uuid) — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `assertions`: array of — required
    string (uuid)
  - `created`: boolean — required
  - `event`: string (uuid) — required

**Example prompts**

- “The 1900 census names John Doe as Mary Doe's father — attach that parent relationship.”
- “Mark these two as spouses based on the marriage record I just added.”
- “Add Jane as a child of Robert — I have the birth certificate as the source.”

<!-- doc-md:tool:relationship.reparent -->
### `relationship.reparent` (Preview)

Replace one of a child's parents with a different parent, in place — the displaced participation is superseded by the replacement, so the correction stays auditable. Refuses ancestry cycles before writing anything. Requires research_context, same as assertion.capture.

**Parameters**

object
  - `child`: string (uuid) — required
  - `from_parent`: string (uuid) — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `to_parent`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `assertions`: array of — required
    string (uuid)
  - `created`: boolean — required
  - `event`: string (uuid) — required

**Example prompts**

- “The census I sourced this from actually names a different father — replace him with the correct one.”
- “I attached the wrong mother earlier; swap her for the one this new record names.”

<!-- doc-md:tool:relationship.unlink -->
### `relationship.unlink` (Preview)

Remove a parent or spouse edge between two persons — refutes or supersedes the underlying participation assertions, never deletes the persons or any other edge. Same 'kind' vocabulary as relationship.attach. Requires research_context, same as assertion.capture.

**Parameters**

object
  - `kind`: string — required
  - `person`: string (uuid) — required
  - `relative`: string (uuid) — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “Remove the parent link I added by mistake — that census entry was for a different family.”
- “Undo the spouse relationship between these two; the marriage record turned out to be for someone else.”

### Undo

<!-- doc-md:tool:edit.undo -->
### `edit.undo` (Preview)

Undo one of your own edits. Pass 'event' to target exactly that event, or omit it to undo your most recent executable edit (the same one edit.undo_preview's 'target' reports). Undo always unwinds the calling agent's own writes only — it can never undo a human's edits, and a second call with nothing of your own left to undo is refused, not a silent no-op. The compensating event (e.g. a refutation) is returned, never applied silently.

**Parameters**

object
  - `event`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `compensating`: array of — required
    string (uuid)
  - `undone`: string (uuid) — required
  - `undone_event_type`: string — required

**Example prompts**

- “That last detail you recorded was wrong — take it back.”
- “Undo your most recent change to this tree.”
- “You attached that fact to the wrong person; undo it and we'll re-enter it correctly.”

<!-- doc-md:tool:edit.undo_preview -->
### `edit.undo_preview` (Preview)

Report undo status without undoing anything: the literal last edit you (this agent) authored, with its disposition (undoable now, already undone, deferred, or not undoable — each with why), and what a no-target edit.undo call would actually undo. Scope is always your own edits — never a human's.

**Parameters**

object
  - `tree_id`: string — optional

**Output shape**

object
  - `last_edit`: object (optional) — optional
    - `event`: string (uuid) — required
    - `event_type`: string — required
    - `undoable`: object — required
      - `rationale`: string (nullable) — optional
      - `state`: string — required
  - `target`: object (optional) — optional
    - `at_ms`: integer (uint64) — required
    - `event`: string (uuid) — required
    - `event_type`: string — required

**Example prompts**

- “Before you undo anything, show me what your last change was and whether it can be taken back.”
- “What would an undo revert right now?”

### Sessions & assignments

<!-- doc-md:tool:assignment.get -->
### `assignment.get` (Preview)

Read a recorded assignment by id: what prompt (and arguments) it was recorded from, and when. Assignments are recorded automatically the first time this connection successfully calls get_prompt — there is no tool to create one directly.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `content`: tagged union: — required
    - `kind` = `raw`:
      - `kind`: string [const: raw] — required
      - `text`: string — required
    - `kind` = `structured`:
      - `arguments`: map: string → string — required
      - `kind`: string [const: structured] — required
      - `prompt_name`: string — required
  - `id`: string (uuid) — required
  - `recorded_at_ms`: integer (uint64) — required

**Example prompts**

- “What prompt and arguments was this assignment recorded from?”
- “Look up assignment {id} — what was I actually asked to do?”

<!-- doc-md:tool:session.current -->
### `session.current` (Preview)

Read the calling connection's own current session, if it has minted one yet (no args). Reports null before this connection's first write — never a synthesized session.

**Parameters**

object
  - `tree_id`: string — optional

**Output shape**

object
  - `actor_model`: string (nullable) — optional
  - `actor_reported_harness`: string (nullable) — optional
  - `current_assignment`: string (nullable) — optional
  - `event_count`: integer (uint) — required
  - `first_seen_at_ms`: integer (uint64) — required
  - `last_seen_at_ms`: integer (uint64) — required
  - `session`: string (uuid) — required

**Example prompts**

- “What's my current session, if I have one yet?”
- “Have I minted a store session on this connection?”

<!-- doc-md:tool:session.get -->
### `session.get` (Preview)

Read a store session's summary by id: which agent model wrote under it, its declared harness, first/last-seen time, how many events it wrote, and its current assignment (if any). A session is minted server-side the first time a connection writes.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `actor_model`: string (nullable) — optional
  - `actor_reported_harness`: string (nullable) — optional
  - `current_assignment`: string (nullable) — optional
  - `event_count`: integer (uint) — required
  - `first_seen_at_ms`: integer (uint64) — required
  - `last_seen_at_ms`: integer (uint64) — required
  - `session`: string (uuid) — required

**Example prompts**

- “Who was writing under this session, and how many events did they record?”
- “Look up session {id} — what model and harness did it declare, and does it have a current assignment?”

### Evidence capture

<!-- doc-md:tool:assertion.capture -->
### `assertion.capture` (Preview)

Capture a new assertion (a claim about a persona, backed by a source). 'research_context' is required, not optional metadata: the research log is a byproduct of every capture, per the GPS's reasonably-exhaustive-search component. Agent-origin assertions are always captured in the pending lane (epistemic status 'pending confirmation') — never immediately usable as fact until a human confirms them.

**Parameters**

object
  - `attached`: array of — optional
    tagged union:
      - `kind` = `external_record`:
        - `id`: tagged union: — required
          - `kind` = `ancestry_record`:
            - `dbid`: string — required
            - `kind`: string [const: ancestry_record] — required
            - `record`: string — required
          - `kind` = `doi`:
            - `doi`: string — required
            - `kind`: string [const: doi] — required
          - `kind` = `family_search_ark`:
            - `ark`: string — required
            - `kind`: string [const: family_search_ark] — required
          - `kind` = `permalink`:
            - `kind`: string [const: permalink] — required
            - `url`: string — required
        - `kind`: string [const: external_record] — required
      - `kind` = `offline_record`:
        - `citation`: string — required
        - `kind`: string [const: offline_record] — required
      - `kind` = `source_blob`:
        - `blob`: string — required
        - `kind`: string [const: source_blob] — required
  - `claim`: string — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `kind`: tagged union: — required
    - `kind` = `fact`:
      - `kind`: string [const: fact] — required
    - `kind` = `transcription_claim`:
      - `kind`: string [const: transcription_claim] — required
      - `of_blob`: string — required
      - `region`: object (optional) — optional
        - `height`: number (double) — required
        - `width`: number (double) — required
        - `x`: number (double) — required
        - `y`: number (double) — required
  - `persona`: string (uuid) — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `claim`: string — required
  - `epistemic_status`: string — required
  - `event_context`: object (optional) — optional
    - `date`: tagged union: (optional) — optional
      - `kind` = `about`:
        - `kind`: string [const: about] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `after`:
        - `kind`: string [const: after] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `before`:
        - `kind`: string [const: before] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `between`:
        - `kind`: string [const: between] — required
        - `value`: array of — required
          any
      - `kind` = `exact`:
        - `kind`: string [const: exact] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `range`:
        - `kind`: string [const: range] — required
        - `value`: array of — required
          any
    - `event_id`: string (uuid) — required
    - `kind`: object — required
      - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
      - `phrase`: string — optional
    - `kind_phrase`: string (nullable) — optional
    - `place`: object (optional) — optional
      - `authority_id`: string (nullable) — optional
      - `coordinates`: object (optional) — optional
        - `latitude_nanodeg`: integer (int64) — required
        - `longitude_nanodeg`: integer (int64) — required
      - `jurisdictions`: array of — required
        string
      - `valid_time_note`: string (nullable) — optional
    - `sibling_participations`: array of — required
      - `SiblingParticipationOutput`: object
        - `persona_id`: string (uuid) — required
        - `persona_label_id`: string — required
        - `role`: object — required
          - `phrase`: string — optional
          - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
        - `status`: string — required
    - `source_id`: string (uuid) — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `id`: string (uuid) — required
  - `next_step`: string — required
  - `persona`: string (uuid) — required
  - `source`: string (uuid) — required

**Example prompts**

- “Capture that Arthur Fernwood was born about 1870 in Elsewhere County, citing the 1900 census I just found.”
- “Add an assertion for Beatrice's death date from the obituary — remember, it needs a real source and research context, not just my hunch.”

<!-- doc-md:tool:assertion.evidence.attach -->
### `assertion.evidence.attach` (Preview)

Attach a piece of corroborating evidence (a source blob or an external record id) to an existing assertion. Strengthens — never substitutes for — human confirmation.

**Parameters**

object
  - `assertion`: string (uuid) — required
  - `evidence`: tagged union: — required
    - `kind` = `external_record`:
      - `id`: tagged union: — required
        - `kind` = `ancestry_record`:
          - `dbid`: string — required
          - `kind`: string [const: ancestry_record] — required
          - `record`: string — required
        - `kind` = `doi`:
          - `doi`: string — required
          - `kind`: string [const: doi] — required
        - `kind` = `family_search_ark`:
          - `ark`: string — required
          - `kind`: string [const: family_search_ark] — required
        - `kind` = `permalink`:
          - `kind`: string [const: permalink] — required
          - `url`: string — required
      - `kind`: string [const: external_record] — required
    - `kind` = `offline_record`:
      - `citation`: string — required
      - `kind`: string [const: offline_record] — required
    - `kind` = `source_blob`:
      - `blob`: string — required
      - `kind`: string [const: source_blob] — required
  - `tree_id`: string — optional

**Output shape**

object
  - `claim`: string — required
  - `epistemic_status`: string — required
  - `event_context`: object (optional) — optional
    - `date`: tagged union: (optional) — optional
      - `kind` = `about`:
        - `kind`: string [const: about] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `after`:
        - `kind`: string [const: after] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `before`:
        - `kind`: string [const: before] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `between`:
        - `kind`: string [const: between] — required
        - `value`: array of — required
          any
      - `kind` = `exact`:
        - `kind`: string [const: exact] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `range`:
        - `kind`: string [const: range] — required
        - `value`: array of — required
          any
    - `event_id`: string (uuid) — required
    - `kind`: object — required
      - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
      - `phrase`: string — optional
    - `kind_phrase`: string (nullable) — optional
    - `place`: object (optional) — optional
      - `authority_id`: string (nullable) — optional
      - `coordinates`: object (optional) — optional
        - `latitude_nanodeg`: integer (int64) — required
        - `longitude_nanodeg`: integer (int64) — required
      - `jurisdictions`: array of — required
        string
      - `valid_time_note`: string (nullable) — optional
    - `sibling_participations`: array of — required
      - `SiblingParticipationOutput`: object
        - `persona_id`: string (uuid) — required
        - `persona_label_id`: string — required
        - `role`: object — required
          - `phrase`: string — optional
          - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
        - `status`: string — required
    - `source_id`: string (uuid) — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `id`: string (uuid) — required
  - `next_step`: string — required
  - `persona`: string (uuid) — required
  - `source`: string (uuid) — required

**Example prompts**

- “Attach this second census image as corroborating evidence for the birth-date assertion I already captured.”
- “Add the FamilySearch record id as supporting evidence on this claim, alongside the source blob I already cited.”

<!-- doc-md:tool:assertion.supersede -->
### `assertion.supersede` (Preview)

Correct a claim by superseding it with a replacement assertion, rather than editing it in place — the original stays in the record, marked superseded, and the replacement takes its place. Refused if the original is already refuted, superseded, or still awaiting human confirmation. This does NOT resolve a conflict: a contradiction is settled only by a proof argument, so any conflict the superseded claim stood in stays open and comes back in 'conflicts_still_open'.

**Parameters**

object
  - `assertion`: string (uuid) — required
  - `by`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `conflicts_still_open`: array of — required
    string (uuid)
  - `ok`: boolean — required

**Example prompts**

- “I found a better source for her birth date — supersede the old claim with the one I just captured, rather than editing it.”
- “That transcription was wrong. Replace it with the corrected claim I just logged, and leave the original marked as superseded.”

<!-- doc-md:tool:association.record -->
### `association.record` (Preview)

Record an eventless typed association between two personas (e.g. godparent, neighbor, employer) — no shared life event required. Requires research_context, same as assertion.capture.

**Parameters**

object
  - `attached`: array of — optional
    tagged union:
      - `kind` = `external_record`:
        - `id`: tagged union: — required
          - `kind` = `ancestry_record`:
            - `dbid`: string — required
            - `kind`: string [const: ancestry_record] — required
            - `record`: string — required
          - `kind` = `doi`:
            - `doi`: string — required
            - `kind`: string [const: doi] — required
          - `kind` = `family_search_ark`:
            - `ark`: string — required
            - `kind`: string [const: family_search_ark] — required
          - `kind` = `permalink`:
            - `kind`: string [const: permalink] — required
            - `url`: string — required
        - `kind`: string [const: external_record] — required
      - `kind` = `offline_record`:
        - `citation`: string — required
        - `kind`: string [const: offline_record] — required
      - `kind` = `source_blob`:
        - `blob`: string — required
        - `kind`: string [const: source_blob] — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `other`: string (uuid) — required
  - `persona`: string (uuid) — required
  - `relation`: tagged union: — required
    - `relation` = `employer`:
      - `relation`: string [const: employer] — required
    - `relation` = `friend_or_associate`:
      - `relation`: string [const: friend_or_associate] — required
    - `relation` = `godparent`:
      - `relation`: string [const: godparent] — required
    - `relation` = `neighbor`:
      - `relation`: string [const: neighbor] — required
    - `relation` = `other`:
      - `phrase`: string — required
      - `relation`: string [const: other] — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `assertion_id`: string (uuid) — required

**Example prompts**

- “Record that Bertram Cole was Arthur Fernwood's neighbor, per the 1878 town directory.”
- “Note that this minister witnessed the marriage — record him as an associate, even though there's no shared event for it yet.”

<!-- doc-md:tool:event.correct -->
### `event.correct` (Preview)

Propose a correction to a life event's date, place, or kind — field-discriminated by the 'field' parameter ("date" | "place" | "kind"). This tool always files a Proposal; corrections filed by an agent never apply directly, only a human approving the proposal executes them. Requires a source and evidence, since a corrected value must stay citation-carrying.

**Parameters**

object
  - `date`: tagged union: (optional) — optional
    - `kind` = `about`:
      - `kind`: string [const: about] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `after`:
      - `kind`: string [const: after] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `before`:
      - `kind`: string [const: before] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `between`:
      - `kind`: string [const: between] — required
      - `value`: array of — required
        any
    - `kind` = `exact`:
      - `kind`: string [const: exact] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `range`:
      - `kind`: string [const: range] — required
      - `value`: array of — required
        any
  - `event`: string (uuid) — required
  - `evidence`: array of — optional
    tagged union:
      - `kind` = `external_record`:
        - `id`: tagged union: — required
          - `kind` = `ancestry_record`:
            - `dbid`: string — required
            - `kind`: string [const: ancestry_record] — required
            - `record`: string — required
          - `kind` = `doi`:
            - `doi`: string — required
            - `kind`: string [const: doi] — required
          - `kind` = `family_search_ark`:
            - `ark`: string — required
            - `kind`: string [const: family_search_ark] — required
          - `kind` = `permalink`:
            - `kind`: string [const: permalink] — required
            - `url`: string — required
        - `kind`: string [const: external_record] — required
      - `kind` = `offline_record`:
        - `citation`: string — required
        - `kind`: string [const: offline_record] — required
      - `kind` = `source_blob`:
        - `blob`: string — required
        - `kind`: string [const: source_blob] — required
  - `field`: string — required
  - `kind`: object (optional) — optional
    - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
    - `phrase`: string — optional
  - `kind_phrase`: string (nullable) — optional
  - `place`: object (optional) — optional
    - `authority_id`: string (nullable) — optional
    - `coordinates`: object (optional) — optional
      - `latitude_nanodeg`: integer (int64) — required
      - `longitude_nanodeg`: integer (int64) — required
    - `jurisdictions`: array of — required
      string
    - `valid_time_note`: string (nullable) — optional
  - `source_id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `applied`: boolean — required
  - `message`: string — required
  - `proposal_id`: string (nullable) — optional

**Example prompts**

- “The marriage date I recorded is wrong — propose a correction with the deed I just found as the source.”
- “Fix the place on this census life event, it should say Franklin County, not Franklin Township — cite the record.”

<!-- doc-md:tool:event.get -->
### `event.get` (Preview)

Read a single life event by id: its anchor fields (kind, date, place, source) plus every participation recorded against it, live or not, each with its own epistemic status.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `date`: tagged union: (optional) — optional
    - `kind` = `about`:
      - `kind`: string [const: about] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `after`:
      - `kind`: string [const: after] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `before`:
      - `kind`: string [const: before] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `between`:
      - `kind`: string [const: between] — required
      - `value`: array of — required
        any
    - `kind` = `exact`:
      - `kind`: string [const: exact] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `range`:
      - `kind`: string [const: range] — required
      - `value`: array of — required
        any
  - `date_phrase`: string (nullable) — optional
  - `id`: string (uuid) — required
  - `kind`: object — required
    - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
    - `phrase`: string — optional
  - `kind_phrase`: string (nullable) — optional
  - `participations`: array of — required
    - `EventParticipantViewOutput`: object
      - `assertion`: string (uuid) — required
      - `persona`: string (uuid) — required
      - `persona_label`: string — required
      - `role`: object — required
        - `phrase`: string — optional
        - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
      - `status`: string — required
  - `place`: object (optional) — optional
    - `authority_id`: string (nullable) — optional
    - `coordinates`: object (optional) — optional
      - `latitude_nanodeg`: integer (int64) — required
      - `longitude_nanodeg`: integer (int64) — required
    - `jurisdictions`: array of — required
      string
    - `valid_time_note`: string (nullable) — optional
  - `source`: string (uuid) — required
  - `source_title`: string — required

**Example prompts**

- “Show me everyone who's recorded as a participant in this 1900 census household event.”
- “Pull up the full detail on this marriage event, including everyone's role and epistemic status.”

<!-- doc-md:tool:event.participant.add -->
### `event.participant.add` (Preview)

Add one more participation to an EXISTING life event (a persona playing a role in an anchor already recorded). An exact live duplicate (same persona, role, source) is a no-op returning the existing participation id — 'created: false' in the result tells you which happened.

**Parameters**

object
  - `attached`: array of — optional
    tagged union:
      - `kind` = `external_record`:
        - `id`: tagged union: — required
          - `kind` = `ancestry_record`:
            - `dbid`: string — required
            - `kind`: string [const: ancestry_record] — required
            - `record`: string — required
          - `kind` = `doi`:
            - `doi`: string — required
            - `kind`: string [const: doi] — required
          - `kind` = `family_search_ark`:
            - `ark`: string — required
            - `kind`: string [const: family_search_ark] — required
          - `kind` = `permalink`:
            - `kind`: string [const: permalink] — required
            - `url`: string — required
        - `kind`: string [const: external_record] — required
      - `kind` = `offline_record`:
        - `citation`: string — required
        - `kind`: string [const: offline_record] — required
      - `kind` = `source_blob`:
        - `blob`: string — required
        - `kind`: string [const: source_blob] — required
  - `event`: string (uuid) — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `participant_role`: object — required
    - `phrase`: string — optional
    - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
  - `persona`: string (uuid) — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `created`: boolean — required
  - `participation_id`: string (uuid) — required

**Example prompts**

- “I missed a household member — add Ida Fernwood as a daughter participant on the census event I already recorded.”
- “Add the officiant as a participant on this marriage event; I forgot him the first time.”

<!-- doc-md:tool:event.record -->
### `event.record` (Preview)

Record a new life event (birth, marriage, census, etc.) as a shared anchor plus one or more participations — one participation per persona named in it, each carrying its own role. Requires research_context (the log is a byproduct, same as assertion.capture). Every participation this tool records is agent-origin: it lands pending confirmation, never immediately usable as fact.

**Parameters**

object
  - `date`: tagged union: (optional) — optional
    - `kind` = `about`:
      - `kind`: string [const: about] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `after`:
      - `kind`: string [const: after] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `before`:
      - `kind`: string [const: before] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `between`:
      - `kind`: string [const: between] — required
      - `value`: array of — required
        any
    - `kind` = `exact`:
      - `kind`: string [const: exact] — required
      - `value`: object — required
        - `calendar`: string [enum: gregorian, julian] — required
        - `day`: integer (nullable) — optional
        - `dual_year`: integer (nullable) — optional
        - `month`: integer (nullable) — optional
        - `year`: integer (int32) — required
    - `kind` = `range`:
      - `kind`: string [const: range] — required
      - `value`: array of — required
        any
  - `kind`: object — required
    - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
    - `phrase`: string — optional
  - `kind_phrase`: string (nullable) — optional
  - `participants`: array of — required
    object
      - `attached`: array of — optional
        tagged union:
          - `kind` = `external_record`:
            - `id`: tagged union: — required
              - `kind` = `ancestry_record`:
                - `dbid`: string — required
                - `kind`: string [const: ancestry_record] — required
                - `record`: string — required
              - `kind` = `doi`:
                - `doi`: string — required
                - `kind`: string [const: doi] — required
              - `kind` = `family_search_ark`:
                - `ark`: string — required
                - `kind`: string [const: family_search_ark] — required
              - `kind` = `permalink`:
                - `kind`: string [const: permalink] — required
                - `url`: string — required
            - `kind`: string [const: external_record] — required
          - `kind` = `offline_record`:
            - `citation`: string — required
            - `kind`: string [const: offline_record] — required
          - `kind` = `source_blob`:
            - `blob`: string — required
            - `kind`: string [const: source_blob] — required
      - `evidence_class`: object (optional) — optional
        - `directness`: string [enum: direct, indirect] — required
        - `informant`: string [enum: primary, secondary] — required
        - `originality`: string [enum: original, derivative] — required
      - `persona`: string (uuid) — required
      - `role`: object — required
        - `phrase`: string — optional
        - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
  - `place`: object (optional) — optional
    - `authority_id`: string (nullable) — optional
    - `coordinates`: object (optional) — optional
      - `latitude_nanodeg`: integer (int64) — required
      - `longitude_nanodeg`: integer (int64) — required
    - `jurisdictions`: array of — required
      string
    - `valid_time_note`: string (nullable) — optional
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `event_id`: string (uuid) — required
  - `participation_ids`: array of — required
    string (uuid)

**Example prompts**

- “Record this 1900 census household as a life event, with Arthur, Martha, and Henry as participants.”
- “Log a marriage event for these two personas, citing the church register I just transcribed.”

<!-- doc-md:tool:note.add -->
### `note.add` (Preview)

Attach a free-text note to zero or more anchors (persona, person, source, assertion, life event, or citation). Notes are annotation, not evidence — no research_context is required or accepted.

**Parameters**

object
  - `anchors`: array of — optional
    tagged union:
      - `kind` = `assertion`:
        - `id`: string (uuid) — required
        - `kind`: string [const: assertion] — required
      - `kind` = `citation`:
        - `id`: string (uuid) — required
        - `kind`: string [const: citation] — required
      - `kind` = `life_event`:
        - `id`: string (uuid) — required
        - `kind`: string [const: life_event] — required
      - `kind` = `person`:
        - `id`: string (uuid) — required
        - `kind`: string [const: person] — required
      - `kind` = `persona`:
        - `id`: string (uuid) — required
        - `kind`: string [const: persona] — required
      - `kind` = `source`:
        - `id`: string (uuid) — required
        - `kind`: string [const: source] — required
  - `citations`: array of — optional
    string (uuid)
  - `text`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `note_id`: string (uuid) — required

**Example prompts**

- “Add a note to this source reminding me that the microfilm quality was poor and some names are hard to read.”
- “Attach a note to this persona saying I still need to verify the spelling of the surname.”

<!-- doc-md:tool:persona.name_pieces.set -->
### `persona.name_pieces.set` (Preview)

Set structured name pieces (given, surname, prefixes, suffixes, nickname, verbatim display text) for a persona. An idempotent replace: any existing live name pieces from the same source are superseded. Requires research_context — a name is a claim about what the source says.

**Parameters**

object
  - `persona`: string (uuid) — required
  - `pieces`: object — required
    - `display`: string — required
    - `given`: array of — optional
      string
    - `name_type`: tagged union: (optional) — optional
      - `type` = `aka`:
        - `type`: string [const: aka] — required
      - `type` = `birth`:
        - `type`: string [const: birth] — required
      - `type` = `immigrant`:
        - `type`: string [const: immigrant] — required
      - `type` = `maiden`:
        - `type`: string [const: maiden] — required
      - `type` = `married`:
        - `type`: string [const: married] — required
      - `type` = `other`:
        - `phrase`: string — required
        - `type`: string [const: other] — required
      - `type` = `professional`:
        - `type`: string [const: professional] — required
    - `nickname`: array of — optional
      string
    - `prefix`: array of — optional
      string
    - `suffix`: array of — optional
      string
    - `surname`: array of — optional
      string
    - `surname_prefix`: array of — optional
      string
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `set`: boolean — required

**Example prompts**

- “Set the structured name pieces for this persona from the census: given name Arthur, surname Fernwood.”
- “Record the given name, surname, and suffix as they appear verbatim in this source, replacing what's there now.”

<!-- doc-md:tool:transcription.claim -->
### `transcription.claim` (Preview)

Record a transcription claim: what you read a specific source image as saying, distinct from the image itself and refutable like any other claim. Requires research_context, exactly like assertion.capture, since a transcription claim is a research act too.

**Parameters**

object
  - `claim`: string — required
  - `of_blob`: string — required
  - `persona`: string (uuid) — required
  - `region`: object (optional) — optional
    - `height`: number (double) — required
    - `width`: number (double) — required
    - `x`: number (double) — required
    - `y`: number (double) — required
  - `research_context`: object — required
    - `search_context`: string — required
    - `session`: string (uuid) — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `claim`: string — required
  - `epistemic_status`: string — required
  - `event_context`: object (optional) — optional
    - `date`: tagged union: (optional) — optional
      - `kind` = `about`:
        - `kind`: string [const: about] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `after`:
        - `kind`: string [const: after] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `before`:
        - `kind`: string [const: before] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `between`:
        - `kind`: string [const: between] — required
        - `value`: array of — required
          any
      - `kind` = `exact`:
        - `kind`: string [const: exact] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `range`:
        - `kind`: string [const: range] — required
        - `value`: array of — required
          any
    - `event_id`: string (uuid) — required
    - `kind`: object — required
      - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
      - `phrase`: string — optional
    - `kind_phrase`: string (nullable) — optional
    - `place`: object (optional) — optional
      - `authority_id`: string (nullable) — optional
      - `coordinates`: object (optional) — optional
        - `latitude_nanodeg`: integer (int64) — required
        - `longitude_nanodeg`: integer (int64) — required
      - `jurisdictions`: array of — required
        string
      - `valid_time_note`: string (nullable) — optional
    - `sibling_participations`: array of — required
      - `SiblingParticipationOutput`: object
        - `persona_id`: string (uuid) — required
        - `persona_label_id`: string — required
        - `role`: object — required
          - `phrase`: string — optional
          - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
        - `status`: string — required
    - `source_id`: string (uuid) — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `id`: string (uuid) — required
  - `next_step`: string — required
  - `persona`: string (uuid) — required
  - `source`: string (uuid) — required

**Example prompts**

- “I've read the handwriting on this record image — log what it says as a transcription claim, tied to that image.”
- “Record my transcription of this ship manifest so someone can check it against the original scan later.”

### Sources & citations

<!-- doc-md:tool:citation.create -->
### `citation.create` (Preview)

Create a citation from typed elements you supply directly (author, title, publication, locator, and so on) — the general-purpose path, for a source that doesn't fit one of the registered citation templates.

**Parameters**

object
  - `elements`: array of — required
    tagged union:
      - `kind` = `access_date`:
        - `kind`: string [const: access_date] — required
        - `value`: string — required
      - `kind` = `author`:
        - `kind`: string [const: author] — required
        - `value`: string — required
      - `kind` = `free_text`:
        - `kind`: string [const: free_text] — required
        - `value`: string — required
      - `kind` = `identifier`:
        - `id`: tagged union: — required
          - `kind` = `ancestry_record`:
            - `dbid`: string — required
            - `kind`: string [const: ancestry_record] — required
            - `record`: string — required
          - `kind` = `doi`:
            - `doi`: string — required
            - `kind`: string [const: doi] — required
          - `kind` = `family_search_ark`:
            - `ark`: string — required
            - `kind`: string [const: family_search_ark] — required
          - `kind` = `permalink`:
            - `kind`: string [const: permalink] — required
            - `url`: string — required
        - `kind`: string [const: identifier] — required
      - `kind` = `locator`:
        - `kind`: string [const: locator] — required
        - `value`: string — required
      - `kind` = `other`:
        - `kind`: string [const: other] — required
        - `label`: string — required
        - `value`: string — required
      - `kind` = `publication`:
        - `kind`: string [const: publication] — required
        - `value`: string — required
      - `kind` = `repository`:
        - `kind`: string [const: repository] — required
        - `value`: string — required
      - `kind` = `source_type`:
        - `kind`: string [const: source_type] — required
        - `value`: string — required
      - `kind` = `title`:
        - `kind`: string [const: title] — required
        - `value`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `citation_id`: string (uuid) — required

**Example prompts**

- “Build a citation for this source by hand — here's the author, title, and where I found it.”
- “This record doesn't fit any of the citation templates. Create a citation from the elements I give you directly.”

<!-- doc-md:tool:citation.elements.set -->
### `citation.elements.set` (Preview)

Replace a citation's current element set with a new revision (full replacement, not a merge). Editing elements on shared evidence mutates every attached use, so this is GatedWrite: a human actor applies directly and gets the new render back; an agent actor only ever files a Proposal for a human to approve.

**Parameters**

object
  - `citation_id`: string (uuid) — required
  - `elements`: map: string → string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `applied`: boolean — required
  - `message`: string — required
  - `proposal_id`: string (nullable) — optional
  - `rendered`: object (optional) — optional
    - `bibliography`: object (optional) — optional
      - `html`: string — required
      - `text`: string — required
    - `csl`: object (nullable) — required
    - `first`: object (optional) — optional
      - `html`: string — required
      - `text`: string — required
    - `subsequent`: object (optional) — optional
      - `html`: string — required
      - `text`: string — required
    - `warnings`: array of — required
      string
  - `revision`: integer (nullable) — optional

**Example prompts**

- “Update the citation elements on this source now that I found the correct enumeration district.”
- “Replace the film number on this citation — I had the wrong one recorded.”

<!-- doc-md:tool:citation.render -->
### `citation.render` (Preview)

Render a citation's three Chicago-style forms (first footnote, subsequent short form, bibliography) plus a CSL-JSON interchange object. Pass either citation_id (a stored citation's current elements) or template_id + elements (an unsaved draft — the live-preview arm). Missing required fields are a structured error, not a degraded render.

**Parameters**

object
  - `citation_id`: string (nullable) — optional
  - `elements`: map: string → string — optional
  - `form`: string [enum: first, subsequent, bibliography, all] — optional
  - `template_id`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `bibliography`: object (optional) — optional
    - `html`: string — required
    - `text`: string — required
  - `csl`: object (nullable) — required
  - `first`: object (optional) — optional
    - `html`: string — required
    - `text`: string — required
  - `subsequent`: object (optional) — optional
    - `html`: string — required
    - `text`: string — required
  - `warnings`: array of — required
    string

**Example prompts**

- “Show me the Chicago-style footnote and bibliography entry for this citation.”
- “Give me a live preview of how this census template would render with the field values I'm about to enter.”

<!-- doc-md:tool:citation.source_type.set -->
### `citation.source_type.set` (Preview)

Record what kind of record a source is by attaching a citation template id to an existing citation — the only way an imported source reaches a template, since import never infers one. Adds the classification and changes nothing else: every existing element survives verbatim, including ones the chosen template has no field for. Re-classifying replaces the type. Missing required fields are reported, not enforced — labelling is not authoring. GatedWrite: a human actor applies directly; an agent actor only ever files a Proposal for a human to approve.

**Parameters**

object
  - `citation_id`: string (uuid) — required
  - `template_id`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `applied`: boolean — required
  - `message`: string — required
  - `missing_required`: array (nullable) — optional
  - `proposal_id`: string (nullable) — optional
  - `revision`: integer (nullable) — optional
  - `template_id`: string (nullable) — optional

**Example prompts**

- “This source came in from a GEDCOM import with no citation template — classify it as a census population schedule so it can render proper Chicago-style prose.”
- “I mis-clicked earlier and classified this source as a will. Reclassify it as a probate file instead.”

<!-- doc-md:tool:citation.templates.list -->
### `citation.templates.list` (Preview)

List the 30 built-in Chicago-style citation templates (optionally filtered by category), each with its field list. Render strings are not included — call citation.render to see formatted output; agents author elements, they don't reimplement rendering.

**Parameters**

object
  - `category`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `registry_version`: integer (uint32) — required
  - `templates`: array of — required
    - `CitationTemplateSummary`: object
      - `category`: string — required
      - `fields`: array of — required
        - `CitationTemplateFieldSummary`: object
          - `element`: string — required
          - `id`: string — required
          - `label`: string — required
          - `required`: boolean — required
      - `id`: string — required
      - `label`: string — required

**Example prompts**

- “What citation templates are available for census records?”
- “List every built-in citation template so I can find the right one for a church baptism record.”

<!-- doc-md:tool:source.attach_blob -->
### `source.attach_blob` (Preview)

Attach an existing content-addressed blob to a source that already exists. Same posture as assertion.evidence.attach — a direct write for both human and agent actors.

**Parameters**

object
  - `blob`: string — required
  - `source`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `blobs`: array of — required
    string
  - `derived_from`: string (nullable) — optional
  - `id`: string (uuid) — required
  - `title`: string — required

**Example prompts**

- “I already stored the second page of this census image as a blob — attach it to the source record too.”
- “Attach the higher-resolution scan I just uploaded to this existing source, alongside the one already there.”

<!-- doc-md:tool:source.cited_by -->
### `source.cited_by` (Preview)

What rests on this source: the claims that cite it, each with the persona it was captured against and — once that persona has been folded into an identity conclusion — the person it belongs to. The inverse of assertion.get's source field, and the only way to answer "what would I lose if this source turned out to be wrong?". Named rows, so prefer this over assertion.list's 'source' filter unless you need to narrow by kind or epistemic state. Paginated (default 50, max 500 — echo next_cursor back as cursor); 'total' reports the whole count behind the page, and matches the 'claim_count' source.list reports for the same source.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `source_id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `claims`: array of — required
    - `SourceCitedClaimView`: object
      - `assertion`: string (uuid) — required
      - `claim`: string — required
      - `epistemic_status`: string — required
      - `kind`: string — required
      - `person`: string (nullable) — optional
      - `person_name`: string (nullable) — optional
      - `persona`: string (uuid) — required
      - `persona_label`: string — required
  - `next_cursor`: string (nullable) — optional
  - `total`: integer (uint) — required

**Example prompts**

- “What claims in my tree rest on this marriage record?”
- “If this source turned out to be wrong, which facts would I lose?”
- “List everyone whose evidence comes from the GEDCOM I imported.”

<!-- doc-md:tool:source.create -->
### `source.create` (Preview)

Create a new Source and its Citation together from a chosen template and its field values, in one atomic step. Same posture as add_source/create_citation — a direct write for both human and agent actors, since this is source infrastructure, not evidence capture.

**Parameters**

object
  - `elements`: map: string → string — required
  - `template_id`: string — required
  - `title`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `citation_id`: string (uuid) — required
  - `created`: boolean — required
  - `rendered`: object — required
    - `bibliography`: object (optional) — optional
      - `html`: string — required
      - `text`: string — required
    - `csl`: object (nullable) — required
    - `first`: object (optional) — optional
      - `html`: string — required
      - `text`: string — required
    - `subsequent`: object (optional) — optional
      - `html`: string — required
      - `text`: string — required
    - `warnings`: array of — required
      string
  - `source_id`: string (uuid) — required

**Example prompts**

- “Create a source and citation together for this 1900 census record using the census template I found.”
- “Set up a new source from the church register template with the field values I just transcribed.”

### Research sessions & log

<!-- doc-md:tool:research.log.capture -->
### `research.log.capture` (Preview)

Record a search-context log entry in one action (D1) — the design law's flagship verb. Auto-links to the given source/persona/plan item, auto-opens or reuses the active research session, and never requires a form. 'found_nothing' marks a negative search; formalize it into NegativeEvidence later with research.log.negative.record.

**Parameters**

object
  - `found_nothing`: boolean — optional
  - `persona`: string (nullable) — optional
  - `question`: string (nullable) — optional
  - `search_context`: string — required
  - `session`: string (nullable) — optional
  - `source`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `linked`: object — required
    - `persona`: string (nullable) — optional
    - `plan_item`: string (nullable) — optional
    - `source`: string (nullable) — optional
  - `log_entry_id`: string (uuid) — required
  - `session_id`: string (uuid) — required

**Example prompts**

- “Log that I searched the county marriage index for this persona and found nothing — mark it as a negative search.”
- “One-step log: I checked FamilySearch for this source and it confirmed the household composition.”

<!-- doc-md:tool:research.log.detail.append -->
### `research.log.detail.append` (Preview)

Append a detail line to an existing research-log entry without mutating the original entry (AC1) — use this for context added after the fact (a page number found later, a correction to what was searched).

**Parameters**

object
  - `detail`: string — required
  - `log_entry`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `log_entry_id`: string (uuid) — required

**Example prompts**

- “I found the exact page number after the fact — append that detail to the log entry I already made.”
- “Add a correction detail to my earlier log entry: it was actually enumeration district 42, not 41.”

<!-- doc-md:tool:research.log.entry.add -->
### `research.log.entry.add` (Preview)

Record a search/source context entry in the research log for a session. The research log is what a human later checks to judge whether a search was reasonably exhaustive (GPS component 2) — junk filler is visible, not hidden.

**Parameters**

object
  - `search_context`: string — required
  - `session`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `log_entry_id`: string (uuid) — required

**Example prompts**

- “Log that I checked the Ohio county marriage index for John Smith and found nothing.”
- “Record in the research log that I searched FamilySearch's 1900 census collection for this household.”

<!-- doc-md:tool:research.log.entry.retract -->
### `research.log.entry.retract` (Preview)

Retract a research-log entry — removes it from every active-view read (research.log.list, the plan/log page) without deleting it from the log. Use this for a duplicate or wrongly-recorded entry. There is no un-retract: record a fresh entry to recover.

**Parameters**

object
  - `log_entry`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “I logged the same search twice — retract the duplicate entry.”
- “Retract that log entry, I recorded it against the wrong research question.”

<!-- doc-md:tool:research.log.entry.text.set -->
### `research.log.entry.text.set` (Preview)

Correct a research-log entry's search context. Records a full-replacement revision — the original entry stays in the log; every read shows the latest revision.

**Parameters**

object
  - `log_entry`: string (uuid) — required
  - `search_context`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “Correct the search context on that last log entry — I searched the state index, not the county one.”
- “That log entry says the wrong parish; change it to St. Brigid's.”

<!-- doc-md:tool:research.log.get -->
### `research.log.get` (Preview)

Read a single research-log entry by id: its search context, attestation, timestamp, the question it belongs to, whether it recorded a null result, and every detail ever appended to it (uncapped — research.log.list caps inline details per row). The read half of research.log.detail.append, which previously had no route or verb to read details back through.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `attestation`: string — required
  - `details`: array of — required
    - `LogDetailView`: object
      - `attestation`: string — required
      - `detail`: string — required
      - `id`: string (uuid) — required
      - `recorded_at`: integer (int64) — required
  - `found_nothing`: boolean — required
  - `id`: string (uuid) — required
  - `persona`: string (nullable) — optional
  - `plan_item`: string (nullable) — optional
  - `question`: string (nullable) — optional
  - `recorded_at`: integer (int64) — required
  - `search_context`: string — required
  - `session`: string (uuid) — required
  - `source`: string (nullable) — optional

**Example prompts**

- “Show me the full record for this log entry, including every detail anyone's appended to it.”
- “What does this log entry say — the original search context plus any follow-up notes?”

<!-- doc-md:tool:research.log.list -->
### `research.log.list` (Preview)

List research log entries, filterable by question, session, persona, or source, each with its durable source/persona/plan-item link. Complements research.question.log.get, which lists raw search-context strings for a question without link info.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `persona`: string (nullable) — optional
  - `question`: string (nullable) — optional
  - `session`: string (nullable) — optional
  - `source`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `entries`: array of — required
    - `ResearchLogListEntryView`: object
      - `attestation`: string — required
      - `details`: array of — required
        - `LogDetailView`: object
          - `attestation`: string — required
          - `detail`: string — required
          - `id`: string (uuid) — required
          - `recorded_at`: integer (int64) — required
      - `found_nothing`: boolean — required
      - `id`: string (uuid) — required
      - `persona`: string (nullable) — optional
      - `plan_item`: string (nullable) — optional
      - `question`: string (nullable) — optional
      - `recorded_at`: integer (int64) — required
      - `search_context`: string — required
      - `session`: string (uuid) — required
      - `source`: string (nullable) — optional
  - `next_cursor`: string (nullable) — optional
  - `total`: integer (uint) — required

**Example prompts**

- “List every research-log entry linked to this persona.”
- “Show me all the log entries tied to this source across every session.”

<!-- doc-md:tool:research.log.negative.record -->
### `research.log.negative.record` (Preview)

Formalize a found-nothing log entry into NegativeEvidence (D6): records what was expected to be found, wasn't, and what that absence implies. Capture the null search first with research.log.capture {found_nothing: true}; call this afterward when ready to reason about the absence.

**Parameters**

object
  - `expectation`: string — required
  - `inference`: string — required
  - `log_entry`: string (uuid) — required
  - `persona`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `negative_evidence_id`: string (uuid) — required

**Example prompts**

- “Formalize that failed marriage-record search into negative evidence — what I expected to find and didn't.”
- “I already logged the null search; now record it as formal negative evidence with what its absence implies.”

<!-- doc-md:tool:research.question.close -->
### `research.question.close` (Preview)

Close a research question once its line of inquiry is settled. Optionally record a disposition (answered | abandoned | superseded | merged) and a reason; future session briefs surface both as the retired lead's record. Does not delete anything — the question and its log remain.

**Parameters**

object
  - `disposition`: string [enum: answered, abandoned, superseded, merged] (optional) — optional
  - `id`: string (uuid) — required
  - `reason`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `closed`: boolean — required

**Example prompts**

- “Close the research question about Arthur's birthplace — we settled it.”
- “This line of inquiry into the missing marriage record is a dead end for now, close it out.”

<!-- doc-md:tool:research.question.log.get -->
### `research.question.log.get` (Preview)

Read the full research log for a research question, across all its sessions — the record a human checks for reasonably-exhaustive-search.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `question`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `entries`: array of — required
    - `LogEntryView`: object
      - `attestation`: string — required
      - `id`: string (uuid) — required
      - `search_context`: string — required
      - `session`: string (uuid) — required
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “Show me the full research log for this question, across every session.”
- “What have I already searched for this research question? I don't want to duplicate work.”

<!-- doc-md:tool:research.question.open -->
### `research.question.open` (Preview)

Open a new research question — the GPS-reasoning unit of work. Every capture and log entry should trace back to a question; open one before starting a research session.

**Parameters**

object
  - `question`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `research_question_id`: string (uuid) — required

**Example prompts**

- “Open a research question for Arthur Fernwood's birth date and place.”
- “Start a new line of inquiry: who were John Smith's parents?”

<!-- doc-md:tool:research.session.end -->
### `research.session.end` (Preview)

End a research session. Does not close the parent research question — a question may span many sessions.

**Parameters**

object
  - `session`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ended`: boolean — required

**Example prompts**

- “I'm done researching for today — end this research session.”
- “Wrap up this session; I'll pick the question back up tomorrow.”

<!-- doc-md:tool:research.session.start -->
### `research.session.start` (Preview)

Start a research session under an existing research question. Sessions group the search/source log entries that justify a reasonably-exhaustive-search claim.

**Parameters**

object
  - `question`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `session_id`: string (uuid) — required

**Example prompts**

- “Start a research session under this question so we can log what we find.”
- “Begin a new session for today's research on Arthur's parents.”

### Research planning & GPS coverage

<!-- doc-md:tool:research.plan.get -->
### `research.plan.get` (Preview)

Read a research question's plan: its ordered candidate-source items (with done state and auto-linked log-entry counts) plus the D2 GPS checklist. The plan items ARE the minimal to-dos — there is no separate task system.

**Parameters**

object
  - `question`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `coverage`: object — required
    - `analysis_correlation`: boolean — required
    - `complete_citations`: boolean — required
    - `conflict_resolution`: boolean — required
    - `exhaustive_search`: tagged union: — required
      - `status` = `claimable`:
        - `status`: string [const: claimable] — required
      - `status` = `in_progress`:
        - `covered`: integer (uint32) — required
        - `status`: string [const: in_progress] — required
        - `total`: integer (uint32) — required
      - `status` = `not_started`:
        - `status`: string [const: not_started] — required
    - `written_conclusion`: boolean — required
  - `items`: array of — required
    - `PlanItemView`: object
      - `description`: string — required
      - `done`: boolean — required
      - `id`: string (uuid) — required
      - `log_entry_count`: integer (uint32) — required
      - `position`: integer (uint32) — required
      - `source_id`: string (nullable) — optional

**Example prompts**

- “Show me the research plan for this question — what sources are still on the to-do list?”
- “Pull up the plan and coverage checklist for this research question.”

<!-- doc-md:tool:research.plan.item.add -->
### `research.plan.item.add` (Preview)

Add a candidate-source item to a research question's plan (an ordered to-do naming a source to check, optionally resolved to a stored SourceId). Appended at the end by default.

**Parameters**

object
  - `description`: string — required
  - `position`: integer (nullable) — optional
  - `question`: string (uuid) — required
  - `source_id`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `item_id`: string (uuid) — required

**Example prompts**

- “Add 'check the 1910 census' as the next item on this question's research plan.”
- “I want to check the church baptism register next — add it to the plan.”

<!-- doc-md:tool:research.plan.item.description.set -->
### `research.plan.item.description.set` (Preview)

Correct a research-plan item's description (e.g. the wrong repository was named). Records a full-replacement revision, same shape as research.question.text.set.

**Parameters**

object
  - `description`: string — required
  - `item`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “Fix the third plan item — it should say the 1880 federal census, not the 1870.”
- “Reword this plan item to name the specific repository I need to visit.”

<!-- doc-md:tool:research.plan.item.done.set -->
### `research.plan.item.done.set` (Preview)

Mark a research-plan item done or not-done (D3): an idempotent, reversible workflow flag, not an epistemic claim — it contributes to coverage display but never to an exhaustive-search claim on its own (only a human coverage confirmation does).

**Parameters**

object
  - `done`: boolean — required
  - `item`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `done`: boolean — required
  - `item`: string (uuid) — required

**Example prompts**

- “Mark the 1900 census plan item as done — I already checked it.”
- “I haven't actually looked at that source yet, mark the plan item as not-done.”

<!-- doc-md:tool:research.plan.item.remove -->
### `research.plan.item.remove` (Preview)

Remove a research-plan item. Does not affect any log entries already linked to it.

**Parameters**

object
  - `item`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “Remove that plan item, the source turned out not to exist for this county.”
- “Take the duplicate plan item off this question's plan.”

<!-- doc-md:tool:research.plan.item.reorder -->
### `research.plan.item.reorder` (Preview)

Move a research-plan item to a new position in its question's ordered plan.

**Parameters**

object
  - `item`: string (uuid) — required
  - `position`: integer (uint32) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “Move the census plan item to the top of the list, I want to check it first.”
- “Reorder the plan so the church register comes before the newspaper archive.”

<!-- doc-md:tool:research.question.coverage.get -->
### `research.question.coverage.get` (Preview)

Read a research question's GPS checklist (D2): the five elements of a proof-standard argument, including the three-state exhaustive-search status. A pure projection — agent-reported-only logs can never reach 'claimable' (the domain's own honesty rule).

**Parameters**

object
  - `question`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `gps`: object — required
    - `analysis_correlation`: boolean — required
    - `complete_citations`: boolean — required
    - `conflict_resolution`: boolean — required
    - `exhaustive_search`: tagged union: — required
      - `status` = `claimable`:
        - `status`: string [const: claimable] — required
      - `status` = `in_progress`:
        - `covered`: integer (uint32) — required
        - `status`: string [const: in_progress] — required
        - `total`: integer (uint32) — required
      - `status` = `not_started`:
        - `status`: string [const: not_started] — required
    - `written_conclusion`: boolean — required

**Example prompts**

- “Show me the GPS checklist for this research question — how close are we to a claimable exhaustive search?”
- “What's the current coverage status on this question's proof-standard checklist?”

<!-- doc-md:tool:research.question.get -->
### `research.question.get` (Preview)

Read a single research question by id.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `id`: string (uuid) — required
  - `question`: string — required
  - `status`: string — required
  - `subjects`: array of — required
    - `SubjectRefInput`: tagged union:
      - `kind` = `life_event`:
        - `id`: string (uuid) — required
        - `kind`: string [const: life_event] — required
      - `kind` = `person`:
        - `id`: string (uuid) — required
        - `kind`: string [const: person] — required
      - `kind` = `persona`:
        - `id`: string (uuid) — required
        - `kind`: string [const: persona] — required

**Example prompts**

- “Pull up the details on the research question about Arthur's parents.”
- “Show me the current status of this research question.”

<!-- doc-md:tool:research.question.list -->
### `research.question.list` (Preview)

List research questions, optionally filtered by status (open/closed) or a linked subject (persona/person/life event). Each result includes its current subjects and status.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `status`: string [enum: open, closed] (optional) — optional
  - `subject`: tagged union: (optional) — optional
    - `kind` = `life_event`:
      - `id`: string (uuid) — required
      - `kind`: string [const: life_event] — required
    - `kind` = `person`:
      - `id`: string (uuid) — required
      - `kind`: string [const: person] — required
    - `kind` = `persona`:
      - `id`: string (uuid) — required
      - `kind`: string [const: persona] — required
  - `tree_id`: string — optional

**Output shape**

object
  - `items`: array of — required
    - `QuestionView`: object
      - `id`: string (uuid) — required
      - `question`: string — required
      - `status`: string — required
      - `subjects`: array of — required
        - `SubjectRefInput`: tagged union:
          - `kind` = `life_event`:
            - `id`: string (uuid) — required
            - `kind`: string [const: life_event] — required
          - `kind` = `person`:
            - `id`: string (uuid) — required
            - `kind`: string [const: person] — required
          - `kind` = `persona`:
            - `id`: string (uuid) — required
            - `kind`: string [const: persona] — required
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “List every open research question about this persona.”
- “Show me all closed research questions so I can review what's been settled.”

<!-- doc-md:tool:research.question.subjects.set -->
### `research.question.subjects.set` (Preview)

Replace a research question's linked subjects with the given set (diff-applied: links what's missing, unlinks what's no longer present). Subjects are personas, concluded persons, or life events — there is no Family entity (CR-3).

**Parameters**

object
  - `question`: string (uuid) — required
  - `subjects`: array of — required
    tagged union:
      - `kind` = `life_event`:
        - `id`: string (uuid) — required
        - `kind`: string [const: life_event] — required
      - `kind` = `person`:
        - `id`: string (uuid) — required
        - `kind`: string [const: person] — required
      - `kind` = `persona`:
        - `id`: string (uuid) — required
        - `kind`: string [const: persona] — required
  - `tree_id`: string — optional

**Output shape**

object
  - `question`: object — required
    - `id`: string (uuid) — required
    - `question`: string — required
    - `status`: string — required
    - `subjects`: array of — required
      - `SubjectRefInput`: tagged union:
        - `kind` = `life_event`:
          - `id`: string (uuid) — required
          - `kind`: string [const: life_event] — required
        - `kind` = `person`:
          - `id`: string (uuid) — required
          - `kind`: string [const: person] — required
        - `kind` = `persona`:
          - `id`: string (uuid) — required
          - `kind`: string [const: persona] — required

**Example prompts**

- “Link this research question to both Arthur and his father — replace whatever subjects are on it now.”
- “Update this question's subjects to include the newly discovered sibling.”

<!-- doc-md:tool:research.question.text.set -->
### `research.question.text.set` (Preview)

Correct a research question's own text (e.g. a typo). Records a full-replacement revision — the original wording stays in the event log; every read shows the latest revision.

**Parameters**

object
  - `question`: string (uuid) — required
  - `text`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `ok`: boolean — required

**Example prompts**

- “I typed this research question wrong — change it to "Who were Mary Ellen Doyle's parents, and where was she born?"”
- “Reword the open question on this persona so it names the county, not just the state.”

### Review & pending lane

<!-- doc-md:tool:assertion.get -->
### `assertion.get` (Preview)

Read a single assertion by id, with its evidence class and epistemic status.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `claim`: string — required
  - `epistemic_status`: string — required
  - `event_context`: object (optional) — optional
    - `date`: tagged union: (optional) — optional
      - `kind` = `about`:
        - `kind`: string [const: about] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `after`:
        - `kind`: string [const: after] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `before`:
        - `kind`: string [const: before] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `between`:
        - `kind`: string [const: between] — required
        - `value`: array of — required
          any
      - `kind` = `exact`:
        - `kind`: string [const: exact] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `range`:
        - `kind`: string [const: range] — required
        - `value`: array of — required
          any
    - `event_id`: string (uuid) — required
    - `kind`: object — required
      - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
      - `phrase`: string — optional
    - `kind_phrase`: string (nullable) — optional
    - `place`: object (optional) — optional
      - `authority_id`: string (nullable) — optional
      - `coordinates`: object (optional) — optional
        - `latitude_nanodeg`: integer (int64) — required
        - `longitude_nanodeg`: integer (int64) — required
      - `jurisdictions`: array of — required
        string
      - `valid_time_note`: string (nullable) — optional
    - `sibling_participations`: array of — required
      - `SiblingParticipationOutput`: object
        - `persona_id`: string (uuid) — required
        - `persona_label_id`: string — required
        - `role`: object — required
          - `phrase`: string — optional
          - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
        - `status`: string — required
    - `source_id`: string (uuid) — required
  - `evidence_class`: object (optional) — optional
    - `directness`: string [enum: direct, indirect] — required
    - `informant`: string [enum: primary, secondary] — required
    - `originality`: string [enum: original, derivative] — required
  - `id`: string (uuid) — required
  - `next_step`: string — required
  - `persona`: string (uuid) — required
  - `source`: string (uuid) — required

**Example prompts**

- “Pull up the details on this assertion, including its epistemic status.”
- “Is this claim confirmed or still pending? Show me the full assertion record.”

<!-- doc-md:tool:assertion.pending.list -->
### `assertion.pending.list` (Preview)

List all agent-origin assertions currently awaiting human confirmation (the pending lane). Each result explains its epistemic status and the next step needed before it can support a proof argument.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `epistemic_status`: string — required
  - `items`: array of — required
    - `PendingAssertionView`: object
      - `claim`: string — required
      - `event_context`: object (optional) — optional
        - `date`: tagged union: (optional) — optional
          - `kind` = `about`:
            - `kind`: string [const: about] — required
            - `value`: object — required
              - `calendar`: string [enum: gregorian, julian] — required
              - `day`: integer (nullable) — optional
              - `dual_year`: integer (nullable) — optional
              - `month`: integer (nullable) — optional
              - `year`: integer (int32) — required
          - `kind` = `after`:
            - `kind`: string [const: after] — required
            - `value`: object — required
              - `calendar`: string [enum: gregorian, julian] — required
              - `day`: integer (nullable) — optional
              - `dual_year`: integer (nullable) — optional
              - `month`: integer (nullable) — optional
              - `year`: integer (int32) — required
          - `kind` = `before`:
            - `kind`: string [const: before] — required
            - `value`: object — required
              - `calendar`: string [enum: gregorian, julian] — required
              - `day`: integer (nullable) — optional
              - `dual_year`: integer (nullable) — optional
              - `month`: integer (nullable) — optional
              - `year`: integer (int32) — required
          - `kind` = `between`:
            - `kind`: string [const: between] — required
            - `value`: array of — required
              any
          - `kind` = `exact`:
            - `kind`: string [const: exact] — required
            - `value`: object — required
              - `calendar`: string [enum: gregorian, julian] — required
              - `day`: integer (nullable) — optional
              - `dual_year`: integer (nullable) — optional
              - `month`: integer (nullable) — optional
              - `year`: integer (int32) — required
          - `kind` = `range`:
            - `kind`: string [const: range] — required
            - `value`: array of — required
              any
        - `event_id`: string (uuid) — required
        - `kind`: object — required
          - `kind`: any [enum: birth, death, marriage, divorce, marriage_banns, engagement, baptism, christening, burial, cremation, adoption, census, residence, emigration, immigration, naturalization, probate, will, graduation, retirement, other] — required
          - `phrase`: string — optional
        - `kind_phrase`: string (nullable) — optional
        - `place`: object (optional) — optional
          - `authority_id`: string (nullable) — optional
          - `coordinates`: object (optional) — optional
            - `latitude_nanodeg`: integer (int64) — required
            - `longitude_nanodeg`: integer (int64) — required
          - `jurisdictions`: array of — required
            string
          - `valid_time_note`: string (nullable) — optional
        - `sibling_participations`: array of — required
          - `SiblingParticipationOutput`: object
            - `persona_id`: string (uuid) — required
            - `persona_label_id`: string — required
            - `role`: object — required
              - `phrase`: string — optional
              - `role`: any [enum: principal, spouse, parent, child, witness, informant, officiant, clergy, godparent, enumerator, household_member, head_of_household, other] — required
            - `status`: string — required
        - `source_id`: string (uuid) — required
      - `evidence_class`: object (optional) — optional
        - `directness`: string [enum: direct, indirect] — required
        - `informant`: string [enum: primary, secondary] — required
        - `originality`: string [enum: original, derivative] — required
      - `id`: string (uuid) — required
      - `persona`: string (uuid) — required
      - `source`: string (uuid) — required
  - `next_cursor`: string (nullable) — optional
  - `next_step`: string — required
  - `state`: string — required

**Example prompts**

- “What claims are you waiting on me to confirm right now?”
- “Show me everything in the pending lane so I can review it before my next session.”

<!-- doc-md:tool:proposal.list -->
### `proposal.list` (Preview)

List all proposed operations (redactions, exports) awaiting human disposition. Proposals are never auto-executed — a human must approve or deny each one.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `items`: array of — required
    - `ProposalView`: object
      - `id`: string (uuid) — required
      - `op_kind`: string — required
      - `status`: string — required
  - `next_cursor`: string (nullable) — optional
  - `next_step`: string — required

**Example prompts**

- “What proposals are waiting on my approval?”
- “Show me every pending redaction or export request so I can decide on them.”

### Proof arguments

<!-- doc-md:tool:conflict.resolve -->
### `conflict.resolve` (Preview)

Settle one conflict by naming the proof argument that concludes it — the only mechanism that closes a contradiction here. The argument must already exist — the id proof.argument.record returns, which for an agent caller is the id its proposal yields once a human approves it; competing claims are never merged and never silently picked between, so superseding a claim does not close its conflict. A conflict is settled once: resolving one already settled is an error, and resolving one leaves every other conflict open. Reports how many conflicts remain open afterwards, read back off the tree.

**Parameters**

object
  - `conflict`: string (uuid) — required
  - `proof_argument`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `conflict`: string (uuid) — required
  - `open_conflicts_remaining`: integer (uint) — required
  - `proof_argument`: string (uuid) — required

**Example prompts**

- “Close out that birth-date conflict using the proof argument we just wrote.”
- “Settle this contradiction — the argument concluding the 1870 date is the one to cite.”
- “Mark the conflict on Temperance Wynwood as resolved by argument 019fd3de.”

<!-- doc-md:tool:proof.argument.export -->
### `proof.argument.export` (Preview)

Export a recorded proof argument as a versioned heartwood.proof_argument.v1 document: question, section-by-section body (evidence summary, conflict resolution, conclusion), citations re-rendered live via citation.render (never a frozen string), and a snapshot of its GPS checklist. This is the pinned interchange contract, not a formatted report.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `certainty`: string — required
  - `claims_exhaustive_search`: boolean — required
  - `gps_checklist`: object — required
    - `analysis_correlation`: boolean — required
    - `complete_citations`: boolean — required
    - `conflict_resolution`: boolean — required
    - `exhaustive_search`: tagged union: — required
      - `status` = `claimable`:
        - `status`: string [const: claimable] — required
      - `status` = `in_progress`:
        - `covered`: integer (uint32) — required
        - `status`: string [const: in_progress] — required
        - `total`: integer (uint32) — required
      - `status` = `not_started`:
        - `status`: string [const: not_started] — required
    - `written_conclusion`: boolean — required
  - `id`: string (uuid) — required
  - `provenance`: object — required
    - `at_ms`: integer (uint64) — required
    - `creator`: string — required
    - `via`: string (nullable) — optional
  - `question`: object — required
    - `id`: string (uuid) — required
    - `text`: string — required
  - `schema`: string — required
  - `sections`: array of — required
    - `ExportSectionOutput`: object
      - `body`: string — required
      - `citations`: array of — required
        - `ExportCitationOutput`: object
          - `assertion_id`: string (uuid) — required
          - `citation_id`: string (uuid) — required
          - `rendered_footnote`: string — required
      - `kind`: string — required

**Example prompts**

- “Export the proof argument I just filed as a versioned document I can review or hand off.”
- “Give me the full exported proof-argument document, with citations re-rendered live, not a frozen string.”

<!-- doc-md:tool:proof.argument.get -->
### `proof.argument.get` (Preview)

Read a single recorded proof argument by id, with its full section breakdown (supporting assertions, correlation reasoning, contrary-evidence treatments, certainty).

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `certainty`: string — required
  - `claims_exhaustive_search`: boolean — required
  - `conclusion`: string — required
  - `contrary_evidence`: array of — required
    - `ContraryTreatmentOutput`: object
      - `assertion`: string (uuid) — required
      - `treatment`: string — required
  - `correlation_reasoning`: string — required
  - `id`: string (uuid) — required
  - `question`: string (uuid) — required
  - `supporting`: array of — required
    string (uuid)

**Example prompts**

- “Show me the full proof argument for this research question, including the certainty and contrary-evidence treatment.”
- “Pull up the recorded proof argument by id and walk me through its reasoning.”

<!-- doc-md:tool:proof.argument.list -->
### `proof.argument.list` (Preview)

List recorded proof arguments, optionally filtered to one research question.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `question`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `items`: array of — required
    - `ProofArgumentView`: object
      - `certainty`: string — required
      - `claims_exhaustive_search`: boolean — required
      - `conclusion`: string — required
      - `contrary_evidence`: array of — required
        - `ContraryTreatmentOutput`: object
          - `assertion`: string (uuid) — required
          - `treatment`: string — required
      - `correlation_reasoning`: string — required
      - `id`: string (uuid) — required
      - `question`: string (uuid) — required
      - `supporting`: array of — required
        string (uuid)
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “List every proof argument filed for this research question.”
- “Show me all recorded proof arguments across the tree.”

<!-- doc-md:tool:proof.argument.record -->
### `proof.argument.record` (Preview)

Record a GPS-standard proof argument: conclusion, certainty, supporting assertions, correlation reasoning, and contrary-evidence treatments. A refuted, pending-AI, or fabricated assertion cannot be cited — the domain's IneligibleAssertion error names which one, for both human and agent callers. Human actors apply directly; agent actors always file a proposal for human approval (D9) — a conclusion is human judgment.

**Parameters**

object
  - `certainty`: string [enum: proved, probable, possible, disproved] — required
  - `claims_exhaustive_search`: boolean — required
  - `conclusion`: string — required
  - `contrary`: array of — optional
    object
      - `assertion`: string (uuid) — required
      - `treatment`: string — required
  - `correlation_reasoning`: string — required
  - `question`: string (uuid) — required
  - `supporting`: array of — required
    string (uuid)
  - `tree_id`: string — optional

**Output shape**

object
  - `applied`: boolean — required
  - `message`: string — required
  - `proof_argument_id`: string (nullable) — optional
  - `proposal_id`: string (nullable) — optional

**Example prompts**

- “Draft and file a proof argument concluding Arthur was born in 1870, citing the confirmed census assertion and my correlation reasoning.”
- “Record a proof argument for this question — remember, only confirmed assertions can be cited, not the one I just captured myself.”

### FAN correlation

<!-- doc-md:tool:fan.network.query -->
### `fan.network.query` (Preview)

Query the FAN (Friends, Associates, Neighbors) network for a persona or a concluded person: shared-event, typed-association, and place-co-occurrence edges within an optional date window. Read-only, and live rows only — refuted and superseded claims never appear as edges.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `edge_types`: array (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `person_id`: string (nullable) — optional
  - `persona_id`: string (nullable) — optional
  - `tree_id`: string — optional
  - `window`: object (optional) — optional
    - `end`: tagged union: — required
      - `kind` = `about`:
        - `kind`: string [const: about] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `after`:
        - `kind`: string [const: after] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `before`:
        - `kind`: string [const: before] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `between`:
        - `kind`: string [const: between] — required
        - `value`: array of — required
          any
      - `kind` = `exact`:
        - `kind`: string [const: exact] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `range`:
        - `kind`: string [const: range] — required
        - `value`: array of — required
          any
    - `start`: tagged union: — required
      - `kind` = `about`:
        - `kind`: string [const: about] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `after`:
        - `kind`: string [const: after] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `before`:
        - `kind`: string [const: before] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `between`:
        - `kind`: string [const: between] — required
        - `value`: array of — required
          any
      - `kind` = `exact`:
        - `kind`: string [const: exact] — required
        - `value`: object — required
          - `calendar`: string [enum: gregorian, julian] — required
          - `day`: integer (nullable) — optional
          - `dual_year`: integer (nullable) — optional
          - `month`: integer (nullable) — optional
          - `year`: integer (int32) — required
      - `kind` = `range`:
        - `kind`: string [const: range] — required
        - `value`: array of — required
          any

**Output shape**

object
  - `edges`: array of — required
    - `FanEdgeOutput`: object
      - `edge_type`: string — required
      - `evidence`: array of — required
        string (uuid)
      - `other_person`: string (nullable) — optional
      - `other_persona`: string (uuid) — required
      - `via`: object — required
        - `association`: string (nullable) — optional
        - `event`: string (nullable) — optional
        - `own_event`: string (nullable) — optional
        - `own_role`: string (nullable) — optional
        - `place_id`: string (nullable) — optional
        - `relation`: string (nullable) — optional
        - `their_event`: string (nullable) — optional
        - `their_role`: string (nullable) — optional
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “Who shares an association or event with Arthur Fernwood between 1870 and 1880?”
- “Show me everyone connected to this persona through neighbors, witnesses, or shared events — I'm hunting for a FAN lead.”

### Browse

<!-- doc-md:tool:assertion.list -->
### `assertion.list` (Preview)

List assertions — the evidence behind a person. Filter by persona (the usual entry point: person.card gives you a person, person.get its personas), by source, by kind, or by epistemic state; omit every filter for a tree-wide read. Rows are compact ids-and-claim (read one in full with assertion.get), so prefer source.cited_by when you want the claims on ONE source with their persona and person names resolved. Paginated (default 50 — echo next_cursor back as cursor to continue). This is how you find 'confirmed' assertion ids, the only ones proof.argument.record can cite.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `kind`: string [enum: fact, transcription_claim, event_participation, persona_association, name] (optional) — optional
  - `limit`: integer (nullable) — optional
  - `persona`: string (nullable) — optional
  - `source`: string (nullable) — optional
  - `state`: string [enum: unevaluated, cited_unverified, pending_ai_origin, confirmed, refuted, superseded] (optional) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `items`: array of — required
    - `AssertionListItemView`: object
      - `claim`: string — required
      - `id`: string (uuid) — required
      - `kind`: string — required
      - `persona`: string (uuid) — required
      - `source`: string (uuid) — required
      - `state`: string — required
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “What's the evidence behind Temperance Wynwood's birth date?”
- “List every claim we've captured from that 1880 census source.”
- “Which of Arthur Fernwood's claims are confirmed and ready to cite?”

<!-- doc-md:tool:assertion.unsourced.list -->
### `assertion.unsourced.list` (Preview)

List assertions still awaiting a human evaluation decision — unevaluated human captures and cited-but-unverified import claims. Agent-origin captures never appear here (they are pending_ai_origin, the review lane's own status); a source already attached doesn't remove a row either, since attaching evidence is not the same as confirming it. Each row carries the persona label and, if the persona has been concluded, the person it belongs to.

**Parameters**

object
  - `tree_id`: string — optional

**Output shape**

object
  - `items`: array of — required
    - `UnsourcedAssertionView`: object
      - `claim`: string — required
      - `id`: string (uuid) — required
      - `kind`: string — required
      - `person`: string (nullable) — optional
      - `persona`: string (uuid) — required
      - `persona_label`: string — required
      - `source`: string (uuid) — required
      - `state`: string — required

**Example prompts**

- “Which claims still need someone to review and confirm them?”
- “Show me everything captured by hand that hasn't been evaluated yet.”
- “List the assertions that came in with a citation but were never verified.”

<!-- doc-md:tool:conflict.list -->
### `conflict.list` (Preview)

List detected conflicts — sets of contradicting assertions about the same persona or identity. 'open_only' narrows to the ones still unresolved, which is exactly what the 'conflict_resolution' item on a research question's GPS checklist is asking about. Conflicts are resolved by writing a proof argument (proof.argument.record) and naming it to conflict.resolve, never by auto-merging. The argument alone closes nothing; until conflict.resolve runs, the GPS item stays unmet.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `open_only`: boolean (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `detection`: object — required
    - `conflicts_recorded`: integer (uint) — required
    - `detection_has_run`: boolean — required
    - `imports_observed`: integer (uint) — required
    - `open_conflicts`: integer (uint) — required
    - `trigger`: string — required
  - `items`: array of — required
    - `ConflictListItemView`: object
      - `assertions`: array of — required
        string (uuid)
      - `id`: string (uuid) — required
      - `open`: boolean — required
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “Are there any unresolved conflicts in this tree?”
- “Show me the contradicting claims I still need to work through.”

<!-- doc-md:tool:person.card -->
### `person.card` (Preview)

One call orients you on one person: display name, lifespan years, and immediate family — parents, spouses, children, each with id + name + lifespan (derived server-side from the same marriage-event participant-role structure fan.network.query exposes) — plus live assertion and distinct-source counts as a cheap source-coverage summary. Pass expand (any of "events", "assertions", "sources") to inline those records instead of paying one call per record to fetch them; each expanded list caps at 100 rows. Follow up with person.get / fan.network.query for anything expand does not cover.

**Parameters**

object
  - `expand`: array of — optional
    string
  - `person`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `assertion_count`: integer (uint32) — required
  - `assertions`: array (nullable) — optional
  - `birth_year`: integer (nullable) — optional
  - `children`: array of — required
    - `PersonSummaryView`: object
      - `birth_year`: integer (nullable) — optional
      - `death_year`: integer (nullable) — optional
      - `id`: string (uuid) — required
      - `name`: string — required
  - `death_year`: integer (nullable) — optional
  - `events`: array (nullable) — optional
  - `id`: string (uuid) — required
  - `name`: string — required
  - `parents`: array of — required
    - `PersonSummaryView`: object
      - `birth_year`: integer (nullable) — optional
      - `death_year`: integer (nullable) — optional
      - `id`: string (uuid) — required
      - `name`: string — required
  - `source_count`: integer (uint32) — required
  - `sources`: array (nullable) — optional
  - `spouses`: array of — required
    - `PersonSummaryView`: object
      - `birth_year`: integer (nullable) — optional
      - `death_year`: integer (nullable) — optional
      - `id`: string (uuid) — required
      - `name`: string — required

**Example prompts**

- “Give me a quick overview of Clara Fernwood — parents, spouse, children, and how well sourced she is.”
- “Who were Arthur Fernwood's children, and when did he live?”

<!-- doc-md:tool:person.duplicate_candidates -->
### `person.duplicate_candidates` (Preview)

Check whether a person you are about to create likely already exists: ranks every person in the tree against the given/surname (and optional GEDCOM-syntax birth/death dates) you supply, using the same similarity scorer the app's own entry forms consult. At least one of given/surname is required. Returns only likely/possible matches, best first, each with id, name, lifespan years, a 0-1000 permille score, and its band. Call this before person.create; an empty list means nothing plausible exists. Advisory only — it never blocks creation.

**Parameters**

object
  - `birth`: string (nullable) — optional
  - `death`: string (nullable) — optional
  - `given`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `surname`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `candidates`: array of — required
    - `DuplicateCandidateView`: object
      - `birth_year`: integer (nullable) — optional
      - `death_year`: integer (nullable) — optional
      - `id`: string (uuid) — required
      - `likelihood`: string — required
      - `name`: string — required
      - `score_permille`: integer (uint16) — required

**Example prompts**

- “Before you add John Smith born 1850 from this census, check whether he already exists in my tree.”
- “I am about to enter Mary Jones, died 1901 — is there already someone like her here?”

<!-- doc-md:tool:person.get -->
### `person.get` (Preview)

Read a single concluded person (identity) by id.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `certainty`: string — required
  - `id`: string (uuid) — required
  - `personas`: array of — required
    string (uuid)

**Example prompts**

- “Show me the concluded person record this persona has been merged into.”
- “Look up this identity by id and tell me what's recorded about them.”

<!-- doc-md:tool:person.search -->
### `person.search` (Preview)

Search persons by name — substring, case-insensitive (the same prefix+fuzzy query plan as the app's own search box). Returns id, display name, and lifespan years per hit, best match first; paginated (default 50 — echo next_cursor back as cursor to continue). Start here to resolve a name to a person id.

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `query`: string — required
  - `tree_id`: string — optional

**Output shape**

object
  - `hits`: array of — required
    - `PersonSummaryView`: object
      - `birth_year`: integer (nullable) — optional
      - `death_year`: integer (nullable) — optional
      - `id`: string (uuid) — required
      - `name`: string — required
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “Find everyone named Fernwood in my tree.”
- “Is there a Clara in this family file? Give me her id so we can dig in.”

<!-- doc-md:tool:persona.get -->
### `persona.get` (Preview)

Read a single persona (a source appearance) by id.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `id`: string (uuid) — required
  - `label`: string — required
  - `source`: string (uuid) — required

**Example prompts**

- “Show me everything recorded about this persona from that one source appearance.”
- “Pull up the persona record for Arthur Fernwood as he appears in the 1900 census.”

<!-- doc-md:tool:project.state.get -->
### `project.state.get` (Preview)

The session brief: current investigation state in one bounded call — open questions with coverage, settled conclusions, retired leads with reasons, pending-lane depth, and conflict-detection state. Pass the previous brief's cursor as 'since' to get only what changed. A digest with pointers; drill down with the research verbs.

**Parameters**

object
  - `since`: string (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `conflicts`: object — required
    - `conflicts_recorded`: integer (uint) — required
    - `detection_has_run`: boolean — required
    - `imports_observed`: integer (uint) — required
    - `open_conflicts`: integer (uint) — required
    - `trigger`: string — required
  - `cursor`: string — required
  - `open_questions`: object — required
    - `items`: array of — required
      - `OpenQuestionBriefView`: object
        - `gps`: object — required
          - `analysis_correlation`: boolean — required
          - `complete_citations`: boolean — required
          - `conflict_resolution`: boolean — required
          - `exhaustive_search`: tagged union: — required
            - `status` = `claimable`:
              - `status`: string [const: claimable] — required
            - `status` = `in_progress`:
              - `covered`: integer (uint32) — required
              - `status`: string [const: in_progress] — required
              - `total`: integer (uint32) — required
            - `status` = `not_started`:
              - `status`: string [const: not_started] — required
          - `written_conclusion`: boolean — required
        - `id`: string (uuid) — required
        - `log_entries`: integer (uint) — required
        - `plan_items`: integer (uint) — required
        - `question`: string — required
        - `subjects`: integer (uint) — required
    - `total`: integer (uint) — required
  - `pending`: object — required
    - `deferred`: integer (uint) — required
    - `depth`: integer (uint) — required
  - `retired`: object — required
    - `items`: array of — required
      - `RetiredLeadBriefView`: object
        - `closed_at_ms`: integer (uint64) — required
        - `disposition`: string (nullable) — optional
        - `id`: string (uuid) — required
        - `question`: string — required
        - `reason`: string (nullable) — optional
    - `total`: integer (uint) — required
  - `rules`: array of — required
    string
  - `settled`: object — required
    - `confirmed_assertions`: object — required
      - `items`: array of — required
        - `ConfirmedAssertionBriefView`: object
          - `claim`: string — required
          - `id`: string (uuid) — required
      - `total`: integer (uint) — required
    - `proof_arguments`: object — required
      - `items`: array of — required
        - `ProofArgumentBriefView`: object
          - `certainty`: string — required
          - `conclusion`: string — required
          - `id`: string (uuid) — required
          - `question`: string (uuid) — required
          - `recorded_at_ms`: integer (uint64) — required
      - `total`: integer (uint) — required
  - `since`: string (nullable) — optional

**Example prompts**

- “Catch me up on this investigation — what's open, what's settled, and what have we ruled out?”
- “What changed in the project since my last session?”
- “Before we start anything new: which leads are retired, and why?”

<!-- doc-md:tool:settled.list -->
### `settled.list` (Preview)

Settled knowledge and retired leads in ONE call — do not re-derive or re-open any of it. Returns: recorded conclusions (proof arguments), refuted/superseded assertions, negative findings (searched and found absent), contrary evidence a conclusion already considered and rejected, and retired leads (closed questions) with their dispositions and human-authored reasons. If a proposed conclusion touches retired ground the engine will refuse it and name the retirement; a retired lead is reopened only by the human, in the app.

**Parameters**

object
  - `tree_id`: string — optional

**Output shape**

object
  - `conclusions`: array of — required
    - `SettledConclusionView`: object
      - `certainty`: string — required
      - `conclusion`: string — required
      - `id`: string (uuid) — required
      - `question`: string (uuid) — required
      - `question_text`: string — required
      - `recorded_at_ms`: integer (uint64) — required
      - `recorded_by`: string — required
  - `negative_findings`: array of — required
    - `NegativeFindingView`: object
      - `expectation`: string — required
      - `inference`: string — required
      - `log_entry`: string (uuid) — required
      - `question`: string (nullable) — optional
      - `recorded_at_ms`: integer (uint64) — required
      - `recorded_by`: string — required
      - `search_context`: string (nullable) — optional
  - `refutations`: array of — required
    - `RuledOutAssertionView`: object
      - `claim`: string — required
      - `id`: string (uuid) — required
      - `ruled_out_at_ms`: integer (nullable) — optional
      - `ruled_out_by`: string (nullable) — optional
      - `status`: string — required
  - `rejected_evidence`: array of — required
    - `RejectedEvidenceView`: object
      - `assertion`: string (uuid) — required
      - `proof_argument`: string (uuid) — required
      - `recorded_at_ms`: integer (uint64) — required
      - `recorded_by`: string — required
      - `treatment`: string — required
  - `retired_leads`: array of — required
    - `RetiredLeadView`: object
      - `closed_at_ms`: integer (uint64) — required
      - `disposition`: string (nullable) — optional
      - `id`: string (uuid) — required
      - `question`: string — required
      - `reason`: string (nullable) — optional

**Example prompts**

- “What has this investigation already settled or ruled out?”
- “Show me every retired lead and the reason we stopped chasing it.”
- “Which claims were refuted, and what did we search for and never find?”

<!-- doc-md:tool:source.get -->
### `source.get` (Preview)

Read a single source by id.

**Parameters**

object
  - `id`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `blobs`: array of — required
    string
  - `derived_from`: string (nullable) — optional
  - `id`: string (uuid) — required
  - `title`: string — required

**Example prompts**

- “Show me the full record for this source, including its citation.”
- “Pull up the details on the census source I cited earlier.”

<!-- doc-md:tool:source.list -->
### `source.list` (Preview)

List the sources in this tree, each with the number of claims captured against it and whether it carries a formal citation. Use this to discover and cite existing evidence instead of creating a duplicate source; source.get reads one in full, and source.cited_by shows which claims rest on it. Paginated (default 50 — echo next_cursor back as cursor to continue).

**Parameters**

object
  - `cursor`: string (nullable) — optional
  - `limit`: integer (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `items`: array of — required
    - `SourceListItemView`: object
      - `claim_count`: integer (uint32) — required
      - `has_citation`: boolean — required
      - `id`: string (uuid) — required
      - `title`: string — required
  - `next_cursor`: string (nullable) — optional

**Example prompts**

- “What sources do I already have in this family file?”
- “Which of my sources still don't have a proper citation?”

<!-- doc-md:tool:tree.stats -->
### `tree.stats` (Preview)

Whole-tree orientation numbers: person count, birth-year span, top-N surname frequencies (default 10, max 50), and counts of persons missing birth/death dates. Read this first when meeting an unfamiliar tree.

**Parameters**

object
  - `top_surnames`: integer (nullable) — optional
  - `tree_id`: string — optional

**Output shape**

object
  - `birth_year_max`: integer (nullable) — optional
  - `birth_year_min`: integer (nullable) — optional
  - `missing_birth_date_count`: integer (uint32) — required
  - `missing_death_date_count`: integer (uint32) — required
  - `person_count`: integer (uint32) — required
  - `top_surnames`: array of — required
    - `SurnameCountView`: object
      - `count`: integer (uint32) — required
      - `surname`: string — required

**Example prompts**

- “How big is this tree, and which surnames dominate it?”
- “How many people here are missing a birth or death date?”

### Export & redaction

<!-- doc-md:tool:redaction.propose -->
### `redaction.propose` (Preview)

Propose redacting (tombstoning) an event. This never executes directly — it only files a Proposal for a human to review and approve or deny. There is no tool that redacts directly; this is the only redaction-adjacent tool available to an agent.

**Parameters**

object
  - `reason`: string — required
  - `target_event`: string (uuid) — required
  - `tree_id`: string — optional

**Output shape**

object
  - `message`: string — required
  - `proposal_id`: string (uuid) — required

**Example prompts**

- “This event turned out to be about the wrong person — propose redacting it.”
- “File a proposal to redact this life event so I can review it before it's tombstoned.”

## Prompts

Named, arguments-driven starting points a client can surface directly (e.g. as a slash command) rather than the model composing the request itself.

<!-- doc-md:prompt:coverage-gap-review -->
### `coverage-gap-review` (Preview)

Surface GPS coverage omissions: plan items without logs, questions without plans, and found-nothing searches never formalized into NegativeEvidence. Omit the question argument for a tree-wide review.

| argument | required | description |
|---|---|---|
| `question` | no | Research question id (omit for a tree-wide review) |

<!-- doc-md:prompt:draft-proof-argument -->
### `draft-proof-argument` (Preview)

Given a research question, review its GPS-checklist state and the eligible (proof-feedable) supporting assertions with open conflicts, then draft a structured proof argument section by section and file it via proof.argument.record.

| argument | required | description |
|---|---|---|
| `question` | yes | Research question id |

<!-- doc-md:prompt:plan-research-question -->
### `plan-research-question` (Preview)

Given a research question, review its current plan/coverage state and the subject's FAN neighborhood, then propose candidate plan-item sources (and a FAN pivot when direct evidence is thin) via research.plan.item.add.

| argument | required | description |
|---|---|---|
| `question` | yes | Research question id |

<!-- doc-md:prompt:resume-investigation -->
### `resume-investigation` (Preview)

Orient a cold session: read the live session brief (open questions, settled conclusions, retired leads with reasons, pending depth, conflict-detection state), state the store's refusal rules, and open with tree.stats and person.search. Pass a previous brief's cursor as 'since' to see only what changed.

| argument | required | description |
|---|---|---|
| `since` | no | A previous brief's cursor — restricts the brief to what changed while you were away (omit for the full digest) |

## Resources

Addressable, read-only `heartwood://` URIs a client can fetch directly instead of calling a tool.

<!-- doc-md:resource:methodology/epistemic-status -->
### `heartwood://methodology/epistemic-status` (Preview)

What each epistemic-status code means and what it implies for your next step. Read this once; records carry the bare code, never the explanation. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:project/state -->
### `heartwood://project/state` (Preview)

The session brief — current investigation state as a bounded digest; append ?since=<cursor> for only what changed. Wraps project.state.get. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:coverage -->
### `heartwood://research/coverage` (Preview)

Tree-wide GPS coverage summary — per-question checklist rollup. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:question/{id} -->
### `heartwood://research/question/{id}` (Preview)

A research question, its subjects, and status. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:question/{id}/coverage -->
### `heartwood://research/question/{id}/coverage` (Preview)

A research question's D2 GPS checklist. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:question/{id}/log -->
### `heartwood://research/question/{id}/log` (Preview)

A research question's full log, attestation-labeled. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:question/{id}/plan -->
### `heartwood://research/question/{id}/plan` (Preview)

A research question's ordered plan items with done/coverage state. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`

<!-- doc-md:resource:questions -->
### `heartwood://research/questions` (Preview)

Open research questions, compact list. Append ?tree_id=<id> to bind this read to a tree; absent reads the active tree (tree.list names it) and naming one never changes it.

MIME type: `application/json`
