STATEK Agents
A STATEK agent defines the Python workspace an LLM is allowed to use. It is not a workflow graph node. It is the role, instructions, tools, metadata, private context, and startup behavior for jobs that run Python durably.
When a job starts, the working agent may already see variables such as:
user
message
calendar
today
timestampThose names can be supplied directly by application code, prepared by warmup code, or created by a dispatcher or coordinator that picked work from a queue. The agent's job is then simple: write Python against the objects and functions available in that workspace.
In production applications, system prompts and model metadata are usually loaded from .md prompt definition files. This page uses raw inline examples first so the core mechanics are visible without introducing file loading. See Prompt Definitions for the full file format.
What an agent defines
An Agent carries the configuration that shapes a job's Python workspace:
role: the agent's role name_system_prompt: instructions for how the agent should behave_tools: Python callables exposed to the model_internal_tools: Python callables available in execution but hidden from model-facing tool listings_metadata: model and provider configuration, includingMODEL_description: optional short capability text for discovery or routingcontext: agent-private runtime objects, adapters, functions, and types
The important distinction is that tools and context are Python, but the job workspace is durable. If the agent keeps a tool result in a job-local variable for later steps, that value must be something dbzero can persist, or a stable handle to something durable.
Good durable values include strings, numbers, booleans, file paths, URLs, ids, enums, dbzero-backed objects, and domain objects that dbzero can persist. Standard Python collections such as list, dict, set, and tuple are fine when they are stored on dbzero-managed objects; dbzero automatically stores them as managed persistent collections behind the scenes.
Check the dbzero collection docs when writing or reviewing STATEK examples that need specialized collection behavior, such as indexed or range-query data.
Runtime-only objects such as open sockets, SDK clients, file handles, database sessions, locks, generators, or process-local callbacks should stay in agent context, application services, or hidden/internal tools. The job should store a durable id, path, receipt, summary, or dbzero object instead.
For example:
meeting = calendar.reserve_room(room="A12") # durable if meeting is dbzero-backed
receipt_id = send_message(user, text).id # store the durable handleAvoid designing tools that return transient client objects for the agent to keep in job locals. If a tool talks to an external service, return the durable result the next Python step needs.
Minimal inline agent
This is a compact teaching example. It shows the shape of an agent definition, not the full application setup.
from statek import tool
from statek.agents.agent import Agent
from statek.prompt_config import make_system_prompt
@tool
def reserve_room(meeting, room, **kwargs):
meeting.room = room
return meeting
agent = Agent(
role="calendar_assistant",
_system_prompt=make_system_prompt("""
You help with scheduling by writing Python against the objects in context.
Available variables: {shared_var_names}
Available functions:
{brief_tools}
"""),
_tools=[reserve_room],
_metadata={"MODEL": "openai/gpt-5-mini"},
_description="Moves meetings and explains schedule changes.",
)The working job might then start with local context prepared elsewhere:
user
message
calendar
todayThe agent can use those variables directly:
meeting = calendar.find_meeting("Weekly planning", day=today)
empty_slot = calendar.find_empty_slot(
after=meeting.ends_at,
duration=meeting.duration,
)
meeting.move_to(empty_slot)Prompt definition files
Inline prompts are useful for learning, but most applications keep agent prompts and metadata in .md files. A prompt file can carry metadata comments followed by a # System Prompt section.
# MODEL: openai/gpt-5-mini
# TEMPERATURE: 0.2
# CHAT_STYLE: DIRECT
# DESCRIPTION: Helps users inspect and update their calendar
# System Prompt
ROLE: Calendar assistant and Python workspace operator.
GOAL: Help the user inspect calendar state and make safe scheduling changes.
--- PYTHON WORKSPACE ---
- State is preserved between Python executions.
- Reuse existing variables when helpful.
- Available variables: {shared_var_names}
--- AVAILABLE FUNCTIONS ---
{brief_tools}When prompt-file loading is configured, STATEK can apply the prompt and metadata for an agent role. That lets application code keep the Python class small while product behavior lives in versioned prompt files.
For the full metadata reference, section syntax, difficulty routing, chat styles, placeholders, and examples, see Prompt Definitions.
Prompt placeholders
STATEK system prompts can include placeholders that are expanded when the prompt is formatted for a job:
{brief_tools}inserts concise tool descriptions.{tools}inserts normal tool descriptions.{detailed_tools}inserts Python-style tool details.{shared_var_names}expands to the names of shared variables supplied to the job.
Use placeholders to tell the model what Python functions and variables are available without manually duplicating tool signatures in every prompt.
Tool and context object wording comes from parsed docstrings. See Agent-Exposed Docstring Conventions for brief(...), docstr(...), VARIANTS/:.../TOOL:..., and STATEK-ACL filtering.
Tools and internal tools
Tools in _tools are Python callables the model can use. STATEK formats their docstrings into the prompt when the prompt includes tool placeholders.
from statek import tool
@tool
def current_time(**kwargs):
"""Return the current local time."""
return datetime.now()Tools whose names start with _ are treated as internal tools: they are available in the execution context but are not reported to the model through {tools}, {brief_tools}, or {detailed_tools}. Tools can also be hidden with tool metadata when the application needs execution-time access without prompt exposure.
Use visible tools for capabilities the model should choose deliberately. Use internal or hidden tools for adapters, plumbing, or support functions that should not become part of the model-facing interface.
Classic tools and Python tools
STATEK exposes functions to agents through two related surfaces.
Classic tools are formal model-provider tool calls. They are selected with LLM_TOOLS_SCOPE and sent to the model as provider tools when the provider and model support tool calling. Use them when the model should choose a structured function call directly, without first writing Python.
Python tools are functions available in the durable Python workspace. The model sees them through prompt placeholders such as {brief_tools}, {tools}, and {detailed_tools}, then calls them from Python code:
slot = find_empty_slot(calendar, after=meeting.ends_at)
move_meeting(meeting, slot)In DIRECT chat style, Python code runs through the python_cli system tool. In Markdown-style execution, Python tools can be called from Python code blocks. In both cases, the result is normal Python state: the job can keep variables, pass objects between calls, print results, and continue in later steps. See Chat Styles for when to use Markdown code blocks versus formal tool calls.
The same Python function can be useful on either surface. A domain function decorated with @tool can be described in the prompt as a Python-callable application function, exposed as a classic provider tool through LLM_TOOLS_SCOPE: APPLICATION, or both. A framework tool decorated with @tool(system=True) is a system tool; predefined STATEK system tools include docstr, brief, list_of_examples, show_example, list_of_documents, show_document, panic, and python_cli. These system tools are also injected into Python execution context, so a code-first agent can call docstr(calendar) or show_example() from Python when that is the better interaction shape.
Choose the surface deliberately:
- Use classic provider tools for small models, non-coding agents, simple one-shot actions, or workflows where structured tool calls are more reliable than Python generation.
- Use Python tools for code-first agents, multi-step work, object composition, durable intermediate variables, and tasks where the agent needs to inspect state before deciding the next call.
- Use predefined STATEK tools as classic tools when they bootstrap execution or inspection, especially
python_cli,docstr, andshow_example. - Keep risky side effects behind narrow named functions regardless of surface.
LLM_TOOLS_SCOPE controls the formal provider tool payload. It does not change what {brief_tools} expands to in the system prompt. See Tools for tool declaration details, Prompt Definitions for prompt metadata, and Agent-Exposed Docstring Conventions for writing the documentation those placeholders expose.
Agent context
context is private runtime context owned by the agent. It is useful for:
- adapters such as message formatters
- dynamically created tools registered by name
- private callables that should not be stored directly in a persistent list
- application classes or enum types needed by tools, warmup code, or type resolution
Conceptually:
agent.context["_calendar_adapter"] = calendar_adapter
agent.context["Calendar"] = Calendar
agent.context["find_available_room"] = find_available_roomget_adapter("calendar_adapter") checks common key variants such as calendar_adapter, _calendar_adapter, and __calendar_adapter.
Supervised agents and warmup
SupervisedAgent extends Agent with optional warmup definitions. Warmup code prepares the durable Python workspace before the model starts working.
For a queued message, warmup might set the request context the prompt says the agent can expect:
user = message.sender
calendar = user.calendar
today = message.created_at.date()
print(message)Agent-level warmup and job-level warmup can be combined. Dynamic job warmup can set task-specific names before reusable agent warmup runs. Warmup can also be split into blocks, record framework tool calls such as show_example(), and hide advanced setup blocks when needed.
See Warmup Code for the full lifecycle, block syntax, #STATEK: as tool, hidden setup, examples-first startup, and queue-picking patterns.
Dialog agents and reminders
DialogAgent is the specialized agent for one-to-one conversations. It wraps a send_message(body, media=None) callable, exposes an answer(...) tool by default, and defaults its job definitions to the MD_DIALOG chat style unless metadata or the caller overrides it.
Dialog agents can also carry a looping reminder. A reminder is persisted on the agent and can be injected back into the job as system chat history when the agent gives a text-only dialog response that would otherwise finish. Use reminders for model-visible guidance such as locale policy, final-answer discipline, or application-supplied runtime context.
See Harness Reminders for examples and API limits.
Multiple agents in one application
An agent is a reusable role definition. A job is one durable run of that role.
A small application may have one agent. Larger applications often define several agents because different work needs different prompts, tools, models, or permissions.
Example roles:
dispatcher_agent: receives a user message or queued event and decides what should handle itcalendar_agent: works with calendar objects and scheduling toolsretrieval_agent: gathers background information for another jobreview_agent: checks, summarizes, or validates another job's result
The dispatcher does not share its Python locals with the specialist. It starts or delegates to another job and passes only the durable context that job needs.
Many jobs can be active, waiting, or ready at the same time. Each job keeps its own Python locals. Jobs share application state only when the application deliberately gives them references to the same dbzero-backed object or external resource.
For small cross-job facts such as user preferences, known entities, or vocabulary, an application can initialize provisional shared context during warmup and expose a narrow write tool for approved memory updates. See Long-Term Memory for the current shared-context model and limits.
Larger applications may store long-lived agent objects on a durable root or registry so workers can find the same dispatcher, coordinator, and specialists after restart. That is an application organization pattern, not a requirement for every STATEK app.
For a practical dispatcher plus specialist-worker architecture, see Practical Examples.
Model and provider metadata
MODEL metadata is required when creating job definitions. It can be supplied directly on the agent or loaded from a prompt definition file.
agent = Agent(
role="analyst",
_system_prompt=make_system_prompt("Analyze the objects in context."),
_tools=[],
_metadata={"MODEL": "openai/gpt-5-mini"},
)PROVIDER metadata can select a provider when the model string itself does not already specify one. When a JobDef is created, STATEK freezes the metadata for that job definition. Later changes to the agent's metadata do not rewrite already-created job definitions.
Guardrails
An agent can only use the Python objects, tools, credentials, files, network paths, and runtime context that the application makes available. That is the main permission boundary you design.
Good practice:
- expose the smallest useful set of tools and objects
- keep sensitive operations behind explicit Python functions
- require approval before irreversible or external side effects
- avoid placing broad credentials in agent-accessible context
- sandbox or otherwise control code execution in production
- log and inspect job history for sensitive workflows
STATEK persists agent execution state, but it does not by itself make arbitrary Python execution safe. Production deployments need sandboxing, permissioning, secrets discipline, resource limits, and application-level controls for external side effects.
When to create another agent
Create another agent when the work needs a different role, prompt, model, tool surface, context setup, or operational boundary.
Examples:
- one dispatcher agent for routing queued messages
- one coordinator agent for delegation and follow-up
- one specialist for read-only analytics
- one specialist for state-changing operations
- one background retriever that gathers facts for another job
Keeping agents separate makes each Python workspace easier to reason about. It also keeps prompts shorter, tool surfaces smaller, and job history easier to inspect.
See Practical Examples for a complete dispatcher, information_retriever, and schedule_assistant pattern.