Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/reference/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,12 @@ A connection is a tool server that a person adds to the org through their agent,

The recipes are also prompts, which a client that shows MCP prompts offers in its menu. A connection offers a prompt only when it lists every tool the recipe's steps call, and the description of `get_guide` names the same recipes. So a playbook endpoint offers no `first-playbook`, the org endpoint offers none, and a key that may only read gets none.

| Prompt | Title | Arguments | What the recipe does |
| ---------------- | ------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------- |
| `first-playbook` | Create your first playbook | None | Creates a playbook with a first reasoning function, and runs it once the person agrees |
| `remember` | Make the playbook remember something | `what` to remember | Keeps what the playbook's runs returned or its events recorded in a recall function |
| `give-tools` | Give the playbook tools | Optional `server`, a tool server | Gives a reasoning function the tools of a tool server the playbook may use |
| `schedule` | Run a workflow on a schedule | The `workflow`, and `when` to run it | Starts a workflow on a schedule or on an event, and reads how its runs went |
| Prompt | Title | Arguments | What the recipe does |
| ---------------- | ------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `first-playbook` | Create your first playbook | None | Creates a playbook with a first reasoning function, runs it once the person agrees, and, where the work should run on its own, saves the workflow that runs it |
| `remember` | Make the playbook remember something | `what` to remember | Keeps what the playbook's runs returned or its events recorded in a recall function |
| `give-tools` | Give the playbook tools | Optional `server`, a tool server | Gives a reasoning function the tools of a tool server the playbook may use |
| `schedule` | Run a workflow on a schedule | The `workflow`, and `when` to run it | Starts a workflow on a schedule or on an event, and reads how its runs went |

A prompt returns the recipe with the person's words filled in, followed by the format guide it needs.

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/first-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ The playbook now holds a workflow that reviews a brief and asks for an approval,
Next:

- An interaction function can also send its question through a tool, such as a chat message, and take the person's reply as the answer. [Interaction function format](../reference/interaction-format.md) shows how.
- [Run a workflow on a schedule or event](../guides/triggers.md) starts a workflow without anyone asking.
- [Run a workflow on a schedule or event](../guides/triggers.md) starts a workflow without anyone asking. A new playbook can start that way from the beginning: when you ask your agent for one, it asks how the work starts, unless your words already say so, such as "every morning" or "whenever a brief arrives". Where the work should start on its own, it writes the workflow that runs your first function, once you have seen that function answer.
- [Workflows and runs](../concepts/workflows.md) explains how runs start, wait and end.

