User Query Processing Flow

This document describes how a user query flows through Soothe from CLI entry to final response.

Repository layout: The runner lives under packages/soothe/src/soothe/runner/ and the StrangeLoop engine under packages/soothe/src/soothe/foundation/sloop/. The CLI Typer app lives under packages/soothe-cli/src/soothe_cli/.

Overview

User Input → CLI Entry → Daemon → Runner → Planning → Agent Execution → Response

1. Entry Points

CLI Entry

The CLI supports two primary modes:

soothe -p "query"     →  Headless mode (single query)
soothe                →  TUI mode (interactive)

Flow:

main.py:main()
    ↓
run_cmd.py:run_impl()
    ↓
┌─────────────┐
│ --no-tui?   │
└─────────────┘
    ↓           ↓
   YES         NO
    ↓           ↓
headless.py   run_tui()
    ↓
run_headless()

Key files:

  • packages/soothe-cli/src/soothe_cli/cli/main.py — Typer app entry point
  • packages/soothe-cli/src/soothe_cli/cli/commands/run_cmd.pyrun_impl() routing logic
  • packages/soothe-cli/src/soothe_cli/cli/execution/headless.py — Headless execution

Daemon Connection

In headless mode, the system checks for a running daemon:

run_headless()
    ↓
SootheDaemon._is_socket_live()?
    ↓              ↓
   YES            NO
    ↓              ↓
Connect via    Auto-start daemon
DaemonClient      ↓
    ↓         daemon_cmd.py:daemon_start()
run_headless_via_daemon()

Key files:

  • packages/soothe-cli/src/soothe_cli/cli/execution/daemon.py — Daemon client interaction

2. Daemon Processing

The daemon server handles incoming queries:

DaemonClient.send_new_thread(text, thread_id)
    ↓
DaemonServer._handle_transport_message()
    ↓
_handle_client_message()
    ↓
┌────────────────────┐
│ Message Type:       │
│ - "input"           │ → _run_query()
│ - "resume_thread"   │ → Resume existing thread
│ - "interrupt"       │ → Handle interrupt
└────────────────────┘
    ↓
_run_query()
    ↓
SootheRunner.astream(text, thread_id, ...)
    ↓
Broadcast events → EventBus → Subscribed clients

Key files:

  • packages/soothe/src/soothe/daemon/server.pySootheDaemon class
  • packages/soothe/src/soothe/daemon/_handlers.py — Query handling logic
  • packages/soothe/src/soothe/daemon/event_bus.py — Event routing

3. Runner Orchestration

SootheRunner.astream() is the central orchestration point:

SootheRunner.astream(text, thread_id, autonomous, subagent)
    ↓
┌─────────────────────────────────────┐
│         Initial Classification      │
│   UnifiedClassifier.classify()     │
└─────────────────────────────────────┘
                 ↓
    ┌────────────┼────────────┐
    ↓            ↓            ↓
quiz        agentic      subagent?
    ↓            ↓            ↓
quiz        default      direct
response    mode         execution
                ↓
        _run_strange_loop()

Routing Logic:

Condition Path Description
subagent specified Direct Route directly to subagent
autonomous=True Autonomous Goal-driven execution
Default Agentic Loop Iterative observe-act-verify

Key files:

  • packages/soothe/src/soothe/runner/__init__.py — Runner package entry
  • packages/soothe/src/soothe/runner/_runner_strange_loop.py — Agentic loop
  • packages/soothe/src/soothe/runner/_runner_autonomous.py — Autonomous mode

4. Agentic Loop (RFC-624)

The default execution mode follows an iterative observe-act-verify cycle:

┌─────────────────────────────────────────────────────┐
│                  AGENTIC LOOP                       │
│           (max_iterations: default 3)              │
└─────────────────────────────────────────────────────┘
                        │
         ┌──────────────┼──────────────┐
         ↓              ↓              ↓
    ┌─────────┐   ┌──────────┐   ┌───────────┐
    │ OBSERVE │ → │   ACT    │ → │  VERIFY   │
    └─────────┘   └──────────┘   └───────────┘
         │              │              │
         ↓              ↓              ↓
    - Context      - Plan         - Reflect
      projection     creation      on results
    - Memory        - Agent       - Should
      recall         execution     continue?
    - Classify     - Tools

