Fionn

AI-Powered Renewal Management System — Architecture Document

1. System Overview

Fionn is an AI-based software renewal management system that automates the end-to-end renewal lifecycle — from initial customer outreach through final contract closure. It operates across multiple integrated platforms to identify upcoming renewals, engage customers via intelligent email conversations, track renewal stages, and provide real-time operational visibility through a management dashboard.

Fionn replaces manual renewal management with an autonomous AI agent that handles outreach, follow-ups, sentiment analysis, pricing, quoting, and stage progression — all while maintaining strict communication discipline and contract rules.

Core Principles

2. High-Level Architecture

graph TB
    subgraph FionnSystem [Fionn System]
        SF["Salesforce<br/>(Trilogy Sales + No Software Speed)"]
        CC["CuChulainn<br/>AI Engine<br/>4 Packages · 14 Skills · 1 Agent"]
        DASH["Fionn Dashboard<br/>S3 + CloudFront + Cognito SSO"]
        MCP["MCP Server<br/>Function URL"]
        SBX["Sandbox Stack<br/>Parallel test environment"]
        COMP["Compliance<br/>Indexer + Docs API"]
    end

    subgraph ExternalSystems [External Systems]
        KLAIR["Klair<br/>NetSuite Data"]
        NETSUITE["NetSuite REST<br/>Subsidiary + Subscription"]
        KAYAKO["Kayako<br/>Support Tickets"]
        OPENAI["OpenAI<br/>GPT-5.3-instant + Cost API"]
        GAS["Gmail + Apps Script<br/>gmailwatch · senders"]
        GDRIVE["Google Drive + Sheets<br/>Compliance documents"]
        ADOBE["Adobe Sign<br/>Agreements"]
        ADOPT["Klairvoyant<br/>Adoption API"]
        COGNITO["AWS Cognito<br/>Google SSO"]
    end

    SF <--> CC
    CC <--> DASH
    SF <--> DASH
    CC --> KLAIR
    CC --> KAYAKO
    CC --> OPENAI
    GAS <--> CC
    OPENAI --> DASH
    DASH <--> COGNITO
    DASH <--> MCP
    DASH <--> ADOBE
    DASH --> ADOPT
    COMP --> GDRIVE
    COMP --> NETSUITE
    DASH <--> COMP
    DASH <--> SBX
    SBX --> CC
    CUSTOMER["Customer<br/>Email Inbox"] <--> GAS
    CC --> CUSTOMER
            

3. Component Architecture

3.1 CuChulainn — AI Execution Engine

CuChulainn is the core AI platform that powers Fionn's autonomous operations. It provides the runtime for AI packages, each containing workflows, skills, and agents.

graph LR
    subgraph CuChulainn Platform
        GO["GeneralOutbound<br/>First contact with customer"]
        GI["GeneralInbound<br/>Reply to every inbound email"]
        RP["ReminderPackage<br/>Follow-up + find more users"]
        EI["EmailInvestigator<br/>Find alternate emails"]
        SA["SalesforceAgent<br/>6 Salesforce skills"]
        SK["14 Shared Skills<br/>API endpoints"]
    end

    GO --> SK
    GI --> SK
    RP --> SK
    EI --> SK
    GO --> SA
    GI --> SA
    RP --> SA
    EI --> SA
            

3.2 Fionn Dashboard — Operational Visibility Layer

The dashboard provides real-time pipeline visibility, AI health assessments, and operational controls.

ComponentTechnologyPurpose
FrontendS3 + CloudFrontStatic HTML/JS/CSS dashboard with Cognito SSO
CDNCloudFront (E22HOIROU34E4P)Global content delivery + API routing
API GatewayHTTP API (o61t89e1x1)REST endpoint routing
AuthenticationAWS Cognito (Google SSO)User pool + role lookup via FionnUsers

3.3 Lambda Inventory

All deployed Lambdas in account 582049328161 / us-east-1. Source-of-truth deploy mappings live in .cursor/rules/lambda-deploy.mdc.

LambdaSourcePurpose
fionn-dashboard-apilambda_handler.handlerMain HTTP API monolith — opps, emails, AI, notes, flags, agreements, financials, sentiment, RCA, users
fionn-dashboard-synclambda_handler.sync_handlerDaily Salesforce → DynamoDB sync + safe upsert
fionn-auto-triggerlambda_handler.auto_trigger_handlerAuto-trigger Fionn at ≤180 days; smart-defer reminders
fionn-ai-scan-dailylambda_handler.ai_scan_handlerDaily AI status refresh + deferral signal scan
fionn-openai-cost-updaterlambda_handler.openai_cost_refresh_handlerHourly pull of OpenAI org cost into FionnFinancials
fionn-mcp-serverlambda_mcp_server.pyMCP Streamable HTTP server (Function URL with token)
fionn-compliance-indexerlambda_compliance_indexer.pyDaily Drive + Sheet walker → FionnComplianceIndex
fionn-compliance-docslambda_compliance_docs.pyFunction URL: opp/NS ID → subsidiary cache → compliance docs (with Drive bytes)
fionn-email-loggerlambda_email_logger.pyIngest email events into FionnEmailLogs (+ sandbox table)
fionn-netsuite-apilambda_netsuite_api.pyNetSuite ID lookup; synthetic sandbox payloads
fionn-sandbox-testinglambda_sandbox_testing.pyTrigger sandbox CuChulainn package; scan logs/tests
fionn-sandbox-querylambda_sandbox_query.pySandbox FionnEmailLogs-Sandbox thread / product lookup
fionn-adoption-apilambda_adoption_api.pyProxy for Klairvoyant adoption API per account/opp
fionn-pricing-calculatorlambda_pricing_calculator.pyRenewal pricing (1/3/5y × tiers) with FX + per-product floors
fionn-quote-clonerlambda_quote_cloner.pyCPQ clone / tier swap / approvals in NoSoftware Speed SF
fionn-address-validatedlambda_address_validated.pySets Addresses_Validated__c on Opportunity (NSS SF)
fionn-chat-websocketlambda_chat_ws.pyWebSocket chat: dashboard context + CuChulainn + OpenAI
fionn-task-completelambda_task_complete.pyTrivial TASK_COMPLETE handler for integrations

3.4 DynamoDB Tables

