CLI reference

The commonly used commands and flags, with a pointer to per-command help.

This page lists the commands and flags you are most likely to use. agni <command> --help always prints the authoritative, up-to-date detail. The reader for a file is chosen by its extension, so you pass the design file directly and never name a format.

Where a <file> is expected you may also name a design folder, one holding a design.yaml that declares which file is the design’s entry. Agni then reads that entry. The descriptor also lists companion views, and Agni reads those too, so a netlist’s connectivity rules and a board’s copper rules run from one argument. See Projects and designs.

The commands fall into four groups by what each one takes as its argument.

The agni commands grouped by what they take. Seven commands read one design and report on it. One compares two revisions. Two host the browser viewer. Three work on a results document written by an earlier run, which stays readable after the design is gone. READ ONE DESIGN COMPARE TWO REVISIONS HOST A VIEWER KEEP A RUN a design file or a design folder an older revision and a newer one a design and the project it is in a results document from an earlier run agni start scaffolds the folder form stats check review query render intake emit diff open serve results results --compare import-results component, section, and net counts findings, gated by --fail-on one outcome per checklist item rows of facts, each with its provenance the schematic or the board, svg or pack a sanitized summary, no net names in it the same design as IPC-2581 what changed, as text, json or csv one design, prints its URL named mounts, the whole viewer renders it with the design deleted the three-way entity split writes one from another tool's report Not shown: the global flags, and the developer commands native, validate, census and derive.

Commands

stats <file>

Print component, section, and net counts for one design. Run it first to confirm the tool read your design the way you expect before trusting anything downstream.

The counts cover what the read extracted. On a hierarchical EDIF netlist that is the top cell alone unless you pass --flatten-hierarchy, and stats then adds a line saying how much it left out. Every command that reads the design prints the same fact to stderr, naming the largest cells:

note: board.edn is hierarchical and only its top cell was read. 7 sub-cells holding 74 instances were not extracted (B: 40, E: 12, G: 9, D: 7, A: 3, 2 more). Counts and checks cover the top cell alone. Pass --flatten-hierarchy to read every block.
...
not extracted:       7 sub-cells holding 74 instances (counts above are the top cell only)
flag what it does
--format <fmt> text (the counts above, the default) or json, the GetDesign rpc’s GetDesignResponse
--mask <paths> with json, which fields to carry, over GetDesignResponse: design is the design’s whole IR, design.nets or design.components.mpn parts of it (a path through a list applies to every element), * everything. Unset is the summary alone. Repeatable or comma-separated; an unknown path is an error
--net <name> with a mask selecting design, keep only this net in design.nets (repeatable)
--ref <ref_des> with a mask selecting design, keep only this component in design.components (repeatable)

Every analysis reads the IR. By then the project’s configuration has applied and the ingestion passes have run, which promote part numbers and stamp classes. A script prototyping its own analysis can read the IR here rather than parse the netlist again. A mask entirely under design reads the netlist and skips the drawing.

$ agni stats designs/gateway/gateway.edn --format json --mask design.nets --net PMIC_EN
{
  "name": "",
  "sourceFormat": "",
  "componentCount": 0,
  "netCount": 0,
  "undrawn": [],
  "layout": "",
  "sheets": [],
  "nativeAvailable": false,
  "availableLayouts": [],
  "contentHash": "",
  "unexpandedHierarchy": [],
  "design": {
    "name": "",
    "irVersion": "",
    "sourceFormat": "",
    "libraries": [],
    "components": [],
    "nets": [
      {
        "name": "PMIC_EN",
        "netClasses": [],
        "connections": [
          {
            "componentRef": "U1",
            "pinRef": "4",
            "attributes": {},
            "prov": {
              "sourceFile": "designs/gateway/gateway.edn",
              "span": null,
              "nativeId": "IU1",
              "nativeIdKind": "edif-rename-id"
            }
          },
          {
            "componentRef": "R2",
            "pinRef": "1",
            "attributes": {},
            "prov": {
              "sourceFile": "designs/gateway/gateway.edn",
              "span": null,
              "nativeId": "IR2",
              "nativeIdKind": "edif-rename-id"
            }
          }
        ],
        "id": "f8eb746d81f9",
        "roles": [
          {
            "roleKind": "ROLE_CONTROL",
            "source": "ROLE_SOURCE_CONVENTION",
            "reason": "the name matches the control pattern _EN$"
          }
        ],
        "attributes": {},
        "prov": {
          "sourceFile": "designs/gateway/gateway.edn",
          "span": null,
          "nativeId": "PMIC_EN",
          "nativeIdKind": "edif-rename-id"
        }
      }
    ],
    "sheets": [],
    "inputDiagnostics": null,
    "footprints": [],
    "layers": [],
    "stackup": null,
    "constraints": [],
    "bom": [],
    "netGroups": [],
    "attributes": {},
    "fidelity": [],
    "prov": null
  },
  "hierarchyNote": "",
  "drawingNote": "",
  "flattenReadsMore": false
}

check <file>

Run the rule catalog and report findings. See Checks and reports.

