feat(capabilities): add QuestionCapability and migrate BackgroundTaskCapability from xeno-agent - #346
Conversation
…support Move question tools into a proper AbstractCapability that accepts args.schemas and args.enabled_tools, mirroring BackgroundTaskCapability. Replaces the bare QuestionTools entry point so consumers can customize LLM-facing parameter descriptions via YAML schema files without writing their own capability wrapper.
Move 51 question tool unit tests (40 for question_for_user + 11 for ask_followup_question) from xeno-agent to agentpool. Tests now import from agentpool_toolsets.builtin.question_tools instead of xeno_agent. Two assertions adjusted to match agentpool's actual implementation: - Error message regex: 'questionnaire' → 'questions' (agentpool naming) - ask_followup_question metadata: dropped suggestion_attributes check (agentpool's _format_followup_response doesn't emit this field) Also add RUF001 per-file ignore for CJK fullwidth punctuation in test data.
Add a 'question' tool to QuestionCapability that replicates the legacy
QuestionTool behavior (simple prompt + optional response_schema via MCP
Elicit). This unifies all user-interaction tools under one capability:
- question_for_user: XML multi-question questionnaire
- ask_followup_question: single question with <suggest> options
- question: simplest single-question (replaces QuestionTool)
Mark QuestionToolConfig (tools: [{type: question}]) as deprecated,
directing users to capabilities: [{type: question}].
Add 3 new tests covering the question tool: default-enabled, enabled
alone, enabled via schemas.
…gent Migrate the complete BackgroundTaskCapability implementation to agentpool: - capability.py: full lifecycle management (task, background_output, background_cancel, steer_task tools) - manager.py: BackgroundTaskManager with concurrent task execution, cleanup, and session isolation - notification.py: NotificationBatcher for debounced completion notifications - types.py: BackgroundTask, SessionTaskState dataclasses - utils/tool_schema.py: YAML schema loading and LLM-facing schema override Includes 277 tests (unit + integration + resource provider) covering lifecycle, concurrency, error propagation, notification batching, history isolation, and cancellation regression. Config schema files (task.yaml, background_output.yaml, background_cancel.yaml, steer_task.yaml) provide default LLM-facing parameter descriptions. Entry point 'background_task' registered in pyproject.toml.
Three CI failures fixed:
1. ruff format: question.py ternary expression reformatted
2. mypy (16 errors → 0):
- tool_schema.py: cast yaml.safe_load/json.loads results to
OpenAIFunctionDefinition, construct TypedDict with explicit
key-value pairs instead of ** unpacking
- question.py: add ToolResult return annotations to tool wrappers
- manager.py: re-read task_model.status into locals after await
to prevent mypy narrowing from concurrent status changes
- capability.py: type session_pool as SessionPool | None,
fix delivered bool assignment from followup() str|None return,
remove dead config.type == 'team' comparison (agents dict never
contains team configs), rename shadowed task_model variable
3. Flaky tests (8 failures): replace fixed asyncio.sleep(0.8) with
_wait_until_called() polling helper in 3 test files. The fixed
sleep was too tight under CI load — debounce timers fire late
when the event loop is busy with parallel workers.
…cationBatcher Root cause: loop.call_later callbacks run in a separate contextvars context where anyio's sniffio async-library detection fails with AsyncLibraryNotFoundError. This prevented _schedule_flush from calling tg.start_soon, so _flush never executed and deliver_callback was never invoked — all 25 batcher tests + 8 notification tests failed in CI. Fix: replace anyio.create_task_group/start_soon with asyncio.ensure_future for scheduling flush coroutines. Keep anyio.CancelScope and anyio.fail_after for timeout protection (these don't require sniffio context). Track flush tasks in a set[asyncio.Task] and cancel/await them in shutdown. Also restore source_type team detection by checking config.type via str() cast (mypy-safe for AnyAgentConfig union that doesn't include team types at the type level, but mocks provide type='team' at runtime).
Merge ask_followup_question, question_for_user, and question into one unified question tool per reviewer feedback. The question_for_user implementation (richest, supports multi-question XML with enum/multi/ input types) is retained as the canonical implementation, renamed to question. ask_followup_question (legacy compat) and the simple question tool are removed. Changes: - question_tools.py: remove ask_followup_question + _format_followup_response, rename question_for_user to question - question.py: simplify QuestionCapability to expose only question - Update all tests, docs, and tool name references
|
No GitHub token is available for direct Review:
|
#1 tool_call_id: QuestionCapability._question already uses replace() to propagate tool_name/tool_call_id/tool_input — verified with L2 test. #2 telemetry: add @logfire.instrument to QuestionCapability._question; background_task modules already instrumented. #3 state cleanup: after_run() evicts _session_states and _ephemeral_states, shuts down batcher and task manager. #5 queued cancel: pending cancel path fires on_completed before completion_event.set(). #6 flush exception: _flush catches broad Exception, marks delivered regardless of success/failure. #7 timeout message: CancelledError handler checks task.status == 'timed_out' before choosing message. #8 private API: guard pydantic_ai._agent_graph import with try/except and helpful error message. #9 L2 test: add test_question_tool_propagates_tool_call_id_to_agent_context. #10 error contract: _build_definition raises ValueError with descriptive message for missing name or non-dict input. Nit: remove DEBUG_TASK_MGR prefix, type coro as Coroutine, fix test names.
|
/oc |
…tion/background_task entry points)
- src/agentpool_toolsets/builtin/subagent_tools.py: remove list_available_nodes method + create_tool registration - src/agentpool_config/toolsets.py: SubagentToolName Literal now only accepts 'task'; docstring updated - tests/toolsets/test_tool_filtering.py: update assertions - tests/toolsets/builtin/test_as_capability.py: update assertions - tests/servers/acp_server/test_claude_acp_toolset_integration.py: update assertion - tests/tools/test_runcontext.py: remove prompt referencing list_available_nodes (test was already xfail) - docs/how-to/advanced/acp-integration.md: update docs - docs/how-to/servers/mcp-server.md: update docs The tool was legacy code; Leoyzen noted agents list is now injected directly into system prompt.
|
/oc |

Summary
Migrate
questionandbackground_taskcapabilities from xeno-agent into agentpool as built-inAbstractCapabilitysubclasses with YAML schema override support.QuestionCapability (
src/agentpool/capabilities/question.py)questiontool with multi-question XML questionnaire support (enum/multi/input types)schemas: dict[str, str]for YAML schema file paths (resolved relative toCONFIG_DIR)enabled_tools: list[str]to control which tools are exposedagentpool_toolsets.builtin.question_tools.QuestionToolsask_followup_questionmerged intoquestionper reviewer feedback — single tool, simpler APIBackgroundTaskCapability (
src/agentpool/capabilities/background_task/capability.py)task,background_output,background_cancel,steer_taskload_skills,expected_output,titleparameters on taskNotificationBatcherfor debounced batch completion notificationsforce_retrievalmode (disabled/tool_choice/directive) to ensure pending task results are retrievedSessionTaskStatewith concurrency control, timeout, and cleanupafter_node_runhook for completion notification injectionBug Fixes
anyio.TaskGroupwithasyncio.ensure_futureinNotificationBatcherto fix Python 3.13 compatibilityChanges