From 88cd15ab319614dd674b3368c1fb6a85bee6dd1e Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 4 Oct 2026 16:58:43 +0100 Subject: [PATCH 1/5] feat(operations): say that a prompt names the model in the remedy The remedy for a model the server is not set up for said "once it names a model from one of those". With a spec of inference now called a reason function, "it" would be the reason function, but it is the prompt that configures it which names the model. The remedy now reads "once its prompt names a model from one of those, which the details below list, it can be tried again." The phrasing and explanation tests use reason function as their sample noun instead of prompt. Co-Authored-By: Claude Opus 5.5 --- .../src/plain-language/explanation.test.ts | 8 +++++--- .../operations/src/plain-language/explanation.ts | 2 +- .../src/plain-language/phrasing.test.ts | 16 ++++++++-------- packages/server/src/mcp/plain-results.test.ts | 2 +- .../specs/src/plain-language/run-words.test.ts | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/operations/src/plain-language/explanation.test.ts b/packages/operations/src/plain-language/explanation.test.ts index 8f9af434c..64054ddb7 100644 --- a/packages/operations/src/plain-language/explanation.test.ts +++ b/packages/operations/src/plain-language/explanation.test.ts @@ -50,7 +50,7 @@ describe('explanationOf', () => { expect(explanationOf({ reason: 'unavailable', kind: 'model_not_offered' })).toEqual({ why: 'this server is not set up to use the provider of the model named, but it can use others', remedy: - 'This can be put right on your side: once it names a model from one of those, which the details below list, it can be tried again.', + 'This can be put right on your side: once its prompt names a model from one of those, which the details below list, it can be tried again.', }); }); @@ -78,8 +78,10 @@ describe('unsuccessfulWords', () => { }); it('gives the reference of an unexpected failure, and says it was not the person’s doing', () => { - expect(unsuccessfulWords('run the prompt “summary”', 'command', { status: 'failed', incident: 'abc' })).toBe( - 'Could not run the prompt “summary”: something went wrong inside the server. It was not caused by anything you did. If it happens again, whoever runs the server can look into it with this reference: abc.', + expect( + unsuccessfulWords('run the reason function “summary”', 'command', { status: 'failed', incident: 'abc' }), + ).toBe( + 'Could not run the reason function “summary”: something went wrong inside the server. It was not caused by anything you did. If it happens again, whoever runs the server can look into it with this reference: abc.', ); }); diff --git a/packages/operations/src/plain-language/explanation.ts b/packages/operations/src/plain-language/explanation.ts index 568f57145..cb3b09130 100644 --- a/packages/operations/src/plain-language/explanation.ts +++ b/packages/operations/src/plain-language/explanation.ts @@ -45,7 +45,7 @@ const explanationByKind: Readonly> = { model_not_offered: { why: 'this server is not set up to use the provider of the model named, but it can use others', remedy: - 'This can be put right on your side: once it names a model from one of those, which the details below list, it can be tried again.', + 'This can be put right on your side: once its prompt names a model from one of those, which the details below list, it can be tried again.', }, }; diff --git a/packages/operations/src/plain-language/phrasing.test.ts b/packages/operations/src/plain-language/phrasing.test.ts index 65a3027c3..74f6aa9de 100644 --- a/packages/operations/src/plain-language/phrasing.test.ts +++ b/packages/operations/src/plain-language/phrasing.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { alternatives, asSentence, capitalized, counted, listed, quoted } from '../index.ts'; -const prompt = { one: 'prompt', other: 'prompts' }; +const reasonFunction = { one: 'reason function', other: 'reason functions' }; const lists: ReadonlyArray = [ [['a'], 'a'], @@ -20,23 +20,23 @@ describe('phrasing', () => { }); it('capitalizes the first letter of a sentence, and leaves one that starts with a quote', () => { - expect([capitalized('the prompt'), capitalized('“summary” is'), capitalized('')]).toEqual([ - 'The prompt', + expect([capitalized('the reason function'), capitalized('“summary” is'), capitalized('')]).toEqual([ + 'The reason function', '“summary” is', '', ]); }); it('lists alternatives with or', () => { - expect(alternatives(['prompt', 'workflow'])).toBe('prompt or workflow'); + expect(alternatives(['reason function', 'workflow'])).toBe('reason function or workflow'); }); it.each([ - [0, '0 prompts'], - [1, '1 prompt'], - [2, '2 prompts'], + [0, '0 reason functions'], + [1, '1 reason function'], + [2, '2 reason functions'], ])('counts %i as %s', (count, text) => { - expect(counted(count, prompt)).toBe(text); + expect(counted(count, reasonFunction)).toBe(text); }); it.each([ diff --git a/packages/server/src/mcp/plain-results.test.ts b/packages/server/src/mcp/plain-results.test.ts index 48949625e..5ff4d2d18 100644 --- a/packages/server/src/mcp/plain-results.test.ts +++ b/packages/server/src/mcp/plain-results.test.ts @@ -237,7 +237,7 @@ describe('the plain words for a prompt that names a model of a provider the serv }); expect(plainTextIn(unoffered)).toBe( - 'Could not run the prompt “summary”: this server is not set up to use the provider of the model named, but it can use others. Nothing was changed. This can be put right on your side: once it names a model from one of those, which the details below list, it can be tried again.', + 'Could not run the prompt “summary”: this server is not set up to use the provider of the model named, but it can use others. Nothing was changed. This can be put right on your side: once its prompt names a model from one of those, which the details below list, it can be tried again.', ); expect(internalTermsIn(plainTextIn(unoffered))).toEqual([]); expect(technicalTextIn(unoffered)).toContain('Configured providers: openai, gateway'); diff --git a/packages/specs/src/plain-language/run-words.test.ts b/packages/specs/src/plain-language/run-words.test.ts index 858d076fd..6c6660c71 100644 --- a/packages/specs/src/plain-language/run-words.test.ts +++ b/packages/specs/src/plain-language/run-words.test.ts @@ -79,7 +79,7 @@ const rejections: ReadonlyArray Date: Sun, 4 Oct 2026 16:59:10 +0100 Subject: [PATCH 2/5] feat(inference): call a spec of inference a reason function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plain words of every result called a spec of inference a prompt. A prompt is what configures it; the thing a person defines is a reason function. The noun of inference is now reason function, so results read "Created the reason function “summary”.", "Ran the reason function “summary”." and "This brain has 1 reason function: “summary”.". The description of inference, which the spec tools carry, now opens: a spec of the inference primitive is a reason function that reasons with a language model, following a prompt, to turn an input into an answer; in conversation, call it a reason function, while the tools take the name inference. The rest of the description is unchanged. Tests that called the thing a prompt now say reason function: the server's plain words, the quick start and the leak check's sample. The spec operations' tests of an unknown primitive name it reason, the word a person might now try, instead of prompt. Co-Authored-By: Claude Opus 5.5 --- .../api/src/testing/internal-terms.test.ts | 2 +- .../src/development/quick-start.test.ts | 2 +- packages/server/src/mcp/plain-results.test.ts | 30 +++++++++++-------- .../specs/src/operations/get-spec.test.ts | 4 +-- .../specs/src/operations/retire-spec.test.ts | 4 +-- .../specs/src/operations/update-spec.test.ts | 4 +-- .../primitive/inference-description.test.ts | 4 +-- .../src/primitive/inference-description.ts | 5 ++-- .../src/primitive/inference-primitive.test.ts | 4 +-- .../src/primitive/inference-primitive.ts | 2 +- 10 files changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/api/src/testing/internal-terms.test.ts b/packages/api/src/testing/internal-terms.test.ts index dd7f18c72..bb3e050ab 100644 --- a/packages/api/src/testing/internal-terms.test.ts +++ b/packages/api/src/testing/internal-terms.test.ts @@ -31,7 +31,7 @@ describe('internalTermsIn', () => { it('finds none in words for a person', () => { expect( internalTermsIn( - 'Created the prompt “summary”. What it does: Summarizes a text. It has been saved but has not been run yet.', + 'Created the reason function “summary”. What it does: Summarizes a text. It has been saved but has not been run yet.', ), ).toEqual([]); }); diff --git a/packages/server/src/development/quick-start.test.ts b/packages/server/src/development/quick-start.test.ts index 78f06ac97..f21b1a06f 100644 --- a/packages/server/src/development/quick-start.test.ts +++ b/packages/server/src/development/quick-start.test.ts @@ -150,7 +150,7 @@ describe( timeout: developmentTestTimeoutMs, }, () => { - it('creates a brain, stores a classifying prompt, runs it, reads its record and runs a new version', async () => { + it('creates a brain, stores a reason function that classifies tickets, runs it, reads its record and runs a new version', async () => { const steps = await onPnpmDev(async (session) => ({ brain: structured(await session.callTool('create_brain', { brain, name: 'Support' })), stored: await stored(session, 'inference', 'classify-ticket', classifyingPrompt('Answer as JSON.')), diff --git a/packages/server/src/mcp/plain-results.test.ts b/packages/server/src/mcp/plain-results.test.ts index 5ff4d2d18..005dbc379 100644 --- a/packages/server/src/mcp/plain-results.test.ts +++ b/packages/server/src/mcp/plain-results.test.ts @@ -101,18 +101,24 @@ async function brainsCalled(session: McpSession): Promise { ]; } -async function promptsCalled(session: McpSession): Promise { - const prompt = { primitive: 'inference', name: 'summary' }; - const created = await session.callTool('create_spec', inSales({ ...prompt, source: summary })); - const executed = await session.callTool('execute_spec', inSales({ ...prompt, input: { text: 'the quarter' } })); +async function reasonFunctionsCalled(session: McpSession): Promise { + const reasonFunction = { primitive: 'inference', name: 'summary' }; + const created = await session.callTool('create_spec', inSales({ ...reasonFunction, source: summary })); + const executed = await session.callTool( + 'execute_spec', + inSales({ ...reasonFunction, input: { text: 'the quarter' } }), + ); const executionId = String(executed.structuredContent?.['execution_id']); return [ ['create_spec', created], ['list_specs', await session.callTool('list_specs', inSales({ primitive: 'inference' }))], - ['get_spec', await session.callTool('get_spec', inSales(prompt))], + ['get_spec', await session.callTool('get_spec', inSales(reasonFunction))], [ 'update_spec', - await session.callTool('update_spec', inSales({ ...prompt, source: summary.replace('Summarize: ', 'Sum up: ') })), + await session.callTool( + 'update_spec', + inSales({ ...reasonFunction, source: summary.replace('Summarize: ', 'Sum up: ') }), + ), ], ['execute_spec', executed], ['get_execution', await session.callTool('get_execution', inSales({ execution_id: executionId }))], @@ -196,7 +202,7 @@ describe('the plain words that lead each result over MCP', { timeout: workflowTe tools: toolNamesIn(await session.listTools()), successes: [ ...(await brainsCalled(session)), - ...(await promptsCalled(session)), + ...(await reasonFunctionsCalled(session)), ...(await workflowsCalled(session)), ], errors: await errorsCalled(session), @@ -225,19 +231,19 @@ describe('the plain words that lead each result over MCP', { timeout: workflowTe }); }); -describe('the plain words for a prompt that names a model of a provider the server is not set up for', () => { +describe('the plain words for a reason function whose prompt names a model of a provider the server is not set up for', () => { it('say that it can be switched to a provider the server has, when there are others', async () => { server = await servingInference([() => Effect.fail(unofferedProvider)]); - const prompt = { primitive: 'inference', name: 'summary' }; + const reasonFunction = { primitive: 'inference', name: 'summary' }; const unoffered = await onMcp(async (session) => { await session.callTool('create_brain', { brain: 'sales', name: 'Sales' }); - await session.callTool('create_spec', inSales({ ...prompt, source: summary })); - return session.callTool('execute_spec', inSales({ ...prompt, input: { text: 'the quarter' } })); + await session.callTool('create_spec', inSales({ ...reasonFunction, source: summary })); + return session.callTool('execute_spec', inSales({ ...reasonFunction, input: { text: 'the quarter' } })); }); expect(plainTextIn(unoffered)).toBe( - 'Could not run the prompt “summary”: this server is not set up to use the provider of the model named, but it can use others. Nothing was changed. This can be put right on your side: once its prompt names a model from one of those, which the details below list, it can be tried again.', + 'Could not run the reason function “summary”: this server is not set up to use the provider of the model named, but it can use others. Nothing was changed. This can be put right on your side: once its prompt names a model from one of those, which the details below list, it can be tried again.', ); expect(internalTermsIn(plainTextIn(unoffered))).toEqual([]); expect(technicalTextIn(unoffered)).toContain('Configured providers: openai, gateway'); diff --git a/packages/specs/src/operations/get-spec.test.ts b/packages/specs/src/operations/get-spec.test.ts index f028245da..d9f4e0bf8 100644 --- a/packages/specs/src/operations/get-spec.test.ts +++ b/packages/specs/src/operations/get-spec.test.ts @@ -65,10 +65,10 @@ describe('get_spec rejecting', () => { reason: 'not_found', detail: 'There is no echo spec plain in this brain', }); - expect(await call(getSpec, toAlpha(acmeAdmin, { primitive: 'prompt', name: 'plain' }))).toEqual({ + expect(await call(getSpec, toAlpha(acmeAdmin, { primitive: 'reason', name: 'plain' }))).toEqual({ status: 'rejected', reason: 'not_found', - detail: 'There is no primitive prompt', + detail: 'There is no primitive reason', }); }); diff --git a/packages/specs/src/operations/retire-spec.test.ts b/packages/specs/src/operations/retire-spec.test.ts index cbac115f5..105bdc729 100644 --- a/packages/specs/src/operations/retire-spec.test.ts +++ b/packages/specs/src/operations/retire-spec.test.ts @@ -87,10 +87,10 @@ describe('retire_spec rejecting', () => { reason: 'not_found', detail: 'There is no echo spec plain in this brain', }); - expect(await call(retireSpec, toAlpha(acmeAdmin, { primitive: 'prompt', name: 'plain' }))).toEqual({ + expect(await call(retireSpec, toAlpha(acmeAdmin, { primitive: 'reason', name: 'plain' }))).toEqual({ status: 'rejected', reason: 'not_found', - detail: 'There is no primitive prompt', + detail: 'There is no primitive reason', }); }); diff --git a/packages/specs/src/operations/update-spec.test.ts b/packages/specs/src/operations/update-spec.test.ts index 22374c3dc..247390135 100644 --- a/packages/specs/src/operations/update-spec.test.ts +++ b/packages/specs/src/operations/update-spec.test.ts @@ -106,10 +106,10 @@ describe('update_spec rejecting', () => { detail: 'The echo spec greet is retired and can no longer change', kind: 'retired', }); - expect(await call(updateSpec, toAlpha(acmeAdmin, { primitive: 'prompt', name: 'greet', source: hello }))).toEqual({ + expect(await call(updateSpec, toAlpha(acmeAdmin, { primitive: 'reason', name: 'greet', source: hello }))).toEqual({ status: 'rejected', reason: 'not_found', - detail: 'There is no primitive prompt', + detail: 'There is no primitive reason', }); }); diff --git a/primitives/inference/src/primitive/inference-description.test.ts b/primitives/inference/src/primitive/inference-description.test.ts index 96a037397..de69ac912 100644 --- a/primitives/inference/src/primitive/inference-description.test.ts +++ b/primitives/inference/src/primitive/inference-description.test.ts @@ -6,9 +6,9 @@ const calls = 'Calls a language model once per execution, with a prompt rendered from the input, and answers with the text of the model or with a JSON value that matches a schema.'; describe('the description of inference on a server', () => { - it('opens by saying that a spec of inference is a prompt, and which name the tools take', () => { + it('opens by saying that a spec of inference is a reason function, and which name the tools take', () => { expect(inferenceDescriptionFor({ providers: ['anthropic'], aliases: [] })).toMatch( - /^A spec of the inference primitive is a prompt: instructions a language model follows to turn an input into an answer. In conversation, call it a prompt; the primitive's name, `inference`, is what the tools take. Calls a language model/u, + /^A spec of the inference primitive is a reason function: it reasons with a language model, following a prompt, to turn an input into an answer. In conversation, call it a reason function; the primitive's name, `inference`, is what the tools take. Calls a language model/u, ); }); diff --git a/primitives/inference/src/primitive/inference-description.ts b/primitives/inference/src/primitive/inference-description.ts index ad63120eb..0437e98d5 100644 --- a/primitives/inference/src/primitive/inference-description.ts +++ b/primitives/inference/src/primitive/inference-description.ts @@ -7,8 +7,9 @@ const offers = providerNamespaces .join('; '); const naming = [ - 'A spec of the inference primitive is a prompt: instructions a language model follows to turn an input into an answer.', - "In conversation, call it a prompt; the primitive's name, `inference`, is what the tools take.", + 'A spec of the inference primitive is a reason function: it reasons with a language model, following a prompt, to turn', + 'an input into an answer.', + "In conversation, call it a reason function; the primitive's name, `inference`, is what the tools take.", ].join(' '); const calls = [ diff --git a/primitives/inference/src/primitive/inference-primitive.test.ts b/primitives/inference/src/primitive/inference-primitive.test.ts index eef31c75f..6c566b134 100644 --- a/primitives/inference/src/primitive/inference-primitive.test.ts +++ b/primitives/inference/src/primitive/inference-primitive.test.ts @@ -16,8 +16,8 @@ describe('the inference primitive', () => { expect(primitive).toMatchObject({ name: 'inference', title: 'Inference', mediaType: 'text/markdown' }); }); - it('calls a spec a prompt', () => { - expect(primitive.noun).toEqual({ one: 'prompt', other: 'prompts' }); + it('calls a spec a reason function', () => { + expect(primitive.noun).toEqual({ one: 'reason function', other: 'reason functions' }); }); it('repeats a short answer, renders a small structured one, and points to the details for a long one', () => { diff --git a/primitives/inference/src/primitive/inference-primitive.ts b/primitives/inference/src/primitive/inference-primitive.ts index 75303b032..3507e5e10 100644 --- a/primitives/inference/src/primitive/inference-primitive.ts +++ b/primitives/inference/src/primitive/inference-primitive.ts @@ -49,7 +49,7 @@ export function makeInference(options: InferenceOptions): Primitive { name: 'inference', title: 'Inference', description: inferenceDescriptionFor(options.offered), - noun: { one: 'prompt', other: 'prompts' }, + noun: { one: 'reason function', other: 'reason functions' }, describeOutput: describeAnswer, mediaType: 'text/markdown', parse, From 1c10ad7bc73430e66a3a65f01f4a4a687677214b Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 4 Oct 2026 16:59:20 +0100 Subject: [PATCH 3/5] feat(orchestration): say in its description that a workflow coordinates The description of orchestration, which the spec tools carry, now opens with the words a person uses: a spec of the orchestration primitive is a workflow that coordinates the brain's other functions, running them in order, deciding what happens next and waiting for input; in conversation, call it a workflow, while the tools take the name orchestration. The rest of the description is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../src/primitive/orchestration-description.test.ts | 2 +- .../orchestration/src/primitive/orchestration-description.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/primitives/orchestration/src/primitive/orchestration-description.test.ts b/primitives/orchestration/src/primitive/orchestration-description.test.ts index ab008ff9c..8a3a98916 100644 --- a/primitives/orchestration/src/primitive/orchestration-description.test.ts +++ b/primitives/orchestration/src/primitive/orchestration-description.test.ts @@ -18,7 +18,7 @@ function triagedAs(urgency: string): (call: SpecCall) => SpecCallResult { describe('the opening of the description of orchestration', () => { it('says that a spec of orchestration is a workflow, and which name the tools take', () => { expect(orchestrationDescription).toMatch( - /^A spec of the orchestration primitive is a workflow: steps that run other specs of the brain, wait for events and decide what happens next. In conversation, call it a workflow; the primitive's name, `orchestration`, is what the tools take. Runs a workflow:/u, + /^A spec of the orchestration primitive is a workflow: it coordinates the brain's other functions, running them in order, deciding what happens next and waiting for input. In conversation, call it a workflow; the primitive's name, `orchestration`, is what the tools take. Runs a workflow:/u, ); }); }); diff --git a/primitives/orchestration/src/primitive/orchestration-description.ts b/primitives/orchestration/src/primitive/orchestration-description.ts index 40fda9b30..a05f7f4fd 100644 --- a/primitives/orchestration/src/primitive/orchestration-description.ts +++ b/primitives/orchestration/src/primitive/orchestration-description.ts @@ -1,6 +1,6 @@ const introduction = [ - 'A spec of the orchestration primitive is a workflow: steps that run other specs of the brain, wait for events and', - 'decide what happens next.', + "A spec of the orchestration primitive is a workflow: it coordinates the brain's other functions, running them in order,", + 'deciding what happens next and waiting for input.', "In conversation, call it a workflow; the primitive's name, `orchestration`, is what the tools take.", 'Runs a workflow: deterministic steps that execute other specs of the brain, branch, loop, run in parallel,', 'wait, retry and catch errors, durably, until they end.', From 015dd5c921a824cfcfd19a23d90040e9ce6621a7 Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 4 Oct 2026 16:59:32 +0100 Subject: [PATCH 4/5] docs(global): describe a brain by what it can do How a brain works now leads with what a brain can do: reason, interact, compute, recall and predict, coordinating those functions through workflows. Its table has a row per brain function, with what a person defines for it, the primitive that runs it, linked to its package, and whether it is built, in place of the table of primitives and its column What you make with it. A budget-review workflow shows the functions together. The ledger paragraph and the diagram stay. The quick start, the MCP section, the api README and the opening of the inference README call a spec of inference a reason function, and use prompt only for what configures it. Co-Authored-By: Claude Opus 5.5 --- README.md | 36 +++++++++++++++++++--------------- packages/api/README.md | 2 +- primitives/inference/README.md | 2 +- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index af8bbb7b9..c228d9d64 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,21 @@ auto-brain is the server a brain runs on. Auto can host it for you, or you can r ## How a brain works -A brain is made of **primitives** that share one **ledger**. - -| Primitive | Today | What you make with it | What it does | -| ----------------------------------------- | ------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Interaction](primitives/interaction) | Planned | | Input and output between the brain and people or machines, in both directions | -| [Orchestration](primitives/orchestration) | Built | A workflow | Runs a workflow of deterministic steps that execute other specs, branch, loop, retry, wait and listen for events, durably, on Temporal. A workflow starts when its spec is executed; starting one from an event or on a schedule is planned | -| [Inference](primitives/inference) | Built | A prompt | Calls a language model with a prompt written in Markdown: front matter sets the model, its settings and the JSON Schemas of the input and output, and a Liquid template renders the prompt from the input. Skills, tools and context from the rest of the brain are planned | -| [Prediction](primitives/prediction) | Planned | | A machine-learning model that makes a prediction, for when an LLM isn't the right tool | -| [Computation](primitives/computation) | Planned | | A deterministic function that workflows and agents can call | -| [Recollection](primitives/recollection) | Planned | | A materialized view of the brain's history, built from the ledger | -| [Dream](primitives/dream) | Planned | | Explores the ledger around a subject to suggest new scenarios and better ways of working, and can iterate towards a goal | +A brain can reason, interact, compute, recall and predict. It coordinates those functions through workflows. + +| Brain function | What you define | How it runs | Built or planned | +| -------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| Reason | A reason function, configured by a prompt | [Inference](primitives/inference) calls a language model with a prompt written in Markdown: front matter sets the model, its settings and the JSON Schemas of the input and output, and a Liquid template renders the prompt from the input. Skills, tools and context from the rest of the brain are planned | Built | +| Interact | | [Interaction](primitives/interaction) carries input and output between the brain and people or machines, in both directions | Planned | +| Compute | | [Computation](primitives/computation) runs a deterministic function that workflows and agents can call | Planned | +| Recall | | [Recollection](primitives/recollection) keeps a materialized view of the brain's history, built from the ledger | Planned | +| Predict | | [Prediction](primitives/prediction) makes a prediction with a machine-learning model, for when an LLM isn't the right tool | Planned | +| Coordinate | A workflow | [Orchestration](primitives/orchestration) runs a workflow of deterministic steps that execute other specs, branch, loop, retry, wait and listen for events, durably, on Temporal. A workflow starts when its spec is executed; starting one from an event or on a schedule is planned | Built | +| Not named yet | | [Dream](primitives/dream) explores the ledger around a subject to suggest new scenarios and better ways of working, and can iterate towards a goal | Planned | + +A budget-review workflow could recall previous decisions, compute the remaining budget, predict outcomes, reason about alternatives, and interact with a person for approval. Coordinating those functions is what the workflow does. + +Each function runs on a **primitive**, the name the code and the API use for it, and all of them share one **ledger**. The [ledger](packages/ledger) records every input and output of every primitive. That record lets a brain recall what happened and explain its decisions. It also lets you evaluate and improve the method over time. @@ -99,18 +103,18 @@ Next, connect your AI assistant to `http://localhost:8080/mcp`. Local mode needs - **VS Code**, in `.vscode/mcp.json`: `{"servers": {"auto-brain": {"type": "http", "url": "http://localhost:8080/mcp"}}}` - **Other assistants** take the entry under [Connecting an agent over MCP](#connecting-an-agent-over-mcp), without its header. -Then ask it, in order. When you ask for a prompt, name a model your provider serves, such as `anthropic/claude-sonnet-4-5` or `gateway/`: the assistant learns which providers the server has, but not which models your account offers. +Then ask it, in order. When you ask for a reason function, name a model your provider serves, such as `anthropic/claude-sonnet-4-5` or `gateway/`: the assistant learns which providers the server has, but not which models your account offers. 1. "Create a brain called support for our customer support team." -2. "In support, write a prompt that classifies a support ticket by category (billing, bug, account or other) and urgency (low, normal or high), answering in JSON, and run it on: I was charged twice for March and nobody has answered for three days." +2. "In support, create a reason function that classifies a support ticket by category (billing, bug, account or other) and urgency (low, normal or high), answering in JSON, and run it on: I was charged twice for March and nobody has answered for three days." 3. "How many tokens did that run use, and what exactly was sent to the model?" -4. "Change the prompt so that anything about money is billing and at least normal urgency, then run it on the same ticket again." +4. "Change the reason function's prompt so that anything about money is billing and at least normal urgency, then run it on the same ticket again." 5. "Build a workflow that classifies a ticket and, only when it is urgent, drafts a two-sentence note for the on-call lead. Run it on that ticket and on: How do I export my invoices as CSV?" 6. "Start a workflow that waits for a manager to approve a refund, then send it the approval." You don't need to teach the assistant anything first: each tool's description says how the documents of its primitive are written. -To try it without an assistant, `scripts/try-inference.sh http://localhost:8080 ` and `scripts/try-workflows.sh http://localhost:8080 ` run a prompt and a workflow over HTTP and print what happened. [How it works](#how-it-works) walks through the same steps. +To try it without an assistant, `scripts/try-inference.sh http://localhost:8080 ` and `scripts/try-workflows.sh http://localhost:8080 ` run a reason function and a workflow over HTTP and print what happened. [How it works](#how-it-works) walks through the same steps. ### Configuring a model @@ -373,7 +377,7 @@ Most MCP clients take an entry of this shape. In Claude Code, `claude mcp add -- | `POST /orgs/{org}/mcp` | `create_brain`, `list_brains`, `get_brain`, `update_brain` and `retire_brain` | to manage the brains of one org and nothing else | | `POST /orgs/{org}/brains/{brain}/mcp` | the tools inside a brain, acting in that brain, without a `brain` argument | to lock a connection to one brain, such as for one agent | -Every endpoint speaks streamable HTTP without sessions. It serves the current stateless revision (`2026-07-28`) and the earlier ones the SDK supports (`2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05` and `2024-10-07`), so agents built on older SDKs connect too. Each tool carries the operation's description and its input and output JSON Schemas, and is marked read-only when it only reads. Each result leads with a sentence or two in plain words for the person the agent works for, then gives the details for follow-up calls. Those words call a spec of the inference primitive a prompt, one of the orchestration primitive a workflow, and an execution a run, while the tools take the primitive's name, `inference` or `orchestration`; each primitive's description, which the spec tools carry, says so. A tool that cannot do what was asked returns `isError`, its plain words saying what could not be done and who can fix it, and then the same problem document HTTP would answer with, as text, so the agent can read the `reason` and the `detail`, and correct its arguments when the `reason` is `invalid_input`. The key's permissions and brains hold as they do over HTTP: a read-only key can call `list_brains` but gets `forbidden` from `create_brain`, a key limited to some brains gets `forbidden` for any other, and a brain the org does not have is `not_found`. [`packages/api`](packages/api) describes the mappings in full. +Every endpoint speaks streamable HTTP without sessions. It serves the current stateless revision (`2026-07-28`) and the earlier ones the SDK supports (`2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05` and `2024-10-07`), so agents built on older SDKs connect too. Each tool carries the operation's description and its input and output JSON Schemas, and is marked read-only when it only reads. Each result leads with a sentence or two in plain words for the person the agent works for, then gives the details for follow-up calls. Those words call a spec of the inference primitive a reason function, one of the orchestration primitive a workflow, and an execution a run, while the tools take the primitive's name, `inference` or `orchestration`; each primitive's description, which the spec tools carry, says so. A tool that cannot do what was asked returns `isError`, its plain words saying what could not be done and who can fix it, and then the same problem document HTTP would answer with, as text, so the agent can read the `reason` and the `detail`, and correct its arguments when the `reason` is `invalid_input`. The key's permissions and brains hold as they do over HTTP: a read-only key can call `list_brains` but gets `forbidden` from `create_brain`, a key limited to some brains gets `forbidden` for any other, and a brain the org does not have is `not_found`. [`packages/api`](packages/api) describes the mappings in full. ### Errors diff --git a/packages/api/README.md b/packages/api/README.md index 053d9b043..c1c96ca10 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -63,7 +63,7 @@ All three sit behind the same chain as every other path. Before the SDK runs, a | succeeded | `structuredContent` is the output; the first text content says in plain words what happened, and the second is the same output as JSON | | rejected, failed, cancelled | `isError: true`; the first text content says in plain words what could not be done, and the second is the problem document HTTP would answer with, as JSON; no `structuredContent` | -The plain words are for the person an agent works for, so they name things as that person knows them, and carry no ids, versions, formats or status codes: a spec of the inference primitive is a prompt, one of the orchestration primitive a workflow, and an execution a run. The tools still take the primitive's name, `inference` or `orchestration`, and each primitive's description opens by saying which word goes with it, so an agent knows that a person's prompt is a spec of `inference`. Each operation says them itself, through the `plainLanguage` of its definition: a `task` and an `attempt` that name what it does, and an `outcome` from its output; each primitive gives the noun for its specs and a sentence for what one of its runs gave back. An endpoint refuses, when it is mounted, to serve an operation without them. The words for what could not be done come from one place, `unsuccessfulWords` in `@beonauto/operations`, by the reason of the rejection and its `kind`, when it has one (`taken`, `retired`, `concurrent_change` or `unworkable` for a conflict, `model_not_offered` for `unavailable`): a rejection the agent can correct says so; one only whoever runs the server can resolve says that, and that nothing on the person's side needs to change; a prompt that names a model of a provider the server is not set up for, while it can use others, is `model_not_offered`, and its words say that the prompt can be switched to one of those, which the details list; one about the request names what is missing, not allowed, taken or retired; and an unexpected failure gives the reference to quote. HTTP answers are unchanged. +The plain words are for the person an agent works for, so they name things as that person knows them, and carry no ids, versions, formats or status codes: a spec of the inference primitive is a reason function, one of the orchestration primitive a workflow, and an execution a run. The tools still take the primitive's name, `inference` or `orchestration`, and each primitive's description opens by saying which word goes with it, so an agent knows that a person's reason function is a spec of `inference`, and that its prompt is the spec's document. Each operation says them itself, through the `plainLanguage` of its definition: a `task` and an `attempt` that name what it does, and an `outcome` from its output; each primitive gives the noun for its specs and a sentence for what one of its runs gave back. An endpoint refuses, when it is mounted, to serve an operation without them. The words for what could not be done come from one place, `unsuccessfulWords` in `@beonauto/operations`, by the reason of the rejection and its `kind`, when it has one (`taken`, `retired`, `concurrent_change` or `unworkable` for a conflict, `model_not_offered` for `unavailable`): a rejection the agent can correct says so; one only whoever runs the server can resolve says that, and that nothing on the person's side needs to change; a reason function whose prompt names a model of a provider the server is not set up for, while it can use others, is `model_not_offered`, and its words say that the prompt can name a model from one of those instead, which the details list; one about the request names what is missing, not allowed, taken or retired; and an unexpected failure gives the reference to quote. HTTP answers are unchanged. So invalid arguments are a tool result with `isError` and an `invalid_input` problem pointing at each field, as the protocol asks, and an agent can correct them. The SDK's parsing of the arguments drops one named `__proto__`; each endpoint reads the request body before the SDK, within the same 1 MiB, and puts such an argument back before the call is dispatched, so it is rejected as an excess field, `invalid_input` at `/__proto__`, as over HTTP. A tool the endpoint does not list is a JSON-RPC error, `-32602`, from the SDK. A call still running when the server stops gets a `503` `unavailable` problem. A tool that throws instead of settling is reported as an incident, as an HTTP request would be, and answered with the `500` `internal` problem, so the SDK never puts an error message of its own in the result. diff --git a/primitives/inference/README.md b/primitives/inference/README.md index a23d8dc2e..5d063cc5b 100644 --- a/primitives/inference/README.md +++ b/primitives/inference/README.md @@ -1,6 +1,6 @@ # @beonauto/inference -Inference is the primitive of a brain that calls a language model. A spec of inference names a model, gives it instructions and a prompt built from what the brain knows, and says whether the answer is free text or a JSON value that must match a JSON Schema. Each execution sends one request to the model and records the answer, the tokens it used and how it finished. This package calls the models through the Vercel AI SDK, behind a small interface of its own, so that every provider below works from settings alone, in Auto's cloud hosting and in a self-hosted container. auto-brain is source-available under the Elastic License 2.0. +Inference is the primitive through which a brain reasons: it calls a language model. A spec of inference, which a person calls a reason function, names a model, gives it instructions and a prompt built from what the brain knows, and says whether the answer is free text or a JSON value that must match a JSON Schema. Each execution sends one request to the model and records the answer, the tokens it used and how it finished. This package calls the models through the Vercel AI SDK, behind a small interface of its own, so that every provider below works from settings alone, in Auto's cloud hosting and in a self-hosted container. auto-brain is source-available under the Elastic License 2.0. The first half of this document covers how models are called and configured; the second half, from [The spec document format](#the-spec-document-format), how a spec is written, created and executed. From f5974b18c60da257077ab5ef7a09b5b705f97354 Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 4 Oct 2026 16:59:44 +0100 Subject: [PATCH 5/5] docs(global): give builders the words a user reads CLAUDE.md now says, under What this is, which word a user reads for each primitive: a brain can reason (inference, whose specs are reason functions, each configured by a prompt), interact, compute, recall and predict, and coordinates them through workflows (orchestration); a primitive is a brain function and an execution a run, while code, the API and the architecture keep the primitive names. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 96b284a97..251b9101d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,8 @@ - `primitives/*`: one package per brain primitive (interaction, orchestration, inference, prediction, computation, recollection, dream) - `TODO.md`: setup work that is still outstanding +In text a user reads, a primitive is a brain function and an execution a run: a brain can reason (inference, whose specs are reason functions, each configured by a prompt), interact (interaction), compute (computation), recall (recollection) and predict (prediction), and it coordinates them through workflows (orchestration); code, the API and the architecture keep the primitive names. + ## Commands ```bash