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_ENABLEDis unset orfalse - THEN
send-compliant-emailsimulates 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-bookingis 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-providerreturns an empty result for a non-crisis
triage
- THEN that triage is recorded as an unclaimed
open_demandrow,
available for marketing to reverse-match and sell-demand-lead to charge a provider for
Scenario: A crisis search matches no provider
- WHEN
match-providerreturnsnullfor a crisis triage - THEN no
open_demandrow is written for it
Scenario: Two providers race to claim the same lead
- WHEN
sell-demand-leadis called twice for the sameopen_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:
- 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.
- 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).
- 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.
- 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
runOutreachBatchprocesses 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_claimcookie 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:
- 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.
- The first lead for a brand-new provider SHALL be free (the claim path
above), as the wedge's proof of value.
- 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.
- Funnel tracking - read through
skills/query-funnel-kpis, never a
duplicate query path.
- 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_referralreturns an empty match list - THEN the tool result carries
web_suggestionsthe seeker is
offered as public listings to contact directly
Scenario: A crisis search matches nothing
- WHEN
search_referralreturnsnullfor 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_ENABLEDis unset orfalse - THEN no web request is made -
searchProvidersWebreturns
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.tscompletes asearch_referralcall - 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
marketingneeds matching logic - THEN it imports
match-providerdirectly 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.mdpoints 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.tsis run to test that a conversation can
drive match-provider to a correct result
- THEN it calls the real Anthropic API with
search_referralwired
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 testcalls the skill directly with hand-built inputs
and makes no LLM call at all