TablePurpose
FionnDashboard (a.k.a. FionnDashboard-prod)Opportunity data, AI status, flags, notes, defer state
FionnEmailLogsEmail conversation history
FionnProductConfigProduct-to-project mappings and aliases
FionnFinancialsVendor spend + OpenAI cost rollups
FionnUsersCognito user → role mapping (admin / standard)
CustomerSentimentHistorySentiment alert history (/sentiment-alerts)
FionnComplianceIndexIndexed Drive + Sheet compliance documents
FionnSubsidiaryCacheNetSuite subsidiary resolution cache
FionnDashboard-SandboxSandbox opportunity data
FionnEmailLogs-SandboxSandbox email log mirror

3.5 Scheduled Processes

Five scheduled jobs run via AWS EventBridge:

ScheduleLambdaCronPurpose
Daily Syncfionn-dashboard-synccron(0 6 * * ? *) — 6 AM UTCSalesforce data sync + safe upsert
AI Scanfionn-ai-scan-dailyDaily ~7 AM GMTAI status refresh + deferral signal scan across all open opps
Auto-Triggerfionn-auto-triggercron(0 14 * * ? *) — 2 PM GMTAuto-trigger at ≤180 days; reminders for stale Pending opps
Compliance Indexfionn-compliance-indexerDailyWalk Drive folder + Sheet → upsert FionnComplianceIndex
OpenAI Costfionn-openai-cost-updaterHourlyPull OpenAI org cost into FionnFinancials

3.6 Function URL Lambdas

Two Lambdas are exposed via dedicated AWS Function URLs (no API Gateway):

LambdaURL PatternAuth
fionn-mcp-serverhttps://eu4zkntsvzzv66gwmir2lragi40kxcpe.lambda-url.us-east-1.on.aws/?token=<MCP_ACCESS_TOKEN>Bearer token in query string
fionn-compliance-docshttps://no7calsf7skczvbrkulue7jdbq0fieav.lambda-url.us-east-1.on.aws/IAM / open (read-only lookup)

4. CuChulainn Packages — Detailed Flows

4.1 GeneralOutbound

Purpose: First contact with the customer when a renewal is approaching.
Trigger: Dashboard trigger (manual or auto-trigger at ≤180 days to renewal).
Package ID: PACKAGE-d3a9354c-3d3e-4edd-9494-c55898403852 (common for all products)

flowchart LR
    T["Trigger<br/>(Manual or Auto)"] --> FETCH["Fetch Opportunity<br/>from Salesforce"]
    FETCH --> KLAIR["Get NetSuite<br/>Contract Data"]
    KLAIR --> COMPOSE["AI Composes<br/>Outreach Email"]
    COMPOSE --> SEND["Send Email<br/>to Customer"]
    SEND --> LOG["Log to<br/>FionnEmailLogs"]
    LOG --> UPDATE["Update Opp Stage<br/>→ Outreach"]
            

Key behaviors

4.2 GeneralInbound

Purpose: Process and respond to every inbound customer email related to a renewal.
Trigger: Google Apps Script routes inbound email to CuChulainn.

flowchart LR
    IN["Inbound Email<br/>via Apps Script"] --> PARSE["Parse Email<br/>+ Thread Context"]
    PARSE --> ANALYZE["AI Sentiment<br/>Analysis"]
    ANALYZE --> DECIDE["Determine<br/>Response Strategy"]
    DECIDE --> REPLY["AI Composes<br/>Reply"]
    REPLY --> SEND["Send Reply<br/>to Customer"]
    SEND --> STAGE["Update Stage<br/>if Needed"]
            

Key behaviors

4.3 ReminderPackage

Purpose: Follow up when no customer response has been received. Discovers and adds additional contacts to widen the outreach net.
Trigger: Auto-trigger (daily at 2 PM GMT for Pending opps with no reply in 5+ days) or manual dashboard trigger.
Package ID: PACKAGE-9dc9897a-3d92-4dc1-b7c6-ddd3591a74e8

flowchart LR
    CHECK["Check Last<br/>Mail Date"] --> DEFER{"Defer Signal<br/>Detected?"}
    DEFER -->|Yes| PAUSE["Pause Until<br/>Follow-up Date"]
    DEFER -->|No| CONTACTS["Find Additional<br/>Contacts"]
    CONTACTS --> COMPOSE["AI Composes<br/>Follow-up"]
    COMPOSE --> SEND["Send Reminder<br/>Email"]
    SEND --> LOG["Log + Update<br/>Reminder Count"]
            

Key Differentiator: Actively searches for additional contacts beyond Salesforce, broadening reach when the original contact is unresponsive.

4.4 EmailInvestigator

Purpose: Find alternate email addresses when delivery fails or the primary contact is unreachable.
Trigger: Called by other packages when email bounces or no response after multiple reminders.

flowchart LR
    BOUNCE["Delivery Failure<br/>or No Response"] --> SEARCH["Search Kayako<br/>+ Other Systems"]
    SEARCH --> VALIDATE["Validate Found<br/>Addresses"]
    VALIDATE --> UPDATE["Update Contact<br/>in Salesforce"]
    UPDATE --> RETRY["Retry Outreach<br/>with New Address"]
            

5. Automated Trigger & Reminder System

The system runs autonomously at 2:00 PM GMT daily via the fionn-auto-trigger Lambda.

5.1 Auto-Trigger Rules

RuleConditionAction
Auto-Trigger Outbound≤180 days to renewal AND not yet triggered AND not closed/finalizingCalls Fionn outbound API, marks FionnTriggered = Yes, AutoTriggered = true
Auto-Send ReminderStage is "Pending" AND already triggered AND last mail direction is outbound AND 5+ days since last mailAnalyzes emails for defer → if no defer, sends reminder

5.2 Smart Defer Detection

Before sending any auto-reminder, the system analyzes the last 5 emails using GPT-5.3-instant to detect defer signals from the customer.

Detected signals include

When a defer is detected

  1. GPT extracts the follow-up date (converts relative dates like "in 2 weeks" to actual dates)
  2. ReminderPausedUntil is stored in DynamoDB with the extracted date
  3. DeferReason captures the customer's statement
  4. Reminders are suppressed until the follow-up date
  5. When the date arrives, reminders automatically resume
flowchart TB
    AUTO["Auto-Trigger<br/>2 PM GMT Daily"] --> SCAN["Scan All<br/>Open Opportunities"]
    SCAN --> TRIGGER{"≤180 Days<br/>& Not Triggered?"}
    TRIGGER -->|Yes| OUTBOUND["Call Fionn<br/>Outbound API"]
    TRIGGER -->|No| PENDING{"Pending Stage<br/>& 5+ Days Stale?"}
    PENDING -->|Yes| PAUSED{"Reminder<br/>Paused?"}
    PAUSED -->|Yes, Future| SKIP["Skip — Wait for<br/>Follow-up Date"]
    PAUSED -->|No or Expired| ANALYZE["AI Analyzes<br/>Recent Emails"]
    ANALYZE --> DEFER{"Defer Signal<br/>Found?"}
    DEFER -->|Yes| STORE["Store Pause Date<br/>+ Reason"]
    DEFER -->|No| REMIND["Send Reminder<br/>via ReminderPackage"]
    PENDING -->|No| NEXT["Next Opportunity"]
            

5.3 Manual Pause/Resume

The dashboard provides manual controls to pause or resume reminders:

6. Skills Reference

All skills are shared across packages. Each skill is an API endpoint performing one atomic task.

#Skill NameDescription
1send_email_fionnSend email from Fionn mailbox to customer
2get_sf_detailsGet full opportunity details from Salesforce
3update_sf_stageUpdate opportunity stage in Salesforce
4get_klair_contractFetch NetSuite contract data via Klair
5get_klair_pricingFetch pricing and subscription details
6search_kayako_ticketsSearch Kayako for support history
7get_kayako_contactsFind alternate contacts from Kayako
8pull_andon_cord_fionnEmergency stop — halts all processing
9get_sf_contact_rolesGet contact roles for an opportunity
10update_sf_contactUpdate contact information in Salesforce
11search_sf_accountsSearch Salesforce accounts by criteria
12get_email_threadRetrieve full email thread history
13validate_emailValidate email address deliverability
14log_activityLog activity to FionnEmailLogs DynamoDB table

7. Agents Reference

AgentSkills UsedPurpose
SalesforceAgentget_sf_details, update_sf_stage, get_sf_contact_roles, update_sf_contact, search_sf_accounts, log_activityManages all Salesforce interactions with AI intelligence to decide which data to fetch/update

8. Renewal Pipeline — Stage Progression

The renewal lifecycle follows a strict sequential pipeline. Fionn manages transitions automatically based on conversation state.

flowchart LR
    P["Pending"] --> O["Outreach"]
    O --> E["Engaged"]
    E --> PR["Proposal"]
    PR --> QF["Quote Follow Up"]
    QF --> F["Finalizing"]
    F --> CW["Closed Won"]
    F --> CL["Closed Lost"]
            

Stage Definitions

StageMeaningTypical Timeline
PendingRenewal identified, no outreach yet>180 days
OutreachInitial email sent, awaiting response180–145 days
EngagedCustomer is actively responding145–90 days
ProposalQuote or proposal shared90–60 days
Quote Follow UpAwaiting customer decision on quote60–30 days
FinalizingContract in final closing stage (treated as closed)30–0 days
Closed WonRenewal completed successfully
Closed LostCustomer chose not to renew

Stage Treatment

9. Data Flow — End to End

graph TB
    subgraph ExternalDataSources [External Data Sources]
        SF_TS["Salesforce: Trilogy Sales<br/>Fionn AI owned opportunities"]
        SF_NS["Salesforce: No Software Speed<br/>Renewal data · ARR · Stages"]
        GDRIVE["Google Drive + Sheets<br/>Compliance documents"]
        ADOBE["Adobe Sign<br/>Agreement status"]
    end

    subgraph AIandCost [AI & Cost Services]
        OAI["OpenAI GPT-5.3-instant<br/>AI status · Summaries · Defer analysis"]
        OAI_COST["OpenAI Org Cost API<br/>Hourly billing pull"]
        AWS_COST["AWS Cost Explorer<br/>Vendor spend"]
    end

    subgraph FionnCore [Fionn Core]
        CC["CuChulainn<br/>4 Packages"]
        LAMBDA_SYNC["Lambda: Sync<br/>Daily 6 AM UTC"]
        LAMBDA_AI["Lambda: AI Scan<br/>Daily ~7 AM GMT"]
        LAMBDA_API["Lambda: API<br/>Dashboard backend"]
        LAMBDA_AUTO["Lambda: Auto-Trigger<br/>Daily 2 PM GMT"]
        LAMBDA_OAI_COST["Lambda: Cost Updater<br/>Hourly"]
        LAMBDA_COMP["Lambda: Compliance<br/>Indexer + Docs"]
        LAMBDA_MCP["Lambda: MCP Server<br/>Function URL"]
        LAMBDA_SBX["Lambda: Sandbox<br/>Testing + Query"]
        DDB["DynamoDB<br/>10 tables (prod + sandbox)"]
        DASH["Dashboard UI<br/>S3 + CloudFront + Cognito"]
    end

    SF_TS --> LAMBDA_SYNC
    SF_NS --> LAMBDA_SYNC
    LAMBDA_SYNC --> DDB
    LAMBDA_AI --> OAI
    LAMBDA_AI --> DDB
    DDB --> DASH
    DASH --> LAMBDA_API
    LAMBDA_API --> CC
    LAMBDA_API --> ADOBE
    LAMBDA_AUTO --> CC
    LAMBDA_AUTO --> OAI
    LAMBDA_AUTO --> DDB
    LAMBDA_OAI_COST --> OAI_COST
    LAMBDA_OAI_COST --> AWS_COST
    LAMBDA_OAI_COST --> DDB
    LAMBDA_COMP --> GDRIVE
    LAMBDA_COMP --> DDB
    DASH --> LAMBDA_MCP
    LAMBDA_MCP --> LAMBDA_API
    DASH --> LAMBDA_SBX
    LAMBDA_SBX --> CC
    LAMBDA_SBX --> DDB
            

Data Sync Process (Daily + On-Demand)

  1. Trilogy Sales query — Fetch all Fionn AI-owned opportunity IDs via SOAP API
  2. No Software Speed query — Fetch renewal data (ARR, stage, dates) for those IDs via REST API
  3. Email log merge — Fetch from FionnEmailLogs DynamoDB + legacy mail tracking API
  4. Safe upsert — Write to DynamoDB preserving protected fields (triggers, reminders, flags, notes, AI status, defer state)
  5. Stale cleanup — Remove records no longer in Salesforce
  6. AI refresh — Generate/update AI health status for all open opportunities
  7. DaysSinceLastMail — Recalculated in real-time on every API response (not stored stale)

10. Dashboard Features

10.1 Hierarchical Table View

Opportunities are organized in a collapsible hierarchy:

▶ Business Unit (e.g., JigTree) ▶ Product (e.g., Tivian) Customer A — $50,000 ARR — 45 days — Engaged — 🟢 On Track Customer B — $12,000 ARR — 120 days — Outreach — 🟡 Attention

All groups are collapsed by default. Expand All / Collapse All toggle is available. A second "Flat View" tab shows all opportunities without grouping.

10.2 Per-Opportunity AI Status

Each opportunity has an AI-generated health assessment powered by GPT-5.3-instant:

SignalMeaningCriteria
🟢 GREENOn TrackStage matches or ahead of timeline, active communication, no overdue AR
🟡 YELLOWAttentionStage slightly behind, or communication stalled >14 days
🔴 REDAt RiskStage significantly behind, customer expressed intent to cancel, OR any overdue AR balance > $0 on the customer or the reseller (deterministic override — see 10.2.1)

AI status is refreshed:

10.2.1 AR / Overdue Risk Override

The AI prompt is given the customer's NetSuite overdue balance plus, for indirect deals, the reseller's overdue balance. If either is > $0 the signal is forced to RED (deterministic post-process, independent of what the model returns) and the summary is prefixed with the overdue amounts. This guarantees that an account who is 30+ days behind on invoices never shows GREEN regardless of stage or email engagement.

10.3 AI Status Click-to-Expand

Clicking an AI signal expands an inline detail panel below that opportunity showing:

10.4 Smart Reminder Controls

Each opportunity's reminder column shows contextual controls:

10.5 KPI Cards & Running Totals

MetricDescription
Urgent (<30d)Open opportunities within 30 days of renewal
Upcoming (30-90d)Open opportunities 30-90 days from renewal
Fionn TriggeredTotal opportunities with outbound initiated
Emails SentTotal email count across all opportunities
DeferredOpportunities with paused reminders (AI or manual)
Running ARRDynamic ARR total in toolbar, updates with filters

10.6 Additional Features

10.7 Dashboard Sections (Sidebar Views)

The dashboard exposes multiple views via the sidebar in dashboard/index.html + dashboard/app.js. Some are admin-only (gated by FionnUsers role). The sidebar order matches the in-product order; Performance is the default landing route after sign-in (Viewer accounts fall back to Renewals).

Performance, Renewals, and Conversation Quality share a single Unified Filter Bar (Date Range · BU · Products · Stages · Fionn Status · Renewal Window · ARR Tier · Status · Search). Filter state persists in URL params with a localStorage fallback (fionn.filters.v1) and follows the operator across these three views — an active-filter pill row appears above the KPIs with per-pill clear buttons and a "Clear all" link. The BU control is a preset that auto-populates Products with the selected Business Unit's product list (mirrors BUSINESS_UNIT_MAP in backend/lambdas/lambda_handler.py); manually editing products auto-resets BU to "All". Window-scope KPIs and charts respect the Date Range; portfolio-scope sections are tagged with a Portfolio badge and ignore the Date Range.

The Performance header is organised into three semantic strips: Portfolio (Total ARR Managed — portfolio-scope, ignores Date Range), Retention (Gross Retention, Net Retention, DM%, Renewal Event Retention — all window-scope, Playbook conventions; Net Retention carries a ✓/⚠ indicator vs the 90% floor), and ARR Flow (Closed ARR, Active ARR, New ARR out-for-signature, Churn $, Downsell $, Overdue ARR). Gross and Net Retention share the same denominator (Total Revenue = Old ARR = Curr ARR on Won + Lost); Gross clamps each renewal to min(New, Curr), Net includes upsells. DM% (Double Maintenance) = (Total Revenue + Upgrades − Downgrades − Churn + New Business) / Total Revenue, with New Business = 0 for this dataset. Every $ tile in the flow strip is a drill-through: clicking mutates the unified FILTERS state (status/stages/win, preserving dr) and navigates to the Renewals or Agreements view. Prior-window deltas (same-length window immediately preceding) appear as ▲/▼ arrows; colours invert for churn and downsell so rising = red. Overdue ARR ignores the Date Range end (always as-of-now) and renders a red accent when > 0.

Below the three primary strips, the Performance tab renders a secondary Agent metrics row containing five demoted KPIs (Win Rate, Automated Resolution, Response Rate, Avg Reminders / Deal, Avg Emails to Close) plus a Conversation Quality link tile that navigates to the Conversation Quality view. Each tile exposes its formula as small muted text at rest (readable without hovering). Win Rate, Automated Resolution, and Response Rate render a 90-day weekly SVG sparkline (_perfSparkline) with a dashed target line — 80%, 50%, 60% respectively — and the line/last-point colours green when at/above target, amber when below.

The Performance tab also carries three window-scoped diagnostic charts/queues, all respecting the unified filter bar: (1) HVO Cadence Funnel — opportunities with ARR_USD (or ARR) ≥ $100K distributed across cadence buckets (Pre-cadence >210d / Upcoming / Mid / Urgent / Overdue) using DaysToRenewal; bars are clickable drill-throughs into Renewals with win+tier=hvo pre-applied, and an "N off-cadence" pill surfaces when any HVO falls into the Overdue bucket. (2) AR Step Funnel — Sub-HVO accounts (<$100K) on auto-renewal, counted at each of the six live notice milestones projected on Opportunity: D-104 → D-89 → D-60 → D-40 → D-14 → D-0 (confirm). (3) Escalation Queue — a table of AI-flagged escalations (AISignal=RED) sorted by urgency (lowest DaysToRenewal) then ARR desc, with a "N flagged" pill in the header. The Stage Funnel bars now carry inline numeric labels via the shared _perfValueLabelsPlugin (Chart.js afterDatasetsDraw). Earlier iterations included three placeholder sections (D-82/D-21/D-7 greyed AR bars, a Pricing Strategy Mix card awaiting Opportunity.PricingStrategy__c, and a "Linked Fix Ticket" column awaiting FixTicketURL) — all removed to keep the Performance tab live-data-only.

