Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

407 lines
20 KiB
YAML

# MCP tool definition — tool entries are scaffolded from the OpenAPI schema.
# The tool list and operation IDs are kept in sync automatically:
# pnpm --filter=@hanzo/mcp run scaffold-yaml -- --sync-all
#
# To enable a tool, set enabled: true and add required scopes + annotations.
# All other fields (title, description, enrich_url, etc.) are yours to configure.
category: User interview topics
feature: user_interviews
url_prefix: /user_interviews
ui_apps:
invite-email-preview:
type: detail
view_prop: data
tools:
user-interview-topics-add-interviewee:
operation: user_interview_topics_add_interviewee_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Add an interviewee to a topic
description: >
Append a single interviewee identifier to a user interview topic. Email-shaped identifiers (including the
`Display Name <email@host>` form) go to `interviewee_emails`; everything else goes to
`interviewee_distinct_ids`. Idempotent — adding an identifier that is already present leaves the topic
unchanged. Returns the updated topic.
include_params:
- identifier
feature_flag: user-interviews
user-interview-topics-create:
operation: user_interview_topics_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Create user interview topic
description: >
Plan a user interview by recording who you want to target and what product or idea you want to ask them
about. `topic` is the free-text description of the question or product area. Targets are specified as a
combination of `interviewee_emails` (list of email addresses) and `interviewee_distinct_ids` (list of
Insights distinct IDs) — at least one of the two must be provided. To target a cohort, resolve cohort members
to emails/distinct_ids in the calling agent first (see the planning-voice-agent-user-interviews skill) and
pass them explicitly here — topics snapshot their audience at creation time. Pass `agent_context` (free
text) to give the voice agent extra background or instructions, and `questions` (list of strings) for the
ordered set of questions the agent should work through.
include_params:
- interviewee_emails
- interviewee_distinct_ids
- topic
- agent_context
- questions
- invite_subject
- invite_message
feature_flag: user-interviews
user-interview-topics-destroy:
operation: user_interview_topics_destroy
enabled: false
user-interview-topics-generate-links:
operation: user_interview_topics_generate_links_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Generate public interview links for a topic
description: >
Generate one public, unauthenticated interview link per targeted interviewee on a user interview topic. For
each identifier in the topic's `interviewee_emails` and `interviewee_distinct_ids`, materializes (or reuses)
an IntervieweeContext row and an enabled SharingConfiguration, and returns the merged voice-agent context
alongside the URL the interviewee should open. The URL hosts the call publicly — no Insights login required.
Calling this multiple times is safe: existing share links are reused.
feature_flag: user-interviews
user-interview-topics-interviewees-bulk-create:
operation: user_interview_topics_interviewees_bulk_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Create many per-interviewee context rows at once
description: >
Create up to 500 per-interviewee context rows for a topic in a single request. Each item is
(interviewee_identifier, agent_context). Rows whose (topic, interviewee_identifier) already exists are
skipped — the response surfaces `inserted_count`, `skipped_count`, and `skipped_identifiers` so the caller
can reconcile. Items must have unique `interviewee_identifier` values within the batch. Prefer this over
`user-interview-topics-interviewees-create` when adding many rows.
include_params:
- items
feature_flag: user-interviews
user-interview-topics-interviewees-create:
operation: user_interview_topics_interviewees_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Add per-interviewee context to a topic
description: >
Attach extra context for a single interviewee on a user interview topic. Use this to give the voice agent
background it should know about this specific person — e.g. "uses the replay product but has never used
summarization". `interviewee_identifier` is the email or Insights distinct ID matching one of the entries in
the parent topic's targeting fields. `agent_context` is free text. At most one row per (topic,
interviewee_identifier) — calling create twice for the same pair will conflict. Prefer
`user-interview-topics-interviewees-bulk-create` when adding many rows at once.
include_params:
- interviewee_identifier
- agent_context
feature_flag: user-interviews
user-interview-topics-interviewees-destroy:
operation: user_interview_topics_interviewees_destroy
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: true
idempotent: true
title: Delete a per-interviewee context row
description: >
Remove the extra context attached to a single interviewee on a user interview topic. Only deletes the
`IntervieweeContext` row — does not change the topic's targeting (`interviewee_emails` /
`interviewee_distinct_ids`). Use `user-interview-topics-remove-interviewee` if you want to drop the
interviewee from targeting as well.
feature_flag: user-interviews
user-interview-topics-interviewees-list:
operation: user_interview_topics_interviewees_list
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
list: true
title: List per-interviewee contexts for a topic
description: >
List the per-interviewee context rows attached to a single user interview topic. Each row is
(interviewee_identifier, agent_context). Use this to see who has extra context attached before adding more.
feature_flag: user-interviews
user-interview-topics-interviewees-partial-update:
operation: user_interview_topics_interviewees_partial_update
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Update a per-interviewee context row
description: >
Update the `agent_context` field on a per-interviewee context row. Pass only the fields you want to change.
Does not modify the topic's targeting; use `user-interview-topics-add-interviewee` /
`user-interview-topics-remove-interviewee` for that.
include_params:
- agent_context
feature_flag: user-interviews
user-interview-topics-interviewees-retrieve:
operation: user_interview_topics_interviewees_retrieve
enabled: false
user-interview-topics-interviewees-update:
operation: user_interview_topics_interviewees_update
enabled: false
user-interview-topics-links-csv:
operation: user_interview_topics_links_csv_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Export interview links for a topic as CSV
description: >
Same materialization as `user-interview-topics-generate-links` but returns the per-interviewee links as a
downloadable CSV with columns `interviewee_identifier`, `interviewee_email`, `user_name`, `interview_url`.
Use this when the human plans to mail-merge the links into their own email tooling rather than letting
Insights send the invites.
feature_flag: user-interviews
user-interview-topics-list:
operation: user_interview_topics_list
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
list: true
title: List user interview topics
description: >
List planned user interview topics in the current project, newest first. Each result includes the topic,
targeting (interviewee_emails, interviewee_distinct_ids), and the voice agent setup (agent_context,
questions). Supports `search` for substring match against the topic, and `limit`/`offset` for pagination.
feature_flag: user-interviews
user-interview-topics-partial-update:
operation: user_interview_topics_partial_update
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Update a user interview topic
description: >
Update fields on a user interview topic. Pass only the fields you want to change. For incremental edits to
targeting, prefer `user-interview-topics-add-interviewee` and `user-interview-topics-remove-interviewee` —
sending `interviewee_emails` or `interviewee_distinct_ids` here replaces the entire array.
include_params:
- topic
- agent_context
- questions
- interviewee_emails
- interviewee_distinct_ids
- invite_subject
- invite_message
feature_flag: user-interviews
user-interview-topics-preview-invite:
operation: user_interview_topics_preview_invite_create
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Preview the invite email for a targeted interviewee
description: >
Render the invitation email exactly as a specific targeted interviewee would receive it — the personalized
subject and HTML body — without sending anything and without creating any share links. Pass
interviewee_identifier (an email or distinct ID already on the topic) to preview for a particular person, or
omit it to preview for the first targeted interviewee. The response includes the rendered subject and html,
the recipient, whether they are emailable, and whether the embedded link is a placeholder.
include_params:
- interviewee_identifier
ui_app: invite-email-preview
feature_flag: user-interviews
user-interview-topics-preview-invite-create:
operation: user_interview_topics_preview_invite_create
enabled: false
user-interview-topics-remove-interviewee:
operation: user_interview_topics_remove_interviewee_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: true
idempotent: true
title: Remove an interviewee from a topic
description: >
Remove an interviewee identifier from a topic's targeting. Drops the identifier from both
`interviewee_emails` and `interviewee_distinct_ids`, and disables any active SharingConfiguration tied to
that interviewee on this topic so the removed person can no longer open their interview link. Idempotent —
removing an identifier that isn't present is a no-op. Returns the updated topic.
include_params:
- identifier
feature_flag: user-interviews
user-interview-topics-retrieve:
operation: user_interview_topics_retrieve
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Get user interview topic
description: >
Retrieve a single user interview topic by ID, including its full targeting (`interviewee_emails`,
`interviewee_distinct_ids`), voice agent setup (`agent_context`, `questions`), and metadata. Use this to
list the current interviewees on a topic before adding, editing, or removing them.
feature_flag: user-interviews
user-interview-topics-send-invites:
operation: user_interview_topics_send_invites_create
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Email interview invites to every targeted interviewee
description: >
Generate (if not yet created) and email a personalized public interview link to every targeted interviewee
on a user interview topic whose identifier is an email address. The email includes a friendly greeting and a
button linking to the public voice-interview page. Distinct-ID-only interviewees are skipped and surfaced in
the response. Use this once a topic is finalized; safe to call repeatedly — invites are deduplicated per
share via the messaging system's idempotency record, and a token rotation produces a fresh send key. Email
delivery is intentionally capped at 500 recipients per send (and rate-limited per team). For larger
audiences, don't loop this — use user-interview-topics-generate-links (or the links CSV export) to get one
public link per interviewee and mail-merge them through your own email tooling instead.
include_params:
- subject
- reply_to
- send_async
feature_flag: user-interviews
user-interview-topics-shared-link-create:
operation: user_interview_topics_shared_link_create
enabled: false
user-interview-topics-shared-link-destroy:
operation: user_interview_topics_shared_link_destroy
enabled: false
user-interview-topics-test-link-retrieve:
operation: user_interview_topics_test_link_retrieve
enabled: false
user-interview-topics-update:
operation: user_interview_topics_update
enabled: false
user-interviews-create:
operation: user_interviews_create
enabled: false
user-interviews-destroy:
operation: user_interviews_destroy
enabled: false
user-interviews-list:
operation: user_interviews_list
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
list: true
title: List user interview responses
description: >
List completed user interview responses in the current project, newest first. Each row includes the
interviewee email(s) and identifier, the linked `topic` UUID (set when the interview came in via a topic's
shared link), and the AI-generated `transcript` and `summary` fields. Pass `topic` with a topic UUID to
scope to a single research topic — use this to answer questions like "do I have any responses on my X topic
yet?". Use `limit`/`offset` for pagination. `transcript` and `summary` can be long; when listing, prefer
skimming the response shapes and then call `user-interviews-retrieve` for the full text of a specific
interview. Pass `classifications` (comma-separated; any of `abandoned`, `off-topic`) to return only
responses carrying any of those classifications — e.g. `classifications=abandoned` to find drop-offs.
feature_flag: user-interviews
user-interviews-partial-update:
operation: user_interviews_partial_update
enabled: true
scopes:
- user_interview:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Set classifications on an interview response
description: >
Replace the searchable `classifications` on a single interview response. The valid classifications are
`abandoned` and `off-topic`. `abandoned` is auto-derived from the transcript when the response is recorded;
use this to correct it or to apply `off-topic`, which is never auto-set. Sending `classifications` replaces
the entire list — pass the full desired set, not a delta, so include the auto-derived `abandoned` if you
want to keep it. Filter by these later via the `classifications` parameter on `user-interviews-list` and
`user-interviews-search`.
include_params:
- classifications
feature_flag: user-interviews
user-interviews-retrieve:
operation: user_interviews_retrieve
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Retrieve a user interview response
description: >
Fetch a single completed user interview response by id, including the full AI-generated `transcript` and
`summary`. Use after `user-interviews-list` has narrowed down to a specific interview — for example, to read
what a particular interviewee said about a research topic.
feature_flag: user-interviews
user-interviews-search:
operation: user_interviews_search_create
enabled: true
scopes:
- user_interview:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Semantic search across interview responses
description: >
Search recorded user interviews by meaning, not keywords. Embeds the natural-language `query` with the same
model used to index transcripts and summaries, then returns the top matches by cosine similarity. Each match
is a single (interview, document_type) pair — an interview can appear up to twice if both its transcript and
summary score above other interviews. Use `document_types` to restrict to just `transcript` or just
`summary`. Filter by `topic_id` to scope the search to a single interview topic. `limit` caps the number of
returned matches (1-50, default 10). Filter by `classifications` (any of `abandoned`, `off-topic`) to
restrict the search to responses carrying those; combines with `topic_id` as AND. Useful for surfacing
relevant excerpts when the caller has a research question rather than a known string to grep for.
feature_flag: user-interviews
user-interviews-update:
operation: user_interviews_update
enabled: false