Generate a single self-contained HTML file ($RUN_DIR/recommendation-report.html) that
presents the agent architecture recommendation in v3 document style — a consulting-grade
report that mirrors the structure of recommendation.md. The file uses inline CSS and a
CDN-loaded Mermaid.js for the diagram — no other external dependencies. Users can open it in
any browser and use “Print to PDF” if needed.
Before writing the HTML: load the shared shell (references/report-shell.md) — inline
its CSS block at the {{ SHARED_SHELL_CSS ... }} marker and its SRI-pinned mermaid@10.9.3
script tag at the {{ SHARED_SHELL_MERMAID_TAG ... }} marker in <head>. The remaining
rules in the <style> block below are this report’s OWN content CSS. The v3 shell defines
.help-strip; render the help CTA using that component (text + inline button), not the
3-card form from report-help-banner.md — but ONLY when report-help-banner.md‘s
banner_status reads LIVE. It currently reads SUPPRESSED (support page not launched), so
render NO help CTA at all (skip the .help-strip block in the body below). When it flips to
LIVE, substitute {{ HELP_URL }} with the single-source destination URL from
references/report-help-banner.md — never hardcode it here.
Non-blocking: if HTML generation fails for any reason, log a warning, do NOT fail the
Generate phase, and continue. The recommendation.md is the authoritative document.
from estimate.md (Polling tier, Execution tier, Temporal Cloud actions, What it replaces)
[]
TEMPORAL_COST
the system-level orchestration cost band for the §-cost roll-up row: from estimate.json — the Temporal Cloud orchestration assumption band (actions/mo × $0.01) when TEMPORAL_WAY == "cloud"; else "see §5 commercials" (self_hosted has no dollar line)
“see §5 commercials”
TEMPORAL_COST_SUMMARY
the label cell paired with TEMPORAL_COST in the cost-summary table: e.g. “Temporal Cloud orchestration (system-level: all Activities)” when Way = cloud; “Temporal orchestration (self-hosted — no AWS charge)” otherwise
“Temporal orchestration”
COST_DOMINANT_NOTE
one clause naming the dominant tier from the ACTUAL estimate.json bands, not a fixed assumption: usually “model tokens dominate most lines”, but “the polling fleet dominates” or “compute and model tokens are comparable” when the numbers say so
“model tokens dominate most lines”
TEMPORAL_RUNBOOK_STEPS
The selected cutover runbook steps from recommendation.md §3c
[]
SERVERLESS_WORKERS_IN_TIER1
true if any task queue’s Tier 1 choice is serverless_workers
LAYERED, not top-level: answers.json.system.provenance for system dims + answers.json.units[<id>].provenance for per-unit dims (each maps dim → “detected”/”asked”/”inherited”/”adapter”/”interview”). For a given (dim, scope) look up the matching layer
{}
ANSWERS
answers.json.answers (the primary unit’s fully-merged dims — legacy mirror only)
{}
ANSWER_LAYERS
the LAYERED answer document from the full answers.json: .system (system dims + provenance) and .units (map of unit_id → per-unit dims + provenance). This is the source the §2 Assessment-inputs table enumerates — NOT ANSWERS, which is only the primary merge
{system:{}, units:{}}
SCORING_RESULT
scoring-result.json (units{} keyed by unit ID, each with scores{}, eliminations, winner)
null
ESTIMATE
estimate.json (units{} with per-unit breakdown{compute, model_tokens, other} + monthly_magnitude_usd; total_monthly_magnitude_usd; total_compute/total_model/total_other; drivers[] with {unit, driver, effect, lever}; assumptions[])
Extract the fenced ```mermaid block from diagram.md
null
DIAGRAM_ASCII
Extract the ASCII block inside <details> from diagram.md
null
RUN_DATE
From design.json or current date
“2026”
RUN_ID
From design.json or run directory name
“draft”
ENTRY_POINT
answers.json.entry_point OR context-signals.json.units[].trigger (for the primary unit)
“build”
REGION
answers.json.answers.region
“us-east-1”
RECOMMENDATION_MD_PATH
$RUN_DIR/recommendation.md
—
SCAFFOLD_EXISTS
true if $RUN_DIR/scaffold/ directory exists and is non-empty
false
SYSTEM_NAME
answers.json.system.name or the repo/app name, if known
“Agent Platform”
PRIMARY_UNIT
answers.json.primary_unit (the unit id chosen as primary in Clarify) — used to tag the “· primary unit” label in §3
null
unit.runner_up_runtime
NOT a global scalar — derived INSIDE the §3 per-unit loop for each agent_session unit: the highest-scoring runtime in THAT unit’s SCORING_RESULT.units[unit.id].scores EXCLUDING unit.verdict (filter out the winner first, then take the top of the rest — never just “2nd sorted”, else a co_recommend winner picked from a tie could show as its own runner-up). Each unit has its own; never shared
null (omit the Runner-up row)
unit.runner_up_score
that runner-up runtime’s score from the SAME SCORING_RESULT.units[unit.id].scores map
The v3 report mirrors the reference HTML structure (report-v3-reference-multi-agent.html)
exactly:
doc-head (kicker/title/meta: run ID, date, entry point, region, status)
.help-strip (inline CTA: text + button)
§1 Summary (lede paragraph + workloads table: Workload/What it is/Recommended
target/Basis/Est. monthly; §1.1 Platform decision ONLY when PLATFORM_DECISION.offer ≠ null)
§2 Assessment inputs (Dimension/Value/Scope/Source — Source from PROVENANCE)
§3 Workload recommendations (one .unit-sec per unit: scored form = score bars
titled “Runtime comparison for this agent” + eliminated note + “Why X” bullets + item table
[model/services/runner-up/key change]; rule-based form = decision table with rule-cite
considered-and-rejected; each unit with a non-null model_recommendation additionally
gets the “Why this model” card — rationale visible, full model/migration detail in a
collapsed <details>, all values from unit.model_recommendation)
§4 Target architecture (figure w/ per-unit entry points from trigger + figcap; 4.1
component detail table [Entry point from trigger / Compute / Model access / Supporting
services]; 4.2 Security & networking table [from the runtime cards’ Serving & security
notes]; 4.3 Scaling & limits [volatile_facts + service cards, “verify current”];
pipelines note)
[§5 Temporal migration] — CONDITIONAL (only when TEMPORAL_UNITS_PRESENT): scope
callout, layer table w/ Public Preview flag, runbook w/ preconditions callout + ordered steps,
Bedrock follow-up note. Per the temporal reference (report-v3-reference-temporal.html).
§Cost (5.1 breakdown table from estimate.json.units[].breakdown; 5.2 assumptions
list; 5.3 “What moves the number” from drivers[]) — section number is 5 if temporal
absent, 6 if present.
§Next steps (ordered, per-unit poc paths) — section number is 6 or 7.
§Generated artifacts (table w/ relative dl links) — section number is 7 or 8.
doc-foot (freshness statement from volatile_facts; “draft for review”)
Dynamic numbering: when the Temporal section is absent, cost is §5, next steps §6,
artifacts §7. When the Temporal section is present, cost is §6, next steps §7, artifacts §8.
Single-unit collapse: when UNITS.length === 1, the report renders exactly one §3
subsection (the single unit’s card), NO §1.1 (platform decision is N/A), and the summary
table still lists the one unit. Everything else (§2, §4, cost, next steps, artifacts)
renders identically.
Write $RUN_DIR/recommendation-report.html with the following structure. Every {{ }} is a
substitution from Step R0/R1. Do not output placeholder text — if a value is absent, hide
that element entirely (use display:none or omit the HTML block).
html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AWS Migration Recommendation</title><!-- SRI-pinned mermaid@10.9.3 script tag — inline it VERBATIM from the shared shell (references/report-shell.md), same tag/integrity hash used by every report with a diagram. -->{{ SHARED_SHELL_MERMAID_TAG from references/report-shell.md }}
CSS/HTML cross-check (do this before writing the file): every class used in the
template HTML must be defined — either by the shell block or by the content CSS above —
and every content-CSS rule must have HTML that uses it. No orphans in either direction.
Postcondition: The “Workload recommendations” section (§3) contains one .unit-sec card
per unit. When UNITS.length === 1, §1.1 is omitted and the summary table lists the single
unit. When TEMPORAL_UNITS_PRESENT === false, the Temporal section is omitted and sections
renumber accordingly (cost is §5, not §6).
<thead><tr><th>{{ IF UNITS.length > 1 }}Agent / Workload{{ ELSE }}Workload{{ END IF }}</th><th>What it is</th><th>Recommended target</th><th>Basis</th><th>Est. monthly</th></tr></thead>
<tbody>
{{ FOR EACH (unit, index) IN UNITS }}
<tr><td class="em">{{ unit.id }}</td><td>{{ unit.description from context-signals or answers }}</td>
<td class="target">{{ unit.effective_runtime }}{{ IF unit.effective_runtime !== unit.verdict }} <span class="muted">(consolidated; best-fit alone: {{ unit.verdict }})</span>{{ END IF }}</td><td>{{ IF unit.workload_class === "agent_session" }}scored {{ SCORING_RESULT.units[unit.id].scores[unit.verdict] }} — §3.{{ index }}{{ ELSE }}{{ unit.rationale }} — §3.{{ index }}{{ END IF }}</td><td>{{ unit.monthly_magnitude_usd or "—" }}</td></tr>
<td class="target">{{ TEMPORAL_WAY === "cloud" ? "Temporal Cloud" : (TEMPORAL_WAY === "self_hosted" ? "Self-hosted (no change)" : "no change") }}</td><td>{{ TEMPORAL_WAY === "cloud" ? "user decision — §5" : "§5" }}</td><td>{{ TEMPORAL_COST or "see §5 commercials" }}</td></tr>
{{ END IF }}
</tbody>
</table>
<p class="note">{{ IF UNITS.length > 1 }}Platform decision: {{ PLATFORM.mode }}{{ IF PLATFORM.mode === "split" }} — each workload on its optimal runtime{{ END IF }}. {{ END IF }}Total estimate {{ ESTIMATE.total_monthly_magnitude_usd or "TBD" }}/month, order-of-magnitude.</p>
{{ IF PLATFORM_DECISION.offer !== null }}
<h3>1.1 Platform decision</h3>
<div class="callout"><b>{{ PLATFORM_DECISION.mode === "split" ? "Split confirmed" : "Consolidated" }}.</b> {{ PLATFORM_DECISION.mode === "consolidated" ? "Consolidated onto " + PLATFORM_DECISION.offer.superset + " — trade-offs: " + JOIN(PLATFORM_DECISION.offer.sacrifices, "; ") : "Each workload runs on its own optimal runtime; the consolidation offer (" + PLATFORM_DECISION.offer.superset + ") was declined to avoid: " + JOIN(PLATFORM_DECISION.offer.sacrifices, "; ") }}</div>
<!-- Enumerate the actual answer dimensions from ANSWER_LAYERS (the layered answers.json): the
system dims (ANSWER_LAYERS.system) with scope "system", then each unit's dims
(ANSWER_LAYERS.units[id]), EXCLUDING the "provenance" key in each layer (it's metadata,
not a dimension). Source comes from the matching provenance layer. NOTE: use
ANSWER_LAYERS, not ANSWERS — ANSWERS is only the primary unit's merged dims. -->
{{ FOR EACH (dim, value) IN ANSWER_LAYERS.system EXCEPT "provenance" }}
<tr><td>{{ dim }}</td><td>{{ value }}</td><td>system</td><td>{{ ANSWER_LAYERS.system.provenance[dim] or "detected" }}</td></tr>
{{ END FOR }}
{{ FOR EACH unit_id IN ANSWER_LAYERS.units }}
{{ FOR EACH (dim, value) IN ANSWER_LAYERS.units[unit_id] EXCEPT "provenance", "workload_class" }}
<tr><td>{{ dim }}</td><td>{{ value }}</td><td>{{ unit_id }}</td><td>{{ ANSWER_LAYERS.units[unit_id].provenance[dim] or "detected" }}</td></tr>
{{ END FOR }}
{{ END FOR }}
</tbody>
</table>
<!-- ═══ 3. WORKLOAD RECOMMENDATIONS ═══ -->
<h2><span class="no">3.</span>{{ IF UNITS.length > 1 }}Workload recommendations{{ ELSE }}Recommendation{{ END IF }}</h2>
{{ FOR EACH (unit, index) IN UNITS }}
<div class="unit-sec">
<div class="unit-sec-head">
<div><span class="us-name">{{ IF UNITS.length > 1 }}3.{{ index }} {{ END IF }}{{ unit.id }}</span><span class="us-kind">{{ unit.workload_class }} · {{ IF unit.workload_class === "agent_session" }}scored{{ ELSE }}rule-based{{ END IF }}{{ IF unit.id === PRIMARY_UNIT }} · primary unit{{ END IF }}</span></div>
<div class="us-target">→ {{ unit.verdict }}{{ IF unit.deployment_model }} ({{ unit.deployment_model }}){{ END IF }}</div>
</div>
<div class="unit-sec-body">
<p>{{ unit.description from context-signals.evidence or answers }}{{ IF unit.workload_class === "agent_session" }}. Runtime comparison for this agent:{{ END IF }}</p>
{{ IF unit.workload_class === "agent_session" }}
<!-- SCORED FORM — `pct` is NOT in scoring.json (scores is a plain {runtime: score} map).
Derive it here as the bar width, normalized to the unit's own top score:
pct = ROUND(100 × score / MAX(SCORING_RESULT.units[unit.id].scores.values)), so the
winner's bar is full-width and the rest scale relative to it. -->
<div class="scores">
{{ FOR EACH (runtime, score) IN SCORING_RESULT.units[unit.id].scores SORTED DESC }}
{{ LET pct = ROUND(100 * score / MAX(SCORING_RESULT.units[unit.id].scores.values)) }}
<div class="score-row">
<div class="score-name {{ IF runtime === unit.verdict }}winner{{ END IF }}">
{{ RUNTIME_DISPLAY_NAME(runtime) }}
</div>
<div class="bar-track">
<div class="bar-fill {{ IF runtime === unit.verdict }}winner{{ END IF }}"
style="width:{{ pct }}%"></div>
</div>
<div class="score-val {{ IF runtime === unit.verdict }}winner{{ END IF }}">{{ score }}</div>
</div>
{{ END FOR }}
</div>
{{ IF NOT EMPTY(SCORING_RESULT.units[unit.id].eliminated) }}
<p class="elim-label">{{ FOR EACH (runtime, reason) IN SCORING_RESULT.units[unit.id].eliminated }}{{ runtime }} was eliminated before scoring: {{ reason }}.{{ END FOR }}</p>
{{ END IF }}
<h3>Why {{ unit.verdict }}</h3>
<ul class="plain">
{{ TOP_3_WHY_BULLETS from recommendation.md §3.{index} "wins because" }}
<tr><td>Account verification</td><td>{{ unit.model_recommendation.live_verification.status OR "not_run" }}</td><td>{{ IF unit.model_recommendation.live_verification.status === "passed" }}Invocation ID verified{{ ELSE }}Runnable access not yet established{{ END IF }}</td></tr>
<tr><td>AgentCore services</td><td>{{ COMMA_JOIN(unit.agentcore_services) }}</td><td>{{ service notes }}</td></tr>
{{ END IF }}
{{ IF unit.runner_up_runtime }}<tr><td>Runner-up</td><td>{{ unit.runner_up_runtime }} ({{ unit.runner_up_score }})</td><td>{{ runner-up reason }}</td></tr>{{ END IF }}
<strong>Compatibility:</strong> native {{ COMMA_JOIN(mr.compatibility.native, each wrapped in <code>) OR "—" }} · portable {{ COMMA_JOIN(mr.compatibility.portable, each wrapped in <code>) OR "—" }} · rearchitecture {{ COMMA_JOIN(mr.compatibility.rearchitecture, each wrapped in <code>) OR "—" }}</p>
<tr><td>Polling tier</td><td>{{ TEMPORAL_POLLING_DECISION }}{{ IF SERVERLESS_WORKERS_IN_TIER1 }} <span class="pre-flag">Public Preview</span>{{ END IF }}</td>
<thead><tr><th>{{ IF UNITS.length > 1 }}Agent / Workload{{ ELSE }}Workload{{ END IF }}</th><th>Compute</th><th>Model tokens</th><th>Other</th><th>Subtotal /mo</th></tr></thead>
<p class="note">Not included: one-time engineering effort (policy: never presented as dollar
figures). For migration entry points, precise TCO comparison and current-spend delta are produced by the migration plugins (see the llm-to-bedrock or gcp-to-aws run for model-by-model
pricing deltas); this estimate shows target-state run cost only. {{ IF ESTIMATE === null }}Estimate may be absent if the phase failed, or for add_capabilities which bypasses Estimate.{{ END IF }}</p>
<tr><td><a class="dl-link" href="recommendation.md" download>recommendation.md</a></td><td>full document{{ IF UNITS.length > 1 }} incl. system topology{{ END IF }}{{ IF TEMPORAL_UNITS_PRESENT }} (§3b){{ END IF }}{{ IF TEMPORAL_UNITS_PRESENT }} and Temporal (§3c) sections{{ END IF }}</td></tr>
<tr><td><a class="dl-link" href="design.json" download>design.json</a></td><td>units[], platform decision{{ IF PLATFORM.mode === "split" }} (split){{ END IF }}, per-{{ UNITS.length > 1 ? "unit" : "workload" }} verdicts{{ IF TEMPORAL_UNITS_PRESENT }}; Temporal block (Way, per-queue rules){{ END IF }}</td></tr>
{{ VOLATILE_FACTS_TEXT from recommendation.md Section 12 freshness footer }}
· This report is a draft for review.{{ IF UNITS.length > 1 }} <b>Multi-unit system ({{ UNITS.length }} {{ PLATFORM.mode === "split" ? "independent" : "consolidated" }} workload{{ UNITS.length > 1 ? "s" : "" }}).</b>{{ END IF }}