Skip to content

feat(agent-org): [8/10] enable direct Member UserDirectedWork and Return to Work #763

Description

@ShiboSheng

Effort

Delivery boundary P50 P90 Substantive files
Direct UserDirectedWork, user_intervention, and Return to Work 2,500 review lines 4,300 review lines 18–29

Delivery contract

Dependency and single goal

Strictly stack on PR 7 and deliver only the direct source from the Member page. Reuse lifecycle, flat snapshot, Turn context/FIFO, Task gates, lease handoff, Archive fence, and Writer resolution. Leave Group mention and peer Inbox to PR 9.

State after merge

With the rollout gate enabled, the user can use a Member's direct page in Working/Idle/Paused to ask questions, edit files, run commands/tests, and use approved tools. A normal Member cannot change the formal graph; an additional Writer may use PR 7's graph-admin Tool. When the Member is busy with TaskExecution, a direct message creates visible user_intervention, yields the old execution at a safe point, and runs UserDirectedWork in the same runtime slot without opening a second lane. Intervention does not disappear because of TTL, page close, Stop, or restart. Only the user's Return to Work consumes it.

develop paths that must be replaced

state/commands/session/message/send.rs currently auto-resumes a Paused Team and injects busy-Member direct input as TaskExecution steering. agent_member_interventions.rs currently has TTL/startup cleanup. org_tasks/intervention.rs currently polls every 500 ms for up to 90 seconds and blanket-drains Inbox on Return. The independent agent_org_send_user_message_to_member/enter-intervention command is still a second send path. Replace these semantics on the Agent Org branch while preserving ordinary SDE steering, Queue, and Stop.

Implementation steps and authoritative entry points

  1. Freeze the production send chain as useUserIntentSubmit → SessionService.sendMessage → agent_send_message → typed Agent Org direct dispatcher. The frontend carries the existing stable turnIntentId and source EventStore user event; the backend verifies both belong to the target canonical Member Session. Delete the independent command or reduce it to a stateless thin adapter over the same dispatcher.
  2. One IMMEDIATE transaction validates Team, Member, source, and queue cap; writes UserDirectedWork{source=DirectMember} context, FIFO sequence, and receipt; and, when TaskExecution is active, writes the intervention chain and unique original-Task continuation receipt. Only after commit request yield from the old execution.
  3. The old runtime must execute release_if_current against lease/generation before the direct Turn can take the same slot. Suspend timeout shows waiting/yield_timeout only and never opens a second runtime.
  4. The UserDirectedWork finalizer first writes assistant output with exact reply_to_event_id to EventStore, then terminates the Turn, releases runtime, and kicks FIFO. It never calls Task complete/fail/requeue hooks and does not block formal Quiescence merely because the participant is a Writer.
  5. Return uses a stable request ID to atomically consume the same receipt. In Working, if the original Task remains open with the same owner, it creates at most one continuation. In Paused/Idle, it only clears intervention. If the Task is terminal, cancelled, or reassigned, record no_longer_needed. Delete the 500 ms/90-second poll and blanket Inbox drain.
  6. Member page and Overview show yielding, user_intervention, side quest, and Return. Stop stops only the current UserDirectedWork and preserves intervention. Remove TTL countdown/expiry timers and make clear that off-board side quests do not automatically enter the Task board.

Primary files and entry points

src-tauri/crates/agent-core/src/core/coordination/agent_member_interventions.rs
src-tauri/crates/agent-core/src/core/coordination/agent_org_user_directed_work/*
src-tauri/crates/agent-core/src/state/commands/session/{message/send.rs,org_tasks/intervention.rs}
src-tauri/crates/agent-core/src/core/session/scheduler.rs
src-tauri/crates/agent-core/src/{lifecycle.rs,init/tool_assembly.rs}
src/api/tauri/agent/orgTasks.ts
src/engines/ChatPanel/hooks/useWorkspaceChat/useUserIntentSubmit.ts
src/engines/ChatPanel/InputArea/components/{useAgentOrgIntervention,AgentOrgInterventionPinBar,AgentOrgOverviewPanel}.tsx*

Scope boundaries

Do not add Task waiting_for_user, modify the original Task's status/owner/dependencies, classify direct input as TaskExecution small steering, automatically Return on TTL/Stop, resume a cancelled/reassigned Task, add TaskExecution revocation, open a second runtime, or add Coordinator TaskExecution.

Failure scenarios that must be rejected or remain no-ops

  • Receipt failure still yields or starts work.
  • Yield timeout opens a second runtime.
  • A crash after old-lease release but before FIFO kick loses the Turn.
  • TTL, page close, or startup automatically Returns.
  • Duplicate direct input suspends twice.
  • Duplicate Return creates two continuations.
  • Stopping UserDirectedWork cancels a formal Task.
  • Return in Paused/Idle resumes formal work automatically.
  • A cancelled/reassigned original Task still receives a continuation.
  • A normal Member mutates the formal graph.
  • A started UserDirectedWork crash automatically replays file/external side effects.
  • Archived, CLI, or noncanonical targets start a Provider.

Acceptance and handoff

Verify the Working/Idle/Paused direct matrix; real file edit, shell, and test production paths; normal-Member three-layer graph denial and Writer allow; suspend timeout, restart, duplicate direct, and duplicate Return; intervention survives Stop, page close, and elapsed time; per-Member runtime count is always ≤ 1; suspend P90 ≤ 5 seconds and hard wait ≤ 10 seconds; runtime release after terminal UserDirectedWork P50 ≤ 1 second and P90 ≤ 5 seconds; and ordinary SDE steering/Queue/Stop regression. After merge, PR 9 may add source adapters only to enqueue_user_directed_work(canonical member, stable turn id, typed source, root authority, optional intervention handoff) and must not copy scheduler/finalizer logic.

Plain-language contract

Dependency

PRs 1–7.

How it works

Connect generic direct send to the one typed UserDirectedWork dispatcher. An idle Member enters its FIFO. If the Member is busy with TaskExecution, first persist the intervention/continuation receipt, then let the old lease exit at a safe point, and finally run UserDirectedWork in the same runtime slot. The reply lands in EventStore with the exact reply ID before the Turn ends and runtime releases. Return uses a stable request and creates one continuation only when the Team is Working and the original Task is still valid.

State after merge

In Working/Idle/Paused, the user can chat directly with a Member, modify files, and run commands/tests. Busy direct work shows that the Member “was called away by the user” and never opens a second runtime. Intervention does not disappear because of TTL, page close, Stop, or restart; only Return clears it.

Boundaries

Do not treat direct input as “small steering,” add a Task waiting state, let a normal Member modify the graph, automatically Return on TTL/Stop, or resume cancelled/reassigned work. Group and peer Inbox remain closed.

How to test

Verify real file/shell/test paths, normal-Member denial and Writer allow, suspend timeout/restart/duplicate Return, intervention surviving Stop/page close/time, per-Member runtime ≤ 1, release P50 ≤ 1 second/P90 ≤ 5 seconds, and ordinary SDE steering/Queue/Stop regression.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Effort: HighUXImprovements to user experience, workflow smoothnessenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions