Human-in-the-Loop
Agents can ask users for input when they need clarification or approval. This works at both the agent level and the orchestrator level.
Agent-level input
Pass an on_ask_user callback to let the agent ask for user input:
agent_hitl.py
from veska import Agent
def ask_user(question: str) -> str:
"""Called when the agent needs user input"""
print(f"Agent asks: {question}")
return input("Your answer: ")
agent = Agent(
name="assistant",
system_prompt="You help build projects. Ask the user for preferences when needed.",
model="claude-sonnet-4-6",
on_ask_user=ask_user,
ask_user_timeout=300, # 5 minutes to respond (default)
)
# The agent will call ask_user when it needs input
result = agent.run("Set up a new web project for me")The callback can be sync or async. Veska handles both automatically. If the user doesn't respond within ask_user_timeout seconds, the agent continues without input.
Orchestrator clarification
The Orchestrator can ask questions before planning. You provide a prompt that tells the AI what to ask about:
clarification.py
from veska import Orchestrator
def ask_user(questions: str) -> str:
print(f"\nOrchestrator needs clarification:\n{questions}")
return input("\nYour answers: ")
orchestrator = Orchestrator(
model="claude-sonnet-4-6",
agents=[backend, frontend],
clarification_prompt="""Before planning, ask about:
- Database preference (PostgreSQL, MySQL, SQLite)
- Frontend framework (React, Vue, plain HTML)
- Authentication requirements
Only ask what's relevant to the request.""",
on_ask_user=ask_user,
)
# When the user says "Build me a blog app", the orchestrator will
# first ask about database, frontend, and auth preferences,
# then create a plan based on the answers
result = orchestrator.run("Build me a blog app")Checkpoints
Checkpoints pause execution and wait for approval. The orchestrator uses them to present the plan before executing:
checkpoint.py
from veska import EventType
# Listen for checkpoint events
async def on_checkpoint(event):
print(f"\nCheckpoint: {event.data['title']}")
print(f"Description: {event.data['description']}")
approval = input("Approve? (y/n): ")
if approval == "y":
orchestrator.events.approve_checkpoint(
event.data["checkpoint_id"],
feedback="Looks good"
)
else:
orchestrator.events.reject_checkpoint(
event.data["checkpoint_id"],
feedback="Please add more research tasks"
)
orchestrator.events.on(EventType.CHECKPOINT, on_checkpoint)Input requests via events
For web UIs, use the event system instead of callbacks:
events_hitl.py
# Listen for input requests
async def on_need_input(event):
prompt = event.message
# Show prompt in your UI, wait for user response
# Then send the feedback back:
await orchestrator.events.send_feedback(user_response)
orchestrator.events.on(EventType.NEED_INPUT, on_need_input)Pause and resume
control.py
# Pause execution (e.g., user clicks "pause" in UI)
await orchestrator.events.pause()
# Resume when ready
await orchestrator.events.resume()
# Cancel everything
await orchestrator.events.cancel()