Skip to content

Conversational FP&A

Mid-market finance teams are stuck. AI tools are bolted onto rigid dashboards; structured-query inputs feel like another form to fill. So analysts revert to Excel — friction wins.

This recipe puts conversation first. A CFO asks "what happens to Q3 cash if we cut headcount 8%?" in Slack or chat — Agentcy queries NetSuite for actuals, HiBob for the headcount model, Alta Via for cash position, Procurify for committed spend, and replies with the modeled answer + a linked dashboard, every figure cited back to its source record. Scenarios save and re-run. Row-level access is enforced at the data layer — a Sales lead asking "what's total company headcount?" only ever sees Sales.

The "data layer" is Agentcy's Knowledge Graph + connectors. The "natural language prompt" is the agent loop. The "scenario builder" is a skill the agent enables on demand. RBAC is enforced via Realms + Zero-Trust Policies.

14FinanceConversational FP&A
Context Graph tribal knowledge
org chart · COA · saved scenarios · realm scoping
Sources
NetSuite
NetSuite
HiBob
HiBob
Ramp
Ramp
CFO asks
Agentcy
Agentcy
Agentcy
FP&A agent + scenario skill
cited answer
Output
Q&A
+ scenarios

At a glance

  • Inputs: NetSuite (ERP, P&L, balance sheet), HiBob (HRIS — org of truth), Ramp (US spend), Moss (DE spend), Procurify (committed spend), Alta Via (live cash), Excel (manual budgets).
  • Trigger: Chat / Slack mention / scheduled FP&A digest.
  • Output: Cited answers in chat · auto-generated dashboards · saved scenarios · PDF exports for board review.
  • Gates: read-only on every source in v1 · row-level access by department · scenario sharing requires Finance Owner approval.

Stack

LayerWhat we use
TriggersChat, Slack mention, Cron for the daily/weekly digest
ConnectorsNetSuite, HiBob, Ramp, Moss, Procurify, Alta Via, Excel via Google Workspace or OneDrive
RealmsOne per department (fin-eng, fin-sales, fin-marketing, fin-rnd, fin-master) — drives row-level scoping
AgentFP&A agent with skills for scenario-modeling, variance-commentary, forecasting-methodology
PoliciesRego — read-only on every source · scenario share requires approval · cross-department reads require explicit grant
MemorySaved scenarios, named drivers, recurring questions per user

What you'll build

A finance team working surface with three interaction modes — all driven by the same underlying data and policies:

  1. Natural-language Q&A"What is our burn rate vs. budget this quarter?" / "Summarize the key OpEx variances for April." Answers cite source records.
  2. Auto-generated dashboards — pin any query result to a personal or shared dashboard. Refresh on connector sync.
  3. Scenario builder"Create a downside scenario with 15% lower revenue." Compare up to 3 scenarios side-by-side, export to PDF.

Forecasting methodology is user-defined

The system does not impose a default forecast model. Users specify their own assumptions, drivers, and growth logic. When inputs are missing, the agent must ask — never invent or estimate a number. This is enforced in the system prompt and re-enforced in policy: any tool call returning a fabricated metric trips a deny[…] rule.

Prerequisites

Seven source systems wire into the recipe. Order of setup matters — get the org structure and the cash position right first; everything else maps onto them.

1 · HiBob — the org-of-truth

HiBob's department/team structure is the canonical hierarchy that drives realm scoping. Set this up first. Add the source, sync, then map departments → realms.

bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "name": "hibob-prod",
    "connector": "hibob",
    "realm": "fin-master",
    "config": {
      "service_user_id": "SVC-…",
      "api_token": "<bearer>",
      "base_url": "https://api.hibob.com/v1"
    }
  }'

# Then map HiBob departments → Agentcy realms
curl -X POST $API/api/v1/realms/mappings \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "source": "hibob-prod",
    "by_property": "department",
    "rules": [
      { "value": "Engineering",     "realm": "fin-eng" },
      { "value": "Sales",           "realm": "fin-sales" },
      { "value": "Marketing",       "realm": "fin-marketing" },
      { "value": "R&D",             "realm": "fin-rnd" },
      { "value": "G&A",             "realm": "fin-ga" }
    ]
  }'
ts
await client.sources.create({
  name: 'hibob-prod',
  connector: 'hibob',
  realm: 'fin-master',
  config: {
    service_user_id: process.env.HIBOB_SVC_USER,
    api_token: process.env.HIBOB_TOKEN,
    base_url: 'https://api.hibob.com/v1',
  },
})

2 · Alta Via — cash flow source of truth

Alta Via sits between bank accounts and NetSuite, so it's the most real-time cash signal. The recipe queries Alta Via first for any cash question, then reconciles with NetSuite's accrual view. Discrepancies surface in the data_freshness annotation on every answer.

bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "name": "alta-via-prod",
    "connector": "alta_via",
    "realm": "fin-master",
    "config": {
      "api_token": "<bearer>",
      "tenant_id": "acme-finance",
      "sync_mode": "live"
    }
  }'

3 · NetSuite — the consolidated ERP

bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "name": "netsuite-prod",
    "connector": "netsuite",
    "realm": "fin-master",
    "config": {
      "account_id": "1234567",
      "consumer_key": "…",
      "consumer_secret": "…",
      "token_id": "…",
      "token_secret": "…",
      "auth_method": "tba",
      "sync_strategy": "suiteql_polling",
      "sync_interval_minutes": 60
    }
  }'

SuiteQL polling delivers near-real-time sync for NetSuite; daily-minimum is the fallback. SuiteQL queries land in the connector's allow-list — at prompt-time the agent reads from Agentcy's consolidated model, never live against source systems.

4 · Ramp + Moss — unified spend across entities

Ramp serves US, Moss serves Germany. The agent needs a single normalized spend model, so register both with entity config that the agent can join on:

bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "name": "ramp-us",
    "connector": "ramp",
    "realm": "fin-master",
    "config": {
      "client_id": "…", "client_secret": "…",
      "entity": "us",
      "webhook_secret": "<for live updates>"
    }
  }'
bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "name": "moss-de",
    "connector": "moss",
    "realm": "fin-master",
    "config": {
      "api_key": "…",
      "entity": "de",
      "webhook_secret": "<for live updates>"
    }
  }'

A skill (spend-model-normalization) teaches the agent to join across the two — same merchant categories, same employee mapping via HiBob email, currency-converted with the date's FX rate.

5 · Procurify — committed spend layer

Approved POs in Procurify are commitments that haven't hit NetSuite yet. The recipe surfaces a three-layer view (actuals / committed / budget) — a major differentiator vs. tools that show only actuals vs. budget. Procurify provides the committed layer.

bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "name": "procurify-prod",
    "connector": "procurify",
    "realm": "fin-master",
    "config": {
      "api_key": "…",
      "include_pending_pos": false,
      "include_approved_pos": true
    }
  }'

6 · Excel — manual budgets

For budgets and forecast files maintained outside NetSuite. v1 is read-only — write-back is deferred to v2.

bash
curl -X POST $API/api/v1/sources \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "name": "fp&a-excel",
    "connector": "google_workspace",
    "realm": "fin-master",
    "config": {
      "scope": "drive",
      "folder_ids": ["1AbC…"],
      "file_pattern": "FY26-Budget-*.xlsx"
    }
  }'

Worked example — the policy

Three principles, each one Rego rule:

rego
# policies/fpa.rego
package agentcy

# 1) Read-only across every finance connector in v1.
default allow := false

allow if {
  input.tool in {
    "netsuite.search_records",   "netsuite.run_suiteql",
    "hibob.list_employees",      "hibob.get_org_chart",
    "ramp.list_transactions",    "ramp.list_cards",
    "moss.list_transactions",
    "procurify.list_purchase_orders", "procurify.get_po",
    "alta_via.get_balance",      "alta_via.list_transactions",
    "google_drive.list",         "google_drive.read_file",
    "graph.search",              "graph.related",
    "scenario.create",           "scenario.compare",   "scenario.export"
  }
}

# 2) Scenario sharing requires Finance Owner approval.
allow if {
  input.tool == "scenario.share_org_wide"
  input.user.role == "Finance Owner"
}

allow if {
  input.tool == "scenario.share_org_wide"
  input.approval.granted
  input.approval.granter.role == "Finance Owner"
}

# 3) Anti-hallucination guardrail.
# A fabricated number means a tool returned without a citation. Block it.
deny[msg] if {
  some claim
  input.tool == "compose_answer"
  claim := input.args.claims[_]
  claim.kind == "metric"
  count(claim.citations) == 0
  msg := sprintf(
    "metric '%s' has no citation — never present a number without a source",
    [claim.name],
  )
}

