AnyAct: Universal Action for Self-Evolving Agents
Organizations: The University of Hong Kong
Abstract
As large language models (LLMs) advance, AI agents are increasingly deployed in open-world environments to tackle complex sequential tasks (e.g., document processing, cross-application collaboration), relying heavily on actions ranging from GUI operations to semantic APIs. However, three core challenges persist: the "scale dilemma" of massive tool ecosystems exceeding LLM context windows, the "non-stationarity" of tool quality due to updates or outages, and the "heterogeneity" of feedback formats (pixels, text, structured data) creating information silos. To address these, we propose AnyAct, a universal action layer that unifies available capabilities into a self-evolving action space, enabling agents to operate efficiently and reliably in large-scale, dynamic tool ecosystems. AnyAct's core design focuses on two objectives: constructing this action space via hierarchical progressive retrieval (filtering task-relevant actions) and test-time reliability evolution (pruning unreliable actions), and enabling reliability-aware action orchestration through a heterogeneous observation grounding module that unifies multi-modal feedback. Additionally, it defines a hybrid action space (primitive + semantic actions) and optimizes for a balance between task success rate and execution cost. Evaluations on LiveMCPBench and OSMCP (a new benchmark we developed for multi-granularity action collaboration) demonstrate state-of-the-art performance. AnyAct delivers substantial performance gains over baseline methods across various LLM base models on LiveMCPBench and improvements are particularly notable for models with constrained native capabilities. On OSMCP, it achieves 77.27% overall success with only 50 steps, which is half the steps required by most competitors.
Figures & tables
| Model | Method | Office | Leisure | Travel | Lifestyle | Finance | Shopping | Overall ( ) |
|---|---|---|---|---|---|---|---|---|
| Claude-Sonnet-4 | Naive | 90.32 | 64.29 | 75.00 | 80.00 | 78.57 | 66.67 | 78.95 |
| AnyAct w/o Retrieval | 80.65 | 71.43 | 75.00 | 93.33 | 90.91 | 77.78 | 81.52 | |
| AnyAct w/o Evolution | 83.87 | 78.57 | 58.33 | 80.00 | 91.67 | 33.33 | 75.27 | |
| AnyAct | 93.55 | 92.86 | 100.00 | 100.00 | 83.33 | 88.89 | 93.55 | |
| DeepSeek-V3 | Naive | 41.94 | 42.86 | 50.00 | 40.00 | 28.57 | 55.56 | 42.11 |
| AnyAct w/o Retrieval | 51.61 | 42.86 | 33.33 | 60.00 | 50.00 | 33.33 | 47.31 |
| Method | Model | Steps | vlc | gimp | thunderbird | calc | writer | impress | vs_code | Overall (/) | Overall ( ) |
| Foundation E2E GUI | |||||||||||
| Claude-Sonnet-4.5 | — | 15 | 4.89/17 | 13.00/26 | 10.00/15 | 17.00/47 | 14.00/23 | 22.96/47 | 14.00/23 | 95.85/198 | 48.41 |
| opencua-72b-preview | — | 50 | 8.87/17 | 14.00/26 | 12.00/15 | 18.00/47 | 13.00/23 | 26.96/47 | 18.00/23 | 110.83/198 | 55.97 |
| UI-TARS-2-2509 | — | 100 | 8.49/17 | 13.00/26 | 11.00/15 | 31.00/47 | 14.00/23 | 26.50/47 | 17.00/23 | 120.99/198 | 61.11 |
| Claude-Sonnet-4.5 | — | 50 | 9.00/17 | 15.00/26 | 10.00/15 | 31.00/47 | 15.00/23 | 27.02/47 | 16.00/23 | 123.02/198 | 62.13 |
| EvoCUA-20260105 | — | 50 | 8.39/17 | 20.00/26 | 12.00/15 | 26.00/47 | 16.0/23 | 27.96/47 | 20.00/23 | 130.35/198 | 65.83 |
| Task ID: 04d9aeaf-7bed-4024-bedb-e10e6f00eb7f |
|---|
| Task Instruction: In a new sheet with 4 headers “Year”, “CA changes”, “FA changes”, and “OA changes”, calculate the percentage annual changes compared to last year in 2015 to 2019 for the Current Assets, Fixed Assets, and Other Assets columns. |
| Stepwise Execution (Reasoning Tool Call): |
| Step 0 (GUI): “Check if LibreOffice Calc is open with the balance sheet file ready.” gui_agent(task_description="Verify Calc open with SmallBalanceSheet.xlsx", max_steps=15) Step 1 (MCP): “Let me check the current document to understand the data structure.” libreoffice-calc.get_all_document_contents(max_rows=100, max_cols=20) Step 2 (MCP): “Now I’ll create a new sheet with the 4 headers.” libreoffice-calc.create_new_sheet(sheet_name="Annual Changes") Step 3 (MCP): “Set up the headers and calculate the percentage changes.” libreoffice-calc.set_cell_content(cells=[A1:D1 headers, A2:A6 years, B2:D6 formulas]) Complete: “Created sheet ‘Annual Changes’ with percentage annual changes for 2015–2019.” |
| Key Execution Snapshots (cropped worksheet and sheet-tab views): |
| (a) Initial state (b) Sheet created (c) Formulas populated |
Appendix figures & tables9 assets
Supplementary material from the paper’s appendix.
Appendix
| Application | Tasks | Infeasible | Tools | Atomic | Compound | Workflow |
|---|---|---|---|---|---|---|
| LibreOffice Calc | 47 | 1 | 32 | 6 | 21 | 5 |
| LibreOffice Impress | 47 | 0 | 23 | 3 | 20 | 0 |
| LibreOffice Writer | 23 | 1 | 21 | 5 | 14 | 2 |
| GIMP | 26 | 10 | 9 | 4 | 4 | 1 |
| Thunderbird | 15 | 1 | 13 | 10 | 3 | 0 |
| VLC | 17 | 3 | 11 | 8 | 3 | 0 |
| Type | Calc | Impress | Writer | Thunderbird | VS Code | VLC | GIMP | Total |
|---|---|---|---|---|---|---|---|---|
| Operation type | ||||||||
| Read | 3 | 3 | 1 | 4 | 5 | 3 | 1 | 20 |
| Write | 8 | 13 | 15 | 3 | 1 | 1 | 1 | 42 |
| Create | 6 | 2 | 1 | 3 | 0 | 0 | 0 | 12 |
| Transform | 9 | 0 | 3 | 0 | 0 | 0 | 5 | 17 |
| Control | 3 | 1 | 0 | 0 | 7 | 6 | 1 | 18 |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| Fill_blank_above | Fill blank cells with values from cells above | Write | Compound |
| add_new_row | Add new row with data and formulas | Write | Compound |
| calculate_annual_asset_changes | Calculate year-over-year changes in asset values | Transform | Workflow |
| calculate_employee_ages | Calculate ages from birthday column | Transform | Compound |
| clean_text_formatting | Clean and format text in columns (remove whitespace, apply title case) | Transform | Compound |
| copy_column | Copy a column to a new sheet | Write | Compound |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| mcp_libreoffice_add_image_to_slides | Add images to slides with configurable size and position | Write | Compound |
| mcp_libreoffice_add_slide_notes | Add notes to slides, either custom text or copying the slide’s title text | Write | Compound |
| mcp_libreoffice_analyze_slides | Analyze and extract all text contents from slides with formatting info | Read | Compound |
| mcp_libreoffice_configure_auto_save | Configure auto-save functionality to automatically save every N minutes | Configuration | Atomic |
| mcp_libreoffice_create_new_slides | Create new blank slides with configurable positioning | Create | Compound |
| mcp_libreoffice_duplicate_slides | Duplicate specific slides or the last N slides | Create | Compound |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| mcp_libreoffice_add_content_at_cursor | Add content at the current cursor position | Write | Atomic |
| mcp_libreoffice_add_page_numbers | Add page numbers to the document | Write | Compound |
| mcp_libreoffice_append_content | Append plain text content to the document | Write | Atomic |
| mcp_libreoffice_append_formatted_content | Append formatted content to the document | Write | Compound |
| mcp_libreoffice_change_font | Change font throughout the entire document text | Write | Compound |
| mcp_libreoffice_conditional_text_formatting | Apply formatting based on text conditions (e.g., color words by pattern) | Write | Workflow |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| add_email_attachment | Add an attachment to a compose window | Write | Atomic |
| bulk_flag_folder | Flag all messages in a specific folder | Write | Compound |
| get_accounts | Get email accounts via API | Read | Atomic |
| get_auto_forward_rules | Get all configured automatic email forwarding rules | Read | Atomic |
| get_folders | Get folders via API | Read | Atomic |
| get_subject_filter_rules | Get all configured subject-based email filtering rules | Read | Atomic |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| code_checker | Retrieve diagnostics from language services for the active workspace | Read | Atomic |
| execute_command | Execute a command in an integrated terminal | Control | Compound |
| execute_vscode_command | Execute any VSCode command by its command ID | Control | Atomic |
| focus_editor | Open file in editor and navigate to specific line and column | Control | Atomic |
| get_terminal_output | Retrieve the output from a specific terminal by ID | Read | Atomic |
| list_debug_sessions | List all active debug sessions in the workspace | Read | Atomic |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| add_url_to_playlist | Add a URL to playlist without playing immediately | Write | Atomic |
| adjust_volume | Increase or decrease the volume by a specified percentage | Control | Atomic |
| get_available_videos | Get all available videos with their path | Read | Atomic |
| get_status | Get the current status of playback | Read | Atomic |
| get_volume | Get the current volume level (as a percentage) | Read | Atomic |
| seek | Seek to a specific position in the video | Control | Atomic |
| Tool Name | Description | Operation Type | Granularity |
|---|---|---|---|
| adjust_brightness_contrast | Adjust the brightness and contrast of an image or layer | Transform | Atomic |
| call_api | Call GIMP API through the socket connection | Control | Workflow |
| convert_to_palette_mode | Convert an image to palette-based (indexed color) mode | Transform | Compound |
| enhance_color_vibrancy | Enhance color vibrancy and saturation using various methods | Transform | Compound |
| export_image | Export an image to a specified location with a given filename | Export | Compound |
| flip_image | Flip an image or layer horizontally or vertically | Transform | Atomic |