Skip to content

docs(eve): background tasks - define task and notification contract plan#1085

Draft
ruiconti wants to merge 6 commits into
mainfrom
research/background-tasks
Draft

docs(eve): background tasks - define task and notification contract plan#1085
ruiconti wants to merge 6 commits into
mainfrom
research/background-tasks

Conversation

@ruiconti

@ruiconti ruiconti commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes-tracking #1084. Research plan only — no runtime changes, no changeset.

What

Adds research/background-tasks.md: a plan for letting model-initiated work outlive the turn that started it.

Today results can only wake an active turn — subagents hold the parent turn parked until every child resolves — and HITL requests either expire (questions cleared as ignored by the next message) or block (approvals defer all later input). A response that arrives after its request left pendingInputs is downgraded to text and never executes the original action. There is no third state: work that neither keeps the turn active nor gates the conversation.

The doc leads with the capability delta this buys: fire-and-forget delegation, subagent HITL that reaches the parent without blocking it, child progress on the parent's surfaces, late answers that execute via updateTask, and detached executors that cover non-subagent integrations.

The shape

  • A durable task record aligned with the MCP Tasks extension (io.modelcontextprotocol/tasks, SEP-2663): same object shape (ttlMs, pollIntervalMs, DetailedTask variants carrying inputRequests/result/error inline) and same operation semantics (tasks/get/tasks/update/tasks/cancel). Shapes and semantics only — no MCP wire surface; that stays a future adapter.
  • The spec is organized around the two roles eve plays: caller (elect background, take a CreateTaskResult-shaped placeholder at the call position, register the session driver as wake endpoint, route the arriving notification) and callee (run the work, transition the record, POST a notification envelope per transition to registered endpoints). Subagents compose the two.
  • Notifications are a single URL/POST delivery path targeting the session driver — the generalization of the existing remote-agent callback: { token, url } contract, paying a loopback POST locally to avoid a local/remote branch. The driver's wake vocabulary stays deliver-only and NextDriverAction stays closed; discrimination lives in step bodies, so the pinned driver is untouched.
  • Design goal: the agent tool ends up authorable as a plain defineTool on public primitives — ctx.task handle, detachable execution (ctx.task.detach()), endpoint registration — with the privileged internal path deleted as the exit criterion.

Three vertical slices, each cutting through both roles end-to-end: inert task mode (no public electors) → subagents as the first electors, via sync()/defer()/background() combinators on defineAgent → the public authoring API on defineTool(), closing with the agent tool re-implemented on it. Normalizing sync invocations onto the record (mode discriminator, "who awaits") is deferred, as is token-budget accounting for detached work.

For reviewers

The open questions section is the review surface: subagent default (defer() vs sync()), a built-in cancel_task tool, ttl-gating of late input_required answers, observer registration for channels/external callers, whether to chase the extension draft's unsettled negotiation names, and what would make flipping the default to defer() safe for arbitrary tools.

Research plan for durable task records aligned with the MCP Tasks
extension (io.modelcontextprotocol/tasks, SEP-2663): sync vs background
becomes a property of the invocation, background elections terminalize
the tool-call position with a CreateTaskResult-shaped placeholder, and
status transitions reach subscribers as deliveries to registered
notification endpoints - the generalization of the existing remote-agent
callback, entering parked sessions through the driver's existing
deliver-only wake vocabulary under the initiator auth principal.

Phased additively: task contract (no users), notifications contract
(registered endpoints on session state), task-to-notification wiring,
defineTool() election via execution.taskSupport, and migration of the
built-in agent tool and subagents.

Issue: #1084
Signed-off-by: Rui Conti <ruiconti@gmail.com>
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
eve-docs Ready Ready Preview, Comment, Open in v0 Jul 24, 2026 11:24pm
eve-docs-1644 Ready Ready Preview, Comment, Open in v0 Jul 24, 2026 11:24pm
eve-docs-4759 Ready Ready Preview, Comment, Open in v0 Jul 24, 2026 11:24pm

…oles

Reshape the plan from five horizontal phases into three vertical slices
(inert task mode; subagents on the task model; public authoring API),
organized by the two roles eve plays in the task contract: caller
(elect, park, wake, route) and callee (run, transition, notify), each
with its own protocol section and sequence diagram.

New commitments since the first draft:

- Design goal: the agent tool must be authorable as a plain defineTool
  on public primitives - ctx.task handle, detachable execution
  (ctx.task.detach()), and endpoint registration - with the privileged
  internal path deleted as slice 3's exit criterion.
- Notification envelope: explicit event kinds (task.created/progress/
  status/terminal) carrying the full DetailedTask snapshot; default
  routing is wake-worthy kinds only, per-endpoint filters for passive
  observers.
- Endpoints collapse to a single URL/POST delivery path targeting the
  session driver (loopback locally), generalizing the remote-agent
  callback instead of special-casing it.
- Election combinators sync()/defer()/background() as sugar over
  2025-11-25 taskSupport semantics; sync-by-default is explicitly
  transitional (new open question 6 on flipping to defer()).
- Mid-turn notification arrival scenario: buffer-then-route via the
  existing buffered-delivery and turn-delivery-request machinery.
- Sync-invocation normalization onto the record (mode discriminator,
  "who awaits") moved to deferred.

Issue: #1084
Signed-off-by: Rui Conti <ruiconti@gmail.com>
@ruiconti ruiconti reopened this Jul 23, 2026
Add a "What this unlocks" section after Motivation stating the
today-vs-after delta per capability: fire-and-forget delegation,
subagent HITL reaching the parent without blocking it (today's proxy
exists only because the parent turn is parked on the child), child
progress on the parent's surfaces at statusMessage granularity (today
the parent stream has only subagent.called/completed), late answers
executing via updateTask instead of degrading to text, and detached
executors covering non-subagent integrations.

Issue: #1084
Signed-off-by: Rui Conti <ruiconti@gmail.com>
…consumers

'Notification' names the emitted event itself (the TaskNotification
envelope a transition produces), so using it for the receiving side too
conflated the two halves of the return path. The registered receivers —
previously 'notification endpoints', 'endpoints', 'subscribers', and
'observers', inconsistently — are now event consumers: EventConsumer,
DEFAULT_CONSUMER_EVENTS, consumer registration, per-consumer guards.

The task-resolution URL ctx.task.detach() returns keeps the name
'callback endpoint': it resolves the task; it does not consume events.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
ruiconti added 2 commits July 24, 2026 19:20
The TaskService surface is not framework-internal: agents operate on
their session's live tasks too — read, answer, abort, and await,
including a join across all live tasks (fan out, then block until
every task settles). Awaiting is an invocation posture over an
unchanged record, so background and sync remain two postures over one
task. Narrows open question 2 to the model-facing shape of those
operations.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
Implementation of Slice 1 (#1190) showed session state cannot
own the record: it is threaded through step results, while transitions
must be writable from paths holding no threadable state — the routing
step while parked, the callback route, a detached executor. Each record
is now owned by a dedicated durable task run (single-writer actor,
command hook in, snapshots out, cross-run tail reads); the caller keeps
a live-task index on session state and the recorded election rides the
pending action batch, keeping the extension capability contracts
untouched. Also notes the inert-mode consumer-orphaning edge under
continuation-token rotation.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
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.

1 participant