Skip to Content
ReferenceDefinitions

Definitions

Two constructors are what a project actually builds. Agents, Capabilities, and Workflows cover why each argument exists and how they compose; this page is the exhaustive one — every field and every method, with its type and default.

Agent

from agentdeck import Agent
ArgumentTypeDefaultPurpose
basetype[AgentDeclaration] | NoneNonea shareable set of defaults (see below); keyword-only
namestrbase.name, or the base class nameregistry key and SDK agent name
instructionsstr | Callablebase.instructionsthe system prompt, or a callable computing it per turn
handoff_descriptionstr | Nonebase.handoff_descriptionhow this agent describes itself to a peer deciding whether to hand off
modelstr | Nonebase.modelper-agent override; None uses the OPENAI_MODEL setting
model_settingsMapping[str, Any]base.model_settingsforwarded to agents.ModelSettings(**model_settings)
toolsSequence[Any]base.toolsplain functions (compiled at build()), already-built SDK tool objects, or a Workflow to expose as a tool
handoffsSequence[Any]base.handoffspeer agents this one can transfer to — a registry name (str), a built Agent, or a Handoff
output_typetype | AgentOutputSchemaBase | Nonebase.output_typea Pydantic model (or AgentOutputSchema) for structured output; None for free text
hooksAgentHooks | Nonebase.hooksSDK lifecycle callbacks; a method may declare a Context first instead of the SDK’s wrapper
skillsSequence[str]base.skillsnames resolved against the owning Deck’s skills= roots
mcpSequence[str]base.mcpnames resolved against the owning Deck’s mcp= file

Agent is immutable once constructed (AttributeError on assignment) — a Deck compiles it once at build(), and nothing about the compiled result can drift out from under a mutation afterwards. A value explicitly passed here always wins over base’s, including an explicit empty value: omission, not falsiness, is what defers to the base. base= is keyword-only by construction, so Agent(SomeDeclaration, name=...) is a TypeError rather than a silently accepted positional base.

A tool is a plain function. build() compiles it: the name comes from the function, the description from its docstring, and the schema the model sees from its annotated parameters.

def find_slots(day: str) -> str: """Find free appointment slots on a given day.""" ...

One parameter is not shown to the model. A parameter annotated agentdeck.Context[T] receives the value passed to deck.run(..., context=...) — see Deck — and is absent from the tool schema entirely, whatever it is named:

from agentdeck import Context def find_slots(day: str, environment: Context[Calendar]) -> str: """Find free appointment slots on a given day.""" return environment.data.lookup(day)

Declaring two such parameters is a build() error. So is a callable whose signature cannot be read — a decorator that dropped functools.wraps is the usual cause — since there is then no honest schema to show the model and no way to tell “declares no context” from “could not look”. And so is a T the owning deck’s Deck(context=...) cannot satisfy, when it declared one: a ContextTypeError naming both types, at build(). See Deck for what counts as compatible and what is deferred to invocation instead.

An already-built Agents SDK tool object (@function_tool, WebSearchTool(), …) is still accepted and passed straight through. It is engine-native: agentdeck introspects nothing about it, it gets no portability guarantee, and it cannot receive a Context.

The same annotation works at the other three injection sites, with the same rules. instructions= may be a callable taking at most one Context[T] and nothing else, in which case only the string it returns reaches the model. A hooks= method may name a Context[T] where the SDK’s own wrapper would go — it has to be the first parameter, since that is where the SDK passes its context — and a hooks object declaring none is passed through untouched. A workflow node takes one alongside its state, and the two stay strictly separate: state is the workflow’s mutable data, ctx.data is the environment the run was handed.

MethodSignatureBehavior
build()() -> agents.Agentcompiles a fresh SDK agent from the fields above — no caching, and no catalog: handoffs by name and tools=[a_workflow] need Deck.build()’s two-pass compile instead
run()(message=None, **runner_options) -> agents.RunResultone-shot headless run

Agent.run() is not Deck.run(). It calls the SDK’s own Runner.run directly — no Runtime, no event log, and it returns the SDK’s RunResult, not a TurnResult. Reach for it inside a script or a test that only needs one headless call and does not care about the log; reach for Deck.run() (see Deck) for anything that should be recorded and readable back afterwards.

It also passes no context. There is no context= on the headless runner, so a tool declaring one refuses when the model calls it, saying the run “carries NoneType rather than an AgentDeck run context”. A context-declaring agent is a Deck.run() agent.

skills=/mcp= names unresolved against the owning Deck’s catalog fail Deck.build() naming the offending agent and the names it declared that nothing provides.

AgentDeclaration

from agentdeck.authoring import AgentDeclaration

A reusable set of defaults: subclass it, set class attributes, and pass the subclass as base= to as many Agent(...) calls as need it.

class BookingBase(AgentDeclaration): instructions = "You handle bookings." model_settings = {"temperature": 0.2} booking = Agent(base=BookingBase, name="booking", tools=[find_slots]) # find_slots: see above support = Agent(base=BookingBase, name="support", instructions="You handle support tickets.")

AgentDeclaration is never constructed or run directly — every attribute it carries is a ClassVar, and it exists only to be named as Agent(base=...).

Workflow

from agentdeck import Workflow
ArgumentTypeDefaultPurpose
basetype[WorkflowDeclaration] | NoneNonea shareable graph-building declaration (see below); keyword-only
namestrbase.name, or the base class nameregistry key
descriptionstrbase.descriptiontool description when exposed via as_tool()
statetype (a Pydantic model)base.statethe graph’s state schema — required one way or the other
durableboolbase.durable (False)compiles with a checkpointer (AGENTDECK_CHECKPOINT_*) when True, so a run can resume by thread_id after the process dies
graph() -> StateGraphbase.build_grapha bare graph-building factory, in place of overriding build_graph() on a base=

Exactly one of base= (a WorkflowDeclaration subclass overriding build_graph()) or graph= (a bare () -> StateGraph factory) supplies the graph — the same override-on-construction shape Agent(base=...) has, so the two constructors read as one pattern. Workflow is immutable once constructed, for the same reason Agent is.

MethodSignatureBehavior
build_graph()() -> StateGraphthe graph, uncompiled — delegates to graph=/base.build_graph()
build()() -> CompiledStateGraphcompiles the graph, with a checkpointer iff durable
run()(state=None, *, thread_id=None, **runner_options) -> Anyruns the graph once, direct-call (no event log); returns the final state or an InterruptResult
run_stream()(state=None, *, thread_id=None, **runner_options) -> AsyncIteratornode_update/custom events, then done — or an InterruptResult in its place
resume()(thread_id, value, **runner_options) -> Anycontinues a paused run; interrupt() returns value
pending()() -> list[InterruptResult]every thread of this workflow currently paused on an interrupt
as_tool()(*, name=None, description=None, output_keys=None, defaults=None, strict_json_schema=False) -> FunctionToolexposes the workflow as a tool
node_names()() -> list[str]node names on the graph, excluding START/END

resume() and a paused interrupt() both raise ConfigError when durable is False — there is no checkpointer to resume from. thread_id is required when durable is True (a ValueError otherwise) and ignored when it is not.

The same asymmetry as Agent.run() applies here. Calling some_workflow.run(...) directly drives the compiled graph and writes nothing to the event log; Deck.run() and Deck.answer() are what the Runtime actually plays, and record the run — see Deck. Deck.stream() plays a workflow the same way, so nothing on Deck calls run_stream() — it stays useful for a script or a test that wants the raw graph stream with no log.

The asymmetry goes further for a node that declares a Context[T]. The bridge that puts a context into a node is installed when a Deck builds its catalog, so run(), resume() and as_tool() never install it: such a node reached by one of those paths dies with LangGraph’s own TypeError: <node>() missing 1 required positional argument, not with agentdeck’s message. Loud and immediate, but it is the raw engine error — the price of leaving a log-free convenience alone.

as_tool() requires state to be a Pydantic model (TypeError otherwise, since the tool’s JSON schema comes from state.model_json_schema()), and requires durable=False — a tool call supplies no thread_id, which a durable workflow needs for its checkpoint, so Deck.build() raises ConfigError naming the agent and the workflow. A durable workflow is a root invocable: reach it through deck.run(...) with a session, not as an agent’s ability. defaults pins specific state fields to fixed values regardless of what the model passes, and strips those fields from the schema the model sees; output_keys filters the final state down to a subset of channels in the tool’s return value. Both are optional — the default is the whole schema, the whole final state.

WorkflowDeclaration

from agentdeck.authoring import WorkflowDeclaration

Override state and build_graph():

class Booking(WorkflowDeclaration): state = BookingState durable = True @classmethod def build_graph(cls): g = StateGraph(cls.state) ... return g booking_flow = Workflow(base=Booking, name="Booking")

Never constructed or run directly — it exists only to be named as Workflow(base=...).

Last updated on