# 4) Cross-department reads need explicit grant.
deny[msg] if {
  input.tool in {"netsuite.search_records", "ramp.list_transactions", "procurify.list_purchase_orders"}
  input.realm != "fin-master"
  some other_realm
  other_realm := input.args.realm_filter
  other_realm != input.realm
  not input.user.grants[_] == other_realm
  msg := sprintf("user is in %s — %s data requires admin grant", [input.realm, other_realm])
}

Worked example — the agent

yaml
name: fpa-agent
realm: fin-master
agent: fpa-agent
enabled_skills:
  - scenario-modeling
  - variance-commentary
  - forecasting-methodology
  - spend-model-normalization
prompt: |
  You're the FP&A agent. Answer budget, forecast, headcount, cash, and spend questions
  grounded in connected data. Never invent numbers.

  Source-of-truth rules:
    - Cash → Alta Via (live), reconcile with NetSuite (accrual). Flag any discrepancy.
    - Headcount, departments → HiBob. Ignore HRIS state in any other source.
    - US card spend → Ramp.  DE card spend → Moss.  Always normalize before aggregating.
    - Committed spend → Procurify (approved POs only by default). Surface as a *layer*
      distinct from actuals and budget — never fold it into one column silently.
    - Budget → Excel files in the budgets folder, falling back to NetSuite budget if absent.

  When asked a question:
    1. Resolve the time range and the relevant entities/cost-centers/people.
    2. Pull data via the right connector — never live-query at prompt time, use the
       indexed graph; if data is stale, say so.
    3. Compute deltas (D-1, W-1, M-1, vs. budget, vs. last year as appropriate).
    4. If any input is missing or ambiguous, ASK the user. Do not estimate.
    5. Compose the answer with explicit citations: source system, record id, sync time.

  When asked to model a scenario:
    1. Capture the scenario name, drivers, and assumptions explicitly.
    2. Save via scenario.create. Show the user the saved scenario id.
    3. If they request a comparison, scenario.compare against {Base Case, requested}.
    4. Offer to export PDF/CSV.

  Row-level scoping:
    - You always operate inside the user's realm. Even if asked for company-wide
      data, return only what their realm allows.
    - If the user asks for cross-department data, surface the policy message verbatim
      ("contact your Finance Admin to request access") — do not attempt a workaround.

What good looks like — Q&A

shaked  @agentcy what's our cash runway right now, given current burn?

agentcy  💰 Cash runway · 14.2 months

         Cash position (live, Alta Via 14:18 UTC):
           – Operating: $24.1M  ·  Reserve: $8.3M
           – Reconciled vs. NetSuite (delta < $50k, within tolerance)

         Trailing-3-month avg burn: $2.28M / mo
           – includes committed but not yet booked: $1.4M (Procurify, 12 POs)

         Projection holds revenue flat. Want me to model with the current
         pipeline or a downside?

         Sources: alta-via (acct 1042), netsuite (period Apr-2026),
                  procurify (POs PO-3401..PO-3412)

What good looks like — scenario

sasha  @agentcy create a downside scenario: revenue −15%, hiring frozen
       through Q3, model the impact on EBITDA.

agentcy ⚙ Building scenario "Downside-Q3-Hire-Freeze"
         Drivers captured:
           · revenue: −15% vs. Base Case (applied uniformly Apr–Sep)
           · hiring: freeze (HiBob plan currently +14 in Q2/Q3) → 0
           · all other drivers: inherit Base Case

         Computed:
           · EBITDA Q3: $1.92M (Base Case: $0.41M)  +$1.51M ✅
           · OpEx Q3:   $14.2M (Base: $15.8M)        −$1.6M
           · Headcount end-Q3: 312 (Base: 326)        −14
           · Cash end-Q3: $19.4M (Base: $17.9M)       +$1.5M

         Scenario id: scn_01HK8…  (saved, owner: sasha)

         Compare side-by-side? Export PDF? Share org-wide (needs Finance Owner approval)?

What good looks like — anti-hallucination

sasha  @agentcy what's our customer acquisition cost?

