Logic Flows

Build conditional loops and multi-step agent execution

Overview

Logic flows enable agents to execute multiple steps based on conditions. Instead of single-shot execution, agents can loop, make decisions, and execute complex workflows using the LogicRunner class.

LogicRunner Class

The core class for managing conditional agent loops:

from agentic import LogicRunner, LogicConfig, LogicCondition

config = LogicConfig(
    logic_id="my_loop",
    max_iterations=10,
    stop_conditions=[...],  # List of LogicCondition objects
    loop_until_conditions=[...]
)

runner = LogicRunner(agent_runner, context, patterns, config)

# Execute with streaming events
async for event in runner.run_stream("initial input"):
    if isinstance(event, StepCompleteEvent):
        print(f"Step {event.result.iteration} done")

LogicConfig

Configuration for logic flow execution:

from agentic import LogicConfig, ProcessingMode

config = LogicConfig(
    logic_id="research_loop",
    max_iterations=20,                    # Safety limit
    stop_conditions=[...],                # Conditions to stop loop
    loop_until_conditions=[...],          # Loop until satisfied
    break_on_error=True,                  # Stop on agent errors
    processing_mode=ProcessingMode.THREAD, # Default processing mode
    context_health_checks=[...]           # Monitor context health
)

LogicCondition

Define conditions for when to stop or continue looping:

Basic Structure

from agentic import LogicCondition

condition = LogicCondition(
    pattern_set="default",       # Pattern set name
    pattern_name="done",         # Pattern/text to match
    match_type="contains",       # "contains" | "equals" | "regex"
    target="response",           # "response" | "reasoning" | "tool_output" | "context:{key}"
    evaluation_point="auto"     # When to evaluate (see below)
)

Match Types

1. Contains (Pattern Detection)

Check if a specific pattern appears in the output:

# Stop when "done" pattern detected in response
LogicCondition(
    pattern_set="default",
    pattern_name="done",
    match_type="contains",
    target="response"
)

2. Equals (Exact Match)

Check if text exactly equals a value:

# Stop when context key equals "complete"
LogicCondition(
    pattern_set="default",
    pattern_name="complete",
    match_type="equals",
    target="context:status"
)

3. Regex (Pattern Matching)

Use regular expressions for complex pattern matching:

# Stop when confidence score >= 95% appears
LogicCondition(
    pattern_set="default",  # Not used for regex
    pattern_name=r"confidence:\s*(\d+)%",
    match_type="regex",
    target="response"
)

Target Options

Evaluation Points

Control when conditions are evaluated during agent execution. This is crucial for early stopping and fine-grained control:

# Common evaluation points:
"auto"           # Smart default (context → step_complete, patterns → llm_complete)
"llm_chunk"      # On every LLM chunk as it streams (real-time, for early exit)
"llm_complete"   # After LLM generation completes
"tool_detected"  # When tool pattern detected in output
"tool_finished"  # After tool execution completes
"step_complete"  # After entire agent step completes (most common)
"any_event"      # On every event (most frequent, use sparingly)

# Advanced evaluation points (for fine-grained control):
"pattern_start"  # When a pattern starts being detected
"pattern_end"    # When a non-tool pattern completes
"pattern_content"# During pattern content streaming
"tool_start"     # When tool execution begins
"tool_output"    # During tool output streaming
"context_write"  # When context is written
"error"          # When an error occurs
"status"         # On status changes

Example: Early Exit on Streaming

# Stop immediately when "ABORT" appears in LLM stream
LogicCondition(
    pattern_set="default",
    pattern_name=r"ABORT",
    match_type="regex",
    target="response",
    evaluation_point="llm_chunk"  # Check during streaming!
)

Helper Functions

Convenient functions for common loop patterns:

Loop N Times

from agentic import loop_n_times

runner = loop_n_times(agent_runner, context, patterns, n=5)

async for event in runner.run_stream("Execute 5 iterations"):
    if isinstance(event, StepCompleteEvent):
        result = event.result
        print(f"Iteration {result.iteration}: {result.status}")

Loop Until Pattern

from agentic import loop_until_pattern

runner = loop_until_pattern(
    agent_runner,
    context,
    patterns,
    pattern_set="default",
    pattern_name="done",
    target="response",
    max_iterations=20
)

async for event in runner.run_stream("Complete task"):
    if isinstance(event, StepCompleteEvent):
        print(f"Step {event.result.iteration} done")

Loop Until Regex

from agentic import loop_until_regex