The Adobe Sign Quote & Term Breakdown (3 summary tiles: Agreements Sent · Signed · Waiting to be Signed, plus 4 term cards: 12mo, 36mo, 60mo, No Term Data) lives on the Agreements tab rather than Performance. The "No Term Data" card absorbs agreements with no matched opp/term and non-standard terms (18/24/48/…). renderQuoteTermBreakdown(_agreementsData) is called from refreshAgreements() and from _refreshAgreementsAfterOverride() so that manual signed overrides propagate into the breakdown tiles.

Settings access. Settings is no longer a sidebar nav entry — it now opens from a gear icon at the bottom of the sidebar (Admin-only). The redesigned modal uses a card-based product list with inline search, two-column forms, and in-modal toast feedback instead of blocking alert() dialogs. Esc closes the modal; overlay click still dismisses.

Agreements carry an optional manual status override (e.g. "customer signed outside Adobe Sign"). Overrides are stored per-browser in localStorage["fionn.agreementStatusOverrides"] keyed by Adobe Sign agreement ID; the override rewrites the effective status at match-cache build time, so Performance Strip 3 ("New ARR out-for-signature"), the Agreements table, the Quote & Term Breakdown, and the status chip counters all stay coherent without branching. The raw Adobe Sign status is preserved on _rawStatus and exposed in the agreement detail panel alongside an Undo control.

#ViewAccessPurpose
1Performance (default landing)Manager+Three KPI strips (Portfolio · Retention · ARR Flow), Agent metrics secondary row with sparklines, AI health / funnel charts, product performance
2RenewalsAllHierarchical + Flat opportunity views (described in §10.1). Column triage: 10 essential columns always visible (Customer, Product, Renewal Date, Days, Auto-Renew, Curr ARR, New ARR, Overdue, Stage, Fionn status) + 6 icon columns (Flag, Notes, Analysis, Reseller, Excl., AI). 17 contextual columns (NetSuite, NS Sub, Quote, Opp ID, etc.) behind inline row-expand chevron. Frozen default: Through Customer. Column preferences persisted per user via localStorage.
3Conversation QualityManager+Summary tiles (Avg Score, Scored, Good/Fair/Poor), 30-day rubric trend chart (7 dimensions + 7/10 target line), per-prompt-version comparison table (auto-hidden unless ≥2 versions), scored-conversation table with per-row "View conversation" action (/email-detail), bulk + single re-scoring (/conversation-quality). Uses shared filter bar.
4AgreementsManager+Adobe Sign agreement status + linked opportunities; Quote & Term Breakdown (12/36/60mo + No Term Data); manual signed-outside-Adobe override
5ReportsManager+Filtered renewal PDF reports with AI executive summary (described in §10.6)
6Customer AsksAllStandalone request tracker (requests.html, see §19)
7ArchitectureAdminThis document
8FinancialsAdminOpenAI cost rollups + AWS Cost Explorer vendor spend (FionnFinancials)
9UsersAdminManage Cognito users + roles in FionnUsers
10Sandbox TestingAdminTrigger sandbox CuChulainn package; list test results + sandbox logs
11RCAManager+ (no FinanceAdmin)Root Cause Analysis: create/list incidents with PDF export
12DeveloperAdmin (no FinanceAdmin)Embedded Swagger + /app-config MCP key/URL + internal Function URL table
13SentimentManager+Customer sentiment timeline (/sentiment-alerts, CustomerSentimentHistory)
14Contract ReviewManager+Toxic-contract analysis via external Lambda URL + /contract-review-ai
15SupportAllExternal link to Kayako (fionn-renewals.kayako.com)

Standalone pages

11. Integration Map

graph TB
    subgraph ExternalDataSources [External Data Sources]
        SF_TS["Salesforce: Trilogy Sales<br/>Fionn AI owned opportunities"]
        SF_NS["Salesforce: No Software Speed<br/>Renewal data · ARR · Stages"]
        KLAIR["Klair<br/>NetSuite contract · Pricing"]
        NETSUITE["NetSuite REST<br/>Subsidiary · Subscription"]
        KAYAKO["Kayako<br/>Support tickets · Contacts"]
        GDRIVE["Google Drive + Sheets<br/>Compliance documents"]
        ADOBE["Adobe Sign<br/>Agreements"]
        ADOPT["Klairvoyant Adoption API"]
    end

    subgraph AIServices [AI Services]
        OAI_CC["OpenAI<br/>CuChulainn email gen + sentiment"]
        OAI_DASH["OpenAI GPT-5.3-instant<br/>AI status · Defer · Summaries"]
        OAI_COST["OpenAI Org Cost API"]
        AWS_COST["AWS Cost Explorer"]
    end

    subgraph FionnCore [Fionn Core]
        CC["CuChulainn<br/>4 Packages"]
        LAMBDA["AWS Lambda<br/>18 functions"]
        MCP["MCP Server<br/>Function URL"]
        WS["WebSocket Chat<br/>fionn-chat-websocket"]
        DDB["DynamoDB<br/>10 tables"]
        DASH["Dashboard UI<br/>S3 + CloudFront"]
    end

    subgraph AuthAndComms [Auth & Communication]
        COGNITO["AWS Cognito<br/>Google SSO"]
        GAS["gmailwatch + senders<br/>Apps Script"]
        CUST["Customer<br/>Email inbox"]
    end

    SF_TS --> LAMBDA
    SF_NS --> LAMBDA
    LAMBDA --> DDB
    DDB --> DASH
    LAMBDA --> OAI_DASH
    LAMBDA --> OAI_COST
    LAMBDA --> AWS_COST
    LAMBDA --> ADOBE
    LAMBDA --> ADOPT
    LAMBDA --> NETSUITE
    LAMBDA --> GDRIVE

    CC --> KLAIR
    CC --> KAYAKO
    CC --> OAI_CC
    CC --> SF_NS

    DASH --> COGNITO
    DASH --> LAMBDA
    DASH --> MCP
    DASH --> WS
    WS --> CC
    MCP --> LAMBDA
    LAMBDA --> CC

    GAS --> CC
    CC --> CUST
    CUST --> GAS
            

12. Security & Authentication

