feat(deploy): per-agent base_url and workspace_path routing

- AgentDefinition gains base_url and workspace_path fields (optional)
- load_agent_registry parses them from matrix-agents.yaml
- _build_platform_from_env uses agent.base_url per agent (falls back to AGENT_BASE_URL)
- _agent_workspace_root() resolves workspace per agent from registry
- _materialize_incoming_attachments saves files to agent workspace_path/incoming/
- send_outgoing accepts workspace_root param; reads outgoing files from agent workspace_path
- dispatch loop computes workspace_root from room agent_id and passes to _send_all
- config/matrix-agents.yaml and example updated with base_url and workspace_path
This commit is contained in:
Mikhail Putilovskij 2026-04-28 03:22:21 +03:00
parent d6b7720eca
commit 4bbae9affa
5 changed files with 108 additions and 21 deletions

View file

@ -146,10 +146,11 @@ def _build_platform_from_env(*, store: StateStore, chat_mgr: ChatManager) -> Pla
prototype_state = PrototypeStateStore()
registry = _load_agent_registry_from_env(required=True)
assert registry is not None
global_base_url = _agent_base_url_from_env()
delegates = {
agent.agent_id: RealPlatformClient(
agent_id=agent.agent_id,
agent_base_url=_agent_base_url_from_env(),
agent_base_url=agent.base_url or global_base_url,
prototype_state=prototype_state,
platform="matrix",
)
@ -300,6 +301,8 @@ class MatrixBot:
sender,
incoming,
)
agent_id = (room_meta or {}).get("agent_id")
workspace_root = self._agent_workspace_root(agent_id)
try:
outgoing = await self.runtime.dispatcher.dispatch(incoming)
except PlatformError as exc:
@ -319,7 +322,7 @@ class MatrixBot:
else:
if clear_staged_after_dispatch:
await clear_staged_attachments(self.runtime.store, room.room_id, sender)
await self._send_all(room.room_id, outgoing)
await self._send_all(room.room_id, outgoing, workspace_root=workspace_root)
def _is_file_only_event(
self, event: RoomMessage, incoming: IncomingMessage | IncomingCommand
@ -439,13 +442,27 @@ class MatrixBot:
True,
)
def _agent_workspace_root(self, agent_id: str | None) -> Path:
default = Path(os.environ.get("SURFACES_WORKSPACE_DIR", "/workspace"))
if agent_id is None or self.runtime.registry is None:
return default
try:
agent = self.runtime.registry.get(agent_id)
if agent.workspace_path:
return Path(agent.workspace_path)
except Exception:
pass
return default
async def _materialize_incoming_attachments(
self,
room_id: str,
matrix_user_id: str,
incoming: IncomingMessage,
) -> IncomingMessage:
workspace_root = Path(os.environ.get("SURFACES_WORKSPACE_DIR", "/workspace"))
room_meta = await get_room_meta(self.runtime.store, room_id)
agent_id = (room_meta or {}).get("agent_id")
workspace_root = self._agent_workspace_root(agent_id)
materialized = []
for attachment in incoming.attachments:
materialized.append(
@ -596,9 +613,20 @@ class MatrixBot:
self.runtime.registry,
)
async def _send_all(self, room_id: str, outgoing: list[OutgoingEvent]) -> None:
async def _send_all(
self,
room_id: str,
outgoing: list[OutgoingEvent],
workspace_root: Path | None = None,
) -> None:
for event in outgoing:
await send_outgoing(self.client, room_id, event, store=self.runtime.store)
await send_outgoing(
self.client,
room_id,
event,
store=self.runtime.store,
workspace_root=workspace_root,
)
async def prepare_live_sync(client: AsyncClient) -> str | None:
@ -613,6 +641,7 @@ async def send_outgoing(
room_id: str,
event: OutgoingEvent,
store: StateStore | None = None,
workspace_root: Path | None = None,
) -> None:
if isinstance(event, OutgoingTyping):
await client.room_typing(room_id, event.is_typing, timeout=25000)
@ -627,7 +656,9 @@ async def send_outgoing(
room_id, "m.room.message", {"msgtype": "m.text", "body": event.text}
)
if event.attachments:
workspace_root = Path(os.environ.get("SURFACES_WORKSPACE_DIR", "/workspace"))
workspace_root = workspace_root or Path(
os.environ.get("SURFACES_WORKSPACE_DIR", "/workspace")
)
for attachment in event.attachments:
if not attachment.workspace_path:
continue