I’ve been working on refining the UX for "Hybrid Agents"—assistants that need to switch seamlessly between casual conversation, clarifying requirements, and executing rigid tasks (like an IDE or Researcher).
The challenge is always the same: How do we know when to chat and when to show the "Run" button?
I see a lot of implementations using complex Router Chains or heavy System Prompts to classify intent. But after experimenting with different patterns, I’ve found that the cleanest approach is actually much simpler. It relies entirely on JSON Schema Constraints (Implicit Intent Routing).
Here is the logic I’m using:
1️⃣ The Constraint: Define your tools with strict required parameters (e.g., specific colors, dates, or file paths).
2️⃣ The Router: Use tool_choice="auto" and trust the model.

The result is a seamless 3-state flow without any explicit state management code:
✅ Scenario A (Chat):
User: "Hi"
Agent: Knows it doesn't need tools.
Result: Chat Response.
✅ Scenario B (Negotiation):
User: "Update the UI."
Agent: Wants to use the tool, but the required parameter (color) is missing. It is forced by the schema to ask for it.
Result: "Sure, what color do you prefer?"
✅ Scenario C (Execution):
User: "Update the button to Blue."
Agent: Schema is fully satisfied.
Result: Intercept the tool call → Show "Proceed" Button.
It effectively removes the need for a separate "Intent Classifier" model. The schema is the classifier.
I’d love to hear how you are handling this "Chat vs. Execute" flow in your agents. Is anyone using a different pattern that works better?
# 1. The Schema acts as the State Manager
tool_config = {
"name": "update_ui",
"parameters": {
"properties": {"color": {"type": "string"}},
"required": ["color"] # <--- The magic constraint
}
}
# 2. One generic loop handles all states
response = llm.chat(user_input, tools=[tool_config], tool_choice="auto")
if response.tool_calls:
# Schema satisfied = Intent is clear
# Intercept and show UI Button
show_confirmation_ui(response.tool_calls[0])
else:
# Schema not satisfied OR User is just chatting
# Fallback to conversation
print(response.content)
If you're building agents in 2025, try this pattern for a week.
Would love to hear if this helps you too.
Top comments (0)