Skip to main content
Wildo.ai Coming soon

Foundations

Flows and actors

Flows and actors is the part of Wildo that runs AI agents and multi-step work inside an application, and shows that work to the people using it. An application declares an agent (which model answers, what it is for, which records it may read or change), groups agents into a system, and the engine does the rest: it talks to the model vendor, streams the answer into a chat screen, persists the conversation as an ordinary record, pauses when a person must approve an action, and opens the same agent to outside agents over standard protocols. The leverage is that the agent reaches a record through the same operation, the same role checks and the same tenant isolation as a web request, so giving an application an assistant does not open a second door into its data.

The idea

You declare, once, a FlowsActors_Agent (provider, model, prompt as structure, tools by reference) and a FlowActorSystem that binds actors to agents. Everything else is derived: the provider client and its secret, the provider-shaped prompt, the streaming channel, the persisted conversation in three owner scopes, the approval pause and its resume, the tracing of every model call, the chat screen, and the A2A agent card that lets another organisation’s agent talk to yours. A tool is a resource operation the application already has, run as the caller, so authorisation is never re-implemented for the agent. The same word, execution, also names a second, separate runtime on the frontend: an execution view is a rich screen that choreographs ordinary resource operations with frontend-only state, declared on the resource it belongs to. This document covers both, and says which parts are type-only today.

What you get for free

  • Provider client construction, secret resolution and rate limiting per provider, from a providerRef on the agent.
  • A provider-shaped system prompt compiled from a structured promptSpec, with trust-level separation of context.
  • Token streaming to the browser over the application’s existing WebSocket, with rate limiting per event.
  • A persisted transcript per conversation, in the caller’s own scope, with read, list and delete as ordinary operations and the engine’s privacy treatment applied.
  • Tool execution as the caller: the operation’s role checks, tenant scope and server-set fields hold without a line of agent-specific authorisation.
  • A human-in-the-loop pause that survives the turn, with approve and deny both resuming.
  • A streaming chat screen, addressable at /flows-actors/:ref, from one view declaration.
  • A trace of every model call, and an analytics sink when an observability provider is selected.
  • An A2A agent card and task plane, and MCP tools over exposed operations, from the same declarations.
  • On the frontend runtime: participations hydrated through ordinary operations, per-action status, dirty and error aggregation, and a local pane, from one useExecution() call.

Where you plug in

  • flowsActors on a backend module: flowActorSystemsFactoryMap, agents, functionTools.
  • FlowsActors_Agent.promptSpec for behaviour; capabilities.tools.functionRefs for reach.
  • resourceReadTool(type), resourceWriteTool(type, op, { inputSchema, requiresHumanApproval }), or a hand-written FunctionTool for anything that is not a record set.
  • executionProfileRef, and the execution profiles under the provider’s Agent Client Protocol transport in wildo.saas.config.ts, for a coding agent.
  • flowsActorsViews on a frontend module: FlowsActorsViewDefinition with systemRefs.
  • frontendConfiguration.executionViews on a resource: resourceExecutionView(Component, ...) with anchor, participations and surfaceInOperations.
  • mcp: { exposed, description } on a resource operation, to publish it as a tool.
  • AgentCallTraceSink, to route traces somewhere the engine does not.
  • AgentTraceabilityPolicy.level, to decide how much of each call is recorded.
For engineers

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.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.