Agents Hub: main-thread orchestration and delegated execution
Status: proposed architecture; no application changes or deployment performed. Reviewed: 2026-11-15 UTC.
Decision
Run the main conversation on Robodev with a restricted orchestrator profile. Give it only task-management tools and structured communication with the person. Run research, integrations, terminal commands, file operations, SSH, browsing and coding in worker sub-tasks. Keep the execution transcript authoritative in Robodev. Agents Hub stores a small projection of the main conversation, interaction cards, task references and lifecycle metadata.
The runtime may register all available capabilities centrally, but it must expose and authorize them separately for each role. Do not hand every tool or credential to the main thread and rely on its instructions to delegate. Workers receive their own scoped capabilities from a broker; the main thread never receives the workers' bearer tokens, SSH credentials or vault contents.
What the deployed code does today
Inspection scope
Agents Hub was inspected inside production container mission-control-api-1, image ghcr.io/povio/mission-control:5b3b34e9547ec761deea9e791abe4777eabc2e39. Its backend TypeScript was recovered from the deployed JavaScript source maps. Robodev was inspected inside staging container robodev-agents-staging-api-1, image robodev-agents-staging/api:b8f253e. The generic /opt/robodev-agents-src directory is older, so findings below use the deployed container snapshot instead.
This review traced backend persistence, task handling, event contracts and harness configuration. The frontend source was not included in the deployed backend source snapshot; exact frontend component names and existing hover behavior still need repository inspection during implementation. No private conversation rows or credential values were needed for this review.
Current ownership and duplication
| Data | Current code path | Current storage / delivery |
|---|---|---|
| Execution prompts and results | Robodev Prisma Session / Turn |
Robodev PostgreSQL |
| Normalized execution events | Robodev recorder.ts, Prisma Event |
Robodev PostgreSQL, paginated history and SSE |
| Raw harness output | Robodev recorder, Prisma RawLine |
Robodev PostgreSQL |
| In-progress partial text | Robodev recorder partial() |
Live only; partial events themselves are not persisted |
| Main case messages | Hub WorkflowCaseAgentMessage |
Hub PostgreSQL; parsed reply, status, questions and connection requests |
| Tool-call summaries | Hub turn service toolCalls() / summariseToolCalls() |
Copied from Robodev into Hub toolCalls JSON; up to 50 calls with displayed input summaries |
| Run capture | Hub persistRun() → persistAiAgentRun() |
Hub WorkflowAiAgentRun.stdout/stderr/error; Robodev polling capture contains the last response body, including result/usage data, rather than necessarily the full event stream |
| Detailed session stream | Hub WorkflowCaseAgentStreamService / proxyRobodevStream() |
Authorized proxy to Robodev; payloads trimmed before forwarding |
| Coding task state / result | Hub WorkflowCodingTaskService |
Hub task metadata and encrypted child result; report() also inserts up to 30,000 characters of child output as a case event and queues a main reply |
| Personal-agent tool summaries | Hub AgentRuntimeService.rememberTools() |
Also copied to Hub Slack-message history; separate from case chat |
Answer to the storage question: it is mixed today. Robodev owns the full execution history, but Hub independently persists chat messages, tool summaries, run captures and coding-task results. Details can also be streamed from Robodev. The implementation is not currently “everything lives only on staging and is fetched only when Logs opens.” The code confirms these write paths; this review did not audit historical database contents or prove how often every frontend opens a stream.
Existing pieces to reuse
- Hub already authorizes a case before proxying its session events, and validates session membership. Extend this to worker sessions; never accept an arbitrary session id from the browser.
- Hub already has a session lifecycle SSE service separate from the detailed event proxy. Adapt it to a durable, case-level activity feed containing no execution payloads.
- Hub has coding-task creation, idempotency, leases, reconciliation, cancellation and completion reporting. It currently requires a project, advances one child per owner per reconciliation sweep, reconciles on a minute schedule, and only accepts continuation after a task settles. These constraints do not fit every general sub-task.
- Robodev already normalizes nested Codex sub-agent events with
parentItemIdand sub-agent metadata. That establishes visibility, not separate authorization of native tools. - MCP
allowedToolsis already supported. The bridge hides tools from listing and rejects disallowed calls; Codex receivesenabled_tools. However the harness uses full-access settings for native operations. An MCP allowlist alone cannot remove shell/file/browser capabilities. - Hub's self-MCP tokens already carry tool scopes and run identity. Preserve that server-side enforcement when adding role and task scopes.
Desired product behavior
The conversation contains user messages, main-thread assistant messages, questions, connection prompts and secret-request forms. Worker output, reasoning, raw errors, commands and tool calls never become ordinary chat bubbles automatically. A worker can request an interaction; the orchestrator or a trusted UI projection presents the structured form in the main conversation.
Show Agent is working while the orchestrator is generating, a worker is provisioning/running/cancelling, or a result is queued for the orchestrator to summarize. A main-thread turn finishing after delegation does not end this indicator. A task waiting solely for a person shows Waiting for your answer or Waiting for a secret. When other tasks are still running, retain the working indicator alongside the pending form. A disconnected status feed shows a reconnecting/stale state, rather than implying completion.
Reveal a small Logs action on assistant-row or activity-row hover. Also reveal it on keyboard focus and provide a tap/menu equivalent on touch devices. Hover only reveals the control; clicking opens a drawer and starts the details request. No trace prefetch on hover or page open. Closing the drawer aborts its event stream. Keep questions and secret forms available in normal chat, independent of the drawer.
The drawer presents the task tree, tool calls, commands, outputs and diagnostic events, grouped by worker and turn. It can include orchestrator task-management events, which are still logs even though they are hidden from the normal conversation. Logs must not expose secret values or promise private model reasoning that the provider does not supply.
Example: “Which VPSes can you reach?”
- The user message is accepted with a stable id and submitted to the Robodev orchestrator.
- The main thread says “I’m checking access to the configured VPSes.” It starts a verification sub-task using a stable idempotency key. The UI shows Agent is working.
- The broker starts a worker with SSH and terminal capabilities. The worker reads the configured hosts and performs the authorized checks. Commands and outputs stay in the worker history on Robodev.
- The worker returns a bounded structured summary and evidence references. Completion durably queues one parent continuation.
- The main thread reports the verified hosts and any failures in a normal message. The working indicator clears when all associated activity settles.
- If the person clicks Logs, Hub authorizes the request and fetches the worker's details from Robodev. Nothing in step 6 is required for step 5 to work.
Tool and credential boundaries
These are proposed interfaces, not existing API names.
| Capability | Main orchestrator | Worker |
|---|---|---|
| Start/list/inspect task status | Yes, own case only | Own descendants only if explicitly enabled |
| Send task instruction / cancel task | Yes, own tasks | No sibling or parent authority |
| Read task result | Bounded structured summary / artifact references | Own full result |
| Public message / question / secret-request reference | Structured output or narrow communication tool | Request routed to main UI; no raw worker bubble |
| Terminal, files, SSH, browser, arbitrary HTTP | No | As authorized for the task |
| Business MCP tools | No | Intersection of owner grants, run policy and task scope |
| Vault secrets and integration tokens | No | Broker injects only required credentials |
| Raw log API | User-facing authenticated proxy; not a main-agent tool | Own execution context as needed |
Suggested management surface: start_task, list_tasks, get_task_status, get_task_result, send_task_message, cancel_task. Responses must exclude raw event pages. Public messages/questions can continue using the existing structured reply contract; no extra tool is necessary just to speak.
The broker binds owner/team, case, parent session, child task/session, role, permission ceiling and expiry to each capability token. A child result never grants new authorization. Disabled tools and workflow approval policies remain effective. Moving execution into a worker must not turn a review-required write into an automatic write. Payments remain disabled.
Reject both disallowed discovery and invocation. Reject role changes and grant widening on session resume. Distinguish an omitted allowlist from an explicitly empty allowlist: current harness checks such as allowedTools?.length must not interpret “no tools” as “all tools.” Do not mount the user's shared workspace, SSH agent or credential files into the orchestrator environment.
Runtime implementation choice
Recommended first implementation: retain the existing coding-capable runtime for workers and run a separate minimal orchestrator executor on Robodev. It can use a model tool loop that exposes only the task broker and structured reply schema. It needs no terminal, user filesystem or browser. This makes the main/worker boundary enforceable without depending on an instruction or an unverified CLI tool-filter feature.
A native Codex parent/child implementation is an optimization only after proving the deployed harness can enforce different native-tool sets for parent and child. The current nested-event mapper does not prove that. If a supported harness can meet the isolation contract, use it behind the same task API. Otherwise keep the minimal orchestrator plus separate worker sessions. Do not silently fall back to a full-access main session.
The server may know all MCP definitions and resolve them lazily for each child. Avoid putting the entire catalogue in the main prompt. Resolve relevant capabilities from a task type/description and mint child-scoped relay tokens. No need to proxy terminal/file operations through the main model: execute them in the worker sandbox and record them in its log.
Data ownership after the change
| Concern | Authoritative owner | What Hub retains |
|---|---|---|
| Accepted main-thread conversation and ordering | Robodev durable conversation/event feed | Main-thread projection for display, unread state and search |
| Tool events, command output, raw harness logs | Robodev | Session/turn/task references and cursors only |
| Full worker results and detailed artifacts | Robodev / its artifact storage | Artifact references; no full child transcript |
| Task execution state | Robodev task service | Mirrored status, ids, timestamps and error codes needed for UI |
| Questions and secret requests | Durable interaction records with Robodev execution linkage | Public question/form metadata and request id; never submitted secret values |
| Workflow definitions, projects, application approval records | Hub | Existing business/application records |
| Usage totals and latency metrics | Robodev execution records | Optional aggregate numbers; no stdout as a usage database |
Keeping main messages in Hub is intentional, as requested. They are a rebuildable projection, not a second competing execution transcript. Workers may need summaries fetched automatically for the parent to answer; “details only on request” applies to full execution logs, not task lifecycle or bounded results required to complete the work.
Use a transactional submission/outbox protocol: Hub can retain an undelivered user message marked pending, submit it with a stable message id, and mark it accepted only after Robodev acknowledges it. A retry must not create another turn. Record public assistant output and structured interactions durably on Robodev before acknowledging projection delivery. Hub projects with (conversationId, eventId) uniqueness and an ordered cursor. User-visible state can be replayed after reconnect or a Hub restart.
Proposed Robodev records: conversation, public message, task, task outcome, interaction request and delivery/outbox event. Tasks link parentTaskId, orchestrator session/turn, child session/turn and capability profile. Hub can retain a minimal task-link row; avoid retaining task instructions and full results after the handoff if the canonical task service already has them.
For existing normalized execution events, preserve sessionId, turnId, itemId, parentItemId and seq. Version any added envelope fields according to Robodev AGENTS.md. Keep raw logs separate from the public conversation projection.
Lifecycle and delivery
Proposed task states: queued, provisioning, running, waiting_for_user, waiting_for_secret, cancelling, completed, failed, cancelled. Parent continuations have their own queued/running/settled state; a completed worker can therefore still leave the case working while the final answer is being prepared.
Use signed lifecycle callbacks or an authenticated durable subscription from Robodev to Hub. Deliver small state transitions, public messages and interaction references, not raw traces. A durable outbox plus idempotent consumers is preferable to depending on process-local live-session maps. A low-frequency metadata-only reconciliation loop recovers missed notifications. Do not poll full event histories to update a spinner or task summary.
On completion, store the result first, then enqueue the parent wake-up atomically. Deduplicate with task id plus result/turn version. Serialize parent turns; coalesce completions received while it is busy. Resumed tasks create another result version. Store internal completion references outside the public message table; do not inject the entire child result into a visible event. The main thread receives a bounded result through context or get_task_result.
Handle active-task steering explicitly. The existing coding-task continuation refuses active turns: the new API must distinguish queued instructions, steer, interrupt and settled-task continuation, with clear acknowledgement and cancellation races. An uncertain external write is reconciled instead of blindly retried.
Bound task concurrency, nesting depth, runtime and result size. Reuse separate task workspaces; coding uses isolated Git worktrees and serialized Git metadata operations. Cancellation revokes task capabilities and stops its process/session without deleting canonical history. Do not let a late completion resurrect a cancelled task.
API and frontend changes
Proposed routes are illustrative contracts to implement, not claims about existing endpoints:
- Conversation messages/status endpoint: only public messages, structured interactions, activity state and references.
- Case activity SSE: compact status transitions and accepted public output, with resumable event ids.
GET /cases/:caseId/tasks/:taskId/events?after=...&limit=...: authorization against the case and stored task link, then paginated Robodev proxy.- Equivalent SSE route for an open Logs drawer, bound to the selected task and turn; abort upstream when the user closes it.
The proxy holds service credentials server-side. Apply user/case/team access checks, task membership, rate limits, size bounds and redaction. Use private/no-store cache policy for detail responses and avoid response bodies in application/access logs. Permission revocation must also terminate or revalidate long-lived streams. Browser memory may cache an open drawer briefly; do not persist traces in localStorage or IndexedDB.
Normal conversation serializers must stop returning tool summaries, run stdout/stderr and child completion dumps. Normal chat must not subscribe to the raw event proxy. The Logs drawer may reuse the existing normalized event renderer. Status and secret interactions must continue working while the drawer is closed.
Implementation work by area
| Area | Starting points in inspected code | Required change |
|---|---|---|
| Hub conversation execution | src/modules/workflows/agent-chat/workflow-case-agent-turn.service.ts |
Select orchestrator profile, receive public projection, remove post-turn tool-history scrape and copied summaries |
| Hub case persistence | workflow-case-agent.store.ts, agent-chat/persistors/prisma/workflow-case-agent.prisma-repository.ts, WorkflowCaseAgentMessage |
Store public messages/interactions and execution references; replace embedded child-result events |
| Hub run history | src/modules/workflows/workflow.preparation.service.ts, WorkflowAiAgentRun |
Keep execution references and usage aggregates; stop storing new trace/response captures for migrated sessions |
| Hub worker foundation | src/modules/workflows/runtime/workflow-coding-task.service.ts, .mcp.ts, .request.ts |
General task API, optional project, bounded results, worker policy, lifecycle wake-ups, active steering; migrate execution authority to Robodev |
| Hub self-MCP / integrations | src/modules/workflows/agent/workflow-agent-self-mcp.ts, workflow-agent-mcp-setup.ts, custom MCP relay |
Orchestrator-only tool scope; child-specific tokens, run context and existing approval semantics |
| Hub logs and status | workflow-case-agent-stream.service.ts, workflow-agent-session-stream.service.ts, workflow-robodev-stream.ts |
Separate compact activity delivery from on-demand details; include authorized worker sessions |
| Hub personal-agent parity | src/modules/agents/agent-runtime.service.ts |
Remove rememberTools() copying when applying this design outside case chat |
| Hub frontend | Locate in source repository during implementation | Clean chat renderer, working/waiting states, hover/focus/tap Logs control, lazy drawer, abort behavior |
| Robodev tasks and policy | apps/api/src/routes.ts, runtime.ts, harness.ts, codex-app-server.ts, mcp-bridge.mjs |
Durable tasks, enforced roles, minimal orchestrator executor, capability broker, parent wake-up and lifecycle API |
| Robodev storage/contracts | apps/api/prisma/schema.prisma, apps/api/src/recorder.ts, packages/events/src/envelope.ts |
Canonical public conversation and task records; retained execution history and versioned event changes |
Hub paths in this table are original TypeScript paths recovered from deployed source maps. Existing generated .resolved.prisma files are evidence, not the files to edit. Locate their original schema fragments and create migrations in the actual repository.
Migration and rollout
- Add versioned task/lifecycle/public-message contracts and metadata references. Establish canonical retention and recovery before removing any Hub log copies.
- Add compact activity delivery and the lazy Logs drawer behind a feature flag. Verify ordinary chat performs zero detailed-event requests.
- Deploy the capability broker and minimal orchestrator executor. Prove direct execution is denied before routing real conversations through it.
- Generalize coding tasks, enable no-project tasks and preserve workflow/run authorization in worker tokens. Switch one-off task cases first; migrate scheduled/workflow cases separately without changing their approval policy.
- Stop new tool-summary copying, full child-result chat events and migrated run captures. Switch existing history readers to execution references.
- Backfill session/turn/task links. Existing
WorkflowAiAgentRunrows do not have an explicit session field in the inspected schema; recover references from captures only where verifiable. Quarantine unmappable legacy rows rather than inventing ids or deleting the only copy. - Verify Robodev history is readable after sandbox stop/termination and after restoration from backups. Robodev already distinguishes ending execution from deleting history; validate retention and explicit deletion behavior for all referenced sessions/tasks.
- Remove duplicate legacy payloads only after reference coverage and retention are verified. Keep this as a separate, deliberate data migration. Backups follow their own retention schedule.
Rollback restores the prior routing behind the flag while retaining canonical Robodev logs and identifiers. Do not restore broad tools to a session labelled restricted as a silent fallback. A runtime incompatibility must fail closed or use the minimal orchestrator.
The staging service would become a production dependency for Hub's chat execution and log history. Give it production-grade backup, monitoring, retention and recovery guarantees before relying on it as the sole execution store. “Single source” means one authoritative logical store; replicas and backups are still required.
Acceptance criteria and validation
- VPS-check end-to-end test: main acknowledges, child performs SSH, main reports evidence-based results; normal chat has no commands or worker bubbles.
- Capability tests: main cannot list or invoke terminal, file, browser, business MCP or generic HTTP tools; calling a forbidden tool directly is rejected. Test empty allowlist, resume and token replay boundaries.
- Worker grants: a worker receives exactly the task's authorized tools and run identity. Review-required writes remain held, secret values are absent from messages/logs, and revoked tokens fail.
- Network test: opening chat and hovering Logs cause zero raw-event/history calls. Clicking fetches authorized details; closing aborts the stream. Reopen resumes from a cursor without duplicated entries.
- UI tests: working spans provisioning, child execution and queued parent summary; waiting forms survive reconnect; one waiting child does not mask another running child; keyboard and touch users can open Logs.
- Durability tests: restart Hub, restart the task service, duplicate/reorder callbacks, lose a callback and finish multiple children at once. There is one task per start key and one parent continuation per completion version.
- Cancellation/steering tests: no late resurrection, no replay of an uncertain write, predictable active-task instructions and settled continuation.
- Persistence test: new Hub records contain public main-thread content and metadata but no tool-input summaries, trace stdout/stderr or full child transcript/result dumps. Logs remain available from Robodev after worker sandbox shutdown.
- Access tests: unrelated user, team, case, task and historical-session ids are denied, including on SSE reconnect.
- Run repository-required checks when implementing; Robodev requires
bun testandbun run typecheck, contract versioning and real sanitized fixtures for changed harness behavior.
This deliverable was validated against deployed backend code and a rendered Povio diagram. It changes documentation and creates a diagram only; application tests and deployment are outside this proposal's completed work.
Remaining implementation decisions
The architecture does not need a product decision before it can be implemented. Start with the minimal orchestrator executor and separate general worker sessions. During implementation, confirm the frontend repository layout, supported harness restrictions, retention duration, callback transport and operational SLOs. If native child threads can later enforce the same capability boundary, they can replace worker-session provisioning behind the task API without changing the chat or storage model.
