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

# Integrating an agent

> A consumer is anything that asks ShoulderTap for context — an AI agent, a service, or the CLI. Learn how consumers submit requests, declare kinds, and receive answers.

A **consumer** is anything that submits requests to ShoulderTap and receives answers back — most often an AI agent's low-confidence path, but also your own service, the CLI, or the MCP server. Integrating a consumer means answering three questions: how you **submit** a request, which **kinds** of questions you handle, and how you want the answer **delivered**.

## Ways to integrate

<CardGroup cols={3}>
  <Card title="MCP" icon="plug" href="/consumers/mcp">
    Any MCP-capable agent can tap a human expert with no SDK, via the `ask_expert` and `check_answer` tools over stdio.
  </Card>

  <Card title="Python SDK" icon="python" href="/consumers/sdk">
    `ShoulderTapClient` for HTTP consumers, or in-process callbacks for a consumer embedded alongside the engine.
  </Card>

  <Card title="HTTP + webhooks" icon="webhook" href="/consumers/webhooks">
    Submit over the REST API and register a webhook to receive answers as they're accepted.
  </Card>
</CardGroup>

## Submitting a request

Every consumer submits a `ContextRequest`. Four fields are required — `kind`, `topic`, `question`, and `consumer` — and the rest are optional:

```json theme={null}
{
  "kind": "glossary.definition",
  "topic": "revenue metrics",
  "question": "What does 'active customer' mean for Q2 reporting?",
  "consumer": "bi.assistant",
  "context": {
    "asked_because": "BI agent hit low confidence answering a user query",
    "entity": "dim_customers.active_flag"
  },
  "dedup_key": "glossary:dim_customers.active_flag"
}
```

`topic` drives [routing](/run/experts). `context` is free-form and shown to the expert (`asked_because` becomes the "why I'm asking" line). `dedup_key` prevents asking an expert the same thing twice. See the [ContextRequest contract](/api/contracts) for every field.

Submitting is asynchronous: you get back `{ id, status }` immediately, and the answer arrives later — once a human replies and a reviewer accepts it.

## Kinds

A request's `kind` determines the schema its answer is captured into. Two ship built in:

| Kind                  | Structured shape                         |
| --------------------- | ---------------------------------------- |
| `glossary.definition` | `{ definition, caveats[], examples[]? }` |
| `freeform.answer`     | `{ summary, details? }`                  |

Register your own kind with a custom JSON Schema at registration time if the built-ins don't fit. See [Kinds](/api/kinds).

## Delivery

Because answers arrive out-of-band, a consumer chooses how it wants to be notified:

<AccordionGroup>
  <Accordion title="Webhook" icon="webhook">
    ShoulderTap POSTs each event to a URL you register. This is the standard choice for a consumer running as its own service. Register with `delivery: { "type": "webhook", "url": "..." }`. See [Webhooks](/consumers/webhooks).
  </Accordion>

  <Accordion title="In-process" icon="microchip">
    For a consumer embedded in the same process as the engine, register callbacks directly with the SDK's `register_in_process`. No HTTP round-trip. See the [SDK](/consumers/sdk).
  </Accordion>

  <Accordion title="Polling" icon="rotate">
    Simplest of all: submit, then poll `GET /requests/{id}` (or the MCP `check_answer` tool) until the status is `accepted`. Good for scripts and the CLI's `shtap ask`.
  </Accordion>
</AccordionGroup>

## Callback events

A registered consumer receives up to four events, whether delivered as an in-process callable or a webhook POST (`{ "event": "<name>", ... }`):

<ResponseField name="on_proposal">
  Informational — fires when a proposal is created, before approval.
</ResponseField>

<ResponseField name="on_proposal_accepted">
  The actual delivery event. Write to your system of record here. This is the one that matters.
</ResponseField>

<ResponseField name="on_proposal_rejected">
  Fires with the rejection reason when a reviewer rejects the proposal.
</ResponseField>

<ResponseField name="on_request_failed">
  Fires with a [reason code](/api/errors) (for example `timeout` or `no_expert_found`) when a request can't be answered.
</ResponseField>

## Auto-accept

By default every answer is held for human review. A consumer can register with `auto_accept: true` to have proposals delivered without review.

<Warning>
  `auto_accept` is off by default because writing an unreviewed expert answer straight into a system of record is a real poisoning risk. Turn it on only for kinds and consumers where an occasional wrong answer is acceptable.
</Warning>

## Deduplication

Set a `dedup_key` on requests that represent the same underlying question. Within a consumer's dedup window (default 24 hours), a matching key means ShoulderTap won't ask an expert again: if the earlier request already resolved, you get its accepted proposal immediately; if it's still open, you're attached as a subscriber and get the answer when it lands. See [Concepts](/concepts#deduplication).
