@vruum/mcp 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/package.json +1 -1
  3. package/tools.json +102 -14
package/README.md CHANGED
@@ -112,6 +112,13 @@ Once connected, these are ordinary requests to your agent:
112
112
 
113
113
  Full schemas, descriptions and MCP safety annotations live in [`tools.json`](tools.json) — generated from the live server definition, never hand-edited.
114
114
 
115
+ The 0.5.0 package includes the deployed P2e revenue contracts in that snapshot:
116
+ `get_revenue` account and service-margin views, and the reviewed finance-statement
117
+ action in `manage_revenue`. Hosted remote MCP already exposes these contracts;
118
+ stdio clients receive the updated advertised schema after package publication
119
+ and upgrade. Finance attestation, binding decisions and measurement approval
120
+ retain their separate server-side checks.
121
+
115
122
  ## Configuration
116
123
 
117
124
  | Env var | Meaning | Default |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vruum/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "mcpName": "ai.vruum/mcp",
5
5
  "description": "Stdio MCP bridge for the hosted Vruum revenue-platform MCP (https://api.vruum.ai/mcp): serves the advertised tool surface for credential-free introspection and proxies tool calls with your Vruum token.",
6
6
  "license": "MIT",
package/tools.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "openWorldHint": false,
10
10
  "readOnlyHint": true
11
11
  },
