Docs

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:write scope; the Amdahl MCP server connected to your client. End state: a working blueprint forked from content-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:

text
read_resource artifact://<content-calendar-blueprint-id>

If you don't know the id off-hand, list public agent_blueprint artifacts:

text
read_resource agent_blueprint://list?slug=content-calendar

You'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's tool steps will call.
  • steps — the recipe body. The steps run in order; the planning blueprint emits placeholder content_piece rows, then plan_and_draft_window (the composition starter) fans out draft_piece per planned piece in parallel.

Step 2 — Decide what to change

Common forks for plan_and_draft_window:

  1. Lock the channel — your team only publishes to LinkedIn; remove the channel input + hardcode 'linkedin' everywhere.
  2. Pin the audience lens — your ICP is fixed; remove the input + hardcode the audience filter.
  3. Tighten the cadence — replace prompt://content_writer/cadence_rhythm with your own fragment if your audience has unusual posting times.
  4. 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.

text
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:

jsonc
{
  "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.

text
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:

text
read_resource agent_blueprint://<your-fork-id>

You get the inputs, the policy, and the step tree. Gather the inputs you'd supply:

text
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.webhook fields 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 with blueprints.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