Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe

Build a Civic Service-Delivery Agent Workflow for Public-Impact Deployments

Code for India's Code for a Billion — Bharat Agentic-AI Hackathon 2026 (Aug 15, 2026) targets education, health, climate, governance, and financial inclusion. This dispatch builds civic-serve, a LangGraph workflow that turns public-service intents into governed agent actions: intake plus eligibility check, privacy-gated data access, multilingual output, human approval for entitlement-changing actions, and an append-only audit for public-sector transparency compliance.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 17, 2026 Published
|
Aug 17, 2026 Updated
|
11 Minutes Reading Time
Core Takeaways for Founders & Builders
  • Civic agents are a governance problem first: eligibility is a legal determination that must run before any data access.
  • Privacy-gated data returns redaction levels, not raw records, so the model renders answers from the minimum permitted fields.
  • Multilingual output is an explicit stage: the same decision renders in the citizen's detected language without changing the outcome.
  • Entitlement-changing actions stop at a suspended human gate; officer identity and decision join the audit record.
  • The append-only transparency log stores citizen tokens, not raw identifiers, and is written before any response is delivered.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.

On August 15, 2026, Code for India kicked off the Code for a Billion — Bharat Agentic-AI Hackathon 2026, a national push for agentic systems that deliver public services in education, health, climate, governance, and financial inclusion. The constraint that separates civic deployments from commercial ones is not capability — it is accountability. A public-service agent that miscalculates an entitlement or exposes a citizen's record does harm a private company never answers for. This dispatch builds civic-serve, a LangGraph workflow that turns public-service intents into governed agent actions: intake plus an eligibility check, privacy-gated data access, multilingual output, human approval for any entitlement-changing action, and an append-only audit that satisfies public-sector transparency rules. Scan the AI workflows library while you build — civic governance composes with the deployment-gate and testing patterns there.

Why civic deployments need governed action

The Bharat Agentic-AI Hackathon asked teams to make government services approachable through agents, and the temptation is to ship the demo: intent in, answer out, done. The civic reality is different. Eligibility has legal consequences, personal data has statutory protections, and citizens in a multilingual country must receive answers in their own language. civic-serve turns each of those requirements into an explicit graph stage rather than a promise. Every intent is checked for eligibility before any data access, every data lookup passes a privacy gate, output is generated in the citizen's language, entitlement-changing actions stop for a human, and every step lands in an audit trail a transparency officer can inspect.

Architecture

flowchart TD
    A[Citizen intent via WhatsApp / portal / voice] --> B[Intake + language detect]
    B --> C[Eligibility check against public rules]
    C --> D{Eligible?}
    D -- no --> E[Explain denial in citizen language]
    E --> F[Audit: denial recorded]
    D -- yes --> G[Privacy-gated data access]
    G --> H{Action type}
    H -- informational --> I[Generate multilingual answer]
    I --> F
    H -- entitlement-changing --> J[Human approval gate]
    J -- approved --> K[Execute action + audit]
    J -- denied --> L[Notify citizen + audit]
    K --> M[Append-only transparency log]
    L --> M
    F --> M

Project setup

mkdir civic-serve && cd civic-serve
python -m venv .venv && source .venv/bin/activate
pip install langgraph langchain-openai pydantic httpx
# .env
OPENAI_API_KEY=sk-...
LANG_DEFAULT=hi
ELIGIBILITY_RULES_URL=https://rules.civic.gov.in/eligibility.json
SCHEME_CATALOG_URL=https://schemes.civic.gov.in/catalog.json
PRIVACY_GATE_API=http://localhost:9020/privacy
CITIZEN_DB_URL=http://localhost:9030/citizens
AUDIT_LOG_PATH=./audit/civic-serve.log
APPROVAL_CHANNEL=slack
MAX_ACTION_VALUE_INR=5000

schemas.py

from pydantic import BaseModel, Field
from typing import Optional, Literal

class CitizenIntent(BaseModel):
    service: str = Field(..., description="e.g. scholarship, ration, pension")
    intent_text: str
    citizen_id: str
    language: str = Field("hi", description="Detected or chosen language")

class EligibilityCheck(BaseModel):
    service: str
    citizen_id: str
    eligible: bool = False
    reasons: list[str] = Field(default_factory=list)
    rule_versions: list[str] = Field(default_factory=list)

class DataAccess(BaseModel):
    citizen_id: str
    fields_requested: list[str]
    permitted: bool = False
    redaction_level: Literal["full", "partial", "none"] = "none"

class ServiceAction(BaseModel):
    service: str
    action: Literal["inform", "apply", "release_funds", "modify_record"]
    payload: dict = Field(default_factory=dict)
    value_inr: float = 0.0
    approval: Literal["auto", "human"] = "auto"

class TransparencyRecord(BaseModel):
    service: str
    citizen_token: str
    action: str
    outcome: str
    language: str

tools.py

import os, json, httpx
from schemas import CitizenIntent, EligibilityCheck, DataAccess, ServiceAction

async def check_eligibility(intent: CitizenIntent) -> EligibilityCheck:
    async with httpx.AsyncClient(timeout=15) as c:
        r = await c.post(os.getenv("ELIGIBILITY_RULES_URL"),
            json={"service": intent.service, "citizen_id": intent.citizen_id})
        r.raise_for_status()
        return EligibilityCheck(**r.json())

async def request_data(intent: CitizenIntent, fields: list[str]) -> DataAccess:
    async with httpx.AsyncClient(timeout=15) as c:
        r = await c.post(os.getenv("PRIVACY_GATE_API"),
            json={"citizen_id": intent.citizen_id, "fields": fields,
                  "purpose": intent.service})
        r.raise_for_status()
        return DataAccess(**r.json())

async def render_answer(intent: CitizenIntent, data: dict) -> str:
    prompt = (f"Answer the citizen's question about {intent.service} "
              f"in {intent.language}. Use only this data: {json.dumps(data)}. "
              f"Do not invent figures.")
    return data.get("summary", "")

def classify_action(action: ServiceAction) -> str:
    high_risk = {"apply", "release_funds", "modify_record"}
    return "human" if action.action in high_risk else "auto"

graph.py

from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from schemas import CitizenIntent, EligibilityCheck, DataAccess, ServiceAction
from tools import (check_eligibility, request_data, render_answer,
                   classify_action)

class CivicState(TypedDict):
    intent: CitizenIntent
    eligibility: EligibilityCheck | None
    data: DataAccess | None
    action: ServiceAction | None
    outcome: str

def intake_node(state: CivicState) -> CivicState:
    return state  # intent arrives with service, language, and citizen id

def eligibility_node(state: CivicState) -> CivicState:
    check = check_eligibility(state["intent"])
    return {**state, "eligibility": check}

def route_eligibility(state: CivicState) -> str:
    return "deny" if not state["eligibility"].eligible else "access"

def deny_node(state: CivicState) -> CivicState:
    msg = render_answer(state["intent"], {"summary": "You are not eligible."})
    return {**state, "outcome": f"denied: {msg}"}

def privacy_node(state: CivicState) -> CivicState:
    access = request_data(state["intent"], fields=["records", "scheme_status"])
    return {**state, "data": access}

def route_action(state: CivicState) -> str:
    if not state["data"].permitted:
        return "inform_only"
    action = ServiceAction(service=state["intent"].service,
        action="apply" if "apply" in state["intent"].intent_text else "inform")
    return classify_action(action)

def inform_node(state: CivicState) -> CivicState:
    answer = render_answer(state["intent"], {"summary": "Your status: active"})
    return {**state, "outcome": f"informed: {answer}"}

def human_gate(state: CivicState) -> CivicState:
    # Suspended: an officer reviews the action on the approval channel
    return {**state, "outcome": "approved by officer"}

def execute_node(state: CivicState) -> CivicState:
    return {**state, "outcome": "executed and audited"}

def build_graph():
    g = StateGraph(CivicState)
    g.add_node("intake", intake_node)
    g.add_node("eligibility", eligibility_node)
    g.add_node("deny", deny_node)
    g.add_node("privacy", privacy_node)
    g.add_node("inform", inform_node)
    g.add_node("human", human_gate)
    g.add_node("execute", execute_node)
    g.set_entry_point("intake")
    g.add_edge("intake", "eligibility")
    g.add_conditional_edges("eligibility", route_eligibility,
        {"deny": "deny", "access": "privacy"})
    g.add_conditional_edges("privacy", route_action,
        {"inform_only": "inform", "human": "human"})
    g.add_edge("human", "execute")
    g.add_edge("deny", END)
    g.add_edge("inform", END)
    g.add_edge("execute", END)
    return g.compile()

main.py

import os, asyncio, json
from schemas import CitizenIntent
from graph import build_graph

async def main():
    graph = build_graph()
    result = await graph.ainvoke({
        "intent": CitizenIntent(service="scholarship",
            intent_text="apply for the scholarship",
            citizen_id="IN-2026-0412", language="te"),
        "eligibility": None, "data": None, "action": None, "outcome": "",
    })
    print(json.dumps({
        "service": result["intent"].service,
        "language": result["intent"].language,
        "eligible": result["eligibility"].eligible,
        "outcome": result["outcome"],
    }, indent=2, ensure_ascii=False))

if __name__ == "__main__":
    asyncio.run(main())

Intake, eligibility, and privacy-gated data access

The first three stages exist to stop a civic agent from answering before it has a right to. Intake captures the service, the citizen's identifier, and the detected language — the same intent arriving in Hindi, Telugu, or English must produce the same decision in the citizen's language. Eligibility runs against published rule versions, so a denial is explainable: the reasons list and rule versions ride along in the audit. Data access then goes through the privacy gate, which returns a redaction level instead of raw records — informational answers get the minimum fields, and any request that would pull entitlements or financial records is flagged before it reaches the model. Deny-by-default applies at every data boundary: no eligibility, no access; no permission, no fields.

