From 7ca60844847b40431ccc9a475448c22efed0f8ec Mon Sep 17 00:00:00 2001 From: laansdole Date: Sat, 7 Feb 2026 10:58:49 +0700 Subject: [PATCH 1/9] Add .worktrees/ to .gitignore for git worktree support --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 96b6481f9..1e65d1cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,7 @@ logs/ node_modules/ data/ temp/ -WareHouse/ \ No newline at end of file +WareHouse/ + +# Git worktrees +.worktrees/ \ No newline at end of file From 42cd389d59976e1abc094c19905604095e2ae688 Mon Sep 17 00:00:00 2001 From: laansdole Date: Sat, 7 Feb 2026 12:27:29 +0700 Subject: [PATCH 2/9] feat: Add loop_timer node for time-based loop control - Add LoopTimerConfig with duration units support (seconds/minutes/hours) - Implement LoopTimerNodeExecutor with standard and passthrough modes - Register loop_timer node type in builtin_nodes.py - Update documentation (execution_logic.md, YAML_FORMAT_QUICK_GUIDE.md) - Add demo workflows for both modes Closes: add-loop-timer change proposal --- YAML_FORMAT_QUICK_GUIDE.md | 915 ++++++++++++++++++ docs/user_guide/en/execution_logic.md | 1 + entity/configs/node/loop_timer.py | 122 +++ runtime/node/builtin_nodes.py | 21 +- runtime/node/executor/loop_timer_executor.py | 148 +++ yaml_instance/demo_loop_timer.yaml | 52 + .../demo_loop_timer_passthrough.yaml | 51 + 7 files changed, 1304 insertions(+), 6 deletions(-) create mode 100644 YAML_FORMAT_QUICK_GUIDE.md create mode 100644 entity/configs/node/loop_timer.py create mode 100644 runtime/node/executor/loop_timer_executor.py create mode 100644 yaml_instance/demo_loop_timer.yaml create mode 100644 yaml_instance/demo_loop_timer_passthrough.yaml diff --git a/YAML_FORMAT_QUICK_GUIDE.md b/YAML_FORMAT_QUICK_GUIDE.md new file mode 100644 index 000000000..03cadff10 --- /dev/null +++ b/YAML_FORMAT_QUICK_GUIDE.md @@ -0,0 +1,915 @@ +# Deep Research: YAML Scenario File Format for ChatDev 2.0 (DevAll) + +This document details the YAML format used for defining multi-agent workflows in ChatDev 2.0. It is based on an analysis of the codebase, specifically the configuration schemas in `entity/configs/` and validation logic in `check/`. + +## 1. File Structure Overview + +A valid workflow file consists of three main top-level keys: + +```yaml +version: "0.0.0" # Optional, defaults to "0.0.0" +vars: # Optional global variables + API_KEY: ${API_KEY} + BASE_URL: ${BASE_URL} +graph: # REQUIRED: The core workflow definition + id: "MyWorkflow" + description: "Description of what this workflow does" + nodes: [] # List of Node objects + edges: [] # List of Edge objects +``` + +## 2. Graph Definition (`graph`) + +| Field | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `id` | `str` | Yes | Unique identifier (alphanumeric, underscores, hyphens). | +| `nodes` | `List[Node]` | Yes | List of nodes. Must contain at least one node. | +| `edges` | `List[Edge]` | Yes | List of directed edges connecting nodes. | +| `description` | `str` | No | Human-readable description. | +| `log_level` | `enum` | No | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`. Default: `DEBUG`. | +| `is_majority_voting` | `bool` | No | Default: `false`. | +| `memory` | `List` | No | List of `MemoryStoreConfig` definitions. | +| `start` | `List[str]` | No* | List of start node IDs. *Inferred if graph has unique source.* | +| `end` | `List[str]` | No* | List of end node IDs. *Inferred if graph has unique sink.* | +| `initial_instruction` | `str` | No | Initial instruction text for the user. | +| `organization` | `str` | No | Organization name. | + +## 3. Node Configuration (`nodes`) + +Each item in the `nodes` list represents a processing unit. + +```yaml +- id: "NodeID" # Required: Unique ID + type: "agent" # Required: Node type (agent, human, loop_counter, etc.) + description: "..." # Optional + context_window: 0 # Optional: 0 (clear), -1 (unlimited), N (keep last N) + config: # Required: Configuration specific to the 'type' + ... +``` + +### Common Node Types & Configurations + +#### **`agent`** +Represents an LLM-based agent. +```yaml +type: agent +config: + name: "gpt-4o" # Required: Model name + provider: "openai" # Required: Provider (openai, etc.) + role: "System prompt..." # Optional: System message + base_url: ${BASE_URL} # Optional: Override URL + api_key: ${API_KEY} # Optional: API Key + params: # Optional: Model parameters + temperature: 0.7 + tooling: # Optional: List of tools + - type: function + config: + tools: + - name: "read_file" + memories: [] # Optional: Memory attachments + retry: # Optional: Retry configuration + enabled: true + max_attempts: 5 +``` + +#### **`human`** +Represents a human interaction point. +```yaml +type: human +config: + description: "Instruction for the human user" + memories: # Optional: Memory attachments for context retrieval and writing + - name: "memory_name" # Reference to a memory store defined in graph.memory + read: true # Enable memory retrieval + write: true # Enable memory writing (human feedback stored) + top_k: 10 # Number of relevant items to retrieve + similarity_threshold: 0.5 # Minimum similarity score (0.0-1.0) + retrieve_stage: # When to retrieve memory + - gen # During generation stage (default if not specified) + - pre_gen_thinking # Before thinking step +``` + +**Note**: +- Memory context retrieved from attached memory stores will be displayed to the human user alongside the description and input data. +- When `write: true`, human feedback will be automatically stored in the memory for future retrieval. +- `retrieve_stage` controls when memory is retrieved. If not specified, defaults to `gen` stage. + +**Best Practice - Memory Flow Patterns**: + +When designing workflows with shared memory between agent and human nodes, consider who should write to which memory: + +1. **State Memory Pattern** (Agent writes, Human reads): + ```yaml + # Environment/state generator agent + - id: environment + type: agent + config: + memories: + - name: environment_memory + read: true + write: true # Agent owns this memory + top_k: 10 + similarity_threshold: 0.0 # IMPORTANT: Use 0.0 to always retrieve recent items + retrieve_stage: + - gen # Retrieve during generation (before model call) + + # Human controller + - id: HumanControl + type: human + config: + memories: + - name: environment_memory + read: true # Human only reads to see history + write: false # Human does NOT write to state memory + top_k: 10 + similarity_threshold: 0.0 # Always show recent history + ``` + **Use when**: Agent generates state/context that human needs to review but not modify directly. The human provides commands via edges, and the agent interprets them to update state. + + **Why `similarity_threshold: 0.0`?** When the agent receives user input like "continue", the query text has low semantic similarity to past state descriptions (e.g., "COVID-19 outbreak" vs "continue"). Setting threshold to 0.0 ensures the agent ALWAYS retrieves its most recent states regardless of query similarity, maintaining continuity. + + **Why `retrieve_stage: gen`?** The `reflection` thinking type only processes memory during the generation stage. Using `pre_gen_thinking` would cause memory to be retrieved but ignored. With `gen` stage, memory is injected into the conversation BEFORE the model generates output, ensuring continuity. + +2. **Feedback Memory Pattern** (Human writes, Agent reads): + ```yaml + # Agent processes feedback + - id: processor + type: agent + config: + memories: + - name: feedback_memory + read: true + write: false + + # Human provides feedback + - id: reviewer + type: human + config: + memories: + - name: feedback_memory + read: true + write: true # Human owns feedback memory + ``` + **Use when**: Human provides annotations, corrections, or judgments that agents need to incorporate. + +3. **Separate Memory Pattern** (Isolated stores): + ```yaml + # Each node has its own memory store + - id: agent_a + memories: + - name: agent_a_memory + + - id: human_b + memories: + - name: human_b_memory + ``` + **Use when**: No memory sharing needed; each node maintains independent history. + +#### **`loop_counter`** +Controls loops by counting iterations. +```yaml +type: loop_counter +config: + max_iterations: 5 # Max allowed loops + reset_on_emit: true # Reset count when condition is met + message: "" # Optional message +``` + +#### **`loop_timer`** +Controls loops by tracking elapsed time. +```yaml +type: loop_timer +config: + max_duration: 5 # Max allowed time + duration_unit: seconds # Unit: 'seconds', 'minutes', or 'hours' + reset_on_emit: true # Reset timer when condition is met + message: "" # Optional message + passthrough: false # Passthrough mode (same as loop_counter) +``` + +#### **`passthrough`** +A simple node that passes data through without modification. +```yaml +type: passthrough +config: {} +``` + +#### **`literal`** +Injects static content into the workflow. +```yaml +type: literal +config: + content: "Static content text" + role: "user" # Role of the message (user, assistant, system) +``` + +#### **`python_runner`** (implied from imports) +Executes Python code. +```yaml +type: python_runner +config: + timeout_seconds: 60 +``` + +## 4. Edge Configuration (`edges`) + +Defines the flow between nodes. + +```yaml +- from: "SourceNodeID" # Required + to: "TargetNodeID" # Required + trigger: true # Default: true. Can trigger target execution? + condition: "true" # Condition to traverse (default "true") + carry_data: true # Pass output of source to target? + keep_message: false # Mark message as 'keep' in target context? + clear_context: false # Clear target's context before adding new data? + clear_kept_context: false # Clear 'kept' messages in target? + processor: # Optional: Transform data before passing to target + type: template + config: + template: "..." + dynamic: # Optional: Dynamic execution (map/tree patterns) + type: map + config: + split_type: regex +``` + +### 4.1 Context Management + +Context management controls how messages accumulate in a node's execution context across workflow execution. + +#### **Context vs Memory** +- **Context**: Message queue visible to a node during execution (temporary, workflow-scoped) +- **Memory**: Persistent storage across workflow runs (permanent, stored in vector database) + +#### **Message Lifecycle** +1. Source node produces output +2. Edge delivers message to target node's context +3. Target node processes messages in its context +4. Context can be cleared or preserved based on edge configuration + +#### **Edge Parameters for Context Control** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `keep_message` | `bool` | `false` | Mark message as "kept" (survives soft reset) | +| `clear_context` | `bool` | `false` | Clear non-kept messages before adding new data | +| `clear_kept_context` | `bool` | `false` | Clear kept messages (requires `clear_context: true`) | + +#### **Soft Reset vs Hard Reset** + +**Soft Reset** (`clear_context: true`): +- Clears only messages with `keep=false` +- Preserves messages marked with `keep_message: true` +- Use case: Clear temporary data but keep system instructions + +```yaml +- from: UserInput + to: Agent + clear_context: true # Soft reset + keep_message: false +``` + +**Hard Reset** (`clear_context: true` + `clear_kept_context: true`): +- Clears ALL messages (including `keep=true`) +- Complete context isolation +- Use case: Start fresh between workflow rounds + +```yaml +- from: LoopControl + to: StartNode + clear_context: true # Hard reset + clear_kept_context: true +``` + +#### **Context Reset Demo Example** + +See `yaml_instance/demo_context_reset.yaml` for a working demonstration: + +**Workflow Structure**: +1. `entry_normal` → `collector` (`keep_message: false`) - Normal message +2. `entry_keep` → `collector` (`keep_message: true`) - Kept message +3. `soft_reset` → `collector` (`clear_context: true`) - Soft reset +4. `hard_reset` → `collector` (`clear_context: true`, `clear_kept_context: true`) - Hard reset + +**Execution Flow**: +- After `entry_normal` + `entry_keep`: collector has 2 messages +- After `soft_reset`: collector has `entry_keep` + `soft_reset` (normal cleared) +- After `hard_reset`: collector has only `hard_reset` (all previous cleared) + +#### **Common Use Cases** + +**Preserve System Instructions**: +```yaml +- from: SystemSetup + to: Agent + keep_message: true # This survives soft resets + +- from: UserInput + to: Agent + clear_context: true # Clears user messages but keeps system +``` + +**Recursive Workflows with Isolation**: +```yaml +- from: HumanControl + to: StartNode + clear_context: true # Fresh start each round + clear_kept_context: true # Complete isolation + condition: + type: keyword + config: + none: ["EXIT"] # Loop until user says "EXIT" +``` + +**Accumulate Important Context**: +```yaml +- from: CriticalNode + to: Aggregator + keep_message: true # Don't lose this data + +- from: TemporaryNode + to: Aggregator + keep_message: false # Can be cleared later +``` + +### 4.2 Condition Configuration + +Edges can have conditions that determine when they are traversed. + +#### **Keyword Condition** +Match specific keywords in the source node output. + +```yaml +condition: + type: keyword + config: + any: ["APPROVED", "SUCCESS"] # Match if ANY keyword present + none: ["ERROR", "FAILED"] # Match if NONE present + all: ["READY", "VERIFIED"] # Match if ALL present +``` + +#### **Regex Condition** +Match output against a regular expression. + +```yaml +condition: + type: regex + config: + pattern: "^RESULT: .*" + flags: ["IGNORECASE"] +``` + +## 5. Memory Configuration + +Memory stores enable persistent data across workflow runs using vector similarity search. + +### 5.1 Graph-Level Memory Stores + +Define memory stores in the `graph.memory` section: + +```yaml +graph: + memory: + - name: patient_memory + type: simple + config: + embedding: + provider: openai + base_url: ${BASE_URL} + api_key: ${API_KEY} + model: text-embedding-bge-reranker-v2-m3 +``` + +### 5.2 Node-Level Memory Attachments + +Attach memory to nodes for read/write operations: + +```yaml +nodes: + - id: PatientGenerator + type: agent + config: + memories: + - name: patient_memory # Must match graph.memory name + top_k: 50 # Retrieve top 50 similar items + similarity_threshold: 0.3 # Minimum similarity score (0-1) + retrieve_stage: # When to retrieve memory + - pre_gen_thinking # Before thinking step + read: true # Enable reading from memory + write: true # Enable writing to memory +``` + +#### **Memory Parameters** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | `str` | Memory store name (must exist in `graph.memory`) | +| `top_k` | `int` | Number of similar items to retrieve (default: 10) | +| `similarity_threshold` | `float` | Minimum similarity score 0-1 (default: 0.5) | +| `retrieve_stage` | `List[str]` | When to retrieve: `pre_gen_thinking`, `post_gen`, etc. | +| `read` | `bool` | Enable reading from memory (default: false) | +| `write` | `bool` | Enable writing to memory (default: false) | + +#### **Use Cases** + +**Cross-Round Uniqueness** (High top_k, Low threshold): +```yaml +memories: + - name: patient_memory + top_k: 50 # Cast wide net + similarity_threshold: 0.3 # Catch similar items + retrieve_stage: [pre_gen_thinking] + read: true + write: true +``` + +**Recent Context Preservation** (Low top_k, Medium threshold): +```yaml +memories: + - name: environment_memory + top_k: 10 # Only recent context + similarity_threshold: 0.5 # Moderately similar + retrieve_stage: [pre_gen_thinking] + read: true + write: true +``` + +**Human Node Memory Support**: + +Human nodes support full memory functionality including read, write, and retrieve_stage configuration: + +```yaml +nodes: + - id: HumanFeedback + type: human + config: + description: "Review the article and provide feedback..." + memories: + - name: feedback_memory + read: true # Retrieve and display previous feedback + write: true # Store human feedback for future reference + top_k: 10 + similarity_threshold: 0.5 + retrieve_stage: # Control when memory is retrieved + - gen # Default stage for human nodes +``` + +**Features**: +- **Read**: Retrieved memory is displayed alongside the description to help users make informed decisions +- **Write**: Human feedback is automatically stored in memory after user input +- **retrieve_stage**: Controls when memory retrieval occurs. Available stages: `gen`, `pre_gen_thinking`, `post_gen_thinking`, `finished` +- If `retrieve_stage` is not specified, defaults to `gen` stage + +**Use Cases**: +- **Feedback Consistency**: Store and retrieve past user feedback to maintain consistent review standards +- **Context Awareness**: Display historical decisions to inform current choices +- **Learning from History**: Build up knowledge base from human inputs over multiple workflow runs + +## 6. Thinking Module + +The thinking module adds reflection and planning capabilities to agent nodes. + +### 6.1 Reflection Thinking + +Enables post-generation reflection and refinement: + +```yaml +nodes: + - id: environment + type: agent + config: + thinking: + type: reflection + config: + reflection_prompt: | + You have just generated output. Now reflect and refine: + + QUALITY CHECK: + 1. Does the output meet all requirements? + 2. Is it consistent with retrieved memory? + 3. Are there any contradictions or gaps? + + Output ONLY the final refined result with no commentary. +``` + +### 6.2 Thinking with Memory Integration + +Combine thinking with memory retrieval for intelligent context-aware processing: + +```yaml +nodes: + - id: PatientGenerator + type: agent + config: + memories: + - name: patient_memory + retrieve_stage: [pre_gen_thinking] # Retrieve BEFORE thinking + read: true + write: true + thinking: + type: reflection + config: + reflection_prompt: | + Review retrieved patient memory and verify uniqueness: + - Are there duplicate names or symptom patterns? + - Generate fresh alternatives if conflicts exist. + + Output the final unique patient list. +``` + +**Execution Flow**: +1. Node generates initial output +2. Memory is retrieved (if `retrieve_stage: [pre_gen_thinking]`) +3. Retrieved memory + initial output passed to thinking module +4. Thinking module refines and returns final output + +## 7. Dynamic Execution + +Dynamic execution enables parallel processing patterns like map-reduce. + +### 7.1 Map Pattern (Parallel Fan-Out) + +Split input into multiple parallel branches: + +```yaml +edges: + - from: PatientGenerator + to: NurseIntake + dynamic: + type: map + config: + split_type: json_array # Split JSON array into items + item_name: patient # Variable name for each item +``` + +**How it works**: +1. `PatientGenerator` outputs JSON array: `[{patient1}, {patient2}, {patient3}]` +2. Edge splits into 3 parallel executions of `NurseIntake` +3. Each execution receives one patient object + +### 7.2 Supported Split Types + +| Split Type | Description | Example | +|------------|-------------|---------| +| `json_array` | Split JSON array | `[{...}, {...}]` → multiple items | +| `regex` | Split by regex pattern | Split by delimiter | +| `lines` | Split by newlines | Multi-line text → separate lines | + +### 7.3 Real-World Example (Hospital Simulation) + +```yaml +# Patient generator creates array of patients +- id: PatientGenerator + type: agent + config: + role: "Generate array of patients in JSON format" + +# Parallel fan-out to process each patient +edges: + - from: PatientGenerator + to: NurseIntake + dynamic: + type: map + config: + split_type: json_array + item_name: patient + +# Each nurse intake processes one patient in parallel +- id: NurseIntake + type: agent + config: + role: "Process patient: {{patient}}" +``` + +**Result**: 5 patients → 5 parallel NurseIntake executions + +## 8. Edge Processors + +Edge processors transform data before passing to target nodes. + +### 8.1 Template Processor + +Use template strings to format data: + +```yaml +edges: + - from: HumanControl + to: environment + processor: + type: template + config: + template: | + USER INPUT: {{content}} + + INSTRUCTION: Update the environment based on user input. + - If input is minimal ("continue"), maintain scenario with time progression + - If input adds elements, integrate them while preserving context +``` + +**Variables available**: +- `{{content}}`: Message content from source node +- Custom variables from node outputs + +### 8.2 Regex Extract Processor + +Extract specific data using regex: + +```yaml +edges: + - from: Analyzer + to: Processor + processor: + type: regex_extract + config: + pattern: "RESULT: (.*)" + group: 1 +``` + +## 9. Common Errors & Best Practices + +1. **Unique IDs**: Ensure every `id` in `nodes` is unique. Duplicate IDs cause validation failure. +2. **Valid References**: `from` and `to` in `edges` must match exactly with a defined `id` in `nodes`. +3. **Root Structure**: The file **must** have the `graph:` key. `vars:` defines placeholders like `${API_KEY}`. +4. **Type Consistency**: + * `context_window` is an **integer**, not a string. + * `condition` is a string expression (e.g., `"true"`, `"false"`) or a config object. +5. **Agent Config**: + * `name` and `provider` are mandatory for `agent` nodes. + * `tooling` must be a list of tool configurations. +6. **Environment Variables**: Use `${VAR_NAME}` in YAML and define them in `.env` or the `vars` section. The validation logic checks schema but resolves variables at runtime. + +## 9. Common Errors & Best Practices + +1. **Unique IDs**: Ensure every `id` in `nodes` is unique. Duplicate IDs cause validation failure. +2. **Valid References**: `from` and `to` in `edges` must match exactly with a defined `id` in `nodes`. +3. **Root Structure**: The file **must** have the `graph:` key. `vars:` defines placeholders like `${API_KEY}`. +4. **Type Consistency**: + * `context_window` is an **integer**, not a string. + * `condition` is a string expression (e.g., `"true"`, `"false"`) or a config object. +5. **Agent Config**: + * `name` and `provider` are mandatory for `agent` nodes. + * `tooling` must be a list of tool configurations. +6. **Environment Variables**: Use `${VAR_NAME}` in YAML and define them in `.env` or the `vars` section. The validation logic checks schema but resolves variables at runtime. +7. **Memory Configuration**: + * Memory store names in `nodes.config.memories` must exist in `graph.memory`. + * `retrieve_stage` determines when memory is retrieved (before/after thinking). + * Balance `top_k` and `similarity_threshold` based on use case. +8. **Context Management**: + * Use `keep_message: true` for system instructions and critical context. + * Use soft reset (`clear_context: true`) to clear temporary data. + * Use hard reset (`clear_context + clear_kept_context`) for complete isolation. +9. **Dynamic Execution**: + * Ensure source node output format matches `split_type` (e.g., JSON array for `json_array`). + * Use descriptive `item_name` for clarity in target node templates. +10. **Thinking Module**: + * Keep reflection prompts focused and concise. + * Clearly specify expected output format. + * Use thinking with memory retrieval for context-aware refinement. + +## 10. Complete Example: Recursive Hospital Simulation + +This example demonstrates advanced features: recursive loops, memory, thinking, dynamic execution, and context management. + +See `yaml_instance/simulation_hospital.yaml` for the full implementation. + +**Key Features**: + +1. **Environment Continuity** (`environment` node): + - Memory retrieval at `pre_gen_thinking` stage + - Reflection thinking detects "continue" vs "evolve" modes + - Maintains scenario context across rounds + +2. **Unique Patient Generation** (`PatientGenerator` node): + - Memory tracks all previously generated patients + - Reflection thinking verifies uniqueness + - Dynamic execution fans out to parallel patient processing + +3. **Recursive Loop** (edges): + - `HumanControl` → `environment` with hard reset + - Condition: `none: ["SIMULATION ENDED."]` (continues) + - Complete context isolation between rounds + +4. **Parallel Processing** (dynamic execution): + - Patient array splits into parallel nurse/doctor workflows + - Each patient processed independently + - Results aggregate before next round + +**Simplified Structure**: + +```yaml +graph: + memory: + - name: patient_memory + type: simple + - name: environment_memory + type: simple + + nodes: + - id: environment + type: agent + config: + memories: + - name: environment_memory + top_k: 10 + similarity_threshold: 0.5 + retrieve_stage: [pre_gen_thinking] + read: true + write: true + thinking: + type: reflection + config: + reflection_prompt: "Detect mode and maintain continuity..." + + - id: PatientGenerator + type: agent + config: + memories: + - name: patient_memory + top_k: 50 + similarity_threshold: 0.3 + retrieve_stage: [pre_gen_thinking] + read: true + write: true + thinking: + type: reflection + config: + reflection_prompt: "Verify uniqueness against memory..." + + - id: HumanControl + type: human + config: + description: "Enter next scenario or 'SIMULATION ENDED.'" + + - id: SimulationEnd + type: literal + config: + content: "Simulation complete." + + edges: + # Dynamic parallel processing + - from: PatientGenerator + to: NurseIntake + dynamic: + type: map + config: + split_type: json_array + item_name: patient + + # Recursive loop with hard reset + - from: HumanControl + to: environment + clear_context: true # Hard reset + clear_kept_context: true # Complete isolation + condition: + type: keyword + config: + none: ["SIMULATION ENDED."] + + # Exit condition + - from: HumanControl + to: SimulationEnd + condition: + type: keyword + config: + any: ["SIMULATION ENDED."] +``` + +## 11. Validation + +Use the project's validation tool to check your YAML: +```bash +uv run python -m check.check --path yaml_instance/your_workflow.yaml +``` + +This tool performs: +* **Schema Validation**: Checks if fields match the defined dataclasses (`entity/configs`). +* **Structure Validation**: Checks for orphan nodes, invalid edges, and logical consistency. +* **Memory Validation**: Verifies memory store references are valid. +* **Condition Validation**: Ensures condition syntax is correct. + +## 12. Quick Reference + +### Context Management + +| Goal | Configuration | +|------|---------------| +| Keep system instructions | `keep_message: true` on edge | +| Clear temporary messages | `clear_context: true` | +| Complete context reset | `clear_context: true` + `clear_kept_context: true` | +| Preserve between rounds | Use `keep_message: true` on critical edges | + +### Memory Patterns + +| Use Case | top_k | similarity_threshold | +|----------|-------|---------------------| +| Broad historical search | 50+ | 0.3 (low) | +| Recent context | 10 | 0.5 (medium) | +| Exact matches | 5 | 0.7+ (high) | + +### Thinking Integration + +| Retrieve Stage | Purpose | +|----------------|---------| +| `pre_gen_thinking` | Provide memory context before thinking | +| `post_gen` | Retrieve after generation (rare) | + +### Dynamic Execution + +| Split Type | Input Format | +|------------|--------------| +| `json_array` | `[{...}, {...}]` | +| `regex` | Text with delimiter | +| `lines` | Multi-line text | + +### Condition Types + +| Type | Use Case | +|------|----------| +| `keyword` | Match specific words (any/none/all) | +| `regex` | Pattern matching | +| `"true"` | Always traverse (default) | + +## 13. Implementation Patterns + +### Pattern 1: Recursive Workflow with Isolation + +```yaml +edges: + - from: HumanControl + to: StartNode + clear_context: true + clear_kept_context: true + condition: + type: keyword + config: + none: ["EXIT"] +``` + +### Pattern 2: Memory-Based Uniqueness Check + +```yaml +nodes: + - id: Generator + type: agent + config: + memories: + - name: tracking_memory + top_k: 50 + similarity_threshold: 0.3 + retrieve_stage: [pre_gen_thinking] + read: true + write: true + thinking: + type: reflection + config: + reflection_prompt: "Verify uniqueness against retrieved memory..." +``` + +### Pattern 3: Parallel Processing with Aggregation + +```yaml +edges: + # Fan out + - from: Generator + to: Processor + dynamic: + type: map + config: + split_type: json_array + + # Aggregate + - from: Processor + to: Aggregator +``` + +### Pattern 4: Conditional Branching + +```yaml +edges: + - from: Decision + to: PathA + condition: + type: keyword + config: + any: ["APPROVE"] + + - from: Decision + to: PathB + condition: + type: keyword + config: + any: ["REJECT"] +``` + +## 14. Reference Files + +- **Demo Workflows**: `yaml_instance/demo_context_reset.yaml` - Context management demo +- **Complex Example**: `yaml_instance/simulation_hospital.yaml` - Full-featured recursive simulation +- **Configuration Schemas**: `entity/configs/` - Dataclass definitions +- **Validation Logic**: `check/check.py` - Schema and structure validation +- **User Guide**: `docs/user_guide/en/` - Official documentation + +--- + +**Last Updated**: Based on codebase analysis and `demo_context_reset.yaml` demonstration workflow. diff --git a/docs/user_guide/en/execution_logic.md b/docs/user_guide/en/execution_logic.md index aff163ce2..55cb0220f 100755 --- a/docs/user_guide/en/execution_logic.md +++ b/docs/user_guide/en/execution_logic.md @@ -114,6 +114,7 @@ Execute according to the topological order: After completing each round of in-cycle execution, the system checks these exit conditions: - **Exit edge triggered**: If any in-cycle node triggers an edge to an out-of-cycle node, exit the loop - **Maximum iterations reached**: If the configured maximum (default 100) is reached, force termination +- **Time limit reached**: If a `loop_timer` node within the cycle reaches its configured time limit, exit the loop - **Initial node not re-triggered**: If the initial node isn't re-triggered by in-cycle predecessors, the loop naturally terminates If none of the conditions are met, return to Step 2 for the next iteration. diff --git a/entity/configs/node/loop_timer.py b/entity/configs/node/loop_timer.py new file mode 100644 index 000000000..699b328b8 --- /dev/null +++ b/entity/configs/node/loop_timer.py @@ -0,0 +1,122 @@ +"""Configuration for loop timer guard nodes.""" + +from dataclasses import dataclass +from typing import Mapping, Any, Optional + +from entity.configs.base import ( + BaseConfig, + ConfigError, + ConfigFieldSpec, + require_mapping, + extend_path, + optional_str, +) + + +@dataclass +class LoopTimerConfig(BaseConfig): + """Configuration schema for the loop timer node type.""" + + max_duration: float = 60.0 + duration_unit: str = "seconds" + reset_on_emit: bool = True + message: Optional[str] = None + passthrough: bool = False + + @classmethod + def from_dict( + cls, data: Mapping[str, Any] | None, *, path: str + ) -> "LoopTimerConfig": + mapping = require_mapping(data or {}, path) + max_duration_raw = mapping.get("max_duration", 60.0) + try: + max_duration = float(max_duration_raw) + except (TypeError, ValueError) as exc: # pragma: no cover - defensive + raise ConfigError( + "max_duration must be a number", + extend_path(path, "max_duration"), + ) from exc + + if max_duration <= 0: + raise ConfigError( + "max_duration must be > 0", extend_path(path, "max_duration") + ) + + duration_unit = str(mapping.get("duration_unit", "seconds")) + valid_units = ["seconds", "minutes", "hours"] + if duration_unit not in valid_units: + raise ConfigError( + f"duration_unit must be one of: {', '.join(valid_units)}", + extend_path(path, "duration_unit"), + ) + + reset_on_emit = bool(mapping.get("reset_on_emit", True)) + message = optional_str(mapping, "message", path) + passthrough = bool(mapping.get("passthrough", False)) + + return cls( + max_duration=max_duration, + duration_unit=duration_unit, + reset_on_emit=reset_on_emit, + message=message, + passthrough=passthrough, + path=path, + ) + + def validate(self) -> None: + if self.max_duration <= 0: + raise ConfigError( + "max_duration must be > 0", extend_path(self.path, "max_duration") + ) + + valid_units = ["seconds", "minutes", "hours"] + if self.duration_unit not in valid_units: + raise ConfigError( + f"duration_unit must be one of: {', '.join(valid_units)}", + extend_path(self.path, "duration_unit"), + ) + + FIELD_SPECS = { + "max_duration": ConfigFieldSpec( + name="max_duration", + display_name="Maximum Duration", + type_hint="float", + required=True, + default=60.0, + description="How long the loop can run before this node emits an output.", + ), + "duration_unit": ConfigFieldSpec( + name="duration_unit", + display_name="Duration Unit", + type_hint="str", + required=True, + default="seconds", + description="Unit of time for max_duration: 'seconds', 'minutes', or 'hours'.", + ), + "reset_on_emit": ConfigFieldSpec( + name="reset_on_emit", + display_name="Reset After Emit", + type_hint="bool", + required=False, + default=True, + description="Whether to reset the internal timer after reaching the limit.", + advance=True, + ), + "message": ConfigFieldSpec( + name="message", + display_name="Release Message", + type_hint="text", + required=False, + description="Optional text sent downstream once the time limit is reached.", + advance=True, + ), + "passthrough": ConfigFieldSpec( + name="passthrough", + display_name="Passthrough Mode", + type_hint="bool", + required=False, + default=False, + description="If true, after emitting the limit message, all subsequent inputs pass through unchanged.", + advance=True, + ), + } diff --git a/runtime/node/builtin_nodes.py b/runtime/node/builtin_nodes.py index 2a2311156..f55ac848d 100755 --- a/runtime/node/builtin_nodes.py +++ b/runtime/node/builtin_nodes.py @@ -12,6 +12,7 @@ from entity.configs.node.literal import LiteralNodeConfig from entity.configs.node.python_runner import PythonRunnerConfig from entity.configs.node.loop_counter import LoopCounterConfig +from entity.configs.node.loop_timer import LoopTimerConfig from runtime.node.executor.agent_executor import AgentNodeExecutor from runtime.node.executor.human_executor import HumanNodeExecutor from runtime.node.executor.passthrough_executor import PassthroughNodeExecutor @@ -19,6 +20,7 @@ from runtime.node.executor.python_executor import PythonNodeExecutor from runtime.node.executor.subgraph_executor import SubgraphNodeExecutor from runtime.node.executor.loop_counter_executor import LoopCounterNodeExecutor +from runtime.node.executor.loop_timer_executor import LoopTimerNodeExecutor from runtime.node.registry import NodeCapabilities, register_node_type @@ -48,9 +50,10 @@ "subgraph", config_cls=SubgraphConfig, executor_cls=SubgraphNodeExecutor, - capabilities=NodeCapabilities( + capabilities=NodeCapabilities(), + executor_factory=lambda context, subgraphs=None: SubgraphNodeExecutor( + context, subgraphs or {} ), - executor_factory=lambda context, subgraphs=None: SubgraphNodeExecutor(context, subgraphs or {}), summary="Embeds (through file path or inline config) and runs another named subgraph within the current workflow", ) @@ -69,8 +72,7 @@ "passthrough", config_cls=PassthroughConfig, executor_cls=PassthroughNodeExecutor, - capabilities=NodeCapabilities( - ), + capabilities=NodeCapabilities(), summary="Forwards prior node output downstream without modification", ) @@ -78,8 +80,7 @@ "literal", config_cls=LiteralNodeConfig, executor_cls=LiteralNodeExecutor, - capabilities=NodeCapabilities( - ), + capabilities=NodeCapabilities(), summary="Emits the configured text message every time it is triggered", ) @@ -91,6 +92,14 @@ summary="Blocks downstream edges until the configured iteration limit is reached, then emits a message to release the loop.", ) +register_node_type( + "loop_timer", + config_cls=LoopTimerConfig, + executor_cls=LoopTimerNodeExecutor, + capabilities=NodeCapabilities(), + summary="Blocks downstream edges until the configured time limit is reached, then emits a message to release the loop.", +) + # Register subgraph source types (file-based and inline config) register_subgraph_source( "config", diff --git a/runtime/node/executor/loop_timer_executor.py b/runtime/node/executor/loop_timer_executor.py new file mode 100644 index 000000000..d86a548b3 --- /dev/null +++ b/runtime/node/executor/loop_timer_executor.py @@ -0,0 +1,148 @@ +"""Loop timer guard node executor.""" + +import time +from typing import List, Dict, Any + +from entity.configs import Node +from entity.configs.node.loop_timer import LoopTimerConfig +from entity.messages import Message, MessageRole +from runtime.node.executor.base import NodeExecutor + + +class LoopTimerNodeExecutor(NodeExecutor): + """Track loop duration and emit output only after hitting the time limit. + + Supports two modes: + 1. Standard Mode (passthrough=False): Suppresses input until time limit, then emits message + 2. Terminal Gate Mode (passthrough=True): Acts as a sequential switch + - Before limit: Pass input through unchanged + - At limit: Emit configured message, suppress original input + - After limit: Transparent gate, pass all subsequent messages through + """ + + STATE_KEY = "loop_timer" + + def execute(self, node: Node, inputs: List[Message]) -> List[Message]: + config = node.as_config(LoopTimerConfig) + if config is None: + raise ValueError(f"Node {node.id} missing loop_timer configuration") + + state = self._get_state() + timer_state = state.setdefault(node.id, {}) + + # Initialize timer on first execution + current_time = time.time() + if "start_time" not in timer_state: + timer_state["start_time"] = current_time + timer_state["emitted"] = False + + start_time = timer_state["start_time"] + elapsed_time = current_time - start_time + + # Convert max_duration to seconds based on unit + max_duration_seconds = self._convert_to_seconds( + config.max_duration, config.duration_unit + ) + + # Check if time limit has been reached + limit_reached = elapsed_time >= max_duration_seconds + + # Terminal Gate Mode (passthrough=True) + if config.passthrough: + if not limit_reached: + # Before limit: pass input through unchanged + self.log_manager.debug( + f"LoopTimer {node.id}: {elapsed_time:.1f}s / {max_duration_seconds:.1f}s " + f"(passthrough mode: forwarding input)" + ) + return inputs + elif not timer_state["emitted"]: + # At limit: emit configured message, suppress original input + timer_state["emitted"] = True + if config.reset_on_emit: + timer_state["start_time"] = current_time + + content = ( + config.message + or f"Time limit reached ({config.max_duration} {config.duration_unit})" + ) + metadata = { + "loop_timer": { + "elapsed_time": elapsed_time, + "max_duration": config.max_duration, + "duration_unit": config.duration_unit, + "reset_on_emit": config.reset_on_emit, + "passthrough": True, + } + } + + self.log_manager.debug( + f"LoopTimer {node.id}: {elapsed_time:.1f}s / {max_duration_seconds:.1f}s " + f"(passthrough mode: emitting limit message)" + ) + + return [ + Message( + role=MessageRole.ASSISTANT, + content=content, + metadata=metadata, + ) + ] + else: + # After limit: transparent gate, pass all subsequent messages through + self.log_manager.debug( + f"LoopTimer {node.id}: {elapsed_time:.1f}s (passthrough mode: transparent gate)" + ) + return inputs + + # Standard Mode (passthrough=False) + if not limit_reached: + self.log_manager.debug( + f"LoopTimer {node.id}: {elapsed_time:.1f}s / {max_duration_seconds:.1f}s " + f"(suppress downstream)" + ) + return [] + + if config.reset_on_emit and not timer_state["emitted"]: + timer_state["start_time"] = current_time + + timer_state["emitted"] = True + + content = ( + config.message + or f"Time limit reached ({config.max_duration} {config.duration_unit})" + ) + metadata = { + "loop_timer": { + "elapsed_time": elapsed_time, + "max_duration": config.max_duration, + "duration_unit": config.duration_unit, + "reset_on_emit": config.reset_on_emit, + "passthrough": False, + } + } + + self.log_manager.debug( + f"LoopTimer {node.id}: {elapsed_time:.1f}s / {max_duration_seconds:.1f}s " + f"reached limit, releasing output" + ) + + return [ + Message( + role=MessageRole.ASSISTANT, + content=content, + metadata=metadata, + ) + ] + + def _get_state(self) -> Dict[str, Dict[str, Any]]: + return self.context.global_state.setdefault(self.STATE_KEY, {}) + + def _convert_to_seconds(self, duration: float, unit: str) -> float: + """Convert duration to seconds based on unit.""" + unit_multipliers = { + "seconds": 1.0, + "minutes": 60.0, + "hours": 3600.0, + } + return duration * unit_multipliers.get(unit, 1.0) diff --git a/yaml_instance/demo_loop_timer.yaml b/yaml_instance/demo_loop_timer.yaml new file mode 100644 index 000000000..7a76f62e6 --- /dev/null +++ b/yaml_instance/demo_loop_timer.yaml @@ -0,0 +1,52 @@ +version: 0.4.0 +graph: + start: + - Writer + end: + - Finalizer + id: loop_timer_demo + description: LoopTimer demo that releases output after 5 seconds of elapsed time. + is_majority_voting: false + log_level: INFO + nodes: + - id: Writer + type: literal + description: Responsible for outputting a fixed draft. + config: + content: Draft iteration from Writer + role: assistant + - id: Critic + type: literal + description: Simulates feedback, always requesting further revisions. + config: + content: Please revise again + role: user + - id: Timer Gate + type: loop_timer + description: Counts elapsed time, only granting passage after 5 seconds. + config: + max_duration: 5 + duration_unit: seconds + reset_on_emit: true + message: Loop finished after 5 seconds + - id: Finalizer + type: literal + description: Receives the release signal from Timer Gate and outputs the final statement. + config: + content: Final summary released + role: assistant + edges: + - from: Writer + to: Critic + - from: Critic + to: Writer + - from: Critic + to: Timer Gate + - from: Timer Gate + to: Writer + trigger: true + condition: 'true' + carry_data: true + keep_message: false + - from: Timer Gate + to: Finalizer diff --git a/yaml_instance/demo_loop_timer_passthrough.yaml b/yaml_instance/demo_loop_timer_passthrough.yaml new file mode 100644 index 000000000..c8c9de0da --- /dev/null +++ b/yaml_instance/demo_loop_timer_passthrough.yaml @@ -0,0 +1,51 @@ +version: 0.4.0 +graph: + start: + - Writer + end: + - Finalizer + id: loop_timer_passthrough_demo + description: LoopTimer passthrough mode demo - passes through messages before the limit, emits at the limit, then becomes transparent. + is_majority_voting: false + log_level: INFO + nodes: + - id: Writer + type: literal + description: Outputs a draft message. + config: + content: Draft iteration from Writer + role: assistant + - id: Critic + type: literal + description: Provides feedback. + config: + content: Please revise again + role: user + - id: Timer Gate + type: loop_timer + description: Passthrough mode - passes messages through before 5 seconds, emits limit message at 5 seconds, then transparent. + config: + max_duration: 5 + duration_unit: seconds + reset_on_emit: false + message: Time limit reached - switching to passthrough + passthrough: true + - id: Finalizer + type: literal + description: Receives messages. + config: + content: Final summary released + role: assistant + edges: + - from: Writer + to: Critic + - from: Critic + to: Timer Gate + - from: Timer Gate + to: Writer + trigger: true + condition: 'true' + carry_data: true + keep_message: false + - from: Timer Gate + to: Finalizer From 1abf173a14c8a89b8fe3cf62bd1cbb46f0b787b9 Mon Sep 17 00:00:00 2001 From: laansdole Date: Sat, 7 Feb 2026 12:55:31 +0700 Subject: [PATCH 3/9] refactor: Merge loop_timer demos and extend duration - Merge demo_loop_timer.yaml and demo_loop_timer_passthrough.yaml into single comprehensive demo - Create dual-branch workflow demonstrating both standard and passthrough modes side-by-side - Increase duration from 5 seconds to 2 minutes for better demonstration - Update YAML_FORMAT_QUICK_GUIDE.md with detailed mode explanations and demo reference - Update tasks.md in add-loop-timer proposal to reflect demo improvements - Remove duplicate demo_loop_timer_passthrough.yaml file --- YAML_FORMAT_QUICK_GUIDE.md | 17 ++- yaml_instance/demo_loop_timer.yaml | 141 ++++++++++++++---- .../demo_loop_timer_passthrough.yaml | 51 ------- 3 files changed, 123 insertions(+), 86 deletions(-) delete mode 100644 yaml_instance/demo_loop_timer_passthrough.yaml diff --git a/YAML_FORMAT_QUICK_GUIDE.md b/YAML_FORMAT_QUICK_GUIDE.md index 03cadff10..3018a3fa7 100644 --- a/YAML_FORMAT_QUICK_GUIDE.md +++ b/YAML_FORMAT_QUICK_GUIDE.md @@ -176,16 +176,19 @@ config: ``` #### **`loop_timer`** -Controls loops by tracking elapsed time. +Controls loops by tracking elapsed time. See `yaml_instance/demo_loop_timer.yaml` for comprehensive demo with both standard and passthrough modes. ```yaml type: loop_timer config: - max_duration: 5 # Max allowed time - duration_unit: seconds # Unit: 'seconds', 'minutes', or 'hours' - reset_on_emit: true # Reset timer when condition is met - message: "" # Optional message - passthrough: false # Passthrough mode (same as loop_counter) -``` + max_duration: 2 # Max allowed time + duration_unit: minutes # Unit: 'seconds', 'minutes', or 'hours' + reset_on_emit: true # Reset timer when condition is met (standard mode) + message: "" # Optional custom message when time limit reached + passthrough: false # false (standard): suppress until limit + # true (passthrough): pass through, emit at limit, then transparent +``` +**Standard mode** (`passthrough: false`): Messages suppressed until time limit, then emits message and allows passage. +**Passthrough mode** (`passthrough: true`): Messages pass through immediately, emits message at limit, then becomes transparent. #### **`passthrough`** A simple node that passes data through without modification. diff --git a/yaml_instance/demo_loop_timer.yaml b/yaml_instance/demo_loop_timer.yaml index 7a76f62e6..09d1f2405 100644 --- a/yaml_instance/demo_loop_timer.yaml +++ b/yaml_instance/demo_loop_timer.yaml @@ -1,52 +1,137 @@ version: 0.4.0 graph: start: - - Writer + - StandardWriter end: - - Finalizer - id: loop_timer_demo - description: LoopTimer demo that releases output after 5 seconds of elapsed time. + - StandardFinalizer + - PassthroughFinalizer + id: loop_timer_comprehensive_demo + description: | + Comprehensive LoopTimer demonstration with both standard and passthrough modes. + + STANDARD MODE BRANCH: + StandardWriter → StandardCritic → Standard Timer Gate (2 min) → StandardFinalizer + + The Standard Timer Gate suppresses messages for 2 minutes, then emits a time limit + message to StandardFinalizer. The feedback loop (StandardCritic → StandardWriter) + continues running but outputs are blocked by the gate until time expires. + + PASSTHROUGH MODE BRANCH: + PassthroughWriter → PassthroughCritic → Passthrough Timer Gate (2 min) → PassthroughFinalizer + + The Passthrough Timer Gate allows messages through immediately, maintains a parallel + feedback loop (PassthroughCritic → PassthroughWriter), and after 2 minutes emits a + time limit message before becoming transparent. is_majority_voting: false log_level: INFO nodes: - - id: Writer + # ===== STANDARD MODE BRANCH ===== + - id: StandardWriter type: literal - description: Responsible for outputting a fixed draft. + description: Standard mode - outputs draft messages that get blocked by timer gate. config: - content: Draft iteration from Writer + content: "[STANDARD] Draft iteration from Writer" role: assistant - - id: Critic + + - id: StandardCritic type: literal - description: Simulates feedback, always requesting further revisions. + description: Standard mode - provides feedback to keep the loop running. config: - content: Please revise again + content: "[STANDARD] Please revise again" role: user - - id: Timer Gate + + - id: Standard Timer Gate type: loop_timer - description: Counts elapsed time, only granting passage after 5 seconds. + description: | + Standard mode (passthrough=false) - Suppresses messages for 2 minutes. + After 2 minutes elapsed, emits time limit message and allows passage to StandardFinalizer. + Timer resets after emission (reset_on_emit=true). config: - max_duration: 5 - duration_unit: seconds + max_duration: 2 + duration_unit: minutes reset_on_emit: true - message: Loop finished after 5 seconds - - id: Finalizer + message: "[STANDARD] Time limit reached after 2 minutes - releasing output" + passthrough: false + + - id: StandardFinalizer type: literal - description: Receives the release signal from Timer Gate and outputs the final statement. + description: Standard mode - receives output only after timer expires. config: - content: Final summary released + content: "[STANDARD] Final summary released" role: assistant + + # ===== PASSTHROUGH MODE BRANCH ===== + - id: PassthroughWriter + type: literal + description: Passthrough mode - outputs draft messages that pass through immediately. + config: + content: "[PASSTHROUGH] Draft iteration from Writer" + role: assistant + + - id: PassthroughCritic + type: literal + description: Passthrough mode - provides feedback to keep the loop running. + config: + content: "[PASSTHROUGH] Please revise again" + role: user + + - id: Passthrough Timer Gate + type: loop_timer + description: | + Passthrough mode (passthrough=true) - Allows messages through immediately. + After 2 minutes elapsed, emits time limit message. + Then becomes transparent gate (no reset, continues passing through). + config: + max_duration: 2 + duration_unit: minutes + reset_on_emit: false + message: "[PASSTHROUGH] Time limit reached after 2 minutes - now transparent" + passthrough: true + + - id: PassthroughFinalizer + type: literal + description: Passthrough mode - receives output immediately and continues receiving after timer. + config: + content: "[PASSTHROUGH] Final summary released" + role: assistant + edges: - - from: Writer - to: Critic - - from: Critic - to: Writer - - from: Critic - to: Timer Gate - - from: Timer Gate - to: Writer + # ===== STANDARD MODE EDGES ===== + # Main forward path + - from: StandardWriter + to: StandardCritic + + - from: StandardCritic + to: Standard Timer Gate + + # Feedback loop (blocked until timer expires) + - from: StandardCritic + to: StandardWriter + trigger: true + condition: 'true' + carry_data: true + keep_message: false + + # Timer gate output (only after 2 minutes) + - from: Standard Timer Gate + to: StandardFinalizer + + # ===== PASSTHROUGH MODE EDGES ===== + # Main forward path + - from: PassthroughWriter + to: PassthroughCritic + + - from: PassthroughCritic + to: Passthrough Timer Gate + + # Feedback loop (always active) + - from: PassthroughCritic + to: PassthroughWriter trigger: true condition: 'true' carry_data: true keep_message: false - - from: Timer Gate - to: Finalizer + + # Timer gate output (immediate passthrough + timer message at 2 min) + - from: Passthrough Timer Gate + to: PassthroughFinalizer diff --git a/yaml_instance/demo_loop_timer_passthrough.yaml b/yaml_instance/demo_loop_timer_passthrough.yaml deleted file mode 100644 index c8c9de0da..000000000 --- a/yaml_instance/demo_loop_timer_passthrough.yaml +++ /dev/null @@ -1,51 +0,0 @@ -version: 0.4.0 -graph: - start: - - Writer - end: - - Finalizer - id: loop_timer_passthrough_demo - description: LoopTimer passthrough mode demo - passes through messages before the limit, emits at the limit, then becomes transparent. - is_majority_voting: false - log_level: INFO - nodes: - - id: Writer - type: literal - description: Outputs a draft message. - config: - content: Draft iteration from Writer - role: assistant - - id: Critic - type: literal - description: Provides feedback. - config: - content: Please revise again - role: user - - id: Timer Gate - type: loop_timer - description: Passthrough mode - passes messages through before 5 seconds, emits limit message at 5 seconds, then transparent. - config: - max_duration: 5 - duration_unit: seconds - reset_on_emit: false - message: Time limit reached - switching to passthrough - passthrough: true - - id: Finalizer - type: literal - description: Receives messages. - config: - content: Final summary released - role: assistant - edges: - - from: Writer - to: Critic - - from: Critic - to: Timer Gate - - from: Timer Gate - to: Writer - trigger: true - condition: 'true' - carry_data: true - keep_message: false - - from: Timer Gate - to: Finalizer From f7ece916ce3a3ce7be231ec0433a0e8c8fc58757 Mon Sep 17 00:00:00 2001 From: laansdole Date: Sat, 7 Feb 2026 13:08:31 +0700 Subject: [PATCH 4/9] fix: Correct YAML format in demo_loop_timer.yaml - Remove invalid 'version' field - Restructure to match DevAll YAML schema (graph at top level) - Move start/end nodes to end of graph block - Validation now passes: 'Workflow OK. Design is valid.' --- yaml_instance/demo_loop_timer.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yaml_instance/demo_loop_timer.yaml b/yaml_instance/demo_loop_timer.yaml index 09d1f2405..7a5ec130b 100644 --- a/yaml_instance/demo_loop_timer.yaml +++ b/yaml_instance/demo_loop_timer.yaml @@ -1,10 +1,4 @@ -version: 0.4.0 graph: - start: - - StandardWriter - end: - - StandardFinalizer - - PassthroughFinalizer id: loop_timer_comprehensive_demo description: | Comprehensive LoopTimer demonstration with both standard and passthrough modes. @@ -22,8 +16,8 @@ graph: The Passthrough Timer Gate allows messages through immediately, maintains a parallel feedback loop (PassthroughCritic → PassthroughWriter), and after 2 minutes emits a time limit message before becoming transparent. - is_majority_voting: false log_level: INFO + is_majority_voting: false nodes: # ===== STANDARD MODE BRANCH ===== - id: StandardWriter @@ -135,3 +129,9 @@ graph: # Timer gate output (immediate passthrough + timer message at 2 min) - from: Passthrough Timer Gate to: PassthroughFinalizer + + start: + - StandardWriter + end: + - StandardFinalizer + - PassthroughFinalizer From b56e89313e3c071a60809c547ac81ada336e8b1f Mon Sep 17 00:00:00 2001 From: laansdole Date: Sat, 7 Feb 2026 13:11:43 +0700 Subject: [PATCH 5/9] fix: Correct loop structure in demo_loop_timer.yaml Key fixes: - Add proper trigger edges: Critic -> Timer Gate with trigger=true - Add feedback loop: Timer Gate -> Writer with trigger=true - Remove incorrect Writer -> Critic -> Timer Gate -> Writer pattern - Follow ChatDev_v1 loop pattern: input -> gate (trigger) -> gate -> output (trigger) - Start both branches simultaneously in start nodes This ensures the timer gates are properly triggered and loops execute correctly. --- yaml_instance/demo_loop_timer.yaml | 59 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/yaml_instance/demo_loop_timer.yaml b/yaml_instance/demo_loop_timer.yaml index 7a5ec130b..dde5ef230 100644 --- a/yaml_instance/demo_loop_timer.yaml +++ b/yaml_instance/demo_loop_timer.yaml @@ -3,26 +3,20 @@ graph: description: | Comprehensive LoopTimer demonstration with both standard and passthrough modes. - STANDARD MODE BRANCH: - StandardWriter → StandardCritic → Standard Timer Gate (2 min) → StandardFinalizer + STANDARD MODE (left branch): + Writer loops with Critic through Standard Timer Gate for 2 minutes, + then releases to StandardFinalizer. - The Standard Timer Gate suppresses messages for 2 minutes, then emits a time limit - message to StandardFinalizer. The feedback loop (StandardCritic → StandardWriter) - continues running but outputs are blocked by the gate until time expires. - - PASSTHROUGH MODE BRANCH: - PassthroughWriter → PassthroughCritic → Passthrough Timer Gate (2 min) → PassthroughFinalizer - - The Passthrough Timer Gate allows messages through immediately, maintains a parallel - feedback loop (PassthroughCritic → PassthroughWriter), and after 2 minutes emits a - time limit message before becoming transparent. + PASSTHROUGH MODE (right branch): + Writer loops with Critic through Passthrough Timer Gate, messages pass through + immediately to PassthroughFinalizer, timer emits at 2 minutes then transparent. log_level: INFO is_majority_voting: false nodes: # ===== STANDARD MODE BRANCH ===== - id: StandardWriter type: literal - description: Standard mode - outputs draft messages that get blocked by timer gate. + description: Standard mode - outputs draft messages. config: content: "[STANDARD] Draft iteration from Writer" role: assistant @@ -38,8 +32,7 @@ graph: type: loop_timer description: | Standard mode (passthrough=false) - Suppresses messages for 2 minutes. - After 2 minutes elapsed, emits time limit message and allows passage to StandardFinalizer. - Timer resets after emission (reset_on_emit=true). + After 2 minutes, emits message to StandardFinalizer. config: max_duration: 2 duration_unit: minutes @@ -57,7 +50,7 @@ graph: # ===== PASSTHROUGH MODE BRANCH ===== - id: PassthroughWriter type: literal - description: Passthrough mode - outputs draft messages that pass through immediately. + description: Passthrough mode - outputs draft messages. config: content: "[PASSTHROUGH] Draft iteration from Writer" role: assistant @@ -73,8 +66,7 @@ graph: type: loop_timer description: | Passthrough mode (passthrough=true) - Allows messages through immediately. - After 2 minutes elapsed, emits time limit message. - Then becomes transparent gate (no reset, continues passing through). + After 2 minutes, emits message then becomes transparent. config: max_duration: 2 duration_unit: minutes @@ -84,54 +76,63 @@ graph: - id: PassthroughFinalizer type: literal - description: Passthrough mode - receives output immediately and continues receiving after timer. + description: Passthrough mode - receives outputs immediately and after timer. config: content: "[PASSTHROUGH] Final summary released" role: assistant edges: # ===== STANDARD MODE EDGES ===== - # Main forward path + # Initial flow: Writer -> Critic -> Timer Gate - from: StandardWriter to: StandardCritic - from: StandardCritic to: Standard Timer Gate + trigger: true + condition: 'true' + carry_data: false + keep_message: false - # Feedback loop (blocked until timer expires) - - from: StandardCritic + # Feedback loop: Timer Gate -> Writer (while time < 2 min) + - from: Standard Timer Gate to: StandardWriter trigger: true condition: 'true' - carry_data: true + carry_data: false keep_message: false - # Timer gate output (only after 2 minutes) + # Exit: Timer Gate -> Finalizer (when time >= 2 min) - from: Standard Timer Gate to: StandardFinalizer # ===== PASSTHROUGH MODE EDGES ===== - # Main forward path + # Initial flow: Writer -> Critic -> Timer Gate - from: PassthroughWriter to: PassthroughCritic - from: PassthroughCritic to: Passthrough Timer Gate + trigger: true + condition: 'true' + carry_data: false + keep_message: false - # Feedback loop (always active) - - from: PassthroughCritic + # Feedback loop: Timer Gate -> Writer (always active) + - from: Passthrough Timer Gate to: PassthroughWriter trigger: true condition: 'true' - carry_data: true + carry_data: false keep_message: false - # Timer gate output (immediate passthrough + timer message at 2 min) + # Passthrough: Timer Gate -> Finalizer (immediate + at 2 min) - from: Passthrough Timer Gate to: PassthroughFinalizer start: - StandardWriter + - PassthroughWriter end: - StandardFinalizer - PassthroughFinalizer From 9031b4d9eef21e08d7b876c711495c38dce68f93 Mon Sep 17 00:00:00 2001 From: laansdole Date: Sun, 8 Feb 2026 11:46:00 +0700 Subject: [PATCH 6/9] fix: loop timer demo --- docs/user_guide/en/workflow_authoring.md | 1 + yaml_instance/demo_loop_timer.yaml | 155 ++++++----------------- 2 files changed, 37 insertions(+), 119 deletions(-) diff --git a/docs/user_guide/en/workflow_authoring.md b/docs/user_guide/en/workflow_authoring.md index 70f823a1f..abbb2f14d 100755 --- a/docs/user_guide/en/workflow_authoring.md +++ b/docs/user_guide/en/workflow_authoring.md @@ -92,6 +92,7 @@ Further reading: `docs/user_guide/en/field_specs.md` (field catalog), `docs/user | `passthrough` | Pass-through node that forwards only the last message by default and can be configured to forward all messages; used for context filtering and graph structure optimization. | `only_last_message` | [passthrough.md](nodes/passthrough.md) | | `literal` | Emits a fixed text payload whenever triggered and discards inputs. | `content`, `role` (`user`/`assistant`) | [literal.md](nodes/literal.md) | | `loop_counter` | Guard node that limits loop iterations before releasing downstream edges. | `max_iterations`, `reset_on_emit`, `message` | [loop_counter.md](nodes/loop_counter.md) | +| `loop_timer` | Guard node that limits loop duration before releasing downstream edges. | `max_duration`, `duration_unit`, `reset_on_emit`, `message`, `passthrough` | [loop_timer.md](nodes/loop_timer.md) | Fetch the full schema via `POST /api/config/schema` or inspect the dataclasses inside `entity/configs/`. diff --git a/yaml_instance/demo_loop_timer.yaml b/yaml_instance/demo_loop_timer.yaml index dde5ef230..0cbd5bf4f 100644 --- a/yaml_instance/demo_loop_timer.yaml +++ b/yaml_instance/demo_loop_timer.yaml @@ -1,138 +1,55 @@ +version: 0.4.0 graph: - id: loop_timer_comprehensive_demo - description: | - Comprehensive LoopTimer demonstration with both standard and passthrough modes. - - STANDARD MODE (left branch): - Writer loops with Critic through Standard Timer Gate for 2 minutes, - then releases to StandardFinalizer. - - PASSTHROUGH MODE (right branch): - Writer loops with Critic through Passthrough Timer Gate, messages pass through - immediately to PassthroughFinalizer, timer emits at 2 minutes then transparent. - log_level: INFO + start: + - Writer + end: + - Finalizer + id: loop_timer_demo + description: LoopTimer demo that releases output after 30 seconds. is_majority_voting: false + log_level: INFO nodes: - # ===== STANDARD MODE BRANCH ===== - - id: StandardWriter + - id: Writer type: literal - description: Standard mode - outputs draft messages. + description: Responsible for outputting a fixed draft. config: - content: "[STANDARD] Draft iteration from Writer" + content: Draft iteration from Writer role: assistant - - - id: StandardCritic + - id: Critic type: literal - description: Standard mode - provides feedback to keep the loop running. + description: Simulates human feedback, always requesting further revisions. config: - content: "[STANDARD] Please revise again" + content: Please revise again role: user - - - id: Standard Timer Gate + - id: Loop Gate type: loop_timer - description: | - Standard mode (passthrough=false) - Suppresses messages for 2 minutes. - After 2 minutes, emits message to StandardFinalizer. + description: Tracks elapsed time, only granting passage after 30 seconds. config: - max_duration: 2 - duration_unit: minutes + max_duration: 30 + duration_unit: seconds reset_on_emit: true - message: "[STANDARD] Time limit reached after 2 minutes - releasing output" - passthrough: false - - - id: StandardFinalizer - type: literal - description: Standard mode - receives output only after timer expires. - config: - content: "[STANDARD] Final summary released" - role: assistant - - # ===== PASSTHROUGH MODE BRANCH ===== - - id: PassthroughWriter - type: literal - description: Passthrough mode - outputs draft messages. - config: - content: "[PASSTHROUGH] Draft iteration from Writer" - role: assistant - - - id: PassthroughCritic - type: literal - description: Passthrough mode - provides feedback to keep the loop running. - config: - content: "[PASSTHROUGH] Please revise again" - role: user - - - id: Passthrough Timer Gate - type: loop_timer - description: | - Passthrough mode (passthrough=true) - Allows messages through immediately. - After 2 minutes, emits message then becomes transparent. - config: - max_duration: 2 - duration_unit: minutes - reset_on_emit: false - message: "[PASSTHROUGH] Time limit reached after 2 minutes - now transparent" - passthrough: true - - - id: PassthroughFinalizer + message: Loop finished after 30 seconds + - id: Finalizer type: literal - description: Passthrough mode - receives outputs immediately and after timer. + description: Receives the release signal from Loop Gate and outputs the final statement. config: - content: "[PASSTHROUGH] Final summary released" + content: Final summary released role: assistant - edges: - # ===== STANDARD MODE EDGES ===== - # Initial flow: Writer -> Critic -> Timer Gate - - from: StandardWriter - to: StandardCritic - - - from: StandardCritic - to: Standard Timer Gate + - from: Writer + to: Critic + - from: Critic + to: Writer + - from: Critic + to: Loop Gate + - from: Loop Gate + to: Writer # keep Loop Gate inside the cycle + - from: Loop Gate + to: Finalizer + - from: Loop Gate + to: Writer trigger: true condition: 'true' - carry_data: false + carry_data: true keep_message: false - - # Feedback loop: Timer Gate -> Writer (while time < 2 min) - - from: Standard Timer Gate - to: StandardWriter - trigger: true - condition: 'true' - carry_data: false - keep_message: false - - # Exit: Timer Gate -> Finalizer (when time >= 2 min) - - from: Standard Timer Gate - to: StandardFinalizer - - # ===== PASSTHROUGH MODE EDGES ===== - # Initial flow: Writer -> Critic -> Timer Gate - - from: PassthroughWriter - to: PassthroughCritic - - - from: PassthroughCritic - to: Passthrough Timer Gate - trigger: true - condition: 'true' - carry_data: false - keep_message: false - - # Feedback loop: Timer Gate -> Writer (always active) - - from: Passthrough Timer Gate - to: PassthroughWriter - trigger: true - condition: 'true' - carry_data: false - keep_message: false - - # Passthrough: Timer Gate -> Finalizer (immediate + at 2 min) - - from: Passthrough Timer Gate - to: PassthroughFinalizer - - start: - - StandardWriter - - PassthroughWriter - end: - - StandardFinalizer - - PassthroughFinalizer + process: null From 9c3df14fa75a097f20b70d54e805ef40f96ba0c8 Mon Sep 17 00:00:00 2001 From: laansdole Date: Sun, 8 Feb 2026 17:15:13 +0700 Subject: [PATCH 7/9] feat: finalize demo loop timer --- yaml_instance/demo_loop_timer.yaml | 67 +++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/yaml_instance/demo_loop_timer.yaml b/yaml_instance/demo_loop_timer.yaml index 0cbd5bf4f..9d4667596 100644 --- a/yaml_instance/demo_loop_timer.yaml +++ b/yaml_instance/demo_loop_timer.yaml @@ -1,40 +1,65 @@ -version: 0.4.0 +version: 0.0.0 graph: start: - Writer end: - Finalizer id: loop_timer_demo - description: LoopTimer demo that releases output after 30 seconds. + description: LoopTimer demo that releases output after 10 seconds of agent iterations. is_majority_voting: false log_level: INFO nodes: + - id: Critic + type: agent + config: + name: ${MODEL_NAME} + provider: openai + role: You are a critic. Provide brief feedback (1 sentence) to improve the draft. + base_url: ${BASE_URL} + api_key: ${API_KEY} + params: {} + tooling: [] + thinking: null + memories: [] + retry: null + description: Reviews the draft and provides feedback for improvement. + context_window: 0 + log_output: true - id: Writer - type: literal - description: Responsible for outputting a fixed draft. + type: agent config: - content: Draft iteration from Writer - role: assistant - - id: Critic + name: ${MODEL_NAME} + provider: openai + role: You are a technical writer. Generate a brief draft (1 sentence). + base_url: ${BASE_URL} + api_key: ${API_KEY} + params: {} + tooling: [] + thinking: null + memories: [] + retry: null + description: Drafts content based on feedback. + context_window: 0 + log_output: true + - id: Finalizer type: literal - description: Simulates human feedback, always requesting further revisions. config: - content: Please revise again - role: user + content: Final summary released + role: assistant + description: Receives the release signal from Loop Gate and outputs the final statement. + context_window: 0 + log_output: true - id: Loop Gate type: loop_timer - description: Tracks elapsed time, only granting passage after 30 seconds. config: - max_duration: 30 + max_duration: 20 duration_unit: seconds reset_on_emit: true - message: Loop finished after 30 seconds - - id: Finalizer - type: literal - description: Receives the release signal from Loop Gate and outputs the final statement. - config: - content: Final summary released - role: assistant + message: Time limit reached - loop automatically terminated + passthrough: false + description: Tracks elapsed time, only granting passage after 10 seconds. + context_window: 0 + log_output: true edges: - from: Writer to: Critic @@ -43,7 +68,7 @@ graph: - from: Critic to: Loop Gate - from: Loop Gate - to: Writer # keep Loop Gate inside the cycle + to: Writer - from: Loop Gate to: Finalizer - from: Loop Gate @@ -53,3 +78,5 @@ graph: carry_data: true keep_message: false process: null +vars: + MODEL_NAME: qwen/qwen3-8b From ca2174a05363464b7d1fcaf89f67625b05ae7de3 Mon Sep 17 00:00:00 2001 From: laansdole Date: Sun, 8 Feb 2026 17:26:15 +0700 Subject: [PATCH 8/9] feat: loop_timer node docs --- docs/user_guide/en/nodes/loop_timer.md | 159 +++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 docs/user_guide/en/nodes/loop_timer.md diff --git a/docs/user_guide/en/nodes/loop_timer.md b/docs/user_guide/en/nodes/loop_timer.md new file mode 100644 index 000000000..9bb5b032c --- /dev/null +++ b/docs/user_guide/en/nodes/loop_timer.md @@ -0,0 +1,159 @@ +# Loop Timer Node + +The Loop Timer node is a loop control node used to limit the duration of a loop in a workflow. Through a time-tracking mechanism, it suppresses output before reaching the preset time limit, and only releases the message to trigger outgoing edges when the time limit is reached, thereby terminating the loop. + +## Configuration + +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `max_duration` | float | Yes | `60.0` | Maximum loop duration, must be > 0 | +| `duration_unit` | string | Yes | `"seconds"` | Time unit: "seconds", "minutes", or "hours" | +| `reset_on_emit` | bool | No | `true` | Whether to reset the timer after reaching the limit | +| `message` | text | No | - | Message content to send to downstream when time limit is reached | + +## Core Concepts + +### How It Works + +The Loop Timer node maintains an internal timer with the following behavior: + +1. **On first trigger**: Timer starts tracking elapsed time +2. **Elapsed time < `max_duration`**: **No output is produced**, outgoing edges are not triggered +3. **Elapsed time >= `max_duration`**: Output message is produced, triggering outgoing edges + +This "suppress-release" mechanism allows the Loop Timer to precisely control when a loop terminates based on time rather than iteration count. + +### Topological Structure Requirements + +The Loop Timer node has special placement requirements in the graph structure: + +``` + ┌──────────────────────────────────────┐ + ▼ │ + Agent ──► Human ─────► Loop Timer ──┬──┘ + ▲ │ │ + └─────────┘ ▼ + End Node (outside loop) +``` + +> **Important**: Since Loop Timer **produces no output until the time limit is reached**: +> - **Human must connect to both Agent and Loop Timer**: This way the "continue loop" edge is handled by Human → Agent, while Loop Timer only handles time tracking +> - **Loop Timer must connect to Agent (inside loop)**: So it's recognized as an in-loop node, avoiding premature loop termination +> - **Loop Timer must connect to End Node (outside loop)**: When the time limit is reached, trigger the out-of-loop node to terminate the entire loop execution + +### Timer State + +- Timer state persists throughout the entire workflow execution +- Timer starts on the first trigger to the Loop Timer node +- When `reset_on_emit: true`, the timer resets after reaching the limit +- When `reset_on_emit: false`, the timer continues running after reaching the limit, outputting on every subsequent trigger + +## When to Use + +- **Time-based constraints**: Enforce time limits for loops (e.g., "review must complete within 5 minutes") +- **Timeout protection**: Serve as a "circuit breaker" to prevent runaway processes +- **Variable iteration time**: When each loop iteration takes unpredictable time, but total duration must be bounded + +## Examples + +### Basic Usage + +```yaml +nodes: + - id: Time Guard + type: loop_timer + config: + max_duration: 5 + duration_unit: minutes + reset_on_emit: true + message: Time limit reached (5 minutes), process terminated. +``` + +### Time-Limited Review Loop + +This is the most typical use case for Loop Timer: + +```yaml +graph: + id: timed_review_loop + description: Review loop with 5-minute time limit + + nodes: + - id: Writer + type: agent + config: + provider: openai + name: gpt-4o + role: Improve articles based on user feedback + + - id: Reviewer + type: human + config: + description: | + Review the article, enter ACCEPT to accept or provide modification suggestions. + + - id: Loop Gate + type: loop_timer + config: + max_duration: 5 + duration_unit: minutes + message: Time limit (5 minutes) reached, process automatically ended. + + - id: Final Output + type: passthrough + config: {} + + edges: + # Main loop: Writer -> Reviewer + - from: Writer + to: Reviewer + + # Condition 1: User enters ACCEPT -> End + - from: Reviewer + to: Final Output + condition: + type: keyword + config: + any: [ACCEPT] + + # Condition 2: User enters modification suggestions -> Trigger both Writer to continue loop AND Loop Gate to track time + - from: Reviewer + to: Writer + condition: + type: keyword + config: + none: [ACCEPT] + + - from: Reviewer + to: Loop Gate + condition: + type: keyword + config: + none: [ACCEPT] + + # Loop Gate connects to Writer (keeps it inside the loop) + - from: Loop Gate + to: Writer + + # When Loop Gate reaches time limit: Trigger Final Output to end the process + - from: Loop Gate + to: Final Output + + start: [Writer] + end: [Final Output] +``` + +**Execution Flow Explanation**: +1. User first enters modification suggestions → Triggers both Writer (continue loop) and Loop Gate (track time, no output) +2. User enters modification suggestions again → Triggers both Writer (continue loop) and Loop Gate (track time, no output) +3. After 5 minutes of elapsed time → Loop Gate outputs message triggering Final Output, terminating the loop +4. Or at any time user enters ACCEPT → Goes directly to Final Output to end + +## Notes + +- `max_duration` must be a positive number (> 0) +- `duration_unit` must be one of: "seconds", "minutes", "hours" +- Loop Timer **produces no output until the time limit is reached**, outgoing edges will not trigger +- Ensure Loop Timer connects to both in-loop and out-of-loop nodes +- The `message` field is optional, default message is `"Time limit reached (N units)"` +- Timer starts on the first trigger to the Loop Timer node From 4c59283a9d87c4d191e93993f5840f7900739e55 Mon Sep 17 00:00:00 2001 From: Do Le Long An <85084360+LaansDole@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:28:43 +0700 Subject: [PATCH 9/9] chores: refactor --- YAML_FORMAT_QUICK_GUIDE.md | 918 ------------------------------------- 1 file changed, 918 deletions(-) delete mode 100644 YAML_FORMAT_QUICK_GUIDE.md diff --git a/YAML_FORMAT_QUICK_GUIDE.md b/YAML_FORMAT_QUICK_GUIDE.md deleted file mode 100644 index 3018a3fa7..000000000 --- a/YAML_FORMAT_QUICK_GUIDE.md +++ /dev/null @@ -1,918 +0,0 @@ -# Deep Research: YAML Scenario File Format for ChatDev 2.0 (DevAll) - -This document details the YAML format used for defining multi-agent workflows in ChatDev 2.0. It is based on an analysis of the codebase, specifically the configuration schemas in `entity/configs/` and validation logic in `check/`. - -## 1. File Structure Overview - -A valid workflow file consists of three main top-level keys: - -```yaml -version: "0.0.0" # Optional, defaults to "0.0.0" -vars: # Optional global variables - API_KEY: ${API_KEY} - BASE_URL: ${BASE_URL} -graph: # REQUIRED: The core workflow definition - id: "MyWorkflow" - description: "Description of what this workflow does" - nodes: [] # List of Node objects - edges: [] # List of Edge objects -``` - -## 2. Graph Definition (`graph`) - -| Field | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `id` | `str` | Yes | Unique identifier (alphanumeric, underscores, hyphens). | -| `nodes` | `List[Node]` | Yes | List of nodes. Must contain at least one node. | -| `edges` | `List[Edge]` | Yes | List of directed edges connecting nodes. | -| `description` | `str` | No | Human-readable description. | -| `log_level` | `enum` | No | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`. Default: `DEBUG`. | -| `is_majority_voting` | `bool` | No | Default: `false`. | -| `memory` | `List` | No | List of `MemoryStoreConfig` definitions. | -| `start` | `List[str]` | No* | List of start node IDs. *Inferred if graph has unique source.* | -| `end` | `List[str]` | No* | List of end node IDs. *Inferred if graph has unique sink.* | -| `initial_instruction` | `str` | No | Initial instruction text for the user. | -| `organization` | `str` | No | Organization name. | - -## 3. Node Configuration (`nodes`) - -Each item in the `nodes` list represents a processing unit. - -```yaml -- id: "NodeID" # Required: Unique ID - type: "agent" # Required: Node type (agent, human, loop_counter, etc.) - description: "..." # Optional - context_window: 0 # Optional: 0 (clear), -1 (unlimited), N (keep last N) - config: # Required: Configuration specific to the 'type' - ... -``` - -### Common Node Types & Configurations - -#### **`agent`** -Represents an LLM-based agent. -```yaml -type: agent -config: - name: "gpt-4o" # Required: Model name - provider: "openai" # Required: Provider (openai, etc.) - role: "System prompt..." # Optional: System message - base_url: ${BASE_URL} # Optional: Override URL - api_key: ${API_KEY} # Optional: API Key - params: # Optional: Model parameters - temperature: 0.7 - tooling: # Optional: List of tools - - type: function - config: - tools: - - name: "read_file" - memories: [] # Optional: Memory attachments - retry: # Optional: Retry configuration - enabled: true - max_attempts: 5 -``` - -#### **`human`** -Represents a human interaction point. -```yaml -type: human -config: - description: "Instruction for the human user" - memories: # Optional: Memory attachments for context retrieval and writing - - name: "memory_name" # Reference to a memory store defined in graph.memory - read: true # Enable memory retrieval - write: true # Enable memory writing (human feedback stored) - top_k: 10 # Number of relevant items to retrieve - similarity_threshold: 0.5 # Minimum similarity score (0.0-1.0) - retrieve_stage: # When to retrieve memory - - gen # During generation stage (default if not specified) - - pre_gen_thinking # Before thinking step -``` - -**Note**: -- Memory context retrieved from attached memory stores will be displayed to the human user alongside the description and input data. -- When `write: true`, human feedback will be automatically stored in the memory for future retrieval. -- `retrieve_stage` controls when memory is retrieved. If not specified, defaults to `gen` stage. - -**Best Practice - Memory Flow Patterns**: - -When designing workflows with shared memory between agent and human nodes, consider who should write to which memory: - -1. **State Memory Pattern** (Agent writes, Human reads): - ```yaml - # Environment/state generator agent - - id: environment - type: agent - config: - memories: - - name: environment_memory - read: true - write: true # Agent owns this memory - top_k: 10 - similarity_threshold: 0.0 # IMPORTANT: Use 0.0 to always retrieve recent items - retrieve_stage: - - gen # Retrieve during generation (before model call) - - # Human controller - - id: HumanControl - type: human - config: - memories: - - name: environment_memory - read: true # Human only reads to see history - write: false # Human does NOT write to state memory - top_k: 10 - similarity_threshold: 0.0 # Always show recent history - ``` - **Use when**: Agent generates state/context that human needs to review but not modify directly. The human provides commands via edges, and the agent interprets them to update state. - - **Why `similarity_threshold: 0.0`?** When the agent receives user input like "continue", the query text has low semantic similarity to past state descriptions (e.g., "COVID-19 outbreak" vs "continue"). Setting threshold to 0.0 ensures the agent ALWAYS retrieves its most recent states regardless of query similarity, maintaining continuity. - - **Why `retrieve_stage: gen`?** The `reflection` thinking type only processes memory during the generation stage. Using `pre_gen_thinking` would cause memory to be retrieved but ignored. With `gen` stage, memory is injected into the conversation BEFORE the model generates output, ensuring continuity. - -2. **Feedback Memory Pattern** (Human writes, Agent reads): - ```yaml - # Agent processes feedback - - id: processor - type: agent - config: - memories: - - name: feedback_memory - read: true - write: false - - # Human provides feedback - - id: reviewer - type: human - config: - memories: - - name: feedback_memory - read: true - write: true # Human owns feedback memory - ``` - **Use when**: Human provides annotations, corrections, or judgments that agents need to incorporate. - -3. **Separate Memory Pattern** (Isolated stores): - ```yaml - # Each node has its own memory store - - id: agent_a - memories: - - name: agent_a_memory - - - id: human_b - memories: - - name: human_b_memory - ``` - **Use when**: No memory sharing needed; each node maintains independent history. - -#### **`loop_counter`** -Controls loops by counting iterations. -```yaml -type: loop_counter -config: - max_iterations: 5 # Max allowed loops - reset_on_emit: true # Reset count when condition is met - message: "" # Optional message -``` - -#### **`loop_timer`** -Controls loops by tracking elapsed time. See `yaml_instance/demo_loop_timer.yaml` for comprehensive demo with both standard and passthrough modes. -```yaml -type: loop_timer -config: - max_duration: 2 # Max allowed time - duration_unit: minutes # Unit: 'seconds', 'minutes', or 'hours' - reset_on_emit: true # Reset timer when condition is met (standard mode) - message: "" # Optional custom message when time limit reached - passthrough: false # false (standard): suppress until limit - # true (passthrough): pass through, emit at limit, then transparent -``` -**Standard mode** (`passthrough: false`): Messages suppressed until time limit, then emits message and allows passage. -**Passthrough mode** (`passthrough: true`): Messages pass through immediately, emits message at limit, then becomes transparent. - -#### **`passthrough`** -A simple node that passes data through without modification. -```yaml -type: passthrough -config: {} -``` - -#### **`literal`** -Injects static content into the workflow. -```yaml -type: literal -config: - content: "Static content text" - role: "user" # Role of the message (user, assistant, system) -``` - -#### **`python_runner`** (implied from imports) -Executes Python code. -```yaml -type: python_runner -config: - timeout_seconds: 60 -``` - -## 4. Edge Configuration (`edges`) - -Defines the flow between nodes. - -```yaml -- from: "SourceNodeID" # Required - to: "TargetNodeID" # Required - trigger: true # Default: true. Can trigger target execution? - condition: "true" # Condition to traverse (default "true") - carry_data: true # Pass output of source to target? - keep_message: false # Mark message as 'keep' in target context? - clear_context: false # Clear target's context before adding new data? - clear_kept_context: false # Clear 'kept' messages in target? - processor: # Optional: Transform data before passing to target - type: template - config: - template: "..." - dynamic: # Optional: Dynamic execution (map/tree patterns) - type: map - config: - split_type: regex -``` - -### 4.1 Context Management - -Context management controls how messages accumulate in a node's execution context across workflow execution. - -#### **Context vs Memory** -- **Context**: Message queue visible to a node during execution (temporary, workflow-scoped) -- **Memory**: Persistent storage across workflow runs (permanent, stored in vector database) - -#### **Message Lifecycle** -1. Source node produces output -2. Edge delivers message to target node's context -3. Target node processes messages in its context -4. Context can be cleared or preserved based on edge configuration - -#### **Edge Parameters for Context Control** - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `keep_message` | `bool` | `false` | Mark message as "kept" (survives soft reset) | -| `clear_context` | `bool` | `false` | Clear non-kept messages before adding new data | -| `clear_kept_context` | `bool` | `false` | Clear kept messages (requires `clear_context: true`) | - -#### **Soft Reset vs Hard Reset** - -**Soft Reset** (`clear_context: true`): -- Clears only messages with `keep=false` -- Preserves messages marked with `keep_message: true` -- Use case: Clear temporary data but keep system instructions - -```yaml -- from: UserInput - to: Agent - clear_context: true # Soft reset - keep_message: false -``` - -**Hard Reset** (`clear_context: true` + `clear_kept_context: true`): -- Clears ALL messages (including `keep=true`) -- Complete context isolation -- Use case: Start fresh between workflow rounds - -```yaml -- from: LoopControl - to: StartNode - clear_context: true # Hard reset - clear_kept_context: true -``` - -#### **Context Reset Demo Example** - -See `yaml_instance/demo_context_reset.yaml` for a working demonstration: - -**Workflow Structure**: -1. `entry_normal` → `collector` (`keep_message: false`) - Normal message -2. `entry_keep` → `collector` (`keep_message: true`) - Kept message -3. `soft_reset` → `collector` (`clear_context: true`) - Soft reset -4. `hard_reset` → `collector` (`clear_context: true`, `clear_kept_context: true`) - Hard reset - -**Execution Flow**: -- After `entry_normal` + `entry_keep`: collector has 2 messages -- After `soft_reset`: collector has `entry_keep` + `soft_reset` (normal cleared) -- After `hard_reset`: collector has only `hard_reset` (all previous cleared) - -#### **Common Use Cases** - -**Preserve System Instructions**: -```yaml -- from: SystemSetup - to: Agent - keep_message: true # This survives soft resets - -- from: UserInput - to: Agent - clear_context: true # Clears user messages but keeps system -``` - -**Recursive Workflows with Isolation**: -```yaml -- from: HumanControl - to: StartNode - clear_context: true # Fresh start each round - clear_kept_context: true # Complete isolation - condition: - type: keyword - config: - none: ["EXIT"] # Loop until user says "EXIT" -``` - -**Accumulate Important Context**: -```yaml -- from: CriticalNode - to: Aggregator - keep_message: true # Don't lose this data - -- from: TemporaryNode - to: Aggregator - keep_message: false # Can be cleared later -``` - -### 4.2 Condition Configuration - -Edges can have conditions that determine when they are traversed. - -#### **Keyword Condition** -Match specific keywords in the source node output. - -```yaml -condition: - type: keyword - config: - any: ["APPROVED", "SUCCESS"] # Match if ANY keyword present - none: ["ERROR", "FAILED"] # Match if NONE present - all: ["READY", "VERIFIED"] # Match if ALL present -``` - -#### **Regex Condition** -Match output against a regular expression. - -```yaml -condition: - type: regex - config: - pattern: "^RESULT: .*" - flags: ["IGNORECASE"] -``` - -## 5. Memory Configuration - -Memory stores enable persistent data across workflow runs using vector similarity search. - -### 5.1 Graph-Level Memory Stores - -Define memory stores in the `graph.memory` section: - -```yaml -graph: - memory: - - name: patient_memory - type: simple - config: - embedding: - provider: openai - base_url: ${BASE_URL} - api_key: ${API_KEY} - model: text-embedding-bge-reranker-v2-m3 -``` - -### 5.2 Node-Level Memory Attachments - -Attach memory to nodes for read/write operations: - -```yaml -nodes: - - id: PatientGenerator - type: agent - config: - memories: - - name: patient_memory # Must match graph.memory name - top_k: 50 # Retrieve top 50 similar items - similarity_threshold: 0.3 # Minimum similarity score (0-1) - retrieve_stage: # When to retrieve memory - - pre_gen_thinking # Before thinking step - read: true # Enable reading from memory - write: true # Enable writing to memory -``` - -#### **Memory Parameters** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `name` | `str` | Memory store name (must exist in `graph.memory`) | -| `top_k` | `int` | Number of similar items to retrieve (default: 10) | -| `similarity_threshold` | `float` | Minimum similarity score 0-1 (default: 0.5) | -| `retrieve_stage` | `List[str]` | When to retrieve: `pre_gen_thinking`, `post_gen`, etc. | -| `read` | `bool` | Enable reading from memory (default: false) | -| `write` | `bool` | Enable writing to memory (default: false) | - -#### **Use Cases** - -**Cross-Round Uniqueness** (High top_k, Low threshold): -```yaml -memories: - - name: patient_memory - top_k: 50 # Cast wide net - similarity_threshold: 0.3 # Catch similar items - retrieve_stage: [pre_gen_thinking] - read: true - write: true -``` - -**Recent Context Preservation** (Low top_k, Medium threshold): -```yaml -memories: - - name: environment_memory - top_k: 10 # Only recent context - similarity_threshold: 0.5 # Moderately similar - retrieve_stage: [pre_gen_thinking] - read: true - write: true -``` - -**Human Node Memory Support**: - -Human nodes support full memory functionality including read, write, and retrieve_stage configuration: - -```yaml -nodes: - - id: HumanFeedback - type: human - config: - description: "Review the article and provide feedback..." - memories: - - name: feedback_memory - read: true # Retrieve and display previous feedback - write: true # Store human feedback for future reference - top_k: 10 - similarity_threshold: 0.5 - retrieve_stage: # Control when memory is retrieved - - gen # Default stage for human nodes -``` - -**Features**: -- **Read**: Retrieved memory is displayed alongside the description to help users make informed decisions -- **Write**: Human feedback is automatically stored in memory after user input -- **retrieve_stage**: Controls when memory retrieval occurs. Available stages: `gen`, `pre_gen_thinking`, `post_gen_thinking`, `finished` -- If `retrieve_stage` is not specified, defaults to `gen` stage - -**Use Cases**: -- **Feedback Consistency**: Store and retrieve past user feedback to maintain consistent review standards -- **Context Awareness**: Display historical decisions to inform current choices -- **Learning from History**: Build up knowledge base from human inputs over multiple workflow runs - -## 6. Thinking Module - -The thinking module adds reflection and planning capabilities to agent nodes. - -### 6.1 Reflection Thinking - -Enables post-generation reflection and refinement: - -```yaml -nodes: - - id: environment - type: agent - config: - thinking: - type: reflection - config: - reflection_prompt: | - You have just generated output. Now reflect and refine: - - QUALITY CHECK: - 1. Does the output meet all requirements? - 2. Is it consistent with retrieved memory? - 3. Are there any contradictions or gaps? - - Output ONLY the final refined result with no commentary. -``` - -### 6.2 Thinking with Memory Integration - -Combine thinking with memory retrieval for intelligent context-aware processing: - -```yaml -nodes: - - id: PatientGenerator - type: agent - config: - memories: - - name: patient_memory - retrieve_stage: [pre_gen_thinking] # Retrieve BEFORE thinking - read: true - write: true - thinking: - type: reflection - config: - reflection_prompt: | - Review retrieved patient memory and verify uniqueness: - - Are there duplicate names or symptom patterns? - - Generate fresh alternatives if conflicts exist. - - Output the final unique patient list. -``` - -**Execution Flow**: -1. Node generates initial output -2. Memory is retrieved (if `retrieve_stage: [pre_gen_thinking]`) -3. Retrieved memory + initial output passed to thinking module -4. Thinking module refines and returns final output - -## 7. Dynamic Execution - -Dynamic execution enables parallel processing patterns like map-reduce. - -### 7.1 Map Pattern (Parallel Fan-Out) - -Split input into multiple parallel branches: - -```yaml -edges: - - from: PatientGenerator - to: NurseIntake - dynamic: - type: map - config: - split_type: json_array # Split JSON array into items - item_name: patient # Variable name for each item -``` - -**How it works**: -1. `PatientGenerator` outputs JSON array: `[{patient1}, {patient2}, {patient3}]` -2. Edge splits into 3 parallel executions of `NurseIntake` -3. Each execution receives one patient object - -### 7.2 Supported Split Types - -| Split Type | Description | Example | -|------------|-------------|---------| -| `json_array` | Split JSON array | `[{...}, {...}]` → multiple items | -| `regex` | Split by regex pattern | Split by delimiter | -| `lines` | Split by newlines | Multi-line text → separate lines | - -### 7.3 Real-World Example (Hospital Simulation) - -```yaml -# Patient generator creates array of patients -- id: PatientGenerator - type: agent - config: - role: "Generate array of patients in JSON format" - -# Parallel fan-out to process each patient -edges: - - from: PatientGenerator - to: NurseIntake - dynamic: - type: map - config: - split_type: json_array - item_name: patient - -# Each nurse intake processes one patient in parallel -- id: NurseIntake - type: agent - config: - role: "Process patient: {{patient}}" -``` - -**Result**: 5 patients → 5 parallel NurseIntake executions - -## 8. Edge Processors - -Edge processors transform data before passing to target nodes. - -### 8.1 Template Processor - -Use template strings to format data: - -```yaml -edges: - - from: HumanControl - to: environment - processor: - type: template - config: - template: | - USER INPUT: {{content}} - - INSTRUCTION: Update the environment based on user input. - - If input is minimal ("continue"), maintain scenario with time progression - - If input adds elements, integrate them while preserving context -``` - -**Variables available**: -- `{{content}}`: Message content from source node -- Custom variables from node outputs - -### 8.2 Regex Extract Processor - -Extract specific data using regex: - -```yaml -edges: - - from: Analyzer - to: Processor - processor: - type: regex_extract - config: - pattern: "RESULT: (.*)" - group: 1 -``` - -## 9. Common Errors & Best Practices - -1. **Unique IDs**: Ensure every `id` in `nodes` is unique. Duplicate IDs cause validation failure. -2. **Valid References**: `from` and `to` in `edges` must match exactly with a defined `id` in `nodes`. -3. **Root Structure**: The file **must** have the `graph:` key. `vars:` defines placeholders like `${API_KEY}`. -4. **Type Consistency**: - * `context_window` is an **integer**, not a string. - * `condition` is a string expression (e.g., `"true"`, `"false"`) or a config object. -5. **Agent Config**: - * `name` and `provider` are mandatory for `agent` nodes. - * `tooling` must be a list of tool configurations. -6. **Environment Variables**: Use `${VAR_NAME}` in YAML and define them in `.env` or the `vars` section. The validation logic checks schema but resolves variables at runtime. - -## 9. Common Errors & Best Practices - -1. **Unique IDs**: Ensure every `id` in `nodes` is unique. Duplicate IDs cause validation failure. -2. **Valid References**: `from` and `to` in `edges` must match exactly with a defined `id` in `nodes`. -3. **Root Structure**: The file **must** have the `graph:` key. `vars:` defines placeholders like `${API_KEY}`. -4. **Type Consistency**: - * `context_window` is an **integer**, not a string. - * `condition` is a string expression (e.g., `"true"`, `"false"`) or a config object. -5. **Agent Config**: - * `name` and `provider` are mandatory for `agent` nodes. - * `tooling` must be a list of tool configurations. -6. **Environment Variables**: Use `${VAR_NAME}` in YAML and define them in `.env` or the `vars` section. The validation logic checks schema but resolves variables at runtime. -7. **Memory Configuration**: - * Memory store names in `nodes.config.memories` must exist in `graph.memory`. - * `retrieve_stage` determines when memory is retrieved (before/after thinking). - * Balance `top_k` and `similarity_threshold` based on use case. -8. **Context Management**: - * Use `keep_message: true` for system instructions and critical context. - * Use soft reset (`clear_context: true`) to clear temporary data. - * Use hard reset (`clear_context + clear_kept_context`) for complete isolation. -9. **Dynamic Execution**: - * Ensure source node output format matches `split_type` (e.g., JSON array for `json_array`). - * Use descriptive `item_name` for clarity in target node templates. -10. **Thinking Module**: - * Keep reflection prompts focused and concise. - * Clearly specify expected output format. - * Use thinking with memory retrieval for context-aware refinement. - -## 10. Complete Example: Recursive Hospital Simulation - -This example demonstrates advanced features: recursive loops, memory, thinking, dynamic execution, and context management. - -See `yaml_instance/simulation_hospital.yaml` for the full implementation. - -**Key Features**: - -1. **Environment Continuity** (`environment` node): - - Memory retrieval at `pre_gen_thinking` stage - - Reflection thinking detects "continue" vs "evolve" modes - - Maintains scenario context across rounds - -2. **Unique Patient Generation** (`PatientGenerator` node): - - Memory tracks all previously generated patients - - Reflection thinking verifies uniqueness - - Dynamic execution fans out to parallel patient processing - -3. **Recursive Loop** (edges): - - `HumanControl` → `environment` with hard reset - - Condition: `none: ["SIMULATION ENDED."]` (continues) - - Complete context isolation between rounds - -4. **Parallel Processing** (dynamic execution): - - Patient array splits into parallel nurse/doctor workflows - - Each patient processed independently - - Results aggregate before next round - -**Simplified Structure**: - -```yaml -graph: - memory: - - name: patient_memory - type: simple - - name: environment_memory - type: simple - - nodes: - - id: environment - type: agent - config: - memories: - - name: environment_memory - top_k: 10 - similarity_threshold: 0.5 - retrieve_stage: [pre_gen_thinking] - read: true - write: true - thinking: - type: reflection - config: - reflection_prompt: "Detect mode and maintain continuity..." - - - id: PatientGenerator - type: agent - config: - memories: - - name: patient_memory - top_k: 50 - similarity_threshold: 0.3 - retrieve_stage: [pre_gen_thinking] - read: true - write: true - thinking: - type: reflection - config: - reflection_prompt: "Verify uniqueness against memory..." - - - id: HumanControl - type: human - config: - description: "Enter next scenario or 'SIMULATION ENDED.'" - - - id: SimulationEnd - type: literal - config: - content: "Simulation complete." - - edges: - # Dynamic parallel processing - - from: PatientGenerator - to: NurseIntake - dynamic: - type: map - config: - split_type: json_array - item_name: patient - - # Recursive loop with hard reset - - from: HumanControl - to: environment - clear_context: true # Hard reset - clear_kept_context: true # Complete isolation - condition: - type: keyword - config: - none: ["SIMULATION ENDED."] - - # Exit condition - - from: HumanControl - to: SimulationEnd - condition: - type: keyword - config: - any: ["SIMULATION ENDED."] -``` - -## 11. Validation - -Use the project's validation tool to check your YAML: -```bash -uv run python -m check.check --path yaml_instance/your_workflow.yaml -``` - -This tool performs: -* **Schema Validation**: Checks if fields match the defined dataclasses (`entity/configs`). -* **Structure Validation**: Checks for orphan nodes, invalid edges, and logical consistency. -* **Memory Validation**: Verifies memory store references are valid. -* **Condition Validation**: Ensures condition syntax is correct. - -## 12. Quick Reference - -### Context Management - -| Goal | Configuration | -|------|---------------| -| Keep system instructions | `keep_message: true` on edge | -| Clear temporary messages | `clear_context: true` | -| Complete context reset | `clear_context: true` + `clear_kept_context: true` | -| Preserve between rounds | Use `keep_message: true` on critical edges | - -### Memory Patterns - -| Use Case | top_k | similarity_threshold | -|----------|-------|---------------------| -| Broad historical search | 50+ | 0.3 (low) | -| Recent context | 10 | 0.5 (medium) | -| Exact matches | 5 | 0.7+ (high) | - -### Thinking Integration - -| Retrieve Stage | Purpose | -|----------------|---------| -| `pre_gen_thinking` | Provide memory context before thinking | -| `post_gen` | Retrieve after generation (rare) | - -### Dynamic Execution - -| Split Type | Input Format | -|------------|--------------| -| `json_array` | `[{...}, {...}]` | -| `regex` | Text with delimiter | -| `lines` | Multi-line text | - -### Condition Types - -| Type | Use Case | -|------|----------| -| `keyword` | Match specific words (any/none/all) | -| `regex` | Pattern matching | -| `"true"` | Always traverse (default) | - -## 13. Implementation Patterns - -### Pattern 1: Recursive Workflow with Isolation - -```yaml -edges: - - from: HumanControl - to: StartNode - clear_context: true - clear_kept_context: true - condition: - type: keyword - config: - none: ["EXIT"] -``` - -### Pattern 2: Memory-Based Uniqueness Check - -```yaml -nodes: - - id: Generator - type: agent - config: - memories: - - name: tracking_memory - top_k: 50 - similarity_threshold: 0.3 - retrieve_stage: [pre_gen_thinking] - read: true - write: true - thinking: - type: reflection - config: - reflection_prompt: "Verify uniqueness against retrieved memory..." -``` - -### Pattern 3: Parallel Processing with Aggregation - -```yaml -edges: - # Fan out - - from: Generator - to: Processor - dynamic: - type: map - config: - split_type: json_array - - # Aggregate - - from: Processor - to: Aggregator -``` - -### Pattern 4: Conditional Branching - -```yaml -edges: - - from: Decision - to: PathA - condition: - type: keyword - config: - any: ["APPROVE"] - - - from: Decision - to: PathB - condition: - type: keyword - config: - any: ["REJECT"] -``` - -## 14. Reference Files - -- **Demo Workflows**: `yaml_instance/demo_context_reset.yaml` - Context management demo -- **Complex Example**: `yaml_instance/simulation_hospital.yaml` - Full-featured recursive simulation -- **Configuration Schemas**: `entity/configs/` - Dataclass definitions -- **Validation Logic**: `check/check.py` - Schema and structure validation -- **User Guide**: `docs/user_guide/en/` - Official documentation - ---- - -**Last Updated**: Based on codebase analysis and `demo_context_reset.yaml` demonstration workflow.