How it is built
One model, two kinds of system
The shared model lives in flows-actors. A system is
FlowActorSystem = ActorSystem | FlowSystem (flows-actors.system.shared.definitions.ts), a
discriminated union on kind with FlowActorSystem_Kind.ACTOR and FLOW. Both leaves extend
FlowActorSystem_BaseSchema (flows-actors.system.shared.schemas.ts): ref, displayName,
description, kind, status (FlowActorSystem_Status: draft, active, inactive, deprecated,
archived), visibility (BACKEND_ONLY or FRONTEND) and a config typed by
FlowsActors_Configuration_Schema (flows-actors.configuration.shared.schemas.ts): access
control with a primary scope and an approval requirement, and resilience settings for circuit
breaker, rate limit and bulkhead.
ActorSystemSchema (actors.shared.schemas.ts) is the leaf that owns an actors list. An
Actor has a roleCategory (Actor_Role_Category: coordinator, task planner, operator,
analyzer, reviewer, human in the loop, flow execution), an executionMode
(Actor_ExecutionMode: AGENTIC, FLOW_EXECUTION, HUMAN_INTERACTION) and an agentRef or a
flowRef.
FlowSystem (flows.shared.schemas.ts) owns a graph: triggers (Flow_Trigger_Type: manual,
schedule, webhook, resource event, event, task completion), steps (Flow_Step_Kind: an agent
call, a resource operation, a sub-flow, branch, switch, loop, parallel, delay, retry, memory read
and write, event emit and wait, error catch, compensate and finally, a human-in-the-loop step and
an actor-system call), edges and finishes. It is a hand-written TypeScript type: Flow_Trigger
and Flow_Step have no runtime schema yet, which is why the union is type-only rather than a
z.discriminatedUnion, and why a system is validated with the schema of its kind.
Around the two kinds sit the vocabularies they share: tasks.actors.shared.schemas.ts (the
canonical Actors_Task_Schema with Actor_Task_Topics, actor memory, an execution plan),
flows-actors.io.shared.schemas.ts (FlowsActors_IO_PartKind: text, file, raw data, structured,
image; audio and video are declared and reserved) and the execution envelope
FlowActorSystem_ExecutionSchema with FlowsActors_Status.
Drawn from index.ts:
const todoAssistantActorSystem: ActorSystem = {
ref: 'todo-assistant',
displayName: 'Todo Assistant',
description: 'Ask questions about your todos and lists, grounded in your own data.',
kind: FlowActorSystem_Kind.ACTOR,
status: FlowActorSystem_Status.ACTIVE,
visibility: FlowActorSystem_Visibility.FRONTEND,
config: { timeout: 60000, accessControl: [{ primaryScope: ResourcePrimaryScope.APPLICATION,
requiresApproval: false }], riskLevel: ResourceOperationRiskLevel.LOW },
actors: [{ ref: 'assistant', displayName: 'Assistant',
roleCategory: Actor_Role_Category.OPERATOR,
executionMode: Actor_ExecutionMode.AGENTIC, agentRef: 'todo-assistant.chat.agent' }],
};
What an agent is
FlowsActors_Agent (agents.shared.schemas.ts) is the whole authoring surface for a callable AI
unit. Its config is discriminated by modality on operationKey: an LLM arm
(FlowsActors_Agent_LLMConfiguration, text and structured output), an image arm and a coding arm
(FlowsActors_Agent_CodingConfiguration). Its operationDefinition carries the input and output
schemas of the provider operation. Its promptSpec (FlowsActors_Agent_PromptSpec) is a prompt
authored as structure, not prose: role, persona, skillType, constraints (prohibitions,
highest precedence), instructions, hints, an output specification and an uncertainty policy;
the compiler renders it in each provider’s preferred format. capabilities.tools.functionRefs
names the tools the agent may call.
Three things about model and transport selection are worth stating precisely.
- An agent names
providerRef and model directly. The model catalogue also defines
LLM_Model_Applicability (deep judgement, structured authoring, high-volume mechanical,
long-context analysis, visual judgement) with a fail-closed resolveModelForApplicability
(llm.external-connectors-models.schemas.ts).
Today the embeddings and document-extraction services resolve their sibling applicabilities;
the agent runtime does not, and the dogfood chat agent pins a concrete LLM_Model.
executionProfileRef selects a transport profile declared by the provider. It is optional on the
LLM and image arms and required on the coding arm: a coding agent has no default API path, and
the profile is where its environment gate and permission ceiling live.
- The coding arm is a real agent, declared like any other, reached over the Agent Client Protocol
(JSON-RPC over stdio).
acp-session-runner.backend.utils.ts spawns the agent, negotiates
initialize, opens a session against a confined working directory and drives one turn at a
time (startACPAgentSession, with runACPCodingSession as the single-turn wrapper). The
permission ceiling is enforced by the runner’s answer to every session/request_permission,
never by trusting the agent’s own session mode. The application authors the policy half in
wildo.saas.config.ts: runner host, authentication mode, permitted environments, working
directory policy, permission ceiling.
Tools are FunctionTool values (function-tools.backend.definitions.ts). Two factories cover the
common case: resourceReadTool(type) and resourceWriteTool(type, op, { inputSchema, requiresHumanApproval }). Each rebuilds an ExecutionContext from the caller’s verified
authentication and routes through the ordinary services handler, so the operation’s own role
checks apply and server-set fields cannot be spoofed through tool arguments. The default
functionRef is resource.<type>.<op>. Agent facets and catalogs
(agent-facets.backend.definitions.ts, agent-catalogs.backend.definitions.ts) are reusable
prompt fragments and presets an agent can be composed from through AgentFacetsBackendService.
Drawn from index.ts:
resourceWriteTool(TasksManager_ResourceType.TODOS, CoreResourceOperation.UPDATE, {
requiresHumanApproval: true,
inputSchema: z.object({
id: z.string().min(1).describe('Id of the todo to update. Required.'),
title: z.string().min(1).max(200).optional().describe('New title, if asked.'),
status: z.enum(Todos_Status).optional().describe('New status.'),
}),
}),
The backend runtime
flows-actors holds the runtime.
application module ──registers──▶ FlowsActorsRegistryService
(systems factory map, agents, systems by kind · agents by ref, provider, operation
function tools) function tools by ref
│
WebSocket flows-actors:start ─┐ ▼
A2A message/send, stream ─────┼──▶ ConversationalExecutionBackendService
│ owner scope → persisted variant · server-held history
│ chat actor → agentRef · approval pause and resume
│ │
│ ▼
│ AgentsBackendService.invokeAgentStreaming
│ PromptCompilerBackendService (trust levels)
│ provider client + secret · tools as the caller
│ AgentCallTraceSink (usage, latency, outcome)
│ │
└───◀ emitter: started · message deltas · approval request
· status update · completed · error
FlowsActorsRegistryService (flows-actors-registry.service.ts) is initialised with the
application’s FlowActorSystem_InitializationFactoryMap, its agents and its function tools,
and answers getSystem(ref, kind), getAgentOrThrow(ref) and getFunctionTool(ref).
AgentsBackendService (agents.backend.service.ts) has two entry points. invokeAgent runs one
agent once and awaits the answer; tools are inert on this path, so a one-shot agent must be
handed its data. invokeAgentStreaming forwards real token deltas and is the only path that
resolves and executes functionRefs, in a bounded multi-step loop. Image configurations branch
to an image executor; coding configurations open an ACP session.
PromptCompilerBackendService (prompt-compiler.backend.service.ts) compiles the agent and the
invocation into a provider-shaped prompt, separating context items by PromptIR_TrustLevel
(prompt-ir.shared.schemas.ts: high for system data, medium for owned documents and prior
artifacts, low for user input and external sources). ContextCurationBackendService runs before
it and is a basic filter today.
ConversationalExecutionBackendService (conversational-execution.backend.service.ts) is the
turn runtime: it resolves the owner scope to a persisted variant, loads the server-held
transcript (no client-supplied history is trusted), finds the system’s agentic actor, streams the
turn through a transport-agnostic emitter, and persists every message. respondToApproval
resumes a paused turn; readExecutionStatus answers a poll.
- Approval is a status, not a side channel. An approval-gated tool moves the execution to
FlowsActors_Status.AWAITING_APPROVAL, the resume state is serialised into the record’s
pausedApproval field (FlowActorExecution_PausedApprovalSchema), and a decision resumes the
same execution; a denial resumes it too, so the agent can explain rather than leave a dead
thread. Machine principals are refused at the gate, because no named person can answer it.
- The WebSocket channel is
CoreWebSocketEvent.FLOWS_ACTORS_START, STARTED, MESSAGE,
APPROVAL_REQUEST, APPROVAL_RESPONSE, STATUS_UPDATE, COMPLETED and ERROR
(websocket.shared.schemas.ts), handled in
websocket.service.ts with a per-event rate limit; flows-actors.controller.ts serves
/flows-actors/frontend-info and /flows-actors/history over HTTP.
FlowsBackendService.executeFlow and ActorsSystemBackendService.executeActor validate the
system and its status, then mark the execution completed without executing steps or actor
behaviour. They are placeholders; the conversational path above is the runtime that runs.
Executions and tasks as resources
A conversation and a unit of work are ordinary resources, stamped out in three owner scopes by
declarePolymorphicSchemas. flowActorExecutionsPolymorphicDeclaration
(flows-actors-execution.shared.schemas.ts) yields userFlowActorExecutions,
organizationFlowActorExecutions and applicationFlowActorExecutions, each carrying
executionId, systemRef, kind, a status, and a messages[] log with
FlowsActors_ConversationRole. Its configuration
(flows-actors-execution.shared.resources-config.schemas.ts) makes read and list the owner’s
chat-history surface, keeps create and update internal to the runtime, and gives delete a role
floor distinct from read. flowActorTasksPolymorphicDeclaration
(flows-actors-task.shared.schemas.ts, configured in
flows-actors-task.shared.resources-config.schemas.ts) is the addressable, pollable, cancelable
work record, derived from Actors_Task_Schema and linked to its conversation by a soft field, so
deleting a conversation never deletes its work. Both families carry the engine’s privacy
decorations, and both have framework-owned specifications
(resources). The heavy orchestration state
(plan, walkthrough, standaloneTasks) is not persisted today.
The frontend execution runtime
The second runtime is execution. An execution view is a rich
interaction surface over persisted data, with frontend-only runtime state. It is declared on the
resource it belongs to, under frontendConfiguration.executionViews, with the
resourceExecutionView(Component, config) factory (execution-view.schemas.ts): an anchor
(the business subject), participations sourced from the anchor, a parent requirement or a
relationship projection, surfaceInOperations naming the operation hosts it appears in, and
view-local labels. ExecutionViewDefinition is RESOURCE-scoped by construction. The host is
ExecutionView.tsx; the renderer consumes one hook, useExecution(), whose ExecutionValue
exposes the anchor, the participations, actions that run resource operations with per-action
status, loading, dirty (fed by useExecutionDirtyBridge from any embedded form), errors,
embeddedContent and a local pane. The dogfood surface is TodoListFlowExecutionView, a graph
of a todo list, its todos, tasks and links, composed through ordinary operations.
Drawn from todo-lists.ui-behavior.tsx:
executionViews: {
[TODO_LIST_FLOW_EXECUTION_VIEW_REF]: resourceExecutionView(TodoListFlowExecutionView, {
labels: todoListFlowExecutionViewLabels,
surfaceInOperations: [Op.READ],
anchor: { bindingKey: R.TODO_LISTS,
source: ExecutionViewAnchorBindingSource.PARENT_RESOURCE_REQUIREMENT },
participations: {
anchor: { source: ExecutionParticipationSource.ANCHOR },
todoList: { source: ExecutionParticipationSource.PARENT_RESOURCE_REQUIREMENT,
bindingKey: R.TODO_LISTS },
todos: { source: ExecutionParticipationSource.RELATIONSHIP_PROJECTION,
fromParticipationRef: 'todoList', targetResourceType: R.TODOS },
},
}),
},
The agent and chat interface
A frontend module contributes flowsActorsViews: a FlowsActorsViewDefinition
(flows-actors-view.schemas.ts) names one or more
systemRefs, a layoutPreset and whether it is addressable at /flows-actors/:ref. The engine
host FlowsActorsView.tsx renders a streaming chat over the system or systems the definition
names; FlowsActorsContext.tsx subscribes to the WebSocket events and merges streamed deltas into
one message by messageId. A kind-agnostic node and edge projection of the system
(flows-actors.frontend.shared.schemas.ts, extracted by
flows-actors-frontend-extractor.utils.ts) is what lets an actor system and a flow system be
described through one shape, and the graph canvas presets that draw it are used by the low-level
actor and flow-execution slots. The addressable host does not itself compose a graph beside
the chat today.
The screen pieces are the ai-elements preset family
(ai-elements): chat (conversation, message, prompt
input, sources, suggestion), trace (reasoning, chain of thought, plan, checkpoint, inline
citation), decision (approval, decision request), execution (task, tool, queue, confirmation),
graph (canvas, node, edge, controls, panel, toolbar), artifacts (artifact, code block, image, web
preview), meta (agent, context, model selector, open in chat) and the execution stream. Beneath
them the framework’s own low-level slots (flows-actors:
actor, agent config, approval, artifact browser, execution plan, flow execution, flow node, system
catalog, task detail, task memory) each carry a specification under
flows-actors.
Drawn from app-level-views.definitions.module.frontend.ts:
const flowsActorsViewsConfig: FlowsActorsViewDefinition[] = [{
ref: 'todo-assistant-view',
scope: FrontendView_ScopeMode.APPLICATION,
isAddressable: true,
operationLike: CoreResourceOperation.READ,
primaryScope: ResourcePrimaryScope.ORGANIZATIONS,
systemRefs: ['todo-assistant'],
layoutPreset: 'Default',
}];
Deliberations and expert panels
For a judgement call, the framework can convene a panel rather than ask one model once. The
vocabulary is deliberation.shared.schemas.ts: an ExpertWorkDomain an expert is narrow on
(visual brand mark, marketing copy, UI design coherence, brand strategy, information
architecture), a RuntimeExpertProfile, an EvaluationRubric whose criteria travel as data in
the judge prompt, a fixed RubricVerdict keyed by criterion reference, DeliberationRound,
DeliberationSynthesis and a DeliberationRecord with a DeliberationStatus (running,
converged, budget exhausted, failed). Weighted aggregation is deterministic code
(aggregateWeightedScore), and a verdict citing a criterion the rubric does not contain is
rejected. The engine round loop is ActorsSystemBackendService.executeDeliberation(plan), which
returns the record and never writes disk; the build-time companion supplies the experts, the
candidate producers and the persistence (deliberations
and src/expert-personas/, with the committed charter contracts in
expert-personas). Deliberations are engine and companion
machinery, not a surface a generated application exposes to its users today.
Traceability
Every model call produces an AgentCallTrace (agent-traceability.shared.schemas.ts): outcome,
model, token usage, latency, and, at AgentTraceabilityLevel.DEBUG_VERBOSE, the resolved prompt
and raw output. AgentCallTraceKind separates a generation from an embedding, whose vectors never
reach a log or an analytics vendor at any level. Sinks implement AgentCallTraceSink and are
best-effort by contract: a sink failure never fails the call. The engine ships a structured-log
sink (agent-call-trace-sink.backend.ts) and a lazy sink that resolves the provider selected for
EngineCapability.LLM_OBSERVABILITY on first use
(llm-observability-agent-call-trace-sink.backend.ts).
The doors for outside agents
Two protocols expose an application, on two different planes.
- Agent2Agent exposes conversations: the same actor systems, through
a2a. a2a.controller.ts serves the public agent
card at /a2a/.well-known/agent-card.json and a JSON-RPC endpoint for message/send,
message/stream (token deltas as server-sent events), tasks/get, tasks/cancel,
tasks/resubscribe and the push-notification configuration methods; a named agent mounts at
/a2a/:instanceRef with its own card. Work becomes a flowActorTasks row when the client asks
for an immediate return or when an approval gate suspends the turn, and services cover the task
lifecycle, cancellation, push delivery and a per-tenant token budget. A dialect codec
(a2a-dialect.backend.schemas.ts) lets one handler set answer two protocol revisions.
- The Model Context Protocol exposes operations, not agents:
mcp
turns every resource operation that declares mcp: { exposed, description } into a tool,
builds its JSON schema, and dispatches a call back through the ordinary services handler. An
agent’s capabilities.tools.mcpRefs is declared in the model and consumed by no runtime path
today; the tools an agent can call are its functionRefs.
Both doors are driven against the running dogfood stack by the a2a-* and mcp-* scenarios under
e2e, including hostile probes of tenant isolation and approval tampering.
The dogfood application
Wonder Todos declares one agent, one actor system and five tools in
index.ts: two
read tools, a create tool, an approval-gated update tool and a hand-written knowledge-base tool
that calls the application’s own custom operation. They are registered through
moduleBackend_FlowsActorsRegistry (flowActorSystemsFactoryMap, agents, functionTools).
The chat screen is the todo-assistant-view above; the execution view is the todo-list graph. The
engine also ships its own systems, for example the market-generation agent
(market-generation.actors-system.backend.definitions.ts) that an application may override under
the same ref. Wonder CRM declares no actor system.
Boundaries and known limits
FlowSystem is a type without a runtime schema, and FlowsBackendService.executeFlow executes
no steps. The flow kind is modelled end to end and does not run; the conversational path
refuses a flow system with a typed not-implemented error.
ActorsSystemBackendService.executeActor is likewise a placeholder; the runtime that runs is
the conversational execution of an actor system’s agentic actor.
- One-shot
invokeAgent ignores an agent’s tools; only the streaming path executes them.
- Streaming covers text-producing operations over the API transport; structured, image and
coding invocations do not stream.
- Approval pauses are user-delegated only; a machine principal is refused at the gate.
- The applicability-class model selection exists in the catalogue and is used by the embeddings
and document-extraction services, not by the agent runtime.
mcpRefs on an agent is declarative; no runtime resolves it.
- Context curation before prompt compilation is a basic filter, not retrieval reranking.
- Audio and video modalities are declared and reserved; the executor fails closed on them.
- The coding lane runs as a backend subprocess and, in the dogfood profile, only in a development
environment; multi-turn continuation on a persistent session is implemented but has not yet
been driven against a real agent adapter.
- The persisted execution record carries the transcript and status, not the plan, walkthrough or
standalone task state of the orchestration model.
- Execution views are resource-scoped only; their runtime state is frontend-only and is not
persisted.
- Deliberations run in the engine and the build-time companion; no generated application exposes
a panel to its users.