Nodes
Nodes are instances of components; actions and flows.
Actions in Trifrom consist of a Python module and metadata, such as requirements and a README. By chaining actions together, either in sequence or in parallel, you create a workflow, or a flow for short.
Whether to work with an action or a flow depends on the complexity of the task at hand.
The core concept of Triform is that your manual work should compound. All code you write and all flows you define should thus be reusable, and are therefore represented as components. Actions and flows, collectively referred to as components, represent arbitrary units of computation which can be used to create ordinary workflows, tools or agents.
When you instantiate a component by placing it into a flow or a project (anywhere on a canvas, really), you create a node.
To achieve optimal performance and to facilitate introspection & granular re-use, we recommend limiting the scope of your actions to one or a few Python functions, leveraging flows for more complex scenarios.
Components are versioned in your component library, and can be used and re-used interchangeably in the platform, for example:
An agent can be either an action or a flow
Flows can contain actions and/or other flows
Flows and actions can be exposed with e.g. API endpoints or as tools in an MCP toolbox utilized by your agents (or both!)
Actions
Actions begin with an entrypoint. Similarly to a main function called from a main guard, the function decorated with @triform.entrypoint
is what the platform executes. Unlike a typical main function in Python, the inputs and outputs of an action have to be explicitly typed, with JSON serializable types, so that the payload(s) can be passed between actions or as a response from an API
Serialization and deserialization is implicit, utilizing a pydantic.TypeAdapter
. Pydantic comes pre-installed and does not need to be included in the requirements.txt.
For example, let’s say we create an action which takes a Person
object representing a car owner, along with a Car
object, and creates a Report
summarizing the car ownership:
from pydantic import BaseModel
class Person(BaseModel):
name: String
age: int
class Car(BaseModel):
model: String
class Report(BaseModel):
summary: String
@triform.entrypoint
def summarize(owner: Person, vehicle: Car) -> Report:
return Report(summary=f”{owner.name} is {owner.age} years old and drives a {vehicle.model}”)
Actions support synchronous and asynchronous value functions and generators, including recursive functions.
Streaming
The streaming
flag of an action/v1
denotes whether the result of an action should be streamed using Server Sent Events (SSE). The ServerSentEvent
class is available to all actions, and can be used in generators to stream responses:
import asyncio
import json
from typing import AsyncGenerator
@triform.entrypoint
async def stream() -> AsyncGenerator[ServerSentEvent, None]:
message: String = "Hello world"
for char in message:
yield ServerSentEvent(event="delta", data=json.dumps({"v": char}))
await asyncio.sleep(0.5)
Flows
Flows are directed acyclic graphs. Each node in the graph contains a reference to a component, either an action or a flow. The node has an array of inputs, referencing other nodes, forming the edges of the graph.
Inputs to a component in a flow are ordered as specified in the flow/v1
resource (link to API reference), this must be reflected in the order of the arguments in the action’s
In a simple flow with two actions, -> A -> B
, A
has specified its parent
input as its input, in other words, the input of the flow, given by the trigger executing the flow (e.g. input provided by the API consumer in the event of an API endpoint trigger). B
will begin executing only when A
has successfully completed executing and returned a result, which is fed to B
as its input.
The flows are distributed, making it possible to scale heavier workloads across multiple servers, even if they are part of a single flow.
Failed actions are automatically retried. Flows are also durable, meaning that their intermediate events and results are persisted and can be re-played on failure. This has two key benefits:
Flows potentially taking days or even weeks, such as those requiring a human in the loop, will continue running even in the event of a server-side failure or restart.
Traces of executions containing timestamped events can be retrieved within the retention period, the platform also supports live traces with events streamed during the execution.
Limitations
Each action is limited to 0.5 CPU cores and 1 GiB of RAM
Flows are limited to 5 concurrently executing actions
The retention period for traces is limited to 30 days
Nesting depth in flows is limited to 10
Retries on recoverable failures is limited to 1
GPU compute will be considered for paying customers upon request