hive-concepts
npx machina-cli add skill aden-hive/hive/hive-concepts --openclawBuilding Agents - Core Concepts
Foundational knowledge for building goal-driven agents as Python packages.
Architecture: Python Services (Not JSON Configs)
Agents are built as Python packages:
exports/my_agent/
├── __init__.py # Package exports
├── __main__.py # CLI (run, info, validate, shell)
├── agent.py # Graph construction (goal, edges, agent class)
├── nodes/__init__.py # Node definitions (NodeSpec)
├── config.py # Runtime config
└── README.md # Documentation
Key Principle: Agent is visible and editable during build
- Files created immediately as components are approved
- User can watch files grow in their editor
- No session state - just direct file writes
- No "export" step - agent is ready when build completes
Core Concepts
Goal
Success criteria and constraints (written to agent.py)
goal = Goal(
id="research-goal",
name="Technical Research Agent",
description="Research technical topics thoroughly",
success_criteria=[
SuccessCriterion(
id="completeness",
description="Cover all aspects of topic",
metric="coverage_score",
target=">=0.9",
weight=0.4,
),
# 3-5 success criteria total
],
constraints=[
Constraint(
id="accuracy",
description="All information must be verified",
constraint_type="hard",
category="quality",
),
# 1-5 constraints total
],
)
Node
Unit of work (written to nodes/init.py)
Node Types:
event_loop— Multi-turn streaming loop with tool execution and judge-based evaluation. Works with or without tools.function— Deterministic Python operations. No LLM involved.
search_node = NodeSpec(
id="search-web",
name="Search Web",
description="Search for information and extract results",
node_type="event_loop",
input_keys=["query"],
output_keys=["search_results"],
system_prompt="Search the web for: {query}. Use the web_search tool to find results, then call set_output to store them.",
tools=["web_search"],
)
NodeSpec Fields for Event Loop Nodes:
| Field | Default | Description |
|---|---|---|
client_facing | False | If True, streams output to user and blocks for input between turns |
nullable_output_keys | [] | Output keys that may remain unset (for mutually exclusive outputs) |
max_node_visits | 1 | Max times this node executes per run. Set >1 for feedback loop targets |
Edge
Connection between nodes (written to agent.py)
Edge Conditions:
on_success— Proceed if node succeeds (most common)on_failure— Handle errorsalways— Always proceedconditional— Based on expression evaluating node output
Edge Priority:
Priority controls evaluation order when multiple edges leave the same node. Higher priority edges are evaluated first. Use negative priority for feedback edges (edges that loop back to earlier nodes).
# Forward edge (evaluated first)
EdgeSpec(
id="review-to-campaign",
source="review",
target="campaign-builder",
condition=EdgeCondition.CONDITIONAL,
condition_expr="output.get('approved_contacts') is not None",
priority=1,
)
# Feedback edge (evaluated after forward edges)
EdgeSpec(
id="review-feedback",
source="review",
target="extractor",
condition=EdgeCondition.CONDITIONAL,
condition_expr="output.get('redo_extraction') is not None",
priority=-1,
)
Client-Facing Nodes
For multi-turn conversations with the user, set client_facing=True on a node. The node will:
- Stream its LLM output directly to the end user
- Block for user input between conversational turns
- Resume when new input is injected via
inject_event()
intake_node = NodeSpec(
id="intake",
name="Intake",
description="Gather requirements from the user",
node_type="event_loop",
client_facing=True,
input_keys=[],
output_keys=["repo_url", "project_url"],
system_prompt="You are the intake agent. Ask the user for the repo URL and project URL.",
)
Legacy Note: The old
pause_nodes/entry_pointspattern still works butclient_facing=Trueis preferred for new agents.
STEP 1 / STEP 2 Prompt Pattern: For client-facing nodes, structure the system prompt with two explicit phases:
system_prompt="""\
**STEP 1 — Respond to the user (text only, NO tool calls):**
[Present information, ask questions, etc.]
**STEP 2 — After the user responds, call set_output:**
[Call set_output with the structured outputs]
"""
This prevents the LLM from calling set_output prematurely before the user has had a chance to respond.
Node Design: Fewer, Richer Nodes
Prefer fewer nodes that do more work over many thin single-purpose nodes:
- Bad: 8 thin nodes (parse query → search → fetch → evaluate → synthesize → write → check → save)
- Good: 4 rich nodes (intake → research → review → report)
Why: Each node boundary requires serializing outputs and passing context. Fewer nodes means the LLM retains full context of its work within the node. A research node that searches, fetches, and analyzes keeps all the source material in its conversation history.
nullable_output_keys for Cross-Edge Inputs
When a node receives inputs that only arrive on certain edges (e.g., feedback only comes from a review → research feedback loop, not from intake → research), mark those keys as nullable_output_keys:
research_node = NodeSpec(
id="research",
input_keys=["research_brief", "feedback"],
nullable_output_keys=["feedback"], # Not present on first visit
max_node_visits=3,
...
)
Event Loop Architecture Concepts
How EventLoopNode Works
An event loop node runs a multi-turn loop:
- LLM receives system prompt + conversation history
- LLM responds (text and/or tool calls)
- Tool calls are executed, results added to conversation
- Judge evaluates: ACCEPT (exit loop), RETRY (loop again), or ESCALATE
- Repeat until judge ACCEPTs or max_iterations reached
EventLoopNode Runtime
EventLoopNodes are auto-created by GraphExecutor at runtime. You do NOT need to manually register them. Both GraphExecutor (direct) and AgentRuntime / create_agent_runtime() handle event_loop nodes automatically.
# Direct execution — executor auto-creates EventLoopNodes
from framework.graph.executor import GraphExecutor
from framework.runtime.core import Runtime
runtime = Runtime(storage_path)
executor = GraphExecutor(
runtime=runtime,
llm=llm,
tools=tools,
tool_executor=tool_executor,
storage_path=storage_path,
)
result = await executor.execute(graph=graph, goal=goal, input_data=input_data)
# TUI execution — AgentRuntime also works
from framework.runtime.agent_runtime import create_agent_runtime
runtime = create_agent_runtime(
graph=graph, goal=goal, storage_path=storage_path,
entry_points=[...], llm=llm, tools=tools, tool_executor=tool_executor,
)
set_output
Nodes produce structured outputs by calling set_output(key, value) — a synthetic tool injected by the framework. When the LLM calls set_output, the value is stored in the output accumulator and made available to downstream nodes via shared memory.
set_output is NOT a real tool — it is excluded from real_tool_results. For client-facing nodes, this means a turn where the LLM only calls set_output (no other tools) is treated as a conversational boundary and will block for user input.
JudgeProtocol
The judge is the SOLE mechanism for acceptance decisions. Do not add ad-hoc framework gating, output rollback, or premature rejection logic. If the LLM calls set_output too early, fix it with better prompts or a custom judge — not framework-level guards.
The judge controls when a node's loop exits:
- Implicit judge (default, no judge configured): ACCEPTs when the LLM finishes with no tool calls and all required output keys are set
- SchemaJudge: Validates outputs against a Pydantic model
- Custom judges: Implement
evaluate(context) -> JudgeVerdict
LoopConfig
Controls loop behavior:
max_iterations(default 50) — prevents infinite loopsmax_tool_calls_per_turn(default 10) — limits tool calls per LLM responsetool_call_overflow_margin(default 0.5) — wiggle room before discarding extra tool calls (50% means hard cutoff at 150% of limit)stall_detection_threshold(default 3) — detects repeated identical responsesmax_history_tokens(default 32000) — triggers conversation compaction
Data Tools (Spillover Management)
When tool results exceed the context window, the framework automatically saves them to a spillover directory and truncates with a hint. Nodes that produce or consume large data should include the data tools:
save_data(filename, data)— Write data to a file in the data directoryload_data(filename, offset=0, limit=50)— Read data with line-based paginationlist_data_files()— List available data filesserve_file_to_user(filename, label="")— Get a clickable file:// URI for the user
Note: data_dir is a framework-injected context parameter — the LLM never sees or passes it. GraphExecutor.execute() sets it per-execution via contextvars, so data tools and spillover always share the same session-scoped directory.
These are real MCP tools (not synthetic). Add them to nodes that handle large tool results:
research_node = NodeSpec(
...
tools=["web_search", "web_scrape", "load_data", "save_data", "list_data_files"],
)
Fan-Out / Fan-In
Multiple ON_SUCCESS edges from the same source create parallel execution. All branches run concurrently via asyncio.gather(). Parallel event_loop nodes must have disjoint output_keys.
max_node_visits
Controls how many times a node can execute in one graph run. Default is 1. Set higher for nodes that are targets of feedback edges (review-reject loops). Set 0 for unlimited (guarded by max_steps).
Tool Discovery & Validation
CRITICAL: Before adding a node with tools, you MUST verify the tools exist.
Tools are provided by MCP servers. Never assume a tool exists - always discover dynamically.
Step 1: Register MCP Server (if not already done)
mcp__agent-builder__add_mcp_server(
name="tools",
transport="stdio",
command="python",
args='["mcp_server.py", "--stdio"]',
cwd="../tools"
)
Step 2: Discover Available Tools
# List all tools from all registered servers
mcp__agent-builder__list_mcp_tools()
# Or list tools from a specific server
mcp__agent-builder__list_mcp_tools(server_name="tools")
Step 3: Validate Before Adding Nodes
Before writing a node with tools=[...]:
- Call
list_mcp_tools()to get available tools - Check each tool in your node exists in the response
- If a tool doesn't exist:
- DO NOT proceed with the node
- Inform the user: "The tool 'X' is not available. Available tools are: ..."
- Ask if they want to use an alternative or proceed without the tool
Tool Validation Anti-Patterns
- Never assume a tool exists - always call
list_mcp_tools()first - Never write a node with unverified tools - validate before writing
- Never silently drop tools - if a tool doesn't exist, inform the user
- Never guess tool names - use exact names from discovery response
Workflow Overview: Incremental File Construction
1. CREATE PACKAGE → mkdir + write skeletons
2. DEFINE GOAL → Write to agent.py + config.py
3. FOR EACH NODE:
- Propose design (event_loop for LLM work, function for deterministic)
- User approves
- Write to nodes/__init__.py IMMEDIATELY
- (Optional) Validate with test_node
4. CONNECT EDGES → Update agent.py
- Use priority for feedback edges (negative priority)
- (Optional) Validate with validate_graph
5. FINALIZE → Write agent class to agent.py
6. DONE - Agent ready at exports/my_agent/
Files written immediately. MCP tools optional for validation/testing bookkeeping.
When to Use This Skill
Use hive-concepts when:
- Starting a new agent project and need to understand fundamentals
- Need to understand agent architecture before building
- Want to validate tool availability before proceeding
- Learning about node types, edges, and graph execution
Next Steps:
- Ready to build? → Use
hive-createskill - Need patterns and examples? → Use
hive-patternsskill
MCP Tools for Validation
After writing files, optionally use MCP tools for validation:
test_node - Validate node configuration with mock inputs
mcp__agent-builder__test_node(
node_id="search-web",
test_input='{"query": "test query"}',
mock_llm_response='{"results": "mock output"}'
)
validate_graph - Check graph structure
mcp__agent-builder__validate_graph()
# Returns: unreachable nodes, missing connections, event_loop validation, etc.
configure_loop - Set event loop parameters
mcp__agent-builder__configure_loop(
max_iterations=50,
max_tool_calls_per_turn=10,
stall_detection_threshold=3,
max_history_tokens=32000
)
Key Point: Files are written FIRST. MCP tools are for validation only.
Related Skills
- hive-create - Step-by-step building process
- hive-patterns - Best practices: judges, feedback edges, fan-out, context management
- hive - Complete workflow orchestrator
- hive-test - Test and validate completed agents
Source
git clone https://github.com/aden-hive/hive/blob/main/.claude/skills/hive-concepts/SKILL.mdView on GitHub Overview
Foundational knowledge for building goal-driven agents as Python packages, covering architecture, node types (event_loop and function), tool discovery, and the overall workflow. This guide helps developers structure agents, define goals, and wire node graphs before implementation.
How This Skill Works
Agents are organized as Python packages with a clear file layout (exports/my_agent, __main__.py, agent.py, nodes, config.py). NodeSpec defines node_type (event_loop or function) and how nodes connect via edges. Edges control flow using on_success, on_failure, always, or conditional expressions, with optional priority to manage evaluation order.
When to Use It
- Starting a new agent development project and defining the base architecture
- When you need a solid understanding of agent fundamentals (nodes, edges, goals)
- Designing node flows and choosing between event_loop and function nodes
- Debugging and validating agent workflows and edge conditions
- Planning tool discovery and workflow routing within a Python package
Quick Start
- Step 1: Scaffold a Python package at exports/my_agent with __init__.py, __main__.py, agent.py, nodes/, and config.py
- Step 2: Implement Goal, NodeSpec (event_loop and function), and EdgeSpec in agent.py to define the graph
- Step 3: Run and test with the CLI (python -m exports.my_agent --info or shell) to validate and interact
Best Practices
- Start by defining the architecture in code (not JSON configs) to align with the Python package layout
- Use NodeSpec to clearly distinguish event_loop (interactive, tool-inclusive) vs function (deterministic) nodes
- Capture goals, success criteria, and constraints directly in agent.py for traceability
- Model edges with explicit conditions and priorities to ensure predictable flow
- Keep runtime config separate and validate progress with the built-in CLI (run, info, validate, shell)
Example Use Cases
- Web research agent that uses a multi-turn event_loop node with a web_search tool
- Deterministic data cleaning agent implemented as a function node
- Multi-step QA workflow routed through edges based on evaluation outcomes
- Support bot that escalates complex issues using conditional edges
- Documentation crawler that builds topic graphs from discovered sources