# One-shot interaction files

A one-shot interaction: one agent, one task, one answer.

What one-shot interactions are for, and how to use them: [Interactions](https://docs.maitento.com/concepts/interactions.md).

## Where the file goes

`interactions/one-shot/`, or any sub-folder of it. The file name without `.mtr` is the one-shot interaction's name. Other files refer to it as `@ref("<name>")`.

A resource's name is its file name without `.mtr` (an app's is its folder's name). Use letters, digits, `_`, `-` and `.` (but not `..`), at most 128 characters. Two files of the same kind may not have the same name, even in different sub-folders or with different capitals.

## Properties

A file must set `agent` and `task`. Everything else is optional.

### `agent`

**Required.** [Block](#the-agent-block).

The seat: the name the agent goes by here, and which agent fills it.

### `task`

**Required.** Text.

What the agents are asked to do. The run's inputs are filled in where it says `%%name%%`, and `{{#if name}}...{{/if}}` keeps text only when an input has a value (`{{#unless name}}` for the opposite). Use `@prompt("name")` for anything longer than a sentence.

```hcl
task = "Hello, how are you?"
```

### `functions.openApi`

Optional. [List of blocks](#each-item-of-functionsopenapi).

Tools available to every agent in the interaction, as well as the tools each agent has itself.

### `inputs.prompts`

Optional. [List of blocks](#each-item-of-inputsprompts).

The inputs a run is started with, which the `task` uses as `%%name%%`.

### `inputs.functions.binding`

Optional. [List of blocks](#each-item-of-inputsfunctionsbinding).

Run inputs whose values go straight into a tool call, such as a customer id: the model never sees or chooses them.

### `inputs.functions.auth`

Optional. [List of blocks](#each-item-of-inputsfunctionsauth).

Run inputs that choose the secret a tool sends with its calls.

### `schema.solution`

Optional. JSON text.

The JSON Schema the answer must match, usually `@json("name")`. The run's output is then JSON of that shape.

```hcl
schema.solution = @json("recipe")
```

### `prompts.system.mode`

Optional. One of the values below. Default: `"AppendToDefault"`.

How `prompts.system` combines with the instructions Maitento gives every agent.

| Value | Meaning |
|---|---|
| `AppendToDefault` | They are added after Maitento's instructions. |
| `OverwriteAll` | They replace everything: the agent's prompt, Maitento's instructions and the list of tools. |
| `OverwriteNonCritical` | They replace Maitento's general guidance, keeping the agent's prompt, safety rules and tools. |

```hcl
prompts.system.mode = "OverwriteAll"
```

### `prompts.system`

Optional. List of text.

Extra system instructions for every agent in the interaction.

```hcl
prompts.system = [ "Write in British English." ]
```

### `duration.warn`

Optional. Duration, `"hh:mm:ss"`.

When the agents are told that time is running short, as `"hh:mm:ss"` from the start of the run.

```hcl
duration.warn = "00:02:00"
```

### `duration.max`

Optional. Duration, `"hh:mm:ss"`.

The longest a run may take, as `"hh:mm:ss"`. The run stops with an error when it is reached. Time spent waiting for a person does not count. No limit when not set.

```hcl
duration.max = "00:04:00"
```

### `context`

Optional. [Block](#the-context-block).

How the conversation is kept within the model's context window, and how many rounds of tool calls an agent may make in one turn. The defaults suit almost every interaction.

### `loom`

Optional. [Block](#the-loom-block).

Long-term memory for the interaction's agents. No memory when not set.

### `credentialStack`

Optional. Text.

The credential stack this interaction's runs use, by name, when their start doesn't name one: its credentials serve every model call, and Maitento's own keys never do. Leave it out to use the namespace's default stack, then your tenant's.

```hcl
credentialStack = "..."
```

### `swarm`

Optional. [Block](#the-swarm-block).

Other interactions the agents may start as sub-runs, and how many. None when not set.

## The `agent` block

A seat in an interaction: the name a participant goes by, and the agent that fills it. `{ name = "Greeter" agent = @ref("greeter") }`.

### `agent` › `name`

**Required.** Text.

The name the participant goes by in this interaction. The other participants, and the transcript, use it, so give each seat its own.

```hcl
name = "Greeter"
```

### `agent` › `agent`

**Required.** Reference to an agent.

The agent that fills the seat.

```hcl
agent = @ref("greeter")
```

### `agent` › `loom`

Optional. Reference to a loom.

A named loom the participant always remembers with, instead of the one the interaction's `loom` settings would give it. A loom chosen when the run starts still takes precedence.

```hcl
loom = @ref("family")
```

### `agent` › `webAccess`

Optional. [Block](#the-agent--webaccess-block).

The participant's built-in web tools, overriding what its agent allows: `webAccess = { search = true fetch = false }`. Leave either out to keep the agent's own setting. Where this block and `webAccess.search` / `webAccess.fetch` are both given, the block wins.

### `agent` › `webAccess.search`

Optional. `true` or `false`.

Gives or takes away the built-in web search tool for this participant, overriding its agent.

```hcl
webAccess.search = true
```

### `agent` › `webAccess.fetch`

Optional. `true` or `false`.

Gives or takes away the built-in web fetch tools for this participant, overriding its agent.

```hcl
webAccess.fetch = true
```

### `agent` › `functions.openApi`

Optional. [List of blocks](#each-item-of-functionsopenapi).

Tools for this participant only, as well as the interaction's and its agent's own.

## Each item of `functions.openApi`

A tool made available in one place (to a whole interaction, to one participant, or to an app), with any values and credentials that apply there: `{ ref = @ref("search-handbook") }`. What is set here overrides the tool's own settings.

The same block is used for `agent › functions.openApi`.

### `functions.openApi` › `ref`

**Required.** Reference to a tool.

The tool.

```hcl
ref = @ref("search-handbook")
```

### `functions.openApi` › `bindings.path`

Optional. Map of text, one line per entry: `bindings.path:<key> = "value"`.

Fixed values for the connector's path parameters, one line each: `bindings.path:orderId = "A-1001"`.

```hcl
bindings.path:orderId = "A-1001"
```

### `functions.openApi` › `bindings.query`

Optional. Map of text, one line per entry: `bindings.query:<key> = "value"`.

Fixed values for the connector's query parameters, one line each: `bindings.query:units = "metric"`.

```hcl
bindings.query:units = "metric"
```

### `functions.openApi` › `name`

Optional. Text.

The name the model calls the tool by here. The tool's own name when not set.

```hcl
name = "search-handbook"
```

### `functions.openApi` › `bindings.request`

Optional. Map of text, one line per entry: `bindings.request:<key> = "value"`.

Fixed values for fields of the request body, which the model never sees or chooses, one line each: `bindings.request:field = "value"`. Separate the names of a nested field with `/` (`bindings.request:customer/id`). A value is read as the type the request schema gives the field.

```hcl
bindings.request:customer/id = "C-42"
```

### `functions.openApi` › `auth`

Optional. [Block](#the-functionsopenapi--auth-block).

A credential to send with every call made here, instead of the tool's own.

### `functions.openApi` › `guidance`

Optional. Text.

When and how to use the tool here, instead of the tool's own `guidance`. The model reads it.

```hcl
guidance = "Use it when the customer asks about an order."
```

### `functions.openApi` › `terminal`

Optional. `true` or `false`. Default: `false`.

A successful call ends the agent's turn: the result is recorded, and the model is not asked to continue. Use it for a "finish" tool that is meant to be the last thing an agent does in a turn.

```hcl
terminal = true
```

## Each item of `inputs.prompts`

An input a run is started with, used in the `task` as `%%name%%`: `{ name = "topic" description = "What the haiku is about" required = true }`.

### `inputs.prompts` › `name`

**Required.** Text.

The input's name: a run gives the input's value under this name.

```hcl
name = "topic"
```

### `inputs.prompts` › `type`

Optional. One of the values below. Default: `"String"`.

The type of value the input takes.

| Value | Meaning |
|---|---|
| `String` | Text. |
| `Integer` | A whole number. |
| `Decimal` | A number that may have a fractional part. |
| `Boolean` | true or false. |

```hcl
type = "Integer"
```

### `inputs.prompts` › `description`

Optional. Text.

What the input is for, shown to whoever starts a run.

```hcl
description = "What the haiku is about"
```

### `inputs.prompts` › `required`

Optional. `true` or `false`. Default: `true`.

Whether a run must give the input. A run without a required input is refused, unless the input has a default, which the run then uses.

```hcl
required = true
```

## Each item of `inputs.functions.binding`

A run input whose value is written straight into a tool's calls, so the model never sees or chooses it: `{ function = @ref("search-handbook") name = "partitionId" target = "partitionId" type = "request" }`.

### `inputs.functions.binding` › `function`

**Required.** Reference to a tool.

The tool whose calls get the value.

```hcl
function = @ref("current-weather")
```

### `inputs.functions.binding` › `target`

**Required.** Text.

Where the value goes: the name of the request body field (separate the names of a nested field with `/`), or of the path or query parameter, as `type` says.

```hcl
target = "temperature_unit"
```

### `inputs.functions.binding` › `name`

**Required.** Text.

The input's name: a run gives the input's value under this name.

```hcl
name = "temperatureUnit"
```

### `inputs.functions.binding` › `agent`

Optional. Reference to an agent.

Only the participant filled by this agent gets the value. Every participant when not set.

```hcl
agent = @ref("weather-bot")
```

### `inputs.functions.binding` › `default`

Optional. Text.

The value used when a run does not give one.

```hcl
default = "celsius"
```

### `inputs.functions.binding` › `type`

Optional. One of the values below. Default: `"Request"`.

What `target` names: a field of the request body, a query parameter or a path parameter.

| Value | Meaning |
|---|---|
| `Request` | A field of the request body. |
| `Query` | A query-string parameter. |
| `Path` | A path parameter. |

```hcl
type = "query"
```

### `inputs.functions.binding` › `description`

Optional. Text.

What the input is for, shown to whoever starts a run.

```hcl
description = "celsius or fahrenheit"
```

### `inputs.functions.binding` › `required`

Optional. `true` or `false`. Default: `true`.

Whether a run must give the input. A run without a required input is refused, unless the input has a default, which the run then uses.

```hcl
required = false
```

## Each item of `inputs.functions.auth`

A run input that chooses the secret a tool sends with its calls: a run gives the id of the secret, such as one per customer. `{ function = @ref("get-order") name = "customerKey" header = "X-Api-Key" }`.

### `inputs.functions.auth` › `function`

**Required.** Reference to a tool.

The tool whose calls send the secret.

```hcl
function = @ref("get-order")
```

### `inputs.functions.auth` › `name`

**Required.** Text.

The input's name: a run gives the input's value under this name.

```hcl
name = "shopKey"
```

### `inputs.functions.auth` › `agent`

Optional. Reference to an agent.

Only the participant filled by this agent sends it. Every participant when not set.

```hcl
agent = @ref("support-agent")
```

### `inputs.functions.auth` › `secret`

Optional. Reference to a secret.

The secret sent when a run does not choose one.

Secrets are not part of a project: create them in the portal, with the SDK or with `mt secret-create` before you deploy.

```hcl
secret = @ref("orders-api-key")
```

### `inputs.functions.auth` › `header`

Optional. Text. Default: `"Authorization"`.

The HTTP header the secret's value is sent in.

```hcl
header = "X-Api-Key"
```

### `inputs.functions.auth` › `description`

Optional. Text.

What the input is for, shown to whoever starts a run.

```hcl
description = "The id of the secret that holds the shop's API key"
```

### `inputs.functions.auth` › `required`

Optional. `true` or `false`. Default: `true`.

Whether a run must give the input. A run without a required input is refused, unless the input has a default, which the run then uses.

```hcl
required = true
```

## The `context` block

`context = { ... }`: how an interaction keeps its conversation within the model's context window, and how many rounds of tool calls an agent may make in one turn. The defaults suit almost every interaction.

### `context` › `mode`

Optional. One of the values below. Default: `"Automatic"`.

What happens when the conversation no longer fits the model's context window.

| Value | Meaning |
|---|---|
| `None` | Nothing until the conversation reaches the model's hard limit; then it is shortened just enough to be sent. |
| `Automatic` | The conversation is shortened once it passes the target share of the window: large tool results first, then the oldest turns in the middle, with a notice that it was trimmed. |
| `Aggressive` | Accepted, and trims as Automatic does. |
| `FailOnOverflow` | No trimming: a conversation that no longer fits makes the run fail, instead of going on without part of it. |

```hcl
mode = "Automatic"
```

### `context` › `targetUtilization`

Optional. Number. Default: `0.9`.

How full the context window may get, as a fraction from 0 to 1, before the conversation is shortened.

```hcl
targetUtilization = 0.8
```

### `context` › `toolResultTrimming.mode`

Optional. One of the values below. Default: `"OnOverflow"`.

When the results of earlier tool calls are replaced, to save space.

| Value | Meaning |
|---|---|
| `Never` | Never: tool results are kept as they came back. |
| `OnOverflow` | When the conversation no longer fits the context window. |
| `AfterCycles` | Once an agent has had a set number of turns: every result but the latest. |
| `Both` | Both: after a set number of turns, and whenever the conversation no longer fits. |

```hcl
toolResultTrimming.mode = "Never"
```

### `context` › `toolResultTrimming.maxCycles`

Optional. Whole number. Default: `3`.

For `toolResultTrimming.mode = "AfterCycles"` or `"Both"`: once an agent has had this many turns, every tool result but the latest is replaced.

```hcl
toolResultTrimming.maxCycles = 6
```

### `context` › `toolResultTrimming.replacement`

Optional. Text. Default: `"[Tool result trimmed to reduce context size]"`.

The text a replaced tool result becomes, with `toolResultTrimming.style = "Placeholder"`.

```hcl
toolResultTrimming.replacement = "[Tool result trimmed to reduce context size]"
```

### `context` › `toolResultTrimming.style`

Optional. One of the values below. Default: `"Placeholder"`.

What a replaced tool result becomes.

| Value | Meaning |
|---|---|
| `Placeholder` | The same replacement text for every result. |
| `Digest` | A digest of what was there: which tool was called, with what, how large the result was, and how it began. Made without a model call, so it costs nothing. |

```hcl
toolResultTrimming.style = "Digest"
```

### `context` › `compaction.type`

Optional. One of the values below. Default: `"None"`.

Whether, and how, older parts of a long conversation are summarised to make room. Suits long, many-turn conversations such as chats.

| Value | Meaning |
|---|---|
| `SameAgent` | The agent summarises its own conversation. Usually the best summary, since the agent already understands it, but an agent with strict instructions may refuse. |
| `StandaloneAgent` | A separate call, without the agent's instructions, writes the summary, so one is always made. It loses the agent's own understanding. |
| `None` | No summaries: the conversation is only trimmed. |

```hcl
compaction.type = "SameAgent"
```

### `context` › `compaction.atUtilization`

Optional. Number. Default: `0.85`.

How full the context window gets, as a fraction from 0 to 1, before the conversation is summarised.

```hcl
compaction.atUtilization = 0.75
```

### `context` › `compaction.keepRecentTokens`

Optional. Whole number. Default: `8000`.

The most recent part of the conversation kept word for word when it is summarised, in tokens.

```hcl
compaction.keepRecentTokens = 16000
```

### `context` › `compaction.targetTokens`

Optional. Whole number. Default: `4000`.

The length the summary aims for, in tokens.

```hcl
compaction.targetTokens = 8000
```

### `context` › `maxToolCallLoops`

Optional. Whole number. Default: `10`.

The most rounds of tool calls an agent may make in one turn. After that its tools are withheld and it must answer. `0` means no limit.

```hcl
maxToolCallLoops = 12
```

### `context` › `responseMaxTokens`

Optional. Whole number. Default: `32000`.

The most the model may write in one reply, in tokens, including any reasoning. `0` allows the model's maximum. A value above the model's maximum is lowered to it; a reply that reaches the limit is kept as far as it got.

```hcl
responseMaxTokens = 64000
```

### `context` › `compaction.maxTokens`

Optional. Whole number. Default: `40000`.

The most a summary may be, in tokens. Used only with `compaction.type` set.

```hcl
compaction.maxTokens = 80000
```

## The `loom` block

`loom = { ... }` on an interaction: long-term memory for its agents. A bare `loom = { enabled = true }` gives each agent a fresh memory for every run, with suggested recall and suggested weaving.

### `loom` › `enabled`

Optional. `true` or `false`. Default: `true`.

Turns memory on. `false` keeps the settings but leaves memory off.

```hcl
enabled = true
```

### `loom` › `scope`

Optional. One of the values below. Default: `"Process"`.

Whose memory a run uses, and so whether it lasts beyond one run.

| Value | Meaning |
|---|---|
| `Process` | A fresh loom for every run. One caller's threads never reach another's run. |
| `Interaction` | One loom per agent (or one for the interaction, when shared) that lasts across every run. |
| `Input` | One loom per value of the run input named by the scope input, such as one per user. |

```hcl
scope = "Input"
```

### `loom` › `scopeInput`

Optional. Text.

For `scope = "Input"`: the run input whose value picks the memory, such as `"userName"`.

```hcl
scopeInput = "email"
```

### `loom` › `shared`

Optional. `true` or `false`. Default: `false`.

All the interaction's agents share one memory. Otherwise each agent has its own.

```hcl
shared = true
```

### `loom` › `recall`

Optional. One of the values below. Default: `"Suggest"`.

How remembered threads come back into a conversation.

| Value | Meaning |
|---|---|
| `Off` | Nothing is offered: the agent searches its memory only when it chooses to. |
| `Suggest` | The agent is told what may connect, and chooses whether to recall it. |
| `Surface` | The relevant threads are shown in the conversation straight away. |

```hcl
recall = "Suggest"
```

### `loom` › `weave`

Optional. One of the values below. Default: `"Suggest"`.

How new memories are made from a conversation.

| Value | Meaning |
|---|---|
| `Manual` | Only the agent weaves, with its LoomWeave tool. |
| `Suggest` | The weaver reads each exchange and offers what may be worth keeping; the agent decides. |
| `Auto` | The weaver weaves weak threads directly; use strengthens them, and disuse lets them fade. |

```hcl
weave = "Manual"
```

### `loom` › `tapestryTokens`

Optional. Whole number. Default: `1500`.

How long the "what you know" summary an agent is given when a conversation starts may be, in tokens.

```hcl
tapestryTokens = 3000
```

### `loom` › `subjectInputs`

Optional. List of text.

Run inputs that name who the agents are working with, such as `"userName"`, so memories are about the right person.

```hcl
subjectInputs = [ "customer" ]
```

### `loom` › `nightWeaving`

Optional. `true` or `false`. Default: `true`.

Tidy up the memories this interaction creates once a conversation goes quiet: summarise it, merge duplicates, note contradictions and write a short portrait of each person.

```hcl
nightWeaving = true
```

### `loom` › `quietMinutes`

Optional. Whole number. Default: `15`.

Minutes without activity before night weaving starts.

```hcl
quietMinutes = 5
```

### `loom` › `weaverModel`

Optional. Text.

The catalogue model that reads conversations to make memories and to night-weave, such as `"anthropic.haiku-4.5"`. The agent's own model when not set.

```hcl
weaverModel = "deepseek.deepseek-v4-flash"
```

### `loom` › `vetModel`

Optional. Text.

A lightweight catalogue model, such as `"anthropic.haiku-4.5"`, that checks what memory offers an agent in the middle of a conversation and drops what does not belong. Off when not set.

```hcl
vetModel = "deepseek.deepseek-v4-flash"
```

### `loom` › `vetRecall`

Optional. `true` or `false`. Default: `false`.

The `vetModel` also checks what an agent's own searches of its memory bring back.

```hcl
vetRecall = true
```

### `loom` › `seedFrom`

Optional. Reference to a loom.

A named loom every memory this interaction creates starts from: a new person's memory starts knowing what the shared loom knows.

```hcl
seedFrom = @ref("family")
```

### `loom` › `readFrom`

Optional. List of references to looms.

Named looms the agents may read but never change.

```hcl
readFrom = [ @ref("family") ]
```

## The `swarm` block

`swarm = { interactions = [ @ref("country-facts") ] maxConcurrent = 5 }`: other interactions this one's agents may start as sub-runs, wait for and combine. An agent also needs `allowSwarm = true`.

### `swarm` › `interactions`

**Required.** List of references to interactions.

The interactions that may be started. Nothing else can be. They may be declared in the same project, this one included.

```hcl
interactions = [ @ref("country-facts") ]
```

### `swarm` › `participants`

Optional. List of text.

The seats (by `name`) that may start them. Every seat whose agent allows it when not set.

```hcl
participants = [ "Lead" ]
```

### `swarm` › `maxDepth`

Optional. Whole number.

How many levels of sub-runs may be started below this one. 1 when not set; at most 4.

```hcl
maxDepth = 2
```

### `swarm` › `maxConcurrent`

Optional. Whole number.

How many sub-runs may run at once, across the whole tree. 5 when not set; at most 20.

```hcl
maxConcurrent = 5
```

### `swarm` › `maxTotal`

Optional. Whole number.

How many sub-runs may be started in all, across the whole tree. 20 when not set; at most 100.

```hcl
maxTotal = 10
```

## The `agent` › `webAccess` block

A participant's built-in web tools: `webAccess = { search = true fetch = false }`. Leave either out to keep its agent's own setting.

### `webAccess` › `search`

Optional. `true` or `false`.

Gives (`true`) or takes away (`false`) the web search tool.

```hcl
search = true
```

### `webAccess` › `fetch`

Optional. `true` or `false`.

Gives (`true`) or takes away (`false`) the web fetch tools.

```hcl
fetch = true
```

## The `functions.openApi` › `auth` block

A credential sent with every call of a tool: `auth = { header = "X-Api-Key" secret = @ref("orders-api-key") }`. The tool's connector needs `auth = "Header"`.

The same block is used for `agent › functions.openApi › auth`.

### `auth` › `header`

**Required.** Text.

The HTTP header the secret's value is sent in. For `Authorization`, a value without a space is sent as `Bearer <value>`.

```hcl
header = "X-Api-Key"
```

### `auth` › `secret`

**Required.** Reference to a secret.

The secret whose value is sent. Create the secret before you deploy.

Secrets are not part of a project: create them in the portal, with the SDK or with `mt secret-create` before you deploy.

```hcl
secret = @ref("orders-api-key")
```

## A complete example

From the [23-support-desk](https://github.com/maitento/maitento/tree/main/examples/23-support-desk) example project:

```hcl title="interactions/one-shot/draft-reply.mtr"
agent = {
  name = "Desk"
  agent = @ref("desk-agent")
}
task = @prompt("draft-reply-task")
inputs.prompts = [
  {
    name = "customer"
    description = "The customer's name"
    required = true
  },
  {
    name = "email"
    description = "The customer's email address: whose memory the agent wakes up with"
    required = true
  },
  {
    name = "message"
    description = "What the customer wrote"
    required = true
  },
  {
    name = "category"
    required = true
  },
  {
    name = "urgency"
    required = true
  },
  {
    name = "orderId"
    required = false
  }
]
functions.openApi = [
  {
    name = "find-order"
    ref = @ref("find-order")
  },
  {
    name = "issue-refund"
    ref = @ref("issue-refund")
  },
  {
    name = "search-policies"
    ref = @ref("search-policies")
  }
]
// The knowledge base id comes in with each run and goes straight into the search (tutorial 13).
inputs.functions.binding = [
  {
    function = @ref("search-policies")
    name = "policiesId"
    target = "partitionId"
    description = "The knowledge base that holds the shop's policies"
    required = true
    type = "request"
  }
]
// One memory per customer, chosen by their email address (tutorial 20).
loom = {
  enabled = true
  scope = "Input"
  scopeInput = "email"
  subjectInputs = [ "customer" ]
  recall = "Suggest"
  weave = "Manual"
}
duration.warn = "00:03:00"
duration.max = "00:05:00"
```
