The Agent Harness - AG2

Harness

A bare Agent is just a model loop. The harness is the set of opt-in primitives you compose onto it to give it richer capabilities — context assembly, persistent knowledge, sub-task spawning, and the supporting middleware they wire in.

This page is the configuration reference for those primitives. For the conversational entry point (agent.ask(), tools, HITL, observing events), see Agent Communication.

Constructor #

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br> ```
Agent(
name: str,
prompt: str

The loop-related parameters (config, tools, middleware, observers, prompt, …) are covered in Agent Communication and the parameter-specific guides. The harness hooks are assembly=, knowledge=, and tasks=, each documented below.

assembly= — context policies #

A list of AssemblyPolicy instances. When non-empty, the Agent wires an internal AssemblerMiddleware at the outermost position of the middleware chain so your policies transform (prompts, events) before every LLM call.

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br> <br>from autogen.beta import Agent<br>from autogen.beta.policies import (<br> AlertPolicy,<br> SlidingWindowPolicy,<br> WorkingMemoryPolicy,<br>)<br>agent = Agent(<br> "assistant",<br> config=config,<br> assembly=[<br> WorkingMemoryPolicy(), # inject /memory/working.md<br> AlertPolicy(), # deliver ObserverAlerts, halt on FATAL<br> SlidingWindowPolicy(max_events=50), # cap history footprint<br> ],<br>)<br>

Order matters — see the ordering rule in the assembly doc. AssemblerMiddleware.validate_order() will flag known problematic compositions.

knowledge= — KnowledgeConfig #

Groups everything that involves the KnowledgeStore: the store itself, optional bootstrap, and optional compaction + aggregation strategies.

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br> ```
from dataclasses import dataclass
@dataclass
class KnowledgeConfig:
store: KnowledgeStore
expose_tool: bool = True
write_event_log: bool = True
compact: CompactStrategy
Field What it does
store Registered in context.dependencies[KnowledgeStore] so policies like WorkingMemoryPolicy / EpisodicMemoryPolicy can read it.
expose_tool When True (default), the agent gets an auto-injected knowledge action-group tool that lets the LLM call read / write / list / delete on the store. Set to False when the store should be policy-only — the model never sees the tool, and the bootstrap SKILL.md text drops its "use the knowledge tool" sentence.
write_event_log When True (default), the agent persists its stream history to /log/{stream_id}.jsonl at the end of each ask(). Set to False to keep the store free of stream logs (e.g. when the store is purely user-facing memory).
compact / compact_trigger Wires a compaction middleware that fires compact() between turns when the trigger thresholds are exceeded.
aggregate / aggregate_trigger Wires an aggregation middleware that fires aggregate() on the configured cadence. Failures emit AggregationFailed on the stream — see Aggregation › Wiring onto an Agent for the full lifecycle event triple.
bootstrap Runs once on first use to seed the store. None falls back to DefaultBootstrap(mention_tool=expose_tool), so the generated SKILL.md text matches whether the LLM can actually call the knowledge tool.
<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br> <br>from autogen.beta import Agent, KnowledgeConfig<br>from autogen.beta.aggregate import AggregateTrigger, ConversationSummaryAggregate<br>from autogen.beta.compact import CompactTrigger, TailWindowCompact<br>from autogen.beta.knowledge import DiskKnowledgeStore<br>from autogen.beta.policies import WorkingMemoryPolicy<br>from pathlib import Path<br>store = DiskKnowledgeStore(Path("./knowledge"))<br>agent = Agent(<br> "assistant",<br> config=main_config,<br> knowledge=KnowledgeConfig(<br> store=store,<br> compact=TailWindowCompact(target=100),<br> compact_trigger=CompactTrigger(max_events=200),<br> aggregate=ConversationSummaryAggregate(config=summarizer_config),<br> aggregate_trigger=AggregateTrigger(every_n_turns=10, on_end=True),<br> ),<br> assembly=[WorkingMemoryPolicy()],<br>)<br>

The compaction and aggregation middleware are opt-in per field: passing compact= without compact_trigger= still works (a default CompactTrigger() with all thresholds disabled is used). Omit a strategy entirely and the corresponding middleware is not wired.

tasks= — TaskConfig #