12
- "description": "Fetch\n\nFetch one entity (or a small batch) by type + id — the consolidated read tool.\n\nTypes and id semantics:\n- claim: one hypothesis-ledger claim by UUID — statement, scope,\n derived confidence, invalidating conditions, linked evidence with\n provenance and liveness, and the append-only verdict history (VRU-840).\n Find ids with search type='claims'.\n- objective: an objective by UUID — the record + live pins + members\n count (array of UUIDs → per-id map). List objectives with `search`\n type='objectives'; create/update/retire/pause/launch/member changes with\n `manage_outreach`. A paused objective has no direct resume action.\n- plan: a person outreach plan. SCALAR id = the plan UUID; ARRAY id =\n PERSON UUIDs (routes to the native batch person-plans read, returning a\n person_id → plan|null map). Note the scalar/array dimension difference.\n- conversation: full message history for a person (id = person_id).\n- person_research: cached person research (id = person UUID; array → per-id map).\n- company_research: cached company research (id = website/domain string;\n array → native batch lookup).\n- account_state: per-account state row (id = company_id).\n- unit_economics: per-account impact × labor 2×2 + delivery-cost\n decomposition (VRU-864). id = company_id, optional — omit for every\n committed-stage account plus any account with labor. Filters:\n {window_days? (default 30), hourly_rate_usd?, horizon_years?}. Each\n account carries motion, scoped labor limit, labor splits (founder\n repair as its own series), recurring-impact attainment, the 2×2\n quadrant decision, and the Ch10 cost decomposition with per-number\n basis labels; pending_confirmation_dates lists days awaiting the\n daily confirm (see manage_account confirm_labor).\n- insights (subtype required): quality | pattern_tags | prompts |\n benchmarks | improve | reply_diagnosis (id = person_id) |\n mcp_patterns | mcp_pattern_detail (id = pattern_id).\n- scoreboard (subtype required): impact | bowtie (id = company_id;\n filters: {window_days?}).\n- settings (subtype required): profile | automation | marketing |\n channel_status | booking_link | objective_delegations | ai_compute\n (no id). ai_compute is READ-ONLY status: which provider/model\n interactive and background runs currently resolve to, the fallback\n behavior, coarse per-source health, and whether the caller's own\n subscription is connected. Connecting, rotating, testing and revoking\n compute credentials is deliberately not reachable from any tool.\n- billing: billing status for your company (no id).\n- publish_readiness: publish pre-check for a LinkedIn draft (id = post_id).\n- research_playbook: ICP + research workflow (id = optional objective UUID).\n- job: background job status — poll after async tools like\n people create / import_prospects / auto_fill_company_profile (id =\n scalar job UUID; a one-element array is normalized).\n- csv_import: CSV import progress (id = the job id returned by\n import_prospects csv_start).\n- company_resolution_queue: people missing an employer link (depth\n counts only genuinely-unlinked people; silent pin re-verification is\n excluded), oldest age, deferral reason, and projected completion\n horizon (no id).\n- deal: a raw deal row by UUID (for the enriched view use get_deal_360).\n- deal_pipelines: the tenant's configured pipelines and stages (no id;\n filters: {include_archived?}). Returns pipeline/stage IDs, ordering,\n stage kinds, probability defaults, archived state, and the default\n pipeline ID.\n- deal_alerts: at-risk deal alerts (no id).\n- post_analytics: LinkedIn post performance (id = post UUID; omit for\n all posts).\n- seller_signals: the seller signal bundle for content drafting (id =\n author user id, optional; filters: draft_brief/include_* flags).\n- stats (subtype required): outreach (filters: {start_date?, end_date?})\n | plan | person | prose (filters: {days?, default 30, use 7 for a\n weekly cut} — prose-quality trends from the outcome-linked review\n corpus: per-surface reply outcomes + reply rate and per-gate-code\n outcome lift (primary), plus keep/edit/kill/override mix, override\n codes, edit-rate trend vs the prior window, rules_version\n distribution) — aggregate stats snapshots.\n- marketing (subtype required): overview | activity (filters: {days?,\n limit?}).\n- ads (subtype required): attribution (filters: {window_days?,\n objective_id?, by}; by=creative|audience is REQUIRED and\n selects a per-creative or per-audience engagement funnel) |\n | creative (id = ad creative UUID; returns the row + a derived\n `terminal` verdict — failed with a probe-code error prefix means the\n asset itself is bad, re-export and re-store; otherwise `next_call`\n hints the poll/retry) | campaign (id = ad campaign UUID; returns the\n row + spend_today_cents + last_sync_at) | targeting_entities (filters:\n {facet (titles|seniorities|industries|locations|staff_count_ranges),\n q (min 2 chars), integration_id?}; resolves display names into the\n LinkedIn entity URNs the boost audience.facets form requires —\n locations targeting is mandatory on every facet boost).\n- skill: a published skill body (id = skill UUID).\n- relationship_attempt: one durable relationship action (id = the\n `attempt_...` reference returned by manage_relationship_action).\n\nBatch: id arrays are accepted for objective, plan, person_research and\ncompany_research (max 100 ids). Loop batches return ``{id: payload}`` with\nper-id 4xx failures mapped to ``{id: {\"error\": detail}}``; any 5xx aborts\nthe whole call. Errors carry the same status as the underlying endpoint\nwith the detail prefixed ``\"<type> '<id>': ...\"``.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
12
+ "description": "Fetch\n\nFetch one entity (or a small batch) by type + id — the consolidated read tool.\n\nTypes and id semantics:\n- journey_comparison: replay two approved journey observation reads for one\n objective UUID. Required filters: baseline_run_action_id, current_run_action_id,\n baseline_event_cutoff, current_event_cutoff, baseline_knowledge_cutoff,\n current_knowledge_cutoff (all times include a timezone). Same run and event\n window yield recorded evidence changes as knowledge advances, not business\n performance. Different cohorts report missing/mismatched comparison context\n and source coverage with no performance delta. No writes or external calls.\n- claim: one hypothesis-ledger claim by UUID — statement, scope,\n derived confidence, invalidating conditions, linked evidence with\n provenance and liveness, and the append-only verdict history (VRU-840).\n Find ids with search type='claims'.\n- objective_measurement: approved observation, frozen definition, known results, source coverage, and execution progress for an objective UUID. A read never starts work.\n- objective_workspace: shared objective readiness, next decision, source\n coverage and recoverable work (id=objective UUID). Read-only: it does not\n run research, call providers or models, create Actions, or approve launch.\n Discovery requirements are separate from execution readiness. Independent\n observation uses its own reviewed Action without an outreach launch.\n- objective: an objective by UUID — the record + live pins + members\n count + gtm_model (the Revenue Architecture touch level the book pairs\n with the selected offer's price, and the declared motion's alignment;\n a fact, not a rule) (array of UUIDs → per-id map). List objectives with `search`\n type='objectives'; create/update/retire/pause/launch/member changes with\n `manage_outreach`. A paused objective has no direct resume action.\n- plan: a person outreach plan. SCALAR id = the plan UUID; ARRAY id =\n PERSON UUIDs (routes to the native batch person-plans read, returning a\n person_id → plan|null map). Note the scalar/array dimension difference.\n- conversation: full message history for a person (id = person_id).\n- person_research: cached person research (id = person UUID; array → per-id map).\n- customer_impact: account engagements, promise history and verified criterion\n occurrences. Optional filters journey_id, claim_id, occurrence (default 1),\n journey_offset and source_offset (20-row pages). Exact IDs restore history.\n source_ids keeps up to 20 selected source records visible across pages.\n source_context=account reads source choices for a new engagement.\n This is a read only; use manage_account impact actions to record decisions.\n- company_family: immediate parent, subsidiaries, public ownership source,\n and separately labeled parent research (id=company UUID). No CRM adoption.\n- company_research: cached company research (id = company UUID or website/domain string;\n array → native batch lookup).\n Shared-domain lookups return candidates with match_reason=ambiguous.\n Select a candidate company UUID explicitly; never treat parent facts as child facts.\n- account_state: per-account state row (id = company_id).\n- unit_economics: per-account impact × labor 2×2 + delivery-cost\n decomposition (VRU-864). id = company_id, optional — omit for every\n committed-stage account plus any account with labor. Filters:\n {window_days? (default 30), hourly_rate_usd?, horizon_years?}. Each\n account carries its service model, scoped labor limit, labor splits (founder\n repair as its own series), recurring-impact attainment, the 2×2\n quadrant decision, and the Ch10 cost decomposition with per-number\n basis labels; pending_confirmation_dates lists days awaiting the\n daily confirm (see manage_account confirm_labor).\n- growth_formula: the Revenue Architecture Growth Formula (Ch 7 §7.2)\n as a stateless compute — no id; `filters` IS the request body:\n {conversions: [{id, value, basis, label?, numerator?, denominator?,\n source?}] in journey order, input_volume?, list_price?, discount?\n (same factor shape), offer_id? (the catalog offer: its annual list\n price becomes list_price with basis declared, and the median\n discount across this workspace's agreed won deals on that offer\n becomes discount with basis observed, unless either is supplied;\n the reply's `price` block names the currency and unit), target?\n {value, unit: wins|revenue},\n normalize? {per: input|win|revenue, amount}, scenario?\n {input_delta, conversion_deltas: number | {factor_id: number},\n input_volume?}, break_even? {factor_id, discount}}. Every factor\n names its basis (observed | declared | inferred | benchmarked |\n assumed). The reply carries throughput, net price, the bottleneck,\n the chain's weakest basis and, when asked, the forward output, the\n normalized unit economics, the backsolve with every stage rounded UP\n and its coverage, the scenario before/after, and the break-even\n rate. Do the arithmetic here, never in prose; a shape the formula\n cannot compute over is a 422 naming the reason.\n- insights (subtype required): quality | pattern_tags | prompts |\n benchmarks | improve | reply_diagnosis (id = person_id) |\n mcp_patterns | mcp_pattern_detail (id = pattern_id).\n- scoreboard (subtype required): impact | bowtie (id = company_id;\n filters: {window_days?}).\n- settings (subtype required): profile | automation | marketing |\n channel_status | booking_link | objective_delegations | ai_compute\n (no id). ai_compute is READ-ONLY status: which provider/model\n interactive and background runs currently resolve to, the fallback\n behavior, coarse per-source health, and whether the caller's own\n subscription is connected. Connecting, rotating, testing and revoking\n compute credentials is deliberately not reachable from any tool.\n- billing: billing status for your company (no id).\n- publish_readiness: publish pre-check for a LinkedIn draft (id = post_id).\n- research_run: frozen assignment and execution results (id=Action UUID).\n- research_sources: original saved facts and optionally permitted history.\n id=objective UUID; filters={subject_type,subject_id,question_id,company_id?,\n use_history?:false}. No model or provider spend. Returns exact references,\n excerpts and original observation times for research save_answer.\n- research_answers: objective research answers and decision readiness\n (id = objective UUID; filters: {subject_type, subject_id, company_id?,\n stage?}). Subjects: company, person, person_at_company, deal. company_id\n is required only for person_at_company. Stages: qualification, activation\n (default), discovery, purchase, impact. Includes inherited account answers,\n exact question definitions, source quality, stale/conflicting/missing states.\n Readiness covers the brief; existing identity/channel/safety gates still apply.\n- research_playbook: ICP + research workflow (id = optional objective UUID).\n- job: background job status — poll after async tools like\n people create / import_prospects / auto_fill_company_profile (id =\n scalar job UUID; a one-element array is normalized).\n- csv_import: CSV import progress (id = the job id returned by\n import_prospects csv_start).\n- company_resolution_queue: people missing an employer link (depth\n counts only genuinely-unlinked people; silent pin re-verification is\n excluded), oldest age, deferral reason, and projected completion\n horizon (no id).\n- deal: a raw deal row by UUID (for the enriched view use get_deal_360).\n- deal_pipelines: the tenant's configured pipelines and stages (no id;\n filters: {include_archived?}). Returns pipeline/stage IDs, ordering,\n stage kinds, probability defaults, archived state, and the default\n pipeline ID.\n- deal_alerts: at-risk deal alerts (no id).\n- post_analytics: LinkedIn post performance (id = post UUID; omit for\n all posts).\n- seller_signals: the seller signal bundle for content drafting (id =\n author user id, optional; filters: draft_brief/include_* flags).\n- stats (subtype required): outreach (filters: {start_date?, end_date?})\n | plan | person | prose (filters: {days?, default 30, use 7 for a\n weekly cut} — prose-quality trends from the outcome-linked review\n corpus: per-surface reply outcomes + reply rate and per-gate-code\n outcome lift (primary), plus keep/edit/kill/override mix, override\n codes, edit-rate trend vs the prior window, rules_version\n distribution) — aggregate stats snapshots.\n- marketing (subtype required): overview | activity (filters: {days?,\n limit?}).\n- ads (subtype required): attribution (filters: {window_days?,\n objective_id?, by}; by=creative|audience is REQUIRED and\n selects a per-creative or per-audience engagement funnel) |\n | creative (id = ad creative UUID; returns the row + a derived\n `terminal` verdict — failed with a probe-code error prefix means the\n asset itself is bad, re-export and re-store; otherwise `next_call`\n hints the poll/retry) | campaign (id = ad campaign UUID; returns the\n row + spend_today_cents + last_sync_at) | targeting_entities (filters:\n {facet (titles|seniorities|industries|locations|staff_count_ranges),\n q (min 2 chars), integration_id?}; resolves display names into the\n LinkedIn entity URNs the boost audience.facets form requires —\n locations targeting is mandatory on every facet boost).\n- skill: a published skill body (id = skill UUID).\n- relationship_attempt: one durable relationship action (id = the\n `attempt_...` reference returned by manage_relationship_action).\n\nBatch: id arrays are accepted for objective, plan, person_research and\ncompany_research (max 100 ids). Loop batches return ``{id: payload}`` with\nper-id 4xx failures mapped to ``{id: {\"error\": detail}}``; any 5xx aborts\nthe whole call. Errors carry the same status as the underlying endpoint\nwith the detail prefixed ``\"<type> '<id>': ...\"``.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
13
13
  "inputSchema": {
14
14
  "properties": {
15
15
  "filters": {
@@ -63,7 +63,7 @@
63
63
  "type": "string"
64
64
  },
65
65
  "type": {
66
- "description": "Entity type to fetch. One of: objective, csv_import, company_resolution_queue, plan, conversation, person_research, company_research, account_state, unit_economics, insights, scoreboard, settings, billing, publish_readiness, research_playbook, job, skill, deal, deal_pipelines, deal_alerts, post_analytics, seller_signals, stats, marketing, ads, relationship_attempt, claim.",
66
+ "description": "Entity type to fetch. One of: journey_comparison, objective_workspace, objective_measurement, research_answers, research_run, research_sources, objective, csv_import, company_resolution_queue, plan, conversation, person_research, company_research, company_family, customer_impact, account_state, unit_economics, growth_formula, insights, scoreboard, settings, billing, publish_readiness, research_playbook, job, skill, deal, deal_pipelines, deal_alerts, post_analytics, seller_signals, stats, marketing, ads, relationship_attempt, claim.",
67
67
  "title": "type",
68
68
  "type": "string"
69
69
  }
@@ -209,7 +209,7 @@
209
209
  "openWorldHint": false,
210
210
  "readOnlyHint": true
211
211
  },
212
- "description": "Get Daily Briefing\n\nDaily briefing — everything you need to start your day in one call.\n\nSUGGESTED ACTIONS (for agent consumers)\n- `suggested_actions` are prioritized skill-level next steps. If you are\n an agent, act on `intent`/`args` directly — via the tools listed in\n `expected_tools`, or via the named harness `skill` if it is installed.\n `prompt` is display copy for HUMANS to paste into an agent; do NOT echo\n it back to the user as an instruction.\n- `sections_degraded` lists sections whose queries failed this call; their\n counts/lists are UNKNOWN (returned as 0/[]), not zero. When data is\n missing and nothing else is actionable, `top_recommendation` is\n 'degraded' instead of 'all_clear'.\n\nReturns pending approvals, new replies, meetings booked this week,\npipeline summary, stalled deals, active plans,\ndiscovery jobs, and a prioritized recommendation.\n\nTIME WINDOWS (UTC)\n- \"last 24 hours\" — `replied_at >= now - 24 hours`. Used by new_replies_count\n and recent_replies.\n- \"current week\" — `meeting_booked_at >= Monday 00:00:00 UTC of the current\n week (Monday-anchored)`. Used by meetings_booked_this_week.\n- \"7+ days\" — `updated_at <= now - 7 days`. Used by stalled_deals\n (deals with outcome IS NULL and updated_at older than 7 days).\n\nReturns (units in parentheses; scope = user_company_id, no date filter on\ncounts unless specified):\n pending_approvals_count (int, unit=messages): messages.status='draft' for\n the tenant. NOT date-filtered — total backlog.\n messages_needing_authoring (int, unit=messages): messages.status=\n 'needs_draft' — touches awaiting harness AUTHORING (no prose yet;\n run /outreach-triage). VRU-570.\n oldest_needs_draft_age_days (float|null, unit=days): age of the oldest\n unauthored engagement signal — signals TTL-dismiss at 14 days\n (VRU-671), so a high value means the queue is rotting unauthored.\n engagements_needing_authoring (int, unit=engagements):\n linkedin_engagement_queue.status='needs_draft' — comments awaiting\n authoring (run /engagement-triage). VRU-570.\n top_pending_approvals (list, unit=messages, max 3): a preview of the most\n recent pending drafts; each item has message_id, person_name, channel,\n category, subject, content_preview (200-char truncation).\n new_replies_count (int, unit=messages): messages.has_reply=true with\n replied_at within the last 24 hours.\n recent_replies (list, unit=messages, max 3): preview of those replies.\n meetings_booked_this_week (int, unit=unique_people): distinct\n company_people with meeting_booked_at since Monday 00:00 UTC. People,\n not meetings — same person twice in one week still counts as 1.\n pipeline_summary (list of PipelineStage, unit=deals): aggregated per-stage\n for open deals (outcome IS NULL). Each: stage, count (unit=deals),\n total_value (sum of estimated_value).\n stalled_deals (list, unit=deals, max 5): open deals (outcome IS NULL) whose\n updated_at is older than 7 days. days_stalled is calendar days since\n updated_at.\n deal_alerts (list of DealAlert, unit=deals): per-deal alerts surfaced from\n DealService.get_deal_alerts (severity / detail / days).\n deal_alerts_count (int, unit=deals): len(deal_alerts).\n active_outreach_plans (int, unit=plans): outreach_plans.status='active'.\n Not date-filtered.\n channel_holds (list, unit=sender accounts): current LinkedIn quota or\n reconnect blocks, including retry_at and affected approved sends.\n Agents must report these before claiming outreach is all-clear.\n discovery_jobs_in_progress (int, unit=jobs):\n background_jobs.operation_type='person_discovery' with status IN\n ('pending', 'processing').\n tasks_due_today_count (int, unit=tasks): open/in_progress tasks with\n due_at <= now (due today or overdue) — same set as\n get_tasks(due_today=true). Committed follow-ups, incl. action\n items captured from meetings.\n top_tasks_due (list of TaskDue, max 5): preview of those tasks; each\n has task_id, title, due_at, priority, person_id, deal_id.\n motion_health (MotionHealth): outbound conversion over the trailing 60\n days, tenant-wide, as UNIQUE PEOPLE contacted vs replied. READ THIS\n BEFORE RECOMMENDING MORE VOLUME. state='broken' (>=100 contacted,\n <1% reply rate) means the queue is not the bottleneck and approving\n more drafts is negative-EV work; the fix is worth more than the\n throughput. state='unmeasured' (<30 contacted, or an incomplete\n scan) means there is not enough evidence to judge — treat it as\n silence, never as failure. Carries worst_objective_* for the\n objective that spent the most contacts for zero replies.\n objective_health (list of ObjectiveConversion): the same numbers per\n objective, highest-volume first.\n reply_rate is null (not 0.0) when nothing was contacted —\n unmeasured is not zero.\n content_cadence (ContentCadence): days_since_last_post and 30-day post\n volume. A dark feed removes the warm surface outreach converts\n against.\n expansion_candidates_count (int, unit=deals): won deals 60+ days old\n whose account has no open follow-on (/expansion-fill cohort).\n winback_candidates_count (int, unit=deals): deals lost between 90 days\n and 18 months ago. WINDOW ONLY — `deals.loss_reason` is free text,\n so the terminal-loss filter is the skill's job, not this count's.\n synthesis_applied (bool): true when suggested_actions came from the\n cached daily LLM synthesis; false means the deterministic floor is\n serving (synthesis absent, stale, or contract-rejected).\n synthesis_headline (str|null): one-line statement of the actual\n bottleneck from that synthesis; null when the floor is serving.\n top_recommendation (str): the winning suggested action, as a legacy\n alias where one exists ('channel_reconnect', 'pending_approvals',\n 'new_replies', 'deal_alerts', 'tasks_due') and otherwise the\n action's own intent (e.g. 'fix_objective', 'work_warm_paths'). It is\n derived FROM suggested_actions[0], so the two can never disagree.\n 'degraded' = section data is missing and nothing else is actionable\n (see sections_degraded above); 'all_clear' = nothing actionable.\n recommendation_detail (str): human prose explanation — the winning\n action's `reason`.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"pending_approvals_count\": 1,\n \"messages_needing_authoring\": 1,\n \"engagements_needing_authoring\": 1,\n \"engagers_awaiting_decision\": 1,\n \"top_pending_approvals\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"new_replies_count\": 1,\n \"recent_replies\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"meetings_booked_this_week\": 1,\n \"pipeline_summary\": [\n {\n \"stage\": \"Stage\",\n \"count\": 1,\n \"total_value\": 1.0\n }\n ],\n \"stalled_deals\": [\n {\n \"deal_id\": \"Deal Id\",\n \"days_stalled\": 1\n }\n ],\n \"deal_alerts\": [\n {\n \"deal_id\": \"Deal Id\",\n \"deal_name\": \"Deal Name\",\n \"alert_type\": \"Alert Type\",\n \"severity\": \"Severity\",\n \"detail\": \"Detail\",\n \"days\": 1\n }\n ],\n \"deal_alerts_count\": 1,\n \"active_outreach_plans\": 1,\n \"channel_holds\": [\n {\n \"channel\": \"Channel\",\n \"state\": \"State\",\n \"reason_code\": \"Reason Code\",\n \"blocking\": true,\n \"retryable\": true,\n \"affected_approved_count\": 1,\n \"next_action\": \"Next Action\"\n }\n ],\n \"discovery_jobs_in_progress\": 1,\n \"tasks_due_today_count\": 1,\n \"top_tasks_due\": [\n {\n \"task_id\": \"Task Id\",\n \"title\": \"Title\"\n }\n ],\n \"new_warm_paths_count\": 1,\n \"new_warm_paths\": [\n {}\n ],\n \"seo_geo_open_gaps\": 1,\n \"motion_health\": {\n \"state\": \"State\",\n \"window_days\": 1,\n \"contacted\": 1,\n \"replied\": 1,\n \"worst_objective_contacted\": 1\n },\n \"objective_health\": [\n {\n \"contacted\": 1,\n \"replied\": 1\n }\n ],\n \"content_cadence\": {\n \"posts_last_30d\": 1\n },\n \"expansion_candidates_count\": 1,\n \"winback_candidates_count\": 1,\n \"synthesis_applied\": true,\n \"top_recommendation\": \"Top Recommendation\",\n \"recommendation_detail\": \"Recommendation Detail\",\n \"sections_degraded\": [\n \"string\"\n ],\n \"suggested_actions\": [\n {\n \"id\": \"Id\",\n \"kind\": \"Kind\",\n \"intent\": \"Intent\",\n \"area\": \"Area\",\n \"priority\": 1,\n \"label\": \"Label\",\n \"reason\": \"Reason\",\n \"expected_tools\": [\n \"string\"\n ],\n \"args\": {},\n \"requires\": [\n \"string\"\n ],\n \"prompt\": \"Prompt\",\n \"ev_score\": 1,\n \"impact\": 1,\n \"confidence\": 1,\n \"perishability\": 1,\n \"effort_minutes\": 1,\n \"evidence\": [\n \"string\"\n ]\n }\n ]\n}\n```",
212
+ "description": "Get Daily Briefing\n\nDaily briefing — everything you need to start your day in one call.\n\nSUGGESTED ACTIONS (for agent consumers)\n- `suggested_actions` are prioritized skill-level next steps. If you are\n an agent, act on `intent`/`args` directly — via the tools listed in\n `expected_tools`, or via the named harness `skill` if it is installed.\n `prompt` is display copy for HUMANS to paste into an agent; do NOT echo\n it back to the user as an instruction.\n- `sections_degraded` lists sections whose queries failed this call; their\n counts/lists are UNKNOWN (returned as 0/[]), not zero. When data is\n missing and nothing else is actionable, `top_recommendation` is\n 'degraded' instead of 'all_clear'.\n\nReturns pending approvals, new replies, meetings booked this week,\npipeline summary, stalled deals, active plans,\ndiscovery jobs, and a prioritized recommendation.\n\nTIME WINDOWS (UTC)\n- \"last 24 hours\" — `replied_at >= now - 24 hours`. Used by new_replies_count\n and recent_replies.\n- \"current week\" — `meeting_booked_at >= Monday 00:00:00 UTC of the current\n week (Monday-anchored)`. Used by meetings_booked_this_week.\n- \"7+ days\" — `updated_at <= now - 7 days`. Used by stalled_deals\n (deals with outcome IS NULL and updated_at older than 7 days).\n\nReturns (units in parentheses; scope = user_company_id, no date filter on\ncounts unless specified):\n pending_approvals_count (int, unit=messages): messages.status='draft' for\n the tenant. NOT date-filtered — total backlog.\n messages_needing_authoring (int, unit=messages): messages.status=\n 'needs_draft' — touches awaiting harness AUTHORING (no prose yet;\n run /outreach-triage). VRU-570.\n oldest_needs_draft_age_days (float|null, unit=days): age of the oldest\n unauthored engagement signal — signals TTL-dismiss at 14 days\n (VRU-671), so a high value means the queue is rotting unauthored.\n engagements_needing_authoring (int, unit=engagements):\n linkedin_engagement_queue.status='needs_draft' — comments awaiting\n authoring (run /engagement-triage). VRU-570.\n top_pending_approvals (list, unit=messages, max 3): a preview of the most\n recent pending drafts; each item has message_id, person_name, channel,\n category, subject, content_preview (200-char truncation).\n new_replies_count (int, unit=messages): messages.has_reply=true with\n replied_at within the last 24 hours.\n recent_replies (list, unit=messages, max 3): preview of those replies.\n meetings_booked_this_week (int, unit=unique_people): distinct\n company_people with meeting_booked_at since Monday 00:00 UTC. People,\n not meetings — same person twice in one week still counts as 1.\n pipeline_summary (list of PipelineStage, unit=deals): aggregated per-stage\n for open deals (outcome IS NULL). Each: stage, count (unit=deals),\n values (per-currency recurring annual value, project totals, unstated figures).\n stalled_deals (list, unit=deals, max 5): open deals (outcome IS NULL) whose\n updated_at is older than 7 days. days_stalled is calendar days since\n updated_at.\n deal_alerts (list of DealAlert, unit=deals): per-deal alerts surfaced from\n DealService.get_deal_alerts (severity / detail / days).\n deal_alerts_count (int, unit=deals): len(deal_alerts).\n active_outreach_plans (int, unit=plans): outreach_plans.status='active'.\n Not date-filtered.\n channel_holds (list, unit=sender accounts): current LinkedIn quota or\n reconnect blocks, including retry_at and affected approved sends.\n Agents must report these before claiming outreach is all-clear.\n discovery_jobs_in_progress (int, unit=jobs):\n background_jobs.operation_type='person_discovery' with status IN\n ('pending', 'processing').\n tasks_due_today_count (int, unit=tasks): open/in_progress tasks with\n due_at <= now (due today or overdue) — same set as\n get_tasks(due_today=true). Committed follow-ups, incl. action\n items captured from meetings.\n top_tasks_due (list of TaskDue, max 5): preview of those tasks; each\n has task_id, title, due_at, priority, person_id, deal_id.\n motion_health (MotionHealth): outbound conversion over the trailing 60\n days, tenant-wide, as UNIQUE PEOPLE contacted vs replied. READ THIS\n BEFORE RECOMMENDING MORE VOLUME. state='broken' (>=100 contacted,\n <1% reply rate) means the queue is not the bottleneck and approving\n more drafts is negative-EV work; the fix is worth more than the\n throughput. state='unmeasured' (<30 contacted, or an incomplete\n scan) means there is not enough evidence to judge — treat it as\n silence, never as failure. Carries worst_objective_* for the\n objective that spent the most contacts for zero replies.\n objective_health (list of ObjectiveConversion): the same numbers per\n objective, highest-volume first.\n reply_rate is null (not 0.0) when nothing was contacted —\n unmeasured is not zero.\n content_cadence (ContentCadence): days_since_last_post and 30-day post\n volume. A dark feed removes the warm surface outreach converts\n against.\n expansion_candidates_count (int, unit=deals): won deals 60+ days old\n whose account has no open follow-on (/expansion-fill cohort).\n winback_candidates_count (int, unit=deals): deals lost between 90 days\n and 18 months ago. WINDOW ONLY — `deals.loss_reason` is free text,\n so the terminal-loss filter is the skill's job, not this count's.\n synthesis_applied (bool): true when suggested_actions came from the\n cached daily LLM synthesis; false means the deterministic floor is\n serving (synthesis absent, stale, or contract-rejected).\n synthesis_headline (str|null): one-line statement of the actual\n bottleneck from that synthesis; null when the floor is serving.\n top_recommendation (str): the winning suggested action, as a legacy\n alias where one exists ('channel_reconnect', 'pending_approvals',\n 'new_replies', 'deal_alerts', 'tasks_due') and otherwise the\n action's own intent (e.g. 'fix_objective', 'work_warm_paths'). It is\n derived FROM suggested_actions[0], so the two can never disagree.\n 'degraded' = section data is missing and nothing else is actionable\n (see sections_degraded above); 'all_clear' = nothing actionable.\n recommendation_detail (str): human prose explanation — the winning\n action's `reason`.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"pending_approvals_count\": 1,\n \"messages_needing_authoring\": 1,\n \"engagements_needing_authoring\": 1,\n \"engagers_awaiting_decision\": 1,\n \"top_pending_approvals\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"new_replies_count\": 1,\n \"recent_replies\": [\n {\n \"message_id\": \"Message Id\",\n \"person_name\": \"Person Name\"\n }\n ],\n \"meetings_booked_this_week\": 1,\n \"pipeline_summary\": [\n {\n \"stage\": \"Stage\",\n \"count\": 1,\n \"values\": [\n {\n \"currency\": \"Currency\",\n \"deals\": 1,\n \"annual_minor\": 1,\n \"weighted_annual_minor\": 1,\n \"project_total_minor\": 1,\n \"weighted_project_total_minor\": 1,\n \"unstated_minor\": 1,\n \"unstated_deals\": 1\n }\n ]\n }\n ],\n \"stalled_deals\": [\n {\n \"deal_id\": \"Deal Id\",\n \"terms\": {\n \"currency\": \"Currency\",\n \"quantity\": 1,\n \"stated\": true,\n \"agreed\": true,\n \"text\": \"Text\"\n },\n \"days_stalled\": 1\n }\n ],\n \"deal_alerts\": [\n {\n \"deal_id\": \"Deal Id\",\n \"deal_name\": \"Deal Name\",\n \"alert_type\": \"Alert Type\",\n \"severity\": \"Severity\",\n \"detail\": \"Detail\",\n \"days\": 1\n }\n ],\n \"deal_alerts_count\": 1,\n \"active_outreach_plans\": 1,\n \"channel_holds\": [\n {\n \"channel\": \"Channel\",\n \"state\": \"State\",\n \"reason_code\": \"Reason Code\",\n \"blocking\": true,\n \"retryable\": true,\n \"affected_approved_count\": 1,\n \"next_action\": \"Next Action\"\n }\n ],\n \"discovery_jobs_in_progress\": 1,\n \"tasks_due_today_count\": 1,\n \"top_tasks_due\": [\n {\n \"task_id\": \"Task Id\",\n \"title\": \"Title\"\n }\n ],\n \"new_warm_paths_count\": 1,\n \"new_warm_paths\": [\n {}\n ],\n \"seo_geo_open_gaps\": 1,\n \"motion_health\": {\n \"state\": \"State\",\n \"window_days\": 1,\n \"contacted\": 1,\n \"replied\": 1,\n \"worst_objective_contacted\": 1\n },\n \"objective_health\": [\n {\n \"contacted\": 1,\n \"replied\": 1\n }\n ],\n \"content_cadence\": {\n \"posts_last_30d\": 1\n },\n \"expansion_candidates_count\": 1,\n \"winback_candidates_count\": 1,\n \"synthesis_applied\": true,\n \"top_recommendation\": \"Top Recommendation\",\n \"recommendation_detail\": \"Recommendation Detail\",\n \"sections_degraded\": [\n \"string\"\n ],\n \"suggested_actions\": [\n {\n \"id\": \"Id\",\n \"kind\": \"Kind\",\n \"intent\": \"Intent\",\n \"area\": \"Area\",\n \"priority\": 1,\n \"label\": \"Label\",\n \"reason\": \"Reason\",\n \"expected_tools\": [\n \"string\"\n ],\n \"args\": {},\n \"requires\": [\n \"string\"\n ],\n \"prompt\": \"Prompt\",\n \"ev_score\": 1,\n \"impact\": 1,\n \"confidence\": 1,\n \"perishability\": 1,\n \"effort_minutes\": 1,\n \"evidence\": [\n \"string\"\n ]\n }\n ]\n}\n```",
213
213
  "inputSchema": {
214
214
  "properties": {
215
215
  "for_company": {
@@ -230,7 +230,7 @@
230
230
  "openWorldHint": false,
231
231
  "readOnlyHint": true
232
232
  },
233
- "description": "Get Deal Compound\n\nComplete deal context in one call — replaces 3 separate tool calls.\n\nAggregates deal info, stakeholders with person details, MEDDIC qualification\nstate with gap descriptions, and recent activity timeline.\n\nLike get_person_360 but for deals. Use this as the default tool for\nunderstanding a deal's current state.\n\nOperators: use X-Company-Id header for cross-company access.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
233
+ "description": "Get Deal Compound\n\nComplete deal context in one call — replaces 3 separate tool calls.\n\nAggregates deal info, stakeholders with person details, MEDDIC qualification\nstate with gap descriptions, the Customer Impact record (SPICED: situation,\npain, impact, critical event, decision — with its scoreboard and whether it\nverifies the priority), and recent activity timeline.\n\nLike get_person_360 but for deals. Use this as the default tool for\nunderstanding a deal's current state.\n\nOperators: use X-Company-Id header for cross-company access.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
234
234
  "inputSchema": {
235
235
  "properties": {
236
236
  "deal_id": {
@@ -465,7 +465,7 @@
465
465
  "openWorldHint": false,
466
466
  "readOnlyHint": true
467
467
  },
468
- "description": "Get Objective Outcomes\n\nCompare objectives on real outcomes — contacted, replied, meetings booked.\n\nA legacy outreach campaign is a MIRROR objective — pass its objective id,\nnot the campaign id.\n\nAnswers \"which of these objectives actually worked\" for up to 100\nobjectives in one call, over an explicit UTC window. Every objective you\nask for comes back, including ones with no usable data — those carry\n`availability: \"unavailable\"` rather than being dropped or reported as\nzero, so an objective never looks like a failure when it is really just\nunmeasured.\n\n**Use `get_performance_metrics` instead** for funnel and time-series views\nof the whole account; this tool is for ranking specific objectives against\neach other on a like-for-like cohort.\n\nReading the response:\n\n- **`outcome_cutoff`** is the \"as of\" moment. A reply or meeting counts\n only if it happened by then, so an objective that started last week is\n not punished for outcomes that have not had time to land. Keep it\n identical across objectives you intend to compare.\n- **`coverage_complete: false`** means attribution for that objective is\n still incomplete. Report the numbers, but do not rank on them — that is\n the difference between \"performed badly\" and \"we cannot tell yet\".\n- **`data_watermark`** is how far message and meeting ingestion has caught\n up. Outcomes after the watermark are not in these numbers yet.\n- **`computed_at`** stamps the calculation, which is worth quoting when\n the same window is re-run later and moves.\n\nWindow rules the server enforces: all three timestamps must carry a UTC\noffset, `start_at < end_at <= outcome_cutoff`, and `outcome_cutoff` cannot\nbe in the future. `objective_ids` must be unique.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"computed_at\": \"2023-01-01T00:00:00Z\",\n \"outcome_cutoff\": \"2023-01-01T00:00:00Z\",\n \"coverage_complete\": true,\n \"items\": [\n {\n \"objective_id\": \"Objective Id\",\n \"availability\": \"Availability\",\n \"contacted\": 1,\n \"replied\": 1,\n \"meetings\": 1,\n \"won\": 1,\n \"reply_rate\": 1.0,\n \"meeting_rate\": 1.0,\n \"attribution_quality\": \"Attribution Quality\",\n \"exact_count\": 1,\n \"reconstructed_count\": 1,\n \"inferred_count\": 1,\n \"coverage_complete\": true,\n \"data_watermark\": {\n \"pending_sends\": 1\n }\n }\n ]\n}\n```",
468
+ "description": "Get Objective Outcomes\n\nCompare objectives on real outcomes — contacted, replied, meetings booked.\n\nA legacy outreach campaign is a MIRROR objective — pass its objective id,\nnot the campaign id.\n\nAnswers \"which of these objectives actually worked\" for up to 100\nobjectives in one call, over an explicit UTC window. Every objective you\nask for comes back, including ones with no usable data — those carry\n`availability: \"unavailable\"` rather than being dropped or reported as\nzero, so an objective never looks like a failure when it is really just\nunmeasured.\n\n**Use `get_performance_metrics` instead** for funnel and time-series views\nof the whole account; this tool is for ranking specific objectives against\neach other on a like-for-like cohort.\n\nReading the response:\n\n- **`outcome_cutoff`** is the \"as of\" moment. A reply or meeting counts\n only if it happened by then, so an objective that started last week is\n not punished for outcomes that have not had time to land. Keep it\n identical across objectives you intend to compare.\n- **`coverage_complete: false`** means attribution for that objective is\n still incomplete. Report the numbers, but do not rank on them — that is\n the difference between \"performed badly\" and \"we cannot tell yet\".\n- **`data_watermark`** is how far message and meeting ingestion has caught\n up. Outcomes after the watermark are not in these numbers yet.\n- **`computed_at`** stamps the calculation, which is worth quoting when\n the same window is re-run later and moves.\n\nWindow rules the server enforces: all three timestamps must carry a UTC\noffset, `start_at < end_at <= outcome_cutoff`, and `outcome_cutoff` cannot\nbe in the future. `objective_ids` must be unique.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n{\n \"computed_at\": \"2023-01-01T00:00:00Z\",\n \"outcome_cutoff\": \"2023-01-01T00:00:00Z\",\n \"coverage_complete\": true,\n \"items\": [\n {\n \"objective_id\": \"Objective Id\",\n \"availability\": \"Availability\",\n \"contacted\": 1,\n \"replied\": 1,\n \"meetings\": 1,\n \"won\": 1,\n \"reply_rate\": 1.0,\n \"meeting_rate\": 1.0,\n \"attribution_quality\": \"Attribution Quality\",\n \"exact_count\": 1,\n \"reconstructed_count\": 1,\n \"inferred_count\": 1,\n \"coverage_complete\": true,\n \"data_watermark\": {\n \"pending_sends\": 1\n },\n \"reply\": {\n \"metric\": \"Metric\",\n \"denominator\": 1,\n \"metric_definition_version\": \"Metric Definition Version\",\n \"sample\": \"Sample\",\n \"floor\": 1\n },\n \"meeting\": {\n \"metric\": \"Metric\",\n \"denominator\": 1,\n \"metric_definition_version\": \"Metric Definition Version\",\n \"sample\": \"Sample\",\n \"floor\": 1\n }\n }\n ]\n}\n```",
469
469
  "inputSchema": {
470
470
  "properties": {
471
471
  "end_at": {
@@ -707,7 +707,7 @@
707
707
  "openWorldHint": false,
708
708
  "readOnlyHint": true
709
709
  },
710
- "description": "Get Revenue\n\nRead Stripe-backed revenue truth.\n\nViews:\n- connection: provider connection and freshness state.\n- metrics: current gross MRR/ARR, net charge volume, collections and health.\n- evidence: recent immutable ingestion evidence for calculation drilldown.\n- bindings: suggested, confirmed, or rejected commercial-object bindings.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
710
+ "description": "Get Revenue\n\nRead provider revenue and explicitly attested project finance.\n\nViews:\n- connection: provider connection and freshness state.\n- metrics: current gross MRR/ARR, net charge volume, collections and health.\n- evidence: recent immutable ingestion evidence for calculation drilldown.\n- bindings: suggested, confirmed, or rejected commercial-object bindings.\n- account: confirmed USD account revenue, invoices and latest recorded charge.\n- service_margin: finance-attested project margin for an exact closed period.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
711
711
  "inputSchema": {
712
712
  "properties": {
713
713
  "binding_status": {
@@ -720,6 +720,20 @@
720
720
  "title": "binding_status",
721
721
  "type": "string"
722
722
  },
723
+ "company_id": {
724
+ "anyOf": [
725
+ {
726
+ "format": "uuid",
727
+ "type": "string"
728
+ },
729
+ {
730
+ "type": "null"
731
+ }
732
+ ],
733
+ "description": "Required for the account view.",
734
+ "title": "company_id",
735
+ "type": "string"
736
+ },
723
737
  "currency": {
724
738
  "anyOf": [
725
739
  {
@@ -754,6 +768,32 @@
754
768
  "title": "for_company",
755
769
  "type": "string"
756
770
  },
771
+ "journey_id": {
772
+ "anyOf": [
773
+ {
774
+ "format": "uuid",
775
+ "type": "string"
776
+ },
777
+ {
778
+ "type": "null"
779
+ }
780
+ ],
781
+ "title": "journey_id",
782
+ "type": "string"
783
+ },
784
+ "knowledge_cutoff": {
785
+ "anyOf": [
786
+ {
787
+ "format": "date-time",
788
+ "type": "string"
789
+ },
790
+ {
791
+ "type": "null"
792
+ }
793
+ ],
794
+ "title": "knowledge_cutoff",
795
+ "type": "string"
796
+ },
757
797
  "limit": {
758
798
  "default": 50,
759
799
  "maximum": 200.0,
@@ -778,12 +818,48 @@
778
818
  "title": "metric",
779
819
  "type": "string"
780
820
  },
821
+ "offset": {
822
+ "default": 0,
823
+ "description": "Account records pagination offset.",
824
+ "maximum": 10000.0,
825
+ "minimum": 0.0,
826
+ "title": "offset",
827
+ "type": "integer"
828
+ },
829
+ "period_end": {
830
+ "anyOf": [
831
+ {
832
+ "format": "date",
833
+ "type": "string"
834
+ },
835
+ {
836
+ "type": "null"
837
+ }
838
+ ],
839
+ "title": "period_end",
840
+ "type": "string"
841
+ },
842
+ "period_start": {
843
+ "anyOf": [
844
+ {
845
+ "format": "date",
846
+ "type": "string"
847
+ },
848
+ {
849
+ "type": "null"
850
+ }
851
+ ],
852
+ "title": "period_start",
853
+ "type": "string"
854
+ },
781
855
  "view": {
782
856
  "enum": [
783
857
  "connection",
784
858
  "metrics",
785
859
  "evidence",
786
- "bindings"
860
+ "bindings",
861
+ "account",
862
+ "service_margin"
787
863
  ],
788
864
  "title": "view",
789
865
  "type": "string"
@@ -895,7 +971,7 @@
895
971
  "openWorldHint": false,
896
972
  "readOnlyHint": false
897
973
  },
898
- "description": "Manage Account\n\nMaintain account state and impact history — the consolidated account writer.\n\nActions:\n- state: update an account's lifecycle state (id = the account's\n company_id; `payload` = {account_stage?, health_score?, arr_current?,\n renewal_at?, notes?, motion?, scoped_labor_minutes_per_month?, …}).\n motion (managed | self_serve_support | design_partner) and the scoped\n labor limit are the VRU-864 delivery-economics settings.\n- record_impact: log a value-delivered impact event on an account\n (id = company_id; `payload` = {practice, event_type, person_id?,\n value_delivered_numeric?, summary?, …}).\n- delete_impact: permanently remove an impact event (id = the impact\n EVENT/activity id, not the account). DESTRUCTIVE.\n- adopt_directory: add a shared canonical company to this tenant's CRM\n (id = directory company UUID; no payload). The global company remains\n shared and cannot be edited unless the tenant separately owns it.\n- record_labor: log labor observations — the minutes a work session\n spent serving an account (no id; `payload` = {entries: [{company_id?,\n motion, work_type, minutes, source_ref?, note?}], work_date?,\n source? (harness_telemetry | operator_manual)}). Session-level\n granularity; idempotent per entry on source_ref (a source_ref reused\n with different content is refused — use one per observed session).\n operator_manual entries are confirmed at insert; telemetry waits for\n confirm_labor. The batch is write-safe: per-entry errors are\n reported in `errors`, never silently half-applied. Motion\n self_serve_support marks founder repair on a self-serve account —\n never blend it into managed. Work types: onboarding, campaign_ops,\n reply_handling, meeting, content, research, repair, support,\n expansion, admin.\n- confirm_labor: the daily one-line confirmation (no id; `payload` =\n {work_date, entries?, confirm_telemetry?}). entries REPLACE their\n account's day outright (every prior active row in the group is\n superseded, so corrected minutes win); confirm_telemetry=true stamps\n the remaining unconfirmed telemetry as-is. Refreshes each touched\n account's labor-scope claim in the ledger (failures reported in\n claim_failures). Read the economics with fetch\n type='unit_economics'.\n\nBulk: id arrays loop with per-item status (partial failure reported per\nitem, never silent; max 100). Payloads are validated by the underlying\nroute's schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
974
+ "description": "Manage Account\n\nMaintain account state and impact history — the consolidated account writer.\n\nActions:\n- journey_measurement_fact: record/correct/retract a sourced operator fact for\n an approved journey observation (no id; JourneyMeasurementFactRequest).\n Pin run_action_id, journey_id and independent exact-journey source_evidence_ids.\n checkpoint_balance describes the balance at the approved horizon;\n terminal_loss records its event time; impact_followup attests continuous\n observation with no qualifying impact from commitment through observed_at\n for the pinned promise. Requires explicit reviewed_source_records confirmation\n and an owner/operator. It does not certify complete external source coverage.\n- customer_journey: record one sourced engagement (no id; payload is\n CustomerJourneyCreate: account_id, offer_id, kind, source_key,\n source_evidence_id and optional originating deal / trial relationship).\n Read fetch type=customer_impact id=account UUID first. Account UI: Impact tab.\n- impact_promise: record/revise a sourced promise (no id; ImpactPromiseRequest).\n Freeze the journey, next promise_revision, criteria, owner and verifier policy.\n Supply expected_claim_id from the workspace. Preserve existing SPICED context.\n- impact_report: report/correct/retract one criterion occurrence (no id;\n ImpactObservationRequest). Use the exact claim_id and current predecessor.\n attribute_sources_to_journey=true explicitly attributes matching account\n evidence to THIS result; it never rewrites the original source. Show this\n assignment to the user. Customer confirmation needs a matching inbound\n source, person and quotation. A model's assertion is not confirmation.\n- impact_verify: verify the exact saved observation (no id;\n ImpactVerificationRequest). Obtain the user's explicit verification decision;\n recording a report is not permission to verify it. Current tenant membership,\n frozen verifier policy and source validity are rechecked by the same REST path.\n No action changes canonical First Impact/Recurring Impact metrics yet.\n- parent: set or clear corporate ownership (id=child company UUID).\n payload={parent_company_id: UUID|null, expected_parent_company_id: UUID|null,\n source:{url,title?,observed_at}}. Read fetch company_family first. A public\n source is required even when clearing. Native owner or operator only.\n Does not merge companies, inherit research, or add family members to CRM.\n- list: create, rename, archive, or unarchive an account list. No id;\n payload={action, list_id? (required except create), name? (create/rename), description?}.\n- list_members: add/remove tenant accounts in a list. No id;\n payload={action: add|remove, list_id, company_ids: [UUID, ...]} (1–200).\n Lists never change objectives or start outreach.\n- state: update an account's lifecycle state (id = the account's\n company_id; `payload` = {renewal_at?, notes?, service_model?,\n scoped_labor_minutes_per_month?}. The lifecycle stage is NOT settable:\n it is a projection over recorded facts — a won deal (committed), the\n first post-commit impact event (onboarded), recurring impact\n (adopting; expansion_ready when no deal is open), sixty days without\n impact (dormant), a recorded `churn` event or a cancelled bound\n subscription (churned). Record the fact with record_impact or a deal\n outcome and the stage follows; the state read carries `lifecycle`\n with the basis and the facts).\n service_model (managed | self_serve_support | design_partner) and the scoped\n labor limit are the VRU-864 delivery-economics settings.\n- record_impact: log a value-delivered impact event on an account\n (id = company_id; `payload` = {practice, event_type, person_id?,\n value_delivered_numeric?, summary?, …}).\n- delete_impact: permanently remove an impact event (id = the impact\n EVENT/activity id, not the account). DESTRUCTIVE.\n- adopt_directory: add a shared canonical company to this tenant's CRM\n (id = directory company UUID; no payload). The global company remains\n shared and cannot be edited unless the tenant separately owns it.\n- record_labor: log labor observations — the minutes a work session\n spent serving an account (no id; `payload` = {entries: [{company_id?,\n service_model, work_type, minutes, source_ref?, note?}], work_date?,\n source? (harness_telemetry | operator_manual)}). Session-level\n granularity; idempotent per entry on source_ref (a source_ref reused\n with different content is refused — use one per observed session).\n operator_manual entries are confirmed at insert; telemetry waits for\n confirm_labor. The batch is write-safe: per-entry errors are\n reported in `errors`, never silently half-applied. Service model\n self_serve_support marks founder repair on a self-serve account —\n never blend it into managed. Work types: onboarding, campaign_ops,\n reply_handling, meeting, content, research, repair, support,\n expansion, admin.\n- confirm_labor: the daily one-line confirmation (no id; `payload` =\n {work_date, entries?, confirm_telemetry?}). entries REPLACE their\n account's day outright (every prior active row in the group is\n superseded, so corrected minutes win); confirm_telemetry=true stamps\n the remaining unconfirmed telemetry as-is. Refreshes each touched\n account's labor-scope claim in the ledger (failures reported in\n claim_failures). Read the economics with fetch\n type='unit_economics'.\n\nBulk: id arrays loop with per-item status (partial failure reported per\nitem, never silent; max 100). Payloads are validated by the underlying\nroute's schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
899
975
  "inputSchema": {
900
976
  "properties": {
901
977
  "action": {
@@ -1049,7 +1125,7 @@
1049
1125
  "openWorldHint": false,
1050
1126
  "readOnlyHint": false
1051
1127
  },
1052
- "description": "Manage Deal\n\nCreate and manage deals through the pipeline — the consolidated deal writer.\n\nActions (id = deal UUID):\n- create: open a deal (no id; `payload` = {deal_name, person_id?,\n company_id?, deal_source? (outbound|inbound|referral|manual|imported;\n default manual), estimated_value?, stage?, pipeline_id?, stage_id?,\n close_probability?, notes?}). `pipeline_id` and `stage_id` must be\n supplied together; bulk via `items`.\n- update: patch deal fields (`payload` = any of deal_name,\n estimated_value, close_probability, expected_close_date, next_step,\n notes, stage, …).\n- stage: advance/move the deal — sugar for update; `payload` must include\n {stage}.\n- qualify: run AI MEDDIC-style qualification on the deal (no payload).\n- stakeholders: add/update/remove a deal stakeholder (`payload` =\n {action: add|update|remove, person_id?, role?, notes?}).\n- won / lost / stalled: record the deal outcome (`payload` optional:\n {loss_reason?, win_factors?}).\n- meeting_outcome: record how the meeting went (`payload` = the outcome\n fields).\n- reopen: reopen a closed deal (`payload` optional: {stage}).\n- create_pipeline: create a pipeline (no id; `payload` = {name,\n is_default?, position?, external_id?, stages?}).\n- rename_pipeline / archive_pipeline / unarchive_pipeline /\n set_default: `id` = pipeline UUID. Rename takes\n `payload={name}`; the others take no payload.\n- reorder_pipelines: no id; `payload={ordered_pipeline_ids}`.\n- create_stage: `id` = pipeline UUID; `payload={name, position?,\n stage_kind?, probability_default?, external_id?}`.\n- update_stage / archive_stage / unarchive_stage: `id` = pipeline\n UUID and `payload.stage_id` = stage UUID. Update also accepts name,\n stage_kind, and probability_default.\n- reorder_stages: `id` = pipeline UUID;\n `payload={ordered_stage_ids}`.\n\nBulk: `id` arrays apply the same action+payload per deal (server-side\nloop, per-item status — partial failure is reported per item, never\nsilent; max 100). Payloads are validated by the underlying route's own\nschema, so a bad field returns that route's precise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1128
+ "description": "Manage Deal\n\nCreate and manage deals through the pipeline — the consolidated deal writer.\n\nActions (id = deal UUID):\n- create: open a deal (no id; `payload` = {deal_name, person_id?,\n company_id?, deal_source? (outbound|inbound|referral|manual|imported;\n default manual), terms?, stage?, pipeline_id?, stage_id?,\n close_probability?, notes?}). `terms` is the deal's money record:\n {offer_id?, amount_minor?, currency?, billing_period?, quantity?,\n agreed?} — amount_minor is the net price PER BILLING PERIOD, per\n unit, in the currency's MINOR units (1,800.00 USD/month = 180000);\n billing_period one_time|monthly|quarterly|annual (recurring periods\n carry an annual contract value, one_time is a project total); the\n offer supplies currency and period when omitted; agreed=true only\n when the customer agreed the price (an agreed, won deal becomes\n committed revenue, an estimate never does). Omit terms to let the\n value agent estimate. `pipeline_id` and `stage_id` must be supplied\n together; bulk via `items`.\n- update: patch deal fields (`payload` = any of deal_name, terms\n (partial: only the keys given change), close_probability,\n expected_close_date, next_step, notes, stage, …).\n- stage: advance/move the deal — sugar for update; `payload` must include\n {stage}.\n- qualify: extract the customer's SPICED record from the deal's conversations and write the Customer Impact record; MEDDIC is derived from it (no payload).\n- impact_commitment: record the deal's Customer Impact (Revenue Architecture\n SPICED) as a claim: payload {situation?, pain?, impact? {rational? {metric,\n baseline, target, unit, by}, emotional?}, critical_event? {kind:\n compelling|critical|mandatory, due|milestone, consequence}, decision?\n {criteria, process, decider, champion, decider_person_id,\n champion_person_id}, first_impact_by?, beneficiary_person_id?, offer_id?,\n statement?}. Gaps fill from the standing record and MEDDIC; the record\n supersedes the standing one and earns its status from evidence. A\n critical event with a consequence plus a named beneficiary is what makes\n the deal a verified priority (VM4).\n- stakeholders: add/update/remove a deal stakeholder (`payload` =\n {action: add|update|remove, person_id?, role?, notes?}).\n- won / lost / stalled: record the deal outcome (`payload` optional:\n {loss_reason?, win_factors?}).\n- meeting_outcome: record how the meeting went (`payload` = the outcome\n fields).\n- reopen: reopen a closed deal (`payload` optional: {stage}).\n- create_pipeline: create a pipeline (no id; `payload` = {name,\n is_default?, position?, external_id?, stages?}).\n- rename_pipeline / archive_pipeline / unarchive_pipeline /\n set_default: `id` = pipeline UUID. Rename takes\n `payload={name}`; the others take no payload.\n- reorder_pipelines: no id; `payload={ordered_pipeline_ids}`.\n- create_stage: `id` = pipeline UUID; `payload={name, position?,\n stage_kind?, probability_default?, external_id?}`.\n- update_stage / archive_stage / unarchive_stage: `id` = pipeline\n UUID and `payload.stage_id` = stage UUID. Update also accepts name,\n stage_kind, and probability_default.\n- reorder_stages: `id` = pipeline UUID;\n `payload={ordered_stage_ids}`.\n\nBulk: `id` arrays apply the same action+payload per deal (server-side\nloop, per-item status — partial failure is reported per item, never\nsilent; max 100). Payloads are validated by the underlying route's own\nschema, so a bad field returns that route's precise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1053
1129
  "inputSchema": {
1054
1130
  "properties": {
1055
1131
  "action": {
@@ -1510,7 +1586,7 @@
1510
1586
  "openWorldHint": false,
1511
1587
  "readOnlyHint": false
1512
1588
  },
1513
- "description": "Manage Outreach\n\nStart and steer outreach plans — the consolidated outreach writer.\n\nActions:\n- start: enroll a person in outreach. id = PERSON UUID (scalar starts\n one plan; an id ARRAY routes to the native bulk-start endpoint).\n `payload` optional: {max_touches?, allowed_channels?} — applied to\n every started plan. Enrollment always schedules the first action\n immediately; a `start_immediately` key is ignored (forced true).\n For researched/discovered prospects, call manage_person action=save_discovered\n first with a strong current-company binding and\n start only when its response has `company_bound=true` and the quality\n gates passed. Never use start to bypass an unresolved or name-only\n company save.\n- plan_override: record a user override on a plan (id = plan UUID;\n `payload` = the override fields).\n- channels: legacy plan-local policy action. It returns 409\n `objective_policy_owned`; update the objective policy or use\n `plan_override` for one touch.\n- trigger_touch: queue the plan's next touch now (id = plan UUID; no\n payload). Refused with a structured 409 when the plan's pending\n approved email cannot send now: `email_daily_cap_reached` (the\n conversation's owning mailbox — or, for a first email, the sender\n pool — is at its daily cap; the error carries the capacity numbers,\n when capacity resets, and when the plan retries automatically) or\n `thread_mailbox_unavailable` (the owning mailbox is disconnected,\n resting, or excluded — reconnect it; another mailbox is never\n substituted into an existing conversation). A success here means the\n touch is genuinely queued, not that it may silently re-defer.\n- pause / resume: pause or resume a plan (id = plan UUID; `payload`\n is not needed).\n- update: legacy plan-local policy action. It returns 409\n `objective_policy_owned`; update the objective policy instead.\n- objective_create: create one draft objective (no id; payload carries\n name, goal_statement, target, cohort, and policy_envelope). It does not\n activate or send.\n- objective_update: edit a native draft/paused objective (id = objective\n UUID; payload requires expected_planning_version and carries any of\n name, goal_statement, target, cohort, or policy_envelope). Required\n rates are the one active-objective edit:\n {<metric>: {min_rate: 0-1, min_rate_basis: '<arithmetic>'}}\n for contacted_to_reply | contacted_to_meeting | meeting_to_won, merged\n into target.required_rates (a metric set to null clears it). It is the\n viability loop's first seed basis (VRU-846); never an industry average.\n- objective_members: assign or unassign people (id = objective UUID;\n payload = {action: assign|unassign, person_ids: [...]}, max 100).\n- objective_pin: set one claim-backed hard constraint (id = objective\n UUID; payload = {pin_key, pin_value, statement?}).\n- objective_unpin: retire one pin and its Claim (id = objective UUID;\n payload = {pin_key, reason}).\n- objective_forecast: read the current derived path distribution, weekly\n channel load, target math, five sample plans, commitments, abstentions,\n and prior-forecast diff (id = objective UUID; no payload).\n- objective_retire: end an objective for good (id = objective UUID; no\n payload); its runnable plans stop under objective_retired. Works the\n same for every objective — a former campaign mirror is an ordinary\n objective now.\n- objective_clone: a new draft with the objective's inputs and live pins\n (id = objective UUID; optional payload {name}); members are not\n copied. Works the same for every objective.\n- objective_pause: pause an objective (id = objective UUID; no payload).\n Its runnable plans park under the objective's own stop reason;\n relaunch through forecast and approval restores exactly those. Works\n the same for every objective.\n- objective_launch: propose the review-class launch Action over the exact\n current fingerprint (id = objective UUID; no payload). It does not\n activate or send.\n- objective_approve: accept that launch Action after a fresh fingerprint\n check (id = objective UUID; payload = {action_id}). A changed forecast\n abandons the action and returns 409. Approval activates the objective,\n enrolls eligible members, and schedules execution under its policy.\n and enqueues the executor, which enrolls the forecast's planned members\n through the research and supply gates; sends then follow the normal\n draft-and-approve path.\n- objective_sourcing_plan: calculate the exact cohort size, live provider\n order, bounded cost and candidate limits, and blocking reasons.\n- objective_source: propose a review Action for that exact sourcing plan\n (id = objective UUID). It does not import anyone.\n- objective_source_approve: approve the sourcing Action after a fresh\n fingerprint check (id = objective UUID; payload = {action_id}). Approval\n enqueues one bounded cohort build. Only people who pass the existing\n score gate join the objective.\n- objective_apply_template: render a touch's template over the\n objective's UNAUTHORED (needs_draft) rows for that touch —\n deterministic substitution of the operator's own template prose\n ({{first_name}}, {{last_name}}, {{company}}, {{title}}), never an LLM\n (id = objective UUID; payload = {step, cursor?}). BATCHED: processes up\n to 500 rows per call and returns {rendered, skipped (first 50),\n skipped_total, skipped_counts, total_unauthored, has_more,\n next_cursor}. While has_more is true you MUST call again with\n payload {step, cursor: <next_cursor>} — repeating without the cursor\n restarts from the beginning and skipped rows block the tail.\n Unrenderable rows are skipped with the reason and stay unauthored\n (missing variable value, channel outside the step's chain,\n email/InMail template without a subject). Set the template first via\n objective_update: a policy_envelope sequence step gains\n {\"template\": {\"subject\"?, \"body\"}}.\n- objective_touch_asset: attach or remove one reusable asset for one\n touch channel (id = objective UUID; payload = {step, channel,\n asset_id} referencing a library entry uploaded through the objective\n assets route; or {step, channel, remove: true}). No inline upload or\n Drive-source fields are accepted. Assets are supported on email,\n linkedin_message, and linkedin_inmail only.\n- objective_delegate: let one member manage every current and future\n objective for a sender (no id; payload = {sender_user_id,\n delegate_user_id}). The sender, owner, or operator can grant access.\n- objective_revoke_delegation: revoke that grant immediately (id =\n delegation UUID). The sender, owner, or operator can revoke access.\n\nBulk: `start` arrays hit the native bulk endpoint; pause/resume/\nplan_override/channels/trigger_touch arrays loop with per-item status —\npartial failure is reported per item, never silent (max 100). Payloads\nare validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1589
+ "description": "Manage Outreach\n\nStart and steer outreach plans — the consolidated outreach writer.\n\nActions:\n- start: enroll a person in outreach. id = PERSON UUID (scalar starts\n one plan; an id ARRAY routes to the native bulk-start endpoint).\n `payload` optional: {max_touches?, allowed_channels?} — applied to\n every started plan. Enrollment always schedules the first action\n immediately; a `start_immediately` key is ignored (forced true).\n For researched/discovered prospects, call manage_person action=save_discovered\n first with a strong current-company binding and\n start only when its response has `company_bound=true` and the quality\n gates passed. Never use start to bypass an unresolved or name-only\n company save.\n- plan_override: record a user override on a plan (id = plan UUID;\n `payload` = the override fields).\n- channels: legacy plan-local policy action. It returns 409\n `objective_policy_owned`; update the objective policy or use\n `plan_override` for one touch.\n- trigger_touch: queue the plan's next touch now (id = plan UUID; no\n payload). Refused with a structured 409 when the plan's pending\n approved email cannot send now: `email_daily_cap_reached` (the\n conversation's owning mailbox — or, for a first email, the sender\n pool — is at its daily cap; the error carries the capacity numbers,\n when capacity resets, and when the plan retries automatically) or\n `thread_mailbox_unavailable` (the owning mailbox is disconnected,\n resting, or excluded — reconnect it; another mailbox is never\n substituted into an existing conversation). A success here means the\n touch is genuinely queued, not that it may silently re-defer.\n- pause / resume: pause or resume a plan (id = plan UUID; `payload`\n is not needed).\n- update: legacy plan-local policy action. It returns 409\n `objective_policy_owned`; update the objective policy instead.\n- objective_create: create one draft objective (no id; payload carries\n name, goal_statement, target, cohort, and policy_envelope). It does not\n activate or send.\n- objective_setup_context: read Architect inputs (id=objective).\n- objective_setup_list: read saved setup reviews, optionally filtered by action_id.\n- objective_setup_propose: persist SetupProposalRequest for review; no configuration writes.\n- objective_setup_prepare: explicitly invoke the Architect and persist its setup review.\n- objective_setup_apply: apply the saved action_id and expected_version. Saves\n only configuration atomically, never sourcing, enrollment, launch, or sending.\n- objective_setup_reject: reject the saved action_id and expected_version without applying it.\n- objective_update: edit a native draft/paused objective (id = objective\n UUID; payload requires expected_planning_version and carries any of\n name, goal_statement, target, cohort, or policy_envelope). Required\n rates are the one active-objective edit:\n {<metric>: {min_rate: 0-1, min_rate_basis: '<arithmetic>'}}\n for contacted_to_reply | contacted_to_meeting | meeting_to_won, merged\n into target.required_rates (a metric set to null clears it). It is the\n viability loop's first seed basis (VRU-846); never an industry average.\n Without applicable observed conversion, a reviewed calibration pilot uses\n target.pilot = {max_contacts: 1-100, duration_days: 1-28,\n assumed_conversion_rate: >0 and <=1, assumption_basis, measurement_plan}.\n Preserve the saved target fields. A pilot also needs an executable\n sequence saved through objective_pin; target.pilot.claim_id is unsupported.\n- objective_audience: read paginated accounts or people (id = objective UUID;\n payload = {view: \"accounts\"|\"people\", query?, status?, company_id?,\n unresolved?, limit?, offset?}). Counts and account outreach are projections.\n- objective_audience_build: read the deterministic audience volume plan,\n required research facts, and latest saved sourcing run (id = objective UUID;\n optional payload = {pilot_size: 1-100}). Does not attach CRM people,\n invoke a model, or start providers. A pilot is not a meeting forecast.\n Includes brief_context_fingerprint and research_brief_stale for reviewed briefs.\n fact_definitions lists workspace definitions with description, subject_type,\n and answer_schema. For an equivalent public question, copy these as\n fact_definition, subject_type and answer_schema in the reviewed brief.\n Leave fact_definition unset for private conversations and interpretations.\n- objective_research_brief: generate commercial research questions for review\n (id = objective UUID; payload = {expected_planning_version, guidance?}).\n Uses a budgeted model; never saves, researches prospects, assigns, or sends.\n Review/edit the proposal before objective_update saves cohort.research_brief;\n preserve other cohort fields and the proposal's expected_planning_version.\n Your own agent can instead draft the same typed brief using objective and\n company context, with brief_context_fingerprint from objective_audience_build.\n- objective_account: select or deselect an existing CRM account (id = objective\n UUID; payload = {company_id, selected: bool}). Accounts may have zero contacts.\n Selection is planning context, not enrollment or a sourcing restriction.\n Read selected accounts with objective_audience before proposing named-account\n sourcing. Never enroll every employee automatically.\n- objective_members: assign or unassign people (id = objective UUID;\n payload = {action: assign|unassign, person_ids: [...]}, max 100).\n- objective_pin: set one claim-backed hard constraint (id = objective\n UUID; payload = {pin_key, pin_value, statement?}) on a draft/paused objective.\n Creates and links the backing Claim automatically. For an outreach sequence,\n use pin_key=\"sequence\", pin_value=[{step: 1, delay_days: 0,\n channels: [{channel: \"linkedin_connection\"}], instructions: \"...\"}, ...],\n and statement=the sequence rationale. Steps need unique positive step\n numbers and non-empty channels; delay_days and instructions are optional.\n A standalone Claim, verdict, or tone/drafting brief does not save a sequence.\n For a bare connection request, separately pin connection_request_no_note=true.\n Repeating a live pin_key returns the existing pin; it does not replace it.\n- objective_unpin: retire one pin and its Claim (id = objective UUID;\n payload = {pin_key, reason}).\n- objective_forecast: read the current derived path distribution, weekly\n channel load, target math, five sample plans, commitments, abstentions,\n and prior-forecast diff (id = objective UUID; no payload).\n Resolve blocking_reasons before objective_launch. pilot_sequence_claim_required\n means save the reviewed sequence and rationale through objective_pin.\n- objective_retire: end an objective for good (id = objective UUID; no\n payload); its runnable plans stop under objective_retired. Works the\n same for every objective — a former campaign mirror is an ordinary\n objective now.\n- objective_clone: a new draft with the objective's inputs and live pins\n (id = objective UUID; optional payload {name}); members are not\n copied. Works the same for every objective.\n- objective_pause: pause an objective (id = objective UUID; no payload).\n Its runnable plans park under the objective's own stop reason;\n relaunch through forecast and approval restores exactly those. Works\n the same for every objective.\n- objective_observe: propose a measurement review for an observe objective (id = objective UUID; payload = source_bindings and client_request_id). No sender, research, enrollment, or outreach.\n- objective_observe_approve: approve only after the user reviews and confirms the observation definition and sources (payload = action_id and expected_version). This freezes measurement, not execution permissions.\n- objective_cohort_revise: record an explicit, evidenced admission or correction on an approved run (payload = run_action_id and revision). Corrections preserve the previous report and need the user's approval.\n- objective_launch: propose the review-class launch Action over the exact\n current fingerprint (id = objective UUID; no payload). It does not\n activate or send. Requires a ready objective_forecast. For\n pilot_sequence_claim_required, use objective_pin with pin_key=\"sequence\",\n then refresh the forecast. Separate claims/verdicts cannot clear this blocker;\n changing the target metric or pilot duration cannot save the missing sequence.\n- objective_approve: accept that launch Action after a fresh fingerprint\n check (id = objective UUID; payload = {action_id}). A changed forecast\n abandons the action and returns 409. Approval activates the objective,\n enrolls eligible members, and schedules execution under its policy.\n and enqueues the executor, which enrolls the forecast's planned members\n through the research and supply gates; sends then follow the normal\n draft-and-approve path.\n- objective_sourcing_plan: calculate the exact cohort size, live provider\n order, bounded cost and candidate limits, and blocking reasons. Optional\n payload {pilot_size: 1-100} requests an explicit pilot audience size including\n existing eligible members, without an assumed or observed conversion rate.\n- objective_source: propose a review Action for that exact sourcing plan\n (id = objective UUID; optional payload {pilot_size: 1-100}). First checks\n existing live sourcing work; otherwise checks CRM imports and attaches fits, then sizes additional sourcing.\n No new discovery starts until approval. A pilot is one bounded sourcing run,\n not an outreach launch or a forecast of replies. Existing launch gates apply.\n- objective_source_approve: approve the sourcing Action after a fresh\n fingerprint check (id = objective UUID; payload = {action_id}). Approval\n enqueues one bounded cohort build. Only people who pass the existing\n objective fit checks join the objective.\n- objective_sourcing_run: read saved preparation counts and recovery by action_id.\n- objective_sourcing_stop: stop further work with action_id and expected_version; saved results remain.\n- objective_sourcing_recovery: read saved failure phase, resume eligibility,\n and current reviewed_inputs (id = objective UUID; payload = {job_id}).\n- objective_sourcing_resume: explicitly approve recovery of saved work\n (id = objective UUID; payload = {job_id, reviewed_inputs}). Copy reviewed_inputs\n from the recovery read. Changed inputs renew fit on saved candidates without\n discovery or enrichment; the rubric model may run. Unknown provider outcomes\n require reconciliation and cannot be overridden with resume.\n- objective_apply_template: render a touch's template over the\n objective's UNAUTHORED (needs_draft) rows for that touch —\n deterministic substitution of the operator's own template prose\n ({{first_name}}, {{last_name}}, {{company}}, {{title}}), never an LLM\n (id = objective UUID; payload = {step, cursor?}). BATCHED: processes up\n to 500 rows per call and returns {rendered, skipped (first 50),\n skipped_total, skipped_counts, total_unauthored, has_more,\n next_cursor}. While has_more is true you MUST call again with\n payload {step, cursor: <next_cursor>} — repeating without the cursor\n restarts from the beginning and skipped rows block the tail.\n Unrenderable rows are skipped with the reason and stay unauthored\n (missing variable value, channel outside the step's chain,\n email/InMail template without a subject). Set the template first via\n objective_update: a policy_envelope sequence step gains\n {\"template\": {\"subject\"?, \"body\"}}.\n- objective_touch_asset: attach or remove one reusable asset for one\n touch channel (id = objective UUID; payload = {step, channel,\n asset_id} referencing a library entry uploaded through the objective\n assets route; or {step, channel, remove: true}). No inline upload or\n Drive-source fields are accepted. Assets are supported on email,\n linkedin_message, and linkedin_inmail only.\n- objective_delegate: let one member manage every current and future\n objective for a sender (no id; payload = {sender_user_id,\n delegate_user_id}). The sender, owner, or operator can grant access.\n- objective_revoke_delegation: revoke that grant immediately (id =\n delegation UUID). The sender, owner, or operator can revoke access.\n\nBulk: `start` arrays hit the native bulk endpoint; pause/resume/\nplan_override/channels/trigger_touch arrays loop with per-item status —\npartial failure is reported per item, never silent (max 100). Payloads\nare validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1514
1590
  "inputSchema": {
1515
1591
  "properties": {
1516
1592
  "action": {
@@ -1580,7 +1656,7 @@
1580
1656
  "openWorldHint": false,
1581
1657
  "readOnlyHint": false
1582
1658
  },
1583
- "description": "Manage Person\n\nCreate and manage people in your pipeline — the consolidated person writer.\n\nActions (id = person UUID unless noted):\n- create: add a person manually (no id; `payload` = the manual-person\n fields, e.g. {name, company, title, email, linkedin_url, skip_research});\n bulk via `items` (one payload per person).\n- update_contact: override contact fields (`payload` = contact overrides).\n- set_position: set the primary position (`payload` = position fields).\n- note: add a timeline note (`payload` = {body}).\n- log_interaction: log a manual touch that happened outside an automated\n sequence — a call (with disposition + what was said), or an\n email/LinkedIn/other touch made elsewhere. `payload` =\n {interaction_kind: call|email|linkedin|meeting|other, direction?:\n outbound|inbound, summary?, disposition? (calls only), occurred_at?\n (ISO, backdatable), deal_id?}. Lands on the person's timeline attributed\n to you (performed_by_user_id).\n- archive: soft-delete one or many people — stops outreach plans and\n rejects pending drafts (id or id array; no payload). DESTRUCTIVE.\n- unarchive: restore archived people (id or id array; no payload).\n- meeting_booked: mark a meeting booked with this person (`payload` =\n {idempotency_key: UUID}; reuse it when retrying the same booking).\n- save_discovered: THE creator — save a researched prospect into the\n pipeline, atomically (no id; bulk via `items`). Two shapes:\n NEW prospect: `payload` = {person: {first_name, last_name,\n linkedin_url|email, company_id (from research action=save_company) or\n company_name plus company_domain/company_website/company_linkedin_url,\n ...research fields}, assessment: {match_score\n 0-100, match_summary, alignment_points?, concerns?, why_now?,\n recommended_approach?, overall_confidence?, scored_by?}, objective_id or\n assessment_objective_id}. Person + research + pipeline membership land\n in ONE transaction — a rejected or failed save persists nothing.\n EXISTING person: `payload` = {person_id, company_id, objective_id?,\n assessment_objective_id?, assessment?}; if company_id is unavailable,\n pass company_name plus at least one top-level company_domain,\n company_website, or company_linkedin_url. Never send a bare person_id:\n the binding atomically adds/promotes the current position and pins the\n pipeline membership while the assessment is applied update-in-place\n (THE path to score an existing stub). Pass exactly one of `person` /\n `person_id`. Use assessment_objective_id to preserve\n objective-scoring provenance without assigning the person. Your\n assessment is AUTHORITATIVE: the backend records it with provenance and\n never re-scores it. Backend floor stays mechanical: dedupe first\n (anchors resolving to an existing person continue as a duplicate\n update); new rows require a LinkedIn URL or email and stay within the\n per-tenant discovery-volume cap; the `person` shape requires\n `assessment` + an objective ref (did no fit analysis? use action=create\n instead). Duplicates do not consume or re-check the new-row cap. The\n response includes `company_id` and `company_bound`; require\n `company_bound=true` before treating the save as company-complete or\n calling manage_outreach action=start. If false, do not enroll—repair the\n company binding and retry save_discovered.\n- set_persona: set a contact's buying-role persona — economic_buyer /\n decision_maker / influencer / unknown (`payload` = {persona,\n reasoning?}). The harness classifies buying role at touch time and\n writes it here; there is no backend classifier.\n- adopt_directory: add an existing shared canonical person to this tenant's\n CRM (id = directory person UUID; no payload). This copies no tenant-private\n notes, activity, objective, score, or outreach state. It is free for the\n tenant.\n\nBulk: `id` arrays apply the same action to each person (archive/unarchive\nroute to the native bulk endpoints; others loop with per-item status —\npartial failure is reported per item, never silent). `items` is for\ncreate-like actions, max 100 per call. Payloads are validated by the\nunderlying route's own schema, so a bad field returns that route's\nprecise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1659
+ "description": "Manage Person\n\nCreate and manage people in your pipeline — the consolidated person writer.\n\nActions (id = person UUID unless noted):\n- create: add a person manually (no id; `payload` = the manual-person\n fields, e.g. {name, company, title, email, linkedin_url, skip_research});\n bulk via `items` (one payload per person).\n- update_contact: override contact fields (`payload` = contact overrides).\n- set_position: set the primary position (`payload` = position fields).\n- note: add a timeline note (`payload` = {body}).\n- log_interaction: log a manual touch that happened outside an automated\n sequence — a call (with disposition + what was said), or an\n email/LinkedIn/other touch made elsewhere. `payload` =\n {interaction_kind: call|email|linkedin|meeting|other, direction?:\n outbound|inbound, summary?, disposition? (calls only), occurred_at?\n (ISO, backdatable), deal_id?}. Lands on the person's timeline attributed\n to you (performed_by_user_id).\n- archive: soft-delete one or many people — stops outreach plans and\n rejects pending drafts (id or id array; no payload). DESTRUCTIVE.\n- unarchive: restore archived people (id or id array; no payload).\n- meeting_booked: mark a meeting booked with this person (`payload` =\n {idempotency_key: UUID}; reuse it when retrying the same booking).\n- save_discovered: THE creator — save a researched prospect into the\n pipeline, atomically (no id; bulk via `items`). Two shapes:\n NEW prospect: `payload` = {person: {first_name, last_name,\n linkedin_url|email, company_id (from research action=save_company) or\n company_name plus company_domain/company_website/company_linkedin_url,\n ...research fields}, assessment: {match_score\n 0-100, match_summary, alignment_points?, concerns?, why_now?,\n recommended_approach?, overall_confidence?, scored_by?}, objective_id or\n assessment_objective_id}. Person + research + pipeline membership land\n in ONE transaction — a rejected or failed save persists nothing.\n EXISTING person: `payload` = {person_id, company_id, objective_id?,\n assessment_objective_id?, assessment?}; if company_id is unavailable,\n pass company_name plus at least one top-level company_domain,\n company_website, or company_linkedin_url. Never send a bare person_id:\n the binding atomically adds/promotes the current position and pins the\n pipeline membership while the assessment is applied update-in-place\n (THE path to score an existing stub). Pass exactly one of `person` /\n `person_id`. Use assessment_objective_id to preserve\n objective-scoring provenance without assigning the person. Your\n assessment is AUTHORITATIVE: the backend records it with provenance and\n never re-scores it. Backend floor stays mechanical: dedupe first\n (anchors resolving to an existing person continue as a duplicate\n update); new rows require a LinkedIn URL or email and stay within the\n per-tenant discovery-volume cap; the `person` shape requires\n `assessment` + an objective ref (did no fit analysis? use action=create\n instead). Duplicates do not consume or re-check the new-row cap. The\n response includes `company_id` and `company_bound`; require\n `company_bound=true` before treating the save as company-complete or\n calling manage_outreach action=start. If false, do not enroll—repair the\n company binding and retry save_discovered.\n- set_persona: set a contact's buying-center role (Revenue Architecture\n Table 8.2) — initiator / user / champion / decider / gatekeeper /\n influencer / executive_buyer / approver / purchaser / unknown\n (`payload` = {persona, reasoning?}).\n- set_meeting_purpose: say what a meeting was for (`id` = the meeting id,\n or `li:<interaction id>` for a logged meeting; `payload` = {purpose}\n — discovery / demo / proposal / negotiation / commit / kickoff /\n impact_review / renewal / expansion / winback / internal / other, or\n null to clear). The seller's word outlives the calendar title; a held\n meeting's timeline event moves to the new purpose's practice. The harness classifies buying role at touch time and\n writes it here; there is no backend classifier.\n- adopt_directory: add an existing shared canonical person to this tenant's\n CRM (id = directory person UUID; no payload). This copies no tenant-private\n notes, activity, objective, score, or outreach state. It is free for the\n tenant.\n\nBulk: `id` arrays apply the same action to each person (archive/unarchive\nroute to the native bulk endpoints; others loop with per-item status —\npartial failure is reported per item, never silent). `items` is for\ncreate-like actions, max 100 per call. Payloads are validated by the\nunderlying route's own schema, so a bad field returns that route's\nprecise 422.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1584
1660
  "inputSchema": {
1585
1661
  "properties": {
1586
1662
  "action": {
@@ -1835,7 +1911,7 @@
1835
1911
  "openWorldHint": true,
1836
1912
  "readOnlyHint": false
1837
1913
  },
1838
- "description": "Manage Revenue\n\nManage the tenant's Stripe revenue connection.\n\nActions:\n- connect: return the Stripe App OAuth URL ({destination, livemode}).\n- reconcile: enqueue an idempotent provider scan ({idempotency_key,\n object_family}).\n- replay: retry one failed evidence record (id = evidence record UUID).\n- decide_binding: confirm or reject one suggestion (id = binding UUID,\n payload = {decision}).\n- disconnect_preview: return a short-lived nonce and exact retention impact.\n- disconnect: destroy delegated credentials using that nonce\n ({preview_nonce}); historical ledger evidence is retained.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1914
+ "description": "Manage Revenue\n\nManage revenue connections and explicitly reviewed project finance facts.\n\nActions:\n- service_margin_statement: record an explicitly reviewed finance statement\n (journey, closed period, earned-revenue/cost lines, canonical labor IDs,\n source documents, coverage, confirmation; corrections name the prior receipt).\n- connect: return the Stripe App OAuth URL ({destination, livemode}).\n- reconcile: enqueue an idempotent provider scan ({idempotency_key,\n object_family}).\n- replay: retry one failed evidence record (id = evidence record UUID).\n- decide_binding: confirm or reject one suggestion (id = binding UUID,\n payload = {decision}).\n- disconnect_preview: return a short-lived nonce and exact retention impact.\n- disconnect: destroy delegated credentials using that nonce\n ({preview_nonce}); historical ledger evidence is retained.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1839
1915
  "inputSchema": {
1840
1916
  "properties": {
1841
1917
  "action": {
@@ -1975,7 +2051,7 @@
1975
2051
  "openWorldHint": true,
1976
2052
  "readOnlyHint": false
1977
2053
  },
1978
- "description": "Research\n\nRun prospect/company research and save the results — the consolidated research tool.\n\nLookup actions (results are returned, not stored unless noted):\n- find_linkedin: locate a person's LinkedIn URL (id = person UUID;\n `payload` optional {name_override?, company_override?, auto_save?} —\n auto_save stores a high-confidence match).\n- enrich_account: acquire a bounded first-party evidence bundle for a\n tenant-visible account (id = canonical company UUID). The response also\n carries canonical company fields, cached shared research, and freshness\n metadata for the harness to combine with its own web and MCP sources.\n Persist sourced conclusions with save_company. The legacy enrich_company\n action remains the domain/page website-fetch primitive.\n- find_email: find a work email (`payload` = {first_name, last_name,\n domain}).\n- linkedin_fetch: pull a LinkedIn profile (`payload` = {linkedin_url,\n include_company?, include_posts?, posts_limit?}).\n- enrich_person: enrich each person according to missing contact and profile\n fields (id = person UUID, or an array of up to 100;\n `payload` optional {force_refresh?}). Returns a job_id; poll it with\n fetch type=job. Outcomes report a stable reason code, and jobs with any\n errored outcome terminate as completed_with_failures. Successful outcomes\n report readiness=email_ready, incomplete, or manual_review plus the\n canonical employer binding.\n Vruum applies separate monthly profile and Hunter-credit soft limits.\n Harness web search and other MCP tools remain complementary for deeper\n research. The legacy enrich_profile name is an alias.\n- enrich: canonical scoped enrichment. It inspects each person and runs\n only the needed profile or verified-email work. Pass `payload` with exactly one of\n `{person_ids: [...]}`, `{objective_id: ...}`, or `{list_id: ...}`.\n Returns a job_id; poll it with fetch type=job. The enrich_emails action\n remains as a compatibility alias for the same scoped planner.\n- careers_parse: parse a careers page for hiring signals (`payload` =\n {domain}).\n\nSave actions (write research rows):\n- save_person: UPDATE research on an EXISTING person — pass the person UUID\n as `payload.person_id` (preferred; refreshes researched_at and the\n research fields in place), or matching email/linkedin anchors. Cannot\n create: new prospects go through manage_person action=save_discovered\n with `payload.person` + `payload.assessment`. Never pass the person UUID\n as `id` — this action takes no id.\n- save_company: atomically patch sourced public company research. Pass\n `payload={idempotency_key, name, industry, person_id?, website?, linkedin_url?,\n company_summary?, company_stage?, current_priorities?, funding_data?,\n growth_metrics?, sources_by_field}`.\n `industry` is required and must be one CanonicalIndustry value:\n Agriculture, Construction, Consumer Goods, Consumer Services, Education,\n Energy & Utilities, Financial Services, Government, Healthcare,\n Hospitality & Travel, Legal Services, Manufacturing, Marketing & Advertising,\n Media & Entertainment, Nonprofit, Professional Services, Real Estate,\n Retail & E-commerce, Software & Technology, Staffing & Recruiting,\n Telecommunications, Transportation & Logistics, Wholesale & Distribution,\n or Other. Provider labels and Unspecified are rejected.\n Omitted research fields are preserved; explicit null clears. When\n researching a specific person's employer,\n ALWAYS pass their UUID as `payload.person_id` — the response's\n `person_link` confirms the write landed on that person's linked company\n (repointing an anchorless duplicate link when needed); a `mismatch`\n status means the person's touches will NOT see this research, and a\n `conflict` status is a transient race — replay the identical payload\n (person_id is exempt from the idempotency hash, so adding it to a\n replay is the supported repair).\n For a researched-prospect save, save_company is also the company identity resolution step.\n Reuse a trustworthy Vruum company_id when already known; otherwise call\n save_company using name plus website and/or linkedin_url. Call it even\n with no new research fields, and capture the returned company_id for\n manage_person action=save_discovered. A company name alone is not a save identity.\n `sources_by_field` must contain exactly every\n supplied non-null research field, with entries\n `{url, title?, observed_at}`. Example:\n `{\"idempotency_key\":\"pipeline/example.com/save-v1\",\"name\":\"Example\",\n \"industry\":\"Software & Technology\",\"website\":\"https://example.com\",\n \"company_summary\":\"Example sells …\",\n \"sources_by_field\":{\"company_summary\":[{\"url\":\"https://example.com/about\",\n \"title\":\"About\",\"observed_at\":\"2026-07-30T20:00:00Z\"}]}}`.\n\nHypothesis-ledger actions (VRU-840 — beliefs with scoreboards, never prose):\n- save_claim: record one falsifiable belief. `payload` = {statement,\n scope ({person_id|company_id|deal_id|objective_id|segment|tenant}),\n origin (diagnostic|system|operator|pin|experiment),\n invalidating_conditions?: [..], evidence?: [{kind, payload, source,\n method?, staleness_class (durable|slow|fast|volatile),\n trust? (first_party|untrusted_capture|third_party), stance?\n (supports|contradicts), weight?, person_id?|company_id?|\n deal_id?|objective_ref?|tenant_scoped? (accounts are the tenant-company\n pair — link company_id)}]}. Confidence is derived from the\n linked evidence and cannot be asserted; identical evidence payloads\n dedupe. `origin='diagnostic'` requires non-empty evidence and\n invalidating_conditions, so a diagnostic cannot save unsupported prose.\n Positioning-diagnostic scopes (`scope.surface='positioning_diagnostic'`)\n always need evidence + invalidating_conditions (any origin) and must\n carry tenant=true, a `dimension`, a trimmed `segment`, and — for\n icp_reachability / price_plausibility / motion_fit only — a cohort\n `objective_id` anchor plus a structured\n `scope.test` ({version:'viability_test.v1', metric, comparator,\n threshold, min_sample>=20, window_days}) that the viability loop\n evaluates from spine outcomes. Returns the claim with its full\n scoreboard. Use `items` to save a batch (e.g. a positioning\n diagnostic's claim set).\n- save_evidence: mint one observation with provenance, optionally linking\n it to an existing claim (`payload.claim_id` + stance/weight).\n- record_claim_verdict: append a verdict (id = claim UUID; `payload` =\n {status, basis, confidence?, note?, experiment_id?, superseded_by?}).\n basis='causal' requires experiment_id — the laboratory honesty rule.\n A `retired` verdict may name `superseded_by` (the LIVE replacement\n claim's UUID) so readers can follow a retired scope to its successor;\n any non-retired verdict clears the pointer.\nRead claims with search type='claims' and fetch type='claim'.\n\nBatch research returns per-item results (partial failure is reported per\nitem, never silent). The facade's backward-compatible request cap is 100;\nenrich_account accepts up to 10 accounts per request. Operators should execute the smaller policy waves\ndocumented by pipeline-fill (10 companies or 5 people at a time) so\nprogress and retries stay bounded. find_linkedin also takes person-id\narrays. Payloads are validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
2054
+ "description": "Research\n\nRun prospect/company research and save the results — the consolidated research tool.\n\n- preview_gaps: READ ONLY assignment, no research or spend. id=objective UUID;\n payload={subjects:[{type,person_id?,company_id?,deal_id?}], question_ids?,\n stage?, use_public_web?:true, use_history?:false}. Up to 20 subjects and\n 40 unique gaps. Reuses current account answers across people.\n Equivalent reviewed fact definitions also reuse original observations across\n objectives when their subject, schema, sources, quality and freshness match.\n Reused answers retain original evidence IDs; do not save duplicate copies.\n Read gaps, question definitions, source permissions and limits before choosing work.\n- stop_gaps: id=objective UUID, payload={action_id}. Stop future calls and\n answer writes. Already sent provider work can finish.\n- propose_gaps: same input; saves an Action without executing research.\n- approve_gaps: id=objective UUID, payload={action_id}. Only after the user\n approves that exact saved assignment. Starts bounded Vruum research.\n Poll fetch research_run id=action UUID. Never repeat approval to retry.\n For research on YOUR compute, use preview_gaps, fetch research_sources,\n your permitted tools, then save_answer. Do not approve Vruum research as\n a substitute for external research. Your harness pays its own compute.\n Use the exact subject, question ID and definition fingerprint from the\n assignment. Public URL excerpts remain reported. Never assert customer\n confirmation without an explicit operator attestation.\n- save_answer: record one answer for a saved objective research question\n (id = objective UUID; payload = {idempotency_key, question_id,\n definition_fingerprint, subject: {type, person_id?, company_id?, deal_id?},\n state: answered|unknown|unavailable, value, source: {kind, reference?,\n excerpt, observed_at}, note?, supersedes_evidence_id?}). Read the question\n and answer_schema through fetch research_answers first. Source kinds:\n operator_report, public_url (reported only), activity, evidence, meeting,\n customer_confirmation (explicit operator attestation). Existing references\n must match the tenant, subject, excerpt and original observation time.\n Exact retries retain their UUID; changed content needs a new UUID.\n Corrections explicitly supersede one observation. Answers never authorize\n outreach. Generic save_evidence/save_claim cannot write research answers.\n\nLookup actions (results are returned, not stored unless noted):\n- find_linkedin: locate a person's LinkedIn URL (id = person UUID;\n `payload` optional {name_override?, company_override?, auto_save?} —\n auto_save stores a high-confidence match).\n- enrich_account: acquire a bounded first-party evidence bundle for a\n tenant-visible account (id = canonical company UUID). The response also\n carries canonical company fields, cached shared research, and freshness\n metadata for the harness to combine with its own web and MCP sources.\n Persist sourced conclusions with save_company. The legacy enrich_company\n action remains the domain/page website-fetch primitive.\n- find_email: find a work email (`payload` = {first_name, last_name,\n domain}).\n- linkedin_fetch: pull a LinkedIn profile (`payload` = {linkedin_url,\n include_company?, include_posts?, posts_limit?}).\n- enrich_person: enrich each person according to missing contact and profile\n fields (id = person UUID, or an array of up to 100;\n `payload` optional {force_refresh?}). Returns a job_id; poll it with\n fetch type=job. Outcomes report a stable reason code, and jobs with any\n errored outcome terminate as completed_with_failures. Successful outcomes\n report readiness=email_ready, incomplete, or manual_review plus the\n canonical employer binding.\n Vruum applies separate monthly profile and Hunter-credit soft limits.\n Harness web search and other MCP tools remain complementary for deeper\n research. The legacy enrich_profile name is an alias.\n- enrich: canonical scoped enrichment. It inspects each person and runs\n only the needed profile or verified-email work. Pass `payload` with exactly one of\n `{person_ids: [...]}`, `{objective_id: ...}`, or `{list_id: ...}`.\n Returns a job_id; poll it with fetch type=job. The enrich_emails action\n remains as a compatibility alias for the same scoped planner.\n- careers_parse: parse a careers page for hiring signals (`payload` =\n {domain}).\n\nSave actions (write research rows):\n- save_person: UPDATE research on an EXISTING person — pass the person UUID\n as `payload.person_id` (preferred; refreshes researched_at and the\n research fields in place), or matching email/linkedin anchors. Cannot\n create: new prospects go through manage_person action=save_discovered\n with `payload.person` + `payload.assessment`. Never pass the person UUID\n as `id` — this action takes no id.\n- save_company: atomically patch sourced public company research. Pass\n `payload={company_id?, allow_create?, idempotency_key, name, industry, person_id?, website?, linkedin_url?,\n company_summary?, company_stage?, current_priorities?, funding_data?,\n growth_metrics?, sources_by_field}`.\n `industry` is required and must be one CanonicalIndustry value:\n Agriculture, Construction, Consumer Goods, Consumer Services, Education,\n Energy & Utilities, Financial Services, Government, Healthcare,\n Hospitality & Travel, Legal Services, Manufacturing, Marketing & Advertising,\n Media & Entertainment, Nonprofit, Professional Services, Real Estate,\n Retail & E-commerce, Software & Technology, Staffing & Recruiting,\n Telecommunications, Transportation & Logistics, Wholesale & Distribution,\n or Other. Provider labels and Unspecified are rejected.\n Omitted research fields are preserved; explicit null clears. When\n researching a specific person's employer,\n ALWAYS pass their UUID as `payload.person_id` — the response's\n `person_link` confirms the write landed on that person's linked company\n (repointing an anchorless duplicate link when needed); a `mismatch`\n status means the person's touches will NOT see this research, and a\n `conflict` status is a transient race — replay the identical payload\n (person_id is exempt from the idempotency hash, so adding it to a\n replay is the supported repair).\n For a researched-prospect save, save_company is also the company identity resolution step.\n For an existing account, ALWAYS pass payload.company_id, including the ID\n returned by enrich_account. Updates never create or merge a company.\n Otherwise call save_company with allow_create=true plus name and website\n and/or linkedin_url. Never combine company_id with allow_create=true.\n Conflicts require identity reconciliation; never retry by dropping the ID.\n Identity selection, missing native identifiers, and research commit together.\n Call it even\n with no new research fields, and capture the returned company_id for\n manage_person action=save_discovered. A company name alone is not a save identity.\n `sources_by_field` must contain exactly every\n supplied non-null research field, with entries\n `{url, title?, observed_at}`. Example:\n `{\"allow_create\":true,\"idempotency_key\":\"pipeline/example.com/save-v1\",\"name\":\"Example\",\n \"industry\":\"Software & Technology\",\"website\":\"https://example.com\",\n \"company_summary\":\"Example sells …\",\n \"sources_by_field\":{\"company_summary\":[{\"url\":\"https://example.com/about\",\n \"title\":\"About\",\"observed_at\":\"2026-07-30T20:00:00Z\"}]}}`.\n\nHypothesis-ledger actions (VRU-840 — beliefs with scoreboards, never prose):\n- save_claim: record one falsifiable belief. `payload` = {statement,\n scope ({person_id|company_id|deal_id|objective_id|segment|tenant}),\n origin (diagnostic|system|operator|pin|experiment),\n invalidating_conditions?: [..], evidence?: [{kind, payload, source,\n method?, staleness_class (durable|slow|fast|volatile),\n trust? (first_party|untrusted_capture|third_party), stance?\n (supports|contradicts), weight?, person_id?|company_id?|\n deal_id?|objective_ref?|tenant_scoped? (accounts are the tenant-company\n pair — link company_id)}]}. Confidence is derived from the\n linked evidence and cannot be asserted; identical evidence payloads\n dedupe. `origin='diagnostic'` requires non-empty evidence and\n invalidating_conditions, so a diagnostic cannot save unsupported prose.\n Positioning-diagnostic scopes (`scope.surface='positioning_diagnostic'`)\n always need evidence + invalidating_conditions (any origin) and must\n carry tenant=true, a `dimension`, a trimmed `segment`, and — for\n icp_reachability / price_plausibility / motion_fit only — a cohort\n `objective_id` anchor plus a structured\n `scope.test` ({version:'viability_test.v1', metric, comparator,\n threshold, min_sample>=20, window_days}) that the viability loop\n evaluates from spine outcomes. Returns the claim with its full\n scoreboard. Use `items` to save a batch (e.g. a positioning\n diagnostic's claim set).\n- save_evidence: mint one observation with provenance, optionally linking\n it to an existing claim (`payload.claim_id` + stance/weight).\n- record_claim_verdict: append a verdict (id = claim UUID; `payload` =\n {status, basis, confidence?, note?, experiment_id?, superseded_by?}).\n basis='causal' requires experiment_id — the laboratory honesty rule.\n A `retired` verdict may name `superseded_by` (the LIVE replacement\n claim's UUID) so readers can follow a retired scope to its successor;\n any non-retired verdict clears the pointer.\nRead claims with search type='claims' and fetch type='claim'.\n\nBatch research returns per-item results (partial failure is reported per\nitem, never silent). The facade's backward-compatible request cap is 100;\nenrich_account accepts up to 10 accounts per request. Operators should execute the smaller policy waves\ndocumented by pipeline-fill (10 companies or 5 people at a time) so\nprogress and retries stay bounded. find_linkedin also takes person-id\narrays. Payloads are validated by the underlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1979
2055
  "inputSchema": {
1980
2056
  "properties": {
1981
2057
  "action": {
@@ -2054,6 +2130,18 @@
2054
2130
  "additionalProperties": false,
2055
2131
  "description": "List-mode filters for ``type='people'`` (routes to ``get_user_people``).\n\nMutually exclusive with ``query`` — pass one or the other, never both.",
2056
2132
  "properties": {
2133
+ "account_list": {
2134
+ "anyOf": [
2135
+ {
2136
+ "type": "string"
2137
+ },
2138
+ {
2139
+ "type": "null"
2140
+ }
2141
+ ],
2142
+ "description": "Comma-separated account list UUIDs. Only people with a known employer in any selected list.",
2143
+ "title": "Account List"
2144
+ },
2057
2145
  "custom": {
2058
2146
  "anyOf": [
2059
2147
  {
@@ -2114,7 +2202,7 @@
2114
2202
  "type": "null"
2115
2203
  }
2116
2204
  ],
2117
- "description": "Buying-role filter: influencer, decision_maker, economic_buyer, unknown, or 'unclassified' (no classification run yet).",
2205
+ "description": "Buying-center role filter: initiator, user, champion, decider, gatekeeper, influencer, executive_buyer, approver, purchaser, unknown, or 'unclassified' (no classification run yet).",
2118
2206
  "title": "Persona"
2119
2207
  },
2120
2208
  "relationship_type": {