Skip to content

Deep research feature: Added base deep-research functionality#2

Merged
tangkenzee merged 43 commits into
mainfrom
deep-research-feature
Jan 8, 2026
Merged

Deep research feature: Added base deep-research functionality#2
tangkenzee merged 43 commits into
mainfrom
deep-research-feature

Conversation

@Thambolo

Copy link
Copy Markdown
Collaborator

This pull request introduces several significant improvements and updates to the browser-agent project, focusing on documentation, configuration, CI/CD setup, and code organization. The most important changes include a major expansion of a new GitHub Actions workflow for automated testing, and updates to project structure and configuration files to reflect recent best practices and enhancements.

Documentation and Developer Experience:

  • Renamed CLAUDE.md to GEMINI.md and updated its content to reflect the switch from Claude to Gemini as the default LLM, along with improvements to setup, testing, and development instructions. [1] [2] [3] [4] [5] [6]
  • Updated README.md to clarify the project structure, usage of the new browser_agent package, plugin integration, and the Chrome profile handling mechanism. [1] [2] [3] [4] [5] [6]

Automation and Configuration:

  • Added a new GitHub Actions workflow (.github/workflows/ci.yml) to automate testing on push and pull requests, including environment setup, dependency installation, Playwright browser setup, and running tests (excluding manual ones).
  • Updated .co/config.toml to use a newer framework version, set the default model to co/gemini-2.5-pro, and improved secret management by referencing a .env file.

Project Organization:

  • Refactored project structure in README.md to reflect the introduction of the browser_agent package and its submodules, and clarified the location of key files and scripts for easier navigation and maintenance. [1] [2] [3]

These changes collectively modernize the project, improve developer onboarding, and lay the groundwork for more robust and reusable agent functionality.

References:
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15]

@Thambolo Thambolo self-assigned this Dec 16, 2025

@wu-changxing wu-changxing left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding CI and deep research feature, but the file structure needs rework.

Problem

Creating browser_agent/ folder with entrypoint.py is over-engineered:

  • agent.pybrowser_agent/entrypoint.py → creates Agent (extra layer for no benefit)
  • Folder name browser_agent/ doesn't match the feature being added (deep research)
  • Moving existing files breaks imports unnecessarily

Proposed Structure

browser-agent/
├── agent.py                 # Main entry point (keep in root)
├── tools/
│   ├── __init__.py
│   ├── web_automation.py    # Shared browser tool
│   └── scroll_strategies.py
├── agents/
│   ├── __init__.py
│   └── deep_research.py     # Sub-agent
├── prompts/
│   ├── browser_agent.md     # Main agent prompt
│   └── deep_research.md     # Sub-agent prompt
├── tests/
├── .github/workflows/ci.yml  # Keep this - CI is good
├── CLAUDE.md                 # Keep - don't rename to GEMINI.md
└── README.md

Why This Structure

Folder Purpose
tools/ Shared capabilities (web_automation used by ALL agents)
agents/ Sub-agents only (deep_research, future sub-agents)
prompts/ All system prompts in one place
agent.py in root Entry point, not a sub-agent

