This document is for someone opening the repo for the first time. It
walks the six top-level packages under src/eda_agents/, shows how
they interlock, and points at the single source of truth (code,
skill, or doc) for each concern. It does not enumerate every
module — read the CHANGELOG for chronology and the package docstrings
for details.
+-------------------------------------------------------------------+
| Published deliverables |
| scripts/ + examples/ + eda-bridge CLI + scripts/run_bench.py |
+-------------------------------------------------------------------+
|
v
+--------------------+ +------------+ +-----------------+
| agents/ |----->| skills/ |<-----| bench/ |
| (LLM harnesses, | | (prompt + | | (schemas + |
| DAGs, roles) | | tool | | runner that |
+--------------------+ | specs) | | invokes the |
| +------------+ | harnesses |
v | read-only) |
+--------------------+ +------------+ +-----------------+
| topologies/ | | specs/ | |
| (CircuitTopology, |<-----| (Pydantic | v
| SystemTopology) | | YAML) | +-------------+
+--------------------+ +------------+ | bridge/ |
| | (orchestr. |
v | + SSH + |
+-------------------------------------------------------------+
| core/ |
| PdkConfig, SpiceRunner, GmIdLookup, KLayout/Magic/LibreLane|
| runners, DigitalDesign ABC, stage runners (GL sim etc.) |
+-------------------------------------------------------------+
Bench and bridge are peers of agents, not replacements. They sit at the same layer because they both call into core + topologies through the harness and tool surfaces, without reimplementing them.
core.topology.CircuitTopology is the ABC every analog block
implements: design_space, params_to_sizing, generate_netlist,
compute_fom, check_validity, plus the prompt-metadata methods the
harnesses derive tool specs from. core.system_topology.SystemTopology
is the multi-block composition used by SAR ADCs.
core.pdk.PdkConfig is a frozen dataclass registry with two built-in
PDKs — IHP-SG13G2 (PSP103 / OSDI, VDD = 1.2, subckt prefix X) and
GF180MCU-D (BSIM4, VDD = 3.3). netlist_lib_lines() and
netlist_osdi_lines() are the single source of truth for model
includes; topologies call them rather than hardcoding .lib.
core.spice_runner.SpiceRunner wraps ngspice with async support,
measurement parsing (S8 fixed the regex), optional extra OSDI
(extra_osdi=), and optional XSPICE code models
(extra_codemodel=). core.gmid_lookup.GmIdLookup reads the
pre-computed LUTs (data/gmid_luts/ for GF180; the IHP path defaults
to the external ihp-gmid-kit install).
Physical verification wrappers live alongside:
glayout_runner.py, magic_pex.py, klayout_drc.py,
klayout_lvs.py, librelane_runner.py. The digital flow adds
core/stages/ (lint, sim, synth, physical, STA, GL sim, precheck,
veriloga compile, xspice compile) and core/designs/ (the digital
design wrappers consumed by the RTL-to-GDS pipeline).
Known limitation: IHP Magic steps hang upstream and IHP KLayout LVS
decks are incomplete (see docs/upstream_issues/). The code paths
are KLayout-only on IHP and full-chain on GF180.
Each module is either a designer helper (MillerOTADesigner,
analytical sEKV-based sizing) or a CircuitTopology subclass
consumed by harnesses without further glue:
miller_ota/ota_miller— IHP SG13G2 Miller OTA. Caveat:ProcessParamsatmiller_ota.py:104is hardcoded to IHP. Passingpdk=GF180MCU_Donly swaps symbol names; the sizing math keeps IHP sEKV constants and produces sub-Wmin transistors for GF180. Documented indocs/upstream_issues/miller_ota_gf180_process_params.md; will be fixed in the S9-gap-closure session.ota_analogacademy— IHP AnalogAcademy PMOS-input OTA.ota_gf180— GF180MCU OTA (the GF180 path that does close).comparator_strongarm— StrongARM latch.sar_adc_8bit+sar_adc_netlist— 8-bit SAR composed ofStrongARM + CDAC + Verilator SAR logic. Caveat in the class docstring: the effective resolution is closer to 7 bits (hence theTODO_naming.mdbacklog).sar_adc_8bit_behavioral— S7 behavioural variant; XSPICE comparator in place of the StrongARM.sar_adc_11bit— S7 design_reference (DESIGN_REFERENCE = True). True 11-bit CDAC (test_cdac_is_true_11bit), MSB-first decode, PVT / metastability / supply-ripple / reference-settling checks.
Each harness renders prompts and tool specs from skills and the topology in question; none of them should need to change when a new topology arrives.
autoresearch_runner.AutoresearchRunner— the greedy exploration loop (adapted from Karpathy's autoresearch). Persistsprogram.md+results.tsvand resumes from them.adk_harness.AdkHarness+adk_agents.py+adk_prompts.py— Google ADK orchestration withFlowRunner / DRCChecker / LVSVerifiersub-agents. Do not touch — this is the Track D hardening path.analog_roles/— the Session 6 DAG (Librarian / Architect / Designer / Verifier). Has aDryRunExecutorthat exercises the wiring without calling a model. The bench invokes it read-only.digital_adk_agents.py+digital_adk_prompts.py+digital_cc_runner.py+digital_autoresearch.py— the digital RTL-to-GDS multi-agent hierarchy (ProjectManager -> VerificationEngineer / SynthesisEngineer / PhysicalDesigner / SignoffChecker). Two backends: Google ADK (backend="adk") and Claude Code CLI (backend="cc_cli"). The autoresearch greedy loop over flow config knobs (density / clock / PDN) lives indigital_autoresearch.py.postlayout_validator.py— orchestrates layout -> DRC -> LVS -> PEX -> post-layout SPICE. Works on GF180 today; the IHP Magic hang blocks the PEX step on IHP.handler.py/system_handler.py/scenarios.py/tool_defs.py— shared infrastructure;tool_defsis where LibreLane helper functions and the digital RTL-to-GDS prompt builder live.
A Skill bundles four things: an optional prompt-template callable,
an optional OpenAI/ADK tool-spec dict, an optional validator, and
zero or more reference-file paths. The registry is populated at
import time; get_skill(...) / list_skills(prefix=...) are the
public API.
Twenty-three skills are registered today, grouped by prefix:
analog.*(11):explorer,corner_validator,orchestrator,adc_metrics,behavioral_primitives,gmid_sizing,sar_adc_design, plus the four roles underanalog.roles.{librarian,architect,designer,verifier}.digital.*(5):project_manager,verification,synthesis,physical,signoff.flow.*(4):runner,drc_checker,drc_fixer,lvs_checker.tools.*(3 legacy):evaluate_miller_ota,gmid_lookup,simulate_miller_ota. Kept as legacy tool specs; new topologies should exposetopology.tool_spec()instead.
src/eda_agents/bench/ exposes Pydantic v2 frozen models
(BenchTask, BenchResult, BenchScores, MetricBound) and enums
that mirror bench/schemas/{task,result}.json. Tests verify the
Pydantic and JSON enums never drift.
adapters.py is the only place bench code talks to EDA tools. Four
adapters are registered in HARNESS_DISPATCH:
dry_run— deterministic mock.analog_roles— invokes the S6 harness withDryRunExecutor.callable— resolves a dotted path undereda_agents.bench.adapters(restricted to prevent arbitrary-code execution from YAMLs). Seed tasks use this to point atanalytical_miller_design,run_pre_sim_gate_on_inline_netlist, andrun_gl_sim_post_synth.digital_autoresearch— stub (S10). ReturnsBenchStatus.SKIPPEDwith an explicitNOT_IMPLEMENTEDnote. The real adapter ships in S9-gap-closure.
runner.py implements audit-downgrade logic explicitly: an adapter
PASS with any failing scoring criterion is downgraded to
FAIL_AUDIT rather than painted over. execute_task and run_batch
are the two entry points; run_batch will use the Session 8
JobRegistry when workers > 1.
scripts/run_bench.py is the CLI surface. It writes per-task JSON +
summary.json + report.md under bench/results/<run_id>/ and a
local bench/results/latest.md pointer. Only
bench/results/s9_initial_smoke/ is committed — see
bench/results/README.md.
Reimplementation of the virtuoso-bridge-lite shape under
Apache-2.0, pointed at the open-source stack (ngspice / xschem /
KLayout / Magic / OpenROAD) instead of Virtuoso / Spectre.
models.py—BridgeResult,SimulationResult,ExecutionStatusenum with.ok,save_json,load_json. DistinguishesSUCCESS / PARTIAL / FAILURE / ERROR / CANCELLED.jobs.py—JobRegistry: UUID -> JSON under~/.cache/eda_agents/jobs/.ThreadPoolExecutor+ Future.submit / get / list / cancel / wait / poll_until_terminal / sweep / shutdown. Jobs whose result fails to serialize are markedERRORrather than stuck inRUNNING.ssh.py— Linux-only OpenSSH wrapper with jump-host (-J) and optional ControlMaster. Entirely mocked in tests.xschem.py— headlessXschemRunner.export_netlistwith aninfra_errorflag to separate tool-bug from setup-bug.klayout_ops.py— facade that delegates to the existingcore/klayout_*.pyrunners. Intentionally no logic of its own.cli.py—eda-bridge init / status / jobs / cancel / stop / start xschem-netlist.
- A spec or LLM request enters via a skill rendered by a harness
(
agents/), which wires a tool list onto the specific topology instance (topologies/). - The harness calls topology methods to produce a netlist and the
topology, in turn, asks
core/pdk.pyfor include lines. The result is run throughSpiceRunnerwith optional OSDI / XSPICE code models preloaded. - For the physical path:
core/glayout_runner.pyproduces a layout,core/klayout_*.pyhandle DRC + LVS,magic_pex.pyextracts parasitics, and the post-layout netlist goes throughSpiceRunneragain. The digital equivalent iscore/stages/*.pywired together bylibrelane_runner.pyand the agents inagents/digital_adk_*.py. - Bench tasks never implement any of the above. They are YAML
files under
bench/tasks/that name a harness (mapped to an adapter inHARNESS_DISPATCH) plus scoring criteria. The adapters are thin — they call the harnesses / runners / topologies read-only. The runner wraps the adapter result inaudit_adapter_resultand emits aBenchResult. - Bridge jobs use the same runners under the hood. The bridge's
value is job tracking (JobRegistry), remote execution (SSH), and
a stable CLI surface (
eda-bridge). Theexamples/14_bridge_e2e.pydemo exercises the full IHP path.
Autoresearch and Claude Code CLI runners prepend a rendered block of
methodology skills to the system prompt before the run-local
program.md content. The pipeline is:
- Each topology or design declares its skills via
relevant_skills() -> list[str | (str, dict)](added in S10b, seeCircuitTopology,SystemTopology, andDigitalDesign). skills.registry.render_relevant_skills(entries, context)looks up every entry in the skill registry and concatenates the rendered bodies with\n\n---\n\nseparators. A soft 12k-token cap emits a warning on overflow; the prompt is still returned whole.- The injection happens in four prompt construction sites:
AutoresearchRunner._system_prompt(analog SPICE).DigitalAutoresearchRunner._system_prompt(digital LibreLane).build_cc_spice_system_promptinagents/tool_defs.py(analog Claude Code CLI).build_digital_rtl2gds_promptinagents/tool_defs.py(digital Claude Code CLI, used byDigitalClaudeCodeRunner).
- Order is fixed: skills → program.md / agent preamble →
response-format suffix. Skills carry atemporal methodology;
program.mdcarries run-local strategy and accumulated learnings. Do not conflate them — a skill should never talk about "the current best" andprogram.mdshould never redefine gm/ID from scratch.
Escape hatch: setting EDA_AGENTS_INJECT_SKILLS=0 reverts every
injection site to the pre-S10c prompt. Use it to A/B bench behaviour
or to unblock a session if a newly added skill regresses Pass@1.
- Plan master:
~/.claude/plans/concurrent-beaming-bear.md. - License matrix:
docs/license_status.md— what can be reused verbatim and what must be reimplemented. - Upstream blockers:
docs/upstream_issues/ihp_magic_hang.md,docs/upstream_issues/ihp_klayout_lvs_deck.md,docs/upstream_issues/miller_ota_gf180_process_params.md. - Per-session diffs:
CHANGELOG.md+ the listed commit hashes. - Session handoff:
SESSION_HANDOFF.mdis gitignored but always refreshed at the end of a session with the state the next session needs.