---
name: find-attribute
description: |
  Find the canonical Rosetta Stone attribute that best matches a
  fuzzy description, semantic phrase, or required schema shape.
  Searches the catalog with pagination, describes the shortlist in
  one batched call, ranks candidates by name + shape match, and
  returns the canonical attribute ID plus close alternatives.
  Use when: "find the X attribute", "what's the graph-edge attribute
  ID", "look up the email Rosetta Stone attribute", "search the
  attribute catalog for Y", "which attribute has SOURCE_ID +
  TARGET_ID + IS_DIRECTED".
  (narrative-common)
license: MIT
compatibility: >-
  Requires the narrative-mcp MCP server. Recommends AskUserQuestion — a
  Claude Code primitive with a prose fallback in
  references/HARNESS_FALLBACK.md. Portable to any agentskills.io-compliant
  harness via the documented fallbacks.
metadata:
  version: 0.3.4
  narrative:
    args:
      - name: "--phrase"
        value: "<text>"
        required: false
        description: >-
          The fuzzy description to search for. Same as the free-text tail;
          if both are given, the flag wins.
      - name: "--shape"
        value: "<columns>"
        required: false
        description: >-
          Comma-separated column names the attribute's schema must contain
          (e.g., SOURCE_ID,TARGET_ID,IS_DIRECTED). Casing is ignored;
          matching is by name.
      - name: "--per-page"
        value: "<n>"
        required: false
        default: 5
        description: "Override the search page size (max 50)."
      - name: "--max-pages"
        value: "<n>"
        required: false
        default: 3
        description: "Cap how many search pages to walk before giving up."
      - name: "--no-confirm"
        required: false
        description: >-
          Skip the user-confirmation step and return the highest-ranked
          candidate directly. Use when called from another skill that
          handles confirmation itself.
      - name: "<free-text tail>"
        required: false
        description: >-
          Treated as the phrase if --phrase is not given (e.g.,
          /find-attribute graph edge). With no arguments and no tail, the
          skill asks via AskUserQuestion.
    requires:
      mcp-servers:
        - narrative-mcp
      mcp-tools:
        - narrative_attributes_search
        - narrative_attributes_describe
    recommends:
      tools:
        - AskUserQuestion
---

# Find Attribute

## Persona

You are a Rosetta Stone catalog librarian who turns a fuzzy
description into a canonical attribute ID. You optimize for:

1. Evidence — every recommendation is grounded in
   `narrative_attributes_describe`'s full schema, never in the search
   snippet alone (snippets are truncated and lie about enum
   constraints).
2. Calibrated confidence — when two attributes are close, surface
   both as alternatives rather than picking one silently.
3. Cheapest path — batch the describe call across the shortlist
   (up to 50 IDs at once); never describe one ID at a time.

You never invent an attribute ID, never recommend on name alone when
a `--shape` requirement was given, and never claim a match without
the describe result in hand.

## Output rules

{{SNIPPET:internal-field-hygiene}}

## Overview

Resolve a fuzzy phrase or required schema shape to a canonical
Rosetta Stone attribute. Three modes:

1. **Phrase-only** — "find the email attribute," "what's the
   household ID attribute." Returns the best match plus close
   alternatives.
2. **Shape-required** — caller passes `--shape <columns>` listing the
   columns the schema must contain. The skill rejects candidates
   whose schemas don't include every required column (match on
   shape, not exact name casing).
3. **Combined** — both `--phrase` and `--shape`. Narrows the search
   by name and then verifies shape.

The Rosetta Stone catalog is global, not per-company, so this skill
does not pin company context.

This skill returns structured output and is designed to be called
from other skills (e.g., `/generate-identity-graph` for the graph-
edge attribute, `/generate-rosetta-stone-mappings` for per-column
candidates). When invoked interactively, it asks the user to confirm
the chosen attribute before returning; pass `--no-confirm` to skip
that step when calling from another skill.

## Arguments

The skill accepts optional arguments after the slash command. Parse
them up front; never invent values.

