molecule-core/workspace
Hongming Wang 5950d4cd81 feat(delegations): agent-side cutover — sync delegate uses async+poll path (RFC #2829 PR-5)
Behind feature flag DELEGATION_SYNC_VIA_INBOX (default off). When set,
tool_delegate_task no longer holds an HTTP message/send connection
through the platform proxy waiting for the callee's reply. Instead:

  1. POST /workspaces/<src>/delegate (returns 202 + delegation_id)
     — platform's executeDelegation goroutine handles A2A dispatch
     in the background. No client-side timeout dependency on the
     platform holding a connection open.
  2. Poll GET /workspaces/<src>/delegations every 3s for a row with
     matching delegation_id reaching terminal status (completed/failed).
  3. Return the response_preview text on completed; surface the
     wrapped _A2A_ERROR_PREFIX error on failed (so caller error
     detection stays unchanged).

This closes the bug class that broke Hongming's home hermes on
2026-05-05 ("message/send queued but result not available after 600s
timeout" while the callee was actively heartbeating "iteration 14/90").

## Compatibility

Default-off feature flag — flag-off path is byte-identical to the
legacy send_a2a_message behavior, pinned by
TestFlagOffLegacyPath::test_flag_off_uses_send_a2a_message_not_polling.

Idempotency-key derivation matches tool_delegate_task_async (SHA-256
of source:target:task) so a restart-mid-delegation gets the same key
and the platform returns the existing delegation_id.

## Recovery on timeout

If the polling budget (DELEGATION_TIMEOUT, default 300s) elapses
without a terminal status, the error message includes the
delegation_id + a "call check_task_status('<id>') to retrieve later"
hint. The platform's durable row is still live — work is NOT lost,
just the synchronous wait is over. Caller can poll for the result
later via the existing check_task_status tool.

## Stack with PR-2

PR-2 added the SERVER-SIDE result-push to the caller's a2a_receive
inbox row. PR-5 (this PR) adds the AGENT-SIDE cutover. Together they
remove the proxy-blocked sync path entirely. PR-2 default-off keeps
existing behavior; PR-5 default-off keeps existing behavior. Operators
flip both for full effect after staging burn-in.

## Coverage

9 unit tests:

  - flag off → byte-identical to legacy (send_a2a_message called,
    _delegate_sync_via_polling NOT called)
  - dispatch HTTP exception → wrapped error
  - dispatch non-2xx → wrapped error mentioning HTTP code
  - dispatch missing delegation_id → wrapped error
  - completed first poll → response_preview returned
  - failed status → wrapped error with error_detail
  - transient poll error → keeps polling, eventually succeeds
  - deadline exceeded → wrapped timeout error mentions delegation_id +
    check_task_status hint for recovery
  - filters by delegation_id (other delegations' rows ignored)

All passing locally. CI will run the same suite on a clean env.

Refs RFC #2829.
2026-05-04 21:31:11 -07:00
..
adapters
builtin_tools
lib
molecule_audit
platform_tools feat(mcp): multi-workspace routing for memory + chat_history + workspace_info 2026-05-04 14:17:58 -07:00
plugins_registry
policies
scripts
skill_loader
tests feat(delegations): agent-side cutover — sync delegate uses async+poll path (RFC #2829 PR-5) 2026-05-04 21:31:11 -07:00
.coveragerc
a2a_cli.py
a2a_client.py feat(mcp): multi-workspace routing for memory + chat_history + workspace_info 2026-05-04 14:17:58 -07:00
a2a_executor.py fix(a2a): route terminal Message via TaskUpdater.complete/failed in task mode 2026-05-03 04:06:45 -07:00
a2a_mcp_server.py fix(mcp): wire source_workspace_id through dispatcher for memory + chat_history + workspace_info 2026-05-04 14:41:24 -07:00
a2a_tools.py feat(delegations): agent-side cutover — sync delegate uses async+poll path (RFC #2829 PR-5) 2026-05-04 21:31:11 -07:00
adapter_base.py feat: drop shared_context — use memory v2 team namespace instead 2026-05-04 16:30:26 -07:00
agent.py
agents_md.py
boot_routes.py test(runtime): pin PR #2756's card-vs-setup decoupling with build_routes helper 2026-05-04 14:59:56 -07:00
build-all.sh
card_helpers.py fix(runtime): isolate card-skill enrichment + transcript handler from adapter shape mismatch 2026-05-04 14:15:27 -07:00
config.py feat: drop shared_context — use memory v2 team namespace instead 2026-05-04 16:30:26 -07:00
configs_dir.py fix(runtime): auto-fallback CONFIGS_DIR for non-container hosts (closes #2458) 2026-05-01 13:07:55 -07:00
consolidation.py
coordinator.py feat: drop shared_context — use memory v2 team namespace instead 2026-05-04 16:30:26 -07:00
Dockerfile
entrypoint.sh
event_log.py feat(workspace): event_log module + EventLogConfig (#119 PR-2) 2026-05-03 00:17:12 -07:00
events.py
executor_helpers.py docs(a2a): correct misleading v1-tolerance comments 2026-05-02 02:33:00 -07:00
heartbeat.py feat(workspace): wire observability config into heartbeat + uvicorn (#119 PR-3a) 2026-05-03 01:01:57 -07:00
inbox.py mcp: support multi-workspace external-agent registration (PR-1) 2026-05-04 08:06:00 -07:00
initial_prompt.py
internal_chat_uploads.py fix(workspace): surface errno + path on chat-upload mkdir failure 2026-05-01 11:47:53 -07:00
internal_file_read.py
main.py test(runtime): pin PR #2756's card-vs-setup decoupling with build_routes helper 2026-05-04 14:59:56 -07:00
mcp_cli.py fix: bot-lint nits — drop unused imports, add reason to except 2026-05-04 08:16:12 -07:00
molecule_ai_status.py
not_configured_handler.py fix(runtime): redact secret-shaped tokens from JSON-RPC error.data 2026-05-04 15:07:53 -07:00
platform_auth.py feat(mcp): cross-workspace delegation routing (multi-ws PR-2) 2026-05-04 08:32:24 -07:00
platform_inbound_auth.py fix(runtime): auto-fallback CONFIGS_DIR for non-container hosts (closes #2458) 2026-05-01 13:07:55 -07:00
plugins.py
preflight.py fix(preflight): downgrade required_env + auth_token failures to warnings 2026-05-04 12:20:34 -07:00
prompt.py feat: drop shared_context — use memory v2 team namespace instead 2026-05-04 16:30:26 -07:00
pytest.ini
rebuild-runtime-images.sh
requirements.txt chore(deps)(deps): update starlette requirement in /workspace 2026-05-03 01:36:45 +00:00
runtime_wedge.py
secret_redactor.py fix(runtime): redact secret-shaped tokens from JSON-RPC error.data 2026-05-04 15:07:53 -07:00
shared_runtime.py
smoke_mode.py chore(smoke): runtime_wedge follow-ups from PR #2473 review 2026-05-01 18:01:51 -07:00
transcript_auth.py
watcher.py