flag what it does
--rule <name> run only this rule (repeatable)
--tag <key>=<value> run only rules with this tag, e.g. --tag category=power (repeatable)
--subject <kind>:<ref> answer for this net, component or pin alone, spelled net:SDA0, component:U7 or pin:U7.3 (repeatable). Each rule reports exactly what a full run reports about it, and a component covers its own pins, so --verdicts --subject net:SDA0 lists every rule’s conclusion about that net with its evidence. Some rules take a symbol file, a drawn bus or a wire end as their subject. Those have no answer for one net or part, so they are not run and the output lists them as skipped. text, json and csv only, since a report or --results-out reads as the whole design’s run
--format <fmt> text (default), markdown, json, csv, report, or html. csv emits one row per finding with a stable header, for a spreadsheet-driven review. html is the verdict report as a self-contained page and turns --verdicts on by itself
--verdicts report the CONSIDERED SET instead of the violations. Each rule looks at a set of subjects, and this reports its conclusion about each one, with the evidence for a pass. The verdicts are a separate table, so plain --format csv is unchanged. Honours --format text/csv/json/html, and with json it prints the whole CheckDesign response, findings and verdicts both, as a server returns it. Only rules that state a considered set contribute. A rule absent from the output declines to say, and is not reporting that it considered nothing
--order-by <cols> order the csv rows by these columns, comma-separated, a leading - for descending: --order-by=rule,-subject. A column sorts by its type, so a net or part column puts R2 before R10. csv only. The Tabulate rpc applies the same order, so the csv and a workbook built from the same answer agree
--query-sets with --format html, answer every query set the design’s project keeps and add each as a Queries section after the rules, so one file carries the checks and the queries (agni issue 1190). Any other format is refused
--table <name> with --verdicts --format csv, write one of the run’s tables instead of the verdicts. The Tabulate rpc returns the same tables for the run: pin_map (a design’s IO map one row per declared pin, beside the netlist and the datasheet), verdicts_by_rule and skipped. A name with no table in the run is refused, and the error lists the tables the run has
--server <where> where this run’s links should point. Empty, the default, mints none, so a pipeline gets no links. self starts a viewer on a free port, serves THIS run’s mount table, and blocks until Ctrl-C, so a link cannot disagree with what was read and a mount minted for the run is linkable like any other. self:PORT does the same on that port and fails if it is taken. A URL names a server someone else runs, and the CLI asks that server whether it serves the same mounts from the same roots
--remote <url> send this command’s request to a running agni serve instead of reading the design here, and if the server already holds the design, it answers from what it kept (a repeated check on the Jetson board takes 0.18 s against 1.39 s run locally). The output is byte for byte what the local command prints. The request carries the design’s files, but only the ones the server does not already hold, so the server needs no copy of them and no mount over the path (agni issue 1061). If the design’s files cannot be listed here, the command fails before anything is sent. Some flags configure the engine in this process rather than travel with the request. Those (--params, --profile-path) are refused, since the server answers with what it was started with. --timing is refused too, because it would time this process rather than the server. --server still builds its links from this machine’s mount table
-o, --out <file> write the --format output to this file, - for stdout (the default), so a report needs no shell redirect and composes with a pipe. Distinct from --results-out. This flag writes what you would have redirected, and --results-out writes a check-result DOCUMENT, which agni results re-renders. The written-file note goes to stderr
--fail-on <sev> exit non-zero when a finding sits at or above error / warning / info. This is the severity axis. For the coverage axis, see review --fail-on-outcome and --min-answered below
--conventions <file> compose a naming-convention config into the run (see Naming conventions)
--profile-path <dir> compose a directory of YAML interface-profile declarations into the catalog, namespaced profile-overlay/ (see Interface profiles)
--params <dir> load a datasheet parameter set, enabling datasheet-backed rules (see Datasheets)
--board-path <file> attach a board that is NOT a declared companion of the design, so board-tier rules resolve instead of finding no copper
--timing print where the run’s time went to stderr: each stage (resolving the project, reading each tier, building the model, the rules, locating), which cache tier answered each read, and the slowest rules, so the answer on stdout is unchanged. A served request reports the same breakdown when asked (see Where a request’s time goes)
--timing-memory --timing, plus the process’s memory as each stage ended (Sys and the heap) and what each rule allocated, to find which stage raised the peak

review <file>...

Run a review checklist over one or more designs and report one outcome per item. Where check asks “what is wrong with this board”, review answers “which of our questions did we actually answer”. Its outcome vocabulary distinguishes a check that passed from one that never ran.

flag what it does
--checklist <name-or-file> which checklist to run. A value ending in .yaml or .yml is a review manifest file read from outside the project, and - reads one from stdin; any other value names a checklist declared under checklists: in the design’s project.yaml. Optional when the design belongs to a project that declares one, since the project’s first checklist is its default
--conventions <file> a naming-convention config, whose rules join the catalog and whose lexicon reaches the design read
--profile-path <dir> interface-profile declarations added to the catalog
--params <dir> a datasheet parameter set, enabling datasheet-backed items
--intent-path <file> a design-intent declaration sent with the run, so intent-bound items resolve instead of reading needs-design-intent. It replaces the design’s own intent: for this run rather than adding to it
--board-path <file> a board-geometry file attached to a netlist design, so board-tier items resolve instead of n/a
--coverage a per-area rollup of how many items each area decided, instead of the per-item report. Renders as markdown only, so it refuses an explicit --format; the --format html page carries the same rollup in its header
--ratified-floor <n> datasheet-confidence floor below which a fail reports as provisional (default 0.9)
--fail-on-outcome <list> exit non-zero when any item sits at one of these outcomes, e.g. fail or fail,provisional. Off by default
--min-answered <n> exit non-zero when fewer than n items produced an answer. Off by default
--format <fmt> markdown (default), json, csv, or html. csv writes one row per checklist item (area, id, title, outcome, note, findings, unmet) for one design, as the Tabulate rpc projects it. html is the checklist as a self-contained page: areas and items in the manifest’s order, every finding per item rather than the markdown cell’s first three, and one link per finding when --server is given. One design at a time, since a page’s title, hash and links all name one design. json is protojson of the CreateReview rpc’s Review, with every finding per item and a summary, and the --fail-on-outcome and --min-answered gates read their counts from that summary; several designs give a ListReviewsResponse with one Review each. A CLI run is stored nowhere, so its name is empty
--order-by <cols> order the csv rows by these columns, comma-separated, a leading - for descending: --order-by=outcome,id. csv only
--server <where> where this run’s links should point. Empty, the default, mints none, so a pipeline gets no links. self starts a viewer on a free port, serves THIS run’s mount table, and blocks until Ctrl-C, so a link cannot disagree with what was read and a mount minted for the run is linkable like any other. self:PORT does the same on that port and fails if it is taken. A URL names a server someone else runs, and the CLI asks that server whether it serves the same mounts from the same roots
--remote <url> send this command’s request to a running agni serve instead of reading the design here, and if the server already holds the design, it answers from what it kept (a repeated check on the Jetson board takes 0.18 s against 1.39 s run locally). The output is byte for byte what the local command prints. The request carries the design’s files, but only the ones the server does not already hold, so the server needs no copy of them and no mount over the path (agni issue 1061). If the design’s files cannot be listed here, the command fails before anything is sent. Some flags configure the engine in this process rather than travel with the request. Those (--params, --profile-path) are refused, since the server answers with what it was started with. --timing is refused too, because it would time this process rather than the server. --server still builds its links from this machine’s mount table
--results-out <file> also write the run as a self-contained check-result document
-o, --out <file> write the --format output to this file, - for stdout (the default), so a report needs no shell redirect and composes with a pipe. Distinct from --results-out. This flag writes what you would have redirected, and --results-out writes a check-result DOCUMENT, which agni results re-renders. The written-file note goes to stderr
--render <dir> also write an annotated schematic SVG per design, each finding highlighted in place
--companion <file> a geometry file to draw --render images on, joined to netlist findings by net name
--timing print where the run’s time went to stderr: each stage (resolving the project, reading each tier, building the model, the rules, locating), which cache tier answered each read, and the slowest rules, so the answer on stdout is unchanged. A served request reports the same breakdown when asked (see Where a request’s time goes)
--timing-memory --timing, plus the process’s memory as each stage ended (Sys and the heap) and what each rule allocated, to find which stage raised the peak

Gating a pipeline on a review

check --fail-on and the two flags above gate on different axes. --fail-on pivots on finding severity, which states how bad an answer was. --fail-on-outcome and --min-answered pivot on item outcome, which states whether the question was answered at all. A checklist can stop answering four of its items with its failure count unchanged at zero, and no severity predicate can see that.

--min-answered counts the items that produced an answer: pass, fail, provisional, and computed-n/a. The report also shows a covered count, and --min-answered is deliberately stricter than that.

Why answered is stricter than covered

Covered subtracts only not-automated, which moves when a rule leaves the catalog. It does not move when a rule is present and its inputs are gone. Suppose the corpus behind a datasheet-backed item moves. The item then reads not-applicable, which still counts as covered but does not count as answered. That gap is the regression worth gating on.

A provisional does not trip --fail-on-outcome fail. It is a failure based on mock or below-floor datasheet data, so gating on it by default fails a pipeline on data quality rather than on design quality. Name it explicitly when you want it: --fail-on-outcome fail,provisional.

With several designs the gate reads every one of them, and the first design to violate it stops the run. Exit codes:

code meaning
0 the run completed and no gate tripped
2 a gate tripped
1 the run itself failed: an unreadable design, an invalid manifest, an unknown outcome name

check --fail-on uses the same codes, so one CI script can treat every gate in this CLI alike, reading 2 as problems in the design and 1 as the tool failing to do its job.

Machine configuration in agni.yaml

The flags that say where bytes are can live in a file instead of on every command. agni.yaml is searched for beside the working directory, upward a few levels, then in $XDG_CONFIG_HOME/agni/ (or ~/.config/agni/). The first one found wins outright.

# agni.yaml
mounts:
  boards: /srv/boards
  shared: /srv/shared
symbol_paths:
  - /usr/share/kicad/symbols
web_dir: /usr/share/agni/web
native_tools:
  - kicad-cli

Paths are resolved against the directory you ran from. A mount is an error when it names a directory that is not there, so a file meant to serve every working directory wants absolute paths.

A run says which file it took config from, on stderr. An explicit --mount, --symbol-path, --web-dir or --enable-native wins outright rather than merging, because naming a mount table answers for the whole table.

web_dir is where the viewer’s own assets are. It belongs to this tier because it locates bytes and cannot change what a run concludes, and a wrong value fails at startup rather than quietly. A repo checkout needs nothing here, since the default web already resolves per-directory. An installed binary has no relative answer when it runs from a design folder, so it needs this key or AGNI_WEB_DIR.

native_tools is the file form of serve --enable-native. It belongs here by the same test, because it says which golden renderers EXIST. Naming one that is not installed fails at the point of use, with the tool’s own name in the error. Only serve consumes it, though every command reports having read it, because the note says what the file supplied rather than what the command went on to use.

It carries only tier-1 config, deliberately. Naming conventions, interface profiles, seeded parameters, design intent and review checklists belong to a project instead. See Projects and designs.

Why analysis config cannot live here

Those five decide what a design is checked against, so they belong where they are scoped to the designs that declared them. Picture a machine-wide conventions file applied to every design opened with the CLI. That was a bug, and per-design config fixed it. Unknown keys are rejected, so a conventions: key here is an error rather than a silent global analysis tier.

checklists <design>

List the review checklists declared by a design’s project, in the order the project writes them, with the checklists it inherits through extends first. agni review runs the first one when no --checklist is named, and the viewer’s picker offers the same list. A design in no project, and a project that declares none, each say so, since the fix differs.

flag what it does
--format <fmt> text (one checklist per line, the default marked) or json (the ListChecklists rpc’s ListChecklistsResponse, each checklist carrying its manifest)

propose <folder|file.zip>

Group the files under a folder, or inside a .zip, into the designs they make, and print the design.yaml that would declare each, then each unread file and why no design reads it. The viewer shows the same proposal when files are dropped on it, from the same code, so the two agree. A folder already holding a design.yaml is reported as declared rather than guessed over.

The rules, briefly. A netlist claims the .eds, .kicad_sch and .kicad_pcb of its own name as its companions, and netlists sharing a first word (board.edn, board-rev-b.edn) are one design’s revisions, never merged. A .kicad_pro makes a KiCad design with its child sheets and the board of its name. So does a .kicad_sch when no other sheet names it as a child. A .kicad_pcb that matches nothing is left unread with that reason. Write a printed proposal into the folder as its design.yaml to declare it.

flag what it does
--format <fmt> text (each design.yaml, then what is not read) or json (the ProposeDesignsResponse the ProposeDesigns rpc returns)

site <outdir>

Write the browser-only demo as plain files for a static host: a landing page listing the seeded example boards, the viewer at each seeded design and at the drop page, the built viewer and wasm engine, and each seed’s files with a listing of them. Every page runs the engine in the visitor’s browser, so no agni server is involved. make demo-site runs it over the tutorial and the samples corpus. Run make ui wasm first, since the command copies the built assets.

flag what it does
--base <path> the path the site is served under, such as /agni/demo/; every asset and link goes through it
--seed <name>=<folder> an example board (repeatable); the folder becomes mount <name>, and its LICENSE line is shown beside it
--web-dir <dir> the viewer’s own templates and built static/

start <design-file> [dir]

Scaffold a review project around an existing design file, so the commands above can stop taking flags. dir defaults to the current directory.

agni start boards/gateway.edn ./gateway-review
gateway-review/
├── project.yaml            the project's id, a conventions stub, and a review checklist seeded from the shipped catalog
└── designs/gateway/
    ├── design.yaml         names the entry and its companion views
    ├── gateway.edn         copied
    └── gateway.kicad_pcb   copied, declared as a companion

After it, agni check gateway-review/designs/gateway and agni review … resolve the whole configuration from the descriptors.

flag what it does
--name <id> the project’s declared id; defaults to the target directory’s name, lowercased with anything outside [a-z0-9._-] replaced by -
--title <text> the human-readable label; defaults to the id

The design is copied, and the project owns its copy. Editing the original afterwards does not reach the project. The command prints what it copied and from where, and the generated design.yaml records the origin in a comment.

Companions are detected by a narrow rule. A sibling is declared a companion only when it shares the design’s stem and carries schematic geometry or a board. That excludes a later revision (gateway-rev-b.edn, a different stem and a legitimate analysis source of its own) and a second netlist encoding (gateway.edf, same stem but no view to contribute). Check the generated design.yaml and edit it, because membership cannot be inferred reliably and is declared per file instead.

Nothing is overwritten. An existing file stops the command and is named, and every planned write is checked before any write happens, so a refusal leaves nothing half-created. Pointing at a folder that already holds a project.yaml adds the design to that project rather than nesting a second one.

intake <file>

Extract a sanitized summary of a design: counts, class census, rail voltages, anomalies, and the parts list. It structurally cannot carry a net name or a connection, so it is safe to hand to someone who should not see the design itself.

flag what it does
--params <dir> a parameter set, which populates the MPN and datasheet-gap columns. A design inside a project reads the params/ declared by its project and needs no flag; this names a corpus for a design that belongs to none
--parts <view> types (BOM by distinct part type, default) or full (per-component AVL)
--format <fmt> md (default) or json

trial <design>

Walk a design through what a reviewer does with it (read, run every rule, score the project’s checklists, answer its query sets, trace a signal through a resistor, save the check report) and report how each step went. The report holds counts, timings and the names of shipped rules, and no net name, reference designator, part number, design name or path, because its type has no field for one. A rule a project wrote is counted without its name. It prints a link that opens the report as a GitHub issue to read before submitting. The compose bundle runs it as its trial service; see Trying agni on your own boards. On a hierarchical netlist pass --flatten-hierarchy, or the report counts the top cell alone and says how many blocks it did not read. A flattened report says so on its design line. When a design declares an IO map, the report also counts its pin map, per check and per reason a declared function went unchecked. It keeps no row’s pin, net or function (agni issue 1196).

flag what it does
--format <fmt> md (default) or json
-o, --out <file> write the report to a file rather than stdout

params <mpn>

Print one part’s whole datasheet record: its source documents, every parameter with its limit kind, bounds, test conditions and citation, the pins the datasheet declares, and any constraints between them.

This is the record behind a query answer. The datalog relations carry what a query can bind (param.max, param.range, param.typ, param.pin). This record also holds what a query cannot reach: the conditions under which a value is valid, the pin bindings, the full provenance and the verification state.

It needs no design, because a spec library is not a design. Name a corpus with --params, or name a design with --design so the design’s project supplies its params/.

When someone has verified a parameter, the record reports whether that verification still holds. When the corpus has moved to a later revision of the document, the row reads stale and names BOTH revisions: the one that was checked, and the one held now. Those two revisions tell you what to re-confirm. A pair of content hashes would not.

flag what it does
--params <dir> the datasheet corpus to read. When --design names a design in a project with its own params/, the project’s spec answers for each part it seeds and this for the rest
--design <path> a design whose project supplies the corpus, for a part seeded in a project rather than a loose directory
--format <fmt> text (default) or json. json emits the PartSpec itself, so a script binds to the same message the viewer carries
agni params LM1117 --params seed/
agni params LM1117 --design designs/gateway/gateway.kicad_sch
agni params LM1117 --params seed/ --format json

results <file>

Render a check-result document written earlier by check --results-out or review --results-out. The document is self-contained, so this works with the design deleted.

flag what it does
--format <fmt> the same output formats the live run offers. A review document’s json is the same Review the live run emits, unnamed
--compare <file> compare against another results document and print the three-way entity split instead of a report

import-results <report.json>

Read another tool’s check report (a kicad-cli DRC or ERC JSON report) as a check-result document, so it can be rendered and compared against a run of this engine with results --compare.

query <file> <query>

Search the design as data with an ad-hoc datalog query. Each answer prints with its provenance. See Querying your design.

flag what it does
--params <dir> a parameter set, to query datasheet facts (param.max(...))
--conventions <file> apply a naming convention’s LEXICON to the read, so net.rail/net.feedback/pin.type answer under your project’s vocabulary (see Naming conventions). The rules half is unused here, since a query runs no rules
--board-path <file> attach a separate board export so the board.* relations have facts; without it they are empty
--format <fmt> text (default aligned table), csv (spreadsheet-safe, header row, table only), json (protojson of the RunQuery rpc’s RunQueryResponse, so a script and a client parse one shape; it carries the column kinds, and echoes the query and the design so an answer names its own question), markdown or html (a view, with the question above its answer). See Taking a view out of the tool
--set <file or name> answer a query set instead of one query. A set is named queries sharing a preamble of rules, all answered over ONE read of the design (see Ask many questions at once). Takes the design alone, no query argument; - reads the set from stdin, a value ending .yaml or .yml is a file, and any other value names a set kept in the design’s project under queries/. json is the RunQueriesResponse, markdown/html/text one document with a section per query, and csv is refused. Every answer is written, then the command exits non-zero if any query could not be answered
--remote <url> send this command’s request to a running agni serve instead of reading the design here, and if the server already holds the design, it answers from what it kept (a repeated check on the Jetson board takes 0.18 s against 1.39 s run locally). The output is byte for byte what the local command prints. The request carries the design’s files, but only the ones the server does not already hold, so the server needs no copy of them and no mount over the path (agni issue 1061). If the design’s files cannot be listed here, the command fails before anything is sent. Some flags configure the engine in this process rather than travel with the request. Those (--params, --profile-path) are refused, since the server answers with what it was started with. --timing is refused too, because it would time this process rather than the server. --server still builds its links from this machine’s mount table
--order-by <cols> order the csv rows by these columns, comma-separated, a leading - for descending: --order-by=rule,-subject. A column sorts by its type, so a net or part column puts R2 before R10. csv only. The Tabulate rpc applies the same order, so the csv and a workbook built from the same answer agree
--title <name> name the view (or replace a set’s title), used as the heading in --format markdown and html
--lib <dir> a directory of derived-relation modules laid out as a project’s lib/ (<module.path>.dl, optional docs/<member.path>.md), sent with the query beside any library in the design’s project (see Adding a library member). Repeatable. Also read by --relations, so the catalog lists the modules’ members
--bind <name>=<value> bind a query variable to a value, repeatable: --bind ref=U1 asks with ?ref bound to U1, exactly as if "U1" were written in its place. A value is read as the type declared for its position, so a count or a voltage takes 3 or "3" alike and refuses abc; where nothing types the variable, a value that parses as a number binds a number and a quoted one (--bind 'n="3"') binds text. Naming a variable twice binds it to both values, and the query answers for each (--bind n=I2C_SDA --bind n=I2C_SCL). Binding a variable the query never uses is an error. Refused with --set, whose queries carry their own bind: (see Querying)
--limit <n> answer only the first n rows, and say how many the limit left out (total in --format json). Where the query writes its own limit, the smaller applies. Refused with --set, whose queries write their own (see Sort and page an answer)
--witnesses print how each row was derived: under each row, the fact or rule behind every clause of the query, a library member’s rule with its body facts filled in, and a not that held as not ... (absent). text and html put the tree under its row, markdown after the table, and json in each row’s witnesses; csv refuses it. Off by default, and a query without it costs what it always did (see Querying)
--omit-locations leave out where each answer entity is drawn (its sheetIds and reason in --format json’s entities) and skip loading the drawing, for a script that draws nothing
--budget <work> stop a query once it has done this much work and report it as over budget rather than answer late. 0, the default, sets none. --format json reports as work how much work an answer cost, so choose a budget from that number (see Running the server)
--server <url>|self print a link that asks the query again in the viewer’s query panel, with its --bind values and --title, or for a project set’s query, by the set’s name and the query’s (see Querying). self serves the design from this process until Ctrl-C. Refused with --speclib, --relations and --examples, which read no design
-o, --out <file> write the --format output to this file, - for stdout (the default). A view is a thing to hand to someone, so it wants a filename rather than a redirect. The written-file note goes to stderr
--timing print where the run’s time went to stderr: each stage (resolving the project, reading each tier, building the model, the rules, locating), which cache tier answered each read, and the slowest rules, so the answer on stdout is unchanged. A served request reports the same breakdown when asked (see Where a request’s time goes)
--timing-memory --timing, plus the process’s memory as each stage ended (Sys and the heap) and what each rule allocated, to find which stage raised the peak
--explain print each query’s evaluation plan to stderr: the order each rule body ran its literals in, the work and rows each produced, and how each relation was read and which index answered it. The tool for a query that is slower than it should be (see Where a request’s time goes)

trace <file>

Follow a signal from one pin to another and print what it goes through. See Following a signal.

The walk crosses series pass elements (resistors, inductors, ferrites, fuses), because those split a net without breaking the path. A capacitor is a DC block and is never crossed. A route may END on a rail or a plane but never passes through one, so a trace onto a supply reports the supply and stops there.

Both endpoints are pins, named <ref-des>.<pin>, where the pin is its package designator (U2.1) or its functional name from the part type (U2.MDIO). An IO map accepts the same two spellings (agni issue 1193). If several pins carry a name (U2.GND), it is refused, and the error lists their designators. So is a name that matches a pin only after dropping tokens, since a route from a guessed pin is about the wrong node. If an endpoint names nothing in the design, the command fails and exits non-zero. A pin spelled wrong in a declaration is a different answer from two pins that are not connected.

flag what it does
--from <ref.pin> the pin to start at, e.g. U7.3 or U7.SDA
--to <ref.pin> the pin to end at
--hops <n> how many series crossings to search through (default 6). Unlike the protection radii this is a search budget rather than an electrical claim, and every answer states the value it used, so a no-route can be re-asked wider
--format <fmt> text (default), or json, which emits the TraceDesign rpc’s Trace message in protojson, so a script reading the CLI and a client reading the API parse one shape
--server <where> where this run’s links should point. Empty, the default, mints none, so a pipeline gets no links. self starts a viewer on a free port, serves THIS run’s mount table, and blocks until Ctrl-C, so a link cannot disagree with what was read and a mount minted for the run is linkable like any other. self:PORT does the same on that port and fails if it is taken. A URL names a server someone else runs, and the CLI asks that server whether it serves the same mounts from the same roots
--remote <url> send this command’s request to a running agni serve instead of reading the design here, and if the server already holds the design, it answers from what it kept (a repeated check on the Jetson board takes 0.18 s against 1.39 s run locally). The output is byte for byte what the local command prints. The request carries the design’s files, but only the ones the server does not already hold, so the server needs no copy of them and no mount over the path (agni issue 1061). If the design’s files cannot be listed here, the command fails before anything is sent. Some flags configure the engine in this process rather than travel with the request. Those (--params, --profile-path) are refused, since the server answers with what it was started with. --timing is refused too, because it would time this process rather than the server. --server still builds its links from this machine’s mount table
-o, --out <file> write the --format output to this file, - for stdout (the default), so a report needs no shell redirect and composes with a pipe. Distinct from --results-out. This flag writes what you would have redirected, and --results-out writes a check-result DOCUMENT, which agni results re-renders. The written-file note goes to stderr
--render <file.svg> also draw the answer: the route’s nets and the parts crossed, on the design’s own schematic where it has one and on an auto-layout of its netlist where it does not. The output says which, so you never take the second for the first. A no-route draws too, marking the two nets that fail to join
--timing print where the run’s time went to stderr: each stage (resolving the project, reading each tier, building the model, the rules, locating), which cache tier answered each read, and the slowest rules, so the answer on stdout is unchanged. A served request reports the same breakdown when asked (see Where a request’s time goes)
--timing-memory --timing, plus the process’s memory as each stage ended (Sys and the heap) and what each rule allocated, to find which stage raised the peak

walk <file>

Walk outwards from a part or one pin, hop by hop, and print the nets reached, the part each was entered through, and the parts the walk did not cross, with the reason. Hop 1 is the start’s own nets. --kind picks which parts a hop crosses. series crosses what trace crosses, small-parts crosses any part with up to four connected pins and stops at ICs, connectors and rails, and all crosses every part with a cap per hop. See Walking outwards from a part. A start that names nothing in the design fails and exits non-zero.

--goal asks a goal question from a net the walk reached, and prints its routes after the walk. pin routes to the pin --to names, pull-up finds every pull-up resistor that takes the net to a rail, and rail routes to the nearest rail. Each route runs from the start along the walk to --at, then on to the goal. A goal with no route within its radius is an answer and exits 0. A goal that names nothing, such as a net the walk never reached, exits non-zero. See Goal questions from a walk.

flag what it does
--from <ref> or <ref.pin> the part (R1) or pin (U7.3) to start from
--kind <kind> series (default), small-parts or all
--hops <n> how many hops to walk, hop 1 being the start’s own nets (default 2)
--through <refs> parts to cross whatever --kind says, such as --through U7
--goal <goal> ask a goal question from the walk, pin, pull-up or rail
--at <net> with --goal, the reached net to ask from. The default is a pin start’s own net, or a part start’s first own net that is not a rail
--to <ref.pin> with --goal pin, the pin to route to, such as --to J4.2
--goal-hops <n> with --goal pin or rail, the crossings to search from --at (default 6)
--format <fmt> text (default), json, which emits the WalkDesign rpc’s Walk message in protojson, or csv, which writes one of the walk, walk_boundary and walk_goal tables that Tabulate projects
--table <name> with --format csv, walk (default), walk_boundary, or walk_goal, one row per net of each goal route, and one row with the outcome and reason when there is no route
--order-by <cols> with --format csv, sort by these columns, - before one to reverse it
--remote <url> send the request to a running agni serve, as on trace
-o, --out <file> write the --format output to this file
--timing print where the run’s time went to stderr, as on trace

pinmap <file>

Draft a pin map for one device from the netlist (agni issue 1238). It lists every pin of --device with the net it sits on, and walks from each signal pin through series parts, as trace does, to the pins of other ICs and connectors it reaches. A pin reaching exactly one gets it as the row’s far end, and one reaching several lists them in a comment. A pin on ground or a supply rail is not walked, since every part reaches it. The output is an intent file with an io_map. check --intent-path reads it, and the run then lays the map out a pin at a time (see Reading the map one pin at a time).

The draft agrees with the netlist it was read from, so a check against it passes by construction. Review each row against the datasheet or the interface before relying on it, then check later revisions against the reviewed map.

flag what it does
--device <ref> the part whose pins to map (required)
--pin <pin> map only this pin, by designator or functional name; repeat it for several
--hops <n> how many series parts a far-end walk may cross (default 6), as for trace
-o, --out <file> write the draft to this file, - for stdout (the default)

lexicon <design>

Show the naming vocabulary a design is read with (agni issue 1266). Every pattern, exclusion, ref-des prefix and part-number family is listed under its vocabulary (net.rail, pin.supply, class.tvs) with where it came from: built-in, or the convention that won, from the server’s --conventions, the project, the design’s own design.yaml, or the request. When a convention switches a built-in off with replace: true, the built-in is still listed, marked replaced.

--net <name> explains one net instead. It prints the net’s roles, each with its source and reason, then every net-name pattern that matches its name, with where that pattern came from. A matching exclude is listed too, because an exclude kept the name out of a vocabulary. When no net carries a name exactly, the name is looked up by its hierarchy leaf, so SCL finds /SCL when only one net ends that way.

--try <conventions.yaml> reads the design again with that file in place of the convention it uses, rules and lexicon both, runs the checks under both reads, and lists the nets whose roles change and the findings that appear or disappear. A file stating no lexicon: reads names with the built-ins alone. Each role is written with its evidence, as rail (convention), so a rail backed by a datasheet as well does not change when its name pattern goes.

flag what it does
--net <name> explain this net’s roles
--try <file> compare a candidate conventions file against the one in use
--format text|json|yaml json is the wire message; yaml writes the effective lexicon as a conventions file, every list stated whole with replace: true, so a project carrying it reads the design exactly as this run did
--remote <url> ask a running agni serve
-o, --out <file> write to this file, - for stdout (the default)

tabulate <request.json|->

Project an answer into tables. Every csv and workbook of that answer holds the same tables. The input is a TabulateRequest as protojson. It holds an analysis command’s --format json output (a check run, a query, a query set, a diff, a review) and optionally order_by and column_types. The output is the Tabulate rpc’s TabulateResponse. It reads no design, and it exists so a client on the CLI transport gets the engine’s rows rather than deriving its own (agni issue 862).

flag what it does
--format json the only format, the TabulateResponse

diff <old> <new>

Structural diff between two revisions, over the IR. See Comparing revisions.

flag what it does
--format <fmt> text (default human summary), json, or csv (one row per change, with a change_class column naming which kind it is)
--rename-approx also pair a net renamed AND changed slightly with its old self, as renamed-approx with the evidence (see Comparing revisions)
--include-equal also report the nets that did not change: kind equal in json, net-equal rows in csv, and a count in text. Off by default, since most diffs are about what changed
--order-by <cols> order the csv rows by these columns, comma-separated, a leading - for descending: --order-by=change_class,subject. csv only. The Tabulate rpc applies the same order
--server <where> print a look at it: link to stderr that opens <old> in the viewer with Compare started against <new> (?against=<mount>/<path>), in every format, so stdout holds only the diff. Takes the same values as check --server, and a link is printed only when both revisions can be linked there (agni issue 1126)

render <file>

Draw a design’s schematic or board view.

flag what it does
--layout <name> faithful (default, the design’s own geometry) or an auto-layout (force, grid, layered, orthogonal, stress) computed from the netlist
--format <fmt> svg (default) or pack (for the WebGL viewer)
--conventions <file> read the design under a naming convention not yet in its project, as check does. stats and diff take it too, and diff reads both sides under it
-o <file> output path

open <design>

Serve one design and print the URL that shows it, so a board can be looked at from the directory it lives in. Binds loopback on a free port and serves only that design (and its project, where it has one). It also prints a ready-made agni check … --server … line, which carries --mount because a mount is minted per process and a second agni would not know this one.

agni check <design> --server self is the same idea from the other end and needs no second command: one process reads the design and serves it, so the links resolve without anything being declared. open remains the way to look at a board without running anything over it.

flag what it does
--addr <addr> listen address; empty picks a free port on loopback
--web-dir <dir> the viewer’s own assets, as for serve

serve

Host the browser viewer and the web API on one port. Build the web bundle first.