Sub-task delegation is off by default — a bare Agent has no run_subtask / run_subtasks tools. Pass tasks=TaskConfig(...) to opt in, and the Agent will auto-inject the pair of sub-task tools that let the LLM spawn isolated child Agents to handle self-contained work. TaskConfig configures how those children are built.

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br> ```
from dataclasses import dataclass
@dataclass
class TaskConfig:
config: ModelConfig
Field What it does
config The ModelConfig used for sub-task Agents. Falls back to the parent Agent's config.
prompt Default system prompt for sub-task Agents.
include_tools Allowlist of parent-tool names to inherit. None means "inherit all".
exclude_tools Blocklist of parent-tool names to drop. Applied after include_tools.
extra_tools Additional tools given to sub-tasks that the parent does not have.

By default a sub-task Agent inherits all of the parent's user-supplied tools. Sub-tasks are themselves constructed with tasks=False (the Agent default), so they have no run_subtask / run_subtasks tools — recursive delegation is structurally impossible and no depth limit is needed.

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br> <br>from autogen.beta import Agent, TaskConfig<br>agent = Agent(<br> "orchestrator",<br> config=main_config,<br> tools=[search, fetch_url, summarize],<br> tasks=TaskConfig(<br> config=worker_config, # cheaper model for sub-tasks<br> prompt="You are a focused worker; one step only.",<br> include_tools=["search", "fetch_url"], # don't expose summarize to children<br> ),<br>)<br>

tasks=False — the default #

tasks=False is the Agent default, so a bare Agent never spawns children. You only need to pass it explicitly to be self-documenting; otherwise just omit tasks= entirely.

<br>1<br>2<br>3<br>4<br>5<br>6<br> <br>focused = Agent(<br> "summarizer",<br> prompt="Summarise the input. Do not delegate.",<br> config=main_config,<br> # tasks=False is the default — no run_subtask / run_subtasks tools.<br>)<br>

run_subtask / run_subtasks — auto-injected tools #

When you opt in via tasks=TaskConfig(...), the Agent exposes two tools to the LLM:

The LLM is told (via the tool descriptions) that it can call run_subtask multiple times in parallel within a single response, and that run_subtasks is the deliberate fan-out form. Each child gets a fresh MemoryStream and the parent's tools (filtered by TaskConfig).

For a more explicit, named delegate where the parent LLM sees a tool like task_researcher instead of generic run_subtask, use Agent.as_tool(). The two patterns can coexist: a coordinator can have both auto-injected sub-tasks and a named task_researcher tool.

See Task Delegation for the full sub-task delegation guide — context flow and custom streams for self-delegation via as_tool().

Agent.as_tool() #

Expose any Agent as a FunctionTool so another Agent can invoke it like any other tool:

<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br>10<br>11<br>12<br>13<br> <br>child = Agent(<br> "researcher",<br> prompt="Answer the objective concisely.",<br> config=main_config,<br>)<br>parent = Agent(<br> "lead",<br> config=main_config,<br> tools=[child.as_tool(description="Delegate fact-finding to a researcher.")],<br>)<br>reply = await parent.ask("Find out where Melbourne is.")<br>

as_tool() returns a FunctionTool named task_{child.name} that accepts an objective parameter and forwards it into the child's stream. See Task Delegation for sub-task streams, depth limiting, and stream factories.

Turn lifecycle #

Each await agent.ask(...) runs through the middleware chain in this order (outermost → innermost):

1. AssemblerMiddleware              (if assembly=[...])
2. _HaltCheckMiddleware             (if assembly=[...] — watches for HaltEvent)
3. _CompactionMiddleware            (if knowledge.compact configured)
4. _AggregationMiddleware           (if knowledge.aggregate configured)
5. User-provided middleware         (retry, rate-limit, logging, …)
6. LLM client                       (innermost)

The internal harness middleware (_AssemblerMiddleware, _HaltCheckMiddleware, _CompactionMiddleware, _AggregationMiddleware) are assembled conditionally — you only pay for what you turn on.

Lifecycle events emitted during a turn include ObserverStarted / ObserverCompleted, CompactionCompleted, AggregationCompleted, and HaltEvent. Subscribe to any of them via an Observer or a stream subscriber.

Back to top