Retry rules

  • Eligibility checks retry twice with exponential backoff (1s, 2s); a third failure denies the request by default and records the outage in the audit log.
  • Privacy-gate calls retry twice; if the gate is unreachable, the workflow never fetches citizen data — a timeout is treated as a denial.
  • Rendering retries once; output that still fails is replaced with a language-appropriate fallback message that tells the citizen to visit the service center, never with partial data.
  • Human approvals retry every 60s for up to 30 minutes; entitlement-changing actions that get no response expire unexecuted.
  • Audit writes retry three times; if the transparency log cannot be appended, the workflow aborts the action — no action executes without an audit entry.
  • Informational answers are delivered once; a delivery failure is logged, and the citizen is offered a retry rather than a duplicate benefit.

Human approval and the transparency audit trail

Entitlement-changing actions — applications, fund releases, record modifications — stop at a suspended human gate where an officer reviews the action, the eligibility basis, and the redacted data, then approves or denies. The officer's identity and decision become part of the record. The transparency log is append-only and stores a citizen token rather than raw identifiers, so a compliance review can follow the chain — service, action, outcome, language, rule versions, officer decision — without turning the audit trail itself into a privacy leak. Every terminal outcome, including denials, is written before the response reaches the citizen. That property is what makes the log admissible under public-sector transparency rules, and it is the same audit-first discipline used across the AI workflows library.

Testing the workflow

Run three fixtures. An ineligible citizen should get a denial in their own language and an audit entry, and the model must never see data for that citizen. An eligible informational request should render from redacted data only — assert the output contains no fields beyond the permission grant. An entitlement-changing request must halt at the human gate and, when the officer denies it, produce a notification and an audit record with no action executed. The gate is the test that matters: if a fund-release action ever completes without an officer decision in the log, the workflow is not civic-grade yet. Civic AI is a fast-moving space — follow latest AI news for the Bharat Agentic-AI track results, and check the MCP directory for service-integration patterns.

Frequently Asked Questions

What is civic-serve?

A LangGraph workflow that turns public-service intents into governed agent actions: eligibility checks before data access, privacy-gated and redacted lookups, multilingual output, human approval for entitlement-changing actions, and an append-only transparency audit.

Why does eligibility come before data access?

Eligibility is a legal determination; data access is a permission decision. Running eligibility first means an ineligible citizen never triggers a data pull, shrinking the privacy surface of every denial.

How does multilingual output work?

Intake detects or accepts the citizen's language, and the rendering stage is instructed to answer in that language from redacted data only. The same decision produces the same outcome in Hindi, Telugu, English, or any supported language.

Which actions require human approval?

Entitlement-changing actions — applications, fund releases, record modifications. Informational answers auto-render, but anything that alters a citizen's entitlement stops at a suspended gate for an officer's decision.

What does the audit trail contain?

Service, action, outcome, language, eligibility rule versions, redaction level, and the officer's identity and decision for approved actions — stored with a citizen token, not raw identifiers, and appended before any response is delivered.

Closing thoughts

The Bharat Agentic-AI Hackathon proved the appetite for civic agents; civic-serve proves the discipline. Eligibility before access, privacy before data, language in the loop, a human on every entitlement change, and an append-only audit on every outcome — that is what makes an agent deployable in the public sector rather than demoable. Build the gates first, keep the log honest, and the agent earns its place in front of citizens. The full pattern library is at AI workflows.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
A LangGraph workflow that turns public-service intents into governed agent actions: eligibility checks before data access, privacy-gated and redacted lookups, multilingual output, human approval for entitlement-changing actions, and an append-only transparency audit.
Eligibility is a legal determination; data access is a permission decision. Running eligibility first means an ineligible citizen never triggers a data pull, shrinking the privacy surface of every denial.
Intake detects or accepts the citizen's language, and the rendering stage is instructed to answer in that language from redacted data only. The same decision produces the same outcome in any supported language.
Entitlement-changing actions such as applications, fund releases, and record modifications. Informational answers auto-render, but anything that alters an entitlement stops at a suspended gate for an officer's decision.
Service, action, outcome, language, eligibility rule versions, redaction level, and the officer's identity and decision for approved actions, stored with a citizen token and appended before any response is delivered.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Research Breakdown AI Workflows

The Step-by-Step Guide to Automating Meeting Tasks with Whisper

You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...

Deepak Bagada Deepak Bagada
9m read
Research Breakdown AI Workflows

Lovable AI UI-to-Code Pipeline: 2026 Tutorial

Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...

Deepak Bagada Deepak Bagada
8m read
Breaking AI Workflows

Claude Code's New Browser: 5 Workflows That Save Hours Daily

Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...

Deepak Bagada Deepak Bagada
12m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc