Free & private ask avi. Start chatting
Working docs: Growth strategy - Supply campaign - Internal spec

Internal Operations Specification

Internal Operations Specification (Archived from agentic-system/spec.md)

This file contains requirements that were trimmed from the main openspec/specs/agentic-system/spec.md on 2026-07-14 as part of a seeker-scope simplification pass. They remain authoritative for the internal operations they govern but are not part of the seeker-facing spec. The main spec's Purpose section points here.

Archived: billing, marketing dashboard, web provider sourcing, provider metrics, peer referrals, volume pricing, re-engagement, email compliance, tracing, webhook idempotency, orchestration detail, skill-as-import, pointer capabilities, skill semver (now only in main spec for crisis gate), real-LLM testing, four-agent model (simplified to "sales is the only agent" in main spec).


Requirements

Requirement: Every outbound send passes through send-compliant-email

The send-compliant-email skill SHALL be the only path by which marketing (or any future agent) sends an email, and SHALL refuse to send when the recipient is suppressed, when consentBasis is missing or invalid, or when the lead's acuity is crisis. Suppression checks SHALL fail safe (treated as suppressed) on any data-access error.

Scenario: Kill switch is off (the default)

  • WHEN DEMAND_OUTREACH_SEND_ENABLED is unset or false
  • THEN send-compliant-email simulates and logs the send but never

calls the email provider

Requirement: Providers are billed only on booked appointments

The bill-on-booking skill SHALL charge a provider only when an appointment actually books - never on referral creation or contact alone

  • and SHALL record the charge on the referral in the same operation as

the booking outcome update.

Scenario: Referral never results in a booking

  • WHEN a referral's outcome never reaches "booked"
  • THEN bill-on-booking is never invoked for that referral and no

Stripe charge exists for it

Requirement: Unmatched demand may be sold to a provider as a lead, on a charge path kept separate from bill-on-booking

When match-provider finds no eligible provider for a non-crisis triage, that triage SHALL be persisted as open demand (shared/open-demand.ts) so it is not silently lost, and marketing MAY sell it to a provider via sell-demand-lead. This is a second, distinct charge mechanism from bill-on-booking, triggered by lead delivery rather than a booked appointment, and it SHALL NOT alter the "Providers are billed only on booked appointments" requirement above. A lead SHALL be sold at most once. Crisis-acuity demand SHALL NEVER be captured as open demand or sold as a lead.

Scenario: A non-crisis search matches no provider

  • WHEN match-provider returns an empty result for a non-crisis

triage

  • THEN that triage is recorded as an unclaimed open_demand row,

available for marketing to reverse-match and sell-demand-lead to charge a provider for

Scenario: A crisis search matches no provider

  • WHEN match-provider returns null for a crisis triage
  • THEN no open_demand row is written for it

Scenario: Two providers race to claim the same lead

  • WHEN sell-demand-lead is called twice for the same open_demand

row with two different provider accounts

  • THEN exactly one call charges and claims the lead; the other

returns { chargeId: null, billedCents: 0 } without charging

Requirement: Unmatched demand may be offered to a non-member prospect as a consented, time-boxed claim

When open demand remains unmatched, marketing MAY offer the lead to a not-yet-registered prospect as a warm, consented, time-boxed "claim this lead" invitation instead of (or in addition to) the generic free onboarding pitch (shared/claim-lead.ts). This is the demand-backed onboarding loop at the heart of the growth strategy (docs/ask-avi-growth-strategy.md). The offer SHALL:

  1. Reveal only non-identifying context in the email - type, region,

urgency, specialty keywords, and the private claim link. A seeker's name and contact SHALL NOT appear in the offer email; they unlock only when the claim is accepted.

  1. Be time-bound (claim expiry, default 24h) and never sold: the claiming

provider's first lead is free, and a claim SHALL NOT create a charge (the charge columns stay null - paid leads remain sell-demand-lead's path alone).

  1. Be bound to the offered-to address: redeeming SHALL require the

signed-in provider account's email to equal the address the offer was sent to, so a forwarded link reveals nothing.

  1. Be scoped to the offered lead: the unlaimed lead SHALL be sold/claimed

