Reference base code for ecosystem partners — receive specification changes from an Authority over the A2A protocol, assess them against your own capability profile, negotiate terms, and report readiness — with a pluggable agent at every stage that you replace with your own.
This is the receiving side of a specification change. A central Authority publishes a change; every bank, payment service provider and third-party app provider in the ecosystem has to read it, work out what it costs them, ask questions, build it, and declare themselves ready. This platform does that side of the conversation.
It is reference base code, not a product. Fork it, point it at your Authority, and replace the agent bodies with logic that knows your systems.
- 📥 Change inbox over A2A — change communications arrive from the Authority with the full Product Kit (BRD, technical spec, FAQ, test cases) attached
- 🧭 Feasibility assessment — each change is scored against your own capability profile, so the answer reflects your estate rather than a generic one
- 💬 Query and negotiation loop — ask the Authority questions, accept or counter rollout terms, and track the round until it freezes
- 📊 Progress and readiness — report Design → Coding → Testing and declare ready for certification
- 🔌 Pluggable agents — every stage is an agent you can swap: shipped Python class, an HTTP service you host, or (designed-for) an MCP server
- 🧠 Retrieval over your own code — ingest your repository into pgvector so an agent's assessment cites files it actually read
- 🔐 Signed A2A wire — Bearer JWT plus an HMAC envelope, verified with a constant-time compare inside a 5-minute timestamp window
Important
This platform is a client of a live Authority instance. It needs a reachable Authority A2A endpoint plus Authority-issued credentials, set from the Settings UI. It is not a standalone server.
The wire contract carries payments-ecosystem field names (npci_change_id and
similar) because they are shared with the Authority platform and cannot be
renamed on one side alone. User-visible wording is not fixed: labels default
to neutral text — "the Authority" rather than any named organisation — and are
overridden per deployment through VITE_LABEL_OVERRIDES. See
TRADEMARKS.md.
Three services in the default stack, and a one-way dependency rule that is the whole point of the design:
flowchart TB
Browser(["Browser"])
FE["<b>Frontend</b> · nginx :80 (via edge)<br/>React 19 + Vite"]
AUTH["<b>Authority Platform</b><br/>separate repo & stack"]
subgraph BE [" Partner Backend · FastAPI :8001 (unpublished; reached via edge) "]
direction TB
T2["<b>Tier 2 · Platform</b><br/>api · models · core<br/><i>transport, persistence, audit</i>"]
T1["<b>Tier 1 · Contract</b><br/>a2a_common/<br/><i>mirrored — do not edit</i>"]
T3["<b>Tier 3 · Agent</b><br/>agents/<br/><i>your plug-in zone</i>"]
T2 --> T1
T2 --> T3
end
subgraph data [" Data plane "]
direction LR
PG[("<b>Postgres 16</b><br/>relational + pgvector")]
OL(["<b>Ollama</b><br/>local embeddings"])
end
Browser --> FE --> T2
T2 --> PG
T2 --> OL
T1 <-. " <b>A2A</b> " .-> AUTH
classDef entry fill:#E7ECF6,stroke:#2E4E8F,stroke-width:1.5px,color:#16233D
classDef tier1 fill:#F3E4E4,stroke:#B3261E,stroke-width:2px,color:#3D1512
classDef tier2 fill:#C9DBF5,stroke:#2E4E8F,stroke-width:2px,color:#16233D
classDef tier3 fill:#E4F2EA,stroke:#1F6B45,stroke-width:2px,color:#123322
classDef store fill:#FBF0DC,stroke:#8A5A00,stroke-width:1.5px,color:#3A2703
classDef ext fill:#F2F3F5,stroke:#8B929C,stroke-width:1.5px,stroke-dasharray:5 4,color:#3D444F
class Browser,FE entry
class T1 tier1
class T2 tier2
class T3 tier3
class PG,OL store
class AUTH ext
style BE fill:#FAFBFC,stroke:#C6CCD6,stroke-dasharray:3 3,color:#5C636E
style data fill:#FAFBFC,stroke:#C6CCD6,stroke-dasharray:3 3,color:#5C636E
The three tiers, and why the boundary matters:
| Tier | Path | Yours to edit? |
|---|---|---|
| 1 — Contract | app/a2a_common/ |
No. Mirrored with the Authority platform; hmac_signer.py must stay byte-identical on both sides |
| 2 — Platform | api/ · models · database · core/llm · frontend |
Rarely. Transport, persistence, UI |
| 3 — Agent | app/agents/ |
Yes — this is your plug-in zone |
Dependency direction is one-way: Platform → Agent (through the registry) and Platform → Contract. The agent tier receives plain dicts and returns plain dicts — no DB or ORM object crosses the boundary, which is what keeps your agent decoupled and testable.
The Authority platform — the sending side of every change — lives in its own repository and runs as its own stack: https://github.com/npci/atom-network-platform.
Warning
app/a2a_common/ is mirrored between the two repositories, and each
repository's CI validates only its own copy. Nothing checks the two against
each other, so a signing change must land on both sides as a coordinated
release. Skip that and both test suites still pass — the first symptom is a
rejected signature on a live A2A call.
- Docker with Compose v2 — the supported way to run the full stack
- Alternatively Python 3.12+ and Node 20+ to run the services directly
- Nothing, to start. With no LLM key the feasibility agent returns mock output, so the stack works end to end on a fresh clone.
SESSION_JWT_SECRETmust be set per deployment. It has no safe default.- To talk to a real Authority: the platform URL, your partner API key, and the JWT/HMAC secrets issued during onboarding. These are entered in the Settings UI, never committed.
- To use real agents: one of
PARTNER_ANTHROPIC_API_KEY,PARTNER_OPENAI_API_KEY, orPARTNER_AINXT_API_KEY.
1. Clone and start
git clone https://github.com/npci/atom-partner-platform
cd atom-partner-platform
# TLS — the edge proxy refuses to start without a cert pair.
# Self-signed is fine for local dev; use a real one anywhere else.
mkdir -p deploy/tls
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
-keyout deploy/tls/key.pem -out deploy/tls/cert.pem -subj "/CN=localhost"
cp .env.example .env # set SESSION_JWT_SECRET, PARTNER_SECRET_KEK and ADMIN_PASSWORD
docker compose up -dWindows + Git Bash: prefix the
opensslline withMSYS_NO_PATHCONV=1. Git Bash rewrites the/CN=localhostargument into a Windows path, and OpenSSL then rejects it with "subject name is expected to be in the format /type0=value0/… This name is not in that format:C:/Program Files/Git/CN=localhost" — a message that names the mangled value without ever saying the shell did it.MSYS_NO_PATHCONV=1 openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -keyout deploy/tls/key.pem -out deploy/tls/cert.pem -subj "/CN=localhost"The prefix is inert on Linux and macOS, so that one line is safe to use everywhere. PowerShell does no such rewriting — the original command works there as written.
This brings up Postgres with pgvector, Ollama, the backend, the frontend and the
edge proxy. The seven agents run in-process.
Everything is reached through edge. The backend and frontend are not
published to the host — the proxy is the single front door, and it terminates
TLS.
2. Add an LLM key (optional — skip for a mock-output loop)
# .env (Docker Compose)
LLM_PROVIDER=anthropic # anthropic | openai | ainxt
PARTNER_ANTHROPIC_API_KEY=sk-ant-...docker compose restart backend3. Open the app
| App | URL |
|---|---|
| Partner Platform | https://localhost:8443/a2a-partner/ |
| Agent Card | https://localhost:8443/.well-known/agent-card.json |
Self-signed certificate, so the browser warns once — expected for local dev.
Override the ports with PARTNER_EDGE_HTTPS_PORT / PARTNER_EDGE_HTTP_PORT.
4. Log in and connect to your Authority
The admin username is admin; the password is whatever you set as
ADMIN_PASSWORD in .env. There is no default — the service refuses to seed
an admin user when ADMIN_PASSWORD is unset.
Caution
ADMIN_PASSWORD is required and should be strong. It seeds the first
admin account, so pick a strong value, keep it out of version control, and
rotate it from the UI once the service is reachable by anyone else.
Then open Settings and enter the Authority Platform URL, your Partner API Key, the Authority JWT / HMAC secrets, and your Partner Name. Click Test Connection, then Save.
5. Run the tests
docker compose run --rm backend pytest| Feature | Description |
|---|---|
| Change Inbox | Receive feature change notifications from the Authority with the full Product Kit |
| Product Kit Viewer | View and download the kit docs (BRD, Tech Spec, FAQ, test cases, …) |
| Feasibility | Auto-assessment of each change against your capability profile (pluggable agent) |
| Query / Negotiation | Ask the Authority questions, accept or counter rollout terms |
| Progress & Readiness | Report Design → Coding → Testing, declare ready for certification |
| Code repository RAG | Ingest your own repo into pgvector so assessments cite real files |
| Agent framework | Plug in your own agents — in-process code, or a service you host |
Seven agents are wired in backend/config/agents.yaml. They are reference
implementations: six carry real LLM-powered logic (degrading to documented
mock output without a key), and negotiation is a stub, so the flow is
complete on a fresh clone. Replacing them is the intended use of this
repository.
| Agent | Binding | Status |
|---|---|---|
feasibility |
app.agents.feasibility:FeasibilityAgent |
Real logic; mock report when no LLM key is set |
design |
app.agents.design:DesignAgent |
Real — LLM-powered; mock output when no LLM key is set |
code |
app.agents.code:CodeAgent |
Real — LLM-powered; mock output when no LLM key is set |
test |
app.agents.testing:TestAgent |
Real — LLM-powered; mock output when no LLM key is set |
negotiation |
app.agents.negotiation:NegotiationAgent |
Stub — documented mock output |
code_reviewer |
app.agents.code_reviewer:CodeReviewerAgent |
Review lens — any finding blocks the merge request |
security_reviewer |
app.agents.security_reviewer:SecurityReviewerAgent |
Review lens — any finding blocks the merge request |
Swapping an agent for a service you host is a one-line change, with no code edit:
# backend/config/agents.yaml
code:
url: https://your-host/agents/code
auth: bearer # token from $AGENT_SERVICE_TOKEN
timeout_s: 30
retries: 2Every run — in-process or remote — writes an audit row to agent_runs.
- Agent Card:
GET /.well-known/agent-card.json - JSON-RPC:
POST /a2a-rpc/rpc— the Authority sendschange_communicationandclarification_response; the partner sendsquery,progressandreadiness - Auth: Bearer JWT signed by the Authority, plus an HMAC envelope validated by the Tier-1 middleware
backend/
app/a2a_common/ TIER 1 — A2A wire contract. Mirrored with the Authority. Do not edit.
app/agents/ TIER 3 — YOUR plug-in zone: agents + externalised prompts
app/agents/prompts/ Prompt text as .md, loaded not hardcoded
app/api/dashboard/ Dashboard and workflow endpoints
app/rag/ Code and document ingest, chunking, pgvector retrieval
app/services/ GitLab merge-request integration, PDF sign-off
config/agents.yaml Agent manifest — bindings, prompts, per-agent model overrides
requirements.lock Hash-locked closure; this is what the image installs
frontend/src/ React 19 + Vite UI
data/ Partner capability profile template and worked examples
| Repository | What it is |
|---|---|
| atom-network-platform | The sending side — the Authority's platform that authors changes and distributes them over A2A |
cd backend
python -m venv venv && . venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
# point DATABASE_URL at a reachable Postgres, set SESSION_JWT_SECRET, then:
uvicorn app.main:app --host 0.0.0.0 --port 8001 --reloadFrontend: cd frontend && npm install && npm run dev.
cd backend
pip install -r requirements.txt -r requirements-dev.txt
pytest # agent-contract, handler, dashboard, e2e
ruff check .Note
Dependencies are hash-locked: the image installs from
requirements.lock with --require-hashes, so editing requirements.txt
alone changes nothing until the lock is regenerated. The command is in the
lock's header.
ARCHITECTURE.md— the three tiers, the agent contract, bindings, config/secret split, LLM key resolution, the audit table, prompt customisation, and a build-your-own-agent walkthroughDEPLOYMENT_GUIDE.md— deploying the stackfrontend/DESIGN_SYSTEM.md— UI conventions
See backend/.env.example for the full list.
| Variable | Purpose |
|---|---|
SESSION_JWT_SECRET |
Local login-session signing key — set per deployment |
DATABASE_URL |
Postgres connection string |
AGENTS_CONFIG |
Path to the agent manifest (config/agents.yaml by default) |
PARTNER_PROFILE_PATH |
Capability profile read by the feasibility agent |
LLM_PROVIDER + PARTNER_*_API_KEY |
LLM provider and key for in-process agents (optional) |
AGENT_SERVICE_TOKEN |
Bearer token for any agent configured with a remote url: |
VITE_BRAND_LOGO_URL / VITE_BRAND_NAME |
Deployment-supplied branding |
VITE_SAFE_REDIRECT_HOSTS |
Comma-separated hosts the UI may link out to. Set this to your GitLab host (e.g. gitlab.example.com), otherwise merge-request links render as # — see frontend/src/utils/safeUrl.js |
Stated plainly, because a platform that generates code invites assumptions:
- It does not deploy, and it does not merge.
git_integrator.open_merge_request()creates a branch, commits files and opens a merge request. Never merging is enforced in code — keep it that way in your fork. - Prompt injection is mitigated, not solved. Change communications, Product
Kit documents and retrieved code chunks are untrusted input that reaches a
model. Treat every generated artifact as a proposal for human review. See
SECURITY.md. - One of the seven agents (
negotiation) is a stub. That is by design — the agents are the parts you replace — and without an LLM key even the real ones return mock output, so a fresh clone demonstrates the flow rather than performing real work. - The backend is single-instance as it stands. Login lockout and the JWT denylist are in-memory, so a second replica would not share either.
| Symptom | Cause and fix |
|---|---|
| Every AI feature returns mock output | No LLM key set. See Quick Start step 2. |
PARTNER_ANTHROPIC_API_KEY not set |
LLM_PROVIDER=anthropic with no matching key. Set the key for the provider you selected. |
| A2A calls rejected with a signature error | The Authority's HMAC secret does not match, or the two sides' hmac_signer.py have diverged. Re-check Settings first. |
timestamp_skew on inbound A2A |
Clock drift beyond the 5-minute window. Sync NTP on both sides. |
| Login returns 429 | Brute-force lockout: 5 failures → 60s, 10 → 5 min. Wait it out; there is no CAPTCHA to solve. |
| Retrieval scores 0 for everything | The corpus is empty, or the embedding model was never pulled: docker exec partner_ollama ollama pull nomic-embed-text. |
pip ... hashes do not match on build |
The lockfile was edited or regenerated without rebuilding the image. Regenerate it cleanly — the command is in the lock's header — then rebuild. |
A backend/app/** edit has no effect |
app/ is baked into the image. Rebuild, or add a bind mount. |
No third-party logo or mark is bundled. Set VITE_BRAND_LOGO_URL and
VITE_BRAND_NAME to your own; unset, the UI renders a neutral text wordmark and
refers to "the Authority" rather than any named organisation. See
TRADEMARKS.md.
| Document | What it covers |
|---|---|
FAQ.md |
Why questions the Troubleshooting table does not answer |
CHANGELOG.md |
Release history |
USER_GUIDE.md |
Using the platform, role by role, from idea to certification |
DEPLOYMENT_GUIDE.md |
Installation, Docker and native |
CONFIGURATION.md |
Every setting, which surface wins, and what blocks startup |
ARCHITECTURE.md |
The three tiers, the agent contract, build-your-own walkthrough |
CONTRIBUTING.md |
How to contribute, DCO sign-off, coding standards |
SECURITY.md |
Reporting a vulnerability, and the security posture |
wiki/ |
Deep reference: the wire, the security layers, the data model, retrieval |
Contributions are welcome, under the Developer Certificate of Origin —
no CLA, just git commit -s.
Because this is reference base code, the line between upstream and your fork matters: the platform, the A2A contract and the agent framework are upstream's; agent bodies, prompts and capability profiles are yours.
- CONTRIBUTING.md — how to get started, commit guidelines, DCO sign-off, coding standards, and what belongs upstream
- CODE_OF_CONDUCT.md — our community standards
- GOVERNANCE.md — how decisions are made and who the maintainers are
- SECURITY.md — how to report a vulnerability
- SUPPORT.md — where to ask which question
MIT License — see LICENSE.
NOTICE is not required by MIT and is kept anyway: it is where this
project discharges the attribution its own dependencies require.
Third-party dependency licensing, including the copyleft position, is set out in
THIRD-PARTY-NOTICES.md. No GPL or AGPL package
appears in either the Python or the Node closure.
The licence covers code, not trademarks: see TRADEMARKS.md.
The Partner Platform is a single-vendor open-source project sponsored by the National Payments Corporation of India (NPCI), maintained by the same team as the Authority platform. See GOVERNANCE.md for the full governance model, maintainer list, and decision-making process.