runner = loop_until_regex(
    agent_runner,
    context,
    patterns,
    regex_pattern=r"confidence:\s*([8-9]\d|100)%",  # 80-100%
    target="response",
    max_iterations=10
)

async for event in runner.run_stream("Refine until confident"):
    pass

Stop on Error

from agentic import stop_on_error

runner = stop_on_error(
    agent_runner,
    context,
    patterns,
    max_iterations=100
)

# Will stop immediately on any agent error

Context Health Monitoring

Monitor context size and prevent runaway memory usage:

from agentic import ContextHealthCheck, LogicConfig

config = LogicConfig(
    logic_id="monitored_loop",
    max_iterations=50,
    context_health_checks=[
        # Warn if any context key exceeds 100KB
        ContextHealthCheck(
            check_type="size",
            key_pattern="*",
            threshold=100_000,
            action="warn",
            evaluation_point="step_complete"
        ),
        # Stop if llm_output versions exceed 50
        ContextHealthCheck(
            check_type="version_count",
            key_pattern="llm_output:*",
            threshold=50,
            action="stop",
            evaluation_point="step_complete",
            max_versions_limit=10000
        )
    ]
)

Health Check Types

Key Pattern Matching

Actions

Stop vs Loop-Until Conditions

Stop Conditions

Terminate the loop when condition is met:

config = LogicConfig(
    logic_id="example",
    stop_conditions=[
        # Stop if "ERROR" appears
        LogicCondition(
            pattern_set="default",
            pattern_name=r"ERROR",
            match_type="regex",
            target="response"
        )
    ]
)

Loop-Until Conditions

Continue looping until condition is satisfied:

config = LogicConfig(
    logic_id="example",
    loop_until_conditions=[
        # Loop until "COMPLETE" appears
        LogicCondition(
            pattern_set="default",
            pattern_name=r"COMPLETE",
            match_type="regex",
            target="response"
        )
    ]
)

Real-World Example: Research Agent

Agent that iteratively researches until comprehensive answer is found:

from agentic import (
    LogicRunner, LogicConfig, LogicCondition,
    ContextHealthCheck, ProcessingMode
)

# Configure research loop
config = LogicConfig(
    logic_id="research_agent",
    max_iterations=10,

    # Stop on errors
    break_on_error=True,

    # Stop conditions
    stop_conditions=[
        # Stop if agent says DONE
        LogicCondition(
            pattern_set="default",
            pattern_name="done",
            match_type="contains",
            target="response"
        ),
        # Stop if comprehensive answer detected
        LogicCondition(
            pattern_set="default",
            pattern_name=r"(?=.*sources)(?=.{500,})",  # 500+ chars with "sources"
            match_type="regex",
            target="response",
            evaluation_point="llm_complete"
        )
    ],

    # Monitor context health
    context_health_checks=[
        ContextHealthCheck(
            check_type="size",
            key_pattern="*",
            threshold=500_000,  # 500KB limit
            action="stop"
        )
    ],

    processing_mode=ProcessingMode.THREAD
)

runner = LogicRunner(agent_runner, context, patterns, config)

# Execute research loop
async for event in runner.run_stream("Research quantum computing applications"):
    if isinstance(event, StepCompleteEvent):
        print(f"Research iteration {event.result.iteration} complete")
        print(f"Response: {event.result.segments.response[:100]}...")
    elif isinstance(event, StatusEvent):
        print(f"Status: {event.message}")
    elif isinstance(event, ContextHealthEvent):
        print(f"Context health warning: {event.check_type}")

Batch Mode

For synchronous execution without streaming:

# Collect all results at once
results = runner.run("Execute task")

for i, result in enumerate(results):
    print(f"Iteration {i + 1}: {result.status}")
    if result.segments.response:
        print(f"  Response: {result.segments.response}")
Note: The run() method cannot be called from an async context. Use run_stream() in async code.

Best Practices

Event Handling

LogicRunner emits various events during execution:

from agentic.events import (
    StatusEvent, StepCompleteEvent, ContextHealthEvent,
    LLMChunkEvent, LLMCompleteEvent, ToolEndEvent
)

async for event in runner.run_stream(user_input):
    if isinstance(event, StatusEvent):
        print(f"[{event.status}] {event.message}")

    elif isinstance(event, StepCompleteEvent):
        print(f"Iteration {event.result.iteration} done")

    elif isinstance(event, ContextHealthEvent):
        print(f"Health: {event.key} - {event.check_type}")

    elif isinstance(event, LLMChunkEvent):
        print(event.chunk, end="")  # Stream LLM output

Next Steps