Skip to content

refactor: establish Brain-centered architecture and frontend/backend separation foundations#1536

Open
4pmtong wants to merge 6 commits intomainfrom
refactor_eigent
Open

refactor: establish Brain-centered architecture and frontend/backend separation foundations#1536
4pmtong wants to merge 6 commits intomainfrom
refactor_eigent

Conversation

@4pmtong
Copy link
Copy Markdown
Collaborator

@4pmtong 4pmtong commented Apr 2, 2026

Related Issue

Closes #

Description

This PR delivers the current Brain + Web separation milestone.

It continues the ongoing refactor toward a Brain-centered architecture, where runtime capabilities are determined by the Brain environment rather than by client type. The goal of this phase is to preserve the existing Desktop experience while making Web + Local Brain a supported path built on shared, Brain-facing abstractions instead of Electron-specific assumptions.

Within that scope, this PR has three primary outcomes:

  • it strengthens the Brain-side architecture around routing, session handling, Hands resolution, capability detection, and resource abstraction
  • it advances frontend separation so shared web/desktop flows use Host and Brain-facing paths instead of depending directly on Electron-only globals
  • it folds several high-impact regressions discovered during the refactor back into the new architecture, so the milestone improves behavior without adding more legacy branching

Concretely, this PR moves the codebase further toward the target architecture described in the refactor work:

  • Brain capabilities are determined by deployment environment, not by client channel
  • shared frontend code should not depend directly on Electron-only globals
  • browser, file, and session flows should be routed through Brain / Hands abstractions
  • Desktop remains backward compatible while Web gains first-class support

Notable user-facing improvements included in this PR:

  • web login now returns users to the web app instead of redirecting into an Electron-only flow
  • Browser > CDP Browser Connection now works in dev:web by using Brain APIs in web mode
  • browser connection handling is normalized around endpoint-based state, keeping Local Brain as the default behavior today while preparing the path for future Hands-based browser resource expansion
  • task execution now reuses an existing connected CDP endpoint instead of resetting browser state unnecessarily
  • Agent Folder rendering now handles duplicate filenames correctly when multiple tasks generate files with the same name

This PR should be reviewed as a refactor milestone rather than as a collection of isolated bug fixes. The fixes included here are intentionally integrated into the Brain/Web separation work so the resulting behavior stays aligned with the target architecture.

Out of scope for this PR:

  • making RemoteHands the default runtime path
  • changing the default Local Brain deployment model
  • completing future cluster / worker orchestration beyond the extension points needed for this refactor phase

Testing Evidence (REQUIRED)

  • I have included human-verified testing evidence in this PR.
  • This PR includes frontend/UI changes, and I attached screenshot(s) or screen recording(s).
  • No frontend/UI changes in this PR.

What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Contribution Guidelines Acknowledgement

@4pmtong 4pmtong marked this pull request as draft April 2, 2026 15:34
Comment thread backend/app/controller/skill_controller.py Fixed
Comment thread backend/app/controller/tool_controller.py Fixed
Comment thread src/components/Folder/index.tsx Fixed
@4pmtong 4pmtong marked this pull request as ready for review April 5, 2026 03:33
@4pmtong 4pmtong changed the title WIP: refactor refactor: land the Brain + Web separation milestone Apr 5, 2026
@4pmtong 4pmtong changed the title refactor: land the Brain + Web separation milestone refactor: advance Brain-centered architecture and Web + Local Brain support Apr 5, 2026
@4pmtong 4pmtong changed the title refactor: advance Brain-centered architecture and Web + Local Brain support refactor: establish Brain-centered architecture and frontend/backend separation foundations Apr 5, 2026
@a7m-1st a7m-1st self-requested a review April 9, 2026 01:41
Copy link
Copy Markdown
Collaborator

@bytecii bytecii left a comment

Choose a reason for hiding this comment

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

In general the design is ok. But IMO we should refactor and put things to different PRs, otherwise it's hard to review.

has_browser: bool = False
"""browser hand: can control CDP browser"""

filesystem_scope: str = "full"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe make full etc as the strenum types?

"""workspace root path"""