at most once, whether via sell-demand-lead or a claim, using the same claimed_by_provider_account_id=is.null compare-and-swap.

Accepting a claim SHALL turn the lead into a normal referrals row with status='accepted' and contact_status='unlocked', so the client lands in the provider's existing dashboard queue with no new UI, and SHALL notify both sides exactly like a matched referral.

Scenario: A marketing pass reverse-matches an unmatched lead to a prospect

  • WHEN runOutreachBatch processes a not-yet-registered provider who

matches an unclaimed open_demand lead

  • THEN it mints a claim offer for that lead, emails the private claim

link (subject "… is on hold for you …"), and does not send the second prospect in the same batch a competing offer for the same seeker

Scenario: The claim link arrives

  • WHEN a provider clicks a claim link
  • THEN the token is parked in a short-lived askavi_claim cookie and

stripped from the URL, and the dashboard shows the "client on hold" accept action once they are signed in

Scenario: A claim is accepted

  • WHEN a signed-in provider whose email matches the offer accepts the

claim

  • THEN the lead is claimed to them free, a referral is created

already accepted and contact-unlocked, both parties are notified, and the spent token is cleared so it can never be replayed

Requirement: The provider network grows from a new-entrant supply wedge, self-serve, with no fabrication

Growth strategy posture (docs/ask-avi-growth-strategy.md): the supply side of the two-sided cold start is won by courting NEW ENTRANTS - students and newly registered provisional/graduate providers who have capacity but no client pipeline - ahead of (or at the cost of) established practices, who are curated later for fill-rate. Paid acquisition is not required to reach a first booking. Consequences for the system:

  1. Provider onboarding SHALL stay self-serve and zero-friction ("no sales

call, about a minute") - the email-only portal signup is the bar, and nothing in this milestone may put a paywall, call, or form before a first registered profile.

  1. The first lead for a brand-new provider SHALL be free (the claim path

above), as the wedge's proof of value.

  1. Outreach copy SHALL fabricate no demand evidence - no invented counts,

no "N seekers", no artificial urgency. Every claim sent is about a real seeker who actually asked for help.

Scenario: A brand-new provider opens the portal

  • WHEN a provider signs up with nothing but an email address
  • THEN the full onboarding completes in the email-only flow, no card

is required for the first claim, and no sales call is scheduled

Requirement: The marketing dashboard is a view-agnostic presentation layer over the marketing agent

The system SHALL provide a marketing dashboard that presents marketing's tasks and efforts. It is view-agnostic by design - adding a view means adding a renderer, never a second implementation of matching, charging, or sending. The dashboard SHALL stay simple: two things, both presentation-only.

  1. Funnel tracking - read through skills/query-funnel-kpis, never a

duplicate query path.

  1. The prospect column - one row per provider worth contacting,

showing their contact email and/or phone number. **Demand-backed rows**: providers who could serve unclaimed open demand right now. Coverage rows: not-yet-registered prospects in the target domains (health, legal, wellness) with no current matched demand.

Each row can be approved by the operator; approval, and only approval, sends that prospect the onboarding email or SMS via marketing's outreach functions. Every email still flows through send-compliant-email and every lead charge through sell-demand-lead.

Scenario: A demand-backed prospect row is approved

  • WHEN the operator approves a prospect row that has a contact email

and at least one reverse-matched lead

  • THEN that prospect gets the onboarding draft via

runOutreachBatch, with no seeker contact information

Scenario: Nothing happens without approval

  • WHEN any view renders the funnel or the prospect column
  • THEN no lead is claimed, no charge is made, and no message is

drafted or sent

Requirement: Web sourcing of providers is one gated skill, shared by marketing and sales

skills/search-providers-web SHALL be the only code path that searches the public web for providers. marketing MAY use it for outreach. sales SHALL use it as a fallback so an empty internal pool never means an empty answer for the seeker. The capability sits behind the SOURCING_SCRAPER_ENABLED kill switch (off by default, pending legal sign-off).

Scenario: A seeker's search matches nothing internally

  • WHEN a non-crisis search_referral returns an empty match list
  • THEN the tool result carries web_suggestions the seeker is

offered as public listings to contact directly

Scenario: A crisis search matches nothing

  • WHEN search_referral returns null for a crisis triage
  • THEN no web search runs - the seeker gets the explicit crisis

pathway

Scenario: The kill switch is off (the default)

  • WHEN SOURCING_SCRAPER_ENABLED is unset or false
  • THEN no web request is made - searchProvidersWeb returns

clearly-marked simulated candidates instead

Requirement: Provider performance metrics are computed by a dedicated skill

The skills/query-provider-metrics skill SHALL compute provider-specific performance metrics (total referrals, referrals in last 30 days, booked referrals, booking rate, total revenue, and 30-day revenue) from the referrals table.

Scenario: A provider opens their portal

  • WHEN a provider opens the portal with a valid setup token
  • THEN the portal displays their performance metrics

Requirement: Providers may refer peers to the network

The skills/refer-provider skill SHALL allow a registered provider to refer a peer by sending an invitation email via send-compliant-email.

Scenario: A provider refers a peer who is already registered

  • WHEN a provider attempts to refer an email that already exists in

provider_accounts

  • THEN the skill throws with "this provider is already registered"

Requirement: Tiered volume pricing rewards providers with high booking volumes

shared/pricing.ts SHALL export a volumeDiscountForBookings function that applies tiered discounts based on monthly booking count: 10% at 5+ bookings, 20% at 10+, and 30% at 25+.

Requirement: Inactive providers are periodically re-engaged

The growth agent SHALL run a monthly re-engagement sequence (runReengagementSequence) that identifies providers who are accepting new clients but have received no referrals in the last 14 days.

Requirement: Every agent turn and skill invocation is traced

Every sales conversation turn, and every invocation of a side-effecting or matching-relevant skill by any agent, SHALL write a trace record through shared/db.ts.

Scenario: A search_referral tool call resolves

  • WHEN agents/sales/converse.ts completes a search_referral call
  • THEN a trace record exists linking that turn to the

match-provider invocation

Requirement: Inbound channel webhooks are idempotent

Every inbound webhook-driven channel (sms, whatsapp, voice-phone) SHALL dedupe retried provider deliveries using a durable key stored via shared/db.ts.

Scenario: A provider retries a webhook delivery

  • WHEN Twilio or WhatsApp redelivers a webhook for a message already

processed

  • THEN the duplicate delivery is detected and discarded before it

reaches sales

Requirement: Orchestration is a trigger-to-agent lookup only

orchestration/router.ts SHALL map each trigger to exactly one agent, and no agent SHALL call another agent directly.

Scenario: A trigger matches no routing rule

  • WHEN a manual command does not match any agent's routing keywords
  • THEN the router throws rather than silently guessing an agent

Requirement: Agents call skills as plain function imports

Agents SHALL invoke skills via direct TypeScript imports, not through a runtime registry, message bus, or permission-checking dispatcher. The import graph, enforced by the compiler, SHALL be the only enforcement mechanism for which skills an agent may use.

Scenario: An agent needs a capability another agent's skill provides

  • WHEN marketing needs matching logic
  • THEN it imports match-provider directly from

skills/match-provider

Requirement: Pointer capabilities, not duplicated specs

This system's spec and each agent's agent.md SHALL point to an agent's full behavioural spec in the source project (ask-avi) rather than duplicating it, wherever one already exists and describes capabilities this scaffold actually has.

Scenario: Someone looks for marketing's full strategy

  • WHEN an engineer or agent looks for marketing's detailed

behaviour beyond its mandate

  • THEN agents/marketing/agent.md points them to

ask-avi/docs/spec/marketing/strategy.md

Requirement: Testing an agent's conversational judgement SHALL use a real LLM, never a mock

A test or demo proving an agent's conversational behaviour SHALL make a real call to the LLM the agent is specified to use, with no mocked or simulated model response.

Scenario: Proving sales's matching behaviour works from a conversation

  • WHEN terminal/channel.ts is run to test that a conversation can

drive match-provider to a correct result

  • THEN it calls the real Anthropic API with search_referral wired

to the real matchForTriage

Scenario: This does not apply to skill-level unit tests

  • WHEN a skill's own deterministic logic is under test
  • THEN npm test calls the skill directly with hand-built inputs

and makes no LLM call at all

Internal working document-Home →-Provider portal →