What to Change

  1. Delete browser_agent/ folder and entrypoint.py
  2. Create tools/, agents/, prompts/ folders instead
  3. Keep agent.py in root as entry point
  4. Keep CLAUDE.md (don't rename to GEMINI.md)
  5. Keep CI pipeline - that's useful

This separates concerns (tools vs agents vs prompts) without unnecessary abstraction layers.

Comment thread agents/deep_research.py Outdated
This helps in breaking down complex research goals into manageable sub-tasks.
"""

def __init__(self, web_automation: WebAutomation):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: self.research_agent is created here but NEVER USED. In perform_deep_research() you create a NEW Agent called researcher. This is wasted code.

Either:

  1. Remove this and just create Agent in perform_deep_research(), OR
  2. Use self.research_agent.input(topic) instead of creating new one

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wu-changxing I don't see an issue here, there is no self.research_agent

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have now created it to make the class manage the agent state

Comment thread agents/deep_research.py
"""

from typing import Optional
import os

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over-engineered: Why use a class? This could be a simple function:

def create_research_agent(web):
    return Agent(
        name='deep_researcher',
        tools=web,
        ...
    )

No need for class wrapper when there's no state to manage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm keeping the class because I want to initialize the sub-agent once (in init) and reuse it across multiple calls to avoid overhead. This makes the class a stateful container for the sub-agent.

Comment thread web_automation.py
from connectonion import xray, llm_do
from playwright.sync_api import sync_playwright, Page, Browser, Playwright
import base64
import json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary instance variables for static config:

self.SCREENSHOTS_DIR = 'screenshots'
self.CHROMIUM_PROFILE_DIR = 'chromium_automation_profile'
self.DEFAULT_TIMEOUT = 30000

If only used once, just inline them. No need for 'configurability' that nobody will use.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed them now

Comment thread .co/config.toml Outdated
[cli]
version = "1.0.0"
command = "co init"
template = "none"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not commit personal agent address/email changes. These should stay as they were or be gitignored.

@wu-changxing

Copy link
Copy Markdown
Contributor

More Over-Engineering Issues Found

1. Delete entrypoint.py - vague name

entrypoint.py hidden in a subfolder is confusing. We already have agent.py at root.

If you want CLI functionality, either:

  • Keep it in agent.py (simplest)
  • Create cli.py at root (clear name)

Consider using typer + rich instead of argparse - much cleaner:

import typer
from rich import print

app = typer.Typer()

@app.command()
def run(prompt: str, headless: bool = False, deep_research: bool = False):
    print(f'[bold]Running:[/bold] {prompt}')
    # ...

if __name__ == '__main__':
    app()

2. --headless flag is parsed but NEVER USED

Dead code - either use it or remove it.

3. import os inside functions

Multiple files have import os scattered inside functions. Move to top of file.

4. os.getenv('BROWSER_AGENT_MODEL') can be None

Add fallback: os.getenv('BROWSER_AGENT_MODEL', 'co/gpt-4o')

Thambolo and others added 4 commits December 18, 2025 15:42
This commit introduces a robust, user-confirmed deep research workflow, enhancing the agent's ability to handle complex information gathering tasks.

Key features and improvements include:

- **Two-Track Task System:** The main agent now intelligently classifies user requests as either "simple tasks" or "deep research tasks."
- **User Confirmation for Deep Research:** For complex research queries, the agent proactively asks the user for explicit confirmation (Y/N) before proceeding, preventing unintended long-running operations.
- **Dedicated Deep Research Workflow:** A structured, step-by-step process for deep research has been implemented, covering:
    - Initial deletion of `research_results.md` to ensure a clean slate for each task.
    - Web search capabilities to find relevant sources (`get_search_results`).
    - Systematic exploration of web pages and extraction of relevant information (`explore`, `analyze_html`).
    - Recording of findings to `research_results.md` (`append_to_file`, `read_file`).
    - Synthesis of a comprehensive final report.
- **Proactive Popup Handling:** The `handle_popups()` tool was implemented and integrated into the workflow to automatically dismiss cookie banners and other intrusive popups, improving research reliability.
- **Flexible Browser Closure:** The `close()` tool was enhanced with a `keep_browser_open` option, allowing the agent to leave the browser open for user interaction on simple tasks and close it for research tasks.
- **New Core Tools:** The `WebAutomation` suite in `tools/web_automation.py` was extended with:
    - `analyze_html()`: LLM-driven HTML content analysis.
    - `explore()`: Combines navigation and HTML analysis.
    - `get_search_results()`: Heuristic Playwright-based search result extraction (note: this remains a challenging area due to target site dynamism).
    - `append_to_file()`: Appends content to a specified file.
    - `read_file()`: Reads content from a specified file.
    - `ask_user_confirmation()`: Prompts the user for Y/N input.
    - `delete_file()`: Safely deletes a file.
- **Prompt Refinements:** `prompts/browser_agent.md` and `prompts/deep_research.md` were thoroughly updated to guide the agent through this new, intelligent workflow.
- **Test Suite Enhancements:** Integration tests for `handle_popups()` were added and verified.
- **Gitignore Update:** `research_results.md` and `screenshots/` added to `.gitignore` to prevent tracking of temporary research artifacts.

This work significantly improves the agent's autonomy, user experience, and capability for complex tasks.
…plified architecture

Core Architecture:
- **Index-Based Element Finder**: Migrated from LLM-generated CSS selectors to a robust system where JS injects unique IDs into interactive elements. The LLM now SELECTS from an indexed list, ensuring 100% selector accuracy.
- **Iframe Awareness**: Updated extraction and click logic to traverse all iframes on a page. This allows the agent to 'see' and interact with content inside sub-documents (like cookie banners and login modals).
- **Deep Research Delegation**: Registered the 'DeepResearch' sub-agent as a tool for the main 'browser_agent'. This enables a Manager/Specialist workflow where complex research is delegated to a sub-agent while sharing the same browser session.

Tool & Prompt Improvements:
- **Simplified Toolset**: Removed the specialized 'handle_popups' tool. Updated 'agent.md' to instruct the agent to handle popups naturally using generic clicks.
- **Robust Search**: Refactored 'get_search_results' to use reliable DOM traversal for organic results, providing the agent with a high-quality menu of sources.
- **Full-Page Vision**: Removed character limits on 'analyze_html' and 'analyze_page' to allow the LLM to process entire documents for summarization.
- **Prompt Consolidation**: Replaced legacy prompts with a modernized set: 'agent.md', 'deep_research.md', 'element_matcher.md', and 'form_filler.md'.
- **Modular File Tools**: Extracted 'append_to_file', 'read_file', and 'delete_file' into a standalone 'FileTools' class, registering them exclusively with the deep research sub-agent to maintain separation of concerns.

Verification:
- Added 'tests/test_element_finder.py' for direct logic validation.
- Verified successful end-to-end Deep Research runs on competitive analysis tasks.
- Confirmed all automated tests in the project suite pass.
- Removed unnecessary try-except blocks to follow 'let it break' principles.
- Errors will now bubble up naturally, providing clearer debugging information.
- Maintained a specific guard for cross-origin frame offset calculation as it is a known browser constraint.
@tangkenzee tangkenzee self-assigned this Jan 5, 2026

@wu-changxing wu-changxing left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some files need be modified

Comment thread agent.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is only for define the agent, and for cli normally we need to create another file which is cli.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cli.py

Comment thread main.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep this one for now

Comment thread prompts/agent.md Outdated
Comment on lines +35 to +41
For complex questions that require reading multiple sources and synthesizing a detailed report (e.g., "What are the best marketing strategies for AI tools?"), use the **`perform_deep_research(topic)`** tool.
- This will spawn a specialized sub-agent to handle the deep exploration.
- **Pass the FULL user request** as the `topic` argument. Do not summarize it.
- ✅ Correct: `perform_deep_research("Find the history of the mouse and save it to mouse_history.txt")`
- ❌ Incorrect: `perform_deep_research("history of the mouse")`
- The sub-agent has tools to save files and format reports, so it needs the specific instructions.
- Use it when a task is too big for a single sequential browsing session.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may need some example of when to use the deep research

Comment thread prompts/deep_research.md

## Output Format

Your final response must be the **Comprehensive Research Report** itself. Do not say "I have finished research." Just provide the report.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lack of examples

Comment thread pytest.ini

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need create the test for init create profile and resue

Comment thread tools/file_tools.py Outdated
Comment on lines +7 to +50
class FileTools:
"""Tools for reading and writing files."""

@xray
def append_to_file(self, filepath: str, content: str) -> str:
"""
Appends content to a specified file.
Useful for logging research notes or findings.
"""
with open(filepath, "a", encoding="utf-8") as f:
f.write(content + "\n")
return f"Successfully appended to {filepath}"

@xray
def write_file(self, filepath: str, content: str) -> str:
"""
Writes (or overwrites) content to a specified file.
Useful for saving final reports.
"""
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
return f"Successfully wrote to {filepath}"

@xray
def read_file(self, filepath: str) -> str:
"""
Reads the entire content of a specified file.
Useful for reviewing notes.
"""
if not os.path.exists(filepath):
return f"File not found: {filepath}"
with open(filepath, "r", encoding="utf-8") as f:
return f.read()

@xray
def delete_file(self, filepath: str) -> str:
"""
Deletes a specified file.
Useful for cleaning up temporary notes.
"""
if os.path.exists(filepath):
os.remove(filepath)
return f"Successfully deleted file: {filepath}"
return f"File not found: {filepath}" No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name it for the function, reflect the features , Report class, then create report file append to report file etcs

Comment thread tools/scroll_strategies.py Outdated
Comment on lines +87 to +89
print(f" ❌ {strategy_name} failed: {e}")
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

over engineering

Comment thread tools/scroll_strategies.py Outdated
Comment on lines +203 to +207
def page_scroll_strategy(page, times: int):
"""Scroll the page window."""
for i in range(times):
page.evaluate("window.scrollBy(0, 1000)")
time.sleep(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

over engineering

Comment thread tools/scroll_strategies.py Outdated
Comment on lines +222 to +224
if not page:
return "Browser not open"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

over enginering

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use gmail or such kind of env to test this one and make the code simple and elegant

@wu-changxing wu-changxing left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread agent.py Outdated
Comment on lines +18 to +27
def create_agent(headless: bool = False) -> Tuple[Agent, WebAutomation]:
"""
Initialize and return the browser agent and web automation instance.

Args:
headless (bool): Whether to run the browser in headless mode.

# Create the agent with browser tools and streaming plugins
# image_result_formatter converts base64 screenshots to vision format for LLM to see
# ui_stream sends real-time activity events to connected UI clients via WebSocket
agent = Agent(
name="browser_agent",
model="co/gemini-3-flash-preview",
system_prompt=Path(__file__).parent / "prompts" / "agent.md",
tools=web,
plugins=[image_result_formatter, ui_stream], # Vision + real-time streaming
max_iterations=50 # Increased for scrolling through all emails
)
Returns:
Tuple[Agent, WebAutomation]: The configured agent and web instance.
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI

Comment thread tools/web_automation.py Outdated
Comment on lines +193 to +214
def submit_form(self) -> str:
"""Submit the current form."""
# Try common submit buttons
for selector in [
"button[type='submit']",
"input[type='submit']",
"button:has-text('Submit')",
"button:has-text('Send')",
"button:has-text('Continue')",
"button:has-text('Next')"
]:
if self.page.locator(selector).count() > 0:
self.page.click(selector)
return "Form submitted"

# Try pressing Enter in the last form field
if self.form_data:
last_field = list(self.form_data.keys())[-1]
self.page.press(f"input[name='{last_field}']", "Enter")
return "Form submitted with Enter key"

return "Could not find submit button"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_ele_by_descrition.

Comment thread tools/web_automation.py Outdated
Comment on lines +151 to +191
def find_forms(self) -> List[FormField]:
"""Find all form fields on the current page."""
fields_data = self.page.evaluate(
"""
() => {
const fields = [];
const inputs = document.querySelectorAll('input, textarea, select');

inputs.forEach(input => {
const label = input.labels?.[0]?.textContent ||
input.placeholder ||
input.name ||
input.id ||
'Unknown';

fields.push({
name: input.name || input.id || label,
label: label.trim(),
type: input.type || input.tagName.toLowerCase(),
value: input.value || '',
required: input.required || false,
options: input.tagName === 'SELECT' ?
Array.from(input.options).map(o => o.text) : []
});
});

return fields;
}
"""
)

return [FormField(**field) for field in fields_data]

def fill_form(self, data: Dict[str, str]) -> str:
"""Fill multiple form fields at once."""
results = []
for field_name, value in data.items():
result = self.type_text(field_name, value)
results.append(f"{field_name}: {result}")

return "\n".join(results)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Comment thread tools/web_automation.py Outdated
Comment on lines +17 to +26
# Simple, clear data models
class FormField(BaseModel):
"""A form field on a web page."""
name: str = Field(..., description="Field name or identifier")
label: str = Field(..., description="User-facing label")
type: str = Field(..., description="Input type (text, email, select, etc.)")
value: Optional[str] = Field(None, description="Current value")
required: bool = Field(False, description="Is this field required?")
options: List[str] = Field(default_factory=list, description="Available options for select/radio")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete

Comment thread tools/web_automation.py Outdated
Comment on lines +346 to +359
def smart_fill_form(self, user_info: str) -> str:
"""Generate smart form values and fill the form."""
fields = self.find_forms()
class FormData(BaseModel):
values: Dict[str, str]

field_descriptions = "\n".join([f"- {f.name}: {f.label}" for f in fields])
result = llm_do(
f"Generate form values based on: {user_info}\n\nFields:\n{field_descriptions}",
output=FormData,
model=self.DEFAULT_AI_MODEL,
temperature=0.7
)
return self.fill_form(result.values)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete

Comment thread tools/web_automation.py Outdated
Comment on lines +18 to +26
class FormField(BaseModel):
"""A form field on a web page."""
name: str = Field(..., description="Field name or identifier")
label: str = Field(..., description="User-facing label")
type: str = Field(..., description="Input type (text, email, select, etc.)")
value: Optional[str] = Field(None, description="Current value")
required: bool = Field(False, description="Is this field required?")
options: List[str] = Field(default_factory=list, description="Available options for select/radio")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete

Comment thread tools/web_automation.py Outdated
return f"User confirmed login to {site_name}"

@xray
def get_search_results(self, query: str, max_results: int = 10) -> List[Dict[str, str]]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

google

Comment thread tools/web_automation.py Outdated
Comment on lines +218 to +224
def scroll_page(self, direction: str = "down", amount: int = 1000) -> str:
"""Scroll the page up or down."""
return scroll_strategies.scroll_page(self.page, direction, amount)

def scroll_element(self, selector: str, amount: int = 1000) -> str:
"""Scroll a specific element."""
return scroll_strategies.scroll_element(self.page, selector, amount)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge

@tangkenzee tangkenzee merged commit ec1416d into main Jan 8, 2026
1 check passed
@tangkenzee tangkenzee deleted the deep-research-feature branch January 8, 2026 04:21
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.

3 participants