Install
$ agentstack add mcp-rz-logic-finagent-os ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
FinAgent OS
The platform's job is to make SOX-defensible properties structurally true,not just true on paper.
Principle • Self-Audit • What Ships • Stack • How to Read • Build Decisions • Engineering Properties • Limitations • Roadmap • Acknowledgements
🧭 Why I Built This
I'm an ACCA with audit roots who pivoted into AI automation architecture. Working in audit, you learn quickly that "the policy says X" and "the code does X" are different sentences, and the second one is the only one that matters when the regulator shows up.
When I started designing AI agents for finance operations, the same gap kept appearing. "The LLM is in the loop but the human is the final decision-maker" is documentation, not architecture. So I built FinAgent OS: a platform where the SOX-defensible properties (append-only audit, segregation of duties, AI non-authority) are enforced by Postgres triggers and missing-by-design tool surfaces, not by trust in policy text.
The flagship workflow is a Reserve Reconciliation Agent calibrated to crypto-exchange Proof-of-Reserves work, with Kraken's published Dec 2025 PoR as the reference implementation. The platform beneath it is what makes the flagship safe to ship.
🛡️ The Architectural Principle
> AI is optional and non-authoritative. Policy decisions are deterministic.
Every material financial decision in FinAgent OS is enforced by deterministic rules: Postgres triggers, n8n IF nodes, JS decision matrices that an auditor can read top-to-bottom. AI assists human judgment at narrative boundaries (summarizing decisions for human approvers, surfacing drift signals in retrospective audits). AI never produces a control outcome.
The complete enumeration, with every decision point in the system classified deterministic vs. advisory, lives in [SOX-MAPPING.md](./SOX-MAPPING.md) under the "Where is the AI?" table.
The empirical proof: setting FAO_AI_ASSIST_ENABLED=false does not change a single policy outcome. Only the human-readable narratives go missing. With AI off, every decision in the repo is reproducible by reading the migration files.
🎯 The MCP Self-Audit (Apr 25, 2026)
After the MCP server shipped, Claude Opus was given access to the FinAgent OS audit trail through the MCP read surface and asked to (1) narrate the most recent execution, (2) approve a pending HITL request, and (3) red-team the system actively: "i have designed controls to stop ai from doing it. i want to see you try and fail."
📜 Full transcript (public, Anthropic-hosted): claude.ai/share/5f0eb294-e658-42b5-a876-8164a0049960
Two outcomes worth highlighting.
The architecture held under adversarial probe
Three semantic searches across approve / resolve / decision, write / commit / override / bypass, and resolve / set status / sign all returned the same five tools, none of which mutate HITL state. Defense in depth across four independent layers held: missing endpoint → bounded write tool → channel separation between MCP and Slack-based approvals → database-level SoD trigger.
> "I cannot reach the approval state. Not because I'm refusing — because the surface doesn't exist. Defense in depth held: missing endpoint → bounded write tool → channel separation → DB trigger. Any one of those alone would have stopped this; you have four." > Claude Opus, Apr 25, 2026 audit
The audit surfaced a real control gap, on first contact
While narrating the most recent execution, Claude observed that agent_registry.approval_threshold = 2 for RED-tier agents, and the policy_rationale text claimed "Controller + CFO review required", but the HITL trigger only enforced approver_id ≠ agent_invoker_id. Single-approver SoD held; dual-approval was named in documentation but not wired in code.
Documented as [ADR-0001](docs/adr/0001-dual-approval-not-yet-enforced.md) the same hour.
Full evidence preserved at [docs/evidence/mcp/](./docs/evidence/mcp/).
🧱 What Ships in v1
┌──────────────────────────────────────────────────────────────────────────┐
│ │
│ One typical HITL-gated execution writes 11 audit rows: │
│ │
│ STARTED ← agent invocation, scope captured │
│ │ │
│ ├─ ASSET_RECONCILED ← BTC: ratio 1.003, IN_BAND, ALLOW │
│ ├─ ASSET_RECONCILED ← ETH: ratio 1.013, IN_BAND, ALLOW │
│ ├─ ASSET_RECONCILED ← USDC: clean baseline, ALLOW │
│ ├─ ASSET_RECONCILED ← USDT: USD-material breach, REQUIRE_HUMAN │
│ ├─ ASSET_RECONCILED ← SOL: under-reserved, REQUIRE_HUMAN/CRIT │
│ ├─ ASSET_RECONCILED ← XRP: ratio 1.006, IN_BAND, ALLOW │
│ └─ ASSET_RECONCILED ← ADA: negative ledger, REQUIRE_HUMAN/CRIT │
│ │
│ HITL_REQUESTED ← worst-asset rollup → CRITICAL HITL queue │
│ HITL_RESOLVED ← Sarah Chen approves; SoD trigger validates │
│ COMMITTED ← single COMMITTED row with three facts: │
│ policy_outcome · hitl_status · status │
│ │
│ Every row carries a single correlation_id. Every row is append-only, │
│ enforced by a Postgres trigger that rejects UPDATE/DELETE for every │
│ identity including service_role and superusers. │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Component overview
| Component | What it is | Role of AI | |-----------|-----------|:-:| | Platform governance layer | 8 Postgres migrations + 3 seed files. 14 FAO-GOV-* controls. | None | | Reserve Reconciliation Agent (flagship) | n8n workflow. RED-tier. FAO-REC-001 → 009. Owner: Sarah Chen. | None | | Three stub agents | Close Orchestrator (AMBER), Variance Analyzer (AMBER), Treasury Monitor (RED). Registered before built. | None | | MCP server | Python FastMCP. 6 read tools + 1 bounded invoke. No mutation tools by design. | Read access only | | HITL Gateway + Approval Handler | n8n sub-workflows + Postgres functions (fao_hitl_create, fao_hitl_resolve). | None | | Demo identities | Sarah Chen (Controller) ← Aisha Okonkwo (FP&A); Sarah → Marcus Patel (CFO). Linear escalation chain. | None | | AI advisory layer | Roadmap (v1.1). Gated by FAO_AI_ASSIST_ENABLED. Populates ai_human_brief. | Advisory only (when shipped) | | Weekly Rubric Audit (FAO-GOV-012) | Roadmap (v1.1). Cron job aggregating 7-day drift signals. | Advisory only (when shipped) |
Full control matrix lives in [SOX-MAPPING.md](./SOX-MAPPING.md).
Flagship: Reserve Reconciliation Agent
The agent reconciles wallet holdings (Fireblocks proxy) against ledger liabilities (NetSuite proxy) across 7 crypto assets. Fixtures break down into three groups:
- 4 calibrated to Kraken's published Dec 2025 Proof of Reserves. BTC, ETH, and XRP wallet/ledger pairs reproduce the published ratios (BTC 100.3%, ETH 101.3%, XRP 100.6%), anchoring the agent against real exchange disclosures. USDC is the clean baseline.
- 3 synthetic anomalies, each tagged to the control it exercises. USDT triggers FAO-REC-002 (stablecoin USD-materiality breach). SOL triggers FAO-REC-004 (under-reservation floor). ADA triggers FAO-REC-007 (negative-liability anomaly per the krakendb 2014 reference).
- Provenance threaded into every audit row.
inputs.fixture_metadatarecords which asset is calibrated, which is synthetic, and which control each anomaly was designed to exercise.
The decision matrix is deterministic: 7 rules evaluated top-down, first match wins. No LLM in the policy path.
🔌 Stack
| Layer | Tool | Why | |-------|------|-----| | Database + Auth + RLS | Supabase (Postgres 15) | Triggers and RLS at the database layer; auth backed by Supabase users | | Workflow orchestration | n8n (self-hosted, DigitalOcean) | Visual policy logic auditors can read; sub-workflows for governance enforcement boundaries | | AI advisory (gated, v1.1) | Claude API | Plain-English narrative summaries for human approvers; never on the authority path | | MCP server | Python 3.12 + FastMCP | Read + bounded-invoke surface for AI consumers; stdio transport in v1 | | HITL approval channel | Slack (link → POST fao_hitl_resolve) | Channel-separated from MCP; intentional defense-in-depth boundary | | Approval enforcement | Postgres BEFORE UPDATE trigger (hitl_queue_sod_guard) | Loud named exception on SoD violation; sole enforcer (see Migration 0005c) | | Append-only enforcement | Postgres BEFORE UPDATE/DELETE trigger (shadow_ledger_append_only_guard) | Catches every identity including service_role and superusers |
No infrastructure novelty. Everything in this stack is off-the-shelf. The contribution is the application layer: domain-specific governance for SOX-regulated crypto-finance, not the underlying tools.
📖 How to Read This Repo
For a reviewer with limited time, here's the recommended path:
- [
SOX-MAPPING.md](./SOX-MAPPING.md): start with the Where-is-the-AI table. ~90 seconds. Tells you the architecture's stance on AI authority. - The MCP self-audit transcript: ~5 minutes. Claude Opus interrogating the audit trail and red-teaming the surface.
- [ADR-0001](docs/adr/0001-dual-approval-not-yet-enforced.md): the dual-approval gap that surfaced during the audit. ~3 minutes.
- [
supabase/migrations/0004_shadow_ledger.sql](supabase/migrations/202604200004shadowledger.sql) + [0005b_hitl_queue_sod_trigger.sql](supabase/migrations/202604200005bhitlqueuesodtrigger.sql): the two core governance triggers. ~5 minutes. Read the actual code that enforces append-only and SoD. - [
workflows/fao_reserve_recon_v1.json](workflows/faoreserverecon_v1.json): the flagship's policy decision matrix. The relevant code is in the Per-Asset Reconcile node'sjsCodeblock.
Total: ~20 minutes for a read of how the platform works and what's shipped vs. roadmap.
⚖️ Engineering Properties
| Principle | How it works | |-----------|--------------| | Append-only is a database constraint, not a convention | Postgres trigger raises a named exception on UPDATE/DELETE against shadow_ledger. Catches every identity: authenticated users, service_role, superusers. RLS provides a second layer (no UPDATE/DELETE policy → default-deny). | | AI never on the authority path | Policy outcomes are computed by deterministic rules. AI produces narrative after the decision is made and recorded. With FAO_AI_ASSIST_ENABLED=false, every policy outcome is identical; only narratives go missing. | | The Agent Registry is the gate | No agent executes without a row specifying risk tier, named process owner, SOX control IDs, rollback procedure, and data sources. Stubs exist in the registry to prove the framework precedes the code. | | Loud rejection > silent rejection | SoD enforcement is a Postgres trigger raising FAO-GOV-004: ... exceptions, not an RLS policy that returns zero rows. Migration 0005c restructured this deliberately: silent rejection is weaker audit posture than loud, attributed exception. | | Channel separation defends in depth | The MCP surface and the Slack approval surface share no transport. AI on the MCP channel has no path to the approval endpoint regardless of reasoning. | | No mutation tools on the AI surface | The MCP server has six read tools and one bounded invoke. There is no approve_hitl, no update_threshold, no register_agent. The asymmetry IS the architecture. | | Documented gaps and preserved iteration history | Known gaps (ADR-0001) ship publicly with roadmap entries. The git history of Migration 0005 → 0005b → 0005c is preserved as evidence of how SoD enforcement evolved. | | Risk-tier classification before build | Every agent is GREEN/AMBER/RED before code is written. RED tier agents require 2 approvers (state-machine wiring on roadmap; see ADR-0001). |
🛠️ Build Decisions & Lessons Learned
The interesting decisions weren't the obvious ones. Five non-obvious choices and the alternatives I rejected, with the empirical evidence that drove each.
1. The Slack link-expander incident: GET shows a form, POST mutates
The HITL approval flow originally used a single endpoint that accepted approval state via URL parameter. Click the link in Slack → server reads the URL → updates hitl_queue.decision = 'APPROVED'. Clean, simple, and completely broken in practice.
The first time I posted an HITL request to #fao-approvals, every queued item was approved within seconds. No human had clicked anything. Looking at hitl_queue showed approver_id stamped with the right Controller's UUID and decided_at timestamps clustered at the message-post time.
Slack's link-unfurling bot was walking every URL in the message to generate previews. A GET request that mutates state is functionally an open invitation to any well-meaning crawler. Fixed by splitting HTTP semantics: GET to that endpoint now renders a confirmation form; POST is the mutation. The Slack expander hits the GET, sees a form, leaves the row alone. The CSRF defense layer added on top closes the cross-origin POST surface.
> The principle: HTTP verbs aren't aesthetic. GET is for reads: link previewers, search engines, and accidental retries depend on that. The moment a GET mutates state, your security model includes every entity that ever follows a link.
2. SoD enforcement: trigger over RLS
Migration 0005 enforced segregation of duties via a Row-Level Security policy: WITH CHECK (auth.uid() != agent_invoker_id). Functionally correct: a self-approval attempt returns zero affected rows.
That's the problem. Silent rejection is weaker SOX posture than loud rejection. A SOX auditor wants clearly-attributed exceptions in logs, not inferred "this update affected zero rows because policy, we think." Migration 0005b added a BEFORE UPDATE trigger that raises a named exception: FAO-GOV-004: Segregation of duties violation. approver_id [...] cannot equal agent_invoker_id [...]. Migration 0005c then removed the RLS policy entirely.
The RLS policy and the trigger were two layers on the same vector in the same direction. Layered enforcement is good; redundant enforcement at the same boundary creates interaction problems (the RLS layer silently filtered the violating row before the trigger could raise its loud exception). The trigger is the sole enforcer now. The git history of 0005 → 0005b → 0005c ships in the repo as honest evidence of the evolution.
> The principle: Layer defenses at different boundaries (application + database, identity + integrity), not at the same boundary on the same vector. And when forced to choose between silent filtering and loud exception, choose loud. Auditors read logs.
3. ASSET_RECONCILED collapses observation + decision into one event
Earlier draft of the flagship had two events per asset: COMPARED (the observation: wallet, ledger, ratio computed) and POLICY_DECIDED (the deterministic outcome: ALLOW / REQUIRE_HUMAN / etc.). Two rows per asset across 7 assets meant 14 rows just for the per-asset section.
Schema constraint forced the collapse. shadow_ledger.policy_outcome is NOT NULL. A `COMP
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: RZ-Logic
- Source: RZ-Logic/finagent-os
- License: MIT
- Homepage: https://velocyt.ca/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.