</div>
2 changes: 1 addition & 1 deletion packages/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ The server serves `list_models`, an org query of [`@on.auto/reasoning`](../../ca

`makeAppRuntime(layer)` builds the runtime every call runs in. Its `run(effect, signal?)` answers the effect's value, or `cancelled` when the effect was interrupted, by the runtime's disposal or by the abort of the signal given, which interrupts it so its finalizers run; the server gives the signal of the call a workflow performs, so a call the workflow cancels stops the run it started.

`src/index.ts` is the entry point: `createApiHandler`, `makeAppRuntime`, `operationRoutes`, `openApiDocumentRoutes` and the document's path `openApiPath`, `guideRoutes`, `apiCatalogRoutes`, `clientMetadataRoutes` and its `ClientMetadataDocument`, `guideShelfOf`, `guideShelfFrom(shippedFiles, types)`, the shelf of the guides the API serves for the types of definition it is given, each a `ServedType`, its type, noun and guide, as a `Capability` of `@on.auto/definitions` is one, read through the `ShippedFiles` it is given: the terminology and the format page of each type, `docs` of `@on.auto/runtime`, without the markup of the documentation site and with their links resolved to the published pages, and the recipes, `src/guides/recipes` of `@on.auto/api`, each kept only when its format guide is; `apiShippedRoots()` declares the root of both packages, the runtime's two folders above the API's in the repository, the image and the npm package alike, read only when it is called, so the entry loads where `import.meta.url` names no file (`src/shipped-files/api-roots.test.ts`), `mcpRoutes`, the instructions, the types of a guide and a recipe, the guide's address and media type, the bounds, the types of the served Studio, `ApiStudio`, `HostedStudio`, `ServedStudio`, `StudioHost` and `StudioAnswer`, and their types. `@on.auto/api/testing` exports what the server's tests share: real MCP clients of the current SDK, on either revision, and of the SDK's 1.x line, each of which records in `compiledOutputSchemas` the output schemas it compiles a validator from and refuses every result it checks against one; helpers that read a tool listing, among them `takingPlaybook`, which gives a playbook endpoint's tool the `playbook` argument it has on `/mcp`, and `operationToolsIn`, which answers the listed tools but `get_guide`; helpers that read an OpenAPI document, `operationsIn`, `openApiErrorsIn`, which checks it against the published schema, `unresolvedSchemasIn`, which compiles each of its schemas alone, and `schemaErrorsIn`, which checks a value against one; the old words of the repository's vocabulary, `oldWords`, `oldWordsIn` and `foundIn`; and the `wait_forever` operation, which never finishes on its own.
`src/index.ts` is the entry point: `createApiHandler`, `makeAppRuntime`, `operationRoutes`, `openApiDocumentRoutes` and the document's path `openApiPath`, `guideRoutes`, `apiCatalogRoutes`, `clientMetadataRoutes` and its `ClientMetadataDocument`, `guideShelfOf`, `guideShelfFrom(shippedFiles, types)`, the shelf of the guides the API serves for the types of definition it is given, each a `ServedType`, its type, noun and guide, as a `Capability` of `@on.auto/definitions` is one, read through the `ShippedFiles` it is given: the terminology and the format page of each type, `docs` of `@on.auto/runtime`, without the markup of the documentation site and with their links resolved to the published pages, and the recipes, `src/guides/recipes` of `@on.auto/api`, each kept only when its format guide is, and given the words of another type, and the tools they call, only where that type's guide is carried too, as the first playbook writes the workflow that runs its function only where the workflow guide is; `apiShippedRoots()` declares the root of both packages, the runtime's two folders above the API's in the repository, the image and the npm package alike, read only when it is called, so the entry loads where `import.meta.url` names no file (`src/shipped-files/api-roots.test.ts`), `mcpRoutes`, the instructions, the types of a guide and a recipe, the guide's address and media type, the bounds, the types of the served Studio, `ApiStudio`, `HostedStudio`, `ServedStudio`, `StudioHost` and `StudioAnswer`, and their types. `@on.auto/api/testing` exports what the server's tests share: real MCP clients of the current SDK, on either revision, and of the SDK's 1.x line, each of which records in `compiledOutputSchemas` the output schemas it compiles a validator from and refuses every result it checks against one; helpers that read a tool listing, among them `takingPlaybook`, which gives a playbook endpoint's tool the `playbook` argument it has on `/mcp`, and `operationToolsIn`, which answers the listed tools but `get_guide`; helpers that read an OpenAPI document, `operationsIn`, `openApiErrorsIn`, which checks it against the published schema, `unresolvedSchemasIn`, which compiles each of its schemas alone, and `schemaErrorsIn`, which checks a value against one; the old words of the repository's vocabulary, `oldWords`, `oldWordsIn` and `foundIn`; and the `wait_forever` operation, which never finishes on its own.

## Source

Expand Down
77 changes: 77 additions & 0 deletions packages/api/src/guides/recipes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { readFileSync } from 'node:fs';

import { shippedFilesOnDisk } from '@on.auto/operations/shipped-files-on-disk';
import { describe, expect, it } from 'vitest';

import { apiShippedRoots } from '../index.ts';
import type { Guide, Recipe } from './guide-shelf.ts';
import { recipesFor } from './recipes.ts';

const recipeFiles = shippedFilesOnDisk(apiShippedRoots());

function guidesNamed(names: readonly string[]): readonly Guide[] {
return names.map((name) => ({ name, title: name, description: name, text: `# ${name}\n` }));
}

async function firstPlaybookCarrying(...carried: readonly string[]): Promise<Recipe | undefined> {
const recipes = await recipesFor(recipeFiles, guidesNamed(['reasoning-function', ...carried]));
return recipes.find(({ name }) => name === 'first-playbook');
}

const withWorkflows = await firstPlaybookCarrying('workflow');

const withWorkflowsAndInteraction = await firstPlaybookCarrying('workflow', 'interaction-function');

const withoutWorkflows = await firstPlaybookCarrying();

const firstPlaybookFile = readFileSync(new URL('recipes/first-playbook.md', import.meta.url), 'utf8');

const callsOfTheFunction = ['list_playbooks', 'create_playbook', 'create_definition', 'execute', 'run_definition'];

describe('the first-playbook recipe where the workflow guide is carried', () => {
it('says that a first playbook holds the workflow that runs its function where the work should run on its own', () => {
expect(withWorkflows?.text).toContain(
'A first playbook holds a first reasoning function that the person sees answer, and, where the work should run on its own, the workflow that runs it. Read the reasoning-function guide',
);
});

it('learns how the work starts after what the playbook is for, and takes words that already say so as the answer', () => {
expect(withWorkflows?.text).toContain(
'a budget and a measurable goal. Then learn how its work starts: on its own at set times, on its own whenever something arrives, or only when someone asks. Where their words already say so, such as "every morning", "continuously", "keep reviewing" or "whenever a lead comes in", take that as the answer and say so rather than asking again. When more than one model is offered',
);
});

it('writes the workflow after the function has answered, with its trigger as the workflow guide says, and saves it once the person agrees', () => {
expect(withWorkflows?.text).toContain(
"6. Ask for an input, run the function with run_definition, and tell the person what it answered, in their words.\n7. Where the work starts on its own, read the workflow guide with get_guide and write the workflow that runs the function, with a `schedule` as the guide says. For set times, `cron` in UTC or `every`: a run they start is given only its due time, so the function finds its material with its tools, or the workflow gives it a fixed input in `input.from`. For what arrives, `on` with a filter on an event `type` named in the past tense, such as `com.acme.lead.received`: a run is given a list holding the event, and the workflow passes the event's `data` to the function. Where the material comes from sources not yet connected, start with `on`, so that the person starts runs today with material they paste, and move to a clock once the function's tools reach the sources.\n8. Show the person the workflow's document, and save it with create_definition, `type` workflow, once they agree.\n",
);
});

it('tells the person when it next runs or what starts it, publishes what they paste for an event, and names the schedule recipe and the workflow guide first of what comes next', () => {
expect(withWorkflows?.text).toContain(
'\n9. Tell the person when it next runs, in their own time, or what starts it. For `on`, publish the material they paste with publish_event, as an event of that type with a `source` that names where it came from, and a moment later show what its run answered with list_runs, `type` workflow, and get_run.\n\nThen tell the person what they can do next: change when it runs with the schedule recipe and the workflow guide, give it tools with the give-tools recipe, or make the playbook remember its answers with the remember recipe.\n',
);
});

it('calls publish_event, list_runs and get_run beside the tools of the function', () => {
expect(withWorkflows?.calls).toEqual([...callsOfTheFunction, 'publish_event', 'list_runs', 'get_run']);
});
});

describe('the first-playbook recipe where the interaction-function guide is carried too', () => {
it('names an interaction function among what comes next, after the schedule recipe and the tools', () => {
expect(withWorkflowsAndInteraction?.text).toContain(
'Then tell the person what they can do next: change when it runs with the schedule recipe and the workflow guide, give it tools with the give-tools recipe, send someone a message through a tool and take their answer with an interaction function, or make the playbook remember its answers with the remember recipe.\n',
);
expect(withWorkflowsAndInteraction?.calls).toEqual(withWorkflows?.calls);
});
});

describe('the first-playbook recipe where the workflow guide is not carried', () => {
it('ends at the run of the function and what comes next, as its file has it, and calls only the tools of the function', () => {
expect([withoutWorkflows?.text, withoutWorkflows?.calls]).toEqual([firstPlaybookFile, callsOfTheFunction]);
expect(firstPlaybookFile).toContain(
'tell the person what it answered, in their words.\n\nThen tell the person what they can do next: give it tools with the give-tools recipe, or make the playbook remember its answers with the remember recipe.\n',
);
});
});
Loading