How Do You Handle Conditional Routing and Branching in File-Based AI Agent Workflows?
I had a file-based AI agent that worked beautifully for storing context. It remembered every conversation, tracked every task, and never forgot a user preference. But when I tried to make it branch based on its output, everything fell apart.
The agent would finish a research task and just… stop. It didn’t know whether to continue to writing, or to ask for clarification, or to escalate to a human. The markdown files held all the information, but no logic to act on it.
I posted about this problem and got a blunt reality check:
“Routing logic is where it falls apart. You still need code for conditionals, error handling, retries, rate limiting. The best setups I’ve seen use both. File-based memory, code-based orchestration.” - Dependent_Slide4675
They were right. Markdown files are great for storing knowledge, but they can’t evaluate conditions or make decisions. Let me show you how I solved this.
The Problem: Files Can’t Think
My file-based agent had this structure:
agents/ research-agent/ state.md # Current state memory/ facts.md # What it knows preferences.md # User preferences tasks/ active.md # Current task completed/ # Done tasksThe state.md file looked like this:
# Agent State
## Current TaskResearch competitor pricing for SaaS tools
## StatusResearch Complete
## Last ActionCalled search_web tool with query: "SaaS pricing comparison 2024"
## Findings- Found 15 relevant articles- Extracted pricing for 8 competitors- Need to analyze pricing tiersEverything is there. But how does the agent know what to do next?
I tried encoding logic in the markdown:
# Agent State
## StatusResearch Complete
## Routing Rules- IF status == "Research Complete" AND findings > 10: GOTO analyze- IF status == "Research Complete" AND findings < 5: GOTO clarify- IF status == "Analysis Complete": GOTO reportThis doesn’t work. Markdown is for humans to read, not for machines to execute. The agent would read these “rules” and then ignore them because it had no way to evaluate them.
What File-Based Systems Can and Cannot Do
Let me be clear about the limitations:
CAN DO:+ Store context and memory+ Hold skill definitions+ Provide human-readable instructions+ Version control agent state+ Debug by reading files
CANNOT DO:- Conditional logic (if/then/else)- Looping mechanisms- State evaluation and branching- Error handling and retries- Rate limitingI was trying to make markdown do something it was never designed for. The solution wasn’t to add more rules to files—it was to add code that reads those files.
The Solution: Hybrid Architecture
I restructured my agent to use markdown for knowledge and code for decisions:
+------------------------------------------+| Code Orchestration || +------------+------------+------------+|| | Router | Retrier |Rate Limiter||| +------------+------------+------------+|| | |+------------------------------------------+ |+------------------------------------------+| File-Based Layer || +------------+------------+------------+|| | Skills | Memory | Context ||| | (.md) | (.md) | (.md) ||| +------------+------------+------------+|+------------------------------------------+The code layer handles all the conditional logic. The file layer stores all the knowledge. They work together.
Implementing Conditional Routing with LangGraph
I chose LangGraph for the orchestration layer. It provides a clean way to define states and transitions:
from langgraph.graph import StateGraph, ENDfrom typing import TypedDict, Literalfrom pathlib import Path
class AgentState(TypedDict): task: str status: str findings_count: int next_action: str retry_count: int max_retries: int
def route_decision(state: AgentState) -> Literal["analyze", "clarify", "report", "end"]: """Conditional router based on state evaluation.""" # Check retry limit first if state["retry_count"] >= state["max_retries"]: return "end"
# Route based on findings if state["status"] == "research_complete": if state["findings_count"] >= 10: return "analyze" elif state["findings_count"] >= 5: return "clarify" else: return "end" # Not enough data
if state["status"] == "analysis_complete": return "report"
if state["status"] == "report_complete": return "end"
return "end"
# Build the workflow graphworkflow = StateGraph(AgentState)workflow.add_node("research", research_node)workflow.add_node("analyze", analyze_node)workflow.add_node("clarify", clarify_node)workflow.add_node("report", report_node)
# Add conditional edgesworkflow.add_conditional_edges( "research", route_decision, { "analyze": "analyze", "clarify": "clarify", "report": "report", "end": END })
app = workflow.compile()Now the agent can make decisions. But where does the state come from? It comes from the markdown files.
Reading Routing Hints from Skill Files
Here’s the hybrid part. I store routing hints in the skill markdown files:
# Web Search Skill
## PurposeSearch the web for information.
## Expected Output- results_found: boolean- result_count: number- needs_refinement: boolean
## Routing Hints- If needs_refinement is true, route back to query refinement- If result_count is 0, route to alternative search- If result_count > 10, route to analysisThe code reads these hints and applies them:
from pathlib import Pathimport re
def load_skill_routing_hints(skill_name: str) -> dict: """Extract routing hints from skill markdown.""" skill_path = Path(f"skills/{skill_name}.md") content = skill_path.read_text()
hints = {} hint_section = re.search(r'## Routing Hints\n(.*?)(?=\n##|$)', content, re.DOTALL) if hint_section: for line in hint_section.group(1).strip().split('\n'): if line.startswith('- '): hints[len(hints)] = line[2:]
return hints
def route_from_skill_output(skill_name: str, output: dict) -> str: """Route based on skill output and defined hints.""" hints = load_skill_routing_hints(skill_name)
# Apply the logic described in hints if output.get("needs_refinement"): return "refine_query" if output.get("result_count", 0) == 0: return "alternative_search" if output.get("result_count", 0) > 10: return "analysis"
return "proceed"This gives me the best of both worlds. The routing logic is documented in human-readable markdown, but executed by reliable code.
Adding Error Handling and Retries
One thing markdown absolutely cannot do is error handling. I added a retry mechanism in the orchestration layer:
from dataclasses import dataclassfrom typing import Callable
@dataclassclass RoutingDecision: next_step: str should_retry: bool = False error_message: str = ""
class AgentRouter: def __init__(self, max_retries: int = 3): self.max_retries = max_retries self.retry_counts: dict[str, int] = {}
def route_with_retry( self, step_name: str, output: dict, condition_fn: Callable[[dict], bool] ) -> RoutingDecision: """Evaluate output and determine routing with retry support."""
if condition_fn(output): # Success - reset retry count and move forward self.retry_counts[step_name] = 0 return RoutingDecision(next_step="next")
# Failure - check retry limit current_retries = self.retry_counts.get(step_name, 0) if current_retries < self.max_retries: self.retry_counts[step_name] = current_retries + 1 return RoutingDecision( next_step=step_name, should_retry=True, error_message=f"Retry {current_retries + 1}/{self.max_retries}" )
# Max retries exceeded - route to error handler return RoutingDecision( next_step="error_handler", error_message="Max retries exceeded" )
# Usagerouter = AgentRouter(max_retries=3)
def is_successful(output: dict) -> bool: return output.get("status") == "success"
decision = router.route_with_retry("web_search", output, is_successful)if decision.should_retry: print(f"Retrying: {decision.error_message}")else: print(f"Next step: {decision.next_step}")Now when the web search fails, the agent automatically retries before giving up. No markdown file could do this.
A Complete Workflow Example
Let me show you how all the pieces fit together:
from langgraph.graph import StateGraph, ENDfrom typing import TypedDict, Literalfrom pathlib import Path
class WorkflowState(TypedDict): task: str current_step: str research_results: dict analysis_results: dict retry_count: int
class HybridAgent: def __init__(self, agent_dir: str): self.base_path = Path(agent_dir) self.router = AgentRouter(max_retries=3) self._setup_workflow()
def _setup_workflow(self): """Build the LangGraph workflow.""" workflow = StateGraph(WorkflowState)
# Add nodes workflow.add_node("load_context", self._load_context) workflow.add_node("research", self._research) workflow.add_node("analyze", self._analyze) workflow.add_node("report", self._report)
# Add conditional edges workflow.add_conditional_edges( "research", self._route_after_research, {"analyze": "analyze", "research": "research", "end": END} )
workflow.add_conditional_edges( "analyze", self._route_after_analyze, {"report": "report", "end": END} )
workflow.set_entry_point("load_context") workflow.add_edge("load_context", "research") workflow.add_edge("report", END)
self.app = workflow.compile()
def _load_context(self, state: WorkflowState) -> WorkflowState: """Load context from markdown files.""" state_file = self.base_path / "state.md" if state_file.exists(): # Parse markdown and add to state content = state_file.read_text() state["context"] = self._parse_markdown(content) return state
def _route_after_research(self, state: WorkflowState) -> str: """Decide what to do after research.""" results = state.get("research_results", {})
# Apply routing logic if results.get("findings_count", 0) >= 5: return "analyze" elif state["retry_count"] < 3: state["retry_count"] += 1 return "research" # Try again with different approach else: return "end" # Give up
def _route_after_analyze(self, state: WorkflowState) -> str: """Decide what to do after analysis.""" if state.get("analysis_results"): return "report" return "end"
def run(self, task: str) -> dict: """Execute the workflow.""" initial_state = { "task": task, "current_step": "start", "research_results": {}, "analysis_results": {}, "retry_count": 0 } return self.app.invoke(initial_state)This agent reads from markdown files for context, but uses code for all the decision-making.
Why This Matters
After implementing the hybrid approach, I noticed three key improvements:
Aspect | Pure File-Based | Hybrid--------------------|-----------------|--------Decision making | None | DeterministicError handling | Manual | AutomaticRetry logic | Impossible | ConfigurableDebugging | Read files | Read files + logsState visibility | Excellent | ExcellentThe hybrid approach keeps what makes file-based agents great (visibility, version control, human-readability) while adding what they lack (logic, error handling, control flow).
Common Mistakes
Mistake 1: Trying to encode logic in markdown
## Conditional LogicIF status == "complete": next = "report"IF status == "failed": next = "retry"This is not executable. The agent cannot evaluate these conditions.
Mistake 2: Putting everything in code
If you move all the context to code, you lose the visibility benefit. The hybrid approach keeps knowledge in files and logic in code.
Mistake 3: Over-engineering the router
Keep routing simple. If you need complex decision trees, that’s a sign you need a dedicated tool, not more routing logic.
Mistake 4: Ignoring state management
Without proper state tracking, routing becomes chaotic. Always use a state machine library like LangGraph or XState.
Summary
I spent weeks trying to make my file-based agent make decisions. The answer was simple: files can’t think, but code can.
The hybrid architecture works like this:
- Markdown files store context, memory, skills, and routing hints
- Code orchestration handles conditionals, error handling, retries, and state transitions
- LangGraph provides the state machine for deterministic routing
The result is an agent that has both the visibility of file-based systems and the decision-making capability of code-based systems.
If you’re building a file-based agent, don’t try to make markdown do logic. Add a code layer that reads your files and makes decisions based on them. Your future self will thank you when you can debug both the state (by reading files) and the flow (by reading code).
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 LangGraph Documentation
- 👨💻 Reddit Discussion on File-Based Agents
- 👨💻 XState Documentation
- 👨💻 Python transitions library
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments