Skip to content

Context

The object that crosses every step of a run. Context is the preferred name; AgentContext is the same type, kept as an alias.

ts
import type { Context } from "@thenajs/core";

Shape

ctx                    ← run controls, runtime fields, and your own
 └─ ctx.state          ← the conversation the model actually sees

Any field you write is accepted, typed unknown:

ts
ctx.attempts = ((ctx.attempts as number) ?? 0) + 1;

For what steps genuinely exchange, prefer the typed workflow state (@Workflow({ state })).

Run controls

These belong to the run, not the step. In a parallel block each branch gets its own step context — context() inside a branch resolves to that branch — while these apply to the whole execution.

MemberTypeNotes
runIdstringthe same id in every ExecutionEvent
dataDyour channel. Never goes to the model. Always present — {} when not given
signalAbortSignalpass to your fetch so cancellation reaches inside
usage()BudgetUsageaccumulated usage so far
abort(reason?)voidcancels; the in-flight turn is interrupted
stop()voidends gracefully — later steps skipped, output kept, no throw
onDispose(fn)voidcleanup for the end of the run, reverse order, like defer
meta(data)voidtelemetry onto this step's node; no-op when unobserved
ts
const conn = await pool.acquire();
ctx.onDispose(() => conn.release());

data is typed by you:

ts
type MyRun = { accountId: string };
const app = Thena.create<string, MyRun>(Flow, config);
context<MyRun>().data.accountId; // string, no cast

Use type, not interface extends

An interface extending the run-data shape inherits its index signature, so a misspelled field becomes unknown instead of a compile error.

Runtime fields

FieldType
turnTurnInfo — last turn's summary
outputthe last step's output
budgetBudgetUsage, present when there is a budget
ts
interface TurnInfo {
  calledTool: boolean;
  toolName?: string;
  toolError?: boolean;
  toolCallSource?: "native" | "rescued";
  response: string;
}

ctx.state

Three buckets, each becoming part of the prompt:

BucketTypeReaches the model as
historyMessage[]the messages, in order
memorystring[]a system message at the top
tasksstring[]a note inside the system message
ts
ctx.state.append("memory", "The user prefers short answers");
ctx.state.set("history", ctx.state.history.slice(0, -1));

run({ state }) seeds the memory bucket.

abort() vs stop()

abort(reason)stop()
The runrejects with reasonresolves with the output so far
Later stepsinterruptedskipped
In-flight turninterruptedfinishes

stop() is the same behaviour as a budget in "stop" mode.

context() as a function

The same export is callable and returns the context from wherever you are:

ts
provider: () => new OpenAIProvider({ apiKey: keyFor(context().data) });

Inside a step you get the step's context, with state and turn. Outside one — in a provider factory, which runs at compile time — you get the run's, and touching state throws with an explanation.