Skip to content

Threads & runs

A thread is a durable conversation (Thread -> Turn -> Item). The thread manager starts, resumes, and lists threads; each call returns an active thread handle you run turns against.

Threads

ts
const thread = await dotcraft.threads.start({ userId: "me" });
const resumed = await dotcraft.threads.resume(threadId);
const threads = await dotcraft.threads.list({ userId: "me" });

// Reuse an active or paused thread for an identity, otherwise create one:
const reused = await dotcraft.threads.getOrCreate({ userId: "me" });
csharp
var thread = await client.Threads.StartAsync(
    new DotCraftThreadStartRequest(new SessionIdentity("my-app", Environment.UserName)));
var resumed = await client.Threads.ResumeAsync(new DotCraftThreadResumeRequest(threadId));
var threads = await client.Threads.ListAsync();
python
thread = await dotcraft.threads.start(user_id="me")
resumed = await dotcraft.threads.resume(thread_id)
threads = await dotcraft.threads.list(user_id="me")

# Reuse an active or paused thread for an identity, otherwise create one:
reused = await dotcraft.threads.get_or_create(user_id="me")

The thread handle also exposes subscribe / unsubscribe, setMode, archive, delete, enqueue, and interrupt.

Running turns

run waits for the terminal turn and returns the merged reply. runStreamed yields normalized events as they arrive. Both accept the same input — a string, an array of input parts, or { input, sender }.

ts
// Buffered
const result = await thread.run("Run the tests and summarize failures.");
console.log(result.text);

// Streamed
for await (const event of thread.runStreamed("Now fix them.")) {
  if (event.type === "agent_message_delta") process.stdout.write(event.delta ?? "");
}
csharp
// Buffered
var result = await thread.RunAsync("Run the tests and summarize failures.");
Console.WriteLine(result.Text);

// Streamed
await foreach (var e in thread.RunStreamedAsync("Now fix them."))
{
    if (e.Type == DotCraftRunEventTypes.AgentMessageDelta)
        Console.Write(e.Params.GetProperty("delta").GetString());
}
python
# Buffered
result = await thread.run("Run the tests and summarize failures.")
print(result.text)

# Streamed
async for event in thread.run_streamed("Now fix them."):
    if event.type == "agent_message_delta":
        print(event.params["delta"], end="", flush=True)

Run options

OptionMeaning
senderPer-turn sender context (useful in group chats).
enqueueIfBusy / enqueue_if_busyWhen a turn is already running, enqueue the input instead of raising.
abort / cancellationCancelling after the turn starts interrupts the running turn.
ts
const result = await thread.run("Later: deploy to staging.", { enqueueIfBusy: true });
csharp
var result = await thread.RunAsync(
    "Later: deploy to staging.",
    new RunOptions { EnqueueIfBusy = true });
python
result = await thread.run("Later: deploy to staging.", enqueue_if_busy=True)

The event model

runStreamed emits normalized events. The type is the same string in every SDK:

typeWire source
turn_startedturn/started
item_starteditem/started
agent_message_deltaitem/agentMessage/delta
reasoning_deltaitem/reasoning/delta
tool_arguments_deltaitem/toolCall/argumentsDelta
item_completeditem/completed
approval_resolveditem/approval/resolved
usage_deltaitem/usage/delta
plan_updated / subagent_progress / system_eventplan/updated / subagent/progress / system/event
completed / failed / cancelledturn/completed / turn/failed / turn/cancelled
rawAny subscribed notification not otherwise normalized

Every event keeps the original notification on raw, so nothing is lost. The buffered run reuses the same stream and merges agent-message deltas with the final snapshot, so result.text is never duplicated.

Errors

A failed or cancelled turn raises a typed error from run (and surfaces as a failed / cancelled event in runStreamed):

ConditionTypeScript.NETPython
Agent execution failedTurnFailedErrorTurnFailedErrorTurnFailedError
Turn cancelledTurnCancelledErrorTurnCancelledErrorTurnCancelledError
A turn was already runningTurnInProgressErrorTurnInProgressErrorTurnInProgressError

Pass enqueueIfBusy to turn the last case into an enqueue instead of an error.

See also

Apache License 2.0