flag what it does
--addr <addr> listen address (default :8080). Port 0 takes a free port and the banner names the one it got, for a script starting a server of its own
--web-dir <dir> the viewer’s OWN assets, not designs (default web; then web_dir in the nearest agni.yaml, then AGNI_WEB_DIR)
--mount <name>=<path> expose a design folder in the file browser (repeatable)
--mount-root <dir> expose every subdirectory of this one as a mount named after it, so folders can be bind-mounted in without a --mount each; an explicit --mount of the same name wins, and a missing root yields no mounts rather than an error
--engine <server|wasm> which engine the viewer analyses a design on: server (the default) or wasm, which runs it in the visitor’s browser over the design’s files and needs make wasm; a page’s ?engine= overrides it (see Analysing in the visitor’s browser)
--wasm-max-bytes <n> on the wasm engine, the size limit, in bytes of a design’s files, for analysing it in the browser (default 128 MB); a bigger one goes to the server engine and the page says so. A page’s ?wasm-max-bytes= overrides it
--theme <name> render palette: default or dark
--conventions <file> a naming-convention config (YAML) used as this server’s default: its rules join the catalog every rule-running surface uses, and its lexicon becomes the default naming vocabulary. A request carrying its own convention replaces this one for that request (see Naming conventions)
--params <dir> a directory of published PartSpecs. Every design is checked against them unless its project has its own params/
--params-url <url> read published PartSpecs from a datasheet service instead, such as agnids serve --corpus (see Datasheets); not with --params
--profile-path <dir> compose interface profiles into the catalog every rule-running surface uses, the check panel included (see Interface profiles)
--datasheets-url <url> where the datasheets workbench is served, such as http://host:8090 for a running agnids serve; the landing page links its Datasheets card there, and hides it when this is empty
--review-store <dir> a writable directory that keeps review runs, created if absent; without it the review endpoints report that this server stores none (see Running the server)
--query-budget <work> the most work any one query may do, over the query, check and review rpcs alike; a request may ask for less and never more. 0, the default, enforces none (see A work budget for served queries)
--query-budget-warn <work> log every served query that costs more than this, with the query and a suggested budget (default 10,000,000; 0 logs none)
--design-cache <n> how many reads the server keeps between requests (a parse, a derived design, a drawing, a board or a built model, each counting once), each checked against its files on every use (default 15; 0 keeps none; see Kept reads)
--upload-max-bytes <n> the most bytes the server keeps of the design files sent by clients (--remote). Past it the least recently used are dropped, and a client sends a file again when an ingest asks for it (default 1 GB; 0 takes no files)
--slow-request <duration> log every request that takes longer than this, such as 2s, naming the stage that took the most, which cache tier answered each read, the slowest rules, and the query plan when evaluating was the slowest stage (0, the default, logs none; see Where a request’s time goes)
--mcp also serve the analysis over the Model Context Protocol at /mcp (streamable HTTP, localhost origins only), for an LLM client such as Claude Code; every answer links to this server’s viewer (see Asking through an LLM)
--viewer-rooms with --mcp, let an agent move the viewer tabs open on this server: each tab offers viewer_show_sheet, viewer_locate_verdict and the rest of the page’s methods as tools through a relay at /relay/, and takes calls only once its reader turns on “Let agents drive this tab” (see Moving a viewer tab you have open)

mcp

Serve the same MCP tools as serve --mcp on stdin and stdout, for a client that starts the server itself. It reads the mounts as serve does, and takes --params, --params-url, --profile-path, --conventions and --query-budget as serve does. --server <url> names a running agni serve that mounts the same names, so each answer can link there; without it every answer says why it carries no link (see Asking through an LLM).

healthcheck

GET /healthz on a running server and exit 0 only on a 200. The container image’s HEALTHCHECK runs it, so the image needs no curl or wget. The debian-slim runtime ships neither. Pulling one in for a single HTTP request would add a package to every deployment, and the binary can make the probe itself. Running the server covers where it fits in a container.

flag what it does
--addr <host:port> the server to probe (default localhost:8080). serve’s own default is :8080, which binds every interface and is not a dialable host, so the probe names loopback explicitly
--timeout <duration> how long to wait for a response (default 3s)

A restart policy acts on the status code alone, so the probe reads no other route and does not look at the body.

emit <in> [out]

Convert any design the tool reads into IPC-2581 or an EDIF netlist (stdout if out is omitted). A conversion is A -> IR -> B with the IR as the pivot, so the input format does not have to match the output one.

Flag Meaning
--format <fmt> ipc2581 or edif. Omitted, the OUT file’s extension decides: .edn, .edf and .edif write EDIF, everything else writes IPC-2581. Writing to stdout has no extension to read, so it writes IPC-2581
agni emit board.kicad_sch board.edn

Both writers are lossy-bounded and state their limits, so a conversion carries what the IR holds rather than what the source file held. The EDIF writer emits the netlist tier only. A hierarchical design writes out FLAT. Read without --flatten-hierarchy that is its top cell alone, since the reader drops every sub-cell’s contents before the writer sees it; read with it, the file is the whole schematic in one cell, with each block’s parts and nets under their path names. Array bus declarations are not written. They reach the IR as a diagnostic, and the diagnostic does not record which port declared them.

An EDIF instance needs a name, because a net anchors each of its pins with an instanceRef naming one. The writer prefers the source’s own id, falls back to the ref-des (with the section index appended for a multi-gate part), and breaks a collision by suffixing, so the names are unique across the file and stable between two exports of the same design. Neither seed is unique by itself, because two of the readers record no instance id at all, and a KiCad symbol placed on two sheets of one hierarchy carries the same id under two ref-des.

One connection shape has no instance to name. A KiCad power symbol or PWR_FLAG reaches the IR as a connection on #PWR01 while the component list holds only physical parts, so there is nothing in the contents to anchor it to and minting one would invent a component the source never had. Those are written as bare portRefs and read back as connections with no component, as an EDIF no-ref connection always has. Every other connection is anchored.

The output is written for tools other than this one to read. That is a stricter target than round-tripping through agni’s own reader, and the writer did not always meet it. Three things follow. The libraries come first and the design node last, so a reader that resolves a reference the moment it meets one is never asked to follow a forward reference. A portRef names the cell’s PORT rather than the pin’s physical designator. Each instance carries the mapping between them in a portInstance table, EDIF’s construct for it. And an identifier holds no character a reader rejects, with anything richer moved into the quoted display half of a (rename ...). Raw KiCad UUIDs open with a digit and carry hyphens, and a third-party reader skipped all 1123 instances named by them before the writer renamed them (agni issue 582).

Anything the file references, it also declares. Where a part type declares fewer pins than the netlist connects, the cell’s interface is completed from the connections; where no part type exists at all, the cell is declared too, and so is the top cell the contents live in. A board file is the case that needs all three, because copper carries footprints, pads and nets and never says what the part is. The cellRefs and portRefs in the same file already name these cells and pins, so declaring them invents no part. The declaration is bounded the way a netlist is. A pin on no net is invisible, so a cell completed this way carries the pins the design uses and not the pins the part has.

A part named gateway:CONN4 inside a library called gateway is written as cell CONN4, since EDIF’s own library scoping already carries the qualification and a reader that treats the qualified form as one name rejects the colon in it. The IR keeps the prefix, where it selects which symbol file an external reference resolves from, so this is an encoding choice in the writer and not a rename. A design taken out through EDIF and read back therefore names that part CONN4 in library gateway.

.eds is refused rather than treated as EDIF. It is a dual-capability format, a netlist plus the faithful schematic geometry beside it, and there is no schematic writer yet, so emitting one from the netlist writer alone would produce a file claiming to carry a drawing that carries none.

version

Print this build’s identity: the version, the commit it was built from, and the Go toolchain and platform. agni --version prints just the first line.

A results document records the same string as its producer, so a report archived months ago and the build in front of you can be compared directly. Worth capturing alongside any report you keep.

