This is a submission for the
🌐 Live:
📦 Repo:
🌐 Live URL: (MIT)
skills/ # 25 Hermes Agent skills, validated by skills-ref
marszalek-sejmu/ # the orchestrator — owns the bill-drafting template
ministry-finansow/ # 19 ministry experts (Finance, Health, Climate, ...)
...
party-ko/ # 5 party agents (KO, PiS, TD, Konfederacja, Lewica)
parliament/
session.py # subprocess launcher around `hermes chat -s <skill>`
transcript_parser.py # splits orchestrator stdout into per-speaker utterances
citation_validator.py # every [node:...] must resolve back to a real statute
api.py # FastAPI: POST /sessions, polling SSE /stream/{id}
cli.py # `parliament "<topic>"` (typer)
web/ # Next.js 16 static export, served by FastAPI
deploy/ # Dockerfile entrypoint + Hermes config + demo fixture
My Tech Stack
| Layer | Tech | Notes |
|---|---|---|
| Agent framework | hermes-agent 0.14.0 | the load-bearing piece — pip install hermes-agent==0.14.0 |
| Skills spec | Anthropic Agent Skills + [email protected] | 25 skills, lowercase-hyphen, validated in CI |
| RAG | PageIndex Cloud via MCP | vectorless retrieval over Polish Constitution + ~50 statutes; every citation traces to a real document |
| Models | google/gemini-3.1-flash-lite via OpenRouter | ~$0.04 per full session, fast enough for live demo |
| Orchestrator | Python 3.11 + FastAPI + uvicorn | subprocess launcher around hermes chat |
| Stream | sse-starlette + polling SQLite | per-speaker utterances pushed as event: utterance |
| Frontend | Next.js 16 (App Router, static export) + Tailwind | served from /app/* by the same FastAPI |
| Deploy | Railway (single Docker container) | public HTTPS, ~$5/month |
How I Used Hermes Agent
There's one Hermes property the whole project is built on:
delegate_tasklets a parent skill fan out to N child skills in parallel as a single tool call.
Without that, this project isn't tractable. With it, the entire 25-agent pipeline is 24 LLM calls in a tight DAG, runs in 2 minutes, and the orchestrator never has to manage thread pools or async gathers itself.
Here's the shape:
┌─────────────────────────────┐
│ marszalek-sejmu (skill) │
│ Topic → ministry selection │
└──────────────┬──────────────┘
│ delegate_task(tasks=[...]) ← Hermes batch mode
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ ministry- │ │ ministry- │ │ ministry- │
│ finansow │ │ klimatu │ │ rodziny-pracy │
└────────┬───────┘ └────────┬───────┘ └────────┬───────┘
│ PageIndex RAG │ (cite real statutes)
└────────┬─────────┴─────────┬───────┘
▼ ▼
Synthesized findings → Marszałek
│
▼ 5 × party debate, ×2 readings
┌──────────────┐
│ KO PiS TD │
│ Konf Lewica │
└──────┬───────┘
▼
Seat-weighted vote → Draft bill
Why delegate_task was the right primitive
Ministries are independent. Finance doesn't need to know what Climate said before doing its own analysis. They both run on the same input bill topic, return findings, get merged by the orchestrator. Classic embarrassingly parallel.
Hermes already handles the thread pool. Batch mode usesThreadPoolExecutorto spawnAIAgentchildren. I don't have to mix asyncio with hermes-agent's threaded subagents — a known foot-gun if you roll your own.
Context isolation is free. Each ministry gets its own skill prompt with its own toolsets (pageindex-rag). The Marszałek doesn't pollute their context.
Approval and audit are centralized. When PageIndex is called from a ministry, it goes through Hermes' tool registry. I get the audit trail for free.
The other Hermes pieces that mattered
Skills as the unit of expertise. Every agent is one
SKILL.md. The Marszałek has the bill-drafting template (assets/bill-draft-template.md). The parties have their actual policy positions. None of this fits in one big system prompt — but as 25 separate skills, it's maintainable. I can rewrite Lewica's economic stance without touching Konfederacja.MCP toolsets for retrieval. Every skill that cites Polish law declares
toolsets: ["pageindex-rag"]and gets retrieval for free. Zero Python integration code. The PageIndex MCP server is one config-yaml entry.Subprocess as the integration surface. Hermes is a CLI first. The cleanest way to embed it in FastAPI is
subprocess.Popen(["hermes", "chat", "-s", skill, "-q", topic, "-Q", "--accept-hooks", "--yolo"]). Mysession.pyis essentially that subprocess launcher plus a stdout parser that splits the result into per-speaker utterances for SSE streaming.Bake-time config for the container. For Railway, the Dockerfile copies
hermes-config.yamlto/root/.hermes/config.yamlandskills/*to/root/.hermes/skills/. An entrypoint script materializesOPENROUTER_API_KEYinto~/.hermes/.envat boot. Crucially:disabled_toolsets: [browser, computer-use, voice, terminal-modal]— otherwise Hermes hangs at startup looking for a Chromium binary that isn't inpython:3.11-slim. I only found that via a/diagendpoint I added to introspect the running container.
What this combination unlocks
If I had to write the parallel fan-out + tool registry + skill loader by hand, I'd still be debugging deadlocks instead of arguing with my own bill drafts.
Hermes let me spend my time on the simulation design (how does a Marszałek pick ministries? what does each party's house style sound like? how do you parse "Article 129 §1 is amended to read…" out of free-form markdown?) and the legal-diff UX (the Current law vs proposed change panel) — not on the orchestration framework.
That's the right division of labour for a 5-day contest project, and frankly for most agent projects.
What surprised me
Hermes models matter less than you'd think. Most of the quality comes from the skill prompts. Swappinggemini-flash-lite↔llama-3.3-70bchanges vocabulary, barely changes the structure of the debate.
The frontend is where civic value lives. The pipeline produces a 40 KB markdown blob. Useless to a non-lawyer. The UI panel showing "Czas pracy nie może przekraczać 8 godzin na dobę i przeciętnie 40 godzin…" on the left and the proposed "…32 godzin w przeciętnie czterodniowym…" on the right is what makes this a tool instead of a transcript.
Free OpenRouter tiers are rate-limited into uselessness during contest week. Plan for $5 of paid model credit, or bake a demo fixture into the image. I shipped both.
🇵🇱 Built in Żory. MIT-licensed. Educational simulation only — no real Members of Parliament are represented, no hate speech is produced, and a disclaimer is emitted at the top and bottom of every session.
SOCIAL SHARE CARD GENERATOR