Documentation प्रलेखन
Trinetra is sovereign border & immigration intelligence software for the agencies of Bharat.
Operator console at /dashboard/. Public OSINT pipeline (BSF · CBI · Indian Kanoon ·
GDELT · RSS). Semantic search via Gemini embeddings. Co-indexed in a local Delphi
research server for cross-corpus reasoning.
What is Trinetra?
Trinetra is a Palantir-class intelligence platform engineered for India's border & immigration agencies. It ingests only public OSINT — no surveillance data, no Aadhaar cross-reference, no facial recognition. The product is a graph of entities mentioned in lawful public records (BSF press releases, court judgments, named-accused CBI cases, curated news) and the operator workflow that turns those mentions into usable leads.
What it does
- Daily operational brief — tempo, elevated modus, top districts
- Entity dossiers with risk score, timeline, geography, connections
- Semantic RFI — ask the corpus in plain English
- Watchlist alerts when pinned entities appear in fresh docs
- Cross-source leads with reasoning + suggested next action
- Investigator workspace — search, case lens, notes, CSV/JSON export
What it explicitly is not
- Not a mass-surveillance system
- Not connected to Aadhaar, CCTNS, NATGRID, or biometric stores
- Not facial recognition or face-search
- Not identity-targeting — events are tagged by modus, not community
- Not a substitute for warranted intercepts or judicial process
Quickstart (local)
Three commands, two terminals.
# 1. install
git clone https://github.com/aayambansal/trinetra.git
cd trinetra/pipeline && python3 -m pip install -r requirements.txt
# 2. set the Gemini key (for semantic embeddings)
echo "GEMINI_API_KEY=your_key_here" >> ../.env
# 3. ingest + extract + resolve + graph + agents + embed
python3 -m trinetra run # OSINT ingest + classic graph (one shot)
python3 -m trinetra embed # Gemini embeddings for semantic RFI
In a second terminal:
# API + dashboard
python3 -m trinetra serve
# → http://localhost:8000/ (password: aayam123)
For semantic search via Delphi (optional but recommended):
# requires Docker Desktop
git clone -b hypothetical https://github.com/synthetic-sciences/delphi.git ../delphi
cd ../delphi && cp env.example .env
# edit .env: set SYSTEM_PASSWORD, EMBEDDING_PROVIDER=gemini, GEMINI_API_KEY=...
docker compose --profile dashboard up -d --build
# bootstrap auth + push the corpus
curl -s -X POST http://localhost:8742/api/bootstrap -d '{"password":"<your password>"}'
# copy the returned api_key into trinetra/.env as DELPHI_API_KEY
cd ../trinetra/pipeline && python3 -m trinetra delphi-push
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ OPERATOR CONSOLE │
│ /dashboard/ · 16 static pages · shared shell + sidebar │
│ ▼ │
└────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ TRINETRA API (FastAPI) │ │ STATIC JSON EXPORTS │
│ /api/health │ │ /dashboard/data/*.json │
│ /api/rfi (structured) │ │ stats, nodes, edges, │
│ /api/rfi/semantic │ │ events, briefs, dossiers, │
│ /api/rfi/delphi │ │ modus, districts, ... │
│ /api/similar/{id} │ │ (148 docs, 152 entities) │
│ /api/dossier/{id} │ │ │
│ /api/audit · /api/run │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
│ ▲
▼ │
┌────────────────────────────────────────────────────────────────┐
│ PIPELINE (python -m trinetra ...) │
│ ingest → extract → resolve → graph → export → agents → embed │
│ │
│ SQLite (documents, mentions, entities, edges, events, │
│ embeddings, delphi_papers, ingest_runs) │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ DELPHI (synthetic-sciences/delphi · docker) │
│ PostgreSQL + pgvector · Gemini embeddings │
│ /v1/search/papers · /v1/research · cross-encoder rerank │
│ 148 corpus PDFs indexed for research-grade retrieval │
└────────────────────────────────────────────────────────────────┘
Login
Open localhost:8000/ or the deployed URL. Click LOGIN → password aayam123 → dashboard.
Overview & brief
Overview shows live stat tiles (documents, entities, edges, events, mentions) and surfaces today's operational brief above the recent intel feed. The brief panel links to /dashboard/briefs.html for the full readout — 7d/30d/90d tempo, modus elevated above baseline, top districts, fresh entities. Exportable as JSON or Markdown for the morning meeting.
RFI: ask the corpus
The RFI page (Request for Information) runs three search lanes in parallel against your query:
| Lane | Engine | What it's good at |
|---|---|---|
| Structured | Regex parser → SQL filter | Exact modus, district, source, time window. Sub-millisecond. |
| Semantic | Gemini 3072-dim embeddings, brute-force cosine | Concept search. "Find documents about cross-border infiltration networks" returns the right docs even when the literal phrase isn't there. Sub-second. |
| Delphi | pgvector + cross-encoder rerank | Research-grade retrieval over chunked PDFs. Slower (~6s) but more precise for long-form questions. |
Example queries the parser handles:
cattle smuggling in Murshidabad last 6 monthsForeigners Tribunal cases in Assam this yearhawala in West Bengal last 90 daysdrone recovery Punjab
Entity dossiers
Every resolved entity has a dossier: /dashboard/dossier.html?id=<N>. A dossier is one screen with everything an analyst would write up by hand:
- Identity card · risk score (0-100) · risk factors
- Monthly timeline of appearances (heat cells)
- Geographic footprint on a dark map
- Modus distribution chips
- Connected entities, ranked by edge weight
- Source documents — every claim back to its URL
The risk score weighs: cross-source presence × network degree × time span × mention count. Institutional names (Modi, Shah, etc.) are filtered out of the operational lists.
Alerts & watchlist
Watchlist stores entities you've pinned (★ button on any dossier or graph node). Alerts shows fresh hits on watched entities, plus derived signals: elevated modus, novel districts, cross-source leads. Notes per entity persist locally and export with the watchlist.
Case lens
Cases is the investigator workspace. Search across every
document. Click a result → drawer with all source docs, neighbours, notes, watch toggle,
JSON/CSV export. FIND SIMILAR queries the semantic index for similar
documents — a "more like this" surface that BSF analysts use to follow a thread.
Network · map · timeline
Network renders the entity graph in Cytoscape — kind filter, search, inspector. Map plots geocoded events on a dark CARTO base, modus-coloured pins. Timeline stacks monthly events by modus.
Pipeline
# Stages, individually
python3 -m trinetra ingest # all enabled sources
python3 -m trinetra ingest indian_kanoon # one source
python3 -m trinetra extract --force # re-extract mentions + events
python3 -m trinetra resolve # rebuild entities
python3 -m trinetra graph # rebuild edges
python3 -m trinetra export # write JSON for dashboard
python3 -m trinetra analyze # repeat-persons / hubs / anomalies
python3 -m trinetra agents # brief / dossier / modus / district / cross-source / RFI
python3 -m trinetra embed # Gemini embeddings
python3 -m trinetra delphi-push # push corpus to Delphi
python3 -m trinetra status # counts + last runs
python3 -m trinetra run # everything end-to-end
Agents
Six deterministic agents — no LLM hallucination — each producing a JSON artifact:
| Agent | Output | Page |
|---|---|---|
brief | Today's operational readout | /dashboard/briefs.html |
dossier | Per-entity dossier (1 file each) | /dashboard/dossier.html?id=N |
cross_source | Multi-source leads with reasoning | /dashboard/alerts.html |
modus | Per-modus situation reports | /dashboard/modus.html |
district | Per-district situation reports | /dashboard/districts.html |
rfi | NL query parser + preset answers | /dashboard/rfi.html |
API reference
Trinetra API runs on :8000 when you python -m trinetra serve.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/health | Counts + last ingest timestamp |
| POST | /api/rfi | Structured RFI: {"query": "...", "limit": 60} |
| GET | /api/rfi/parse?q=… | Show the parser output without running the query |
| POST | /api/rfi/semantic | Gemini semantic search: {"query": "...", "k": 25} |
| POST | /api/rfi/delphi | Delphi paper search (proxy): {"query": "...", "k": 15} |
| GET | /api/similar/{id}?k=8 | Documents similar to id |
| GET | /api/dossier/{id} | Live entity dossier |
| GET | /api/audit | Server-side audit ring (last 500) |
| POST | /api/run | Admin pipeline rerun (header X-Admin-Token: aayam123) |
Delphi integration
Delphi is synthetic-sciences/delphi running locally in Docker. It plays two roles:
- Corpus context server. Every Trinetra document is rendered as a one-page PDF and uploaded via
POST /v1/papers/upload. Delphi chunks, embeds (Gemini), and exposes them atPOST /v1/search/papersand the higher-levelPOST /v1/researchendpoint. - MCP server for AI agents. Once configured, Claude Code / Cursor / Windsurf can call Delphi to semantically search the Trinetra corpus from inside any AI chat.
The Trinetra RFI page calls Delphi via /api/rfi/delphi alongside the local Gemini-cosine engine; results are shown side by side.
POST /v1/repositories/index.
Adding sources
Each source is a Python module under pipeline/trinetra/sources/:
def fetch(client: HttpClient, cfg: Config) -> Iterable[Document]:
...
yield Document(source="my_new", title=..., body=..., url=..., published_at=...)
Register it in sources/__init__.py and add a section to config.toml with whatever knobs it needs. Add a relevance filter (regex) to keep noise out. Re-run python -m trinetra ingest my_new.
Deployment
Local-first is the current focus. For production:
- Static frontend → Vercel (already wired via
vercel.json). - Python API + Delphi → Fly.io, Railway, or a Hetzner box. Bring Postgres, mount a persistent volume for Delphi's pgdata + hf-cache.
- Auth → replace the soft client-side gate with server-side OAuth (GitHub) or magic-link. Delphi has these endpoints already; lift them into the Trinetra API.
- Scheduled ingest → systemd timer or GitHub Actions calling
python -m trinetra runon a cadence. - Audit → swap the in-memory ring buffer for a Postgres
audit_logtable.
CLI reference
trinetra ingest [source] run ingest for one or all enabled sources
trinetra extract [--force] extract mentions + events
trinetra resolve resolve mentions into entities
trinetra graph build the edge graph
trinetra export write JSON for the dashboard
trinetra analyze produce analysis reports
trinetra agents run all reasoning agents
trinetra embed [--force] compute Gemini embeddings for every document
trinetra delphi-push push corpus to running Delphi instance
trinetra serve [--port 8000] run the FastAPI backend
trinetra run full end-to-end pipeline
trinetra status DB counts + recent runs
Config
pipeline/config.toml is the single config file. Sections:
[runtime]— user_agent, request_timeout, rate_limit_rps, retry params[paths]— db, cache, export, log[ingest]—enabled = ["bsf","cbi","gdelt","indian_kanoon","rss"][ingest.<source>]— per-source URL, query, max docs[extract.modus]— modus → keyword list
.env at the repo root holds secrets (gitignored):
GEMINI_API_KEY=AIza...
DELPHI_API_URL=http://localhost:8742
DELPHI_API_KEY=synsc_...
Data model
| Table | Rows | Purpose |
|---|---|---|
documents | 148 | Every ingested doc, SHA-deduped |
mentions | 1,236 | Raw extracted spans (person / phone / vehicle / etc.) |
entities | 152 | Resolved canonical entities |
mention_entity | — | Mention → entity link table |
edges | 1,089 | Typed, weighted, evidence-tracked relations |
events | 56 | Geocoded events (document × location × modus × date) |
embeddings | 136 | Gemini 3072-d vectors, float32 blob |
delphi_papers | 138 | Cross-ref: document_id → Delphi paper_id |
ingest_runs | per run | Audit log of ingest cycles |
Troubleshooting
Dashboard shows "T is not defined" or blank
Hard-reload (Cmd-Shift-R / Ctrl-F5). The browser is caching a stale app.js.
RFI "Delphi unreachable"
The Delphi container isn't running. cd ../delphi && docker compose --profile dashboard up -d.
Embeddings stage fails with "GEMINI_API_KEY not set"
Add the key to .env at the repo root, not inside pipeline/.
Delphi 429 rate-limit on bulk push
Bump SYNSC_RATE_LIMIT_INDEX in Delphi's .env and docker compose up -d --force-recreate api worker.
Ingest doc count low
PIB is disabled by default (its WAF rejects identified crawlers). The four working sources (BSF, CBI, GDELT, Indian Kanoon, RSS) carry the corpus. To enable more: add DRI / ED / NCB credentials via their respective registration flows, then add a source module.