LayerMeasure
AuthenticationAWS Cognito user pool with Google SSO; configured in dashboard/config.js
AuthorizationRole-based access via FionnUsers DynamoDB table; admin-only views (Users, RCA create, sensitive admin actions)
MCP TokenBearer token in URL query string (?token=<MCP_ACCESS_TOKEN>) for fionn-mcp-server Function URL; stored as Lambda env var, surfaced to admins via /app-config
Sandbox IsolationSeparate -Sandbox DynamoDB tables (FionnDashboard-Sandbox, FionnEmailLogs-Sandbox); separate sender (emailsendersandbox); synthetic NetSuite payloads via fionn-netsuite-api
API KeysStored in Lambda environment variables / AWS Secrets Manager via secrets_helper.py; never in frontend code or git
CORSOrigin whitelist (CloudFront domain + localhost dev) — no wildcard
Input ValidationSalesforce ID regex validation (^[a-zA-Z0-9]{15,18}$) on all ID parameters
XSS ProtectionAll user-supplied content escaped via escapeHtml() before DOM insertion
Email SanitizationHTML emails sanitized via DOMParser — scripts, iframes, forms stripped; event handlers removed; external images blocked (tracking pixel protection)
CDN IntegritySRI attributes on external script tags (jsPDF, AutoTable)
Sensitive LoggingRequest logging excludes full event payload — only method + path logged
DynamoDBProtected fields preserved during sync (triggers, flags, notes, AI, defer state never overwritten)
Write-Path ApprovalsAnything that writes to NoSoftware Speed SF org requires explicit user approval per workspace rule

13. Communication Guardrails

Fionn operates under strict communication rules enforced at the package level:

Forbidden in All Outbound Communication

Required in All Communication

Contract Rules

14. Deployment Architecture

flowchart LR
    DEV["Developer<br/>Machine"] --> FE["S3 Upload<br/>index.html · app.js<br/>styles.css · logo.png<br/>architecture.html"]
    DEV --> BE["Lambda Deploy<br/>lambda_handler.py<br/>→ API + Sync + Auto-Trigger"]
    DEV --> RT["API Gateway<br/>Route creation"]
    DEV --> EB["EventBridge<br/>Schedule rules"]
    FE --> CF["CloudFront<br/>Cache Invalidation"]
    CF --> LIVE["Live Dashboard"]

    style LIVE fill:#EDFAF3,stroke:#1A7F4B,color:#1A1A18
            
ItemValue
AWS Account582049328161
Regionus-east-1
S3 Bucketfionn-dashboard-frontend-582049328161
CloudFront DistributionE22HOIROU34E4P
API Gatewayo61t89e1x1 (HTTP API)
Cognito User PoolGoogle SSO for dashboard authentication (config in dashboard/config.js)
Lambdas (18)See §3.3 for the full inventory
MCP Function URLhttps://eu4zkntsvzzv66gwmir2lragi40kxcpe.lambda-url.us-east-1.on.aws/?token=<MCP_ACCESS_TOKEN>
Compliance Docs Function URLhttps://no7calsf7skczvbrkulue7jdbq0fieav.lambda-url.us-east-1.on.aws/
DynamoDB Tables (11)FionnDashboard, FionnEmailLogs, FionnProductConfig, FionnFinancials, FionnUsers, CustomerSentimentHistory, FionnComplianceIndex, FionnSubsidiaryCache, FionnRequests, FionnDashboard-Sandbox, FionnEmailLogs-Sandbox
Scheduled Jobs (5)See §3.5 — daily sync, AI scan, auto-trigger, compliance index; hourly OpenAI cost
AI ModelGPT-5.3-instant (via OpenAI API)

15. Email Pipeline

All customer email flows through a layered pipeline: a Gmail Apps Script poller (gmailwatch) routes inbound mail to CuChulainn, dedicated sender scripts handle outbound, and an event logger persists every send/receive into DynamoDB.

graph LR
    INBOX["Customer<br/>Inbox"] --> GAS["gmailwatch<br/>Apps Script poller"]
    GAS --> CC["CuChulainn"]
    GAS --> BOUNCE["/handle-bounce/"]
    GAS --> LOGIN["/log-inbound/"]
    GAS --> PRODQ["fionn-product-query<br/>sandbox-query"]
    CC --> SENDER["emailsenderprod<br/>or emailsendersandbox"]
    SENDER --> LOGGER["fionn-email-logger"]
    LOGGER --> DDB[("FionnEmailLogs<br/>+ Sandbox mirror")]
    SENDER --> INBOX
    CC --> GMAIL_API["lambda_gmail_thread<br/>Gmail API skill"]
            

Components

ComponentPathPurpose
gmailwatchgmailwatch + appsscript.jsonApps Script poller: unread inbox → CuChulainn external adapter; bounce handling; sandbox routing; calls handle-bounce / log-inbound / product-query
emailsenderprodemailsenderprodOutbound sender (production): CuChulainn → SMTP → fionn-email-loggerFionnEmailLogs
emailsendersandboxemailsendersandboxSandbox sender: same pipeline, sandbox tables + sandbox NetSuite lookup
fionn-email-loggerbackend/lambdas/lambda_email_logger.pyIngest email events; updates FionnEmailLogs + dashboard rows
lambda_gmail_threadbackend/lambdas/lambda_gmail_thread.pyGmail API full-thread fetch (CuChulainn skill, not standalone deploy)

16. MCP Server

fionn-mcp-server (backend/lambdas/lambda_mcp_server.py) exposes 56 Fionn operations as Model Context Protocol tools over Streamable HTTP. This lets external MCP clients (Claude Code, Claude Desktop, Cursor) drive Fionn programmatically — querying opportunities, sending emails, triggering AI analysis, and more.

Endpoint

https://eu4zkntsvzzv66gwmir2lragi40kxcpe.lambda-url.us-east-1.on.aws/?token=<MCP_ACCESS_TOKEN>

Authentication

Deployment note

Deploy with python3 deploy_lambda.py fionn-mcp-server. The ZIP must include both lambda_mcp_server.py and secrets_helper.py — the handler is lambda_mcp_server.lambda_handler (not the AWS default lambda_function.lambda_handler).

56 tools by category

