Guidepoint
Access Trusted Expert Insights
- Category
- Data & Analytics
- Primary Subcategory
- Private Markets, Deals & Expert Networks
Integration details
Description
Embed trusted expert insights directly into your research and decision-making workflows in real-time. Powered by Guidepoint's comprehensive and expanding Library of over 120,000+ compliance-reviewed expert interview transcripts. We combine institutional-grade quality with the volume needed for decision-critical work.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Private Markets, Deals & Expert Networks
- Secondary Subcategories
- None listed
- Brand
- Guidepoint
- Access
- Account required
- First tracked
- 2026-07-07
- Tool count
- 12
- Geography
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Get alerts for Guidepoint
Get updates when Guidepoint’s Discoverability Score or category rank changes.
ChatGPT Plugin Discovery Score
ChatGPT Plugin discovery is coming soon
ChatGPT can surface a Plugin when it matches a user's request.Your Plugin Discovery Score measures how often yours appears.
No spam. Unsubscribe any time.
What discovery looks like

Competing in ChatGPT Private Markets, Deals & Expert Networks
View Category12 tools agents can invoke
Search the full text of completed expert interview transcripts — covering what experts said, think, or reported about a company, product, or trend. Each transcript is a structured Q&A exchange between an inquirer and a subject-matter expert, covering investment and market diligence, product and adoption research, and advisory and thematic research. Best for on-the-ground competitive intelligence, channel checks, and practitioner perspectives not available in public filings, news, or analyst reports. Each result returns: - transcript_name Name of the source transcript - date Date the expert call took place - inquirer Person or organization who posed the question - respondent Subject-matter expert who answered - question The specific question asked in the transcript - answer The expert's response - context Surrounding passage from the transcript (if available) - reference_url Link to the full transcript on the Guidepoint 360 platform - source_attribution Structured citation object designed for per-claim - description: Single-line Markdown citation for placing next to a supported claim, so users can trace each assertion to its source transcript. - markdown: Preformatted Markdown citation line: label, linked title, and URL in one line for consistent Guidepoint citation practices. Scope boundary — what this tool does not cover `search_library` searches the full text of COMPLETED interview transcripts only. It has no knowledge of upcoming or past event listings, event dates, event registration status, or credit-unit costs. - Scheduled expert events (teleconferences, roundtables, webinars, focus polls) — discovery and browsing of these is covered by `search_events`. Routing distinction: requests about what experts SAID — past commentary, opinions, quotes, channel checks ("what do experts say/think about X?") — are transcript-content requests answered by this tool, even when the phrasing mentions a "call" or "event" (transcripts originate from expert calls). Requests where the event itself is the object — a session to attend, a date, a registration to manage — are answered by the events tools, not this one. How to search: send a direct thematic `query`, concrete `keywords` (names, products, tickers) to narrow which **transcripts (calls)** matter, optional `start_date` / `end_date`, optional `recency_bias`, and `size` for how many ranked excerpts to return in one response. When both `query` and `keywords` are set, keywords narrow transcripts first; the query then ranks excerpts within that set. **Keywords need not appear in every returned chunk**—they gate which calls are in scope; individual excerpts may omit a keyword entirely. See the tool parameter descriptions for phrasing and limits. Expected behavior and limitations - Results reflect **expert opinions and experiences**, not verified facts - Content is conversational and may vary in depth or specificity - Highly niche, emerging, or hypothetical topics may return: - no results, or - loosely related excerpts from adjacent domains When this occurs, broader or more general formulations of the topic may yield more relevant material. Handling multi-part or comparative requests User queries may contain multiple components. These can behave differently depending on structure: - **Comparisons across distinct companies, products, or entities** (e.g., "Veeva vs Salesforce Health Cloud") → More reliable coverage is often achieved by **searching each entity separately** and then comparing results. - **Multiple aspects of the same topic** (e.g., "Ozempic adoption and payer coverage") → These are typically best handled in a **single combined search**, since relevant insights often appear together in the same transcripts. - **Broad or loosely related multi-topic queries** → May benefit from focusing on the **core shared theme** first, then expanding if needed. Error response behavior Tool calls may fail for operational reasons such as quota exhaustion, rate limits, authentication problems, permission restrictions, invalid parameters, unavailable upstream services, or transient infrastructure issues. Errors are distinguished from empty result sets by the `isError: true` flag on the MCP response. Error payload shape When `isError` is true, the payload carries two surfaces: - `content[0].text` — a human-readable error message authored by the tool. It contains the specific facts of the failure (what went wrong, any reset time, any contact address) and is the canonical user-facing wording. - `_meta` — structured fields for programmatic handling: `error_code`, `http_status`, `retryable`, `retry_after_seconds`, `severity`, and `contact` when applicable. These fields describe the failure; they do not duplicate the `text` content. Values in `text` and `_meta` — reset times, retry intervals, contact addresses, remediation steps — are populated server-side and reflect the actual state of the account or service at the time of the call. Values not present in either surface are not available from this response. Errors vs. empty results A successful call that found no matching excerpts returns `isError: false` with an empty result set. This is a normal outcome, not a failure. Only payloads with `isError: true` represent errors. Retryable vs. non-retryable errors The `retryable` flag distinguishes two failure classes: - Retryable errors (`retryable: true`) are typically transient: 5xx responses, network blips, short-window rate limits. When present, `retry_after_seconds` indicates the suggested wait interval before a retry could succeed. - Non-retryable errors (`retryable: false`, often paired with `severity: "error"`) reflect conditions the same request will not resolve on its own — exhausted quota, revoked credentials, disabled account, invalid parameters. These conditions change only when the underlying state changes: quota reset, plan upgrade, credential refresh, or parameter correction. Rendering contract for error-severity payloads When `isError: true` and `_meta.severity` is `"error"`, the `content[0].text` field is the canonical user-facing error message. It is authored server-side for direct display to the end user and is complete on its own. Characteristics of `content[0].text` for error-severity payloads: - The first line is a single `**bolded statement**` summarizing the failure. The bold is load-bearing — it is what draws the user's eye to the actionable headline among the surrounding detail lines. - Subsequent lines carry the full detail: quantitative facts (limits, quotas, consumed-vs-remaining), reset / retry / expiry timestamps with time zone, account or plan identifiers, support or billing contacts, documentation links, and remediation steps. - Every concrete value (timestamps, identifiers, contact addresses, dollar amounts) is calibrated for this specific failure and this specific account. These values are the user's path to resolution. The verbatim text — including the server-applied markdown bold, line breaks, and any links or code spans — is the highest-fidelity representation of the error. Paraphrasing, summarizing, reordering, or stripping formatting drops information the user needs: - Paraphrasing the headline removes the bold emphasis that distinguishes the failure summary from the detail lines. - Summarizing the detail lines tends to drop at least one concrete value (a timestamp, a request ID, a contact channel). - Stripping markdown collapses the visual hierarchy the message was designed around. Example. The fidelity-preserving rendering of an error-severity payload: **Your Guidepoint data pipeline quota for this month is full** — it resets on 2026-05-31. To get access restored sooner, reach out to [email protected]. request_id: req_011CbCFmMGYGotWo8bq3cLRR A lossy rendering of the same payload, for contrast: Your quota is exhausted and won't reset until the end of the month. You can email sales to restore access sooner. The lossy version omits the exact reset date, the contact address, and the request ID, and collapses the bolded headline into prose — all values the user needs to act on the failure.
search_library
The full screener Q&A for one advisor on a request — the pre-consultation vetting questionnaire they answered, already on file. Read-only: it does not contact the advisor or notify the PM. Triggers: "show me this advisor's screener answers," "what did they say about [topic] in screening." Three sources of what an advisor said, not interchangeable: screeners are what they submitted before the consultation; a whole call is get_meeting_transcript; snippets across calls search_my_network_excerpts; the wider library search_library. If the ask does not say which, ask the client. Needs a requestId (get_requests or search_requests) and advisorId (get_request_advisors). Closed requests are returned too; no write tool acts on one. Related: select_advisor to request availability or screening; search_request_advisors_by_screeners to find advisors by screener content. Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true. An empty result is not an error.
get_advisor_screeners
Retrieve the advisors on a specific request or project. Returns every status by default (review, selected, scheduled, not_interested, completed). Triggers: "who are the advisors on this project," "who is on the [angle] angle." Needs a requestId from get_requests or search_requests. Closed requests are returned too; no write tool acts on one. For a status-scoped question pass the status filter rather than fetching everything; each row carries statusInfo.name, and statusInfo.details the billing/call metadata for completed calls. Every row carries angles — the step that turns a cohort into people: keep the advisors on the angle, then search_meetings once per advisor id. For screener responses use search_request_advisors_by_screeners. To act: select_advisor, instant_book_advisor, reject_advisor, cancel_requested_consultation. Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true. An empty result is not an error.
get_request_advisors
Retrieve a paginated list of the authenticated client's own expert network requests (also called projects), unfiltered. Scoped to that one client — no client or user argument, so "my requests" and "all requests" mean the same set. Use when browsing, or when the right filter values aren't known yet. Triggers: "show me all projects," "list my requests." One page per call, no total: hasMore true means another page probably exists, so page on until it is false before quoting a count or calling a list complete. Rows come in no guaranteed order — position carries no recency or rank. Closed requests are returned too; no write tool acts on one. For anything filtered — status ("what engagements are open") or cohort ("what do formers think") — use search_requests (title, type, status, transcripts-enabled, angle, creation date). Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true. An empty result is not an error.
get_requests
Cancel the authenticated subscription client's existing registration for one upcoming Guidepoint event so they can manage their schedule inline in the AI conversation without switching to GP360. ## Pre-flight When no `event_id` is available in the current conversation context (e.g. the user said *"cancel my Tesla registration"* without a preceding read result), call `get_user_registrations` first to obtain a real `event_id` and present the matching options to the user. `get_user_registrations` is preferred over `search_events` as the id source — it returns only events the user is actually registered for, while `search_events` does not verify registration status and can surface events the user has not registered for (passing that id here returns `NOT_REGISTERED`). Fall back to `search_events` only when `get_user_registrations` returns nothing matching the user's phrasing — which usually means the user asked to cancel something they aren't registered for. Hand-typed, guessed, or fabricated `event_id` values are unsupported. Calling this tool with a fabricated id returns `NOT_REGISTERED` (or `EVENT_LOOKUP_FAILED` when the id isn't a valid Guidepoint event at all). ## When to route away from this tool - the user is asking about their calendar / registrations — use `get_user_registrations`. - the user is browsing the general catalogue — use `search_events`. - the user asks to register — use `register_event`. - the user asks to withdraw a private advisor consultation request (identified by `requestId` + `advisorId`, not an `event_id`) — route to `cancel_requested_consultation` on the `network-mcp` surface. **Split rule**: `event_id` cancellation uses `cancel_registration`; `requestId` plus `advisorId` withdrawal uses `cancel_requested_consultation`. When the ask is a generic *"cancel my Guidepoint call"* with no id-shape signal, ask the user whether they mean an event registration or an advisor consultation before choosing. ## Pre-call confirmation gate Confirmation gate for `cancel_registration`: 1. Present the event's `title`, `date`, `event_type`, and `event advisors` (name, job_title, company when disclosed) to the user. 2. Ask for explicit affirmative confirmation (e.g. "Yes, cancel my registration", "Go ahead"). Mere acknowledgment of the event (e.g. "That's the one") is not confirmation. 3. Wait for a clear affirmative before invoking the tool with `confirmed=true`. The confirmation gate applies to the destructive write path only — `confirmed=true` requires the three steps above. A `confirmed=false` (or omitted) call is the documented preview shape and is safe to invoke without prior user consent: it fires no upstream write and returns the `CONFIRMATION_REQUIRED` envelope carrying the event metadata the caller needs to run steps 1–2 with the user. The non-conforming shape is calling `cancel_registration` with `confirmed=true` before an affirmative from the user has reached this side of the wire. Cancellation is irreversible from this tool — re-registering is a separate `register_event` call and may not always succeed (event may fill up or move into the 24-hour lock-out window). When the event metadata is not already at hand, call `get_user_registrations` first and present its title/date to the user before asking for confirmation. ## Response envelope render This is a WRITE action — the response is a success/error envelope. The confirmation line is the minimum required output; the response may additionally render the full event card (title Markdown link, metadata line, date/time, description, event advisors, cost when > 0, CTA row per the read-tool card structure) so the user sees the full context of the event they just acted on. Inline emoji, SVG markup, and image markup are permitted (widget-capable clients pass them through to their renderer; non-widget clients render as raw Markdown/HTML). **Success envelope** — surface a confirmation with the event `title` and `date`. A one-line confirmation is fine when the caller just wants the outcome; a full event card is fine when the user wants full context. Widget-capable clients may upgrade either shape to a styled confirmation banner or card. **Confirmation-required envelope** (`status: "confirmation_required"`, `isError: false`, `success: false`) — this is a **control-flow signal**, not an error. The write did not fire yet; the sidecar is asking the caller to present the event to the user and collect explicit consent. Read `structuredContent.event` (title, date, event_type, event_advisors, cu_cost, requires_compliance_approval), render it as a preview card, and ask the user a yes/no question. On explicit affirmative, call the tool again with `confirmed=true`. Confirmation-required is part of the tool's normal control flow — treating it as an error and stopping aborts every registration / cancellation at the consent step. Discriminator: `status == "confirmation_required"` AND `isError == false`. **Error envelope** — the response has `isError: true` with an `error_code` drawn from the calling tool's own documented set (see that tool's per-code retry table below — each write tool emits only its own codes) and a sanitised user-facing `message`. Surface the `message` in one sentence, preserving the `error_code` inline when it helps the user's next step (e.g. *"CU_GATED: this event carries a credit-unit cost — complete registration in Guidepoint."*), then stop. Stack traces and internal URLs never surface. The sanitised (URL-scrubbed, control-char-stripped, 200-char-truncated) detail lives in `structuredContent.upstream_error` for observability only. `CONFIRMATION_REQUIRED` is not in this list — see the confirmation-required envelope above. **cancel_registration success shape** — a successful cancellation returns a confirmation envelope with the event `title` and `date`. Render as one line, e.g. *"Your registration for [Title] on [Date] · [TZ] has been cancelled."* The full event card (title Markdown link, metadata, date/time, description, event advisors, CTA row) may optionally follow the one-line confirmation when the user would benefit from full context. The cancellation is irreversible from this tool — offers to "undo" or auto-re-register are avoided; when the user wants to re-register, that's a separate `register_event` call and may not always succeed (event may fill up or move into the 24-hour lock-out window). ## Widget vs. Markdown rendering (write tools) Write tools (`register_event`, `cancel_registration`) return a single-event confirmation, not a list — so the read-tool 'one card per event, emit in full on every client' rule does NOT apply here. On the success path, **one concise confirmation sentence (`title` + `date` + outcome verb) is the whole response**. An optional single-event Markdown card MAY follow when the user asked for full context (e.g. *"register me for the AI supply chain call and remind me what it's about"*), but the card is never mandatory and is never the primary answer. Widget-capable clients render the confirmation as plain text; there is no CTA-label pattern-match to preserve on a write-tool response (the action already happened). On error, surface the envelope's `message` in one sentence and stop — see the per-code retry table above for whether a retry is permitted. ## Permission Enforcement Access to this tool passes through independent server-side gates and fails closed on any of them. 1. **MCP sidecar entitlement** — `require_entitlement(ctx, "cancel_registration")` verifies the caller's subscription tier and the explicit tool allowlist forwarded by the MCP gateway on the `X-Allowed-Tools` header. Unauthorized callers receive `MCP_USER_NOT_ENTITLED` (HTTP 403). 2. **Sidecar `NOT_REGISTERED` gate** — the cancel reads `/forum/full.isRegistered` before the write and refuses with `NOT_REGISTERED` when the caller holds no registration for the event. This is required because the .NET write path short-circuits an unregister for a non-registered forum to an HTTP 200 success (any purchase deleted, transaction committed) WITHOUT reaching `uspx_Save_User_Forum_Registration`, so that SP's own `NOT_REGISTERED` gate (SQL lines 54-59) never runs on that path. `isRegistered` reads the same `User_Forum_Action` row the write service's existence check reads and is reliable for all registration types — unlike the removed `/activities` pre-check, which missed Non-Live Call registrations and produced false negatives. The gate fires on an explicit `isRegistered=False`; a missing value falls through to the write. 3. **Upstream write SP** — `uspx_Save_User_Forum_Registration` enforces `EVENT_EXPIRED`, `CANCELLATION_WINDOW_EXPIRED`, and the remaining eligibility the sidecar does not pre-check (non-{Roundtable, Group Meeting} lockouts, org-compliance policy, seat allocation), and scopes the `User_Forum_Action` update to the caller's user ID so cross-user cancellation is impossible. MCP maps any upstream rejection text back to the documented `error_code` values via a text-pattern matcher and a post-rejection `/forum/full` disambiguation fallback (see the Error Handling section). All gates surface as structured `isError` envelopes; the AI surfaces the envelope's `message` and stops. ## Eligibility Constraints Cancellation is available for upcoming events. Past events return `error_code: EVENT_EXPIRED` for every event type. The **24-hour cancellation lockout applies ONLY to two event types — `Roundtable` and `Group Meeting`.** For those two formats, an event starting within 24 hours of `now` returns `error_code: CANCELLATION_WINDOW_EXPIRED` and cannot be self-cancelled through this tool. **All other event types (Moderated Call, Client-Generated Call, AI-Guided Transcript, Insight Tracker, Focus Poll, Breakfast / Lunch, Marketing, One on One, Primer, Other) can be cancelled at any time up to the event's start.** - Attempting to cancel a Roundtable / Group Meeting starting within 24 hours returns `error_code: CANCELLATION_WINDOW_EXPIRED`. The sidecar checks this on the preview path (`confirmed=false`) so no consent is solicited for a doomed cancellation. - Attempting to cancel a past event returns `error_code: EVENT_EXPIRED` (all event types). - Attempting to cancel a registration the caller does not have returns `error_code: NOT_REGISTERED`. **Timezone semantics of "past" and "within 24 hours":** the sidecar computes event-expiry in UTC. Both the event's `forumStartTime` (an ISO 8601 timestamp with timezone offset or `Z` suffix, delivered by upstream) and "now" are normalised to UTC before comparison. Timezone-safe math — Python's timezone-aware datetime subtraction respects the underlying UTC instant regardless of the original zones, so an event physically past in real time is `EVENT_EXPIRED` for every caller regardless of the caller's local timezone. The 24-hour cancellation window (Roundtable / Group Meeting only) is measured the same way: `forumStartTime` − `now` ≤ 24h in UTC. The user-facing rendering surfaces both times in the caller's local timezone via the `_to_local_iso` helper so consent messages read naturally ("starts at 10:00 AM ET, less than 24 hours from now"). ## Parameters - `event_id` (string, opaque id, 1-64 chars, required) — Guidepoint upstream typically returns 36-char UUIDs but any opaque id in the `^[A-Za-z0-9\-_]+$` character set is accepted for future-proofing. The character-set constraint is enforced at the tool boundary via Pydantic validation (Malformed ids surface as a `ValidationError` before any upstream call), but the regex is stripped from the emitted JSON Schema for Claude App and OpenAI strict-mode compatibility — expect to see only `minLength: 1` / `maxLength: 64` in the schema view. Sourced from `search_events` or (preferred) `get_user_registrations`. The latter is preferred because its result set is already filtered to events the caller is actually registered for, so you avoid the `NOT_REGISTERED` error path. ## Success Response - `success: true` - `event: {event_id, title, date}` — echoed for confirmation. - `message` — human-readable confirmation line. Restate it in one sentence when telling the user. ## Error Handling Each server-side rejection returns a structured `isError: true` envelope with `error_code` set to one of the constants below. Summarize the envelope's `message` for the user in one short sentence, preserving the `error_code` when it helps context. **Retry policy is per-code — read the entry before retrying**; the default is do-not-retry. The only carve-out is a **pre-dispatch** `UPSTREAM_HTTP_ERROR` (the `/forum/full` or `/activities` calls before the write), which may be retried once with explicit user consent because the cancellation itself has not yet been dispatched. A `UPSTREAM_HTTP_ERROR` on the **write call itself** is *ambiguous* — the Guidepoint write may or may not have committed before the transport failure — and MUST NOT be retried blindly. Verify registration state via `get_user_registrations` first; if the row is gone the cancel already succeeded, if it's still there re-issue the confirmed call. | `error_code` | Retry policy | Notes | |---|---|---| | `NOT_REGISTERED` | Terminal — no retry. | User is not registered for the specified event. If they thought they were, ask them to verify in GP360 or re-run `get_user_registrations`. | | `EVENT_EXPIRED` | Terminal — no retry. | Event has already taken place. | | `CANCELLATION_WINDOW_EXPIRED` | Terminal — no retry. | Roundtable or Group Meeting starting within 24 hours; self-service cancellation is no longer available. Other event types (`Moderated Call`, `Client-Generated Call`, `AI-Guided Transcript`) do not trigger this error — the 24-hour lockout applies to Roundtable and Group Meeting only. Direct the user to contact Guidepoint if they need to cancel a locked-out event. | | `INSIGHT_API_REQUEST_REJECTED` | Terminal — no retry. | Upstream `.NET` write SP rejected the request with a 4xx that did not match any of the three eligibility patterns above. The caller can verify the registration state in GP360 or via `get_user_registrations`. | | `INSIGHT_API_UNAVAILABLE` (write dispatched — AMBIGUOUS) with `structured_extras.stage == "post_dispatch"` | **Do NOT retry blindly.** Verify state via `get_user_registrations` first. | Insight.Api was unreachable or returned 5xx / 408 / 429 (timeout, connection error, or server error) on the cancel write itself. The write may or may not have committed — if `get_user_registrations` shows the row is gone the cancel succeeded, if it's still present re-issue the confirmed call. | | `INSIGHT_API_FORBIDDEN` | Terminal — no retry. | Insight.Api returned 401 / 403 on the cancel write; the request was refused and nothing was committed. Not caller-recoverable — direct the user to Guidepoint. | | `UPSTREAM_HTTP_ERROR` (write dispatched — AMBIGUOUS) with `structured_extras.stage == "post_dispatch"` | **Do NOT retry blindly.** Verify state via `get_user_registrations` first. | Network / 5xx / transport failure at the Guidepoint write call after the POST was dispatched. The write may or may not have committed before the failure — retrying without verification can double-cancel or race the compliance cascade. If `get_user_registrations` shows the row is gone, the cancel succeeded; if it's still present, re-issue the confirmed call. Sanitised Guidepoint detail is in `structured_extras.upstream_error`, not the user-facing `message`. | | `UPSTREAM_HTTP_ERROR` (pre-dispatch — SAFE to retry) with `structured_extras.stage="pre_cancel_forum_full"` | One retry permitted after ~2 seconds with user consent; otherwise escalate. | Pre-cancel `/forum/full` read failed or returned no body. No cancel was attempted upstream — the write is guarded because passing the wrong action would write the wrong `Forum_Action_Type_ID` in the CRM. Retry is safe here because the write has not been dispatched. | | `EVENT_LOOKUP_FAILED` | Depends on `structured_extras.reason`: `"lookup_unavailable"` → retry once after ~2s with user consent; `"not_found"` → terminal, do NOT retry with the same id. | Emitted on the preview path (`confirmed=false`) from the pre-cancel `/forum/full` read. Two sub-cases distinguished by `structured_extras.reason`: (a) **`"lookup_unavailable"`** — the read raised (network / 5xx / timeout); the event may exist, so a single retry with user consent is appropriate if the outage is transient. (b) **`"not_found"`** — a payload came back but was unusable (missing `forumName` or `forumStartTime`); the id is stale or removed. Ask the user to re-source it from a recent `get_user_registrations` result and call again. | ## Idempotency & Verification `cancel_registration` is a **write-then-observe** call — the sidecar does not retry the write and does not run a post-write verification loop against `/forum/full`. Rationale: the upstream `.NET` write SP is the authoritative gate for `EVENT_EXPIRED` and `CANCELLATION_WINDOW_EXPIRED`, and its response is trusted directly (`NOT_REGISTERED` is gated in the sidecar from `/forum/full.isRegistered` — see below). The pre-cancel `/forum/full` read runs and is used both for action determination per web-UI parity (`forumType`, `enableBlockLiveParticipation`, `isTranscriptAvailable`) and to enrich the success envelope (`forumName`, `forumStartTime`). The `/forum/full` payload IS consulted on the preview path (`confirmed=false`) for two deterministic eligibility gates — `EVENT_EXPIRED` (`forumStartTime <= now`, all event types) and `CANCELLATION_WINDOW_EXPIRED` (Roundtable / Group Meeting starting within 24h of `now`) — via `_cancel_window_check`. The write path (`confirmed=true`) additionally re-applies `EVENT_EXPIRED` and the `NOT_REGISTERED` gate. `NOT_REGISTERED` IS pre-checked in the sidecar from `/forum/full.isRegistered` (explicit `False` only), because the .NET write short-circuits a not-registered unregister to an HTTP 200 success WITHOUT reaching the write SP — so the SP's `NOT_REGISTERED` gate never runs on that path. `isRegistered` reads the same `User_Forum_Action` row the write service's existence check reads and is reliable for all registration types; the earlier `/activities`-based pre-check was removed because confirmed Non-Live Call registrations are absent from those slices (false negatives), which the `/forum/full` read does not have. Every other eligibility rule — `event_type` lockouts outside Roundtable / Group Meeting, org-compliance policy, seat allocation, and any race with the compliance-officer cascade — is left to the authoritative `uspx_Save_User_Forum_Registration` SP in the .NET write path, and rejection text is mapped back to the documented `error_code` set via `_map_upstream_rejection`. Idempotency: a second `cancel_registration` on the same `event_id` after a successful cancel returns `NOT_REGISTERED` — safe to call twice; no duplicate rows or side effects. On any error envelope, retry with the same `event_id` is unproductive; verify state via `get_user_registrations` first. ## Rate Limits & Caps Single-shot write — no per-tool concurrency limit on the sidecar. Upstream APIM enforces per-caller rate limits at the gateway layer. Upstream call graph, preview vs. confirmed: - Preview call (`confirmed=false`): 1 call — `GET /forum/full` for eligibility gates + preview envelope enrichment. - Confirmed call (`confirmed=true`): 2 calls in the happy path — `GET /forum/full` (pre-cancel read for the eligibility gates + preview envelope) + `POST /event-registration` (the Guidepoint write). On upstream rejection, a post-rejection `GET /forum/full` disambiguation call can add a third. No fan-out beyond that; no client-visible latency amplifier. ## Audit Logging All `cancel_registration` calls (success AND failure) are logged server-side with: user ID, event ID, timestamp, outcome, AND correlation_id — within the same request lifecycle. The `correlation_id` is resolved from the request context (Datadog / APIM trace id) so audit lines link back to the calling user session. The AI does not need to emit its own audit log. ## Transcript content out of scope Cancellation is purely a calendar / registration-ledger operation; the MCP surface does not deliver transcript text or audio. ## When NOT to use - Browsing events (use `search_events`). - Reviewing the caller's schedule (use `get_user_registrations`). - Registering for a new event (use `register_event`). - Withdrawing a private advisor consultation request (use `cancel_requested_consultation` on the `network-mcp` surface). ## Response shape — keep the confirmation clean On success, the response carries the confirmation the tool returned (title, date, message). One confirmation sentence is the whole answer. Keep out of the response: - tool-call or route narration; avoid phrasings like 'the call was cancel_registration with event_id=X', 'confirmed=true was passed', 'routed to /event-registration action=UnRegistered', 'invoked cancel_registration'. The user asked to cancel — the outcome is the answer, not the trace. - envelope-shape or contract-clean commentary describing the response envelope or attesting that the write succeeded per contract; avoid phrasings like 'success and event fields returned per contract', 'no isError so the write succeeded', 'the response was contract-clean', 'audit fields look right', 'side_effects list matches spec'. Test: if a sentence exists to reassure the user the response is well-formed or spec-compliant, it is cut. - behavior-verification statements grading the write's behaviour; avoid phrasings like 'cancellation window honoured', 'NOT_REGISTERED gate behaved as documented', 'text-pattern mapper matched', 'gate 2b short-circuited as expected', 'past-event gate fired as expected'. One confirmation sentence is the whole answer. - reliability, latency, or retry commentary narrating timing, retry attempts, or verification loops; avoid phrasings like 'the post-write verification retried 2× before persisting', 'took N seconds', 'the first attempt hung, the retry succeeded', 'transient, not deterministic'. - cross-run comparisons referencing prior sessions, other tickets, or known bug states; avoid phrasings like 'unlike the last cancellation we saw', 'different failure profile from the earlier CONFIRMATION_REQUIRED bug'. - bug-filing or triage recommendations; avoid phrasings like 'worth filing as intermittent', 'this should be a P2', 'heads up — worth flagging'. A soft filing suggestion is a bug report inside the user's answer. - hypothetical caller / reviewer speculation; avoid phrasings like 'a caller who passed the ID directly would be surprised', 'downstream consumers relying on the success envelope would…'. - unsolicited audit or QA observations; avoid phrasings like 'audit fields look right', 'side_effects list matches spec', 'noting for the reviewer'. - audit-log framing opening with a header that treats the reply as a log entry; avoid phrasings like 'one observation for the log', 'noting for the reviewer', 'for the record', 'observations from this pass'. The chat is not a review channel. - process-narration preambles; avoid phrasings like 'cancelling your registration now…', 'processing your request…', 'based on your confirmation…'. The confirmation sentence itself is the whole answer. - chain-of-thought preambles; avoid phrasings like 'deliberated…', 'synthesised…', 'thought about…'. - unrequested follow-up offers: the response ends with the confirmation sentence. Sentences in the shape 'want me to check your other upcoming events?' or 'shall I look for a replacement slot?' are non-conforming — the shape closes with the confirmation. Compound offers listing alternative next-step parameters are the same failure mode. When the user wants a next step, they say so. One confirmation sentence is the whole answer. Bug reports go elsewhere.
cancel_registration
**Response shape.** Each registration renders as its own multi-line card block — not as a Markdown table row, a numbered list, or a compact one-line prose row. A table with one row per registration is a regression: it hides the description line, event-advisor line, and CTA row, and breaks the widget pattern-match on client renderers. Section headers group the cards and carry the section's count as a trailing suffix (`## Upcoming Registrations — 7`, `## Pending Compliance — 2`, `## Past Registrations — 20`); the count comes from each section's `count` field on the envelope. Cards inside a section render in the array order the sidecar returned. Full card contract is in `## Card contents` further down. **CTA labels this tool emits — a two-value subset of the shared card-component vocabulary.** The shared card component (used by both `search_events` and this tool) supports four CTA labels: `Register`, `Cancel registration`, `Request`, `View in Guidepoint`. **`get_user_registrations` emits only `Cancel registration` or `View in Guidepoint` from that set** — every event returned by this tool has `user_registration_status: "registered"` (or is a pending-compliance PreOrder), so `Register` and `Request` never fit. **The sidecar resolves the correct label per event and emits it as `cta_label` on the event object** — render `cta_label` verbatim rather than re-deriving it from `registration_state`, `event_is_past`, and `cu_cost`. The priority the sidecar applies (first match wins, top to bottom): | # | Condition | `cta_label` | |---|---|---| | 1 | `event_is_past == true` | `View in Guidepoint` | | 2 | `registration_state == "pending_compliance"` | `View in Guidepoint` | | 3 | upcoming confirmed registration | `Cancel registration` | Full per-row button templates live in `## CTA logic` further down. The label vocabulary is closed: `View event`, `View transcript`, `View`, `Attend`, `Join`, `Open in Guidepoint`, `Details`, `Learn more`, or any variant outside the two-value subset above is non-conforming. Actionable CTAs (`Cancel registration`) render as two lines: a Markdown link `[Cancel registration](event_url)` on line 1, then an italic prompt hint `Or copy this prompt: *"…"*` on line 2. Both lines are part of the card — the prompt-hint line gives the reader a copy-paste form of the CTA text and is carried on every actionable card. `View in Guidepoint` is a single Markdown link line with no italic hint. **Actionable CTA — the two-line form in full.** Every actionable CTA carries a two-line form: the Markdown link on line 1, the italic prompt hint on line 2. The canonical shape for a `Cancel registration` CTA on a registered upcoming event is two physical lines: [Cancel registration](event_url) Or copy this prompt: *"Cancel my registration for [title] on [date]"* Non-conforming card shapes to avoid: - Card ending on the bare word `Cancel registration` (no link, no hint). Conforming shape: the two-line form above. - Card ending on `[Cancel registration](event_url)` alone (link but no italic prompt-hint line below). Conforming shape: append the italic hint line. - Card with the prompt-hint line merged into the link line. Conforming shape: the hint sits on its own physical line, so widget renderers can keep it visually distinct from the button. Both lines are emitted on every actionable card. The widget layer decides whether to display the hint as caption text or fold it behind a button; the response carries both. **Section-count line — the closing form.** Sections use the header form `## Upcoming Registrations — N`. Two independent envelope conditions can add a trailer, and the two conditions render different trailers — they do NOT compose into one: - **Truncation trailer** — fires only when `total_events > returned` (the visible list was truncated by `limit`). Trailer text is exactly `Showing N of M registrations.` and nothing else. - **Degraded-enrichment notice** — fires only when `enrichment_degraded_count > 0`. Rendered as a separate one-line notice ('N registrations had incomplete details and are shown with limited information.') and NOT as the truncation trailer. It appears even when the list was not truncated. Both may be present in the same response — render them on separate lines, with the truncation trailer last (closing form). When only degraded records are present and nothing was truncated, the response ends with the degraded notice, no `Showing N of M` line. Non-conforming trailer shapes to avoid: - *"You have 7 upcoming registrations. Want me to show the past ones too?"* — the offer to fan out is added. Conforming shape: the response ends after the last card in the last section. - *"That's your calendar for this week. Let me know if you'd like to cancel any of these."* — the *"let me know"* tail is added. Conforming shape: the response ends after the last card. Sentences in the shape *"want me to…"*, *"should I…"*, *"happy to…"*, *"let me know if you'd like…"* after the cards are non-conforming — the shape closes after the last card. **Invocation guidance.** One tool call per user turn. The sidecar fans out server-side when the ask spans multiple slices; the response does not chain calls itself. Pick `status_filter` by the user's phrasing: - Vague / default ask (*"show me my registrations"*, *"show me my user registrations"*, *"show me my event registrations"*, *"my events"*, *"what am I signed up for"*, *"my calendar"*) → omit `status_filter`. Sidecar fans out to upcoming + pending and returns a two-section envelope (Upcoming Registrations + Pending Compliance). Past events are chatter the user rarely wants on this kind of ask and are not included — the user opts in via `"all"` (or `"past"`). - Exhaustive ask — presence of the word *"all"*, *"everything"*, or *"full history"* in the phrasing (*"show me ALL my registrations"*, *"show me all my user registrations"*, *"show me all event registrations"*, *"everything I've registered for"*, *"my full history"*) → `status_filter="all"`. Sidecar fans out to upcoming + pending + past and returns a three-section envelope. The trigger for the past section is explicitly the presence of "all" / "everything" / "full history" in the user's ask — without one of those words, `status_filter="all"` is not passed. - Specific-slice ask (*"my upcoming registrations"*, *"my pending registrations"*, *"my past registrations"*) → `status_filter="upcoming"` / `"pending"` / `"past"`. Sidecar returns a flat envelope with just that one slice. When the response carries `sections[]`, iterate it and render one labeled block per entry; skip any section with `count == 0`. When the response is flat (`events[]` at the top level), render one section using the caller's chosen slice as the header. See the guidance table further down for the full phrasing → `status_filter` → envelope-shape mapping. Retrieve the events the authenticated subscription client is registered for, so the user can review their schedule, find a session they registered for, or initiate follow-on actions (e.g. cancellation) without leaving the AI research conversation. `status_filter` selects the slice: - omit (default) — vague-ask default. Server-side fan-out to /activities/{2,1} (upcoming + pending); returns a two-section envelope. Past events are not included. - `"all"` — exhaustive ask. Server-side fan-out to /activities/{2,1,0} (upcoming + pending + past); returns a three-section envelope. Reserved for phrasings that explicitly ask for everything — 'ALL my registrations', 'everything', 'my full history'. - `"upcoming"` — single slice, flat envelope. Upcoming confirmed registrations only (does not include compliance-pending PreOrders). - `"past"` — single slice, flat envelope. Past confirmed registrations only. - `"pending"` — single slice, flat envelope. Compliance-pending PreOrders only. Includes past-dated PreOrders (e.g. a compliance approval that didn't come through before the event date) — the Guidepoint activities API is authoritative. Use when the user asks *"show me my pending registrations"*, *"what's awaiting compliance approval"*, or wants exactly the compliance-workflow queue. ## When to route away from this tool - the user's phrase is a catalogue-browse query with no possessive — that's `search_events`, not this tool. Trigger phrases that route to `search_events`, not this tool: *"show me all upcoming events"*, *"any events on <topic>"*, *"upcoming <company/sector> events"*, *"events in Q3"*, *"what's coming up next week"*. - Route to this tool when the user's phrase names their own registrations. Trigger phrases: *"my events"*, *"my registered events"*, *"my registrations"*, *"what am I signed up for"*, *"what am I registered for"*, *"my calendar"* (planning ask). The distinguishing signal is a possessive (*"my"* / *"I"* / *"me"*). - Ambiguous mid-cases — phrases that combine both signals (e.g. *"what events are on my calendar next week"* — has both *"events"* and *"my calendar"*) — avoid guessing. The correct move is to ask a one-line clarification: *"Do you want the events already on your calendar (your registrations), or a browse of what's coming up in the catalogue?"* and route based on the reply. Asking beats misrouting. - **Non-possessive `"registrations"` phrases** (e.g. *"show me all event registrations"*, *"list event registrations"*, *"pull up registrations"*) — the word `registrations` is a registered-user concept, but the phrasing lacks the authenticated-user ownership signal (*"my"* / *"I"* / *"me"*), so it also reads as a catalogue-discovery request. Ask a one-line clarification: *"Do you mean YOUR event registrations (what you're signed up for), or the catalogue of events open for registration?"* and route based on the reply. Do not assume ownership from context alone. - the user asks to register — use `register_event`. - the user asks to cancel — use `cancel_registration` (preferred pattern: use this tool first to obtain a correct `event_id`, then pass it to `cancel_registration`; this tool's results are pre-filtered to events the user is actually registered for). - the user asks what an expert said (transcript-content search) — this tool does not cover transcript content; route the query to `search_library` on the `/aies-mcp` surface (transcript questions, answers, excerpts, and direct quotations exchanged in past calls). - the user asks what was discussed or said in one of their own past 1:1 expert consultations (caller's own meeting-transcript content) — route to `search_my_network_excerpts` on the `/aies-mcp` surface. **Split rule**: `get_user_registrations` covers event-ledger state (which curated events the caller is signed up for, keyed by `event_id`); `search_my_network_excerpts` covers the caller's own completed private-meeting transcripts (questions, answers, discussion excerpts from 1:1 client-expert consultations). - the user asks about completed private consultations or actual meeting records — route to `search_meetings` on the `/network-mcp` surface. **Split rule**: `get_user_registrations` covers event registration history (curated Guidepoint events keyed by `event_id`); `search_meetings` covers completed private consultations and actual meeting records (keyed by `requestId` + `advisorId`). ## Access Control **Required entitlement scope:** `get_user_registrations`. The MCP sidecar calls `require_entitlement(ctx, "get_user_registrations")` as the first statement of the tool body — before any upstream I/O. Callers without the scope receive an `AUTH_FORBIDDEN` envelope and no upstream call fires. This mirrors the enforcement pattern documented in `register_event` / `cancel_registration`'s `## Permission Enforcement` sections; MCP is defense in depth on top of upstream compliance filtering, not the sole gate. MCP access is limited to subscription clients. Results are scoped to the authenticated user only, resolved from the session identity at query time (not cached). This tool does not accept a user-id argument and does not expose another user's data. Results are also scoped to the caller's organization subscription — cross-organization visibility is not exposed; the Guidepoint registration store keys every row on the caller's user identity. ## Data Handling & Privacy The event advisor projection on each registered event carries three fields: `name`, `job_title`, and (when disclosed) `company`. Undisclosed names return the literal `"Anonymous"`; the `company` field is omitted from the JSON entirely — not null, not empty string — when undisclosed. Event identifiers are opaque UUIDs. ## Registration lifecycle vs. attendance — semantic scope This tool covers the **registration lifecycle** — which events the caller has signed up for, which are pending compliance approval, and which have already occurred while the caller was registered. It does NOT cover **attendance** (who actually joined the call, how long they stayed, whether they viewed the recorded transcript afterwards). A returned past event with `registration_state: "upcoming"` and `event_is_past: true` means the caller was registered when the event happened — it is not proof they attended. Do not phrase results as *"events you attended"*; use *"events you registered for"* (or *"events on your calendar"* for upcoming). Attendance analytics live in downstream Guidepoint reporting, not in the MCP surface. ## Rate Limits & Caps Per-call cap: `limit` accepts `1..100` (default 20 — sized to surface a natural sweep of the caller's slice without paging). Per-event enrichment is bounded by an internal concurrency cap of 20 Guidepoint API requests (see `_ENRICHMENT_CONCURRENCY` at the top of the tool module). The Guidepoint APIM gateway enforces per-caller rate limits at the platform layer. **Topical / keyword filtering is not server-side.** This tool has no `query`, `sectors`, `tickers`, `event_types`, or `date_from`/`date_to` parameter — the caller's Guidepoint registration store is fetched as-is per slice, capped at `limit`. Client-side topical filtering is the pattern: fetch the slice, then filter the returned events by `title`, `sectors[]`, `tickers[]`, `tag_categories[]`, or `event_advisors[].name` in the caller's own composition. For narrow topical searches on a caller with many registrations, warn the user that a match may sit beyond the `limit` cap and offer to raise it; do not silently assert *"no matching registrations found"* when the returned window is smaller than `total_events`. ## Error Handling Server-side rejections return a structured `isError: true` envelope with `error_code` set to the constant below. Summarize the envelope's `message` for the user in one short sentence, preserving the `error_code` if it helps context, then stop. - `UPSTREAM_UNAVAILABLE` — the Guidepoint registrations API was unreachable or returned a transport-level failure (network, 5xx). Retry once after ~2 seconds if the user agrees; escalate otherwise. No results were emitted. - `UPSTREAM_TIMEOUT` — the Guidepoint list call exceeded the 15-second wall-clock timeout enforced by the MCP sidecar. Tell the user the Guidepoint API is slow and suggest they retry in a moment; if the problem persists, direct them to view their registrations directly in Guidepoint. Silent retry from within a single call is unproductive — a repeat call under the same conditions will almost certainly time out again. **Soft-signal fallbacks that do not fail the call**: - `structuredContent.enrichment_degraded_count` (integer) — one or more registrations could not be fully enriched (Guidepoint `/forum/full`, `/costConfirmation`, or `/ai-summary-preview` failed). Those records still appear in `events[]` with `enrichment_degraded: true`. Callers should key the response record shape on the `enrichment_degraded` boolean — it is the discriminator between the complete-record variant and the degraded-record variant of the event object. **Complete record** (`enrichment_degraded` unset or `false`) — the full canonical contract above applies; every field listed is present. **Degraded record** (`enrichment_degraded: true`) — the record carries exactly these six fields: `event_id`, `enrichment_degraded: true`, `user_registration_status`, `registration_state`, `event_is_past`, `cta_label`. Nothing else — `title`, `date`, `event_advisors`, `sectors`, `tickers`, `tag_categories`, `cu_cost`, `event_url`, `description`, `registration_status`, and both `requires_*` booleans are OMITTED from a degraded record. `cta_label` remains present because the CTA-priority table keys on `event_is_past` + `registration_state`, both of which the placeholder carries. Renderers should skip the full card layout on a degraded row and surface it as a single line ('Registration [event_id] · details unavailable') so the caller retains length parity with the Guidepoint list. Mention the aggregate degraded count in one line; the degraded events are not silently omitted. **`user_registration_status` on pending-compliance rows — the discriminator rule.** `user_registration_status` is a coarse presence label (the caller has a row in `User_Forum_Action`) and reads as `"registered"` on every row this tool returns, including pending-compliance PreOrders whose `registration_state` is `"pending_compliance"`. A pending-compliance PreOrder is not yet a confirmed registration — the compliance-officer review is still open — but it does have a persisted row. **`registration_state` is the authoritative workflow discriminator** — key CTA-selection, cancel-eligibility, and calendar-invite decisions on `registration_state` (`"upcoming"` / `"past"` / `"pending_compliance"`), not on `user_registration_status`. When surfacing a pending row to the user, say *"pending compliance review"*, not *"registered"*. ## Parameters - `status_filter` (optional, `"upcoming"` | `"past"` | `"pending"` | `"all"`) — selects the slice(s) to fetch. Omit for the vague-ask default (sidecar fans out to upcoming + pending → two-section envelope). Pass `"all"` for the exhaustive ask (sidecar fans out to upcoming + pending + past → three-section envelope) — reserved for phrasings that include "all", "everything", or "full history". Pass `"upcoming"` / `"pending"` / `"past"` for a single-slice ask (flat envelope). One tool call per user turn — the sidecar does the fan-out; the response does not chain calls itself. - `limit` (optional, integer, default 20, max 100) — **maximum registrations per slice**. Default 20 covers the typical 'show me my registrations' mix in each slice. On the multi-slice fan-out paths (omit or `"all"`), each slice returns up to `limit` rows independently — the aggregate response can carry more than `limit` events when multiple sections have data. Raise or lower to surface more or fewer per slice; no pagination navigation is available. ## Event Object Response — canonical contract Same shape as `search_events`. Every event includes all of the following fields when the record is fully enriched (see the degraded-record variant below). `description` is populated for both past events and everything else — past events get a richer summary (AI post-event summary from `/forum/{id}/ai-summary-preview` trimmed to card-brief length); every other event (upcoming, live, unresolved-date) gets an agenda-based summary composed from `forumAgendaItems`. Both may be empty when the Guidepoint API lacks the source material. - `event_id` (string, required) — pass to `cancel_registration`. - `title` (string, required) — event title. - `event_type` (string, required) — one of the 12 display labels: `"Moderated Call"`, `"Client-Generated Call"`, `"AI-Guided Transcript"`, `"Insight Tracker"`, `"Focus Poll"`, `"Group Meeting"`, `"Roundtable"`, `"Breakfast / Lunch"`, `"Marketing"`, `"One on One"`, `"Other"`, `"Primer"`. Server-normalised via `_normalize_event_type` (identical mapping used by `search_events` — cross-tool consistent). Note: `Non-Live Call` (DB) normalises to `Moderated Call` (matches web UI display). - `date` (string, ISO 8601 with timezone offset, required) — event start time in the authenticated user's local timezone, resolved per call. - `event_advisors` (array of `{name, job_title, company?}`, required) — same shape as `search_events`. `name` is the disclosed name or `"Anonymous"`; `job_title` is always present; `company` is omitted (not null, not empty) when undisclosed. - `registration_status` (string, required) — `"open"` / `"closed"`. Internal signal — the literal string `open`/`closed` is not displayed to the user. It drives whether to offer `cancel_registration` (open → cancellable subject to the 24h window; closed → view-only past event). - `user_registration_status` (string, required) — guaranteed `"registered"` for every event returned by this tool. - `event_url` (string, required) — deep-link to GP360. - `sectors` (list[string], required) — canonical sector names (empty list OK). - `cu_cost` (number, required, non-null) — credit-unit cost of the registration. ALWAYS surfaced as the event's bold cost line — `**This event carries a N credit-unit cost.**` — for every registration including free ones (a free registration shows the explicit `0 credit-unit cost` line, never omitted). Parity with `search_events`'s cost-display policy. - `requires_gp360_registration` (boolean, required) — boolean twin of `cu_cost`. `true` when the registration required paid sign-up in Guidepoint (i.e. `cu_cost > 0`); `false` otherwise. Provided as a convenience for clients that prefer a boolean gate over the numeric cost. Same show-when-true / suppress-when-false rendering rule as `cu_cost`. - `requires_compliance_approval` (boolean, required) — `true` when this event's registration goes through the Guidepoint compliance-approval workflow. Cross-tool shape parity with `search_events`. Events surfaced here are already registered or pending, so the flag is mostly informational here; the primary consumer is `search_events` (where it drives the `Request` CTA). Sourced from `isApprovalRequired` on the Guidepoint `/forum/full` payload; defaults to `false` when absent. - `description` (string, optional) — brief topic summary sourced from the Guidepoint API, in this cascading order: (1) the AI-generated summary from `/forum/{id}/ai-summary-preview` (past events only); (2) a top-level summary alias on the /forum/full payload (`forumSummary` / `aiSummary` / etc.); (3) a 1-2 sentence agenda summary composed from `forumAgendaItems`. Whatever lands is trimmed to a card-friendly brief (first ~2 sentences or ≤220 chars, sentence-boundary aware). When all three sources are empty, `description` is null and the skill instructs the caller to compose a 1-2 sentence summary from the event's other populated fields (title, event_type, date, sectors, tickers, tag_categories, event_advisors). The sidecar does not synthesize a template placeholder — client-side composition produces more natural prose. - `tickers` (list[string], required) — stock-ticker symbols tagged on this event (`"AAPL"`, `"TSLA"`). Empty list when no tickers were tagged upstream. Rendered on the card's metadata line alongside `event_type` and `sectors`. Cross-tool shape parity with `search_events`. - `tag_categories` (array of `{name, category}` dicts, required, may be empty) — event-focus tag categories from the Guidepoint index (e.g. `[{"name": "Academic", "category": "Viewpoint"}, {"name": "Equity", "category": "Asset Class"}]`). Rendered by grouping entries by `category` (see the shared card renderer): `Viewpoint: Academic · Asset class: Equity`. Empty array when no tags were assigned. Cross-tool shape parity with `search_events` (identical wire format via the shared `_extract_tag_categories` helper). - `registration_state` (string, required — enum `"upcoming"` | `"pending_compliance"`) — the registration's workflow state as returned by `_map_action_to_registration_state` from the upstream `/activities/{n}` `action` field. `"upcoming"` covers every confirmed registration (Register / Confirmed / missing action); `"pending_compliance"` covers PreOrder / Pending / AwaitingCompliance variants. Note the enum carries no `"past"` value — event tense is a separate axis captured by `event_is_past` below. Drives the CTA logic table: `Cancel registration` renders on `"upcoming"` for a not-yet-past event; `"pending_compliance"` rows surface `View in Guidepoint` (self-cancel is not available on a PreOrder). Values are matched against `status_filter` for section routing: the pending section collects `"pending_compliance"` rows even though the `status_filter` input value is spelled `"pending"`. - `event_is_past` (boolean, required) — `true` when the event's `date` is earlier than `now_utc`, `false` otherwise. This is an independent axis from `registration_state`; the two together drive CTA selection. A past-dated event the caller is confirmed-registered for reports `event_is_past: true` AND `registration_state: "upcoming"` (the registration workflow is confirmed even though the event has occurred). A pending-compliance event reports `registration_state: "pending_compliance"` regardless of `event_is_past`. - `cta_label` (string, required) — the resolved CTA button label for this event, one of `"Cancel registration"` or `"View in Guidepoint"`. The sidecar computes this server-side by applying the CTA priority table (top of description) to `event_is_past` and `registration_state`. Render the value verbatim as the button label — do not re-derive from the underlying fields. ## Response envelope Envelope shape depends on `status_filter`: **Single-slice callers** (`status_filter="upcoming"` / `"pending"` / `"past"`) get the flat envelope: `events` (list), `returned` (integer, matches `len(events)`), and `total_events` (integer, always present). **Multi-slice callers** (omit `status_filter` or pass `"all"`) get the sections envelope: `sections` (list of `{name, count, events}`), `returned` (integer, sum across sections), and `total_events` (integer, sum across sections). Each section's `name` is the verbatim header (`Upcoming Registrations` / `Pending Compliance` / `Past Registrations`); `count == len(events)` within the section. Sections order is the render order — do not reorder. **`total_events` and truncation.** `total_events` is the pre-cap Guidepoint match count (summed across sections on the multi-slice paths). It drives a user-facing render rule: when `total_events > returned`, the response appends exactly one short sentence 'Showing N of M registrations.' at the end (see the response-shape block below for the exact wording). This is not pagination navigation: no `has_more`, no `offset`, no next-page cursor is emitted. **Soft signals.** Optional field `enrichment_degraded_count` (integer, top-level on either envelope shape) appears only when one or more per-event enrichment calls failed. Dates are always in a valid timezone — the sidecar silently falls back to America/New_York (US Eastern, DST-aware) when the caller does not send an `X-Timezone` request header. ## Sort order Registrations are sorted by **Activity Time descending** (most recently added first) client-side before enrichment. The Guidepoint registrations API does not guarantee an ordering, so this sort is applied inside the sidecar. The returned `events[]` array is not re-sorted downstream. ## When to use - "Show me the events I'm signed up for" - "What did I register for on Guidepoint last quarter?" - "Find the roundtable I registered for last week on lithium supply" - As the lookup step before calling `cancel_registration` — you need a specific `event_id`, and this tool gives you the candidate list scoped to events the user IS actually registered for. ## Phrasing → status_filter → envelope-shape mapping One tool call per user turn. The sidecar fans out server-side when the ask spans multiple slices; the response does not chain multiple calls itself. Pick `status_filter` by matching the user's phrasing — the trigger for including past events is explicitly the presence of the word *"all"*, *"everything"*, or *"full history"* in the ask: | User phrasing | `status_filter` value | Envelope shape | Section headers rendered | |---|---|---|---| | *"show me my registrations"*, *"show me my user registrations"*, *"show me my event registrations"*, *"my registrations"*, *"what am I signed up for"*, *"my events"*, *"my calendar"* — the vague/default ask | omit (default) | sections envelope with 2 sections | `Upcoming Registrations` + `Pending Compliance` | | *"show me ALL my registrations"*, *"show me all my user registrations"*, *"show me all event registrations"*, *"everything I've registered for"*, *"my full history"* — the exhaustive ask (word "all" / "everything" / "full history" present) | `"all"` | sections envelope with 3 sections | `Upcoming Registrations` + `Pending Compliance` + `Past Registrations` | | *"show me my upcoming registrations"* — specific-slice ask | `"upcoming"` | flat envelope | `Upcoming Registrations` | | *"show me my pending registrations"* / *"what's awaiting compliance"* — specific-slice ask | `"pending"` | flat envelope | `Pending Compliance` | | *"show me my past registrations"* / *"what did I register for last quarter"* — specific-slice ask | `"past"` | flat envelope | `Past Registrations` | **The distinction between omit and `"all"` matters.** Passing `"all"` without an explicit "all", "everything", or "full history" cue in the user's phrasing over-fetches history the user did not request. A vague *"show me my registrations"* without one of those cue words surfaces upcoming + pending only — past events require the opt-in. **Rendering the sections envelope.** When the response carries `sections[]`, iterate the array in order and render one labeled block per entry. Each entry has `{name, count, events}` — the section header text is `name`, the events under it are `events`, and `count == len(events)`. Sections with `count == 0` are skipped entirely — an empty header is not rendered. The section order in the array is the render order; the array is not reordered downstream. **Rendering the flat envelope.** When the response carries `events[]` at the top level (single-slice calls), render one section using the caller's chosen slice as the header: `Upcoming Registrations`, `Pending Compliance`, or `Past Registrations`. **Section grouping.** When the sidecar returns a sections envelope, the section headers are part of the render — a merged flat list without them loses the state distinction between confirmed calendar entries, PreOrders awaiting compliance approval, and past events. Flattening `sections[]` into a single unlabeled list is the common failure mode to avoid. **Correct** rendering for the vague ask *"show me my registrations"* (envelope carries a two-section array): ``` ## Upcoming Registrations — 2 [card 1 — an upcoming confirmed event] [card 2 — another upcoming confirmed event] ## Pending Compliance — 1 These registrations are awaiting compliance approval and are not yet confirmed on your calendar. [card 3 — a pending PreOrder] ``` **Incorrect** rendering for the same ask (merged flat list, no section headers): ``` [card 1 — upcoming] [card 2 — upcoming] [card 3 — pending] ← indistinguishable from upcoming; user cannot tell this is a PreOrder awaiting compliance ``` **Rationale**: past events are chatter the user rarely wants when asking the vague *"my registrations"* question. The vague default gives the user two sections — Upcoming plus Pending — which answers *"what's on my plate"*. Users who want past events opt in via `status_filter="all"` (three sections) or `status_filter="past"` (single slice). **Zero-count sections are skipped entirely** — if the envelope's `sections[]` array carries `{name: "Pending Compliance", count: 0, events: []}`, an empty `Pending Compliance` header is not rendered. Only sections with `count > 0` are rendered. The order of surviving sections matches the array order. **Section headers are verbatim + count suffix** — use exactly these labels (do not shorten, rephrase, or invent new ones): `Upcoming Registrations`, `Pending Compliance`, `Past Registrations`. These match the `name` field on each section entry; copy verbatim. **Append the section's `count` value as ` — {count}`** so the header reads `## Upcoming Registrations — 7`, `## Pending Compliance — 2`, `## Past Registrations — 20`. Count value comes straight from the section's `count` field on the envelope. **Header is the frame** — no scene-setting preamble before the section header. Openers like *"Here's your registration picture..."* are dropped. Start straight at the section header. For the `Pending Compliance` section specifically, add one short lead-in sentence under the header explaining these are awaiting compliance approval, e.g. *"These registrations are awaiting compliance approval and are not yet confirmed on your calendar."* ## Card contents (rendered per event) **Response format — read first.** Each event renders as its own multi-line card block. Blank line between cards. The response is a stack of these card blocks — nothing else. A Markdown table listing events in rows is not the response format this tool uses; on Claude Desktop, ChatGPT desktop, and Perplexity the widget layer pattern-matches the card block to upgrade it into an interactive card with bound buttons — a table row has no cost line, no description paragraph, no registration-status line, no CTA link, and no prompt hint, so a table strips those fields from the visible response entirely. When the response contains N events, the shape is N card blocks stacked with blank lines between them — see the `Rendered example` further down for the exact card shape. **Shapes this tool's response does not use** (each event always gets its own card block, not one of these): - a Markdown table with one row per event - a compact one-line prose row per event - a card whose fields are collapsed into a single pipe-delimited line (e.g. `Cost: 0 CU | Status: … | Request`) instead of the separate lines below - a numbered list showing title only - any tabular arrangement that trades a card per event for a row per event **Emit the cards in the exact order the `events` array arrives — index 0 first, index 1 second, and so on to the end.** The array order is the answer's order. Do not regroup or re-rank the events under a subheading — not by topic, theme, relevance to the query, company, sector, event type, date, registration status, or CTA. A heading that labels the response as a whole is fine; a heading that splits one call's `events` array into subgroups is not — grouping reorders the array, which departs from the sidecar's chosen sort. Concrete counter-example: a Tesla query returning 15 events spanning robotaxi, humanoid robotics, energy storage, and other topics renders as 15 sequential card blocks in the order the array arrived — NOT as four subsections titled *Robotaxi*, *Humanoid Robotics*, *Energy Storage*, *Other* with the cards partitioned across them. Inline emoji, SVG markup, and image markup are permitted anywhere in the card (widget-capable clients pass them through to their renderer; non-widget clients render them as raw Markdown/HTML). Emit exactly these lines in this order: - **Title** — bold, rendered as a Markdown link: `[Title](event_url)`. The raw `event_url` is not exposed as visible text. - **Event type + sectors + tickers** — one metadata line: event type (title-cased) · comma-separated sectors (if any) · ticker chips from `tickers[]` (if any). No label prefixes like `Type:` or `Sectors:`. Note on sectors: the `event.sectors[]` array carries the deduped TOP-LEVEL PARENT buckets only (e.g. `"Telecom, Media & Technology"`), not the 15-20 leaf sub-sector labels the Guidepoint index tags on each event — already sized for a readable card line. Emit the field verbatim; no further trimming needed. (The `sectors` INPUT filter accepts any level of granularity — canonical top-level names or sub-sectors — since search is permissive; only the OUTPUT array is deduped for readability.) - **Date and time** — prefix with the calendar glyph `📅 ` then `MMM DD, YYYY · hh:MM AM/PM zz` on its own line (12-hour clock with uppercase AM/PM; the `zz` timezone label comes from the ISO offset in the `date` string, e.g. `-04:00` → `ET`). Example: `📅 Jul 27, 2026 · 03:00 PM ET`. Past cards may show date only. `end_time` is not rendered on the card. - **Tag categories** — one line rendered as `Viewpoint: … · Asset class: … · Event focus: … · Event-driven: …`, using only the categories that carry values on `tag_categories[]`. Omit the whole line when the array is empty or every category is absent. Group the values by category (e.g. every `{name: 'Academic', category: 'Viewpoint'}` becomes part of the `Viewpoint:` group). Multiple values in one category join with `, ` (e.g. `Asset class: Equity, Debt`). - **Description** — one paragraph, 1-2 sentences. The sidecar has already trimmed to card-brief length; further truncation drops usable context. - **Event advisors** — prefix with the person glyph `👤 ` then one line per the Event advisor display rule below. - **Registration-status line** — render as a natural-language phrase on its own line, between the event-advisor line and the CTA row. **The phrase is conditional on `registration_state`, not on `user_registration_status`** (which reads `"registered"` on every row returned by `get_user_registrations`, including pending PreOrders, and would otherwise incorrectly claim a pending compliance request is a confirmed registration). The mapping: - `registration_state == "upcoming"` AND `event_is_past == false` → `You're registered` - `registration_state == "upcoming"` AND `event_is_past == true` → `You were registered` (past-tense; the event has occurred but the registration was confirmed) - `registration_state == "pending_compliance"` → `Pending compliance review` (NEVER `You're registered` — the PreOrder is not yet a confirmed registration) - `user_registration_status` outside `"registered"` (`not_registered` / `waitlisted`) → omit the line entirely; the CTA row conveys the state visually. - **Cost line** — always rendered, on every event including free ones (see cost display policy below). - **CTA row** — see the tool's CTA logic table for the exact format. CTA labels are drawn from a FIXED four-value set — exactly `Register`, `Cancel registration`, `Request`, or `View in Guidepoint`. No other label is valid. If none of the four fits, the correct move is `View in Guidepoint` — not to invent a new label. Do not invent labels for brevity, symmetry, or fit — labels like `View event`, `View transcript`, `View recording`, `View`, `Open in Guidepoint`, `Transcript`, `Recording`, `Go to event`, `Attend`, `Join`, `Details`, `Learn more`, `More info`, `Open`, or any variant not in the four-value set is a regression. Labels use sentence case — not title case. The label text is fixed for the chosen CTA. Do not substitute alternate wording based on what the URL path looks like or the event's content type. The URL `platform.guidepoint.com/transcript?id=…` still renders under `View in Guidepoint`; the URL path is a Guidepoint routing detail, not a signal to relabel the CTA. Actionable CTAs (Register / Cancel registration / Request) render as two lines: line 1 is a Markdown link `[Label](event_url)`, and line 2 is an italic prompt hint `Or copy this prompt: *"…"*`. The prompt hint gives the reader a copy-paste form of the CTA text and is carried on every actionable card. The italic prompt text follows the same shape as the CTA — `Register me for [title] on [date]` / `Cancel my registration for [title] on [date]` / `Request approval for [title] on [date]`. Non-actionable CTAs (View in Guidepoint) render as a single Markdown link line `[View in Guidepoint](event_url)` — no italic prompt hint follows, because View in Guidepoint is a URL-open action rather than an LLM re-trigger. A plain-label CTA (a bare `Register` string with no link) breaks the widget upgrade path and leaves non-widget clients with nothing to click; the link form is the emit convention on every card. **Rendered example** — one card looks exactly like this (each event in the response becomes one such block, blank line between blocks): ``` **[Tesla Robotaxi Rollout: Investor Q&A](https://example/e1)** Moderated Call · Automotive · TSLA 📅 Sep 12, 2026 · 02:00 PM ET Viewpoint: Industry · Asset class: Equity · Event focus: Single company A discussion of Tesla's robotaxi launch timeline and near-term unit economics. 👤 Priya Ramachandran — Former Director of Autonomy, Waymo You're registered **This event carries a 0 credit-unit cost.** [Cancel registration](https://example/e1) Or copy this prompt: *"Cancel my registration for Tesla Robotaxi Rollout on Sep 12, 2026"* ``` A response with N events is N such blocks stacked with blank lines between them — one card per event, all fields present per the field list above. ## CTA logic (`get_user_registrations`) Every event in this tool's response has `user_registration_status: "registered"` by definition. **The label to render lives on the event object as `cta_label`** — the sidecar resolves it server-side from `event_is_past` and `registration_state`, and the response emits the resolved value. Render `cta_label` verbatim as the button label. The table below documents which field combinations produce which label for audit and review; it is not a computation the LLM re-runs. | Condition | `cta_label` | |---|---| | Upcoming registered event (`event_is_past == false` AND `registration_state == "upcoming"`) | `Cancel registration` | | Pending event (`registration_state == "pending_compliance"`) | `View in Guidepoint` | | Past registered event (`event_is_past == true`) | `View in Guidepoint` | **About `registration_state` vs `event_is_past`.** These two fields describe different axes and are not conflated: - `registration_state` is the registration type: `"upcoming"` (confirmed registration, active in the system) or `"pending_compliance"` (PreOrder — user submitted but compliance hasn't approved yet). It is derived from the Guidepoint `/activities` write action (PreOrder → pending_compliance; anything else → upcoming). It does not reflect event tense — a past event the user is confirmed-registered for still reports `registration_state: "upcoming"`. - `event_is_past` is the event tense: True if the event's start time is at or before `now_utc`, False otherwise. Computed sidecar-side from `/forum/full.forumStartTime` compared against UTC `now`. The CTA table above uses both fields to distinguish an actionable Cancel from a view-only past-registered row. Checking only `registration_state` is not sufficient. **CTA button rendering.** Every CTA in the table above emits as a clickable Markdown link — that is the button surface. Widget-capable clients (Claude web and desktop, ChatGPT web and desktop, Perplexity web and desktop) render the CTA link as a styled inline button; non-widget clients render the raw Markdown link. A plain-text CTA name (no link) leaves widget clients without a button surface and non-widget clients without a click target — the link form is the emit convention on every card. Per-row button templates: - **Cancel registration** — a clickable link `[Cancel registration](event_url)` followed by an italic copy-prompt hint on the next line: `Or copy this prompt: *"Cancel my registration for [title] on [date]"*`. - **View in Guidepoint** — a single clickable link: `[View in Guidepoint](event_url)`. Used for Pending rows (the caller cannot self-cancel a PreOrder — compliance workflow must complete or reject first) and for past registered events. Rules for the CTA: - `Cancel registration` renders as two lines: a Markdown link `[Cancel registration](event_url)` on line 1, then an italic prompt hint on line 2: `Or copy this prompt: *"Cancel my registration for [title] on [date]"*`. This hybrid gives the user two paths to complete the action: (A) click the blue link → navigates to the GP360 event page where they cancel there; or (B) select+copy the italic prompt text (or type it) → paste into the chat → the response routes to `cancel_registration` with the confirmation gate. - Widget-capable clients (Claude web and desktop, ChatGPT web and desktop, Perplexity web and desktop) render the CTA link as a styled inline button; the italic hint line may be hidden or shown as small caption text by the widget layer. - `View in Guidepoint` renders as a Markdown link only (no italic hint line) — it's a URL-open action, not a prompt trigger. Widget clients upgrade it into a native button that opens the link. - `cancel_registration` is not called directly from the card. The prompt-based routing goes through the cancel confirmation gate. ## Widget vs. Markdown rendering The Markdown card block is the response — emit it in full using the card structure above, on every client, one card per event. Widget-capable clients (Claude web and desktop, ChatGPT web and desktop, Perplexity web and desktop) upgrade the Markdown into interactive cards with bound buttons after pattern-matching on the CTA label text; all other clients (Claude Code, claude.ai, terminal MCP clients) render the Markdown as-is. If the widget layer errors during render, the Markdown is what remains visible — so the emit convention is unconditional. A condensed table with one row per event is a regression: it breaks the widget pattern-match, strips the description line, and hides the CTA row. ## Cost display policy `cu_cost` is always present on the response, non-null, `>= 0`. Display rule: - **Always** surface the cost as its own bold line, on EVERY event — paid and free alike: `**This event carries a 5 credit-unit cost.**` when `cu_cost > 0`, and `**This event carries a 0 credit-unit cost.**` when `cu_cost == 0`. The line is never omitted; a free event shows the explicit `0 credit-unit cost` line rather than silence. Render the number exactly as `cu_cost` reports it. Not rendered as a raw badge or table column. Boolean twin: `requires_gp360_registration` is `true` when `cu_cost > 0`. ## Event advisor display - Prefix the line with the person glyph `👤 ` to signal an event-advisor row. - Show `name` when disclosed. When undisclosed, the response returns the literal `"Anonymous"` — pass through. - Show `job_title` (always present, may be empty string). Render `"👤 Name — Job Title"`. - When `company` is present on the event_advisor object, append `", Company"` → `"👤 Name — Job Title, Company"`. - When `company` is absent (omitted from JSON, not null/empty), the `", Company"` tail is omitted entirely. No placeholder is rendered. - When the `event_advisors` array is empty, the event-advisor line is omitted entirely (the `👤 ` glyph is skipped along with the rest of the line). ## Total-count line (when the list is truncated) The response envelope carries `total_events` (informational upstream match count) alongside `returned` (visible count). Render rule: - When `total_events > returned`, the response ALWAYS ends with exactly one short sentence naming both counts: *"Showing 10 of 276 registrations."* (visible count · "of" · total · plural noun · period). Just that sentence, and it appears on every truncated response — omitting it is a regression. The response ends after this line; no follow-up offer, no pagination prompt, no *"want me to…"* or *"Would you like to register for any of these…"* sentence follows it. - When `total_events == returned`, the line is skipped entirely. All results fit; the cards are the whole set, and the response ends after the last card — nothing else trails it. No pagination navigation exists (no `offset`, no `has_more`, no next-page cursor). Prompts like *"want to see more?"* are skipped — a higher visible count requires a larger `limit` value at request time. ## When NOT to use - Discovering new events the user could register for — use `search_events`. - Querying registrations for a different user — this tool has no user-id parameter and does not expose another user's data. - Topical registration lookups ("find the [topic] event I registered for", "which of my upcoming events is about biotech") — this tool cannot filter by topic, sector, or company; it accepts only `status_filter` and `limit`. Retrieve with `status_filter` and match the topic in-context from the returned `title` / `sectors` / `event_advisors`. Topical registration queries do not route to `search_events`, which cannot scope to the caller's own registrations. ## Transcript content out of scope Even for past events the user is registered for, the `description` field is a short AI summary — not the underlying transcript text or audio. Users who need transcripts go through the GP360 web UI. ## Response shape — what goes around the cards The event-card widget is the response. The response carries the cards, at most one short lead-in sentence, and the soft-signal caveats the envelope surfaces (`returned == 0`, `enrichment_degraded_count`). Everything else is dropped. Keep out of the response: - tool-call or route narration (naming the tool, its parameters, or how the call was shaped — the user asked for events, not the trace); avoid phrasings like 'the call was get_user_registrations with no filter', 'routed to /activities/1', 'default browse per the skill', 'limit: 20' - envelope-shape or contract-clean commentary describing the response envelope or attesting it is well-formed; avoid phrasings like 'returned matches len(events)', 'no pagination fields', 'consistent with the contract', 'the response was contract-clean', 'clean envelope'. Test: if a sentence exists to reassure the user that the response is spec-compliant, it is cut. Additional rule for this section: when `total_events > returned`, the response appends exactly one short sentence in the shape 'Showing 20 of 45 registrations.' at the end — visible count, the word 'of', the total, then 'registrations' and a period. Just that sentence. The line is skipped when `total_events == returned`. - behavior-verification statements that grade what the tool did (sort, filter, timezone, CTA routing); avoid phrasings like 'activity-time desc sort behaved as documented', 'all events future so each card routes to cancel', 'sort order behaved as documented'. The widget is the confirmation. - reliability, latency, retry, or hang commentary; avoid phrasings like 'the call hung for N minutes', 'transient, not deterministic', 'the first attempt hung, the retry succeeded'. - cross-run comparisons referencing prior sessions or known bug states; avoid phrasings like 'unlike the last session', 'different failure profile from the earlier call'. - bug-filing or triage recommendations; avoid phrasings like 'worth filing as intermittent', 'heads up — worth flagging'. A soft filing suggestion is a bug report in the chat window. - hypothetical caller / reviewer speculation; avoid phrasings like 'a caller who passed status_filter=past would be surprised', 'downstream consumers relying on the activity_time ordering would…'. - unsolicited data-quality or fixture observations labelling events as fixtures or commenting on individual field quirks; avoid phrasings like 'description echoes the title', 'near-duplicate events — possibly intentional test fixtures', 'looks like a fixture'. Render as-returned. - cross-record pattern claims comparing fields across the response array; avoid phrasings like 'all upcoming events have empty tickers while the past ones carried TSLA', 'the pending events look different from the confirmed ones'. Internal catalog QC is not part of the answer. - audit-log framing opening with a header that treats the reply as a log entry; avoid phrasings like 'two observations for the log from this pass', 'noting for the reviewer', 'for the record'. The chat is not a review channel. - scene-setting preambles that name environments; avoid phrasings like 'here are your registered events from the QA catalog', 'based on your request…'. Environments are not named. - chain-of-thought preambles; avoid phrasings like 'deliberated…', 'synthesised…', 'thought about…'. - unrequested follow-up offers: the response ends with the last card (or the total-count line, when the list is truncated); no follow-up sentence trails the cards. Phrasings like 'want me to also pull past registrations?', 'shall I re-run with a higher limit?', 'want me to…', 'happy to…', 'Want me to register you for any of these?' are regressions — the response terminates instead. Compound offers listing alternative parameter values are the worst-shape instance of this failure mode. Bug reports go elsewhere. Reliability telemetry lives in the correlation_id; catalog-quality reports live in the ticket tracker; neither belongs in the reply.
get_user_registrations
Register the authenticated subscription client for one specific Guidepoint event (moderated call, roundtable, group meeting, focus poll, insight tracker, client-generated call, or AI-guided transcript) so they can complete registration inline in the AI conversation instead of switching to GP360. ## Pre-flight When no `event_id` is available in the current conversation context (e.g. the user said *"register me for the next Tesla earnings call"* without a preceding `search_events` result), call `search_events` first to obtain a real `event_id` and present the options to the user. Hand-typed, guessed, or fabricated `event_id` values are rejected — calling this tool with a fabricated id returns `EVENT_LOOKUP_FAILED`. ## When to route away from this tool - the user is asking about their own calendar / registrations — use `get_user_registrations`. - the user is browsing the general catalogue — use `search_events`. - the user asks to cancel — use `cancel_registration`. - the user asks to book an advisor consultation on an existing request (they carry a `requestId` and either an `advisorId` or a shortlist) — route to `select_advisor` or `instant_book_advisor` on the `network-mcp` surface. **Split rule**: `event_id` means `register_event`; `requestId` plus `advisorId` means `select_advisor` or `instant_book_advisor`. - the user asks for a new persona-based advisor engagement keyed by `persona_uuid` (no existing `event_id`, no `requestId`) — route to `schedule_interview`. **Split rule**: `register_event` only for an existing catalogue `event_id`; `schedule_interview` only for a `persona_uuid` returned by `search_expert_hint`. ## Pre-call confirmation gate Confirmation gate for `register_event`: 1. Present the event's `title`, `date`, `event_type`, and `event advisors` (name, job_title, company when disclosed) to the user. 2. Ask for explicit affirmative confirmation (e.g. "Yes, register me", "Go ahead"). Mere acknowledgment of the event (e.g. "That's the one") is not confirmation. 3. Wait for a clear affirmative before invoking the tool with `confirmed=true`. The confirmation gate applies to the destructive write path only — `confirmed=true` requires the three steps above. A `confirmed=false` (or omitted) call is the documented preview shape and is safe to invoke without prior user consent: it fires no upstream write and returns the `CONFIRMATION_REQUIRED` envelope carrying the event metadata the caller needs to run steps 1–2 with the user. The non-conforming shape is calling `register_event` with `confirmed=true` before an affirmative from the user has reached this side of the wire. Registration consumes the user's calendar slot and credit-unit allocation; treat it like a financial commitment. ## Response envelope render This is a WRITE action — the response is a success/error envelope. The confirmation line is the minimum required output; the response may additionally render the full event card (title Markdown link, metadata line, date/time, description, event advisors, cost when > 0, CTA row per the read-tool card structure) so the user sees the full context of the event they just acted on. Inline emoji, SVG markup, and image markup are permitted (widget-capable clients pass them through to their renderer; non-widget clients render as raw Markdown/HTML). **Success envelope** — surface a confirmation with the event `title` and `date`. A one-line confirmation is fine when the caller just wants the outcome; a full event card is fine when the user wants full context. Widget-capable clients may upgrade either shape to a styled confirmation banner or card. **Confirmation-required envelope** (`status: "confirmation_required"`, `isError: false`, `success: false`) — this is a **control-flow signal**, not an error. The write did not fire yet; the sidecar is asking the caller to present the event to the user and collect explicit consent. Read `structuredContent.event` (title, date, event_type, event_advisors, cu_cost, requires_compliance_approval), render it as a preview card, and ask the user a yes/no question. On explicit affirmative, call the tool again with `confirmed=true`. Confirmation-required is part of the tool's normal control flow — treating it as an error and stopping aborts every registration / cancellation at the consent step. Discriminator: `status == "confirmation_required"` AND `isError == false`. **Error envelope** — the response has `isError: true` with an `error_code` drawn from the calling tool's own documented set (see that tool's per-code retry table below — each write tool emits only its own codes) and a sanitised user-facing `message`. Surface the `message` in one sentence, preserving the `error_code` inline when it helps the user's next step (e.g. *"CU_GATED: this event carries a credit-unit cost — complete registration in Guidepoint."*), then stop. Stack traces and internal URLs never surface. The sanitised (URL-scrubbed, control-char-stripped, 200-char-truncated) detail lives in `structuredContent.upstream_error` for observability only. `CONFIRMATION_REQUIRED` is not in this list — see the confirmation-required envelope above. **register_event success shapes — two variants, both are success (neither is rendered as an error, a failure, or "registration failed"):** - **Normal registration** — `status: "registered"`, `success: true`, `registered: true`. The user is now registered and the Guidepoint back-office CRM dispatches a calendar invite to their Guidepoint-registered email. Surface a one-line confirmation using the envelope's `message`, e.g. *"Your registration for [Title] on [Date] has been submitted. A calendar invite will be sent to your Guidepoint-registered email."* Fill `[Title]` from `event.title` and `[Date]` from `event.date`. - **Compliance-pending variant** — `status: "pending_compliance"`, `success: true`, `registered: false`. This fires when the event carries `requires_compliance_approval == true` (Guidepoint `isApprovalRequired`). The compliance PreOrder row was written; the user's request is now with their compliance team for review. Surface a one-line confirmation using the envelope's `message`, e.g. *"Your registration for [Title] on [Date] is awaiting compliance approval."* The `registered: false` field means "confirmation is deferred until compliance approves," not "the request failed." Email dispatch is not mentioned on this variant — compliance-pending events don't trigger a calendar invite (the invite only fires once compliance approves). Compliance is not attributed to Guidepoint — the compliance approval is done by the client's own compliance department, not by Guidepoint. Say "compliance approval" without attribution; phrasings like "Guidepoint compliance" or "pending Guidepoint compliance review" are avoided. The full event card (title Markdown link, metadata, date/time, description, event advisors, CTA row) may optionally follow the confirmation line when the user would benefit from full context. ## Widget vs. Markdown rendering (write tools) Write tools (`register_event`, `cancel_registration`) return a single-event confirmation, not a list — so the read-tool 'one card per event, emit in full on every client' rule does NOT apply here. On the success path, **one concise confirmation sentence (`title` + `date` + outcome verb) is the whole response**. An optional single-event Markdown card MAY follow when the user asked for full context (e.g. *"register me for the AI supply chain call and remind me what it's about"*), but the card is never mandatory and is never the primary answer. Widget-capable clients render the confirmation as plain text; there is no CTA-label pattern-match to preserve on a write-tool response (the action already happened). On error, surface the envelope's `message` in one sentence and stop — see the per-code retry table above for whether a retry is permitted. ## Permission Enforcement Access to this tool passes through two independent server-side gates and fails closed on either: 1. **MCP sidecar entitlement** — `require_entitlement(ctx, "register_event")` verifies the caller's subscription tier and the explicit tool allowlist forwarded by the MCP gateway on the `X-Allowed-Tools` header. Unauthorized callers receive `MCP_USER_NOT_ENTITLED` (HTTP 403). No AI-side allowance overrides this — the sidecar refuses to call the upstream endpoint. 2. **Upstream compliance eligibility** — the sidecar's eligibility pre-check reads `/forum/full` for `isApprovalRequired` and `approvalStatus`. Compliance-pending events route through `action="PreOrder"` upstream and return a success-variant envelope with `status: "pending_compliance"` to the AI. **Paid events (`cu_cost > 0`) are refused server-side as the PRIMARY gate** — the sidecar reads `/forum/full.forumPayGo. isCuCostZero` and returns `CU_GATED` before any write fires. The AI's pre-call `cu_cost` check is a courtesy that avoids a round trip; it is NOT the enforcement layer. The upstream `.NET` write SP `uspx_Save_User_Forum_Registration` is the authoritative gate; MCP is defense in depth. Both gates surface as structured `isError` envelopes with a documented `error_code` — see the Error Handling section below. ## Eligibility — when this tool is not called In any of these cases `register_event` is not called — the event's `event_url` is surfaced instead so the user can act in GP360: - `cu_cost > 0` — paid event. Display the event details AND the numeric cost + a "View in Guidepoint" CTA pointing at `event_url`. This tool is not called for paid events. Sample phrasing: "This event carries a 5 credit-unit cost — registration completes through Guidepoint via the event URL." Users are shown the price before being redirected. - `user_registration_status: registered` — caller is already registered. Surface `event_url` instead of re-registering. - `registration_status: closed` — event has passed or is no longer accepting registrations. Surface `event_url`. ## Streaming Progress This tool streams per-step progress markers via the MCP `notifications/message` and `notifications/progress` channels so streamable-HTTP-capable MCP clients can show live status. The 5 steps are: 1. Verifying event eligibility… 2. Confirming cost… 3. Recording purchase intent… 4. Submitting registration… 5. Verifying registration persisted… ## Parameters - `event_id` (string, opaque id, 1-64 chars, required) — Guidepoint upstream typically returns 36-char UUIDs but any opaque id in the `^[A-Za-z0-9\-_]+$` character set is accepted for future-proofing. The character-set constraint is enforced at the tool boundary via Pydantic validation (Malformed ids surface as a `ValidationError` before any upstream call), but the regex is stripped from the emitted JSON Schema for Claude App and OpenAI strict-mode compatibility — expect to see only `minLength: 1` / `maxLength: 64` in the schema view. Sourced from a prior `search_events` or `get_user_registrations` result. ## Success Response The tool's success envelope is a **discriminated union keyed on `status`**. Two variants: **Variant 1 — `status: "registered"`** (normal-register path): - `success: true` - `status: "registered"` - `registered: true` — the write persisted upstream and the user IS registered. **Variant 2 — `status: "pending_compliance"`** (PreOrder written; awaiting compliance approval): - `success: true` - `status: "pending_compliance"` - `registered: false` — the PreOrder row exists but the user is NOT registered yet; compliance approval is pending. The two variants share the remaining fields (below) with the `event_url` and `side_effects` caveats already noted. **`registered` is bound to `status` — always `true` on `"registered"`, always `false` on `"pending_compliance"`. Reading `registered` without checking `status` will mis-classify pending-compliance rows as failures.** ### Fields on both variants - `verified` — boolean. Operator-only signal (logging + audit). `true` means a post-write /forum/full re-read observed `isRegistered: true`. `false` means the re-read couldn't confirm within the retry window (either the verification endpoint was unavailable or the read hit the NOLOCK read-replica before the write's row was visible). The `verified` field is not surfaced to the user and does not vary the response wording. `success: true` + `registered: true` is the canonical user-facing success signal — the Guidepoint write endpoint already returned success by the time this envelope is built. The standard confirmation is surfaced regardless of the `verified` value. - `event: {event_id, title, date, cu_cost, requires_gp360_registration}` — echoed for confirmation. `title` and `date` are guaranteed non-empty on a successful registration (an EVENT_LOOKUP_FAILED envelope fires earlier if they couldn't be resolved). `cu_cost` on the success path is always `0` — the authoritative CU gate at STEP 2 runs on BOTH the normal-register and pending-compliance variants and refuses paid events with `CU_GATED` before the write fires. This includes the fail-closed branch: if `/costConfirmation` fails AND `/forum/full.forumPayGo` gives no definitive numeric cost AND `isCuCostZero` is not explicitly `true`, the tool refuses the write with `CU_GATED` + `structured_extras.reason: "cost_verification_unavailable"` rather than proceeding with an unknown cost. A completed `register_event` response — whether `status: "registered"` or `status: "pending_compliance"` — is therefore always a verified-free registration. `requires_gp360_registration` is always `false` on success; provided as the boolean twin of `cu_cost` for clients that prefer a boolean gate over the numeric cost. - `event_url` — deep-link to the GP360 event page. Populated on `status: "registered"`; empty string (`""`) on `status: "pending_compliance"` (no GP360 URL exists until compliance approves the PreOrder). Callers rendering a link should check for a non-empty value before attempting to display it. - `message` — human-readable confirmation line. Restate it in one sentence when telling the user. - `side_effects` (array) — machine-readable list of asynchronous actions the upstream write triggers. See the `## Side Effects` section for details. ## Side Effects On the normal-register path (`status: "registered"`) the Guidepoint back-office CRM dispatches an `.ics` calendar invite to the caller's registered email address asynchronously. The response's `side_effects` array carries a machine-readable record of this for downstream observability. On the normal-register path the response tells the user a calendar invite is on its way to their Guidepoint-registered email — the fact is factually accurate for that path. On the compliance-pending path (`status: "pending_compliance"`) email is not mentioned — no invite is dispatched until compliance approves, so a promise of one would be misleading. The recipient address is not echoed in the response (the calling session already identifies the user). ## Response Variants The tool emits one of three envelope shapes: 1. **Success (registered)** — `success: true, registered: true`. Standard happy path, described above. 2. **Success (pending compliance)** — `success: true, status: "pending_compliance", registered: false`. The Guidepoint PreOrder write did happen and a compliance-review row was created; the user is not yet registered but the workflow has started. `event_url` is an empty string (no GP360 URL exists until compliance approves). **Distinguish this variant from the normal-register variant when telling the user** — a pending-compliance response is NOT a completed registration and MUST NOT promise a calendar invite. Surface it with the envelope's `message` (which reads *"Your registration for [Title] on [Date] is awaiting compliance approval."*), not with the normal-register phrasing. 3. **Error** — `isError: true` with a structured `error_code` (see below). No upstream state was mutated on any error path. ## Error Handling Errors return a structured `isError: true` envelope with `error_code` set to one of the constants below. Summarize the envelope's `message` in one sentence when telling the user, preserving the error code. **Retry policy is per-code — read the entry before retrying**; do not blanket-retry on any error envelope. | `error_code` | Retry policy | Notes | |---|---|---| | `MCP_USER_NOT_ENTITLED` | Do NOT retry. | HTTP 403 from the entitlement gate — the caller's subscription tier or the `X-Allowed-Tools` allowlist forwarded by the MCP gateway does not permit `register_event`. Not caller-recoverable; direct the user to Guidepoint. | | `EVENT_LOOKUP_FAILED` | Depends on `structured_extras.reason`: `"lookup_unavailable"` → retry once after ~2s with user consent; `"not_found"` → do NOT retry with the same `event_id`. | Two sub-cases distinguished by `structured_extras.reason`: (a) **`reason: "lookup_unavailable"`** — the `/forum/full` lookup raised (network / 5xx / timeout); the event may well exist, so a single retry with user consent is appropriate if the outage is transient. (b) **`reason: "not_found"`** — `/forum/full` returned a payload the sidecar could not use (missing `forumName` / `forumStartTime`, or an empty record); the id is stale or removed. Ask the user to re-source it from a recent `search_events` or `get_user_registrations` result — retrying the same id will fail again. | | `ALREADY_REGISTERED` | Do NOT retry. | User is already registered; return `event_url` from the envelope. | | `EVENT_EXPIRED` | Do NOT retry. | Event date has passed; return `event_url`. | | `CU_GATED` | Do NOT retry (paid event); retry once after ~30s with user consent if `structured_extras.reason == "cost_verification_unavailable"`. | Two sub-cases distinguished by `structured_extras.reason`: (a) **paid event** (no reason, or `reason` absent) — the authoritative `/costConfirmation` returned a positive cost. Return `event_url` so the user can complete registration in Guidepoint. (b) **`reason: "cost_verification_unavailable"`** — `/costConfirmation` failed AND `/forum/full.forumPayGo` gave no definitive numeric signal, so the sidecar cannot verify the event is free. Refused fail-closed to prevent a paid event from being written with a false `cu_cost: 0`. Retry once with user consent if the outage is transient; otherwise, direct the user to the GP360 event page. | | `GROUP_MEETING_PARENT` | Do NOT retry with the parent id. | The `event_id` names a Group Meeting parent (a container that holds registrable child sessions). `structured_extras.child_sessions[]` lists the `{event_id, title, date}` of each child. Present the list and register on a specific child id. | | `REGISTRATION_REJECTED` | Do NOT retry without new user intent. | Upstream returned an error on the write. | | `UPSTREAM_HTTP_ERROR` (registration write dispatched — AMBIGUOUS) with `structured_extras.stage == "post_dispatch"` | **Do NOT retry blindly.** Verify state via `get_user_registrations` first. | Network / 5xx / transport failure on the `/event-registration` write after the POST was dispatched. The registration may or may not have committed before the failure — retrying without verification can double-register or race the compliance cascade. If `get_user_registrations` shows the event, the registration succeeded; if not, re-issue the confirmed call. Sanitised upstream detail is in `structured_extras.upstream_error`, not the user-facing `message`. | | `UPSTREAM_HTTP_ERROR` (purchase step — before the registration write) with `structured_extras.stage == "purchase"` | **Retry once after ~2 seconds** if the user agrees; escalate otherwise. | Transport-level error (network failure, 5xx) while recording purchase intent in STEP 3, before the `/event-registration` write was dispatched. No registration was submitted, so a single retry is safe. Sanitised upstream detail is in `structured_extras.upstream_error`, not the user-facing `message`. | | `INSIGHT_API_UNAVAILABLE` | **Depends on `structured_extras.stage`.** `stage == "post_dispatch"` → do NOT retry blindly; verify via `get_user_registrations` first. `stage == "purchase"` → the registration was never submitted; retry once with user consent if transient. | The upstream Insight.Api was unreachable or returned 5xx / 408 / 429 (timeout, connection error, or server error) on a write step. The `stage` marker says where: `"post_dispatch"` = the `/event-registration` write itself was already dispatched, so it may or may not have committed — if `get_user_registrations` shows the event it succeeded, otherwise re-issue; `"purchase"` = the earlier purchase-intent step, before the registration write. | | `INSIGHT_API_FORBIDDEN` | Terminal — do NOT retry. | Insight.Api returned 401 / 403 on the write; the request was refused and nothing was committed. Not caller-recoverable — direct the user to Guidepoint. | | `INSIGHT_API_REQUEST_REJECTED` | Terminal — do NOT retry without new user intent. | Insight.Api rejected the write with a 4xx (validation / bad request); the write did not commit. | Notes on per-code fields: - `ALREADY_REGISTERED`, `EVENT_EXPIRED`, and `CU_GATED` envelopes carry `event_url` in `structured_extras`; the other codes do not. - `GROUP_MEETING_PARENT` envelopes carry `child_sessions[]` in `structured_extras` — surface the list to the user, do not retry blindly. ## Rate Limits & Caps Single-shot write — no per-tool concurrency limit on the sidecar. Upstream APIM enforces per-caller rate limits at the gateway layer. Post-write verification retries up to 4 attempts with `0.5s / 1.0s / 2.0s / 2.5s` backoff (total ≤ 6.0s) before falling through to `verified: false` on the success envelope — bounded upside on latency + prevents runaway retry storms against the upstream write path. ## Audit Logging All `register_event` calls (success AND failure) are logged server-side with: user ID, event ID, timestamp, outcome, AND correlation_id — within the same request lifecycle. The AI does not need to emit its own audit log. ## Transcript content out of scope Successful registration grants access via the GP360 web UI; the MCP surface does not deliver transcript text or audio. ## When NOT to use - Read-only browsing (use `search_events`). - Looking up the caller's own schedule (use `get_user_registrations`). - Cancellation (use `cancel_registration`). - Booking an advisor consultation on an existing project (use `select_advisor` or `instant_book_advisor` on the `network-mcp` surface). - Requesting a new persona-based advisor engagement (use `schedule_interview`). ## Response shape — keep the confirmation clean On success, the response carries the confirmation the tool returned (title, date, message) plus any required side-effect notices. One confirmation sentence plus required side-effect notices is the whole answer. Keep out of the response: - tool-call or route narration; avoid phrasings like 'the call was register_event with event_id=X', 'confirmed=true was passed', 'routed to /event-registration', 'invoked register_event'. The user asked to be registered — the outcome is the answer, not the trace. - envelope-shape or contract-clean commentary describing the response envelope or attesting that the write succeeded per contract; avoid phrasings like 'response includes success, event, message per contract', 'no isError envelope so the write succeeded', 'the response was contract-clean', 'side_effects list matches spec', 'audit fields look right', 'verified=true as expected'. Test: if a sentence exists to reassure the user the response is well-formed or spec-compliant, it is cut. - behavior-verification statements grading the write's behaviour; avoid phrasings like 'write path succeeded as documented', 'compliance workflow triggered as expected', 'CU gate honoured', 'the two side-effect notices are both present as expected'. One confirmation sentence is the whole answer. - reliability, latency, or retry commentary narrating timing, retry attempts, or verification loops; avoid phrasings like 'the post-write verification retried 2× before persisting', 'took N seconds', 'the first attempt hung, the retry succeeded', 'transient, not deterministic'. The `verified` field is operator-only and is not surfaced. - cross-run comparisons referencing prior sessions, other tickets, or known bug states; avoid phrasings like 'unlike the last registration we saw', 'different failure profile from the earlier CONFIRMATION_REQUIRED bug'. - bug-filing or triage recommendations; avoid phrasings like 'worth filing as intermittent', 'this should be a P2', 'heads up — worth flagging'. A soft filing suggestion is a bug report inside the user's answer. - hypothetical caller / reviewer speculation; avoid phrasings like 'if anyone tried this with cu_cost > 0 expecting X, they would see Y', 'a caller who passed the ID directly would be surprised', 'downstream consumers relying on side_effects would…'. - unsolicited audit or QA observations; avoid phrasings like 'audit fields look right', 'side_effects list matches spec', 'noting for the reviewer', 'the two side-effect notices are both present as expected'. - audit-log framing opening with a header that treats the reply as a log entry; avoid phrasings like 'one observation for the log', 'noting for the reviewer', 'for the record', 'observations from this pass'. The chat is not a review channel. - process-narration preambles; avoid phrasings like 'registering you now…', 'processing your request…', 'based on your confirmation…'. The confirmation sentence itself is the whole answer. - chain-of-thought preambles; avoid phrasings like 'deliberated…', 'synthesised…', 'thought about…'. - unrequested follow-up offers: the response ENDS with the confirmation sentence (plus any required side-effect notices). NEVER trail a follow-up sentence after the confirmation. Phrasings like 'want me to check your other upcoming events?', 'shall I set a reminder?', 'want me to browse similar events?' are regressions — the response terminates instead. Compound offers listing alternative next-step parameters are the worst-shape instance of this failure mode. When the user wants a next step, they say so. One confirmation sentence plus any required side-effect notices is the whole answer. Bug reports go elsewhere.
register_event
Search Guidepoint's curated catalogue of event advisor events — moderated calls, roundtables, group meetings, focus polls, insight trackers, client-generated and AI-guided transcripts — so a subscription client can discover and evaluate live or recorded event advisor events relevant to their research thesis, due-diligence question, sector tracking work, or upcoming-catalyst monitoring WITHOUT leaving the AI chat surface. Guidepoint events are compliance-reviewed, scheduled engagements where one or more vetted subject-matter event advisors present and answer questions on a specific industry, company, regulatory, or technology topic. Every returned event includes a brief `description` when one is available. **Past events** return the AI-generated post-event summary. **Upcoming events** return a 1-2 sentence agenda summary (from the moderator's published agenda items) so the user has topical context before deciding whether to register. Upcoming events also return registration metadata so the user can register through the `register_event` tool in the same conversation. **Response shape.** Every event in the response renders as its own multi-line card block — not as a Markdown table row, a numbered list, a compact one-line prose row, or a bulleted list of titles. A table or compact-list rendering is a regression: it hides the description line, event-advisor line, and CTA row, and breaks the widget pattern-match on client renderers. The N events in the response render as N sequential card blocks in the exact order the `events` array arrived — no theme subsections, no tense buckets (a *"Upcoming — 1 event"* / *"Recent past — 8 events"* split of one call's array is a regression), no sector groupings, no company groupings. Full card contract is in `## Card contents` further down. **CTA labels are FIXED — exactly `Register`, `Cancel registration`, `Request`, or `View in Guidepoint`.** No other label is valid. `View event`, `View transcript`, `View`, `Attend`, `Join`, `Open in Guidepoint`, `Details`, `Learn more` — any variant outside the four-value set is a regression. **The sidecar resolves the correct label per event and emits it as `cta_label` on the event object** — render `cta_label` verbatim rather than re-deriving it from `registration_status`, `user_registration_status`, `cu_cost`, and `requires_compliance_approval`. The priority the sidecar applies (first match wins, top to bottom): | # | Condition | `cta_label` | |---|---|---| | 1 | `cu_cost > 0` | `View in Guidepoint` | | 2 | `open` + registered | `Cancel registration` | | 3 | `open` + not registered + `cu_cost == 0` + `requires_compliance_approval == True` | `Request` | | 4 | `open` + not registered + `cu_cost == 0` + no compliance requirement | `Register` | | 5 | `closed` (any user state) | `View in Guidepoint` | Rule #1 dominates: paid events (`cu_cost > 0`) always route to `View in Guidepoint`, even when the user is registered and even when compliance approval would otherwise apply. Full per-row button templates live in `## CTA logic` further down. Actionable CTAs (`Register` / `Cancel registration` / `Request`) render as TWO lines: a Markdown link `[Label](event_url)` on line 1, then an italic prompt hint `Or copy this prompt: *"…"*` on line 2. Both lines are part of the card — the prompt-hint line gives the reader a copy-paste form of the CTA text and is carried on every actionable card. `View in Guidepoint` is a single Markdown link line with no italic hint. **Actionable CTA — the two-line form in full.** Every actionable CTA carries a two-line form: the Markdown link on line 1, the italic prompt hint on line 2. The canonical shape for a `Register` CTA is two physical lines: [Register](event_url) Or copy this prompt: *"Register me for [title] on [date]"* The same shape carries for `Cancel registration` and `Request` — different prompt text, identical two-line format. Non-conforming card shapes to avoid: - Card ending on the bare word `Register` (no link, no hint). Conforming shape: the two-line form above. - Card ending on `[Register](event_url)` alone (link but no italic prompt-hint line below). Conforming shape: append the italic hint line. - Card with the prompt-hint line merged into the link line (`[Register](url) Or copy this prompt: "…"`). Conforming shape: the hint sits on its own physical line, so widget renderers can keep it visually distinct from the button. Both lines are emitted on every actionable card. The widget layer decides whether to display the hint as caption text or fold it behind a button; the response carries both. **Truncated-list trailer — the canonical forms.** The truncated-list trailer has three shapes, one per envelope signal: - `Showing N of M events.` — the default. Used when `total_events > returned` and the envelope does NOT carry `total_events_is_approximate: true`. The total M is an exact upstream match count. - `Showing N of ~M events.` — the approximate variant. Used when the envelope carries `total_events_is_approximate: true` and M is below the ceiling. The tilde marks the total as a ceiling estimate rather than an exact count. - `Showing N of 500+ events.` — the ceiling variant. Used when the envelope carries `total_events_is_approximate: true` and Guidepoint reported the upstream ceiling (500). The `+` marks that there may be more beyond the ceiling. All three forms share the same shape: visible count, the word 'of', the total (with `~` or `+` marker when applicable), the plural noun, a period. The response ends at that period. When `total_events == returned` the trailer is skipped and the response ends after the last card. Non-conforming trailer shapes to avoid: - *"That's 10 of 60 events scheduled for tomorrow."* — the `That's` preamble and `scheduled for tomorrow` tail are additions. Conforming shape: `Showing 10 of 60 events.` as the whole line. - *"Showing 10 of 60 events. Want me to narrow by sector, event type, or free vs. credit-cost events to make the list more manageable?"* — the trailing follow-up sentence is added. Conforming shape: the response ends at the trailer's period. - *"10 of 60 events for tomorrow. Would you like me to filter further?"* — same shape with different wording. The response ends at the trailer's period. Sentences in the shape *"want me to…"*, *"should I…"*, *"happy to…"*, *"let me know if you'd like…"*, or compound offers listing alternative parameter values are non-conforming — the shape closes after the period. ## Call cadence — one call per user turn Exactly one `search_events` call answers the user's ask. Parse all scoping cues in the user's prompt into tool parameters up front and invoke once — the response IS the answer. Do not fire a follow-up `search_events` call to tighten `time_scope`, `sort`, `date_from`, `date_to`, or any other filter, and do not fire one to *"see if there's a cleaner slice"*, *"check for upcoming ones"*, or *"re-sort by date"*. A mixed past + upcoming return is the intended shape for a plain query; past dates in the response are not a signal to re-query. ## No cross-turn topical bleed Build `query` and every filter parameter STRICTLY from the current user prompt. Prior turns' topics do not carry over. If a previous turn was about Tesla and the current turn is *"show me upcoming events"* with no company / topic in the current words, pass `query=""` (browse — routes to the newest-first list endpoint) with `time_scope="upcoming"` — do NOT set `query="Tesla"` on inference from conversation history. Same rule for `sectors`, `regions`, `tickers`, `event_types`, `date_from`, `date_to`: only populate them from what the user typed on THIS turn. Concrete examples: - Turn 1: *"show me events for Tesla"* → `query="Tesla"`. Turn 2 (same conversation): *"show me upcoming events"* → `query=""`, `time_scope="upcoming"` — NOT `query="Tesla"`. - Turn 1: *"any biotech events"* → `query="biotech"`. Turn 2: *"what's coming up next week"* → `query=""`, `time_scope="upcoming"` — NOT `query="biotech"`. - Turn 1: *"events in North America"* → `regions=["North America"]`. Turn 2: *"show me all past events"* → `time_scope="past"`, no `regions` — NOT `regions=["North America"]`. The user's current words are the only scoping signal. When the current prompt has no topic / no filter cues, the correct call shape is a plain browse — that is the intended semantics of *"show me upcoming events"*. ## Supported filter dimensions `search_events` supports these filter dimensions (all optional; pass as tool arguments): - `query` — free-text keyword / topical phrase for the relevance ranker. - `sectors` — resolved against the LIVE Guidepoint sector taxonomy (any level: parent bucket, sub-sector, leaf). Unknown values surface in `unrecognized_sectors`. - `regions` — resolved against the LIVE Guidepoint region taxonomy (continent, sub-region, or country). Unknown values surface in `unrecognized_regions`. - `tickers` — strict company-ticker filter. - `event_types` — restrict to specific event formats. - `date_from` / `date_to` — inclusive ISO date-window bounds. - `time_scope` — `upcoming` / `past` / `all`. Search is not refused on the grounds that a filter dimension listed above 'isn't supported' — every filter listed here is supported. When the user requests a filter dimension not listed above (e.g. speaker language, meeting duration, city-level geography below country), fold the unsupported term into `query` per the **Unsupported dimensions merge into keyword search** block below — the sidecar does not silently drop such terms. ## Filter selection — prefer `query`, use filters only for explicit scoping The `query` field is relevance-ranked (broader results, handles topical intent). `tickers` / `sectors` / `regions` / `event_types` are strict filters — they collapse the result set and drop otherwise-matching events that lack the tag. **Default to `query` for topic-style asks; reach for the filter parameters when the user's phrasing is unambiguously about scoping.** - *"Show me events for Tesla"* → `query="Tesla"`. Skip auto-inferring `tickers=["TSLA"]`. - *"Show me healthcare events"* → `query="healthcare"`. Skip auto-inferring `sectors=["Healthcare"]`. - *"European fintech IPOs"* → `query="European fintech IPOs"`. Skip auto-inferring `regions=["EMEA"]` or `sectors=["Fintech"]`. - *"Any talks about Cigna"* → `query="Cigna"`. Skip auto-inferring `event_types=["Moderated Call"]` from the word "talks". Filter parameters ARE the right call when the user's phrasing signals scoping intent: - *"Filter events by the Biotech sector"* → `sectors=["Biotech"]`. - *"Show me only moderated calls"* → `event_types=["Moderated Call"]`. - *"Events in North America"* → `regions=["North America"]`. - *"Events for TSLA"* (user typed the ticker) → `tickers=["TSLA"]`. Rationale: auto-inferring a filter from a topical mention silently drops matching events the user would have wanted to see. When in doubt, put the term in `query` and let the relevance ranker surface the right events. ## When to route away from this tool - the user is asking about what an expert said, quoted, or explained in a past call — route to `search_library` on the `aies-mcp` surface. **Split rule**: `search_events` returns event metadata and registration opportunities (title, date, event advisor, cost, registration status); `search_library` returns transcript excerpts, questions, answers, or quotations exchanged in past calls. When the ask is *"find me an AI-guided transcript on topic X"*, ask the user whether they want the event listing (`search_events`) or the transcript content (`search_library`) before choosing. - the user asks about their own registrations with a possessive phrase ("my events", "my registered events", "my calendar", "what am I signed up for") — use `get_user_registrations`. When the phrase mixes both signals ("what events are on my calendar next week" — has both "events" and "my calendar"), the correct move is to ask a one-line clarification: *"Do you want the events already on your calendar (your registrations), or a browse of what's coming up in the catalogue?"* and route based on the reply. - the user asks to register — use `register_event` after this tool surfaces a candidate. - the user asks to cancel — use `cancel_registration`; take `event_id` from `get_user_registrations` preferentially. ## Access Control & Compliance **Required entitlement scope:** `search_events`. The MCP sidecar calls `require_entitlement(ctx, "search_events")` as the first statement of the tool body — before any upstream I/O. Callers without the scope receive an `AUTH_FORBIDDEN` envelope and no upstream call fires. This mirrors the enforcement pattern documented in `register_event` / `cancel_registration`'s `## Permission Enforcement` sections; MCP is defense in depth on top of upstream compliance filtering, not the sole gate. MCP access is limited to subscription clients. Compliance and entitlement filtering are applied by the authoritative Guidepoint `/search` API against the authenticated user's session — MCP surfaces the results Guidepoint returned. Fully-blocked events are excluded by Guidepoint before results reach MCP; compliance pre-approval events (`isApprovalRequired: true`) are included — registering for one initiates the compliance workflow via `register_event` (which returns `COMPLIANCE_PENDING`). `user_registration_status` and `cu_cost` are resolved from the authenticated session at query time; the sidecar does not cache them. Results are scoped to the caller's organization subscription — cross-organization visibility is not exposed; the Guidepoint `/search` API enforces this via the authenticated session, and MCP surfaces the already-filtered results. ## Data Handling & Privacy The event advisor projection on each event carries three fields: `name`, `job_title`, and (when disclosed) `company`. Undisclosed names return the literal `"Anonymous"`; the `company` field is omitted from the JSON entirely — not null, not empty string — when undisclosed. Event identifiers are opaque UUIDs. Write actions launched from search results (via `register_event` / `cancel_registration`) run through the same server-side compliance and entitlement gates as the read layer. ## Query routing (internal) The `query` + filter combination selects one of three internal Guidepoint API paths, matching the web UI's own behavior (browser-captured 2026-07-21): - **`query` has content, no other filter set** → relevance-ranked search path. Purely keyword-driven ranking. - **`query` has content + at least one filter** (`tickers` / `sectors` / `regions` / `event_types` / `date_from` / `date_to` / `time_scope`) → filtered listing path with the keyword populated in `searchKeyword` and the filter dimensions applied alongside. This mirrors the web UI's behaviour exactly. - **`query` is empty** → browse listing. Filtered by dates / types / sectors / regions / tickers / states, ordered by the `sort` parameter (`relevance` coerces to `descending_date` since there's no keyword to rank). The response envelope is identical across all three branches. The routing decision is internal to the sidecar and is not a caller-visible parameter. **Unsupported dimensions merge into keyword search.** The exposed filter set is exactly the parameters listed under `## Parameters` (`query`, `tickers`, `sectors`, `regions`, `event_types`, `date_from`, `date_to`, `limit`, `sort`, `time_scope`). A dimension outside that list — speaker language, meeting duration, city-level geography below country, moderator name, company legal-name lookups without a ticker — is not silently dropped: the caller MUST fold the unsupported term into `query` (upstream tokenises `searchKeyword` against title, description, event-advisor names, and sector tags). Consequently, a search that returned results is not proof that every requested dimension was applied as a strict constraint — treat any dimension outside the exposed list as a relevance-signal contribution to `query`, not a filter. When the user names a company by legal name (no ticker), put the name in `query`; the sidecar composes the upstream keyword accordingly. ## Rate Limits & Caps Per-call cap: `limit` accepts `1..100` (default 10). To surface more or fewer events, callers raise or lower `limit`. The response envelope includes `total_events` (informational match-count from Guidepoint) so the caller can tell whether the visible list was truncated by `limit`, but no pagination navigation is provided (no `offset`, `has_more`, or next-page cursor). Per-event enrichment is bounded by an internal concurrency cap of 20 Guidepoint API requests (see `_ENRICHMENT_CONCURRENCY` at the top of the tool module), so a `limit=100` search stays at ~20 in-flight requests instead of 100. The Guidepoint APIM gateway enforces per-caller rate limits at the platform layer. ## Sort Order **Omit `sort` unless the user explicitly asked for an order.** The sidecar resolves the default from the rest of the request, in this order: 1. You passed `sort` explicitly — that value is used. 2. `query` is empty (browse) — `descending_date`. Relevance has no keyword to rank against. 3. `time_scope` is `upcoming` or `past` — `descending_date`. A tense-scoped calendar slice reads chronologically. 4. Otherwise — `relevance`. This is the normal case for a topical or company lookup. A topical or company query with NO explicit tense (e.g. *"show me events for Tesla"*) must be left WITHOUT `time_scope` so it lands on rung 4 and sorts by `relevance`. Attaching `time_scope="upcoming"` to such a query is a mis-scope: it trips rung 3 and forces `descending_date` — set `time_scope` only when the user's phrasing carries an explicit tense cue (*"upcoming Tesla events"* → `descending_date`; *"events for Tesla"* → `relevance`). The three values, when you do pass one: - `relevance` — full-text relevance ranking; the top hit is the strongest match, which is often NOT the newest event. - `descending_date` — chronological, latest first. Pass when the user says "newest first" / "most recent" / "latest". - `ascending_date` — chronological, earliest first. Pass only when the user explicitly asks for oldest-first or chronological order — the words *"oldest"*, *"oldest first"*, *"in date order"*, *"earliest first"*, *"sorted by date"*, *"chronologically"*, *"events between Jul 10 and Jul 20"*. A plain topical or company lookup does not qualify — the sidecar's context-resolved default picks `relevance` for those. NB on routing: phrases with a possessive naming the user's own events ("my events", "my registered events", "my calendar", "what am I signed up for") route to `get_user_registrations`. When the phrase mixes both signals ("what events are on my calendar next week"), ask the user before choosing. Note `time_scope="all"` means *no state filter*, not a tense, so it takes the `relevance` default like an omitted scope does. **Preserve the server's returned order — do not re-sort by date, title, cost, or any other field on the client.** The `sort` parameter controls ordering at the Guidepoint API layer; the sidecar returns events in that order and the response emits them in the same order. Client-side re-sorting destroys whichever ordering the caller asked for (default `descending_date`, or an explicit `ascending_date` / `relevance`). The array order the sidecar returned is the render order — no local rearrangement. ## Error Handling Server-side rejections return a structured `isError: true` envelope with `error_code` set to one of the constants below. The envelope's `message` is what surfaces to the user (one short sentence, preserving the `error_code` if it helps context), then the response stops. - `UPSTREAM_HTTP_ERROR` — one of the Guidepoint API paths this tool depends on was unreachable or returned a transport-level failure (network, 5xx). `structured_extras.step` names which step failed (`"search"` for the primary Guidepoint search call; `"user_registration_status"` for the batch registration-status lookup). Retry once after ~2 seconds if the user agrees; escalate otherwise. No results were emitted. No silent fallbacks on load-bearing enrichment: the batch `user_registration_status` lookup is required for accurate register/cancel decisions downstream; when it fails, the whole search fails with `UPSTREAM_HTTP_ERROR`. **Soft-signal fallbacks that do not fail the search**: - `structuredContent.unrecognized_sectors: ["..."]` — one or more sector names did not match any entry in the live Guidepoint sectors taxonomy (and were absent from the alias map). Those values were not applied. - `structuredContent.sector_options: ["..."]` — the closest valid sector names from the live taxonomy (or top-level buckets when nothing is close), emitted whenever `unrecognized_sectors` is present. Present these to the user, ask which they meant, and re-call with the chosen name — do not silently proceed. The search is not silently repeated. - `structuredContent.unrecognized_regions: ["..."]` — same semantics as `unrecognized_sectors` but for the regions taxonomy. Values that didn't match a live region name (or alias) surface here and were not applied. - `structuredContent.region_options: ["..."]` — closest valid region names, emitted whenever `unrecognized_regions` is present; present them the same way as `sector_options`. Cross-turn state is not preserved. An `unrecognized_sectors` or `unrecognized_regions` value from an earlier turn's response has no bearing on this turn's search — the live taxonomies are fetched fresh per call chain (24h server-side cache; each turn re-validates). The current search is not declined on the basis of a prior turn's unrecognized outcome; the call runs, and the current envelope is authoritative. - Per-event enrichment failures (a specific event's `/forum/full`, `/costConfirmation`, or `/ai-summary-preview` call raising) do not produce an `isError` envelope. They log a warning server-side and default that event's affected non-actionable field (event advisors / description) so a single flaky event does not fail the whole search response. An event returned with a missing/defaulted display field is still a valid record. - **Cost-enrichment failure is handled differently** because `cu_cost` is an actionable eligibility field (controls the CTA). When `/costConfirmation` fails for an event whose `/forum/full` did not mark it known-zero-cost, the sidecar emits an `eligibility_unknown: true` flag on that event object AND downgrades `cta_label` to `View in Guidepoint` (no `Register` / `Request` button on an unknown-cost event). Callers surfacing the card must render View-in-Guidepoint as the CTA and MUST NOT compose a `cu_cost > 0` / `cu_cost == 0` claim from the (unreliable) numeric field; the `eligibility_unknown` flag supersedes any inferred cost. This fail-closed policy prevents a paid event from being presented as free-to-register after a transient upstream failure. ## Search Parameters Every parameter is optional, including `query`. Callable with zero arguments to return the broadest event set — an **all-tense browse** (both past and upcoming events, ordered by the resolved `descending_date` default). **Zero arguments is not the shape for "show me all upcoming events"** — explicit future-tense prompts (*"upcoming events"*, *"events coming up"*, *"what's coming up next week"*, *"future calls"*) carry `time_scope="upcoming"`. The zero-argument call is reserved for genuinely all-tense browses (*"show me any events"*, *"just show me events"*) where the user did not signal a tense. - `query` (optional, string) — free-text topic, theme, sector, company name, or research question that drives relevance ranking. When the user names a company (e.g. "Tesla", "Cigna", "Apple") the name goes here, not via `tickers`. Omit entirely to return everything the other filters allow (an all-tense browse — pair with `time_scope="upcoming"` when the user's prompt names the tense, per the Search Parameters intro above). NB on routing: phrases with a possessive naming the user's own events ("my events", "my registered events", "my calendar", "what am I signed up for") route to `get_user_registrations`. Mixed phrases ("what events are on my calendar next week") are disambiguated by asking the user before calling. - `tickers` (optional, list[string]) — strict filter on stock-ticker symbols (`"AAPL"`, `"MSFT"`, `"TSLA"`). Events not tagged with one of the supplied tickers are excluded entirely. Company names — "Tesla", "Apple", "NVIDIA", "Cigna" — go in `query`, not here. Populate `tickers` only when the user explicitly typed a ticker symbol like `TSLA` or `AAPL`. Auto-inferring tickers from names collapses results because the Guidepoint API treats this as a strict filter, not a relevance signal. - `sectors` (optional, list[string]) — free-text sector names. The sidecar resolves each name against the live Guidepoint sectors taxonomy and applies the resolved sectorId list as a Guidepoint filter parameter — the Guidepoint API filters on the ID column, so raw names alone silently return unfiltered results. Common aliases resolve too (`"Health Tech"` → `"Healthcare IT"`, `"PE"` → `"Private Equity"`). Unrecognised values surface in the response's `unrecognized_sectors` soft signal (they are not applied to the filter). - `regions` (optional, list[string]) — free-text region names. Same resolution model as `sectors` (live taxonomy lookup, matched name → regionId list applied to the Guidepoint filter parameter). Accepts continent, sub-region, or country names; common aliases resolve (`"na"` → `"North America"`, `"apac"` → `"Asia Pacific"`, `"us"` → `"United States"`). Unrecognised values surface in `unrecognized_regions`. - `event_types` (optional, list[string]) — restrict to one or more of the 12 canonical DISPLAY LABEL enum values: `"Moderated Call"`, `"Client-Generated Call"`, `"AI-Guided Transcript"`, `"Insight Tracker"`, `"Focus Poll"`, `"Group Meeting"`, `"Roundtable"`, `"Breakfast / Lunch"`, `"Marketing"`, `"One on One"`, `"Other"`, `"Primer"`. Pydantic Literal on the tool boundary enforces this exact spelling. Default (omitted) returns every type. - `date_from` (optional, `YYYY-MM-DD`) — inclusive lower bound on event date. The sidecar converts this to the upstream API's `M/D/YYYY` format before the call (e.g. `2026-07-13` → `7/13/2026`); callers always pass ISO. - `date_to` (optional, `YYYY-MM-DD`) — inclusive upper bound. Same ISO → `M/D/YYYY` conversion applies. - `limit` (optional, integer, **default 10**, max 100) — number of events to return per call. To see more or fewer, raise or lower `limit`. The response envelope includes `total_events` so the caller knows whether the visible list was truncated by `limit`; no pagination navigation is provided. - `sort` (optional, string — `relevance` | `descending_date` | `ascending_date`). **Omit unless the user asked for an order**; the sidecar defaults to `relevance`, or `descending_date` when `time_scope` is `upcoming`/`past` or `query` is empty — see `## Sort Order` above. ## Date Filtering Behavior - No date params → ALL events (past AND upcoming). This is the correct call shape for every plain topical or company lookup — *"show me events for Tesla"*, *"events on biotech"*, *"any Cigna events"*. The default `descending_date` sort on empty-query browse surfaces future events at the top; the relevance default on keyword browses ranks by match quality across both tenses. - `date_from` set to today's date → events whose UTC calendar day is today or later. **This is NOT strictly future-only** — events already started earlier today (e.g. an event at 09:00 UTC when `now_utc` is 14:00 UTC) still match the filter because the UTC calendar bound includes the whole current day. For an instant-based future-only filter (`event_dt_utc > now_utc`), use `time_scope="upcoming"` instead (or combine both). - `date_to` set to today's date → events whose UTC calendar day is today or earlier (includes events starting later today). - Both bounds set → events within that range. Callers pass dates as ISO `YYYY-MM-DD` in the tool schema; the sidecar converts to the upstream API's `M/D/YYYY` format before the call. Malformed dates (wrong format, impossible calendar dates like `2026-02-30`, reversed bounds where `date_from > date_to`) are rejected at the tool boundary via Pydantic validation — the pattern `^\d{4}-\d{2}-\d{2}$` and a calendar-date `AfterValidator` run at the runtime edge, surfacing a `ValidationError` before any upstream call. The regex is stripped from the emitted JSON Schema for Claude App and OpenAI strict-mode compatibility — the schema view for `date_from` and `date_to` shows `anyOf: [{type: string}, {type: null}]` only; runtime validation still carries the pattern and calendar-day checks. ### Temporal semantics — three axes and their precedence Three fields set the temporal frame for a search, each with a distinct base: - **`date_from` / `date_to`** — UTC calendar dates. The upstream Guidepoint API interprets each as a UTC-anchored day. The sidecar does not shift these to the caller's local timezone before sending — a `date_from="2026-09-15"` request means the UTC day `2026-09-15T00:00:00Z .. 2026-09-16T00:00:00Z`, even for callers in Asia/Tokyo. Near midnight in the caller's local timezone this can straddle: *"tomorrow"* on 2026-09-14 22:00 ET translates to `2026-09-15` locally, which is still the same UTC day, so no straddle; *"tomorrow"* on 2026-09-14 22:00 JST translates to `2026-09-15` locally, but that's already `2026-09-14T13:00Z`, so the UTC-day filter is one calendar day off. **Natural-language date resolution — canonical rule.** When the caller phrases dates as natural language (*"today"*, *"tomorrow"*, *"this week"*, *"next week"*, *"last Tuesday"*, *"Q3"*), resolve the date in the CALLER's local timezone (from the `X-Timezone` request header, or `America/New_York` fallback), then pass the resolved ISO `YYYY-MM-DD` as `date_from` / `date_to`. Concrete mappings (caller local now = 2026-09-02 15:00 America/New_York): - *"today"* → `date_from=2026-09-02`, `date_to=2026-09-02`. - *"tomorrow"* → `date_from=2026-09-03`, `date_to=2026-09-03`. - *"this week"* → `date_from=<Monday of this local week>`, `date_to=<Sunday of this local week>`. - *"next week"* → `date_from=<Monday of next local week>`, `date_to=<Sunday of next local week>`. - *"Q3 2026"* → `date_from=2026-07-01`, `date_to=2026-09-30`. The resolved local-date then becomes the UTC-day query the upstream API sees — acceptable for coarse date-window filters; near midnight local time, prefer `time_scope="upcoming"` for calendar questions instead of `date_from=today` (avoids the UTC-vs-local straddle case above). - **`time_scope`** — start-time tense gate applied server-side AFTER the upstream response returns (see `_apply_time_scope_gate` in the sidecar). `"upcoming"` returns events whose `event_dt_utc > now_utc`; `"past"` returns events whose `event_dt_utc <= now_utc`; `"all"` (or omit) applies no tense filter. Upstream `registration_status` is a separate per-event lifecycle field (open/closed for the registration flow) carried on each event object — it is not the tense discriminator. Orthogonal to `date_from` / `date_to`. - **Response `date` field** — event start time in the caller's LOCAL timezone (from the `X-Timezone` request header, or `America/New_York` fallback), rendered as ISO 8601 with the local offset. This is a rendering axis only — changing display timezone does not change which events match the filter. **Precedence rule when multiple axes are set** — every supplied axis is applied as an AND-filter intersection. `date_from=2026-09-15` + `time_scope="upcoming"` returns events open for registration whose UTC start day is on or after 2026-09-15. `date_from=2026-09-15` + `date_to=2026-09-15` + `time_scope="past"` returns already-closed events whose UTC start day is 2026-09-15 (a small window of past-day events, which is a valid query for post-mortem lookups). No axis silently overrides another; an impossible combination (e.g. `date_from` in the future with `time_scope="past"`) returns zero events with the usual empty envelope rather than a soft warning. ## Event Object Response — canonical contract Every event in `events[]` returns the following fields. `description` is populated for both past events and everything else — past events get a richer summary (AI post-event summary from `/forum/{id}/ai-summary-preview` trimmed to card-brief length); every other event (upcoming, live, unresolved-date) gets a 1-2 sentence agenda-based summary composed from `forumAgendaItems`. `description` may be null only when upstream lacks both source materials. Required-vs-optional field taxonomy below is authoritative — every field listed as `required` is present on every event; fields marked `optional` may be omitted or null when upstream lacks the source data. - `event_id` (string, required) — stable Guidepoint identifier; pass to `register_event` / `cancel_registration`. - `title` (string, required) — event title as shown in GP360. - `event_type` (string, required) — one of the 12 display labels: `"Moderated Call"`, `"Client-Generated Call"`, `"AI-Guided Transcript"`, `"Insight Tracker"`, `"Focus Poll"`, `"Group Meeting"`, `"Roundtable"`, `"Breakfast / Lunch"`, `"Marketing"`, `"One on One"`, `"Other"`, `"Primer"`. Server-normalised from the Guidepoint `forumType` via `_normalize_event_type` (identical mapping in `search_events` and `get_user_registrations` — cross-tool consistent). Note: `Non-Live Call` (DB) normalises to `Moderated Call` (matches web UI display). - `date` (string, ISO 8601 with timezone offset, required) — event start time in the authenticated user's local timezone (e.g. `2026-07-14T15:30:00-04:00`). Server resolves the timezone from the user's account/session at query time and does not cache it. - `end_time` (string, ISO 8601 with timezone offset, optional) — event end time in the user's local timezone. Present when the Guidepoint API supplies it; may be null or absent for records with no scheduled end. - `event_advisors` (array of `{name, job_title, company?}`, required) — `name` is the disclosed full name or the literal `"Anonymous"` when undisclosed. `job_title` is always present (may be an empty string). `company` is included when disclosed; omitted entirely (not null, not empty string) when undisclosed. - `tickers` (array of strings, optional) — company tickers tagged on the event by the Guidepoint index. Passed through verbatim; may be absent or empty. - `tag_categories` (array of `{name, category}`, optional) — event-focus tag categories from the Guidepoint index (e.g. `{"name": "Industry", "category": "Event Focus"}`). - `registration_status` (string, required) — `"open"` (future date) or `"closed"` (past). `"Roundtable"` and `"Group Meeting"` events always return `"open"` regardless of date. Internal signal — the literal string `open`/`closed` is not displayed to the user. It drives the offer of `register_event`: `open` → offer registration inline (subject to the `cu_cost` gate below); `closed` → present the event as view-only (link to `event_url`). Users infer registrability from the event date and the surrounding sentence — the raw enum value is noise. - `user_registration_status` (string, required) — `"registered"` | `"waitlisted"` | `"not_registered"`. Resolved from the authenticated session token at query time; not cached. - `event_url` (string, required) — deep-link to the event page on GP360. Render the `title` as a Markdown link to this URL. - `sectors` (list[string], required) — canonical sector names this event is tagged under (empty list OK). - `cu_cost` (number, required, non-null) — credit-unit cost of the event. Present and non-null on every event object; resolved from the authenticated session token at query time and not cached. The cost is ALWAYS surfaced as the event's bold cost line — `**This event carries a N credit-unit cost.**` — for every event including free ones: a paid event shows e.g. `5 credit-unit cost` and a free event shows the explicit `0 credit-unit cost` line (never omitted). This field also gates the write path: `register_event` refuses events with `cu_cost > 0` (returns `CU_GATED`) and routes users to `event_url` for paid registration; the cost is surfaced to the user, and inline registration for paid events is skipped. - `requires_gp360_registration` (boolean, required) — boolean twin of `cu_cost`. `true` when the event requires paid registration in Guidepoint (i.e. `cu_cost > 0`); `false` otherwise. Provided as a convenience for clients that prefer a boolean gate over the numeric cost. Same write-side semantics as `cu_cost`: when `true`, `register_event` is not attempted — route the user to `event_url` instead. - `requires_compliance_approval` (boolean, required) — `true` when this event's registration goes through the compliance-approval workflow (PreOrder) before confirming. When `true` AND `cu_cost == 0` AND the user is not registered, the response renders the `Request` CTA (not `Register`) — clicking Register on a compliance-pending event returns a `status: "pending_compliance"` SUCCESS variant that the response renders as "Your registration is awaiting compliance approval" (a success outcome, not an error). Compliance is attributed to the client's own compliance department, not to Guidepoint. See the CTA logic table below for the full priority order. Sourced from `isApprovalRequired` on the Guidepoint `/forum/full` payload during per-event enrichment; defaults to `false` when absent. - `cta_label` (string, required) — the resolved CTA button label for this event, one of `"Register"`, `"Cancel registration"`, `"Request"`, or `"View in Guidepoint"`. The sidecar computes this server-side by applying the CTA priority table (top of description) to `registration_status`, `user_registration_status`, `cu_cost`, and `requires_compliance_approval`. Render the value verbatim as the button label — do not re-derive from the underlying fields (that path is where the rule ordering gets lost on paid events, and the sidecar has already resolved it). - `eligibility_unknown` (boolean, OPTIONAL) — present and set to `true` ONLY when the sidecar could not verify the event's cost (the `/costConfirmation` call failed and the event was not marked known-zero-cost by `/forum/full.forumPayGo.isCuCostZero`). When this flag is true, `cta_label` is downgraded to `"View in Guidepoint"` and the caller MUST render the View-in-Guidepoint CTA — do not present the event as free-to-register on the basis of the (unreliable) `cu_cost` numeric value. Absent on every fully-enriched or known-zero event. - `description` (string, OPTIONAL) — brief topic summary sourced from upstream, in this cascading order: (1) the AI-generated post-event summary from `/forum/{id}/ai-summary-preview` (past events only); (2) a top-level summary alias on the `/forum/full` payload (`forumSummary` / `aiSummary` / etc. — some test-zone Client Generated Calls carry the summary here); (3) a 1-2 sentence agenda summary composed from `forumAgendaItems`. Whatever lands is TRIMMED to a card-friendly brief (first ~2 sentences OR ≤220 characters, sentence-boundary aware) so cards render consistently and never contain multi-paragraph transcript excerpts. When ALL three sources are empty upstream, `description` is null — in that case the SKILL instructs the LLM to compose a 1-2 sentence summary from whichever OTHER event fields are populated (title, event_type, date, sectors, tickers, tag_categories, event advisors). The sidecar deliberately does NOT synthesize a template placeholder — client-side composition produces more natural prose than a fixed template. ## Response envelope Top-level fields: `events` (list), `returned` (integer, matches `len(events)`), and `total_events` (integer, always present). `total_events` is the Guidepoint match count and drives a user-facing render rule: when `total_events > returned`, the response appends exactly one short sentence 'Showing N of M events.' at the end — or when the envelope carries `total_events_is_approximate: true`, the form 'Showing N of ~M events.' (or '500+ events' for the very large case) is used since M is a ceiling estimate rather than an exact count. See the response-shape block below for the exact wording. This is not pagination navigation: no `has_more`, no `offset`, no next-page cursor. Higher visible counts require a larger `limit` value at request time (max 100). Optional soft-signal fields: `unrecognized_sectors: [...]` (sector filter values missing from taxonomy), `unrecognized_regions: [...]` (same for regions), `query_too_short: true` (standalone 1-2 char query short-circuited at the sidecar boundary), `query_collapsed: true` + `dropped_tokens: [...]` (multi-token query returned 0 rows because the Guidepoint tokenizer dropped high-frequency modifiers), and `total_events_is_approximate: true` (Guidepoint reported a ceiling estimate, not an exact count) — each appears only when applicable. Dates are always rendered in a valid timezone — the sidecar silently falls back to America/New_York (US Eastern, DST-aware) when the caller does not send an `X-Timezone` request header, so no client-side caveat is needed. ## Card contents (rendered per event) **Response format — read first.** Each event renders as its own multi-line card block. Blank line between cards. The response is a stack of these card blocks — nothing else. A Markdown table listing events in rows is not the response format this tool uses; on Claude Desktop, ChatGPT desktop, and Perplexity the widget layer pattern-matches the card block to upgrade it into an interactive card with bound buttons — a table row has no cost line, no description paragraph, no registration-status line, no CTA link, and no prompt hint, so a table strips those fields from the visible response entirely. When the response contains N events, the shape is N card blocks stacked with blank lines between them — see the `Rendered example` further down for the exact card shape. **Shapes this tool's response does not use** (each event always gets its own card block, not one of these): - a Markdown table with one row per event - a compact one-line prose row per event - a card whose fields are collapsed into a single pipe-delimited line (e.g. `Cost: 0 CU | Status: … | Request`) instead of the separate lines below - a numbered list showing title only - any tabular arrangement that trades a card per event for a row per event **Emit the cards in the exact order the `events` array arrives — index 0 first, index 1 second, and so on to the end.** The array order is the answer's order. Do not regroup or re-rank the events under a subheading — not by topic, theme, relevance to the query, company, sector, event type, date, registration status, or CTA. A heading that labels the response as a whole is fine; a heading that splits one call's `events` array into subgroups is not — grouping reorders the array, which departs from the sidecar's chosen sort. Concrete counter-example: a Tesla query returning 15 events spanning robotaxi, humanoid robotics, energy storage, and other topics renders as 15 sequential card blocks in the order the array arrived — NOT as four subsections titled *Robotaxi*, *Humanoid Robotics*, *Energy Storage*, *Other* with the cards partitioned across them. Inline emoji, SVG markup, and image markup are permitted anywhere in the card (widget-capable clients pass them through to their renderer; non-widget clients render them as raw Markdown/HTML). Emit exactly these lines in this order: - **Title** — bold, rendered as a Markdown link: `[Title](event_url)`. The raw `event_url` is not exposed as visible text. - **Event type + sectors + tickers** — one metadata line: event type (title-cased) · comma-separated sectors (if any) · ticker chips from `tickers[]` (if any). No label prefixes like `Type:` or `Sectors:`. Note on sectors: the `event.sectors[]` array carries the deduped TOP-LEVEL PARENT buckets only (e.g. `"Telecom, Media & Technology"`), not the 15-20 leaf sub-sector labels the Guidepoint index tags on each event — already sized for a readable card line. Emit the field verbatim; no further trimming needed. (The `sectors` INPUT filter accepts any level of granularity — canonical top-level names or sub-sectors — since search is permissive; only the OUTPUT array is deduped for readability.) - **Date and time** — prefix with the calendar glyph `📅 ` then `MMM DD, YYYY · hh:MM AM/PM zz` on its own line (12-hour clock with uppercase AM/PM; the `zz` timezone label comes from the ISO offset in the `date` string, e.g. `-04:00` → `ET`). Example: `📅 Jul 27, 2026 · 03:00 PM ET`. Past cards may show date only. `end_time` is not rendered on the card. - **Tag categories** — one line rendered as `Viewpoint: … · Asset class: … · Event focus: … · Event-driven: …`, using only the categories that carry values on `tag_categories[]`. Omit the whole line when the array is empty or every category is absent. Group the values by category (e.g. every `{name: 'Academic', category: 'Viewpoint'}` becomes part of the `Viewpoint:` group). Multiple values in one category join with `, ` (e.g. `Asset class: Equity, Debt`). - **Description** — one paragraph, 1-2 sentences. The sidecar has already trimmed to card-brief length; further truncation drops usable context. - **Event advisors** — prefix with the person glyph `👤 ` then one line per the Event advisor display rule below. - **Registration-status line** — render as a natural-language phrase on its own line, between the event-advisor line and the CTA row. On this tool the phrase is keyed solely on `user_registration_status` (the only per-user registration field search_events emits — it carries no confirmed-vs-pending or past-tense signal, so no such distinction is available here). The mapping: - `user_registration_status == "registered"` → `You're registered` - any other value (e.g. `not_registered`, `waitlisted`) → omit the line entirely; the CTA row conveys the state visually. - **Cost line** — always rendered, on every event including free ones (see cost display policy below). - **CTA row** — see the tool's CTA logic table for the exact format. CTA labels are drawn from a FIXED four-value set — exactly `Register`, `Cancel registration`, `Request`, or `View in Guidepoint`. No other label is valid. If none of the four fits, the correct move is `View in Guidepoint` — not to invent a new label. Do not invent labels for brevity, symmetry, or fit — labels like `View event`, `View transcript`, `View recording`, `View`, `Open in Guidepoint`, `Transcript`, `Recording`, `Go to event`, `Attend`, `Join`, `Details`, `Learn more`, `More info`, `Open`, or any variant not in the four-value set is a regression. Labels use sentence case — not title case. The label text is fixed for the chosen CTA. Do not substitute alternate wording based on what the URL path looks like or the event's content type. The URL `platform.guidepoint.com/transcript?id=…` still renders under `View in Guidepoint`; the URL path is a Guidepoint routing detail, not a signal to relabel the CTA. Actionable CTAs (Register / Cancel registration / Request) render as two lines: line 1 is a Markdown link `[Label](event_url)`, and line 2 is an italic prompt hint `Or copy this prompt: *"…"*`. The prompt hint gives the reader a copy-paste form of the CTA text and is carried on every actionable card. The italic prompt text follows the same shape as the CTA — `Register me for [title] on [date]` / `Cancel my registration for [title] on [date]` / `Request approval for [title] on [date]`. Non-actionable CTAs (View in Guidepoint) render as a single Markdown link line `[View in Guidepoint](event_url)` — no italic prompt hint follows, because View in Guidepoint is a URL-open action rather than an LLM re-trigger. A plain-label CTA (a bare `Register` string with no link) breaks the widget upgrade path and leaves non-widget clients with nothing to click; the link form is the emit convention on every card. **Rendered example** — one card looks exactly like this (each event in the response becomes one such block, blank line between blocks): ``` **[Tesla Robotaxi Rollout: Investor Q&A](https://example/e1)** Moderated Call · Automotive · TSLA 📅 Sep 12, 2026 · 02:00 PM ET Viewpoint: Industry · Asset class: Equity · Event focus: Single company A discussion of Tesla's robotaxi launch timeline and near-term unit economics. 👤 Priya Ramachandran — Former Director of Autonomy, Waymo You're registered **This event carries a 0 credit-unit cost.** [Cancel registration](https://example/e1) Or copy this prompt: *"Cancel my registration for Tesla Robotaxi Rollout on Sep 12, 2026"* ``` A response with N events is N such blocks stacked with blank lines between them — one card per event, all fields present per the field list above. ## CTA logic (`search_events`) One CTA per card. **The label to render lives on the event object as `cta_label`** — the sidecar resolves it server-side from `registration_status`, `user_registration_status`, `cu_cost`, and `requires_compliance_approval`, and the response emits the resolved value. Render `cta_label` verbatim as the button label. The priority table below documents which field combinations produce which label for audit and review; it is not a computation the LLM re-runs. **Priority order — first match wins** (evaluate top to bottom): | # | Condition | CTA label | |---|---|---| | 1 | `cu_cost > 0` | `View in Guidepoint` | | 2 | `open` + registered | `Cancel registration` | | 3 | `open` + not registered + `cu_cost == 0` + `requires_compliance_approval == True` | `Request` | | 4 | `open` + not registered + `cu_cost == 0` + not compliance-pending | `Register` | | 5 | `closed` + not registered | `View in Guidepoint` | | 6 | `closed` + registered | `View in Guidepoint` | **CTA button rendering.** Every CTA in the priority table above emits as a clickable Markdown link — that is the button surface. Widget-capable clients (Claude web and desktop, ChatGPT web and desktop, Perplexity web and desktop) render the CTA link as a styled inline button; non-widget clients render the raw Markdown link. A plain-text CTA name (no link) leaves widget clients without a button surface and non-widget clients without a click target — the link form is the emit convention on every card. Per-row button templates: - **View in Guidepoint** — a single clickable link: `[View in Guidepoint](event_url)`. URL-open action; opens the event page in a browser. Paid events (`cu_cost > 0`) always route here regardless of compliance flag. - **Cancel registration** — a clickable link `[Cancel registration](event_url)` followed by an italic copy-prompt hint on the next line: `Or copy this prompt: *"Cancel my registration for [title] on [date]"*`. - **Register** — a clickable link `[Register](event_url)` followed by an italic copy-prompt hint: `Or copy this prompt: *"Register me for [title] on [date]"*`. - **Request** — a clickable link `[Request](event_url)` followed by an italic copy-prompt hint: `Or copy this prompt: *"Request approval for [title] on [date]"*`. Clicking `Request` (or typing the prompt) routes through the same `register_event` flow as `Register` and returns a `status: "pending_compliance"` SUCCESS variant (a success outcome, not an error) — the response renders as *"Your registration is awaiting compliance approval."* Compliance is attributed to the client's own compliance department, not to Guidepoint. A Register button is not shown on a compliance-pending event; the CTA label change is the whole point of the `requires_compliance_approval` signal. Rules for the CTA: - `Register` / `Request` / `Cancel registration` render as two lines: a Markdown link `[Label](event_url)` on line 1, then an italic prompt hint on line 2: `Or copy this prompt: *"[prompt text]"*`. This hybrid gives the user two paths to complete the action: (A) click the blue link → opens the GP360 event page where they complete the action; or (B) select+copy the italic prompt text (or type it) → paste into the chat → the response routes to `register_event` / `cancel_registration` with the confirmation gate. - Widget-capable clients (Claude web and desktop, ChatGPT web and desktop, Perplexity web and desktop) render the CTA link as a styled inline button; the italic hint line may be hidden or shown as small caption text by the widget layer. - `Request` and `Register` both invoke `register_event`. The two CTA labels differ so the user knows what they are committing to; the tool response differs only in the `status` field (`"registered"` vs `"pending_compliance"`), both of which are SUCCESS envelopes. - When `cu_cost > 0`, `Register` and `Request` are not rendered — paid events always route to `View in Guidepoint` (rule #1 wins), even when `requires_compliance_approval == True`. - `View in Guidepoint` renders as a Markdown link only (no italic hint line) — it's a URL-open action, not a prompt trigger; there's no equivalent "say this to trigger" phrase. Widget clients upgrade it into a native button that opens the link. - `register_event` / `cancel_registration` are not called directly from the card. The prompt-based routing goes through the tool's confirmation gate. ## Widget vs. Markdown rendering The Markdown card block is the response — emit it in full using the card structure above, on every client, one card per event. Widget-capable clients (Claude web and desktop, ChatGPT web and desktop, Perplexity web and desktop) upgrade the Markdown into interactive cards with bound buttons after pattern-matching on the CTA label text; all other clients (Claude Code, claude.ai, terminal MCP clients) render the Markdown as-is. If the widget layer errors during render, the Markdown is what remains visible — so the emit convention is unconditional. A condensed table with one row per event is a regression: it breaks the widget pattern-match, strips the description line, and hides the CTA row. ## Cost display policy `cu_cost` is always present on the response, non-null, `>= 0`. Display rule: - **Always** surface the cost as its own bold line, on EVERY event — paid and free alike: `**This event carries a 5 credit-unit cost.**` when `cu_cost > 0`, and `**This event carries a 0 credit-unit cost.**` when `cu_cost == 0`. The line is never omitted; a free event shows the explicit `0 credit-unit cost` line rather than silence. Render the number exactly as `cu_cost` reports it. Not rendered as a raw badge or table column. Boolean twin: `requires_gp360_registration` is `true` when `cu_cost > 0`. ## Event advisor display - Prefix the line with the person glyph `👤 ` to signal an event-advisor row. - Show `name` when disclosed. When undisclosed, the response returns the literal `"Anonymous"` — pass through. - Show `job_title` (always present, may be empty string). Render `"👤 Name — Job Title"`. - When `company` is present on the event_advisor object, append `", Company"` → `"👤 Name — Job Title, Company"`. - When `company` is absent (omitted from JSON, not null/empty), the `", Company"` tail is omitted entirely. No placeholder is rendered. - When the `event_advisors` array is empty, the event-advisor line is omitted entirely (the `👤 ` glyph is skipped along with the rest of the line). ## Total-count line (when the list is truncated) The response envelope carries `total_events` (informational upstream match count) alongside `returned` (visible count). Render rule: - When `total_events > returned`, the response ALWAYS ends with exactly one short sentence naming both counts: *"Showing 10 of 276 events."* (visible count · "of" · total · plural noun · period). Just that sentence, and it appears on every truncated response — omitting it is a regression. The response ends after this line; no follow-up offer, no pagination prompt, no *"want me to…"* or *"Would you like to register for any of these…"* sentence follows it. - When `total_events == returned`, the line is skipped entirely. All results fit; the cards are the whole set, and the response ends after the last card — nothing else trails it. No pagination navigation exists (no `offset`, no `has_more`, no next-page cursor). Prompts like *"want to see more?"* are skipped — a higher visible count requires a larger `limit` value at request time. ## When NOT to use - Full-text Q&A search across completed transcripts — `search_events` matches event metadata (title, event advisor names, tagged sectors), not transcript bodies. Route transcript-content queries to `search_library` on the `aies-mcp` surface. - Listing events the user is ALREADY registered for — use `get_user_registrations`, which scopes to the caller automatically. - Topical "my registrations" queries ("find the [topic] event I registered for", "which of my upcoming events is about biotech") — use `get_user_registrations` and match the topic in-context from the returned `title` / `sectors` / `event_advisors`. `search_events` cannot scope to the caller's own registrations. ## Transcript content (not returned by this tool) Guidepoint event transcripts are not returned by `search_events`, regardless of whether the user has purchased the event. Transcript excerpts are searchable via `external_search_library`, and a full transcript is retrieved via `download_transcript` (or the Guidepoint web surface). ## Response shape — what goes around the cards The event-card widget is the response. The response carries the cards, at most one short lead-in sentence, and the soft-signal caveats the envelope surfaces (`returned == 0`, `unrecognized_sectors`, `unrecognized_regions`, `query_too_short`, `query_collapsed`, `total_events_is_approximate`). Everything else is dropped. Keep out of the response: - tool-call or route narration (naming the tool, its parameters, or how the call was shaped — the user asked for events, not the trace); avoid phrasings like 'the call was search_events() with no arguments', 'routed to /forum/list', 'default browse per the skill', 'limit: 10', 'query: Tesla' - envelope-shape or contract-clean commentary describing the response envelope or attesting it is well-formed; avoid phrasings like 'returned matches len(events)', 'no pagination fields', 'consistent with the contract', 'the response was contract-clean', 'contract-clean', 'clean envelope', 'returned matched the array length'. Test: if a sentence exists to reassure the user that the response is spec-compliant, it is cut. Additional rule for this section: when `total_events > returned`, the response appends exactly one short sentence in the shape 'Showing 10 of 170 events.' at the end — visible count, the word 'of', the total, then 'events' and a period. Just that sentence. The line is skipped when `total_events == returned`. - behavior-verification statements that grade what the tool did (sort, filter, cost policy, ordering, timezone, CTA routing); avoid phrasings like 'relevance order preserved', 'note the top hit isn't the newest', 'all events past so each card routes to view-only', 'descriptions present throughout', 'sort order behaved as documented', 'filter applied correctly'. The widget is the confirmation. - reliability, latency, retry, or hang commentary narrating timing, hangs, retries, or 'call took N minutes'; avoid phrasings like 'the hang was transient, not deterministic', 'hung for 4+ minutes on the first attempt', 'flaky on the default path'. Reliability telemetry lives in the correlation_id, not the answer. - cross-run comparisons referencing prior sessions, other tickets, or known bug states; avoid phrasings like 'different failure profile from the earlier hang', 'consistent with the reproducible limit: N hang we saw before', 'unlike the last time'. - bug-filing or triage recommendations; avoid phrasings like 'worth filing as intermittent', 'this should be a P2', 'arguably worse for users than a reproducible one', 'heads up — worth flagging'. A soft filing suggestion is a bug report in the chat window. - hypothetical caller / reviewer speculation imagining a downstream tester or programmatic caller; avoid phrasings like 'if anyone tested a tickers: [TSLA] strict filter expecting to find these events, it would return only the fixtures', 'the real-content events would be invisible to that filter dimension', 'a caller who passed X would be surprised'. - unsolicited data-quality or fixture observations labelling events as fixtures, dummy data, test artefacts, or commenting on individual field quirks; avoid phrasings like 'doubled title suffix', 'description echoes the title', 'near-duplicate events — possibly intentional test fixtures', 'looks like a fixture', 'AutoEvent fixture', 'Client Notes fixture', 'event_type vs. title mismatch'. Render as-returned. - cross-record pattern claims comparing fields across the response array and volunteering patterns; avoid phrasings like 'ticker tagging looks inverted across the catalog', 'all 10 of these genuinely Tesla-focused events return an empty tickers array, while the unrelated fixture events carried TSLA tags'. Internal catalog QC is not part of the answer. - audit-log framing opening with a header that treats the reply as a log entry, review note, or audit finding; avoid phrasings like 'two observations for the log from this pass', 'noting for the reviewer', 'for the record', 'one thing worth flagging', and numbered observation lists framed as findings. The chat is not a review channel. - scene-setting preambles that name environments; avoid phrasings like 'here are the next 10 upcoming events from the QA catalog', 'based on your request…'. One short lead-in is the maximum; environments are not named. - chain-of-thought preambles that prefix the answer with a reasoning trace; avoid phrasings like 'deliberated formatting…', 'synthesised…', 'thought about…', 'considered several approaches…'. - unrequested follow-up offers: the response ends with the last event card (or the total-count line, when the list is truncated); no follow-up sentence trails the cards. Phrasings like 'want me to raise the limit for a broader Tesla sweep, or scope it — e.g. robotaxi-specific, or a date window?', 'want me to…', 'should I also…', 'happy to…', 'let me know if you'd like…', 'Want me to register you for any of these, or filter to a specific sector/theme?' are regressions — the response terminates instead. Compound offers listing alternative parameter values (limit + sub-topic + date window) are the worst-shape instance of this failure mode. Only exception: `returned == limit` AND the user explicitly asked for a specific count larger than that — one short sentence, no alternatives. Bug reports go elsewhere. Reliability telemetry lives in the correlation_id; catalog-quality reports live in the ticket tracker; neither belongs in the reply.
search_events
Mark an advisor as declined / not interested on a request or project — use when the client has reviewed them and does not want to move forward. Triggers: "not interested in this advisor," "pass on this expert," "reject [advisor name]." It records the client's decision and does not delete the advisor, but it cannot be undone here — select_advisor will not move a declined advisor back, so a reversal needs the Project Manager (PM). confirmed previews the decline first. The service refuses this action on a Closed request. Workflow: get_requests/search_requests → get_request_advisors/search_request_advisors_by_screeners → reject_advisor, using the IDs from those calls. Not for moving an advisor forward (use select_advisor or instant_book_advisor) or withdrawing a consultation already requested (use cancel_requested_consultation). Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true.
reject_advisor
Search completed consultations on one request, optionally by advisor: with whom, when, and their meeting ids, for the authenticated client. Upcoming ones are out of scope, and nothing on this connector reads upcoming 1:1 calls — so no calendar answer that omits them is complete. Not transcript text. Whole call: transcriptId + get_meeting_transcript (transcriptionStatus completed). Snippets: id + search_my_network_excerpts (excerptsAvailable). For "what is [person] saying about [topic]": search_requests, then this with advisorName. Only where the request row has transcriptsEnabled true. Needs a requestId (get_requests/search_requests); for one advisor advisorId (get_request_advisors) or advisorName. Cohort: one call per advisor. Closed requests are returned too; no write tool acts on one. One page per call; matchCount is the full total — page with page/perPage, check hasMore. Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true. An empty result is not an error.
search_meetings
Search and filter advisors on a specific request by their screener Q&A responses. Use when looking for advisors who mentioned a keyword or answered a question a certain way. Triggers: "find advisors who mentioned [keyword]," "which experts said they worked in [region]," "filter by screener response." Screener answers are short vetting responses, not call commentary. Requires a requestId plus at least one text filter. Preferred when you need to find advisors by screener content and read their answers in one call — see includeFullScreeners. Returns advisorId, name and matching screener rows only — no status or availability fields, so call get_request_advisors before scheduling a match. For the full unfiltered advisor list, use get_request_advisors instead. Closed requests are returned too; no write tool acts on one. Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true. An empty result is not an error.
search_request_advisors_by_screeners
Search the authenticated client's own expert network requests (also called projects) by title, research-brief text, angle, type, status, transcripts-enabled, or creation-date range. Scoped to that one client — no client or user argument. Triggers: "show me open requests," "find the project called [title]," "which projects mention [topic]." titleContains searches the title only; anyText searches the title or the research brief, so prefer it for a topic that may not be in the title. For a cohort — "what do formers think" — start with angleContains, then get_request_advisors on each to see who carries it. For screener answers use search_request_advisors_by_screeners. Every filter is ANDed; at least one is required — for an unfiltered list use get_requests. Closed requests are returned too; no write tool acts on one. Errors: isError, a plain message, and _meta.error_code/retryable — relay the message, never invent a cause; retry once only when retryable is true. An empty result is not an error.
search_requests
How do I improve a ChatGPT Plugin's discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Guidepoint alternatives on ChatGPT?
As of 2026-09-28, Guidepoint competes with CapitalDart, CB Insights, Cookiedeal, Dakota Marketplace, Datasite, Evertrace, GLG, Hadaly, Harmonic, Hebbia, In Practise, Mergr, PitchBook, PrefMark, Sacra, Specter, Third Bridge, TrustMRR, Venturu in ChatGPT Private Markets, Deals & Expert Networks, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.