deployment_type: str = "local"
"""deployment type (for logging): local | cloud_vm | sandbox | docker"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what's the cloud vm? just curious

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think she meant a full virtual machine remotely; not just in docker.

if in_docker:
logger.info("Brain running in Docker, using limited capabilities")
deployment = "docker"
caps = BrainCapabilities(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

QQ should this be the HandCapabilities instead? for example browser is hand's capability?

from app.hands.interface import IHands

# Terminal command allowlist for sandbox
SANDBOX_TERMINAL_ALLOWLIST = frozenset(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Seems that this is unused?

@abstractmethod
async def acquire(
self,
resource_type: str,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Make resource type to string enum?

from app.hands.interface import IHands


class RemoteHands(IHands):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe we can put all Hands to a hands subfolder and import it to the hands/init.py



class IHardwareBridge(ABC):
"""Hardware capability bridge. Only Desktop has implementation, others use NullBridge"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So should we implement this for the desktop?

from app.hands.capabilities import detect_capabilities
from app.hands.environment_hands import EnvironmentHands

logger = logging.getLogger("hands.resolver")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's use the following

Suggested change
logger = logging.getLogger("hands.resolver")
logger = logging.getLogger(__name__)

# TODO(multi-tenant): os.environ is global – concurrent sessions overwrite
# each other's API keys, file paths, and browser ports. Pass these values
# through Chat / request context instead of mutating the process environment.
os.environ["file_save_path"] = data.file_save_path()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

to my understanding as the brain in the server, will it has some problems if we set env as previously just in desktop?

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.

Yes, It need redesign if we deploy the brain in the server, can use S3 instead of it.

Comment on lines +97 to +113
if hands is not None:
if (
item in TERMINAL_DEPENDENT_TOOLKITS
and not hands.can_execute_terminal()
):
logger.info(
f"Skipping {item} for {agent_name}: no terminal hand"
)
continue
if (
item in BROWSER_DEPENDENT_TOOLKITS
and not hands.can_use_browser()
):
logger.info(
f"Skipping {item} for {agent_name}: no browser hand"
)
continue
Copy link
Copy Markdown
Collaborator

@a7m-1st a7m-1st Apr 14, 2026

Choose a reason for hiding this comment

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

I think we can simplify this?

Comment on lines +117 to +118
workspace_root: str = "~/.eigent/workspace"
"""workspace root path"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we convert this to PATH type instead? Resolving the path might cause issues down the line.

Comment on lines +187 to +195
@router.get("/files")
async def list_project_files(
project_id: str = Query(..., description="Project ID"),
email: str = Query(..., description="User email"),
task_id: str | None = Query(
None, description="Optional task ID to scope listing"
),
) -> list[dict]:
"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not sure if its me; my task was to write "hello world" file. The file is generated, but the route returned
/files/stream?path=task_1776131785382-755%5Chello_world.txt&project_id=1776121925475-837&email=ahmed%40gmail%20com

  • Thus if you try to click the file title in Eigent, it doesn't open the file. The file rendering is not there too.
  • Lastly just a small comment, previously we were fetching "All project files", thus it appeared having flat hierarchy. But now they are nested ; just a small comment.
Image

self.workspace_root = Path(workspace_root).expanduser()

def read_file(self, path: str) -> str:
resolved = self._ensure_in_workspace(path)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Actually, a neat idea (if we don't mind the bandwidth) is to just send base64 strings as attachments. Previously the /chat sends attaches[] with local file paths. Now it is:

1. upload file separately                                                   
2. get file_id                                                              
3. send the message with file references

We can instead attach a base64 string, then decode it back then save it in whatever the backend is hosted (vm, docker or others) or upload it in the background. This way we don't need to have separate steps; for <10mb resources.
ref: https://github.com/a7m-1st/medigent/blob/main/backend/app/utils/file_utils.py

Comment on lines +43 to +45
def list_dir(self, path: str) -> list[str]:
return [p.name for p in Path(path).iterdir()]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rereading from disk (recursively) using python is expensive. Especially while SSE running, the python thread gets blocked thus waiting for some time before response. We mitigated this issue by migrating to websockets: ref: https://github.com/a7m-1st/medigent/blob/main/backend/app/controller/session_controller.py

Also Consider caching the get all files response unless there is a change.
ref: https://github.com/a7m-1st/medigent/blob/c32f3cf8cad1daa4fccb80085a9668286628f2f8/backend/app/controller/chat_controller.py#L453

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For the first issue; there should be a better solution.. websockets just supressed the problem it didn't fix it.

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.

4 participants