CategoryTools
Dashboardfionn_get_dashboard, fionn_get_stats, fionn_get_sync_health, fionn_get_live_activity, fionn_get_overdue_balance
Emailfionn_get_email_history, fionn_get_gmail_thread, fionn_send_email
Triggersfionn_trigger_ai_outreach, fionn_reset_trigger, fionn_send_reminder, fionn_send_followup, fionn_send_quote_followup, fionn_pause_reminder
AIfionn_refresh_ai_status, fionn_refresh_all_ai, fionn_scan_deferrals, fionn_score_conversation, fionn_executive_summary
Sentimentfionn_get_sentiment_alerts
Agreementsfionn_get_agreements, fionn_get_agreement_detail, fionn_get_current_quote_pdf
Annotationsfionn_get_prior_contacts, fionn_get_feedback_summary, fionn_toggle_flag, fionn_get_notes, fionn_add_note, fionn_delete_note, fionn_set_analysis, fionn_override_reseller, fionn_exclude_automation
Configfionn_get_product_config, fionn_add_product, fionn_update_product, fionn_delete_product, fionn_update_reminder_config
Financialsfionn_get_financial_data, fionn_update_financial_contract, fionn_delete_financial_contract
Usersfionn_get_users, fionn_update_user_role, fionn_delete_user
Lambda-backedfionn_get_ar_info, fionn_get_reseller_contacts, fionn_reseller_check, fionn_get_compliance_docs
Contact Discoveryfionn_discover_contacts, fionn_detect_stale_opportunities, fionn_advance_discovery, fionn_get_discovery_status, fionn_get_escalation_report
Systemfionn_get_me, fionn_health_check, fionn_trigger_sync, fionn_debug_opportunity

Used from

17. Compliance System

A two-Lambda pair maintains an indexed cache of compliance documents (NDAs, MSAs, addenda) and resolves them by opportunity or NetSuite ID.

graph LR
    DRIVE["Google Drive<br/>Compliance folder"] --> INDEXER["fionn-compliance-indexer<br/>Daily EventBridge"]
    SHEET["Google Sheet<br/>Manifest"] --> INDEXER
    INDEXER --> CIDX[("FionnComplianceIndex")]
    CLIENT["Dashboard / Caller"] --> DOCS["fionn-compliance-docs<br/>Function URL"]
    DOCS --> SUB[("FionnSubsidiaryCache")]
    DOCS --> NS["NetSuite REST"]
    DOCS --> CIDX
    DOCS --> DRIVE
    DOCS --> CLIENT
            

Components

ComponentSourcePurpose
fionn-compliance-indexerbackend/lambdas/lambda_compliance_indexer.pyDaily walks Drive folder + Sheet manifest → upserts FionnComplianceIndex
fionn-compliance-docsbackend/lambdas/lambda_compliance_docs.pyFunction URL: opp/NS ID → subsidiary cache → compliance index → optional Drive file bytes

Key dependencies

18. Sandbox Environment

A parallel sandbox stack mirrors the production pipeline so test emails, synthetic NetSuite data, and CuChulainn dry-runs never touch real customer data or production Salesforce.

Components

LayerComponentPurpose
Orchestration Lambdafionn-sandbox-testing (lambda_sandbox_testing.py)Trigger sandbox CuChulainn package; scan logs/tests against sandbox tables
Query Lambdafionn-sandbox-query (lambda_sandbox_query.py)Sandbox FionnEmailLogs-Sandbox thread / product resolution
Synthetic NetSuitefionn-netsuite-api (lambda_netsuite_api.py)Returns synthetic NetSuite payloads when invoked in sandbox mode
Outbound senderemailsendersandbox (Apps Script)Sandbox-scoped sender; logs to sandbox table
Primary storeFionnDashboard-SandboxSandbox opportunity data
Email logsFionnEmailLogs-SandboxSandbox email mirror
Dashboard viewSandbox TestingTrigger packages, list test results + sandbox logs
Sandbox Lambda URLsBundled in dashboard/config.jsFrontend wiring for the sandbox Lambdas

Related skill

19. Customer Asks (Request Tracker)

A standalone web page (dashboard/requests.html, separate from the main renewals dashboard) that surfaces every distinct customer request extracted from email threads (Phase 2: Salesforce meeting transcripts). Each ask is classified into legal / compliance / finance / document / other and routed to the appropriate ticket system. The board hides any opportunity that is not in an in-progress stage or has zero open asks.

graph LR
    GMAIL["Gmail inbound"] --> WATCH["gmailwatch<br/>Apps Script"]
    WATCH -->|/log-inbound| API["fionn-dashboard-api"]
    API --> LOGS[("FionnEmailLogs")]
    SF[("Salesforce<br/>Opportunity + Transcripts")] --> EXT["fionn-request-extractor<br/>EventBridge 30 min"]
    LOGS --> EXT
    EXT -->|"OpenAI classify"| REQS[("FionnRequests")]
    EXT -->|doc lookup| CIDX[("FionnComplianceIndex")]
    CIDX -. auto-resolves doc asks .-> REQS
    EXT -->|writes rollup| DASH[("FionnDashboard<br/>OPP#id#DETAILS")]
    REQS --> PAGE["dashboard/requests.html<br/>standalone page"]
    DASH --> PAGE
    PAGE -->|"POST /asks/create-ticket<br/>(Phase 3)"| ROUTER["fionn-ticket-router"]
    ROUTER --> JIRA["Jira REST<br/>legal"]
    ROUTER --> KCOMP["Kayako Compliance dept"]
    ROUTER --> KFIN["Kayako Finance dept"]
    SYNC["fionn-ticket-sync<br/>EventBridge 30 min<br/>(Phase 4)"] --> JIRA
    SYNC --> KCOMP
    SYNC --> KFIN
    SYNC --> REQS
            

Components (Phase 1 shipped)

