Elicitation Patterns¶
Elicitation is the pattern of asking the user for clarification, confirmation, or a choice at runtime. This is the cornerstone of "human-in-the-loop" (HITL) workflows, ensuring that high-stakes or ambiguous decisions are validated by a human.
aifred-tk provides the elicit_choice function, which is environment-aware:
- In MCP clients (like Claude Code), it shows a native GUI dialog.
- In the CLI, it uses an interactive terminal prompt.
For detailed API documentation, see Elicitation Core API.
Pattern: Safety Confirmation¶
Before performing a destructive or broad-reaching action (like deleting files or pushing to production), always ask for confirmation.
from aifred_tk.elicitation import Choice, elicit_choice
from aifred_tk.core.interfaces import ToolResult
def execute(self, args: ToolArgs) -> ToolResult:
# ... logic that prepared a destructive change ...
choice = elicit_choice(
question="Are you sure you want to delete these 50 files?",
choices=[
Choice("yes", description="Proceed with deletion", example="Delete files"),
Choice("no", description="Abort the operation")
],
context="Deleting these files might break the build if they are still referenced."
)
if choice.selected_label != "yes":
return ToolResult(output={"status": "cancelled", "message": "User aborted deletion."})
# ... proceed with deletion ...
return ToolResult(output={"status": "deleted"}, made_changes=True)
Pattern: Refinement (Free Input)¶
If the user's initial input is ambiguous or insufficient, use elicitation to ask for more details.
def execute(self, args: ToolArgs) -> ToolResult:
if not args.search_query:
choice = elicit_choice(
"What exactly are you looking for?",
[Choice("skip", "Never mind")],
allow_free_input=True
)
if choice.is_free_input:
query = choice.free_text
else:
return ToolResult(output={"status": "skipped"})
# ...
Pattern: Strategic Branching¶
Use elicitation to let the user decide which path the tool should take after an initial analysis.
def execute(self, args: ToolArgs) -> ToolResult:
# 1. Analyze code
# 2. Present options to the user
choice = elicit_choice(
"I found a bottleneck. How should I proceed?",
[
Choice("optimize", "Apply the suggested optimization"),
Choice("refactor", "Restructure the class instead"),
Choice("ignore", "I'll handle it later")
]
)
match choice.selected_label:
case "optimize":
# self._apply_optimization returns a ToolResult
return self._apply_optimization(...)
case "refactor":
# self._apply_refactoring returns a ToolResult
return self._apply_refactoring(...)
case _:
return ToolResult(output={"status": "ignored"})