composition
combine workflow steps into complex behaviors
compose
chain steps together
import { compose, model, tap } from "@threaded/ai";
const workflow = compose(
tap((ctx) => console.log("before model call")),
model(),
tap((ctx) => console.log("after model call")),
);
await workflow("hello");
each step receives conversation context and returns updated context
model
call an llm
with specific model
model format: provider/model-name
supported providers:
- openai:
openai/<model-identifier> - anthropic:
anthropic/<model-identifier> - google:
google/<model-identifier>
with system message
const workflow = model({
system: "you are a helpful coding assistant",
});
await workflow("help me write a function");
system message gets prepended to conversation history
dynamic system message
function receives context and returns system message string
scope
create isolated execution contexts
import { compose, model, scope } from "@threaded/ai";
const workflow = compose(
scope(
{
system: "you are a weather assistant",
tools: [weatherTool],
},
model(),
),
);
scope isolates tools, system messages, and streaming handlers
inherit flags
control what gets passed into scope
import { Inherit } from "@threaded/ai";
scope({ inherit: Inherit.Nothing }, model());
scope({ inherit: Inherit.Conversation }, model());
scope({ inherit: Inherit.Tools }, model());
scope({ inherit: Inherit.All }, model());
Inherit.Nothing - empty context, no conversation history or tools
useful for sub-agents that don't need parent context
const subAgent = scope(
{
inherit: Inherit.Nothing,
system: "you are a specialized validator",
tools: [validateTool],
},
model(),
);
Inherit.Conversation - includes conversation history (default)
Inherit.Tools - includes tools from parent scope
Inherit.All - includes both conversation and tools
until condition
run scope until condition is met
import { noToolsCalled } from "@threaded/ai";
scope(
{
tools: [calculator],
until: noToolsCalled(),
},
model(),
);
keeps calling model until no tools are called (agentic loop)
silent mode
run scope without updating parent history
useful for validation or background tasks
when
conditional execution
import { when, model } from "@threaded/ai";
const workflow = compose(
when(
(ctx) => ctx.history.length > 10,
model({ system: "summarize this conversation" }),
),
model(),
);
runs step only if condition returns true
tap
side effects without modifying context
import { tap } from "@threaded/ai";
const workflow = compose(
tap((ctx) => {
console.log(`history length: ${ctx.history.length}`);
}),
model(),
);
useful for logging, metrics, debugging
retry
retry failed steps
import { retry, model } from "@threaded/ai";
const workflow = compose(
retry(
{ times: 3 },
model(),
),
);
retries up to 3 times on failure
combining everything
import { compose, model, scope, when, tap, retry, Inherit, noToolsCalled } from "@threaded/ai";
const workflow = compose(
tap((ctx) => console.log("starting workflow")),
when(
(ctx) => ctx.history.length > 20,
scope(
{
inherit: Inherit.Conversation,
system: "summarize the conversation so far",
silent: true,
},
model(),
),
),
scope(
{
inherit: Inherit.All,
tools: [calculator, weather, search],
until: noToolsCalled(),
},
retry(
{ times: 2 },
model({ model: "openai/gpt-4o" }),
),
),
);
composition lets you build complex agent behaviors from simple primitives
next: tools