Concepts: Agents
An agent is a code-declared automation that combines a model, a system prompt, and a tuple of tools. Agents live in the AgentHub package and register at import time.
The console exposes them in the Agents page and lets operators run them, chat with them, and apply per-tenant overrides.
How agents are declared
An agent is a Python class that subclasses Agent, declares its model, system prompt, and tool tuple, and calls register_agent(YourAgent) at module import.
class TeamFinder(Agent):
name = "team-finder"
model = "claude-opus-4-7"
system_prompt = "..."
tools = (find_people_by_skill, ...)
required_scopes = ("agents:team-finder:run",)See packages/agenthub/src/agenthub/examples/team_finder.py for the reference shape. The agents under agenthub.examples are reference implementations, not platform defaults — they load only through the dev default of the ACASO_AGENT_MODULES env var. A deployment sets that variable explicitly: a client implementation lists its own modules (under agenthub.custom.<client_slug>) and excludes the examples. See Develop: Agents.
Tools
A tool is an async Python function decorated with @tool. It reads from DataHub through tenant_scope(get_engine(), current_tenant_id()). The tenant_id is bound on the connection by the auth middleware; the LLM never sees it.
Tools can declare their own required_scopes. The runtime filters the tool list before sending it to the LLM AND re-checks each call at invocation time. Both gates use the same matching algorithm covered in Concepts: Scopes.
Required scopes
Every agent's required_scopes defaults to (f"agents:{name}:run",) when not overridden. Three concrete consequences:
- The agent only appears in the Agents list to operators whose effective scopes match.
GET /v1/agents/{name}returns 404 (not 403) for ungated callers, to avoid leaking the agent's existence.- The chat surface refuses to stream against an agent the caller cannot run.
Per-tenant overrides
Code-declared values are defaults. A tenant can override model, disabled tools, or any field exposed through the agent's overrides_schema. The override is stored in tenant_agent_overrides and applied on the next run for that tenant only.
The system prompt is handled separately (ADR-0006): it lives in versioned, per-tenant Prompts edited on the System prompt page, with immutable versions, a movable production label, history, and diff. At run time the agent uses the production version, falling back to the code-declared system_prompt when the tenant has no Prompt.
Note: DB-defined agents (a
dh_agentstable plus a runtime that loads them at request time) are deferred to a future spec. v1's writable agent surface is overrides + versioned prompts.
Streaming and history
A chat opens an SSE stream against POST /v1/agents/{name}/stream. Tokens arrive in real time; tool-call events render as inline collapsible cards. State is checkpointed in LangGraph's Postgres store, indexed by chat_id (which doubles as the LangGraph thread_id).
History replay reads from GET /v1/chats/{chat_id}/messages. The endpoint maps LangGraph's checkpoint state into the same wire format as the live SSE stream, so a single React component renders both surfaces identically.