ComponentSource / locationPurpose
fionn-request-extractorbackend/lambdas/lambda_request_extractor.pyEventBridge cron + ad-hoc invoke. Reads FionnEmailLogs deltas per in-progress opp, classifies asks via OpenAI, dedupes by sha1(opp + category + normalisedQuote), auto-resolves document asks against FionnComplianceIndex, writes rollup attrs (aiOpenAsksSummary, asksCategoryCounts, asksOpenCount, asksOldestOpenAt, asksAttentionScore, asksLastExtractedAt) onto each OPP#<id>#DETAILS row in FionnDashboard-prod.
FionnRequests tableDynamoDB (us-east-1)pk = OPP#<oppId>, sk = REQ#<ulid>. GSI1 (CATEGORY#cat#STATUS#st), GSI2 (THREAD#<tid>), GSI3 (TICKET#<sys>#<id>). Canonical store of every extracted ask.
API routes on fionn-dashboard-apibackend/lambdas/lambda_handler.py_route_asks()GET /asks/board (single composite call powering the board), GET /asks?opportunityId=, PATCH /asks?opportunityId=&askId=, POST /asks/resolve, POST /asks/snooze, POST /asks/extract.
dashboard/requests.html (+ requests.js, requests.css)Standalone S3 pageAuth via the same Cognito flow as the main dashboard. Opportunity-centric card grid (one card per in-flight opp with at least one open ask), heat strip header, slide-in detail drawer, inbox-style keyboard shortcuts (J/K/Enter/R/S///?).

Categorisation & ticket routing

CategoryExamplesTicket destination
legalContracts, MSAs, redlines, NDAs, DPAs, terms negotiationJira (Phase 3)
complianceSOC, ISO, infosec questionnaires, GDPR, security reviewsKayako Compliance dept (Phase 3)
financeInvoicing, PO, payment terms, refund, credit, ARKayako Finance dept (Phase 3)
documentSpecific files (W9, COI, bank letter, SOC 2 report, etc.)Auto-resolves against FionnComplianceIndex if indexed; otherwise Kayako Compliance ticket (Phase 3)
otherScheduling, support, onboardingKayako general (Phase 3)

Board visibility rules (server-side, non-toggleable)

Phasing

20. Product Portfolio

Products are organized by Business Unit. Each product has an Ephor Project ID for CuChulainn integration and may have Salesforce name aliases for normalization.

Business UnitProducts
JigTreeTivian, BroadVision, Influitive, MessageOne, Artemis, Bonzai, ACRM, Pivotal, Aurea Platform, Stratifyd, Playbooks, Saratoga, Jigsaw Platform, Onyx, CRMagic, Engine Yard, CallStream
IgniteTechDNN, NorthPlains, Jive, Tradebeam, Verdiem, Gensym, Everest, ObjectStore, Autotrol, StreetSmart, Gomembers, Infobright, TAKE, Coretrac, Nuview, AnswerHub, Smart Routines, Acorn, Computron, Prysm, SupportSoft, Aurea Social, EPM Live, Myalerts, ScaleArc, AtHand, Prologic, Knova, Pricer, NextDocs, Config, Fog Bugz, Eloquens, Sococo, Olive Software, Suuchi, School Loop
SkyveraNewNet, STL, Mobilogy, Service Gateway, ResponseTek, VoltDelta, Cloudsense, Kandy, PeerApp
CanopyContently, Kayako, CloudFix
Aurea E-CommerceAurea SAS, SLI, Spiral, Quantum Retail, Finserv(AIS)
Ignite SMBKerio, GFI, Exinda

21. Automated Contact Discovery

When Fionn's outreach goes unanswered for 30 days (or a hard bounce occurs immediately), this system automatically discovers and validates new contacts at the customer company using a role-prioritized external lookup, then restarts outreach.

Discovery Flow

graph TB
    subgraph Detection [Detection — Daily Cron]
        Scan[Scan FionnDashboard<br/>for stale opps]
        Bounce[Hard bounce<br/>immediate trigger]
        Stale[0 replies after<br/>30 days]
    end
    subgraph Discovery [Discovery — Per Opportunity]
        Adobe[AdobeSign Delegation<br/>Lookup — Priority 0]
        Apollo[Apollo People Search<br/>Role Priority Matrix 1-7]
        Merge[Merge & Deduplicate<br/>vs existing SFDC contacts]
        Verify[Email Verification<br/>via Apollo]
    end
    subgraph WriteBack [Write-Back]
        SFDC[Create Contact in<br/>Salesforce + ContactRole]
        DDB[Store queue in<br/>FionnContactDiscovery]
        Fionn[Fionn picks up<br/>contact on next cycle]
    end
    subgraph Fallback [14-Day Fallback — Daily Cron]
        Check[Check active<br/>discovery timers]
        Advance[Advance to next<br/>contact in queue]
        Escalate[Escalate to human<br/>after 3 contacts]
    end
    Scan --> Stale
    Scan --> Bounce
    Stale --> Adobe
    Bounce --> Adobe
    Adobe --> Apollo
    Apollo --> Merge
    Merge --> Verify
    Verify --> SFDC
    SFDC --> DDB
    DDB --> Fionn
    Check --> Advance
    Advance --> Escalate
        

Role Priority Matrix

PriorityRoleApollo Search Terms
0AdobeSign Delegate (internal)N/A — delegation history lookup
1Procurement / Purchasingprocurement, purchasing, sourcing, vendor management
2Finance / Accounts Payablefinance manager, accounts payable, controller, CFO
3IT / License AdministrationIT manager, license administrator, IT director
4Operations / Administrationoperations manager, office manager, business operations
5C-Suite / ExecutiveCEO, COO, CTO, founder, managing director
6Department Headdirector, head of, VP
7Original Buyer / Prior ContactRe-verify email only

Lambda Functions

FunctionHandlerTrigger
fionn-contact-discoverylambda_contact_discovery.lambda_handlerFunction URL — HTTP routes
fionn-contact-discovery-detectlambda_contact_discovery.scheduled_detect_handlerEventBridge daily cron
fionn-contact-discovery-advancelambda_contact_discovery.scheduled_advance_handlerEventBridge daily cron

HTTP API Routes

MethodPathDescription
POST/discoverRun discovery for a single opportunity
POST/detectScan all opps for 30-day non-response or bounce
POST/advanceAdvance 14-day fallback for a single opportunity
GET/statusGet discovery queue status
GET/escalation-reportStructured escalation report for human takeover

DynamoDB Table: FionnContactDiscovery

Partition key: pk (OPP#<opportunityId>), Sort key: sk (DISCOVERY). Stores the discovery queue, current contact index, status (active/exhausted/resolved), and source summary for each opportunity.

External Integrations

Escalation

When all 3 discovery contacts are exhausted (3 × 14-day cycles = 42 days), the system generates a structured escalation report for Clarisa/Jay containing: company name, renewal date, every contact tried (name, title, source, dates, verification status), and a recommendation.