1 Data Flow 02 ReAct Loop
blightbow edited this page 2025-12-07 00:47:14 +00:00

Data Flow 02: ReAct Loop Execution

Engineering documentation series - Data flows in the AI Assistant system


Overview

The ReAct (Reasoning + Acting) loop is the core execution engine for multi-action AI behavior. Each tick can execute multiple tool calls in sequence until a termination condition is met.

Document Description
Data-Flow-01-Context-Compaction Context management
Data-Flow-06-Tool-Execution Individual tool execution

1. Tick Entry Point

Trigger

TICKER_HANDLER → AssistantScript.at_tick()
                 └─> Line 710: execute_react_loop() or single action

Pre-Loop Processing

┌─────────────────────────────────────────────────────────────────────────────┐
│ TICK START                                                                  │
│ assistant_script.py:at_tick()                                               │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EARLY EXITS                                                                 │
│ ─────────────────────────────────────────────────────────────────────────── │
│ 1. Already ticking (db.is_ticking == True) → return                         │
│ 2. Not enabled (db.enabled == False) → return                               │
│ 3. Emergency stopped (db.emergency_stopped == True) → return                │
│ 4. No character → return                                                    │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EMERGENCY COMPACTION CHECK (lines 646-667)                                  │
│ ─────────────────────────────────────────────────────────────────────────── │
│ If awake mode AND context >= 80%:                                           │
│   yield compact_conversation_history(force=True)                            │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ SLEEP MODE CHECK (lines 669-693)                                            │
│ ─────────────────────────────────────────────────────────────────────────── │
│ If operating_mode == "sleep":                                               │
│   yield _run_sleep_tick()                                                   │
│   Check wake conditions                                                     │
│   return (skip normal tick)                                                 │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ REFLECTION CHECK (lines 695-699)                                            │
│ ─────────────────────────────────────────────────────────────────────────── │
│ If ticks_since_reflection >= reflection_interval:                           │
│   yield _trigger_reflection()                                               │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXECUTION PATTERN SELECTION (lines 701-708)                                 │
│ ─────────────────────────────────────────────────────────────────────────── │
│ 1. context_type = _determine_context_type()                                 │
│ 2. llm_assessment = yield _assess_task()  (Layer 3)                         │
│ 3. execution_pattern = _get_execution_pattern(context_type, llm_assessment) │
│                                                                             │
│ Three-Layer Composition:                                                    │
│   Layer 1: Static config (db.execution_config)                              │
│   Layer 2: Context config (per-context overrides)                           │
│   Layer 3: LLM assessment (advisory, optional)                              │
└─────────────────────────────────────────────────────────────────────────────┘

2. ReAct Loop Execution

Decision Point

# Line 713
if should_use_react_loop(execution_pattern):
    loop_result = yield execute_react_loop(...)
else:
    # Single action mode (legacy)

Loop Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│ REACT LOOP START                                                            │
│ tool_execution.py:execute_react_loop() line 381                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
          ┌─────────────────────────┴─────────────────────────┐
          │              FOR EACH ITERATION                   │
          │         (up to max_iterations)                    │
          └───────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 1: PRE-ITERATION CHECK (lines 429-438)                                 │
