Skip to content

Implement Junior-to-Junior ACP delegation with durable resume/replay #530

@dcramer

Description

@dcramer

Summary

Add real Agent Client Protocol (ACP) support so one Junior runtime can delegate work to another Junior runtime. The upstream Junior acts as an ACP client; the downstream Junior exposes an ACP agent surface backed by Junior's durable session log and worker model.

This should support timeout-bound compute by using ACP session/load for replay/gap recovery, session/resume for fast reconnects, and ACP _meta fields for Junior-specific prompt dedupe/correlation.

Goal

Enable:

Slack user -> Junior A -> ACP delegation -> Junior B -> durable agent work

Junior A should be able to yield before platform timeout, resume later, reconnect to Junior B, recover missed events, and deliver a final Slack answer without duplicating delegated work.

Protocol Baseline

Implement ACP v1 semantics:

  • initialize
  • session/new
  • session/load
  • session/resume
  • session/list
  • session/prompt
  • session/cancel
  • session/close

Use ACP _meta for Junior-specific correlation:

{
  "sentry.dev/junior": {
    "sourceConversationId": "...",
    "sourceTurnId": "...",
    "sourceToolCallId": "...",
    "delegationId": "..."
  }
}

Transport

Start with a Junior HTTP transport that preserves ACP JSON-RPC semantics.

Suggested shape:

POST /acp        client-to-agent JSON-RPC
GET  /acp/events optional event stream/replay endpoint

Do not depend on the ACP Streamable HTTP/WebSocket RFD being finalized. Keep the implementation close enough to that shape that we can migrate later.

Durability Model

Junior A records outbound ACP delegation in its local session log:

acp.session.created
acp.prompt.submitting
acp.session.update.received
acp.prompt.completed
acp.prompt.failed

Junior B records the authoritative ACP session in its own durable log:

acp.session.created
acp.prompt.accepted
acp.session.update.emitted
acp.prompt.completed
acp.prompt.cancelled

On timeout recovery, Junior A calls session/load, dedupes replayed updates against its mirrored log, and continues.

Prompt Dedupe

ACP does not standardize prompt idempotency. Use Junior _meta identity as a receiver-side dedupe key:

sessionId + sourceConversationId + sourceTurnId + sourceToolCallId

If Junior B has already accepted the prompt, it should not enqueue duplicate work. It should replay/return the existing session state.

Implementation Phases

  1. Add ACP protocol types and JSON-RPC validation under packages/junior/src/chat/acp/protocol/.
  2. Add ACP server adapter that maps ACP sessions to Junior durable conversations/session logs.
  3. Implement initialize, session/new, session/load, and session/prompt.
  4. Add prompt dedupe using _meta source ids.
  5. Add ACP client adapter/tool for Junior A delegation.
  6. Add timeout recovery path: local mirror log + session/load replay + dedupe.
  7. Add session/resume, session/cancel, session/close, and session/list.
  8. Add optional event streaming/replay endpoint for efficiency after correctness works.

Acceptance Criteria

  • Junior A can delegate a prompt to Junior B and receive streamed ACP session/update output.
  • If Junior A times out after prompt submission, it can recover via session/load.
  • Replaying a remote session does not duplicate Slack progress or final replies.
  • Retrying a submitted prompt does not enqueue duplicate work in Junior B.
  • Junior B can cancel and close delegated sessions.
  • The implementation remains ACP-compatible, with Junior-only behavior isolated to _meta or custom _sentry.dev/* methods.

Tests

  • Protocol unit tests for JSON-RPC parsing, method routing, and capability negotiation.
  • Integration test: Junior A delegates to Junior B and receives final output.
  • Integration test: A times out after B accepts prompt; A reconnects with session/load.
  • Integration test: duplicate session/prompt with same _meta key is deduped.
  • Integration test: cancel/close stops downstream work and records terminal state.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions