Build your first blueprint — tutorial
Audience: a tenant employee or external Claude session that wants to ship a custom recipe in 30 minutes. Prerequisites: an Amdahl API key with
artifact:writescope; the Amdahl MCP server connected to your client. End state: a working blueprint forked fromcontent-calendar, customized to your team's pillars and pacing, ready to read and walk from MCP.
Blueprints are recipes an LLM reads and walks itself — on the MCP tool surface (this tutorial's audience) there is no one-shot run tool, so you walk the recipe yourself. A headless server-side SDK runner also exists for unattended/scheduled/backtest runs (see blueprint-runner-sdk.md), and you can register such a run from MCP via blueprints.create_schedule; but you cannot one-shot-run a blueprint from MCP. They're written in a typed DSL (reference) and stored as agent_blueprint artifacts. The platform ships several forkable starters; this tutorial walks through forking the content-calendar starter, customizing it for your team, and walking the customized recipe yourself.
Step 1 — Look at the starter
The Amdahl-shipped starters are public. Read the calendar starter to see what you're starting from:
read_resource artifact://<content-calendar-blueprint-id>If you don't know the id off-hand, list public agent_blueprint artifacts:
read_resource agent_blueprint://list?slug=content-calendarYou'll see the validated DSL. Pay attention to:
identity.slug+version— you'll bump these when you fork.inputs— the surface customers fill in to run the blueprint.policy.tool_allowlist— the operations the blueprint'stoolsteps will call.steps— the recipe body. The steps run in order; the planning blueprint emits placeholdercontent_piecerows, thenplan_and_draft_window(the composition starter) fans outdraft_pieceper planned piece in parallel.
Step 2 — Decide what to change
Common forks for plan_and_draft_window:
- Lock the channel — your team only publishes to LinkedIn; remove the
channelinput + hardcode'linkedin'everywhere. - Pin the audience lens — your ICP is fixed; remove the input + hardcode the audience filter.
- Tighten the cadence — replace
prompt://content_writer/cadence_rhythmwith your own fragment if your audience has unusual posting times. - Pillar-locked — bake the team's pillar set into the blueprint and remove the optional input for fixed-pillar teams.
For this tutorial we'll do #1 — lock to LinkedIn.
Step 3 — Fork the starter
Forking is a single op: agents.fork_blueprint reads the starter, creates a tenant-owned copy, and returns the new artifact id. The fork starts at version 1.0.0, status draft, visibility private — you customize from there.
agents.fork_blueprint
source: 'content-calendar' # starter slug, OR a starter UUID, OR a tenant blueprint UUID
new_slug: 'team-x-linkedin-calendar' # optional; auto-generates `<source>-fork` when omitted
new_name: 'Team X LinkedIn Calendar' # optional; defaults to `Copy of <source name>`Returns:
{
"artifact_id": "<new-uuid>",
"slug": "team-x-linkedin-calendar",
"name": "Team X LinkedIn Calendar",
"source_kind": "starter",
}Once the fork lands, customize it via artifacts.update with the patches you want — drop the channel input, hardcode 'linkedin' in the steps that referenced $inputs.channel, etc. The fork's content_json is a full copy of the starter; you patch only the fields you care about.
artifacts.update
id: '<new-uuid>'
content_json:
inputs:
# Drop the `channel` input entirely; keep the rest.
- { name: 'voice_profile_id', type: 'artifact_ref', required: true, validate: { artifact_type: 'voice_profile' } }
- { name: 'period_start', type: 'date', required: true }
- { name: 'period_days', type: 'integer', default: 7, validate: { min: 3, max: 14 } }
- { name: 'piece_count', type: 'integer', default: 5 }
- { name: 'audience', type: 'string' }
- { name: 'pillar_ids', type: 'artifact_ref_list', validate: { artifact_type: 'content_pillar' } }
steps:
# Replace `$inputs.channel` with the literal `'linkedin'`
# everywhere the starter referenced it.
# ... (omitted for brevity; copy from the starter and edit)The fork now reflects your customization. Promote when ready via another artifacts.update with status: 'published'.
Step 4 — Walk your fork
There is no one-shot run tool on the MCP surface. A blueprint is a recipe you read and walk yourself. Read the recipe back:
read_resource agent_blueprint://<your-fork-id>You get the inputs, the policy, and the step tree. Gather the inputs you'd supply:
voice_profile_id: '<your voice profile uuid>'
period_start: '2026-04-29'
period_days: 7
piece_count: 5
audience: 'B2B operators at Series B+ companies'
pillar_ids: ['<pillar-1>', '<pillar-2>', '<pillar-3>']Then perform each step in order with your own primitive tools — substituting $inputs.* with the values above and each $step_id with the output of the step you already ran. A tool step names the operation to call (data.cluster_search, artifacts.create, etc.); an llm step is reasoning you do yourself; loop / branch / parallel steps tell you how to fan out. You ARE the runner on this surface: nothing on the MCP tool surface one-shot-executes the recipe for you. (Platform-initiated runs — manual "Run now" via REST, schedule/event/webhook triggers, replay, backtest — go through a separate headless SDK runner, not your MCP session; you can register a scheduled run from MCP with blueprints.create_schedule, but it still executes on that runner, not in your session.)
Step 5 — You're done when the steps are done
There's no run row to close — when you've walked every step and written the artifacts the recipe produces (the calendar, the pieces, the drafts), the work is complete. Capture the artifact ids you created as you go; the recipe's outputs_mapping tells you which step output maps to which named output.
Step 6 — Iterate
Once you've walked it once and the output matches your taste, you're done. Common iteration patterns:
- Bump version to 1.1.0 when you tweak the prompt language or add a step. The version is authoring metadata on
identity.version; the next agent that reads the recipe picks up the new body. - Re-read the recipe (
read_resource agent_blueprint://<your-fork-id>) any time to confirm a change landed before walking it again. - Note on unattended use — the
trigger.schedule/trigger.event/trigger.webhookfields declare intent in the DSL. A headless SDK runner now fires these for platform-initiated runs (see blueprint-runner-sdk.md). From your MCP session there is still no one-shot run tool, but you CAN register an unattended run withblueprints.create_schedule(which executes on that runner); for ad-hoc recurring use you can also just walk the recipe yourself (or wrap it in your own scheduled job that does).
What this recipe does and doesn't constrain
The recipe's policy.tool_allowlist is GUIDANCE you should respect, not a server-enforced contract. The API key's scope grammar is the real safety boundary. You may adapt mid-walk when an unanticipated case calls for a different tool.
This means: if a blueprint declares a tight allowlist, follow it; if the API key has broader scope, you have the headroom to adapt. The blueprint declares intent; the API key enforces it. The headless SDK runner does not enforce the allowlist for you either — it runs against a least-privilege scoped token, so the API key's scope grammar, not the allowlist, is the real boundary on every surface.
What's next
- DSL reference — every field, every type, every kind.
- bootstrap_workspace reference, draft_piece reference, plan_and_draft_window reference, research_report reference — per-starter usage docs.