Skip to content

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.

from claw import MockHuman

human = MockHuman(policy="approve_all")

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. Omitting model when human=False raises a ValueError.
# Raises: ValueError: AI agent 'x' must specify a model
Agent(name="x", role="coder")
  • Human agents must have an interface. Setting human=True without interface raises a ValueError.
# Raises: ValueError: Human agent 'y' must specify an interface
Agent(name="y", human=True)
  • 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))

Next Steps

  • Edges -- connect agents with typed relationships (cooperation, competition, oversight, delegation).
  • Societies -- compose agents and edges into a runnable graph.
  • Runtime -- execute a society and observe the results.