Skill: read a design result¶
For: an AI agent that just got a 200 back from a MagicON AI design engine and
is about to tell a human what it means.
Load this before you summarise a result. A design response is not a number with some prose around it. It is a verdict, a list of things the engine chose for you, and a list of things it wants you to decide. Summarise only the numbers and you will hand your user a clean-looking report over a problem the engine already caught.
Every figure on this page was measured against the production API on 2026-09-09. Nothing here is illustrative.
The one rule¶
A 200 is not a verdict. It means the engine answered. It does not mean the
design works, that every slot was filled, or that you asked a meaningful question.
Measured — an empty body to the transmit-chain route:
curl -X POST https://api.pcbgenerator.com/api/rf-chain/design-sync \
-H "Content-Type: application/json" -d '{}'
200, "status": "success", a real catalogue PA (CG2H40010F) and an output of
1.0 dBm. You stated no frequency and no target, and it answered anyway — and it
said so, in chain.params, which echoes the design point it actually used:
{"frequency": null, "target_output_power": 0, "application": null, …}.
The status code tells you the transport worked. Everything that matters is in the body.
Read these four things, in this order, before you write a sentence:
| Read | Field | If you skip it |
|---|---|---|
| 1 | needsPartFlags |
You report a complete module that has a hole in it |
| 2 | warnings and assumptions |
You present the engine's choices as your user's decisions |
| 3 | the power verdict | You tell someone a chain met a target it missed |
| 4 | the as-built output | You quote an aim instead of what the board ships |
1. needsPartFlags — the honesty flag¶
A list of slot names the catalogue could not fill. [] means every slot was
genuinely filled from real parts.
A non-empty list looks like ["substrate"] — the slot name, and the slot left
empty.
⚠ Which routes carry it — measured today: POST /api/receiver/design and
POST /api/tr-module/design both return the key. POST /api/rf-chain/design-sync
does not return it at all. Its absence there is not an empty list and not a
statement that nothing is missing; there is simply no such field on that route, so
read that response's warnings instead.
What it is not: a failure, and never a placeholder. The engines do not substitute a plausible part for a missing one. If no catalogue part satisfies a position in the chain, the position comes back named here and stays empty. That is the whole contract: an engine that will not invent a part has to have somewhere to say so.
What to do: name the flagged slots to your user as an open item. Do not fill them yourself. A part number you produce is not a part number the catalogue stands behind, and your user cannot tell the two apart in your prose.
2. warnings and assumptions are disclosures, not errors¶
Both are lists of plain-language sentences. Neither one means the call failed —
a failure is a 422 or a 500, and it arrives as {"detail": "..."} instead of
a result.
⚠ Which routes carry which, measured today: warnings is on all three design
routes. assumptions is on the receiver route only — /api/rf-chain/design-sync
and /api/tr-module/design do not return that key, and their equivalent
disclosures ride inside warnings and inside the per-budget disclosures lists.
Measured on the 9.4 GHz receiver design (POST /api/receiver/design, {"rfFrequencyHz":
9400000000, "bandwidthHz": 10000000, "requiredSnrDb": 12}): 10 assumptions and
6 warnings, alongside a complete, fully-filled design.
assumptions are the things the engine chose because you did not state them:
"IF of 1128.0 MHz was CHOSEN BY THE ENGINE (~12% of RF, floored at 5x the signal bandwidth), not supplied. Pass an explicit IF to override."
"LO injection side was not specified; high-side chosen (deterministic default)."
"PAPR assumed 9 dB (modulated/pulsed); pass papr_db to override — ~3 dB would be right for CW."
Report a result without these and you are presenting our default as your user's decision. Each one names the field that would override it, so every assumption is also a question you can put back to your user.
Over MCP, every computing tool also returns the machine-readable form (issue 687):
assumed_inputs lists the argument names that fell back to a stock default, and
assumed_values gives the value each one stood for (a value the result already echoes,
such as thermal's ambient_c, is not repeated there). Example, a bare
run_stackup_solver call: target_thickness_mils, impedance_ohms, topology, … —
the 62 mil board and the 50 Ω microstrip were ours, not your user's. Say so.
warnings are findings about the design as built:
"BFP840ESD's 21.5 dB is the MIDPOINT of a 9.5-33.5 dB range the record states as one block, not a gain measured at this frequency. The endpoints differ by 24.0 dB."
That is not noise. It says the headline noise figure rests on a mid-range gain number, which is exactly the caveat a human reviewer would want and exactly the sentence a tidy summary deletes.
Measured on the 9.4 GHz transmit chain: 8 warnings with a 200, including
"Isolator/circulator JIM9250T9750VM1R is rated 40.0 dBm average and this design delivers 43.4 dBm average (CW). That is an overstress on the rating's own terms."
A design that ships with a stated overstress is not a broken call. It is a design with a decision in it. Hand the decision over; do not resolve it silently.
Quote them. Do not paraphrase them into reassurance.
3. The power verdict — which field, on which surface¶
This is the section that matters most, because getting it wrong means telling someone a chain met a target it missed.
Two surfaces carry different field names¶
HTTPS (POST /api/rf-chain/design-sync) |
the design tool (design_rf_chain) |
|
|---|---|---|
| verdict field | chain.meets_power_target |
powerCheck.meets_target |
meets_power_target present? |
yes | no — the key does not exist |
| shipped output | chain.as_built_output_power |
achievedOutputDbm |
| delta | chain.output_delta |
outputDeltaDb |
If you are calling with curl, you are on the first column. Read
chain.meets_power_target. Looking for powerCheck over HTTPS finds nothing;
looking for meets_power_target in a tool result finds nothing.
The verdict is reference-relative, and the two references differ¶
The same helper judges the same part against two different references, so the two
surfaces can return opposite verdicts for one design. Measured at 2.4 GHz,
30 dBm target, application: "wifi":
HTTPS chain.* |
tool powerCheck.* |
|
|---|---|---|
| selected PA | MGA-242740-02 |
MGA-242740-02 — same part |
| verdict | meets_power_target: false |
meets_target: true |
| judged against | 33.65 dBm — the output required at the PA, i.e. your target plus the losses after it | 30 dBm — your target at the antenna port |
| PA's usable linear output | 33.0 dBm (power_range_floor) |
33.0 dBm — same figure |
| result | 0.65 dB short of 33.65 | 3.0 dB of margin over 30 |
Both are true. false means this PA cannot deliver the target through the losses
that follow it. true means this PA is rated above the target itself. Neither
one is the other's error.
⚠ Never report that the two surfaces agree, and never carry a verdict from one
into a sentence about the other. The tool discloses its reference in its own
note field:
"MGA-242740-02: 33 dBm linear (power-range floor) output — meets the 30 dBm target with 3 dB device-capability margin (PA rated output vs target, before chain losses); saturated 40 dBm."
The bare HTTPS boolean does not. When you have only the boolean, say which question it answers, or say nothing about it.
The HTTPS response tells you the same story in warnings, and this is the
sentence to quote:
"PA power: MGA-242740-02 is 0.7 dB short of the 33.65 dBm required PA output (delivers 33 dBm linear output). The reported chain output is a gain-cascade (saturated/CW-basis) figure and does not reflect this shortfall; verify the PA's rated EVM/linear output or choose a higher-power PA."
The verdict is TRI-STATE, and null is the dangerous one¶
powerCheck.meets_target has three values, not two:
| Value | basis |
Means |
|---|---|---|
true |
e.g. power_range_floor, rated_linear |
Proven adequate on a figure that is on record |
false |
as above | Proven short |
null |
uncertain |
Unknown. Not "fine". |
null happens on a modulated application when the part's record carries no linear
or rated-average output figure at all — only saturated / P1dB. The engine will not
invent a back-off to fill the gap, so it returns the honest unknown. Measured
shape (ADPA9007, 30 dBm, application: "wifi"):
{
"mpn": "ADPA9007",
"target_output_dbm": 30.0,
"basis": "uncertain",
"usable_output_dbm": null,
"saturated_dbm": 34.0,
"p1db_dbm": 33.0,
"meets_target": null,
"note": "..."
}
…and the note spells the unknown out rather than papering over it:
"ADPA9007: saturated 34 dBm / P1dB 33 dBm; linear/modulated output for this application is not specified in catalog data — verify the datasheet's rated EVM output before relying on 30 dBm."
null is more dangerous than false, because a null reads as absence of a
problem. An agent that treats a missing verdict as a passing verdict reports "no
issues" where the honest answer is "unknown — verify the EVM rating". Say
"unknown", and pass the note through verbatim.
⚠ On the HTTPS surface the unknown is not visible. chain.meets_power_target
is false only on a proven shortfall; an unknown reads as true, and no
warning is emitted for it. So on HTTPS:
meets_power_target: false→ proven short. Lead with it.meets_power_target: true→ not proven short. That is not the same as proven adequate.meets_power_target: null→ no PA was selected at all, so there was no power judgement to make. Checkchain.selected_paand thewarningsbefore saying anything else about the design.
For a modulated application (wifi, lte, 5g — anything with a high
peak-to-average ratio), do not turn true into "the PA meets the target". Say the
chain is not flagged short, and ask for the PA's rated EVM/linear output. For a
constant-envelope application (radar, CW), the part can be driven to
compression and true carries its ordinary meaning.
4. As-built is not the target, and the gap has no fixed sign¶
Three output figures travel together and they are not interchangeable:
| Field | What it is |
|---|---|
chain.actual_output_power |
the convergence figure — what the design loop aimed at |
chain.as_built_output_power |
the shipped figure — after the discrete attenuator pads that actually exist |
chain.output_delta |
actual minus target — computed against the aim, not against as-built |
Measured, both directions, same route, same day:
| Design point | target | actual_output_power |
as_built_output_power |
output_delta |
as-built vs target |
|---|---|---|---|---|---|
| 2.6 GHz, lte | 30 | 31.0 | 30.65 | 1.0 | +0.65 |
| 9.4 GHz, radar | 40 | 41.0 | 41.15 | 1.0 | +1.15 |
| 2.4 GHz, wifi (ISM ceiling) | 30 | 30.0 | 30.0 | 0.0 | 0.0 |
Five things follow, and each one is a mistake agents actually make:
- Quote
as_built_output_power. It is what the board ships. The design tool headlines this same figure asachievedOutputDbm. output_deltais not the shipped margin. At 2.6 GHz the delta is1.0while as-built is only 0.65 dB over target — the delta is measured against the aim, and the pads then took 0.35 dB more. The chain says so in a warning of the form "Discrete attenuator pads total X dB vs Y dB computed — downstream stages are driven Z dB low."- The gap is not always a shortfall, and it is not always there. At 9.4 GHz as-built sits 0.15 dB above the design figure; at 2.6 GHz, 0.35 dB below. Never write a sentence implying the design always falls short of target, and never write one implying it always hits it exactly. Report the two numbers.
- At a conducted ceiling the engine makes up the pad itself — when it may. If the
design sits on an FCC limit and the input level is the engine's to set (no real VCO,
no stated input power), it raises that input by the pad shortfall so the board ships
the limit exactly — the 2.4 GHz row. It says so: "Input drive raised …". When a
stage would be pushed into compression it leaves the design as it was and says why:
"Input drive not raised …", beside the "driven … low" warning. (Measured
2026-09-26, change
two-way-source-trim.) - Know whose input level it is. Unless the user states the power their radio or signal
source delivers (
input_power_dbm), the engine assumes one, and the FIRST warning says so: "Input power ASSUMED at 0.15 dBm (not stated) …" — the level the design ships with. The source's BOM line then reads "assumed input power", never "user-supplied". A stated level is fixed: the engine designs around it and never moves it, so at a ceiling the pads' rounding can leave the board short, disclosed by the "driven … low" warning. Measured 2026-09-27 on the 2.4 GHz / 30 dBm drone (changemcp-input-power):
| Input | Driver, pad | Delivered | Total |
|---|---|---|---|
| assumed (0.15 dBm) | ADL5521, YAT-3A+ | 30.0 | $287.23 |
| stated 5 dBm | BT09E, MFA1007Q3A | 29.3 | $298.21 |
| stated 0 dBm | TQL9093, AC150-2R25 | 29.9 | $406.12 |
| stated −2 dBm | TQP3M9028, YAT-9A+ | 29.05 | $289.93 |
| stated −10 dBm | BGAP2S20A, YAT-7A+ | 29.85 | $282.94 |
An input_power_dbm equal to the target output, or one the user never said, is not used
and a warning says why.
The T/R module route reports the same idea under its own names —
transmit.targetOutputDbm 40.0 against transmit.asBuiltOutputDbm 40.65,
measured at 9.4 GHz.
5. A regulatory clamp is a compliance action, not a failure¶
When a design point sits in a band with a conducted-power ceiling, the engine holds the design at the limit rather than aiming past it, and says so. Measured at 2.4 GHz, 30 dBm:
"Output aim clamped to the FCC 15.247 conducted limit (30 dBm, 2400-2483.5 MHz ISM): the standard +1 dB convergence margin would exceed it"
That is the engine choosing to be legal. It is the reason the 2.4 GHz row above aims at 30.0 rather than 31.0, and it is why the 9.4 GHz radar row — outside any ISM band — keeps its +1 dB aim.
Do not report a clamp as an unmet target or a design defect. Report it as what it is: the target was held at the regulatory ceiling. If your user asked for more power than the band allows, that is the sentence they need, and the engine has already written it for you.
Over MCP: the same result, different places to look¶
If you reached these engines through the MagicON MCP server
(https://api.pcbgenerator.com/mcp/, see the MCP guide)
rather than with curl, everything above still holds — the engines are the
same — but the fields sit in different places, and extra keys ride on the result.
Where the fields are¶
| Tool | Where its fields sit | needsPartFlags |
|---|---|---|
design_rf_chain |
top level: achievedOutputDbm, powerCheck, warnings |
not returned — the same as the HTTPS route. Read warnings |
design_rf_receiver |
inside result: result.warnings, result.assumptions, result.cascade |
result.needsPartFlags |
design_tr_module |
inside result: result.transmit.asBuiltOutputDbm, result.budgets, result.warnings |
result.needsPartFlags |
Looking for needsPartFlags at the top level of a receiver or T/R result finds
nothing. That is not an empty list. Look one level down.
Two output figures on a transmit chain, and which to quote¶
design_rf_chain returns both output figures, under names that say which is
which:
| Field | What it is | Its HTTPS twin |
|---|---|---|
achievedOutputDbm |
as built — what the board ships. Quote this one | chain.as_built_output_power |
outputDeltaDb |
achievedOutputDbm minus the target |
— |
engineDesignOutputDbm |
the design loop's aim | chain.actual_output_power |
engineDesignDeltaDb |
the aim minus the target | chain.output_delta |
⚠ outputDeltaDb and output_delta are measured from different references: the
MCP one from what the board ships, the HTTPS one from the aim. They often agree.
Measured on production at 3.5 GHz / 33 dBm, both were 1.0 (achievedOutputDbm
34.0, engineDesignOutputDbm 34.0), and at 2.4 GHz / 30 dBm both were 0.0 —
but do not carry a delta from one surface into a sentence about the other
without checking which one it is.
grounding — whose numbers went in¶
Every result carries a grounding key:
grounding.mode |
Means |
|---|---|
user_stated |
You passed user_request, and the numbers you sent were checked against your user's own words |
caller_asserted |
You did not. The numbers you sent were taken on your word |
Either way, the figures the engines computed are unaffected. The label is
about the inputs. If a result says caller_asserted, the design point it
answered is yours, not your user's — say so, or go back and ask.
autoSolvedStackup — a board you did not ask for¶
A design_rf_chain call made before any stackup has been solved in the session
comes back with an autoSolvedStackup key. It means the server also solved a PCB
stackup, so that the PDN and compliance checks have a board to run on. Measured
on production at 2.4 GHz:
"autoSolvedStackup": {
"layerCount": 4,
"layerCountSource": "server default — the caller stated none",
"frequencyGhz": 2.4,
"boardLaminate": {"core": "MEGTRON6", "prepreg": "FR370HR"}
}
Its own reason field says what to do: "Tell the user this happened — they
did not ask for a stackup, and its layer count is a server default." Do.
It also names the board's laminate in boardLaminate. When the design ran before
the solve, the BOM's substrate line is the catalog laminate the loss budget used,
not the board's, and a laminateNote says so. Measured at 3.5 GHz / 33 dBm:
"boardLaminate": {"core": "MEGTRON6", "prepreg": "FR370HR"},
"laminateNote": "The BOM's substrate line is not the board's laminate — the
design ran before this solve. The board laminate is MEGTRON6 per the solved
stackup, but the chain's loss budget and thermal estimate were computed
against the catalog laminate RO4003C-50ohm-SL. The chain was not re-solved
against MEGTRON6."
When you list the parts, say which one the board is made of.
A refusal is a result, not a crash¶
Over MCP a refusal comes back as the tool result with the error flag set: a
short code under error and, where the tool explains itself, the message under
detail. Measured, a design_tr_module call with no arguments:
{"ok": false,
"error": "needs_frequency_or_front_end",
"detail": "design_tr_module needs either a frequencyGhz to design a module at,
or an explicit txPeakPowerDbm and isolationDb for a front end you
already have"}
Treat it exactly like a 422: the message names what is missing, and it is a
question for your user. An over-limit call is different: it returns HTTP 429
with a Retry-After header — wait, do not retry in a loop.
Before you send your summary¶
-
needsPartFlagsquoted where the route carries it —[], or the slot names as open items - Over MCP:
needsPartFlagsread fromresult.needsPartFlagson a receiver or T/R result, and acaller_assertedorautoSolvedStackuppassed on to your user - Over MCP: the draft passed to
check_grounding, and every figure it flagged either replaced with the engine's own value or marked as not tool-derived - Every
assumptions/disclosuresentry that shaped a headline figure passed through -
warningsreported as findings, not deleted or softened - The power verdict named with its reference, or not stated at all
- A
null/uncertainverdict called unknown, never fine - The output figure you quoted is the as-built one —
chain.as_built_output_power, ortransmit.asBuiltOutputDbmon a T/R module - A regulatory clamp described as compliance, not as a miss
- Every number in your summary came out of the response body — none of them came out of you
That last one is the whole point. The engines compute so that you do not have to, and the value of the result is that a human can trace every figure in it back to a published component spec. A figure you supplied looks exactly like one we computed once it is inside your paragraph.
Related¶
- Pick the engine — which endpoint answers which question.
- Design a module — the end-to-end procedure.
- API reference for LLMs — every path, limit and error code.
- RF chain design rules — the constraints the selection engine enforces.