# Apps

An **app** is code that runs on Maitento. Use one when you need logic *around* your agents: start an
interaction, look at its answer, branch, loop, start several more in parallel, write files, run a
[capsule](https://docs.maitento.com/concepts/capsules.md). Apps are written in **Cogniscript**, a small C-like language.

See it in [tutorial 16, Apps](https://docs.maitento.com/tutorials/16-apps.md), which writes the app below, and
[tutorial 17](https://docs.maitento.com/tutorials/17-on-a-schedule.md), which runs it every morning with an [automation](https://docs.maitento.com/concepts/automations.md).
[Tutorial 23](https://docs.maitento.com/tutorials/23-support-desk.md#8-the-nightly-digest)'s nightly digest is an app that calls a tool,
saves the day's tickets to a [file system](https://docs.maitento.com/concepts/file-systems.md) and runs a capsule that reports on them.

```c title="apps/taglines/taglines.mtc"
// The taglines app: it asks for three campaign angles, then writes a tagline for each angle, all at once.
// An app starts at main. main's parameters are the app's inputs: this one has one, product.
string main(string product) {
	// Stop early, with a clear message, when the input is missing.
	if (product == "") {
		throw("Tell me which product to write taglines for.");
	}
	PrintLine("Brainstorming angles for " + product);

	// Start the brainstorm interaction and wait for it. The last three arguments are for memory,
	// model overrides and a response schema; this app uses none of them.
	jsonObject brainstormInputs = JsonObjectCreate();
	brainstormInputs = JsonObjectSetString(brainstormInputs, "product", product);
	guid brainstormId = InteractionStart("brainstorm", brainstormInputs, "[]", "[]", "");
	jsonObject brainstorm = InteractionAwait(brainstormId);
	if (JsonObjectGet(brainstorm, "status") != "Finished") {
		throw("The brainstorm failed: " + JsonObjectGet(brainstorm, "failureDetails"));
	}

	// The brainstorm's answer is JSON that matches json/ideas.json.
	jsonObject result = StringToJsonObject(JsonObjectGet(brainstorm, "output"));
	jsonArray ideas = JsonObjectGet(result, "ideas");

	// Start one tagline run per idea. They all run at the same time.
	jsonArray runIds = JsonArrayCreate();
	int ideaCount = JsonArrayCount(ideas);
	for (int i = 0; i < ideaCount; i++) {
		jsonObject inputs = JsonObjectCreate();
		inputs = JsonObjectSetString(inputs, "product", product);
		inputs = JsonObjectSetString(inputs, "idea", JsonArrayGetAtIndex(ideas, i));
		guid runId = InteractionStart("tagline", inputs, "[]", "[]", "");
		runIds = JsonArrayAdd(runIds, runId);
	}

	// Wait for every tagline run, then collect the answers.
	jsonArray runs = InteractionAwaitAll(runIds);
	jsonArray taglines = JsonArrayCreate();
	int runCount = JsonArrayCount(runs);
	for (int j = 0; j < runCount; j++) {
		jsonObject run = JsonArrayGetAtIndex(runs, j);
		if (JsonObjectGet(run, "status") == "Finished") {
			string tagline = JsonObjectGet(run, "output");
			PrintLine("- " + tagline);
			taglines = JsonArrayAdd(taglines, tagline);
		}
	}

	// A named output is what your code reads back from the finished run. return gives the run's return value.
	AppStateSetOutputJsonArray("taglines", taglines);
	return(JsonArrayCount(taglines) + " taglines");
}
```

## How an app is laid out

- Each app is a folder under `apps/`, named after the app: `apps/taglines/`.
- It holds one or more `.mtc` files with the code, and optionally one `.mtr` file with settings such as
  `duration.max`.
- An app starts at `main`. **`main`'s parameters are the app's inputs**, so `main(string product)` makes an
  app with one input called `product`.
- **A system call takes every argument**, even the ones you don't need: `InteractionStart`'s last three are there
  even when they're empty. The deploy fails ("Argument count mismatch") when one is missing.

## What an app produces

| Field on the finished run | Comes from |
|---|---|
| `returnValue` | `return(...)` in `main` |
| `outputs` | `AppStateSetOutputString(name, value)`, `AppStateSetOutputJsonArray(...)` and friends |
| `consoleOutputs` | `PrintLine(...)` and `TraceLine(...)` |
| `failureDetails` | an uncaught `throw("...")` (its message), or an error the app didn't catch (`InteractionStart failed: …`, naming the system call) |

## Running one

```ts
const run = await mt.apps.run("taglines", { inputs: { product: "a reusable bamboo coffee cup" } });
console.log(run.outputs);
```
```python
run = mt.apps.run("taglines", inputs={"product": "a reusable bamboo coffee cup"})
print(run.outputs)
```
```csharp
var run = await mt.Apps.RunAsync("taglines", new Dictionary<string, string> { ["product"] = "a reusable bamboo coffee cup" });
foreach (var output in run.Outputs) Console.WriteLine($"{output.Name}: {output.Value}");
```
```bash
mt app-run taglines --input-product "a reusable bamboo coffee cup"
```

`run()` waits for the run. If it ends `Error` or `Killed`, `run()` throws (`ProcessFailedError` in TypeScript and
Python, `MaitentoProcessFailedException` in C#) with the run's `failureDetails`. To get the failed run back instead,
pass `throwOnFailure: false` (`throw_on_failure=False` in Python, `ThrowOnFailure = false` in C#'s `RunOptions`). A
start without one of the app's inputs is refused with `api:apps:input-missing`. See it in
[tutorial 16's break-it step](https://docs.maitento.com/tutorials/16-apps.md#break-it-on-purpose).

## Cogniscript in five minutes

```c
string main(string name) {
	string greeting = "Hello " + name;    // variables must be given a value
	int count = 3;
	for (int i = 0; i < count; i++) {
		PrintLine(greeting);
	}
	if (count > 2) {
		return("many");                   // return, exit and throw need brackets
	}
	return("few");
}
```

- **Types:** `string`, `int`, `decimal`, `bool`, `guid`, `date`, `jsonObject` and `jsonArray`. Every value is held
  as text underneath, so treat types as documentation.
- **Arithmetic is decimal:** `7 / 2` is `3.5`. `+` adds two numbers, and joins anything else.
- **Comparisons are exact:** `==` compares text exactly, including case.
- **JSON helpers return a new value:** write `o = JsonObjectSetString(o, "key", "value");`.
- **Errors:** `try { } catch { }` catches them, and `GetLastError()` says what went wrong.
- **Backslashes need doubling in strings:** `"\\d"`.

## The most useful calls

| Area | Calls |
|---|---|
| Interactions | `InteractionStart`, `InteractionAwait`, `InteractionAwaitAll`, `InteractionAwaitAny` |
| Apps | `AppStart`, `AppProcessAwait`, `AppStateSetOutput…` |
| Files | `VfsReadText`, `VfsWriteText`, `VfsFind`, `FileSearch` |
| Sandboxes | `CapsuleStart`, `CapsuleExec`, `CapsuleCommandAwait`, `CapsuleTerminate`: see [tutorial 21](https://docs.maitento.com/tutorials/21-sandboxes.md#4-an-agent-in-the-sandbox) |
| Tools | `ExtOpInvoke` |
| JSON | `JsonObjectCreate`, `JsonObjectGet`, `JsonObjectSetString`, `JsonArrayCreate`, `JsonArrayAdd`, `JsonArrayCount`, `JsonArrayGetAtIndex` |
| Text | `StringToLower`, `StringContains`, `StringToJsonObject`, `RegexIsMatch` |

## Good to know

- **Child runs use your model keys.** Interactions an app starts run on your tenant's
  [model keys](https://docs.maitento.com/concepts/models-and-keys.md), like any other run.
- **Every input is required** when you start the app, and arrives as text.
- **Tools that need approval don't run from apps.** Approvals happen in interactions.