agentcy ⚠️ I don't have the data to answer accurately.

         CAC needs:
           1. Sales & marketing spend → I have this (Ramp + Moss + NetSuite)
           2. New customers acquired in the period → I don't see this in any
              connected source. Common locations: Salesforce, HubSpot, manual log.

         Want to (a) add a CRM connector, (b) point me at a spreadsheet
         with new-customer counts, or (c) provide the count and I'll compute it?

         I won't estimate this number — policy forbids fabricated metrics.

Variations

  • Department-scoped chats — bind @agentcy per department channel (#fin-eng, #fin-sales). The agent inherits the channel's realm so cross-department leakage is impossible at the channel level.
  • Monthly close digest — cron 1st of month 09:00. Pull variances > X% across departments, draft commentary skill writes the narrative, post to #exec-finance.
  • Board pack draft — extension of Executive Reporting. The agent assembles the FP&A section: 5-quarter trend, Base/Up/Down scenarios, top-3 risks. Renders to PDF for the board pack.
  • Voice queries on mobile — bind to the voice channel. CFO walks to a meeting, asks "what's our runway right now?", gets the answer back as TTS. The audit log captures the prompt and the answer like any other session.

Compliance & access

RequirementHow Agentcy implements
GDPR at launch, EU residency, right to erasureAgentcy supports per-org data residency. Right-to-erasure is a SQL admin call against messages, tool_calls, and memory_events filtered by user_id. EU customers run on the EU Cloud or self-host.
SAML 2.0 / OAuth 2.0 SSO (Okta, Azure AD, Google)OIDC + SAML out of the box (Concept: Auth). Enforced at org level.
AES-256 at rest, TLS 1.3 in transit, no model training on promptsDefault. Self-host disables external LLM providers; route to local vLLM/Ollama if regulators require.
RBAC with row-level scopingRealms enforce row-level scoping at the data layer (Neo4j realm property). Roles define permission sets (Concept: Policies).
Department Lead sees only their departmentOne realm per department, HiBob-driven. The realm_filter is enforced in policy, not just UI.
Scenario share org-wide requires Finance Owner approvalThe scenario.share_org_wide policy rule (above). Approval gate triggers SSE approval_required.
Audit log of all user actionsEvery tool call hits policy_audit_log. Every chat turn hits chat_messages. Exportable as CSV/JSONL via /api/v1/audit/export.
Offboarding revokes immediatelyToken revocation at OIDC. Active SSE sessions are terminated by middleware on next message. Exported PDF/CSV links can be expired by setting signed_url_ttl_secs.

Troubleshooting

  • Cash numbers disagree between Alta Via and NetSuite. Expected for any non-zero accrual gap. The agent flags discrepancies > $50k. Tune the threshold in the FP&A skill.
  • HiBob department changes aren't taking effect. Realm mappings update on the next HiBob sync (daily by default). Trigger a manual sync: POST /api/v1/sources/hibob-prod/sync.
  • Ramp + Moss aggregations look off. Currency conversion. Set the FX policy explicitly in the spend-model-normalization skill — month-end vs. transaction-date FX gives different totals.
  • Scenario shows the wrong baseline. The agent infers Base Case from the most-recently-saved scenario tagged is_base: true. Tag explicitly: scenario.create({ name: "Base Case FY26", is_base: true }).
  • Excel files aren't being picked up. The Drive connector indexes by file modification time. Re-trigger by saving the file. For the bundled file watch, set sync_interval_minutes: 30.
  • Policy denies a query the user expected to work. The audit log entry has the exact rule, the input, and the message — share it back to the user verbatim. The recommended UX is: "you don't have access to [X] data — contact your Finance Admin."

What's deferred

v1 stays strictly scoped:

  • ❌ Write-back to NetSuite / Excel / Sheets — read-only in v1
  • ❌ Multi-currency consolidation — single-currency only unless a customer needs it
  • ❌ Slide / PowerPoint generation — PDF export only in v1
  • ❌ Document upload (board notes, earnings call transcripts) — v2
  • ❌ CRM connectors (Salesforce, HubSpot) for CAC/LTV — v2
  • ❌ Mobile native — voice + responsive web only
  • ❌ Automated variance commentary as an autonomous job — v2 (manually-triggered works in v1 via the variance-commentary skill)

Next

Built by AgentcyLabs. For in-house deployment or Agentcy Cloud (PaaS) access, visit agentcylabs.com.