│ ─────────────────────────────────────────────────────────────────────────── │
│ token_usage = _calculate_token_usage(script)                                │
│ continuation = check_loop_continuation(token_usage, iteration, max_iter)    │
│                                                                             │
│ If NOT should_continue:                                                     │
│   termination_reason = continuation["reason"]                               │
│   break                                                                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 2: BUILD LLM MESSAGES (lines 440-447)                                  │
│ ─────────────────────────────────────────────────────────────────────────── │
│ messages = yield build_messages_fn()                                        │
│                                                                             │
│ Includes:                                                                   │
│   - System prompt (persona, tools, context)                                 │
│   - Conversation history                                                    │
│   - Previous tool results (if any)                                          │
│   - Token advisory (if approaching limits)                                  │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 3: CALL LLM (lines 449-462)                                            │
│ ─────────────────────────────────────────────────────────────────────────── │
│ response = yield call_llm_fn(messages)                                      │
│                                                                             │
│ LLM decides:                                                                │
│   - Which tool to call (or noop if done)                                    │
│   - Parameters for the tool                                                 │
│   - Reasoning for the decision                                              │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 4: PARSE TOOL CALL (lines 464-481)                                     │
│ ─────────────────────────────────────────────────────────────────────────── │
│ tool_call = parse_tool_call_fn(response)                                    │
│                                                                             │
│ If tool_name == "noop":                                                     │
│   termination_reason = "noop"                                               │
│   break  (LLM explicitly done)                                              │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 5: VALIDATE TOOL (lines 483-509)                                       │
│ ─────────────────────────────────────────────────────────────────────────── │
│ tool = TOOL_REGISTRY.get_tool(tool_name)                                    │
│                                                                             │
│ Check:                                                                      │
│   - Tool exists in registry                                                 │
│   - Tool category allowed in current context                                │
│                                                                             │
│ If invalid: log error, continue to next iteration                           │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 6: EXECUTE TOOL (lines 511-517)                                        │
│ ─────────────────────────────────────────────────────────────────────────── │
│ result = yield execute_tool_call(script, character, tool_call)              │
│                                                                             │
│ Includes:                                                                   │
│   - Parameter validation                                                    │
│   - Timeout handling                                                        │
│   - Retry logic for transient failures                                      │
│   - Result caching (if tool is cacheable)                                   │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 7: INJECT TOKEN ADVISORY (lines 519-522)                               │
│ ─────────────────────────────────────────────────────────────────────────── │
│ token_usage = _calculate_token_usage(script)                                │
│ result = inject_token_advisory(result, token_usage)                         │
│                                                                             │
│ Adds advisory to result if approaching limits:                              │
│   MODERATE (50-70%): "Consider wrapping up"                                 │
│   HIGH (70-80%): "Complete current task soon"                               │
│   CRITICAL (80%+): "Must conclude immediately"                              │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 8: CHECK TERMINATION CONDITIONS (lines 524-548)                        │
│ ─────────────────────────────────────────────────────────────────────────── │
│                                                                             │
│ TERMINAL tool (say, page, whisper):                                         │
│   └─> termination_reason = "terminal_tool", break                           │
│       (Ends loop to await player response)                                  │
│                                                                             │
│ DANGEROUS tool (execute_command, set_attribute):                            │
│   └─> termination_reason = "dangerous_tool", break                          │
│       (Single execution for safety)                                         │
│                                                                             │
│ CRITICAL token usage (80%+):                                                │
│   └─> termination_reason = "critical_tokens", break                         │
│                                                                             │
│ Otherwise: continue to next iteration                                       │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
                            [NEXT ITERATION]

3. Termination Conditions

Condition Reason Code Behavior
TERMINAL tool terminal_tool Loop ends, await player response
DANGEROUS tool dangerous_tool Single execution per tick
NOOP returned noop LLM explicitly signaled completion
Max iterations max_iterations Safety limit reached
Critical tokens (80%+) critical_tokens Context limit protection
LLM error llm_error Network/API failure
Parse error parse_error Invalid LLM response format

4. Tool Categories

Category Loop Behavior Examples
SAFE_CHAIN Continue loop inspect_location, recall_memories, search_objects
TERMINAL End loop say, page, whisper, emote
DANGEROUS End loop execute_command, set_attribute
ASYNC_REQUIRED Continue loop store_memory, delegate_task

5. Loop Result Structure

{
    "iterations": 3,              # Number of iterations executed
    "results": [                  # Results from each iteration
        {"success": True, "tool": "inspect_location", ...},
        {"success": True, "tool": "recall_memories", ...},
        {"success": True, "tool": "say", ...},
    ],
    "termination_reason": "terminal_tool",
    "success": True,              # At least one successful execution
    "success_count": 3,
    "pattern_mode": "react_loop",
    "max_allowed_iterations": 10,
}

6. Key Files

File Relevant Lines Purpose
assistant_script.py 600-812 at_tick() entry point
tool_execution.py 381-573 execute_react_loop()
tool_execution.py 52-277 execute_tool_call()
tools/__init__.py - check_loop_continuation(), inject_token_advisory()

Document created: 2025-12-06