Observe Phase

# _agentic_observe()
- Context projection from context ledger
- Memory recall from memory backend
- Unified classification of task

Act Phase

# _agentic_act()
- Delegate to `StrangeLoop` (`packages/soothe/src/soothe/foundation/sloop/engine/strange_loop.py`)
- Plan via `LLMPlanner` (`packages/soothe/src/soothe/foundation/sloop/cognition/planner.py`, RFC-604 two-phase assess + generate)
- Execute plan (single step or multi-step DAG via `packages/soothe/src/soothe/foundation/sloop/engine/executor.py`)

Verify Phase

# _agentic_verify()
- Planner reflection on results
- Decision: continue to next iteration or complete

Key files:

  • packages/soothe/src/soothe/runner/_runner_strange_loop.py — Agentic loop
  • packages/soothe/src/soothe/runner/_runner_phases.py — Phase helpers

5. Planning (StrangeLoop + LLMPlanner)

Planning is implemented inside StrangeLoop, not a separate cognition/planning package. LLMPlanner (packages/soothe/src/soothe/foundation/sloop/cognition/planner.py, RFC-604) performs:

  1. StatusAssessment — structured status, goal_progress, confidence, require_goal_completion
  2. PlanGeneration (when not done) — plan_action, AgentDecision steps, next_action

Routing (quiz fast path vs agentic vs subagent) happens earlier in SootheRunner / unified classification. Historical AutoPlanner / SimplePlanner / ClaudePlanner routers have been removed (IG-150 consolidation).

Plan structure (conceptual)

PlanResult / AgentDecision still expose DAG-shaped work:

class Plan(BaseModel):
    steps: list[PlanStep]

class PlanStep(BaseModel):
    id: str
    description: str
    execution_hint: str | None
    depends_on: list[str] = []  # DAG dependencies

Key files:

  • packages/soothe/src/soothe/foundation/sloop/cognition/planner.pyLLMPlanner
  • packages/soothe/src/soothe/foundation/sloop/cognition/phase.py — Plan phase wiring
  • packages/soothe/src/soothe/foundation/sloop/prompts/builder.pyPromptBuilder for assess/generate prompts

6. Step Execution (DAG-based)

Multi-step plans are executed inside StrangeLoop (RFC-220): the compiled graph’s execute phase uses StepScheduler / Executor to respect DAG dependencies and optional parallelism. Each ready step is run as a CoreAgent astream turn with LangGraph interrupt auto-resume in Executor._core_agent_astream_with_interrupt_resume.

StrangeLoop graph (execute_steps)
    ↓
Executor: StepScheduler(plan)  →  Build DAG from depends_on
    ↓
ready_steps = scheduler.ready_steps(limit, parallelism)
    ↓
Single step OR asyncio.gather for parallel steps
    ↓
CoreAgent.astream(stream_input, ...)  (+ interrupt auto-resume loop)
    ↓
PlanStepCompletedEvent / PlanStepFailedEvent (protocol events)

Parallel Execution

Steps with no dependencies run in parallel:

Step A (no deps) ─┬─→ Step C (depends on A, B)
Step B (no deps) ─┘

Execution: A + B parallel, then C

Key files:

  • packages/soothe/src/soothe/foundation/sloop/engine/executor.py — execute waves, DAG batches, CoreAgent streaming
  • packages/soothe/src/soothe/foundation/sloop/cognition/plan_dag_normalizer.py — DAG normalization
  • packages/soothe/src/soothe/foundation/sloop/state/schemas.pyLoopState, step ledger metadata

7. Agent Execution

CoreAgent streaming (execute phase)

Execute steps stream the compiled LangGraph agent via CoreAgent.astream (see Executor._core_agent_astream_with_interrupt_resume for interrupt / resume). The graph input dict carries messages plus optional workspace, git_status, routing_classification, and related fields for middleware and system-prompt XML injection (RFC-104).

# Executor builds graph input (simplified)

stream_input = {
    "messages": messages,
    "workspace": workspace,
    "git_status": git_status,
    "routing_classification": routing_classification,
}

async for chunk in core_agent.astream(
    stream_input,
    stream_mode=["messages", "updates", "custom"],
    subgraphs=True,
):
    # On __interrupt__, Executor auto-resumes in-process (approve tools / empty ask_user)
    yield chunk

Event Types

Mode Content
messages AI messages, tool calls
updates State updates
custom Protocol events

Key files:

  • packages/soothe/src/soothe/foundation/sloop/engine/executor.py — CoreAgent streaming for execute waves
  • packages/soothe/src/soothe/foundation/core/agent/_builder.py — CoreAgent factory

8. Response Streaming

Events flow back to the client through the daemon:

agent.astream() yields (namespace, mode, data)
    ↓
Runner yields events
    ↓
Daemon._run_query() receives events
    ↓
_broadcast(event_msg)
    ↓
EventBus.publish(topic="loop:{loop_id}")
    ↓
Subscribed client sessions receive event
    ↓
Transport.send() to client
    ↓
CLI EventProcessor + Renderer
    ↓
Output to stdout/stderr

Client Event Handling

# In CLI/TUI
async for event in daemon_client.subscribe_thread(thread_id):
    match event["type"]:
        case "status":
            # idle, running, stopped
        case "event":
            namespace = event["namespace"]
            mode = event["mode"]
            data = event["data"]
            # Render based on event type

9. Autonomous Mode (RFC-624)

When autonomous=True, the system uses explicit goal-driven execution:

_run_autonomous()
    ↓
_context_engine.create_goal(user_input)
    ↓
GoalCreatedEvent
    ↓
┌─────────────────────────────────────┐
│      GOAL EXECUTION LOOP            │
└─────────────────────────────────────┘
    ↓
ready_goals = context_engine.list_goals()
    ↓
_execute_autonomous_goal() per goal
    ↓
- Plan creation
- Step loop execution
- Reflection
- Goal directives processing
    ↓
GoalCompletedEvent / GoalFailedEvent
    ↓
FinalReportEvent

Key files:

  • packages/soothe/src/soothe/runner/_runner_autonomous.py
  • packages/soothe/src/soothe/foundation/context/engine.py

10. Event Flow Summary

User Query
    │
    ▼
┌─────────────┐
│ CLI Entry   │  main.py → run_impl()
└─────────────┘
    │
    ▼
┌─────────────┐
│ Daemon      │  Client → Server → _run_query()
└─────────────┘
    │
    ▼
┌─────────────┐
│ Runner      │  SootheRunner.astream()
└─────────────┘
    │
    ▼
┌─────────────┐
│ Classify    │  Unified routing (intent, complexity, workspace, …)
└─────────────┘
    │
    ▼
┌─────────────┐
│ Plan        │  `LLMPlanner` / StrangeLoop Plan phase
└─────────────┘
    │
    ▼
┌─────────────┐
│ Execute     │  Single step or DAG parallel
└─────────────┘
    │
    ▼
┌─────────────┐
│ Stream      │  agent.astream() → events
└─────────────┘
    │
    ▼
┌─────────────┐
│ Respond     │  EventBus → Client → stdout
└─────────────┘

Quick Reference

Key files (quick reference)

Area File Purpose
CLI packages/soothe-cli/src/soothe_cli/cli/main.py Entry point
CLI packages/soothe-cli/src/soothe_cli/cli/commands/run_cmd.py Run command
CLI packages/soothe-cli/src/soothe_cli/cli/execution/headless.py Headless mode
Daemon packages/soothe/src/soothe/daemon/server.py Daemon server
Daemon packages/soothe/src/soothe/daemon/_handlers.py Query handling
Runner packages/soothe/src/soothe/runner/__init__.py Runner package
Runner packages/soothe/src/soothe/runner/_runner_strange_loop.py Agentic loop
Runner packages/soothe/src/soothe/runner/_runner_phases.py Pre-stream (thread, policy, memory, plan bootstrap)
Planning packages/soothe/src/soothe/foundation/sloop/cognition/planner.py LLMPlanner (RFC-604)
Agent packages/soothe/src/soothe/foundation/core/agent/_builder.py CoreAgent factory

RFC References

  • RFC-201 — StrangeLoop plan–execute (observe / act / verify)
  • RFC-624 — Autonomous goal management (ContextEngine)
  • RFC-200 — Autonomous goal management (archived, superseded by RFC-624)
  • RFC-604 — Two-phase plan assess and generation (LLMPlanner)
  • RFC-450 — Daemon communication