Hello, agent

An Orchard agent is a single file. In this lesson you write the smallest useful one, run it offline, and learn exactly what a model call sees. By the end you will understand the four pieces every agent has: a pragma, an agent block, a model block, and a handler.

The smallest agent

Put this in a file called echo.orch:

#!orchard 3.0
agent Echo {
    model { provider: mock, name: "echo" }
    on message(text: str) -> str {
        return gen "Reply briefly and kindly to: {text}"
    }
}

Run it with a task:

orch run echo.orch -t "hi"

That is a complete program. It declares an agent, points it at a model, and handles an incoming message by making one model call and returning the result.

The file, line by line

The pragma

The first line, #!orchard 3.0, is the version pragma. It tells the tool which language version the file targets. It must be the first line. Files written for 2.0 are also accepted, since the 3.0 syntax is a superset, but new files should say 3.0.

The agent block

Everything else lives inside agent Echo { ... }. The name (Echo) is how the agent identifies itself; pick something descriptive. An agent block holds the model, optional memory, persona, policy, tools, skills, state, and the event handlers. You only write the parts you need. Here we need a model and one handler.

The model block

The model block selects the backend that answers gen calls. Offline, you use the mock provider:

model { provider: mock, name: "echo" }

The echo model replies without a network call, a key, or a cost, which is what makes every lesson here runnable. When you want a real model, you change two fields:

model { provider: anthropic, name: "claude-sonnet-4-6" }

The handler does not change. That is the point: you develop and test offline, then point the same agent at a live model. The block also accepts optional temperature, max_tokens, api_key, and a fallback chain, covered later.

The handler

on message(text: str) -> str is an event handler. It runs when the agent receives a message. The handler takes the incoming text as a typed parameter and declares it returns a str. Other handlers exist (on start, on schedule, on file); on message is the one you reach for first.

gen is one model call

gen "prompt" makes exactly one call to the model and returns its reply as a string. The prompt is an ordinary Orchard string, so it supports interpolation: {text} splices the value of the text parameter into the prompt.

This is the single most important thing to understand about gen: it is stateless. A bare gen call sees only the persona and system prompt. It does not see the conversation history, it does not have access to your tools, and it cannot call other functions. It is one prompt in, one string out. That makes it predictable and cheap. When you want history, tools, and a loop, you use delegate instead, which is a later lesson.

reply and return

A handler must hand back its answer explicitly. A bare trailing expression is not auto-returned at the handler level, so this would not work:

on message(text: str) -> str {
    gen "Reply to: {text}"     // computed, then discarded: handler returns nothing
}

Use return to give back the value and end the handler:

on message(text: str) -> str {
    return gen "Reply to: {text}"
}

reply x does the same thing: it ends the handler and sends x as the response. reply reads naturally in conversational handlers; return reads naturally where you think of the handler as a function. They are interchangeable for ending a handler. There is also emit x, which sends an intermediate message without ending the handler, useful for progress updates:

on message(text: str) -> str {
    emit "thinking…"
    return gen "Reply to: {text}"
}

A slightly larger one

Real agents usually want a persona, which is the system prompt the model sees on every gen. Here is a small assistant that sets its tone and instructions, then answers:

#!orchard 3.0
agent Ivy {
    model { provider: mock, name: "echo" }

    persona {
        tone: "friendly, concise"
        traits: ["helpful", "honest about uncertainty"]
        instructions: """
            Answer in two sentences or fewer. If you are unsure,
            say so rather than guessing.
        """
    }

    on message(text: str) -> str {
        return gen "Answer the user: {text}"
    }
}

The persona block becomes the system prompt. Every gen in this agent sees that tone and those instructions, so you write them once instead of repeating them in every prompt string.

One-shot tasks and chat

orch run echo.orch -t "hi" runs a single task and exits. That is the one-shot mode: one message in, one reply out. It is how you test an agent and how you script it from a shell. Run it without -t for an interactive chat session, where each line you type becomes a new on message turn and the agent keeps running until you exit:

orch run echo.orch

Both modes call the same on message handler. The only difference is whether you send one message or many.

Note. Keep orch check echo.orch in your loop. It catches a misspelled field, a wrong return type, or a handler that forgot to return before you ever run the agent.

Next. You have an agent that talks. Before adding typed generation and tools, take a tour of the language so the syntax in later lessons reads cleanly.