Structured agent input
Agent.run(), Agent.stream(), and OpenMultiAgent.runAgent() accept either a
string or a complete LLMMessage[]. The string form is unchanged shorthand for
one user text message. Use the message form when the application owns prior
conversation turns or needs content blocks such as images:
import { OpenMultiAgent, type LLMMessage,} from '@open-multi-agent/core'
const messages: LLMMessage[] = [ { role: 'user', content: [{ type: 'text', text: 'Keep answers concise.' }] }, { role: 'assistant', content: [{ type: 'text', text: 'Understood.' }] }, { role: 'user', content: [ { type: 'text', text: 'What is shown here?' }, { type: 'image', source: { type: 'base64', media_type: 'image/png', data: imageBytes.toString('base64'), }, }, ], },]
const result = await new OpenMultiAgent().runAgent( { name: 'vision', model: 'claude-sonnet-4-6' }, messages,)The selected model/provider must support every supplied content block. OMA does not infer a vision-capability flag or silently remove unsupported blocks; provider errors follow the normal agent failure path.
API boundaries
Section titled “API boundaries”| API | String form | Structured form | Conversation behavior |
|---|---|---|---|
Agent.run(input) | One user text message | Complete readonly LLMMessage[] | Fresh; does not read or update persistent history |
Agent.stream(input) | One user text message | Complete readonly LLMMessage[] | Fresh; same input semantics as run() |
OpenMultiAgent.runAgent(config, input) | One user text message | Complete readonly LLMMessage[] | Fresh one-off Agent with orchestration, tracing, progress, budgets, and evaluation |
Agent.prompt(input) | One user text message | One user turn as readonly ContentBlock[] | Appends that turn and the response to persistent history |
AgentConfig.history restores earlier persistent turns for prompt(). Passing a
content-block list to prompt() does not replace that history and cannot insert
assistant turns; use run(messages) for a caller-owned complete conversation.
runTeam() goals and runTasks() task descriptions remain text-only.
Copying and validation
Section titled “Copying and validation”Structured inputs are runtime-validated with the same LLMMessage shape guard
used at adapter boundaries, then defensively deep-copied before hooks, runners,
progress callbacks, evaluation, or persistent history retain them. Mutating the
caller’s arrays, content blocks, image source, or tool input after calling an API
does not change that run. Agent.getHistory() also returns a deep copy.
Invalid message/content shapes or data that cannot be cloned throw
InvalidMessageError before beforeRun, provider/backend execution, progress,
or online evaluation. Invalid prompt() input is not appended to history.
For stream(), this validation happens when stream() is called, before the
returned iterator starts.
beforeRun semantics
Section titled “beforeRun semantics”beforeRun receives both views of the effective input:
beforeRun(ctx) { return { ...ctx, messages: ctx.messages, // complete defensive message copy prompt: ctx.prompt, // text blocks from the latest user message }}ctx.prompt remains the backwards-compatible concatenation of text blocks from
the latest user message. An image-only turn therefore has an empty prompt but
is fully available in ctx.messages.
Returning messages replaces the complete input. If the hook also changes
prompt, OMA applies the message replacement first, then replaces the latest
user message’s text blocks with one text block. Non-text blocks keep their
relative order. Hook inputs and execution messages are copies, so a hook rewrite
does not mutate caller-owned data or the original user turn stored by
Agent.prompt().
Progress and online evaluation
Section titled “Progress and online evaluation”String runAgent() calls retain the existing agent_start payload
{ prompt: string } and string evaluation input. Structured calls emit
{ messages: LLMMessage[] } and submit a separate message copy to online
evaluation. Progress callback mutations cannot affect execution or evaluation.
The evaluator’s existing storePayloads policy still applies: none omits the
content, while redacted or full serializes a bounded payload according to the
documented privacy contract.
External process and ACP backends
Section titled “External process and ACP backends”Process and ACP backends expose text prompt transports, not OMA’s structured
message protocol. Supplying LLMMessage[] to run() / stream() / runAgent(),
or ContentBlock[] to prompt(), throws InvalidMessageError before a process
is spawned or an ACP session is opened. This fail-fast boundary prevents images
or caller-owned history from being silently discarded. Pass a string instead.
For the same reason, an external agent’s beforeRun may rewrite prompt but may
not change messages. Existing string execution, including the process backend’s
fresh-per-run behavior and ACP’s protocol session behavior, is unchanged.
AgentConfig.history does not seed either external transport; it restores
messages only for LLM-backed prompt() conversations.