agni v0.1.1
  built:    2026-08-10T14:46:21Z
  go:       go1.26.4
  platform: darwin/arm64

A build from a source clone reports the commit rather than a release (b020fea02643, suffixed +dirty when the tree had uncommitted changes).

Global flags

flag what it does
--symbol-path <dir> directory to search for symbol files (.sym for xschem/gEDA, .kicad_sym for KiCad) so schematics that name rather than embed their symbols resolve to pin-level nets and faithful artwork. Repeatable, searched recursively, and the schematic’s own directory is always searched. Defaults to AGNI_SYMBOL_PATH (colon-separated) when the flag is absent. The container image supplies its bundled libraries to every subcommand this way.
--mount <name>=<path> expose a folder as a named mount (repeatable). Every command takes it, not just serve. An artifact is addressed as mount://<mount>/<path>, and with a matching --mount the CLI and a server produce identical URIs for the same design, so a review created either way records the same design and the two are directly comparable. Without it the CLI mints a mount per argument, rooted at the enclosing project when there is one, so agni check some/board.edn still works with no configuration. You may also pass a full mount://... URI in place of a path.
--flatten-hierarchy read a hierarchical netlist as its whole schematic, walking into every block cell the top cell places. A net crossing a block’s port is one net with its parent’s and keeps the parent’s name, a net inside a block keeps its name when it is unique and is otherwise named by the block’s path (/A1/MID), and a part inside a block placed more than once carries the path in its ref-des (B2/R1). Parts sharing a designator are sections of one component wherever they sit. A flat netlist reads the same either way, and only EDIF has blocks to walk today. Over --remote it travels in the snapshot (Snapshot.flatten_hierarchy). A design can declare it once with flatten_hierarchy: true in its design.yaml, and the viewer’s hierarchy strip offers Read every block, which adds ?flatten=1 to the page.
--as-named read exactly the file named, even when its design.yaml declares it a companion view of a different entry. Without it, analysing a declared companion (a schematic export, a board) reads the design’s entry instead, because a companion is a view of the design rather than a second source of it. Reach for this when reading a view as a netlist is the point, such as checking that two views of one design still agree.
--version print the build version and exit.

A command asked for --format json writes a failed request as JSON too. Where its request to the engine fails (a query that does not parse, a design that cannot be read), it writes the error as the server’s API sends it, to stdout or to -o’s file, and still exits non-zero with the text error on stderr. A query that does not parse carries where it stopped:

{"code":"invalid_argument","message":"invalid argument: query: variable ?r ?n: ...","details":[{"type":"agni.v1.webapi.TextPosition","value":"CA4QARgP","debug":{"offset":14,"line":1,"column":15}}]}

A mistake in the command’s own flags is text on stderr alone, since no request was made. A query set writes its answers before exiting non-zero, and writes no error document after them.

Advanced and developer commands

agni also has native (render/open with the design’s own EDA tool), validate (reader-health smoke over many files) and census. The datasheets service is a separate binary, agnids, built from the datasheet/ module. It derives datasheets (agnids derive), publishes a workbench draft (agnids publish), and merges a pin-mux table into a published spec (agnids merge-mux). validate is worth knowing as a user. Point it at a folder of exports and it reports which of them this tool can actually read. The rest sit closer to the engine and are covered in the developer docs.

Which formats are read

The reader is picked by file extension (case-insensitive): EDIF netlists (.edn/.edf/.edif) and geometry (.eds), KiCad (.kicad_sch/.kicad_pcb/.kicad_pro), IPC-2581 (.xml/.cvg), Telesis netlists (.tel, netlist only, no geometry), and .sch (xschem / gEDA / legacy KiCad, sniffed by header). An .xml file is read by its root element. <IPC-2581> is IPC-2581, <ODX> is ODB++X, and any other root is refused with its name.

An ODB++ job is a directory rather than a file, so it is named by its folder, or by a .tgz or .zip of it, and agni enters it through the folder’s matrix/matrix file (agni check board-odb/, agni check board-odb.tgz). It reads as a netlist and as a board, as IPC-2581 does, so agni diff schematic.edn board-odb/ checks a layout against the schematic it was drawn from. ODB++ allows no spaces in a name, so a net the schematic calls /USB Hub/VBUS arrives as /USB_Hub/VBUS and the diff reports it renamed. Members a producer compressed (features.Z, .gz) are read as well.

ODB++X (ODX) is the same data written as one XML file, which Allegro’s “ODB++ XML” export writes (agni check board.xml). It reads as a job does, into the same netlist and board. A pin is named by its number, as the file’s netlist names it. Only metric ODX files are read so far, and an imperial one is refused with its units named.

A Cadence Allegro board (.brd) reads as a netlist and as a board, as an ODB++ job does, so agni check board.brd needs no export from Allegro. Boards saved by Allegro 16.6 and 17.2 are read so far; a board from another release is refused with the release named, and an Eagle board, which shares the extension, is refused as Eagle. The reader keeps the copper, pads, vias, zone outlines, outline and silkscreen. It does not yet read the board’s constraint sets, so track-width and copper-clearance judge an Allegro board against their fixed floors.

An Altium Designer board (.PcbDoc) reads the same way, so agni check board.PcbDoc needs no export from Altium. Only the binary form is read, which is what Altium saves by default. The netlist uses the designators drawn on the board, which differ from the schematic’s on a multi-channel design, and leaves out mounting holes and fiducials, as Altium’s own ODB++ export does. Each part keeps its schematic parameters, its part number and description among them, which an ODB++ export drops, so a check of the .PcbDoc can recognise parts a check of the export cannot. Assembly variants live in the project file and are not read.

An OrCAD Capture design (.DSN) reads as a netlist, so agni check design.DSN needs no export from Capture. A block that keeps its pages in a .DSN of its own is opened by the path the design records for it, relative to the design, so the block files stay where the design expects them. The read is always flattened. Each part takes the designator Capture annotated across the hierarchy, and the board carries the same one. A block whose file is missing is named in a note, and the design reads without it. A pin flagged not connected reads on no net. Only Capture’s modern format is read, and a design in the legacy format is refused with its version named. SPECCTRA router files share the extension and are plain text, so agni refuses one as SPECCTRA. Each page draws as a sheet, each block’s under the page that places it, so agni render design.DSN --sheet 1 draws the first block.

A schematic that references external symbols resolves them by one of three routes, in the order they are searched: its own sym-lib-table for a KiCad project, the symbols libraries its design and project descriptors name, and any --symbol-path directory. A design that belongs to a project needs no flag, and the flag remains for a library the project does not know about.