Skip to content

Conversation

@chughtapan
Copy link
Contributor

@chughtapan chughtapan commented Aug 2, 2025

@evalstate
Copy link
Owner

@chughtapan -- the Python SDK now supports this I believe - is there more to be done or is this good-to-go?

@chughtapan
Copy link
Contributor Author

IIUC SEP-1330 is still in-review - A few weeks ago it was deferred to an live meeting in the async vote, so I'm hoping it'll be on the agenda whenever the next core maintainer meeting is.
The python-sdk modelcontextprotocol/python-sdk#1246 is still open - unless i missed something that happened in another PR?

Tapan Chugh added 2 commits November 18, 2025 21:02
Resolved conflicts:
- Updated import paths from mcp_agent to fast_agent
- Kept ValidatedCheckboxList import for SEP-1330 enum support
- Preserved text_navigation_mode global variable from main
- Moved form_elements.py to correct location (fast_agent/human_input)

This merge brings in 100+ commits from main including:
- Directory restructure (mcp_agent → fast_agent)
- New ACP support
- FastAPI examples
- Tool display improvements
- Many bug fixes and enhancements
@chughtapan chughtapan changed the title WIP: PoC demonstrating new enum schemas + multi-selection MCP SEP-1330: Elicitation schema updates for Enums Nov 19, 2025
@chughtapan chughtapan marked this pull request as ready for review November 19, 2025 05:22
@chughtapan
Copy link
Contributor Author

@evalstate the python-sdk changes were merged recently, so should be included in the next release i guess.

What more do you think this PR needs before merging?

@evalstate
Copy link
Owner

I think when I picked this up last, the test program wasn't including the list of workshops which sent me down an SDK rabbit hole with a mental note to "pick this up when the next SDK is released". I might not upgrade immediately as https://discord.com/channels/1358869848138059966/1443632409844383905/1443711409530867742 makes me want to some extended interop testing/think about what we need to do to support it properly.

@evalstate
Copy link
Owner

image Updating with 1.23.1 think we are all good.

@evalstate evalstate merged commit ff23382 into evalstate:main Dec 6, 2025
6 checks passed
iqdoctor pushed a commit to strato-space/fast-agent that referenced this pull request Dec 11, 2025
* WIP: PoC demonstrating new enum schemas + multi-selection

* Cleanup checkbox impl

* add bare enum support

* Add missing type field in multi-select schema (minor fix for SEP-1330 compliance)

* bump mcp sdk with support for 1330

* update demo

---------

Co-authored-by: Tapan Chugh <[email protected]>
Co-authored-by: evalstate <[email protected]>
@evalstate evalstate mentioned this pull request Dec 14, 2025
evalstate added a commit that referenced this pull request Dec 14, 2025
* workflow: Agents as Tools — BASIC agents with child_agents expose children as tools with parallel execution

- add AgentsAsToolsAgent (ToolAgent subclass) that lists child agents as tools and runs tool calls in parallel
- factory: BASIC with child_agents -> AgentsAsToolsAgent; otherwise keep McpAgent
- validation: include BASIC.child_agents in dependency graph for proper creation order

* workflow: suppress child agent display + simplify aggregated view for Agents-as-Tools

- pass RequestParams(show_chat=False, show_tools=False) to child agents when invoked as tools
- always use aggregated display regardless of single/parallel tool count
- single agent: 'Calling agent: X' with full content blocks in result
- multiple agents: summary list with previews
- removes duplicate stacked tool call/result blocks

* fix: suppress child display via config modification, not RequestParams

- RequestParams doesn't support show_chat/show_tools (those are Settings.logger fields)
- temporarily modify child.display.config before calling generate()
- restore original config in finally block
- fixes 'AsyncCompletions.create() got unexpected keyword argument' error

* display: show detailed I/O for each agent tool call/result

- display individual tool call blocks with full arguments for each agent
- display individual tool result blocks with full content for each agent
- removes minimal aggregated view in favor of detailed per-agent display
- fixes missing chat logs for agent arguments and responses

* feat: add instance count indicator for parallel agent execution

- show 'instances N' in status when multiple agents called in parallel
- metadata['instance_info'] passed to tool_call display
- _instance_count attribute added to tool_result for display
- parallel execution already working via asyncio.gather
- displays in right_info: 'tool request - name | instances 2'

* refactor: optimize AgentsAsToolsAgent code

Optimizations:
- Move json and copy imports to module level (avoid repeated imports)
- Remove unused _tool_names variable
- Simplify child agent lookup with chained or operator
- Streamline input_text serialization logic (remove nested try/except)
- Remove redundant iteration in _show_parallel_tool_results
- Remove unnecessary descriptor_by_id.get() checks (key always exists)
- Simplify inline conditionals for readability

No behavior changes, purely code cleanup and performance improvement.

* feat: add instance IDs to progress + restore child tool logs

Changes:
- Add instance IDs (: 1, : 2, etc.) to child agent names when instances > 1
  - Modified before task creation so progress events use numbered names
  - Restored after execution completes
  - Shows as 'PM-1-DayStatusSummarizer: 1' and 'PM-1-DayStatusSummarizer: 2' in progress panel
