question Tool — Design Doc
Summary
Replace auto-generated action blocks with an explicit question tool. Agents call it when they need structured input from Dom. Buttons render in Discord, clicks route back to the agent session.
Why
Current (action blocks):
- Buttons outlive sessions → expiry/failure
- Every turn spawns embedded Pi to predict follow-ups → noise + cost
- 350 LOC, disk persistence, Symbol map, webhooks → complexity
Proposed (question tool):
- Buttons only exist when agent is actively waiting → no expiry
- Only appears when agent intentionally needs input → no noise
- ~100 LOC, uses built-in component system → simple
Tool Spec
question(params) → { answered: true, selected: "..." }
Parameters
prompt(string, required) — The question text shown above the buttonsoptions(array, required) — 2-5 options, each{ label: string, emoji?: string, description?: string }style(string, optional) —"buttons"(default) or"select"(dropdown for 4+ options)timeout(number, optional) — Seconds before expiry. Default: 3600 (1 hour). Max: 86400 (24h).
Example Call
{
"prompt": "Which approach for the cache layer?",
"options": [
{ "label": "Redis", "emoji": "⚡", "description": "Fast, in-memory, needs separate service" },
{ "label": "Postgres", "emoji": "🐘", "description": "Already running, slower but simpler" },
{ "label": "Skip caching", "emoji": "⏭️" }
]
}
Discord Rendering
Buttons Style (default, ≤3 options)
Uses Discord Components v2 container with:
- Text block for prompt
- For each option: text (description) + action row (button)
- Separators between options
- Accent color:
#5865F2(Discord blurple)
Select Style (4+ options or explicit)
Uses string select component with maxValues: 1.
Response Routing — The Key Insight
Synchronous Tool Response (blocking)
Unlike action blocks (which use webhook + system event to simulate a new user turn), question blocks the tool call until Dom responds:
- Agent calls
question()tool - Plugin sends Discord component message via
sendDiscordComponentMessage - Plugin blocks the tool call — does not return result yet
- Dom clicks a button
- Interactive handler fires, resolves the pending promise
- Tool returns
{ answered: true, selected: "Redis", index: 0 } - Agent continues with the answer in context
Why this eliminates the expiry problem:
- The agent's turn is still active (waiting on tool result)
- The session is alive because the tool is pending
- No session resurrection needed
- No webhook impersonation needed
- No disk persistence needed
Timeout Handling
If timeout elapses before Dom clicks:
- Tool returns
{ answered: false, reason: "timeout" } - Buttons are edited to show "Expired" state (disabled, greyed out)
- Agent can decide to re-ask or proceed with a default
Disabling Action Blocks
Kill entirely (not opt-in). One line change in index.js:
- actionBlocks.setup(api);
+ // actionBlocks.setup(api) — replaced by question tool
Then clean up: delete lifecycle/action-blocks.js, .action-blocks-state.json, and action block refs in tools/debug.js.
Coexistence with show
show= Agent → User (output). Share long-form content via CF Pages link.question= Agent ← User (input). Get structured choice from user via buttons.
They complement each other. An agent could combine them:
show(content="Here's the analysis of both approaches...")question(prompt="Which one?", options=[{label:"A"}, {label:"B"}])
Migration Path
- Implement
questiontool alongside action blocks - Disable action blocks
- Clean up action blocks code
- Update SKILL.md files to document
question
Open Questions
- Multi-select? Add
multi: trueparam for picking multiple options? - Free-text? Optional "Other..." button that opens a modal for custom input?
- Auto-question on explicit questions? No — the whole point is intentional, not automatic.
- Tool availability: All agents, or only mindset agents? (Suggestion: all)
Estimated Complexity
- New:
tools/question.js(~150 LOC) - Modified:
index.js(register tool + disable action blocks) - Deleted:
lifecycle/action-blocks.js,.action-blocks-state.json - Time: ~2-3 hours