# PreCalls deep-dive

Most of the time you let the model decide when to call a tool, you write a prePrompt and it figures out the rest. But sometimes you know _exactly_ what needs to happen: fetch these records, run this transform, before any reasoning starts. That's a **PreCall** — a deterministic, explicit tool or code invocation that runs before prompts.

This guide covers all of it: where PreCalls run (global vs session), the two kinds (tool vs code), and where their output goes (routing to `vars` or `context`).

## Prerequisites

- The [Anatomy of a session](/content/docs/guides/anatomy-of-a-session/index.html) and [Data flow: params, vars & context](/content/docs/guides/data-flow-in-a-plan/index.html) guides.

## PreCall vs. letting the model call the tool

Both run tools, but they're different instruments:

|  | prePrompt (`+`) | PreCall (`call`) |
| --- | --- | --- |
| Who decides to call | The model | You |
| When | During reasoning | Before prompts run |
| Requires exact signature | No | Yes |
| Deterministic | No | Yes |

**Why reach for a deterministic call?** When you drive the call yourself, the same inputs always produce the same call — same function, same arguments, every run. The model never has to _decide_ whether to fetch, never forgets a parameter, and never rephrases the query. That makes the fetch cheaper (no reasoning tokens spent deciding to call it), faster, and testable, and it guarantees the data is in place before any prompt runs. Spend the model's judgment on the reasoning, not the plumbing.

> **Rule of thumb:** use a PreCall only when you know the exact function name and arguments. If you're unsure, write a prePrompt and let the model call the tool.

## Where PreCalls run

### Global PreCall — before the blueprint starts

Placed at the root of the file, a `call` runs **once, before any session**. This is the highest-leverage PreCall: route its result into a **var**, and because `vars` is a global namespace, every session in the blueprint can read it — fetched exactly once, deterministically, before any reasoning starts.

```python
require mcp slack

# Fetch once, at plan start, into a global var
call("search_messages") -> recentMessages {
    author = "{{ .params.name }}"
}
```

`vars.recentMessages` is now available to every session — and no session needs `after`, because the data was in place before any of them ran. Two independent sessions can both consume it (and run concurrently, since neither depends on the other):

```python
session("themes") {
    - Summarize the recurring themes across these messages:
      {{ json .vars.recentMessages }}
    schema { themes: string[] }
}

session("action_items") {
    - Extract any action items mentioned in these messages:
      {{ json .vars.recentMessages }}
    schema { items: string[] }
}
```

That's the payoff of a deterministic global fetch: **one guaranteed call feeds many sessions**, instead of each session independently — and non-deterministically — asking the model to go fetch the same data. Like any tool use, the PreCall's tool must be declared with `require` at the root.

### Session PreCall — before a session's prompts

Placed inside a session body, a `call` runs **before that session's prompts fire**. By default its result is injected into that session's LLM context:

```python
session("triage") {
    use mcp support_desk

call("sf_search_leads") {
        agent_name = "diego"
    }

- Group the leads returned above by priority.
    schema { groups: string[] }
}
```

## The two kinds of PreCall

### Tool PreCall

The first positional argument is the tool function name; the body is its named arguments:

```python
call("list_repositories") {
    owner = "{{ .params.org }}"
    limit = $(params.max_results)
}
```

Argument values take one of three forms — pick based on what the argument must _carry_:

| Form | Syntax | Produces | Use when |
| --- | --- | --- | --- |
| Go text template | "{{ .params.x }}" | A **string** | The arg wants text — a query, a name |
| Wrapped Expr | `$( expr )` | The **native type** (array, object, int, bool) | The arg wants a structured or numeric value from scope |
| Structured literal | `[{name: "foo"}]` | The literal, as-is | The arg is a hardcoded object/array |

The template-vs-`$()` choice matters: a template always yields a string, so passing "{{ .vars.items }}" to an argument that expects an array hands the tool a _string representation_, not the array. Use `$(vars.items)` for that. This distinction has its own guide — [Templates and expressions](/content/docs/guides/templates-vs-expressions/index.html).

### Code PreCall

Instead of a tool, a `call` can run pure JavaScript with a `code(...)` block, handy for reshaping arguments:

```python
call("do_something") {
    code(
        (args.items.map(it => it.name))
    )
    items = [
        { name: "foo" },
        { name: "bar" }
    ]
}
```

Two things to know about `code(...)`:

- It uses **completion-value notation — no `return`**. The value of the last expression is the result. Wrap a single expression in `( )`.
- For multiple statements, end with a semicolon (or wrap the whole thing in parentheses):

```python
code(
    (
        const names = args.items.map(i => i.name);
        names.join(', ');
    )
)
```

Inside `code`, `args` refers to the other arguments defined in the same `call` block.

## Routing the output

By default a PreCall's result lands in the containing session's LLM context. The `->` operator sends it somewhere you can address later instead:

```python
call("list_repositories") -> repos {          # implicit vars target
    owner = "{{ .params.org }}"
}

call("list_repositories") -> vars.repos {      # explicit — identical to above
    owner = "{{ .params.org }}"
}

call("list_repositories") -> context.repos {   # writes to context (experimental)
    owner = "{{ .params.org }}"
}
```

- `-> name` and `-> vars.name` are equivalent — both store the result in `vars.name`.
- `-> context.key` writes into the `context` object; treat it as experimental and use sparingly.

Routing to a var is what makes a fetch reusable across the blueprint — inject it into a prompt with `{{ json .vars.repos }}`, pass it to another tool with `$(vars.repos)`, or gate a session on it with `expect="vars.repos != null"`.

## Putting it together

Fetch deterministically into a var, then reason over it — the fetch is explicit, the reasoning is the model's job:

```python
require mcp github

session("summarize_repos") {
    use mcp github

# Deterministic fetch → vars.repos, reshaped with code
    call("list_repositories") -> repos {
        owner = "{{ .params.org }}"
        code(
            (args.owner.toLowerCase())
        )
    }

context "Repositories:\n{{ json .vars.repos }}"

- Group the repositories by primary language and note the most active ones.
    schema {
        byLanguage: string[]  # one line per language with a count
        mostActive: string[]  # names of the most active repos
    }
}
```

## Common mistakes

| Mistake | Fix |
| --- | --- |
| Using a PreCall for a tool whose signature you don't know | Use a prePrompt (`+`) and let the model call it |
| Putting `return` in a `code(...)` block | Use completion-value notation: wrap in `()` or end with `;` |
| Passing an array via "{{ .context.items }}" | Use `$(context.items)` to preserve the type |
| Forgetting `require` / `use` for the PreCall's tool | Declare `require` at root and `use` in the session |

## Next steps

- Master the value-passing rules in [Templates and expressions](/content/docs/guides/templates-vs-expressions/index.html).
- See PreCalls alongside MCP servers and databases in [Call MCP servers and databases](/content/docs/guides/call-tools-from-a-plan/index.html).
- Full reference: [PreCalls](https://github.com/FragsHQ/frags/wiki) on the Frags wiki.

Run tools deterministically before prompts — global vs session PreCalls, tool vs code invocations, and routing output into vars or context.