- Restore child agent tool call logs (show_tools)
  - Only suppress show_chat (child's assistant messages)
  - Keep show_tools=True to see child's internal tool activity
  - Fixes 'lost logs from child agents' issue

Result: Separate progress lines for parallel instances + full visibility into child tool calls

* fix: use _name attribute instead of name property for instance IDs

- name is a read-only @property that returns self._name
- setting child.name had no effect
- now properly modifies child._name to show instance numbers in progress panel
- fixes missing :1 :2 labels in progress display

* style: align code style with library conventions

- Use modern type hints: dict/list instead of Dict/List (PEP 585)
- Use pipe union syntax: Any | None instead of Optional[Any] (PEP 604)
- Add comprehensive docstrings to all public methods
- Remove unnecessary imports (Dict, List, Optional)
- Improve inline comments clarity
- Match formatting style used in tool_agent.py and parallel_agent.py

No functional changes, pure style alignment.

* style: change instance ID format from ': 1' to '#1'

- Cleaner display format for parallel agent instances
- Shows as 'PM-1-DayStatusSummarizer#1' and 'PM-1-DayStatusSummarizer#2'
- Appears in both progress panel and chat headers

* ui: show instance count in tool name instead of metadata

Changes:
- Agent names: 'PM-1-DayStatusSummarizer[1]' instead of 'PM-1-DayStatusSummarizer#1'
- Tool headers: '[tool request - agent__PM-1-DayStatusSummarizer[2]]' instead of '[... | instances 2]'
- Tool results: '[tool result - agent__PM-1-DayStatusSummarizer[2]]'
- Removed metadata-based instance display from tool_display.py

Cleaner display: instance count embedded directly in tool name for both requests and results.

* fix: show individual instance numbers [1], [2] in tool headers

Fixes:
1. Tool headers now show individual instance numbers [1], [2] instead of total count [2]
   - Tool request: 'agent__PM-1-DayStatusSummarizer[1]' for first call
   - Tool request: 'agent__PM-1-DayStatusSummarizer[2]' for second call
2. Bottom items show unique labels: 'agent__PM-1[1] · running', 'agent__PM-1[2] · running'
3. Store original names before ANY modifications to prevent [1][2] bug
4. Wrapper coroutine sets agent name at execution time for progress tracking

Note: Separate progress panel lines require architecture changes (same agent object issue).

* feat: add separate progress panel lines for parallel instances

Implements user's suggested UX:
1. Parent agent line shows 'Ready' status while instances run
2. New lines appear: PM-1-DayStatusSummarizer[1], PM-1-DayStatusSummarizer[2]
3. Each instance line shows real-time progress (Chatting, turn N, tool calls)
4. After completion, instance lines are hidden from progress panel
5. Parent agent name restored

Flow:
- Emit READY event for parent agent (sets to idle state)
- Create unique agent_name for each instance
- Emit CHATTING event to create separate progress line
- Child agent emits normal progress events with instance name
- After gather() completes, hide instance task lines

Result: Clean visual separation of parallel executions in left status panel.

* fix: show tool call status in instance lines, not parent

Problem: When child agents called tools, progress events (CALLING_TOOL) were emitted
with parent agent name instead of instance name, causing tool status to appear in wrong line.

Root cause: MCPAggregator caches agent_name in __init__, so changing child._name didn't
update the aggregator's agent_name. When aggregator emits progress for tool calls, it
used the old cached name.

Solution:
- Update child._aggregator.agent_name when setting instance name
- Restore child._aggregator.agent_name when restoring original name
- Now tool call progress (Calling tool, tg-ro, etc.) appears in correct instance line

Result: Each instance line shows its own 'Calling tool' status independently.

* fix: explicitly enable show_tools for child agents

Ensures child agent tool calls remain visible in chat log by explicitly
setting show_tools = True when creating temporary config.

* fix: hide parent line during parallel execution, only show instances

Changes:
- Parent agent line now hidden when child instances start (not 'Ready')
- Only child instance lines visible during parallel execution
- Each instance shows independent status
- After completion: parent line restored, instance lines hidden

Result: Clean progress panel with no 'stuck' parent status. Only active
instance lines show during execution.

* docs: add comprehensive README for agents-as-tools pattern

Added module-level documentation covering:

1. Overview
   - Pattern inspired by OpenAI Agents SDK
   - Hierarchical composition without orchestrator complexity

2. Rationale
   - Benefits over traditional orchestrator/iterative_planner
   - Simpler codebase, better LLM utilization
   - Natural composition with parallel by default

3. Algorithm
   - 4-step process: init → discovery → execution → parallel
   - Detailed explanation of each phase

4. Progress Panel Behavior
   - Before/during/after parallel execution states
   - Parent line shows 'Ready' during child execution
   - Instance lines with [1], [2] numbering
   - Visibility management for clean UX

5. Implementation Notes
   - Name modification timing (runtime vs creation time)
   - Original name caching to prevent [1][2] bugs
   - Progress event routing via aggregator.agent_name
   - Display suppression strategy

6. Usage Example
   - Simple code snippet showing pattern in action

7. References
   - OpenAI Agents SDK link
   - GitHub issue placeholder

* fix: hide instance lines immediately when each task completes

Problem: Instance lines stayed visible showing 'stuck' status even after
completing their work. Instance[1] would show 'Chatting' even though it
finished and returned results.

Root cause: Instance lines were only hidden after ALL tasks completed via
asyncio.gather(). If one instance finished quickly and another took longer,
the first instance's line remained visible with stale status.

Solution:
- Add finally block to task wrapper coroutine
- Hide each instance line immediately when its task completes
- Remove duplicate hiding logic from cleanup section
- Now each instance disappears as soon as it's done

Result: Clean, dynamic progress panel where instance lines appear when
tasks start and disappear as each individual task finishes.

* fix: use consistent progress_display instance for visibility control

Problem: Instance lines remained visible ('stuck') even after tasks completed.

Root cause: progress_display was being re-imported in multiple scopes,
potentially creating different singleton instances or scope issues.

Solution:
- Import progress_display once at outer scope as 'outer_progress_display'
- Use same instance in wrapper coroutine's finally block
- Use same instance for parent Ready status update
- Added debug logging to track visibility changes

Note: The 'duplicate records' in chat log are actually separate results from
parallel instances [1] and [2], not true duplicates. Each instance gets its
own tool request/result header for clarity.

* fix: prevent display config race conditions in parallel instances

Problem: Only seeing logs from instance #4 when multiple instances of the
same child agent run in parallel.

Root cause: Multiple parallel instances share the same child agent object.
When instance 1 finishes, it restores display config (show_chat=True), which
immediately affects instances 2, 3, 4 that are still running. The last
instance (#4) ends up with restored config and shows all its chat logs.

Race condition flow:
1. Instance 1 starts → sets show_chat=False on shared object
2. Instances 2,3,4 start → see show_chat=False
3. Instance 1 finishes → restores show_chat=True
4. Instances 2,3,4 still running → now have show_chat=True (see logs!)

Solution: Reference counting
- Track active instance count per child agent ID
- Only modify display config when first instance starts
- Only restore display config when last instance completes
- Store original config per child_id for safe restoration

Data structures:
- _display_suppression_count[child_id] → count of active instances
- _original_display_configs[child_id] → stored original config

Now all instances respect show_chat=False until ALL complete.

* docs: update module documentation with latest implementation details

Updated comprehensive documentation to reflect:

Algorithm section:
- Reference counting for display config suppression
- Parallel execution improvements (name+aggregator updates, immediate hiding)

Progress Panel Behavior:
- As each instance completes (not after all complete)
- No stuck status lines
- After all complete (restoration of configs)

Implementation Notes:
- Display suppression with reference counting explanation
- _display_suppression_count and _original_display_configs dictionaries
- Race condition prevention details (only modify on first, restore on last)
- Instance line visibility using consistent progress_display singleton
- Chat log separation with instance numbers for traceability

All documentation now accurately reflects the production implementation.

* fix: duplicate labels, final logs without instance index

Fixed three issues:

1. Duplicate labels in bottom status bar
   - Before: Each tool call showed ALL instance labels
   - After: Each tool call shows only its OWN label
   - Changed from passing shared bottom_items array to passing single-item array per call

2. Final logs showing without instance index
   - Before: Display config restored in call_tool finally block, causing final logs
     to use original name (no [N])
   - After: Display config restoration moved to run_tools, AFTER all tool results
     are displayed
   - Now all logs (including final) keep instance numbers: PM-1[1], PM-1[2], etc.

3. Display config restoration timing
   - Removed restoration from call_tool finally block
   - Added restoration in run_tools after _show_parallel_tool_results
   - Cleanup of _display_suppression_count and _original_display_configs dictionaries

Result:
- Bottom bar: | PM-1[1] · running | (no duplicates)
- Final logs: ▎◀ PM-1-DayStatusSummarizer[4] [tool result] (keeps index)
- Clean separation of instance logs throughout execution

* fix: label truncation and display config restoration

Fixed three issues:

1. Label truncation in bottom status bar
   - Increased max_item_length from 28 to 50 characters
   - Prevents '...' truncation of long agent/tool names
   - Now shows: agent__PM-1-DayStatusSummarizer[1] (full name)

2. Display config reference counting improvements
   - Separate initialization of _display_suppression_count and _original_display_configs
   - Increment count BEFORE checking if first instance
   - Only modify config if count==1 AND not already stored
   - Added debug logging to track suppression lifecycle

3. Config restoration timing and cleanup
   - Added logging to track decrements in finally block
   - Check existence before accessing/deleting dictionary keys
   - Restore config for both multi-instance and single-instance cases
   - Clean up suppression count only when it reaches 0

The reference counting now ensures:
- First instance (count 0→1): Suppress chat, store original config
- Additional instances (count 1→2,3,4): Use existing suppressed config
- Instances complete (count 4→3,2,1): Keep suppressed config
- Last instance completes (count 1→0): Restore original config

Debug logs added:
- 'Suppressed chat for {name} (first instance)'
- 'Decremented count for {name}: N instances remaining'
- 'Restored display config for {name}'

* fix: move display suppression to run_tools before parallel execution

Problem: Only instance #4 was showing chat logs. The issue was that call_tool
was trying to suppress display config inside each parallel task, creating a
race condition where configs would get overwritten.

Solution:
1. Move display suppression to run_tools BEFORE parallel execution starts
2. Iterate through all child agents that will be called and suppress once
3. Store original configs in _original_display_configs dictionary
4. Remove all suppression logic from call_tool - it just executes now
5. After results displayed, restore all configs that were suppressed

This ensures:
- All instances use the same suppressed config (no race conditions)
- Config is suppressed ONCE before parallel tasks start
- All parallel instances respect show_chat=False
- Config restored after all results are displayed

The key insight: Don't try to suppress config inside parallel tasks - do it
before they start so they all inherit the same suppressed state.

* fix: create new display objects for suppression instead of just modifying config

Problem: Even with pre-suppression, instances were still showing chat logs because
they all share the same display object and config modifications weren't taking
effect properly.

Solution:
1. Create completely new ConsoleDisplay objects with suppressed config
2. Replace child.display with the new suppressed display object
3. Store both the original display object and config for restoration
4. After results shown, restore the original display object (not just config)

This ensures complete isolation - each parallel execution uses a display object
that has show_chat=False baked in from creation, eliminating any timing issues
or race conditions with config modifications.

The key insight: Don't just modify config on shared objects - create new objects
with the desired behavior to ensure complete isolation.

* fix: eliminate name mutation race condition in parallel execution

Problem: All 4 parallel tasks were modifying the same child agent's _name
simultaneously, causing a race condition where the last task to set it (usually
instance [4]) would dominate the logs. Events from instances [1], [2], [3] were
showing up under the main instance name or instance [4].

Root Cause:
- Tasks ran concurrently: asyncio.gather(*tasks)
- Each task did: child._name = instance_name (MUTATING SHARED STATE\!)
- Race condition: Last writer wins, all tasks use that name
- Result: All logs showed instance [4] name

Solution - Sequential Name Ownership:
1. Build instance_map BEFORE tasks start
   - Maps correlation_id -> (child, instance_name, instance_num)
   - No shared state mutation yet

2. Each task owns the name during its execution:
   - On entry: Save old_name, set instance_name
   - Execute: All logs use this instance's name
   - On exit (finally): Restore old_name immediately

3. This creates sequential ownership windows:
   - Task 1: Sets [1], executes, restores
   - Task 2: Sets [2], executes, restores
   - Each task's logs correctly show its instance number

Additional Changes:
- Removed display suppression to see all logs for debugging
- Keep main instance visible in progress panel (don't hide/suppress)
- Each task restores names in finally block (no global cleanup needed)
- Pass correlation_id to wrapper so it can lookup pre-assigned instance info

This ensures each instance's logs are correctly attributed to that instance,
making event routing visible for debugging.

* fix: remove agent renaming to eliminate race condition

Problem: Multiple concurrent tasks were mutating the same child agent's _name,
causing:
1. Race condition - tool calls from different instances got mixed up
2. Duplicate progress panel rows - each rename triggered new events
3. Logs showing wrong instance numbers

Root Cause: Even with try/finally, execution overlaps:
- Task 1: Sets name to [1], starts executing
- Task 2: Sets name to [2] (overwrites\!), Task 1 still running
- Task 1's logs now show [2] instead of [1]

Solution: Don't rename agents AT ALL
- Instance numbers already shown in display headers via _show_parallel_tool_calls
- Display code already does: display_tool_name = f'{tool_name}[{i}]'
- No need to mutate shared agent state
- Each task just calls the tool directly
- Parallel execution works without interference

Benefits:
- True parallel execution (no locks/serialization)
- No race conditions (no shared state mutation)
- No duplicate panel rows (child emits events with original name)
- Instance numbers still visible in tool call/result headers

The instance_map is now only used for logging context, not for renaming.

* fix: suppress child progress events to eliminate duplicate panel rows

Problem: Duplicate progress panel rows showing 4+ entries for PM-1-DayStatusSummarizer

Root Cause: Each child agent execution emits its own progress events, creating
a new panel row each time. With 4 parallel instances, we got 4+ duplicate rows.

Solution: Suppress child display output during parallel execution
1. BEFORE parallel tasks start: Suppress child.display.config
   - Set show_chat = False
   - Set show_tools = False
   - This prevents child from emitting ANY display events

2. Execute parallel tasks: Child runs silently, no panel rows created

3. AFTER results shown: Restore original child.display.config

Benefits:
- Only orchestrator's display headers show (with instance numbers [1], [2], etc.)
- No duplicate progress panel rows
- Clean consolidated view of parallel execution
- Instance numbers still visible in tool call/result headers

The key insight: Child agents should be 'silent' during parallel execution,
letting the orchestrator handle all display output.

* fix: use NullDisplay to completely suppress child output during parallel execution

Problem: Still seeing duplicate progress panel rows despite display config suppression

Root Cause: Progress events are NOT controlled by display.config.logger settings.
They come from a separate progress system that gets called regardless of config.

Solution: Replace child.display with NullDisplay during parallel execution

NullDisplay class:
- Has config = None
- Returns no-op lambda for ANY method call via __getattr__
- Completely suppresses ALL output: chat, tools, progress events, everything

Flow:
1. BEFORE parallel: child.display = NullDisplay()
2. DURING parallel: All child output suppressed (no panel rows)
3. AFTER parallel: child.display = original_display (restored)

Benefits:
- Zero duplicate panel rows (child can't emit ANY events)
- Zero race conditions (no shared state mutations)
- Clean orchestrator-only display with instance numbers [1], [2], [3], [4]
- True parallel execution maintained

* fix: also suppress child logger to prevent progress events

Progress events are emitted by logger.info() calls, not just display.
Need to suppress BOTH display AND logger to eliminate duplicate panel rows.

Added NullLogger class that suppresses all logging calls.
Store and restore both display and logger during parallel execution.

* fix: also suppress aggregator logger to block MCP tool progress events

MCP tools emit progress events via aggregator.logger, not child.logger.
Need to suppress aggregator's logger too.

Now suppressing:
- child.display
- child.logger
- child._aggregator.logger (NEW - this was the missing piece\!)

This should finally eliminate all duplicate progress panel rows.

* refactor: simplify child suppression to config-only approach

Reverted from NullDisplay/NullLogger approach back to simpler config modification.

Suppression approach:
- Store original child.display.config
- Create temp config with show_chat=False, show_tools=False
- Apply temp config during parallel execution
- Restore original config after results shown

Benefits:
- Simpler implementation (no complex null object classes)
- Less intrusive (just config changes, not object replacement)
- Easier to debug and maintain
- Still prevents duplicate progress panel rows

This approach relies on display.config.logger settings to control output,
which should be sufficient for most cases.

* docs: add comprehensive documentation for parallel execution approach

Added detailed inline documentation explaining:

1. PARALLEL EXECUTION SETUP section:
   - Instance numbering strategy (displayed in headers only)
   - Display suppression approach (config modification)
   - Why we avoid agent renaming (prevents race conditions)

2. _show_parallel_tool_calls docstring:
   - Example output showing instance numbers [1], [2], [3], [4]
   - Explains orchestrator displays tool call headers

3. _show_parallel_tool_results docstring:
   - Example output showing matching instance numbers in results
   - Shows how instance numbers correspond to calls

Key design principles documented:
- NO agent renaming during execution (true parallelism)
- Instance numbers ONLY in display headers (no shared state)
- Display suppression via config (prevents duplicate panel rows)
- Orchestrator-only display (child agents silent during parallel execution)

This documentation makes the parallel execution strategy clear
for future maintenance and debugging.

* refactor: first instance runs normally, only instances 2+ get indexed

Architectural improvement suggested by user:
- First instance executes without index or suppression (natural behavior)
- Only when 2nd+ instances appear, they get indexed [2], [3], [4] and suppressed

Benefits:
1. Simpler logic - first instance untouched, runs as designed
2. Less config manipulation - only suppress when truly needed
3. More intuitive - single execution looks normal, parallel adds indexes
4. Cleaner code - fewer edge cases and state changes

New numbering:
- Instance 1: PM-1-DayStatusSummarizer (no index, full display)
- Instance 2: PM-1-DayStatusSummarizer[2] (indexed, suppressed)
- Instance 3: PM-1-DayStatusSummarizer[3] (indexed, suppressed)
- Instance 4: PM-1-DayStatusSummarizer[4] (indexed, suppressed)

Progress panel shows single entry from first instance.
Instances 2+ are silent (suppressed) to avoid duplicates.

Updated documentation and examples to reflect new approach.

* feat: all instances visible in panel, only streaming suppressed for 2+

Major architectural improvements based on user feedback:

1. PANEL VISIBILITY:
   - First instance: PM-1-DayStatusSummarizer (full display + streaming)
   - Instances 2+: PM-1-DayStatusSummarizer[2], [3], [4] (visible in panel)
   - ALL instances shown in progress panel (no hiding)

2. STREAMING SUPPRESSION:
   - First instance: streaming_display=True (typing effect visible)
   - Instances 2+: streaming_display=False (no typing clutter)
   - Instances 2+: show_chat=True, show_tools=True (panel entries visible)
   - Only the typing effect is suppressed, not the entire display

3. THREAD SAFETY:
   - Added self._instance_lock (asyncio.Lock) in __init__
   - Protected instance creation with async with self._instance_lock
   - Prevents race conditions on concurrent run_tools calls
   - Sequential modification of instance_map and suppressed_configs

Benefits:
- User sees all parallel instances progressing in panel
- No visual clutter from multiple streaming outputs
- First instance behaves naturally (untouched)
- Thread-safe instance creation for concurrent calls

This approach provides full visibility into parallel execution while
avoiding the distraction of multiple simultaneous typing effects.

* feat: detach agents-as-tools instances and harden MCP task groups

- Add detached per-call cloning in LlmDecorator so child agents can be
  spawned via spawn_detached_instance and later merged with
  merge_usage_from.
- Rework AgentsAsToolsAgent.run_tools to execute child agents in parallel
  using detached clones, with clearer per-instance progress lines and
  tool-call/result panels.
- Track ownership of MCPConnectionManager in MCPAggregator and only shut
  it down from the owning aggregator, fixing “Task group is not active”
  errors when short‑lived clones exit.
- Improve MCPAggregator tool refresh to rebuild namespaced tool maps per
  server and log UPDATED progress events with tool counts.
- Extend log→ProgressEvent conversion to treat THINKING like STREAMING
  for token counts and to use the typed ProgressAction field.
- Add RichProgressDisplay.hide_task API for future UI behaviors and wire
  small fastagent/listener changes around the updated progress pipeline.

* feat: detach agents-as-tools instances and harden MCP task groups

- Add detached per-call cloning in LlmDecorator so child agents can be
  spawned via spawn_detached_instance and later merged with
  merge_usage_from.
- Rework AgentsAsToolsAgent.run_tools to execute child agents in parallel
  using detached clones, with clearer per-instance progress lines and
  tool-call/result panels.
- Track ownership of MCPConnectionManager in MCPAggregator and only shut
  it down from the owning aggregator, fixing “Task group is not active”
  errors when short‑lived clones exit.
- Improve MCPAggregator tool refresh to rebuild namespaced tool maps per
  server and log UPDATED progress events with tool counts.
- Extend log→ProgressEvent conversion to treat THINKING like STREAMING
  for token counts and to use the typed ProgressAction field.
- Add RichProgressDisplay.hide_task API for future UI behaviors and wire
  small fastagent/listener changes around the updated progress pipeline.

* agents-as-tools: clean debug hooks and finalize progress UI

- Remove temporary FAST_AGENT_DEBUG flag and prints from FastAgent.__init__
- Drop file-based progress debug logging from core.logging.listeners.convert_log_event
- Remove RichProgressDisplay.hide_task and update design docs to FINISHED-based instance lines
- Fix _invoke_child_agent indentation and guard display suppression with suppress_display flag

* agents-as-tools: clean progress wiring and restore upstream listeners

- Restore convert_log_event in core/logging/listeners.py to upstream-style
  ProgressAction handling (no extra debug logging)
- Keep RichProgressDisplay FINISHED/FATAL_ERROR behavior simple: mark the
  current task completed without hiding other tasks
- Align Agents-as-Tools design docs with detached per-call clones and
  FINISHED-based progress lines (no hide_task API)
- Clarify AgentsAsToolsAgent module docstring and helper behavior to match
  current implementation (_invoke_child_agent, detached clones, usage merge)

* Hybrid Agents-as-Tools MCP-aware agent

- Make AgentsAsToolsAgent subclass McpAgent instead of ToolAgent
- Merge MCP tools and agent-tools into a single list_tools() surface
- Route call_tool() to child agents first, then fall back to MCP/local tools
- Update run_tools() to split mixed batches into child vs MCP calls and
  execute child calls via detached clones while delegating remaining tools
  to McpAgent.run_tools(), merging all results and errors
- Keep existing detached per-call clone behavior and progress panel semantics
- Update agents-as-tools design doc and module docstrings to describe the
  hybrid MCP-aware behavior and mark merged MCP + agent-tools view as implemented

* Added §3.3 “Minimal usage sample (for docs and examples)”

* Add PMO Agents-as-Tools examples and tidy AgentsAsToolsAgent

- Add simple PMO Agents-as-Tools example (agents_as_tools_simple.py) with
  NY-Project-Manager and London-Project-Manager using the local `time` MCP server.
- Add extended PMO example (agents_as_tools_extended.py) that uses `time` + `fetch`,
  retries alternative sources on 403/robots.txt, and includes Fast-Agent / BBC / FT hints.
- Update README Agents-as-Tools section with the PMO minimal example and a link
  to the extended workflow file.
- Run black and minor style cleanups on AgentsAsToolsAgent without changing
  behavior.

* Document AgentsAsToolsAgent and polish parallel tool UI

- Expand module docstring with Agents-as-Tools rationale, algorithm, and
  progress/usage semantics.
- Add minimal decorator-based usage example showing agents=[...] pattern.
- Add GitHub-style links to design doc, docs repo, OpenAI Agents SDK, and
  issue #458 for future readers.
- Keep runtime behavior unchanged apart from clearer structure and black
  formatting (no logic changes).

* Finalize Agents-as-Tools PMO examples and hybrid agent docs

- Add simple and extended PMO Agents-as-Tools workflows using local time/fetch MCP servers.
- Document AgentsAsToolsAgent behavior and architecture in README and module docstring.
- Wire detached clone support via LlmDecorator.spawn_detached_instance and merge_usage_from.
- Fix import ordering and type-checking-only imports so scripts/lint.py passes cleanly.

* Add Vertex ADC support and preview model fallback

* Add vertex config tests for Google provider

* Cover Vertex dict config client init and preview fallback

* remove ESC key handling complexity; ctrl+c still cancels generation (#519)

* remove ESC key handling complexity; ctrl+c still cancels generation

* opus 4.5 support

* opus 4.5

* version bump

* integration test (#521)

* update tool timing saving, including transport channel (#523)

more sensitive markdown detection

* allow absolute paths for skills directories (#524)

* Feat/model env option (#526)

* model environment variable option

* model env var

* lint

* Feat/reasoning streaming (#529)

* upgrade skills, export load_prompt for convenience

* stream reasoning tokens

* Implement Agent Client Protocol tools for CLI (#528)

* Add ACP tool call permissions with persistence

Implement tool permission system for ACP mode:

- PermissionStore: Persist allow_always/reject_always decisions in
  .fast-agent/auths.md (human-readable markdown format)
- ACPToolPermissionManager: Request permissions from ACP clients via
  session/request_permission, with support for allow_once, allow_always,
  reject_once, reject_always options
- ToolPermissionHandler: Protocol for MCP aggregator integration,
  enabling permission checking before tool execution
- Fail-safe: Default to DENY on any error during permission checks

CLI changes:
- Add --no-permissions flag to serve and acp commands to disable
  permission requests (allows all tool executions)

ACP compliance:
- Send ToolCall object with permission request per ACP spec
- Support all permission option kinds (allow_once, allow_always,
  reject_once, reject_always)
- Persist 'always' decisions across sessions via auths.md file

Tests:
- Unit tests for PermissionStore and PermissionResult
- Unit tests for _infer_tool_kind function
- Integration tests for permission flow (queued for later verification)

* Add permission checks to ACP terminal and filesystem runtimes

Extend permission checking to cover ACP external runtimes:

- ACPTerminalRuntime: Check permission before executing shell commands
- ACPFilesystemRuntime: Check permission before reading/writing files

The permission handler is now injected into both runtimes from the
ACP server during session setup. This ensures all tool executions
go through the permission system, not just MCP server tools.

Fail-safe: deny execution if permission check fails.

* Fix ACP tool permission test failures

- Fix FakeOutcome constructor parameter name (option_id -> optionId)
- Update integration tests to check notifications instead of non-existent
  PromptResponse.message attribute
- Add --no-permissions flag to filesystem/telemetry tests that don't test
  permissions
- Add test doubles and edge case tests for ACPToolPermissionManager

* improve ACP permissioning

* fix: Centralize robust API retry logic in FastAgentLLM & preserve context (#517)

* fix: Add retry loop for transient API errors (Rate Limits/5xx)

* fix: Add robust retry loop for transient API errors & preserve context

* fix: Implement robust retry logic for transient API errors in agent execution (for all scenarios)

* lint

* test, config file, non-error path reinstated.

* test?

* tests?

* loop diagnosis

* root cause?

---------

Co-authored-by: evalstate <[email protected]>

* Dev/0.4.2 (#530)

* upgrade skills, export load_prompt for convenience

* reasoning improvements

* simplify streaming etc.

* return reasoning_content for models such as kimi-k2-thinking and glm-4.6 with thinking on

* reasoning_content as string

* type safety, hf provider display & acp

* otel off

* switch off otel

* fix test

* acp tool streaming, openai diags

* improve tool streaming

* simplify perms for streaming, env flag for openai trace

* only update title after 20 chunks

* update unit test

* update streaming titles (completion)

* parallel tool calling for ACP

* gpt-oss reasoning/tool interleaving support

* fix linter

* replaced with env option

* Compare session termination handling implementations (#532)

* feat: Add reconnect_on_disconnect option for handling server session termination

When a remote StreamableHTTP MCP server restarts, the session becomes invalid
and the server returns a 404 error. This change adds support for automatic
reconnection when this happens.

Changes:
- Add `reconnect_on_disconnect` config option to MCPServerSettings (default: false)
- Add ServerSessionTerminatedError exception with SESSION_TERMINATED_CODE = -32600
- Detect MCP SDK error code -32600 (session terminated) in MCPAgentClientSession
  with fallback to checking "session terminated" in error message
- Add reconnect_server() method to MCPConnectionManager
- Handle session terminated errors in MCPAggregator with reconnection support
- Refactor connection error handling into dedicated helper methods
- Add comprehensive unit tests for config, exception, and detection logic

The reconnection flow:
1. Session error detected (404 -> MCP error code -32600)
2. If reconnect_on_disconnect is enabled for the server:
   - Disconnect the current server connection
   - Re-establish a fresh connection with new session
   - Retry the failed operation
3. If disabled, show a helpful tip about enabling the option

Usage in fastagent.config.yaml:
```yaml
mcp:
  servers:
    my-server:
      url: https://example.com/mcp
      reconnect_on_disconnect: true
```

* fix: Allow ServerSessionTerminatedError to pass through try_execute for reconnection

The inner try_execute function was catching all exceptions except ConnectionError
and converting them to error results, preventing ServerSessionTerminatedError from
reaching the reconnection handler. Now both ConnectionError and
ServerSessionTerminatedError pass through to the outer exception handlers.

* fix: Prevent infinite reconnection loop on persistent session termination

Added explicit handling for ServerSessionTerminatedError in retry paths to
prevent infinite loops when the server keeps returning session terminated
errors even after reconnection.

Changes:
- Catch ServerSessionTerminatedError separately in _handle_session_terminated
  retry path - return clear error message instead of retrying again
- Catch ServerSessionTerminatedError in _handle_connection_error retry path
- Add descriptive error messages explaining what happened

* fix: Improve session terminated detection with string-based fallback

The detection was failing because it only checked for McpError type with
specific error code or message field. Now it first checks the string
representation of ANY exception for "session terminated", which is more
robust and handles cases where the exception type or structure differs.

This ensures the reconnection logic is triggered regardless of the
exact exception type used by the MCP SDK.

* fix: Use correct positive 32600 error code for session terminated

The MCP SDK streamable_http.py uses positive 32600 for session terminated
errors, not the standard JSON-RPC negative -32600 (INVALID_REQUEST).

This was discovered by reviewing the SDK source code:
https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/client/streamable_http.py#L366

The error code detection should now work correctly for McpError instances.
The string-based fallback remains as an additional safety net.

* refactor: Simplify session terminated detection

Now that we have the correct error code (32600), removed unnecessary
complexity:
- Removed string-based fallback detection
- Simplified _is_session_terminated_error to just check McpError code
- Reduced tests from 18 to 8 focused tests

* fix: Ensure session terminated detection by always using overridden send_request

When call_tool, read_resource, or get_prompt were called without _meta,
they bypassed our overridden send_request() and called super() directly.
This meant the McpError from the SDK transport layer never reached our
_is_session_terminated_error() detection code, causing reconnection to
never trigger.

Now all three methods always construct the request themselves and call
self.send_request() to ensure exceptions flow through our detection logic.

* fix: Handle exceptions during session/transport cleanup in lifecycle task

When disconnecting a server for reconnection, the session or transport
cleanup might throw exceptions (e.g., if the session was already terminated).
These exceptions were propagating to the shared task group and causing
"unhandled errors in a TaskGroup" errors.

Added nested exception handling around both session and transport context
manager exits to catch and log cleanup errors gracefully.

* debug: Add warning-level logging to diagnose session terminated detection

* debug: Add stderr prints to trace exception handling

* debug: Use rich console for debug output

* feat: Clean up session termination handling and add reconnect counter

- Remove debug logging from session termination detection
- Add reconnect_count to ServerStats to track successful reconnections
- Add reconnect_count to ServerStatus for /mcp display
- Display reconnect count in /mcp output when count > 0

The reconnection feature is now production-ready:
- Detects MCP error code 32600 (session terminated from 404)
- Attempts reconnection when reconnect_on_disconnect is enabled
- Prevents infinite retry loops
- Reports failures to the model
- Tracks reconnection statistics

* default reconnect to true for session termination

* forward progress token

* Feat/auth status acp (#533)

* Add /status auth and /status authreset ACP slash commands

- /status auth: displays content of ./fast-agent/auths.md or "No permissions set"
- /status authreset: removes the auths.md file
- Updated hint to show available options [system|auth|authreset]

* Show resolved path in /status auth and authreset output

Helps debug path resolution issues by displaying the absolute path
that was checked in all response scenarios.

* Fix auths.md path: use .fast-agent hidden directory

---------

Co-authored-by: Claude <[email protected]>

* Improve acp progress (#534)

* fix: Include rawInput in ACP permission request toolCall

Per the ACP specification, the ToolCall in a RequestPermissionRequest
should include rawInput so clients can display the full tool arguments
when asking users for permission.

Changes:
- Add rawInput=arguments to ToolCall in permission requests
- Remove non-existent 'prompt' field from RequestPermissionRequest
- Include argument summary in toolCall.title for better UX
- Update test fake to parse title with argument suffixes
- Add test assertions to verify rawInput is included

* feat: Update tool title with MCP progress info

Add progress percentage and message to tool call titles during MCP
progress notifications. This gives users better visibility into
long-running tool operations.

Changes:
- Add _base_titles dict to track base titles by tool_call_id
- Store base title in on_tool_start for both streaming and non-streaming paths
- Update on_tool_progress to build title like "server/tool(args) [50%] - message"
- Clean up _base_titles on completion and session cleanup
- Add tests verifying progress title updates with percentage and message

* fix: Improve progress title display and image removal messaging

Two improvements:

1. Progress title simplification:
   - Use simple title (server/tool) instead of full title with args
     during MCP progress updates for cleaner display
   - e.g., "server/tool [50%] - Downloading..." instead of
     "server/tool(arg=val) [50%] - Downloading..."

2. Image content removal placeholder:
   - Add placeholder text when unsupported content (images, documents)
     is removed due to model limitations
   - Prevents empty content which could cause hangs
   - Message: "[Vision content (image/png) was removed - model does
     not support this content type]"

* fix: Rename message to progress_message in logger to avoid argument conflict

* fix: Remove content from progress updates since title now shows message

* debug: Add logging to diagnose tool completion hang

* debug: Add more logging for tool completion diagnosis

* fix: Ensure permission request uses same toolCallId as streaming notification

When a tool call notification is sent early during streaming, the permission
request must reference the same toolCallId so the client can correlate them.

Changes:
- Add get_tool_call_id_for_tool_use() method to ACPToolProgressManager
- Update ACPToolPermissionAdapter to accept tool_handler reference
- Look up existing ACP toolCallId before creating permission request
- Pass tool_handler when creating permission adapter in ACP server

* fix: Await streaming task before looking up toolCallId for permission

The streaming notification task runs asynchronously and might not complete
before permission is checked. This caused the toolCallId lookup to fail,
resulting in permission requests using a different ID than the tool call
notification.

Changes:
- Make get_tool_call_id_for_tool_use async
- Wait for pending stream task to complete before looking up toolCallId
- Add fallback to check _tool_call_id_to_external_id mapping
- Update adapter to await the async method

* fix: Update progress format to show progress/total per MCP spec

Changed progress title format from percentage ([50%]) to progress/total
format ([50/100]) to align with MCP specification. The MCP spec states
that progress values may be floating point and increment even when
total is unknown - showing raw progress values is more accurate than
computing percentages.

* fix: Only add placeholder text when ALL content is removed

The previous fix added a placeholder for every removed content block,
which broke tests expecting to find image content in channels. Now
placeholder is only added when content would otherwise be completely
empty, which was the original intent to prevent ACP client hangs.

* fix: Truncate long content in /status error channel display

Base64 content (like images) in error channels was being displayed in
full, which could be very long. Now truncates to first 60 characters
and shows total length (e.g., "...60 chars... (12780 characters)").

* fix: Restore full title with parameters on tool completion

When a tool completes, the title was showing the last stale progress
update (e.g., "[50/100]"). Now stores the full title with parameters
at tool start and restores it when the tool completes, so completed
tools show their arguments rather than progress indicators.

* test: Update content filter tests for placeholder-only-when-empty behavior

Tests now expect:
- No placeholder when some content remains (just keeps valid content)
- Placeholder only when ALL content is removed (e.g., tool results)
- Detailed mime type info in error channel, not in placeholder text

* Simplify /status error handling output when no errors (#535)

Show "_No errors recorded_" instead of verbose channel details
when there are no error entries.

Co-authored-by: Claude <[email protected]>

* version bump

* Add custom refinement instruction on @fast.evaluator_optimizer (#538)

* add an instruction for the refinement agent

* added refinment instruction in the refinement prompt also

* remove debugging nonesense

* remove testing print

* restore as it was

* feat: Add video support for Google Gemini provider (#537)

* feat: Add video support for Google Gemini provider
- Add video MIME type handling in GoogleConverter
- Add unit tests for video resource conversion
- Update README with multimodal support details
- Document 20MB inline data limit

* lint

* prep launch

* test, example etc. (stuck on model overload messages)

---------

Co-authored-by: evalstate <[email protected]>

* Feat/acp sdk update (#543)

* upgrade to 0.7.0 and fix a couple of small things

* missed file

* update

* MCP SEP-1330: Elicitation schema updates for Enums (#324)

* WIP: PoC demonstrating new enum schemas + multi-selection

* Cleanup checkbox impl

* add bare enum support

* Add missing type field in multi-select schema (minor fix for SEP-1330 compliance)

* bump mcp sdk with support for 1330

* update demo

---------

Co-authored-by: Tapan Chugh <[email protected]>
Co-authored-by: evalstate <[email protected]>

* tidy up root; sdk bumps

* OpenAI Providers custom HTTP Headers (#544)

* feat: Add custom headers support for OpenAI-compatible providers

Add support for configuring custom HTTP headers via `default_headers`
in provider settings. This enables use cases like Portkey integration
and other API gateways that require custom headers.

Changes:
- Add `default_headers` field to all OpenAI-compatible provider settings
- Add `_default_headers()` method to OpenAILLM base class
- Override `_default_headers()` in each provider to read from config
- Pass headers to AsyncOpenAI client via `default_headers` param
- Add comprehensive unit tests for header configuration

Providers with custom header support:
- OpenAI, DeepSeek, Groq, xAI, Google (OAI), OpenRouter
- Generic, TensorZero, HuggingFace, Aliyun

Example configuration:
```yaml
openai:
  api_key: sk-xxx
  default_headers:
    x-portkey-config: "config-id"
    x-custom-header: "value"
```

* lint

* simplify header management

---------

Co-authored-by: Claude <[email protected]>

* otel off

* fix assertions

* version bump

* Review ACP implementation with new SDK Union types (#549)

* refactor: use ACP SDK's ContentBlock Union type for cleaner type handling

- Import ContentBlock from acp.helpers instead of manually defining
  ACPContentBlock Union in content_conversion.py
- Update agent_acp_server.py prompt method signature to use ACPContentBlock
- Refactor tool_progress.py to use match statements for MCP to ACP
  content conversion (more pythonic)
- Use SDK's resource_block helper for embedded resource conversion
- Extract tool kind patterns into class-level constant for cleaner code
- Simplify annotation conversion with getattr() instead of hasattr checks
- Remove redundant Union imports

All 133 tests pass (78 unit + 55 integration).

* lint

---------

* feat: detach agents-as-tools instances and harden MCP task groups

- Add detached per-call cloning in LlmDecorator so child agents can be
  spawned via spawn_detached_instance and later merged with
  merge_usage_from.
- Rework AgentsAsToolsAgent.run_tools to execute child agents in parallel
  using detached clones, with clearer per-instance progress lines and
  tool-call/result panels.
- Track ownership of MCPConnectionManager in MCPAggregator and only shut
  it down from the owning aggregator, fixing “Task group is not active”
  errors when short‑lived clones exit.
- Improve MCPAggregator tool refresh to rebuild namespaced tool maps per
  server and log UPDATED progress events with tool counts.
- Extend log→ProgressEvent conversion to treat THINKING like STREAMING
  for token counts and to use the typed ProgressAction field.
- Add RichProgressDisplay.hide_task API for future UI behaviors and wire
  small fastagent/listener changes around the updated progress pipeline.

* Agents-as-Tools: options struct, history safety, call_tool compat, UI collapse

* Agents-as-Tools: options plumbing and limits

* Agents-as-Tools: options keyword-only

* Agents-as-Tools: compact display suppression and cleanup

* Agents-as-Tools: implement history fork/merge modes

* Agents-as-Tools: simplify display suppression setup

* Agents-as-Tools: trim redundant import and reuse totals

* Docs/options: clarify AgentsAsTools defaults; move decorator kwargs

* Agents-as-Tools: rename tool options payload

* Agents-as-Tools: drop legacy tool_options fallback

* Docs: mark Agents-as-Tools plan items completed

* Docs: move completion markers to start

* Docs: drop per-instance futures from Agents-as-Tools plan

* Docs: document Agents-as-Tools options and merged tool surface

* Tests: cover Agents-as-Tools list/run/error paths

* Docs: mark tool merge complete in Fix plan

* Tests: cover nested Agents-as-Tools instance labeling

* Agents-as-Tools: add correlation metadata for progress/tool logs

* Chore: fix imports after lint

* remove debug print

* update tool display to show subagent label

* updated lockfile

* chore: remove agents-as-tools plan docs

* revert aggregator tool handling change (this looks like an llm modification error)

* remove debug file writing

* exception trace logging

* simplify lock behavior to avoid racy - concurrent to agent instance

---------

Co-authored-by: shaun smith <[email protected]>
Co-authored-by: usama <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: bandinopla <[email protected]>
Co-authored-by: John Cyriac <[email protected]>
Co-authored-by: Tapan Chugh <[email protected]>
Co-authored-by: Tapan Chugh <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants