Agents¶
An Agent is a node in a Claw society graph. It represents a participant -- either an AI model or a human operator -- that receives events along its edges, processes them, and emits responses. Agents are intentionally unaware of the graph topology they belong to; the compiler handles translating the graph structure into per-agent system prompts, context windows, and available actions.
Key properties of agents:
- Edge-routed events, not global broadcast. An agent only sees events that arrive along its connected edges. It never sees the full society state.
- The compiler writes the prompt. You provide
instructions; the compiler merges them with edge context, artifact snapshots, and tool definitions. - Human = Agent. Human operators are first-class graph nodes with a different runtime backend, not external supervisors.
Creating AI Agents¶
An AI agent is backed by a language model. Provide a name, a model, and
optionally a role, instructions, and tools:
from claw import Agent, Tool
coder = Agent(
name="coder",
role="implementer",
model="claude-opus",
instructions="Write clean, tested Python code.",
tools=[Tool(name="file_edit", description="Edit a file on disk")],
)
The model string is passed to the LLM backend at runtime. Claw does not
validate model names at graph-build time -- that happens when the runtime
resolves the backend. See llm-backends.md for supported
providers.
Agent Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Unique identifier within a society. Used as the node key in the graph. |
role |
str |
"" |
Semantic role label (e.g. "implementer", "critic", "dispatcher"). Included in compiled prompts. |
model |
str \| None |
None |
LLM model identifier. Required for AI agents; must be None for human agents. |
instructions |
str |
"" |
Agent-specific instructions merged into the compiled system prompt. |
tools |
list[Tool] |
[] |
Tools available to this agent. Compiled into tool schemas in the LLM prompt. |
human |
bool |
False |
Whether this agent is backed by a human interface instead of an LLM. |
interface |
str \| None |
None |
Interface type for human agents: "cli", "github", "slack", or "webhook". |
Agent is a Pydantic BaseModel, so all standard Pydantic features
(serialization, schema generation, validation) work out of the box. Agents are
also hashable and compared by name, making them usable as dictionary keys and
set members.
Tools¶
Tools define the actions an agent can take. Each tool has a name,
description, and an optional parameters field that accepts a JSON Schema
dict:
from claw import Tool
file_edit = Tool(
name="file_edit",
description="Edit a file on disk",
parameters={
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path"},
"content": {"type": "string", "description": "New content"},
},
"required": ["path", "content"],
},
)
shell = Tool(name="shell_exec", description="Run a shell command")
At compile time, tools are converted into the tool-call schema that the LLM
backend expects. The runtime's ToolExecutor maps tool names to actual
implementations (FileEditTool, ShellExecTool, GitHubTool).
If you do not need custom parameters, you can omit parameters entirely -- it
defaults to an empty dict.
Human Agents¶
Human agents participate in the same graph as AI agents but use a
HumanInterface backend instead of an LLM. Set human=True and specify an
interface:
from claw import Agent
maintainer = Agent(
name="maintainer",
role="maintainer",
human=True,
interface="cli",
)
Supported interfaces¶
| Interface | Description |
|---|---|
"cli" |
Terminal-based stdin/stdout interaction via CLIHuman. |
"github" |
Collects responses through GitHub PR comments. |
"slack" |
Collects responses through Slack messages. |
"webhook" |
Generic HTTP webhook for custom integrations. |
Human agents receive the same edge-routed events as AI agents. The interface presents the event context, available actions, and artifact state, then collects the human's response.
CLIHuman¶
CLIHuman is the built-in terminal interface. It displays event information,
context, and a numbered menu of available actions, then reads the human's
response from stdin:
from claw import CLIHuman
cli = CLIHuman()
# In the runtime, the interface is called like:
# response = await cli.prompt(event, context, available_tools)
You can also provide custom streams for testing or embedding:
import io
input_buf = io.StringIO("1 approve this\n")
output_buf = io.StringIO()
cli = CLIHuman(input_stream=input_buf, output_stream=output_buf)
When the human enters a number followed by text, CLIHuman maps the number to
the corresponding tool and treats the remaining text as arguments (parsed as
JSON if possible, otherwise wrapped as {"comment": "..."}).
MockHuman¶
MockHuman is a deterministic human interface for tests. It responds
automatically based on a configurable policy, so you can exercise human-in-the-loop
code paths without actual human interaction.
Policies¶
| Policy | Behavior |
|---|---|
"approve_all" |
Always responds "Approved." with an approve tool call. This is the default. |
"reject_all" |
Always responds "Rejected." with a reject tool call. |
"scripted" |
Maps event types to pre-configured HumanResponse objects. Falls back to a generic message for unknown event types. |
"random_delay" |
Like approve_all but sleeps for a random duration first (useful for concurrency testing). |
Scripted policy example¶
from claw import MockHuman, HumanResponse
from claw.llm import ToolCall
human = MockHuman(
policy="scripted",
scripts={
"review_requested": HumanResponse(
content="LGTM",
tool_calls=[ToolCall(name="approve", arguments={})],
),
"task_assigned": HumanResponse(content="On it!"),
},
)
Test assertions¶
MockHuman records every call to prompt() in its calls list. Use
call_count and calls to assert on interactions:
import pytest
from claw import MockHuman
from claw.event import Event
@pytest.mark.asyncio
async def test_human_was_consulted():
human = MockHuman(policy="approve_all")
event = Event(
type="review_requested",
source="coder",
target="maintainer",
edge_id="edge-1",
)
response = await human.prompt(event, context="PR #42: fix typo")
assert human.call_count == 1
assert human.calls[0]["context"] == "PR #42: fix typo"
assert response.tool_calls[0].name == "approve"
# Reset between tests if reusing the instance
human.reset()
assert human.call_count == 0
Protocol compliance¶
Both CLIHuman and MockHuman satisfy the HumanInterface protocol, which
requires a single async method:
async def prompt(
self,
event: Event,
context: str,
available_tools: list[dict[str, Any]] | None = None,
) -> HumanResponse:
...
You can implement your own backend (e.g. a Slack bot or a web UI) by
implementing this protocol. No base class inheritance is required -- it is a
typing.Protocol.
Validation¶
Claw validates agent configuration at construction time using a Pydantic model validator:
- AI agents must have a
model. Omittingmodelwhenhuman=Falseraises aValueError.
- Human agents must have an
interface. Settinghuman=Truewithoutinterfaceraises aValueError.
- Names must be unique within a society. The
Society.add()method rejects duplicate agent names. See societies.md for details.
Human-in-the-Loop¶
Claw treats humans as first-class agents in the society graph. A human agent participates in the same event-driven flow as AI agents, but responses come from a real person instead of an LLM.
Creating a Human Agent¶
from claw import Agent, Tool
maintainer = Agent(
name="maintainer",
role="maintainer",
human=True,
interface="github", # or "cli", "dashboard"
instructions="Review code and approve when satisfied.",
tools=[
Tool(name="approve", description="Approve the changes"),
Tool(name="request_changes", description="Request changes"),
],
)
Registering Interfaces with the Runtime¶
Human agents need a HumanInterface implementation registered with
the runtime:
from claw import LocalRuntime
from claw.human.github import GitHubHuman
gh_human = GitHubHuman(repo="owner/repo", issue_number=42)
runtime = LocalRuntime(
llm,
human_interfaces={"maintainer": gh_human},
)
Available Interfaces¶
| Interface | Class | Use Case |
|---|---|---|
| GitHub | GitHubHuman |
Comments on issues/PRs. Best for CI/CD workflows. |
| CLI | CLIHuman |
Terminal prompts. Best for local development. |
| Dashboard | DashboardHuman |
WebSocket via dashboard UI. Best for monitoring. |
| Mock | MockHuman |
Deterministic responses for testing. |
Async Feedback with GitHubCommentWatcher¶
For mid-flight human feedback (comments while agents are working),
use GitHubCommentWatcher:
from claw.human.watcher import GitHubCommentWatcher
watcher = GitHubCommentWatcher(
repo="owner/repo",
issue_number=42,
human_agent_name="maintainer",
target_agent="dev",
edge_id=edge.edge_id, # edge = society.edge_between(maintainer, dev)
)
runtime = LocalRuntime(
llm,
human_interfaces={"maintainer": gh_human},
comment_watchers=[watcher],
)
Example: Mixed Human + AI Society¶
from claw import Society, Agent, Delegation, Oversight, Tool
s = Society(name="human-review")
maintainer = Agent("maintainer", role="maintainer", human=True, interface="cli")
pm = Agent("pm", role="project-manager", model="claude-sonnet")
dev = Agent("dev", role="developer", model="claude-sonnet")
s.add(maintainer, pm, dev)
s.connect(pm, dev, Delegation())
s.connect(maintainer, dev, Oversight(max_rounds=2))