Docs/Guides/Build a hello-world plan
Plans6 min read

Build a hello-world plan

A plan is a Frags program written in FML — the Frags Modeling Language. A plan decomposes a task into one or more sessions, each an isolated LLM interaction with its own output schema, so every session returns typed, validated JSON instead of freeform text. This guide builds the smallest possible plan and grows it one concept at a time.

Golden rule: FML looks imperative, but it's a description language. There's no branching, no loops (except iterate), and no return statements — every construct is declarative. You describe the sessions and their shapes; the runtime schedules and executes them.

Prerequisites

A working local Frags runtime. If you haven't set one up yet, start with Running Frags Locally — you'll need the frags CLI and a configured AI engine.

Your first plan

The smallest plan is a single session with one prompt and an output schema. Save this as hello.fml:

FML
session("hello") {
    - Say hello to the world in one friendly sentence.

    schema {
        greeting: string
    }
}

Run it with the CLI:

Shell
frags run hello.fml

Frags executes the hello session and returns an object keyed by the session name, matching the schema:

JSON
{
  "hello": {
    "greeting": "Hello, world — it's a great day to build something."
  }
}

Three pieces are doing the work:

  • session("hello") { … } — a session is one isolated LLM task. Every plan has at least one, and its name becomes the key in the output.
  • - … — the prompt (the - line). It's the main instruction and produces the structured output. There's exactly one per session.
  • schema { greeting: string } — the output schema. Frags validates the model's response against it, so you always get a greeting string back.

Add a system prompt and a parameter

Real plans take input and set the assistant's persona. system(...) sets a global system prompt, and parameter(...) declares a typed input — the type is a bare keyword (no quotes), and a default makes it optional. Reference a parameter anywhere with Go template syntax, {{ .params.<name> }}:

FML
system("You are a friendly assistant who keeps things short.")

parameter("name", type=string, default="Rayna", title="Name to greet")

session("hello") {
    - Write a short, friendly one-sentence greeting to {{ .params.name }}.

    schema {
        greeting: string   # The greeting to return
    }
}

Because name has a default, frags run hello.fml runs as-is and greets "World". Inline schema comments like # The greeting to return aren't just for you — the LLM reads them as field descriptions, so add them whenever a field name isn't self-explanatory.

Prompt phases: prepare, then answer

A session can run in phases. A + line is a prePrompt: it runs first, enriches the context, and is the only place allowed to call tools (MCP servers, collections, search). The - line is the main prompt that turns everything gathered into the schema — prompts themselves never call tools.

FML
session("hello") {
    + Consider what makes a greeting feel warm and personal.
    - Using that, write a short, friendly greeting to {{ .params.name }}.

    schema {
        greeting: string
    }
}

You can have several + prePrompts in a session, but only one - prompt. So the pattern is always: gather with prePrompts, then produce the output with the single prompt.

Connect sessions together

Plans get powerful when sessions build on one another. Add after="<session>" so a session waits for another to finish, then pull the earlier session's output into a prompt with {{ .context.<session>.<field> }}:

FML
parameter("name", type=string, default="Rayna")
parameter("language", type=string, default="Italian")

session("greet") {
    - Write a one-sentence greeting to {{ .params.name }}.

    schema {
        greeting: string
    }
}

session("translate", after="greet") {
    - Translate this greeting into {{ .params.language }}: "{{ .context.greet.greeting }}"

    schema {
        greeting_fr: string
    }
}

Frags runs greet first, then translate, wiring the output of one session into the next. Each session contributes its key to the final object:

JSON
{
  "greet":     { "greeting": "Hello, World!" },
  "translate": { "greeting_fr": "Bonjour, World !" }
}

From here, a session can also depend on a condition (expect="..."), run once per item in a list (iterate="..."), or publish under a different key (target="...") — all declared as session arguments.

Next steps

← All guides

Write your first FML plan from scratch — a single session with a schema — then grow it with parameters, prompt phases, and connected sessions.