hermes-gemini-web-research is a small Python controller for running multiple headless Gemini CLI research workers in parallel, validating each worker's strict JSON output with Pydantic, and reconciling the results into a Markdown or JSON report file.
The intended use case is Hermes, or another controller, asking 3-6 independent research angles to investigate one question, then collecting evidence, caveats, telemetry, and a synthesized result without depending on brittle free-form model text.
This is an MVP scaffold. It has a practical subprocess runner, a stable internal contract, deterministic reconciliation, optional semantic synthesis hooks with fallback, and report-file output. The default Gemini command targets headless JSON mode, while still allowing wrapper-specific flags to be overridden.
The package lives under src/hermes_gemini_web_research/:
models.pydefines Pydantic models for research requests, angles, worker input/output, evidence, wrapper telemetry, worker results, and final reconciliation.prompts.pybuilds strict JSON prompts and extracts JSON objects from raw model text.runner.pyruns Gemini CLI viaasyncio.create_subprocess_exec, applies per-worker timeouts, strips terminal escape sequences, retries bounded recoverable failures with exponential backoff, parses optional wrapper JSON, extracts telemetry, and validates worker JSON.orchestrator.pyruns angles concurrently with a bounded semaphore, passes results to reconciliation, and optionally calls a semantic synthesizer.reconcile.pyperforms a conservative deterministic synthesis over successful worker outputs and records limitations from failed or uncertain workers.report.pyrenders final structured results as Markdown.cli.pyprovides thehermes-gemini-web-researchandhgw-researchconsole commands.
The flow is:
- A controller submits a
ResearchRequestcontaining a question and optional angles. - Each angle becomes a
WorkerInput. - The runner builds a strict JSON prompt and invokes Gemini CLI headlessly.
- Gemini stdout is treated as either raw worker JSON or wrapper JSON containing model text plus telemetry.
- Worker JSON is validated as
WorkerOutput. - Worker results are reconciled into a
ResearchResultwithsynthesis_method="deterministic". - If requested through the Python API and a synthesizer is provided, semantic synthesis can refine the deterministic result. Failures fall back to the deterministic result and populate
synthesis_error. - The CLI prints or writes Markdown or JSON.
Install in editable mode:
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"Run tests:
pytestRun with default angles:
hgw-research "What are the current tradeoffs of using Gemini CLI for web research orchestration?"Run with explicit angles:
hgw-research \
"Should Hermes use Gemini CLI workers for current web research?" \
--angle "Implementation: Check subprocess, JSON, timeout, and telemetry concerns" \
--angle "Reliability: Look for failure modes and validation risks" \
--angle "Operations: Assess costs, observability, and debugging workflow"Run with an angle file:
hgw-research "What is the best MVP architecture?" --angle-file examples/angles.jsonAn angle file can be either a list:
[
{
"name": "Current facts",
"description": "Find the most current factual answer with primary sources."
},
{
"name": "Evidence quality",
"description": "Check source quality, conflicts, uncertainty, and caveats."
}
]Or an object:
{
"angles": [
{
"name": "Current facts",
"description": "Find the most current factual answer with primary sources."
}
]
}Output JSON instead of Markdown:
hgw-research "Summarize the latest evidence on a topic" --format jsonWrite a report file instead of printing the rendered report to stdout:
hgw-research \
"Summarize the latest evidence on a topic" \
--format markdown \
--output-file reports/research.mdWhen --output-file is used, parent directories are created automatically and the CLI prints the written path to stderr.
Markdown reports are now citation-aware and reconciliation metadata is exposed structurally:
- each finding renders a Best evidence block with inline source citations like
[1] - repeated sources are collected into a deduplicated
## Sourcesappendix - quotes and publication dates are included when available
- reports include a top-level severity / confidence summary plus contradiction counts
- JSON output includes per-finding
best_evidence,best_evidence_score,source_count,source_diversity,consensus_score,confidence,severity, andcontradicts
Customize the Gemini executable or flags. When omitted, runner arguments default to --output-format json --approval-mode=yolo --prompt, which asks Gemini CLI for wrapper JSON, auto-approves tools in headless mode, and passes the generated prompt as the --prompt value:
hgw-research \
"Research this question" \
--gemini-command gemini \
--gemini-arg=--output-format \
--gemini-arg=json \
--gemini-arg=--approval-mode=yolo \
--gemini-arg=--prompt \
--timeout 180 \
--max-concurrency 6Every worker is instructed to return strict JSON matching WorkerOutput:
{
"angle_name": "Evidence quality",
"answer": "Short answer from this angle.",
"key_findings": ["Finding 1", "Finding 2"],
"evidence": [
{
"type": "source",
"claim": "Evidence-backed claim.",
"source_title": "Source title",
"url": "https://example.com",
"quote": "Short quote when useful.",
"published_date": "2026-04-19",
"confidence": 0.8
}
],
"open_questions": ["What could not be verified?"],
"confidence": 0.7
}The runner accepts either direct worker JSON or wrapper JSON. Wrapper text can be supplied through fields such as text, output, response, or stdout; usage metadata can be supplied through usage, usage_metadata, usageMetadata, or telemetry.
Wrapper-level failures are treated as worker failures even when the subprocess exits 0. Supported error fields include error, errors, and failed status values.
ANSI escape sequences are stripped before wrapper and worker JSON parsing so colored CLI output does not poison validation.
Deterministic reconciliation is always the baseline. It now performs lightweight token-based clustering for near-duplicate findings, keeps the full ranked/deduped evidence set for scoring and JSON output, renders the top evidence in Markdown, and records worker failures or open questions as limitations. Every ResearchResult includes:
synthesis_method:deterministicorsemanticsynthesis_error: the semantic synthesis error when fallback was needed, otherwisenull
Built-in semantic synthesis is available through GeminiSemanticSynthesizer, which runs a second Gemini CLI pass over the deterministic report and refines the summary plus reconciled findings. The CLI can enable it directly:
hgw-research \
"Should Hermes use Gemini CLI workers for current research?" \
--semantic-synthesis \
--format json \
--output-file reports/semantic-research.jsonPython API example:
from hermes_gemini_web_research import GeminiSemanticSynthesizer, ResearchOrchestrator, ResearchRequest
from hermes_gemini_web_research.runner import GeminiRunner
runner = GeminiRunner()
request = ResearchRequest(
question="Should Hermes use Gemini CLI workers for current research?",
semantic_synthesis=True,
)
result = await ResearchOrchestrator(
runner,
synthesizer=GeminiSemanticSynthesizer(runner=runner),
).run(request)SemanticSynthesizer is exported from the package root as a protocol for custom implementations. If semantic synthesis raises or returns invalid JSON, the orchestrator falls back to the deterministic result with synthesis_method="deterministic" and synthesis_error set.
The repository includes a usable Hermes/Codex skill at hermes/skills/gemini-web-research/SKILL.md. It documents when to use the tool, CLI invocation patterns, output-file handling, semantic synthesis, and Python API behavior. Supporting angle-set guidance lives in hermes/skills/gemini-web-research/references/angle-sets.md.
GeminiRunner retries likely recoverable failures with bounded exponential backoff. Invalid worker JSON is retried because the same prompt can often recover on a second model call. Non-zero exits and wrapper-level errors are retried only when the error text looks transient, such as rate limits, 5xx responses, network failures, temporary unavailability, or overload.
The retry policy is configurable on the runner:
runner = GeminiRunner(
max_retries=2,
initial_backoff_seconds=1.0,
backoff_multiplier=2.0,
max_backoff_seconds=10.0,
)GitHub Actions runs pytest on Python 3.11 and 3.12 via .github/workflows/ci.yml.
- The baseline reconciliation step is still intentionally heuristic. It now adds lightweight semantic clustering and metadata-aware evidence ranking, but it is not a full semantic embedding pipeline.
- Gemini CLI flag conventions can still vary by wrapper. The default command shape is
gemini --output-format json --approval-mode=yolo --prompt "<prompt>"; adjust--gemini-commandand repeated--gemini-argvalues as needed. - Tests do not invoke Gemini CLI or any network service.
Useful commands:
python -m pip install -e ".[dev]"
pytest
hgw-research "Question" --format jsonThis repository targets Python 3.11+.