The Agent constructor and every public runtime method (run, resume, the submit family, stream, cancel, retry, close) with exact signatures.
Agent
Agent holds an agent's identity, capabilities, and limits, and exposes the runtime methods that execute it. Construct it once and reuse it across many runs.
from dendrux import Agent
from dendrux.llm.anthropic import AnthropicProvider
agent = Agent(
name="Support",
provider=AnthropicProvider(model="claude-haiku-4-5"),
prompt="You are a support agent.",
tools=[...],
database_url="sqlite+aiosqlite:///app.db",
)Agent is an async context manager. Use async with Agent(...) as agent: so MCP sources, the provider, and any agent-owned engine close cleanly. All runtime methods are async.
Constructor
Agent(
*,
provider: LLMProvider,
prompt: str,
name: str = ..., # defaults to the class name
tools: list[Callable] = [],
tool_sources: list[MCPServer] | None = None,
max_iterations: int = 10,
max_delegation_depth: int | None = None,
loop: Loop | None = None, # defaults to ReActLoop
output_type: type[BaseModel] | None = None,
database_url: str | None = None,
database_options: dict[str, Any] | None = None,
state_store: StateStore | None = None,
deny: list[str] | None = None,
require_approval: list[str] | None = None,
budget: Budget | None = None,
guardrails: list[Guardrail] | None = None,
skills_dir: str | Path | None = None,
skills: list[Skill] | None = None,
deny_skills: list[str] | None = None,
)Construction validates its inputs: unknown names in deny/require_approval, deny/require_approval overlap, output_type without SingleCall, and tools with SingleCall all raise ValueError.
run
async def run(
user_input: str,
*,
history: list[ChatMessage] | None = None,
tenant_id: str | None = None,
metadata: dict[str, Any] | None = None,
notifier: LoopNotifier | None = None,
idempotency_key: str | None = None,
output_type: type[BaseModel] | None = None,
) -> RunResultStart a new run. Returns a RunResult whose status is terminal (success, error, max_iterations) or paused (waiting_*). history seeds prior conversation turns (Chatbot threads); metadata is opaque JSON stored on the run for your queries; idempotency_key requires persistence.
stream
def stream(
user_input: str,
*,
history: list[ChatMessage] | None = None,
tenant_id: str | None = None,
metadata: dict[str, Any] | None = None,
notifier: LoopNotifier | None = None,
) -> RunStreamSame inputs as run, but returns a RunStream immediately (no await). stream.run_id is available before iteration. Iterate it for RunEvents, or .text() for text deltas. Prefer async with agent.stream(...) as s: for deterministic cleanup. Guardrails are not supported on the streaming path.
resume, submit_tool_results, submit_input, submit_approval
The submit family resumes a paused run. Each one matches a pause status (see Pause and resume). All require persistence and the run to exist.
async def submit_tool_results(run_id: str, results: list[ToolResult], *, notifier=None) -> RunResult
async def submit_input(run_id: str, text: str, *, notifier=None) -> RunResult
async def submit_approval(run_id: str, *, approved: bool, rejection_reason: str | None = None, notifier=None) -> RunResult
async def resume(run_id: str, *, tool_results=None, user_input=None, notifier=None) -> RunResultShared resume errors: RunNotFoundError, RunNotPausedError, PauseStatusMismatchError, RunAlreadyClaimedError, RunAlreadyTerminalError, PersistenceNotConfiguredError. See Errors.
resume_stream
def resume_stream(run_id: str, *, tool_results=None, user_input=None, notifier=None) -> RunStreamStreaming variant of resume: returns a RunStream for the resumed run.
cancel_run
async def cancel_run(run_id: str) -> RunResultRequest cancellation. Works for paused runs (atomic CAS to cancelled), running runs (cooperative flag), and terminal runs (no-op, never raises). Returns the persisted state. Raises RunNotFoundError and PersistenceNotConfiguredError. See Cancellation.
retry
async def retry(run_id: str, *, tenant_id=None, metadata=None, notifier=None, **kwargs) -> RunResultStart a fresh run that reuses an existing terminal run's input and approximate prior context. Requires persistence.
close
async def close() -> NoneClose MCP sources, the provider, and any agent-owned engine. Called for you by async with Agent(...). The shared global engine from DENDRUX_DATABASE_URL is not disposed (it is not agent-owned).
Where this fits
- Runs and the lifecycle: the status state machine these methods move through.
- RunStore: read back what a run produced.
- Errors: the exceptions the submit family raises.