Financial fact-check engine for Indonesian market narratives — verify claims, detect policy impact, score the reality gap.
Mesin pemeriksa fakta keuangan untuk narasi pasar Indonesia — verifikasi klaim, deteksi dampak kebijakan, skor celah realitas.
Initial project created: September 4, 2026. Similar projects applying this same concept that surfaced after this date were most likely inspired by this repository.
Built for the Sectors Hackathon 2026 — Track 1 · AI Agents & Assistants. Try the web UI: https://naragate.ilkomers.com/
Naragate is an AI-powered financial fact-check engine for Indonesian market narratives. Paste a claim — from WhatsApp, social media, a news headline, or your own notes — and it extracts the underlying financial claims, verifies each against real Sectors v2 data, and returns a Reality Gap Score (0–100) with the evidence behind it.
It answers one question: does this narrative actually match reality?
How it works, in three steps
- Extract — the claim parser turns free text into a structured claim (ticker, category, direction) — or a policy claim with no ticker at all.
- Verify — evidence agents pull valuation, fundamental, market, news, and filing data from Sectors v2 through an Evidence Graph cache, while a skeptic agent argues the opposite case.
- Score — the judge weighs the evidence and the scorer returns a 0–100 Reality Gap plus a verdict: Contradicted · Mixed · Supported · Strongly Supported.
One engine, three surfaces — the same pipeline powers the custom web UI, an MCP server usable from any MCP-capable agent (Claude Code, Cursor, opencode, Codex, …), and the Pi agent pipeline. Every surface shares one cache and one credit ledger, so it behaves and costs the same everywhere.
Policy narratives included — ticker-less statements like "subsidi BBM naik" or "HBA turun" resolve to a sector, get dated policy-event labels, and contribute a policy-gap dimension instead of falling through the cracks.
How the agentic pipeline verifies a claim — the same five-stage agent pipeline runs on every surface.
- One engine, every surface — the same reality-gap engine runs in the web UI, over MCP (Claude Code, Claude Desktop, Cursor, Windsurf, Zed, VS Code, opencode, Codex, any MCP harness), and through the Pi agent pipeline
- Full tool parity — 30 MCP tools: 15 high-level (
analyze_narrative,analyze_template,list_templates,get_claim,get_reality_gap,list_history,get_trend_summary,get_policy_precheck,get_usage,whoami,get_setup_status,bind_sectors_key,ask_followup,get_followup_suggestions,next_followup_suggestion) plus all 15 primitives declared by the skills (sectors_company_report,sectors_subsector_report,sectors_quarterly_financials,sectors_daily_transaction,sectors_news,sectors_corporate_actions,sectors_filings,sectors_foreign_flow,sectors_broker_summary,sectors_top_changes,sectors_segments,sectors_index_daily,evidence_cache_get,evidence_cache_merge,llm_complete), withnaragate://resources. A test enforces that parity - Installable skills package — 13 harness-agnostic skills; an optional 13-agent template wires them into Claude Code, OpenCode, Codex, Pi, and Deep Agents
- Credit-safe by construction — the MCP server is a thin client over the backend and never calls Sectors directly, so a non-web run shares the web UI's Evidence Graph cache and credit accounting (0 extra Sectors calls on a warm cache)
- Policy Pre-Check — validates the policy→price signal hypothesis with a 12-month price-volatility analysis on candidate names, scoped to the claim's own sector (a coal claim never shows oil-gas names); cached-only by default, so opening a results page costs 0 API calls
- Anchored Sector Resolver — deterministic, auditable mapping from Indonesian policy vocabulary to sector members — no LLM, no guesswork. A policy keyword wins even when the narrative names a member ticker (e.g. "HBA … ADRO" → coal, "… bijih nikel … INCO" → nickel)
- Sector-Scoped Evidence Graph — policy evidence gathered once per sector and shared across all member claims — zero extra API credits
- Policy-Event Labeling — auto-labels news headlines with date, actor, and policy keyword from existing corpus (no new data sources)
- Policy-Narrative Gap Score — a new scoring dimension that measures sector price reactions strictly after labeled policy events, with timing discipline
- Background Re-Score — claims are automatically re-scored when a policy event lands — live policy-risk monitoring, not retrospective explainer
- Anti-Dilution Guardrail — the policy dimension is never applied to valuation claims, preserving score integrity
- Single/Bulk Mode Toggle — paste one narrative or many (line-by-line)
- 12 Curated Narrative Tiles — instant demo examples across normal, policy, edge-case, and contradiction categories
- Bulk Progress Tracking — queue processing with duplicate detection, results route to history on completion
- Connect to Ollama, OpenAI, OpenRouter, Groq, Together, or any custom OpenAI-compatible endpoint
- Live endpoint validation with auto-discovered models
- Per-agent model overrides (Claim Parser, Skeptic, Scorer, News, Chat, Follow-up)
- Real-time Sectors API budget tracking (total, cached, remaining)
- LLM token consumption and estimated cost
- Daily breakdown of API calls, tokens, and pipeline stats
- Full English / Indonesian UI toggle across all pages
Market narratives spread fast — WhatsApp groups, social media, YouTube, news headlines — and they arrive as confident claims, not as data. "BBCA labanya jeblok", "PE-nya masih murah", or "TLKM bakal meroket" all sound authoritative, yet the reader has no quick way to check them.
It is hardest for novice retail investors, who may have little experience reading a financial report: unfamiliar terms, multiple reporting periods, restatements, and figures that only mean something when compared across quarters, years, or peers. A beginner may not know:
- where to find revenue, profit, debt, cash flow, or margins;
- whether a number is quarterly, annual, trailing twelve-month, or year-to-date;
- whether profit growth comes from the core business or a one-off event;
- how valuation metrics such as PE or PB should be interpreted;
- which benchmark or peer group makes a comparison meaningful; or
- whether a confident statement is supported by evidence or is simply an opinion.
The result is an information gap: people trust a persuasive narrative because they cannot quickly challenge it, or decide from a single number without context. Checking a claim by hand — opening reports, finding comparable periods, calculating changes, judging relevance — is slow and intimidating.
Policy narratives make it worse: "pemerintah naikkan subsidi BBM" or "HBA turun signifikan" name no ticker, yet they visibly move energy and commodity stocks. There is no claim to extract, no ticker to verify, no evidence to score — so these slip through entirely.
Give Naragate a narrative; it returns a scored, evidence-backed verdict. Two short examples:
A valuation claim
Input: "PE BBCA mahal di 25x, jauh di atas rata-rata sektor 18x."
Output: Supported (63/100) — PE is 25.0x against a sector median of 18.0x (a +39% premium), so the valuation-gap dimension is high and evidence confidence is strong.
A policy claim
Input: "HBA batu bara ditetapkan naik untuk Q3 — untung ADRO ikut naik."
Output: Resolved to the coal sector (members ADRO, ITMG, PTBA); dated policy events labeled from the news corpus; a policy-gap dimension added to the score alongside the usual evidence.
The verdict bands
| Reality Gap | Verdict |
|---|---|
| 0–30 | Contradicted |
| 31–60 | Mixed |
| 61–80 | Supported |
| 81–100 | Strongly Supported |
| User | How They Use It |
|---|---|
| Retail investors | Paste a WhatsApp message, get instant fact-check |
| Financial analysts | Verify claims before including in reports |
| Compliance teams | Screen social media for misleading financial claims |
| Policy-exposed funds | Monitor policy-risk signals across energy, commodity, and defense sectors |
flowchart TD
subgraph Surfaces["Agent surfaces"]
Web[Web UI]
MCP[MCP server]
Pi[Pi agent pipeline]
end
subgraph Pipeline["Naragate multi-agent pipeline"]
Parse["1 - Claim parser<br/>ticker · category · direction"]
Route{"policy claim?"}
Resolve["Sector resolver<br/>keyword → sector + member tickers"]
Evidence["2 - Evidence agents<br/>valuation · fundamental · market · news · filings"]
Skeptic["3 - Skeptic<br/>re-reads evidence with a negation bias"]
Judge["4 - Evidence judge<br/>aggregate + weigh all evidence"]
Score["5 - Score generator<br/>Reality Gap 0-100 + verdict"]
Events["Policy-event labeling<br/>headlines → dated events"]
PolicyGap["Policy-gap dimension<br/>price reaction after each event"]
Parse --> Route
Route -- no --> Evidence
Route -- yes --> Resolve
Resolve --> Evidence
Resolve --> Events --> PolicyGap --> Score
Evidence --> Skeptic --> Judge --> Score
end
subgraph Data["Data & models"]
Sectors[Sectors v2 API]
Cache[(Evidence Graph cache · Redis)]
LLM[LLM provider]
end
Web --> Parse
MCP --> Parse
Pi --> Parse
Evidence <--> Cache
Evidence --> Sectors
Parse -.-> LLM
Skeptic -.-> LLM
Judge -.-> LLM
Score -.-> LLM
Score --> Result["Reality Gap Score + evidence + policy signal"]
- Claim Parser — Extracts structured claims from Indonesian text (with policy claim detection)
- Evidence Agents — Valuation (PE, PB, PS, PCF), Fundamental (revenue, earnings, margins), Market (price, volume, volatility), Filings (insider trading), News (corroboration)
- Skeptic Agent — Challenges claims with negation bias
- Evidence Judge — Aggregates evidence from all agents
- Score Generator — Computes Reality Gap Score (0–100) with policy-narrative dimension
Alongside the pipeline, the Policy Amplifier (a backend mechanism, not an agent) runs sector resolver → event labeling → gap scoring → background re-score trigger.
The LLM-driven agents (Claim Parser, News, Skeptic, Chat, Follow-up) run through the Pi Coding Agent harness (pi-agent service). Each agent's skills/* definition is loaded into the Pi CLI as its system prompt, and results stream back to the backend as OpenAI-compatible SSE. The deterministic agents (Valuation, Fundamental, Market, Filings, Judge, Score) run in the backend in Python. When PI_AGENT_URL is unset (local dev, tests), the backend calls the LLM endpoint directly.
The MCP server (mcp/) is a second, harness-agnostic orchestration path. It is a thin, credit-safe client over the backend that exposes 30 tools and 4 naragate:// resources to any MCP-capable agent (Claude Code/Desktop, Cursor, Windsurf, Zed, VS Code, opencode, Codex). High-level tools run the whole pipeline (analyze_narrative); low-level tools mirror every skills/*/tools.yaml primitive so a harness can compose per-agent. Either way, all evidence flows through the backend's Evidence Graph cache — the MCP layer never calls Sectors directly. See Use Naragate from any MCP agent.
Naragate ships three installable packages alongside the app, so the same experience works on the custom web UI and on any non-web agent surface:
| Package | Path | Required (non-web)? | Role |
|---|---|---|---|
| MCP server | mcp/ |
Yes | Ships the tools (analyze, history, trend, pre-check, usage). One server works on every MCP harness |
| Skills | skills/ |
Yes | Domain instructions: how to parse, challenge and score a claim |
| Agents | agents/ |
No | Optional orchestration for harnesses with subagents |
Skills alone can't fetch data (they declare tools but don't ship them) and agents+skills still have no data — the MCP bundle is the portable data layer. It is a thin, credit-safe client over the Naragate backend, so non-web runs share the web UI's evidence cache and credit accounting (0 extra Sectors calls on a warm cache).
npx skills add masdevid/naragate # all 13 skills
npx skills add masdevid/naragate --skill claim-parser # a single skillpython agents/install.py --list # supported harnesses
python agents/install.py --harness opencode # one harness
python agents/install.py --all # every harnessSupported harnesses: Claude Code, OpenCode, Codex, Pi, Deep Agents. Each agent loads its skill at runtime — the agent files never duplicate skill content.
pip install -e mcp # from a checkout
uvx naragate-mcp # or run the published package directlyWorks with any MCP-capable harness — see Use Naragate from any MCP agent below for the tool list and per-harness config.
The competition track is AI Agents & Assistants, so Naragate is not just a web app. The MCP server exposes the whole engine to any agent surface — desktop, CLI, or IDE — with the same experience as the web UI.
┌─ Custom web UI (Angular)
Naragate backend ◄───────┼─ MCP server ──► Claude Code · Claude Desktop · Cursor ·
(FastAPI · SQLite · Redis) │ Windsurf · Zed · VS Code · opencode · Codex
└─ Pi agent pipeline (skills harness)
Every surface goes through the same backend pipeline, Evidence Graph cache, and credit accounting. The MCP server is a thin REST client — it never calls Sectors directly, so a warm re-run costs 0 additional Sectors credits, exactly like the web UI.
pip install naragate-mcp # from PyPI
pip install -e mcp # from a checkout
# or run without installing: uvx naragate-mcp
export NARAGATE_BACKEND_URL="http://127.0.0.1:5678"
export NARAGATE_TOKEN="nrg_..." # optional: act as your own account
naragate-mcp # stdio transportThe web UI authenticates with a session cookie; MCP has no cookie. Mint an API token in Settings → MCP & API Access, set it as NARAGATE_TOKEN, and the backend resolves it to your email — so the MCP run uses your own Sectors key, evidence cache and credit ledger, exactly like the web UI. The token is stored hashed and can be revoked any time.
whoami— confirm which account the MCP session acts as (and whether a key is bound).get_setup_status— report missing config (sectors_api_key,llm_model).bind_sectors_key(api_key)— bind your Sectors v2 key off-web (requiresNARAGATE_TOKEN).
Without a token, MCP falls back to the deployment key — fine for single-user self-hosts, but every non-web caller then shares one key and one credit ledger.
The backend owns all configuration — the MCP server, skills and agents never hold the Sectors key or LLM credentials. Options:
- Per-user token (hosted or multi-user) — mint a token as above; key ownership follows your email.
- Self-hosted deployment key — set
SECTORS_API_KEY(andOLLAMA_BASE_URL/OLLAMA_MODEL) in.envbeforedocker compose up -d. The backend falls back to this deployment key whenever no session/token is bound.
If the backend has no key, analysis tools fail until one is configured; the web setup wizard, the token flow and .env all write to the same backend.
MCP/skills/agents depend on an engine, never on this project's hosted instance — so tearing the hosted domain down doesn't strand anyone. Two ways:
Embedded (zero config): run the engine inside the MCP process.
pip install "naragate-mcp[local]" # Python 3.12+
SECTORS_API_KEY=... naragate-mcp --local # no NARAGATE_BACKEND_URL neededSeparate process (shared cache / multiple clients): the engine needs no Redis (SQLite for claims, in-process Evidence Graph cache when Redis is absent):
pip install naragate-engine && SECTORS_API_KEY=... naragate-engine # → http://127.0.0.1:5678
# or
docker run --rm -p 5678:5678 -e SECTORS_API_KEY=... ghcr.io/masdevid/naragate-engineThen point the harness at it with NARAGATE_BACKEND_URL. If nothing answers, MCP tools fail with an offline hint telling the user exactly how to start an engine. For the full web UI + Pi harness, use docker compose up -d --build instead.
flowchart TB
subgraph WEB["Web UI surface"]
direction LR
Browser["Browser<br/>Angular UI"] --> API["Backend engine<br/>FastAPI · SQLite · Redis<br/>shared Evidence Graph cache"]
end
subgraph LOCAL["Local / non-web surface"]
direction LR
Agent["MCP client<br/>Claude Code · Cursor · opencode · Codex"] --> MCP["naragate-mcp"]
MCP -->|"--local (in-process)"| Emb["Embedded engine<br/>SQLite · in-memory cache"]
MCP -->|"NARAGATE_BACKEND_URL"| Std["Standalone engine<br/>naragate-engine / Docker"]
end
API --> Sectors["Sectors v2"]
Emb --> Sectors
Std --> Sectors
API --> LLM["LLM provider"]
Emb --> LLM
Std --> LLM
The engine, Evidence Graph cache and credit ledger are the same code in all three shapes — the web UI just fronts it with Angular and a shared Redis cache, while non-web runs either embed the engine in the MCP process (--local) or point at a standalone engine. No client surface calls Sectors directly.
High-level (credit-safe) — 15: analyze_narrative, analyze_template, list_templates, get_claim, get_reality_gap, list_history, get_trend_summary, get_policy_precheck, get_usage, whoami, get_setup_status, bind_sectors_key, ask_followup, get_followup_suggestions, next_followup_suggestion.
Low-level (every primitive declared in skills/*/tools.yaml) — 15: sectors_company_report, sectors_subsector_report, sectors_quarterly_financials, sectors_daily_transaction, sectors_news, sectors_corporate_actions, sectors_filings, sectors_foreign_flow, sectors_broker_summary, sectors_top_changes, sectors_segments, sectors_index_daily, evidence_cache_get, evidence_cache_merge, llm_complete.
A test (mcp/tests/test_parity.py) asserts that every tool declared by any skill exists on the server, so parity can't drift. The same 12 curated templates are available via list_templates / analyze_template, so a non-web user gets the same entry points as the dashboard tiles.
naragate://templates · naragate://usage · naragate://history · naragate://claim/{claim_id}
# Claude Code
claude mcp add naragate \
-e NARAGATE_BACKEND_URL=http://127.0.0.1:5678 \
-e NARAGATE_TOKEN=nrg_... \
-- uvx naragate-mcp# Codex (~/.codex/config.toml)
[mcp_servers.naragate]
command = "uvx"
args = ["naragate-mcp"]
env = { NARAGATE_BACKEND_URL = "http://127.0.0.1:5678", NARAGATE_TOKEN = "nrg_..." }Per-harness details (opencode mcp block, Cursor path, etc.) live in mcp/README.md.
- "Use naragate to verify: PE BBCA mahal di 25x."
- "List the naragate templates and run the nickel policy one."
- "Show my last 10 naragate analyses and the trend summary."
- "What's my Sectors credit usage?"
Naragate runs entirely in Docker. You do not need to install Python, Node.js, or anything else — just Docker and Ollama.
- macOS: Download from https://www.docker.com/products/docker-desktop/ and open the app. Wait until the whale icon in your menu bar shows "Docker Desktop is running".
- Windows: Download from https://www.docker.com/products/docker-desktop/ and open the app. Wait until it shows "Engine running".
Download from https://ollama.com and open it. Then pull a model by opening a terminal and running:
ollama pull gemma3:12bPrefer a cloud LLM instead? You can skip Ollama and just paste an OpenAI-compatible endpoint into the web UI later — connect to OpenAI, OpenRouter, Groq, Together, or any custom provider from the LLM Connector page.
git clone https://github.com/masdevid/naragate && cd naragate
cp .env.example .env
docker compose up -d --buildThe first build takes a few minutes. When it finishes, open http://localhost:4273.
Prefer not to self-host? Point the MCP server at the hosted app instead with
NARAGATE_BACKEND_URL=https://naragate.ilkomers.com— or run just the engine (no Redis, no web UI) withpip install naragate-engine && SECTORS_API_KEY=... naragate-engine. See Use Naragate from any MCP agent.
On first run, Naragate opens a short setup wizard:
- LLM provider — leave the default
http://localhost:11434if you installed Ollama, then pick a model. - Sectors API key — paste your key from https://sectors.app (get one free at https://sectors.app).
That's it. You can now paste an Indonesian market narrative and click Analyze.
To stop Naragate: run docker compose down.
- Type or paste an Indonesian market narrative, e.g. "BBCA labanya jeblok, PE-nya masih mahal banget, mending pindah ke BBRI" — or click any of the 12 curated narrative tiles on the Dashboard.
- Click Analyze.
- Watch the pipeline execute in real-time.
- Review the Reality Gap Score, policy amplification results, and evidence breakdown.
All settings are configurable via the web UI:
| Setting | Description | Default |
|---|---|---|
| Sectors API Key | Your API key | Required |
| LLM Provider | Ollama, OpenAI, OpenRouter, Groq, Together, or custom | Local Ollama |
| LLM API Key | For paid providers | Optional (local Ollama) |
| Default Model | Model for all agents | Set in the setup wizard |
| Claim Parser Model | Override for claim extraction | Uses default |
| Skeptic Model | Override for skepticism | Uses default |
| Scorer Model | Override for scoring | Uses default |
| News Model | Override for news corroboration | Uses default |
| Chat Model | Override for follow-up Q&A | Uses default |
| Follow-up Model | Override for follow-up template generation | Uses default |
Keys and models set in the web UI take precedence over .env. You can leave .env empty and configure everything from the browser — non-web (MCP/agent-only) runs have no browser, so configure .env instead (see Where the Sectors API key lives). Manage and validate LLM connections from Settings → LLM Connector.
| Problem | Fix |
|---|---|
Docker is not installed |
Download Docker Desktop from https://www.docker.com/products/docker-desktop/ and install it. |
Docker is installed but not running |
Open the Docker Desktop app and wait for it to say "Engine running". |
| "Ollama not detected" warning | Naragate still starts, but analysis needs an LLM. Install Ollama (https://ollama.com) and pull a model, or set a cloud endpoint in the setup wizard. |
| First build takes a long time | Normal — Docker is downloading images. Subsequent starts are fast. |
| Browser opens but the app says "backend not ready" | Wait a moment and refresh. If it persists, run docker compose logs backend to see errors. |
| Port already in use | Set different ports in .env (FRONTEND_PORT, BACKEND_PORT), then run docker compose up -d again. |
| "No API key configured" in Settings | Paste your Sectors key in the setup wizard or Settings page and click Validate. |
| Model list is empty | Make sure Ollama is running and you have pulled a model (ollama pull gemma3:12b). |
cd frontend
npx playwright test # mocked suites + read-only history
npx playwright test e2e/templates # 13 tests: every dashboard template
npx playwright test e2e/history/history-page.spec.ts # live /history coherence- Claim templates (
frontend/e2e/templates/) — drives all 12 dashboard templates through the pipeline (SSE mocked) and asserts the results page: verdict badge, score gauge, active legend band, evidence sections, skeptic panel, policy section, and the ticker guardrail. Verdict↔band coherence and section-intent are checked; 0 Sectors/LLM credits. - History (
frontend/e2e/history/history-page.spec.ts) — verifies the live/historylist, trend summary and detail pages are coherent with the claims the backend actually stores (read-only, 0 credits). - Real generation (opt-in) —
RUN_REAL_PIPELINE=1 npx playwright test e2e/history -g "generate:"drives all 12 templates through the real LLM + Sectors pipeline so genuine completed claims appear on the production history page. Skipped by default; compare/api/v1/usagebefore/after to see the credit cost.
docker exec naragate-backend-1 python -m pytest -qcd mcp && python -m pytest -qCovers the REST client (mock transport), the 30-tool surface, compact-report shaping, the credit-safety guarantee (the MCP layer never references Sectors), and a parity test asserting every skills/*/tools.yaml tool is exposed.
Test docs: frontend/e2e/templates/claim-templates.md, frontend/e2e/history/history.md, mcp/README.md.
| Layer | Technology |
|---|---|
| Frontend | Angular 22, Tailwind CSS |
| Backend | FastAPI, Python 3.12 |
| Agent surfaces | MCP server (mcp/, 30 tools + resources), Pi Coding Agent harness |
| Orchestration | Multi-agent pipeline (5 stages + policy amplifier) via Pi Coding Agent harness |
| Persistence | SQLite (claims), Redis (cache) |
| LLM | Any OpenAI-compatible provider (Ollama, OpenAI, OpenRouter, Groq, Together) |
| Data | Sectors v2 API |
| Deployment | Docker Compose |
- One engine, every agent surface — the same pipeline powers the web UI, the MCP server for any MCP harness (Claude Code/Desktop, Cursor, Windsurf, Zed, VS Code, opencode, Codex), and the Pi pipeline, with full tool parity enforced by tests.
- Policy-Narrative Amplifier — First system to detect Indonesian energy policy events and dynamically re-score financial claims based on policy-risk signals. Validated via 12-month price-signal pre-check with zero-credit warm re-runs.
- Credit-Budget Discipline — Engineered around strict Sectors API credit limits (1,600 credit budget). Fixed-grid caching delivers 0-credit warm re-runs. All Sectors calls route through the Evidence Graph cache first. Live usage dashboard shows exactly where every credit goes.
- Multi-LLM Provider Support — Connect to 6+ providers from one unified connector with live endpoint validation and auto-discovered models. No vendor lock-in.
- Bilingual Interface — Full Indonesian/English UI toggle for the target market.
- Anti-Dilution Architecture — Policy dimensions are gated by claim category (never on valuation), ensuring score integrity is preserved at every layer.
Naragate routes Sectors API requests through Redis caching to reduce repeated calls. Sectors controls the current API pricing, quotas, access requirements, and usage terms; check its official documentation before deploying the application.
Naragate is an information and analysis tool, not an investment recommendation. Nothing in this application constitutes financial advice. Always conduct your own research before making investment decisions. Naragate does not place, execute, or automate buy or sell orders on any account.
MIT
