Appearance
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
HiBob
Ramp
CFO asks
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
| Layer | What we use |
|---|---|
| Triggers | Chat, Slack mention, Cron for the daily/weekly digest |
| Connectors | NetSuite, HiBob, Ramp, Moss, Procurify, Alta Via, Excel via Google Workspace or OneDrive |
| Realms | One per department (fin-eng, fin-sales, fin-marketing, fin-rnd, fin-master) — drives row-level scoping |
| Agent | FP&A agent with skills for scenario-modeling, variance-commentary, forecasting-methodology |
| Policies | Rego — read-only on every source · scenario share requires approval · cross-department reads require explicit grant |
| Memory | Saved 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:
- Natural-language Q&A — "What is our burn rate vs. budget this quarter?" / "Summarize the key OpEx variances for April." Answers cite source records.
- Auto-generated dashboards — pin any query result to a personal or shared dashboard. Refresh on connector sync.
- 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
@agentcyper 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
| Requirement | How Agentcy implements |
|---|---|
| GDPR at launch, EU residency, right to erasure | Agentcy 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 prompts | Default. Self-host disables external LLM providers; route to local vLLM/Ollama if regulators require. |
| RBAC with row-level scoping | Realms enforce row-level scoping at the data layer (Neo4j realm property). Roles define permission sets (Concept: Policies). |
| Department Lead sees only their department | One realm per department, HiBob-driven. The realm_filter is enforced in policy, not just UI. |
| Scenario share org-wide requires Finance Owner approval | The scenario.share_org_wide policy rule (above). Approval gate triggers SSE approval_required. |
| Audit log of all user actions | Every tool call hits policy_audit_log. Every chat turn hits chat_messages. Exportable as CSV/JSONL via /api/v1/audit/export. |
| Offboarding revokes immediately | Token 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-normalizationskill — 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-commentaryskill)
Next
- Concept: Knowledge Graph & Realms — how row-level scoping is enforced at the data layer
- Concept: Zero-Trust Policies — Rego rules, audit log, role definitions
- Concept: Skills — how the scenario-modeling and variance-commentary skills work
- Recipe: Executive Reporting — same data, exec-brief output instead of conversational
- Recipe: FinOps cost watch — sibling recipe for cloud cost (vs. company-wide spend)