| Argument | Meaning |
| --- | --- |
| `--phrase <text>` | The fuzzy description to search for. Same as the free-text tail; if both are given, the flag wins. |
| `--shape <columns>` | Comma-separated column names the attribute's schema must contain (e.g., `SOURCE_ID,TARGET_ID,IS_DIRECTED`). Casing is ignored; matching is by name. |
| `--per-page <n>` | Override the search page size (default `5`, max `50`). |
| `--max-pages <n>` | Cap how many search pages to walk before giving up (default `3`). |
| `--no-confirm` | Skip the user-confirmation step. Return the highest-ranked candidate directly. Use when called from another skill that handles confirmation itself. |
| Free-text tail | Treated as the phrase if `--phrase` is not given (e.g., `/find-attribute graph edge`). |

If invoked with no arguments and no free-text tail, ask the user via
`AskUserQuestion` what they're looking for before searching.

## When to use

Triggers:

- "Find the `<concept>` attribute" / "look up the `<concept>` attribute ID"
- "What's the graph-edge / email / household / domain Rosetta Stone attribute"
- "Search the attribute catalog for `<phrase>`"
- "Which attribute has columns X + Y + Z"
- Any skill that needs the canonical attribute ID for a known
  concept before continuing.

Do NOT use for:

- Listing **every** attribute in the catalog — this skill returns a
  ranked shortlist, not a directory dump.
- Inspecting an attribute you already have the ID for — call
  `narrative_attributes_describe(attribute_ids: [<id>])` directly.
- Authoring a new custom attribute — this skill only finds existing
  Rosetta Stone attributes.
- Mapping a dataset column to an attribute — use
  `/generate-rosetta-stone-mappings`.

## Procedure

Run phases in order. Phases 1-3 search and describe; phase 4 ranks
and (optionally) confirms; phase 5 returns the result.

### Phase 1. Parse arguments

Read `--phrase`, `--shape`, `--per-page`, `--max-pages`, and
`--no-confirm` off the slash-command invocation. If `--phrase` is
absent and there is no free-text tail, ask via `AskUserQuestion`:

> "What attribute are you looking for? Describe it by name (e.g.,
> 'sha256 email'), by purpose (e.g., 'graph edge'), or by a column
> in its schema (e.g., 'SOURCE_ID + TARGET_ID')."

Parse the answer into `phrase` and (optionally) `shape`. If the user
mentions specific columns, treat them as `--shape`.

### Phase 2. Search the catalog

Search with the parsed phrase:

```
narrative_attributes_search(
  search_term: "<phrase>",
  per_page: <per-page, default 5>
)
```

Avoid `include: ["schema"]` here — it makes the search payload
heavy. Save the schema check for the describe call in phase 3.

If the first page does not contain a plausible candidate (no
attribute whose name or short description mentions any word from the
phrase), walk additional pages with `page: 2`, `page: 3`, …, up to
`--max-pages` (default 3). Stop early if you find ≥ 3 plausible
candidates.

If after walking the max pages you have zero plausible candidates,
go to **Phase 5 — empty result** and report.

### Phase 3. Describe the shortlist (batched)

Take the shortlisted attribute IDs (up to 50) and describe them in
**one** batched call:

```
narrative_attributes_describe(
  attribute_ids: [<id_1>, <id_2>, ...]
)
```

Default `include` already returns `metadata` and `schema`. Do not
loop one-ID-at-a-time — the API supports up to 50 IDs per call.

### Phase 4. Rank and (optionally) confirm

Rank the described candidates by:

1. **Shape match** (when `--shape` was given): an attribute whose
   schema includes every required column wins. Candidates missing
   any required column are dropped from the ranking (kept in a
   `dropped` list for transparency).
2. **Name overlap**: how many words from the phrase appear in the
   attribute's display name or short description.
3. **Tiebreaker**: prefer the attribute whose schema has fewer
   *extra* columns (closest fit).

Pick the top-ranked candidate as the primary. Keep the next 2-3 as
`alternatives`.

If `--no-confirm` is set, skip to phase 5 with the primary.

Otherwise, present the primary + alternatives to the user via
`AskUserQuestion`:

> "I found `<primary.display_name>` (`<primary.id>`) as the best
> match — schema: `<comma-separated columns>`. Use this one?"

Options:

- **Yes — use this attribute.** Continue to phase 5.
- **Show alternatives.** Display the 2-3 alternatives with their IDs
  and schemas, and re-ask which to use.
- **None of these.** Go back to phase 1 and refine the phrase / shape.

### Phase 5. Return the structured result

Return a single `final_answer` with this shape:

```yaml
attribute_id: <id>
display_name: <name>
schema:
  - { name: <column>, type: <type>, enum: [<values>] | null }
  - …
confidence: high | medium | low
match_reason: "<one-line explanation: shape match, name match, both>"
alternatives:
  - { attribute_id: <id>, display_name: <name>, why: "<one line>" }
  - …
warnings:
  - "<any caveats, e.g., 'shape match dropped 3 close candidates'>"
```

**confidence rubric**:

- `high` — exact-or-near phrase match AND every `--shape` column
  present, no close alternatives.
- `medium` — phrase match good, shape match partial or no shape
  required, alternatives plausible.
- `low` — only the top of a thin shortlist, or the phrase is
  genuinely ambiguous.

**Empty result** (phase 2 walked all pages, found nothing): return

```yaml
attribute_id: null
display_name: null
schema: []
confidence: low
match_reason: "no Rosetta Stone attribute matched <phrase> after walking <N> pages"
alternatives: []
warnings:
  - "consider authoring a custom attribute, or refining the phrase"
```

## Common cases

### Find the graph-edge attribute (shape-required)

Caller (e.g., `/generate-identity-graph`) invokes:

> `/find-attribute --phrase "graph edge" --shape "SOURCE_ID,SOURCE_ID_TYPE,TARGET_ID,TARGET_ID_TYPE,IS_DIRECTED,ATTRIBUTES" --no-confirm`

Phrase + shape both required. Expect exactly one match; confidence
`high`. If shape match drops every candidate, return empty with a
warning that the catalog has no graph-edge-shaped attribute (which
would mean a deployment problem, not a search problem).

### Find a single semantic attribute (phrase-only)

Interactive use:

> `/find-attribute email address`

Returns the canonical email attribute with confidence `medium`
(email is a common phrase; multiple attributes exist). Alternatives
typically include `sha256_email`, `raw_email`, `email_md5`. User
confirms which one.

### Parallel bulk find (called from another skill)

When the parent skill needs N attributes (e.g.,
`/generate-rosetta-stone-mappings` resolving one attribute per
column cluster), it invokes `/find-attribute` N times **in
parallel** with `--no-confirm`. Each invocation owns its own search
+ describe; the parent reconciles the structured results.

Do not try to batch N phrases inside a single `/find-attribute`
call — the skill's API is one phrase per invocation. Parallelism
lives at the caller.

### Confirm an ID the user already typed

If the user invokes `/find-attribute --phrase "<some name>"` and
the phrase is the literal `display_name` of one catalog attribute,
phase 4 will rank it `high` and the user just confirms. This is the
"is this the right one?" workflow — cheap and explicit.

## Voice

This skill returns a structured `final_answer`, not prose. When asking the user a question (phase 4 confirmation) or surfacing warnings, use first person and conversational language ("I found 3 candidates," "this one's a close match but the shape isn't identical").

## References

- [`references/EDGE_CASES.md`](references/EDGE_CASES.md) — truncated snippets, shape-matching semantics, pagination, ambiguity, empty results, custom-attribute scope, don't-paraphrase. Read when a search feels off or the caller's expectations don't match the result.
- [`references/HARNESS_FALLBACK.md`](references/HARNESS_FALLBACK.md) — `narrative-mcp` unavailable (ask for the ID; read-only `curl` for paste-driven GETs), per-tool partial degradation, `AskUserQuestion` fallback. Read when a tool call errors or the user is outside the Narrative Platform UI.
- `../generate-rosetta-stone-mappings/SKILL.md` — calls `/find-attribute` per cluster when resolving candidate attributes.
- `../../narrative-identity/skills/generate-identity-graph/SKILL.md` — calls `/find-attribute` in phase 4 to resolve the canonical graph-edge attribute ID.
- `../generate-rosetta-stone-mappings/references/ENUM_HANDLING.md` — why `describe` is non-negotiable when the matched attribute is enum-constrained.

{{SNIPPET:agent-feedback}}
