docs: add ADR for batched turn packing in ACP session/prompt#598
docs: add ADR for batched turn packing in ACP session/prompt#598brettchien wants to merge 1 commit intoopenabdev:mainfrom
Conversation
Records the structural decision extracted from RFC openabdev#580 (Turn-boundary message batching): how N concatenated arrival events are packed into the Vec<ContentBlock> crossing the broker → ACP boundary. Reuses the existing per-arrival-event <sender_context> template repeated N times rather than introducing a parallel <message index=N> wrapper schema, with one additive `timestamp` field on SenderContext. Closes the attribution gap surfaced independently by Triage (T1.4) and JARVIS/FRIDAY (B1) reviews.
|
request input from @masami-agent and @shaun-agent |
masami-agent
left a comment
There was a problem hiding this comment.
PR Review: #598
Summary
- Problem: RFC #580 MVP packing flattens
extra_blocksto a tail, destroying attachment ↔ message attribution (flagged independently by T1.4 and B1). - Approach: Reuse existing
<sender_context>template repeated N times with one additivetimestampfield — no new schema, no wrapper tags. - Risk level: Low (docs-only ADR, no code change in this PR)
Core Assessment
- Problem clearly stated: ✅ — grounded in two independent reviews (T1.4, B1) that converged on the same gap
- Approach appropriate: ✅ — reuses existing
<sender_context>rather than inventing parallel schema; minimal surface area - Alternatives considered: ✅ — six alternatives (§6.1–6.5) each with clear rejection rationale
- Best approach for now: ✅ — single uniform code path, no ACP protocol change, additive schema evolution
Findings
Verified against current codebase:
SenderContextstruct inadapter.rscurrently has:schema,sender_id,sender_name,display_name,channel,channel_id,thread_id(optional),is_bot— notimestampfield, confirming the ADR's claim that the addition is purely additive ✅handle_messageinadapter.rsdoes prepend Textextra_blocks(transcripts) before<sender_context>+prompt, then appends non-Text blocks (images) after — confirming the ADR's description of the current asymmetric ordering ✅- The
prompt_with_senderformat matches:<sender_context>\n{json}\n</sender_context>\n\n{prompt}✅ - Sibling ADR links (
./multi-platform-adapters.md,./custom-gateway.md) both resolve ✅ - Tracking issue #580 exists, is OPEN, and its comments confirm T1.4 and B1 independently flagged the attribution gap ✅
- ADR filename follows existing kebab-case convention ✅
What I especially liked:
- The Highest Guideline (§2) with five concrete prohibitions gives future implementation PRs a clear test surface — any packing change can be judged against these rules
- §8 Compliance section is unusually thorough — 8 rules + explicit prohibited transformations in §8.1 make the ADR self-enforcing
- Honest about negatives: Scenario D regression and token cost are acknowledged, not hidden
- The rollback path for Scenario D is pragmatic — hotfix PR rather than permanent feature flag
Review Summary
🔧 Suggested Changes
- §3.1
thread_idomission: TheSenderContextstruct has#[serde(skip_serializing_if = "Option::is_none")]onthread_id, so it's absent from serialized JSON whenNone. The example JSON in §3.1 omits it without explanation. Consider either including it in the example (e.g."thread_id": "123456") or adding a note like (fields withskip_serializing_ifbehavior, such asthread_id, are omitted whenNone) so readers don't wonder if it was forgotten. - §3.2 backward compatibility note: The ADR says "schema stays
openab.sender.v1" — worth adding one sentence explicitly noting that consumers using lenient JSON parsing (ignoring unknown fields) won't break, since this is the property that makes the change safe. Something like: "Consumers that ignore unknown JSON fields (the default forserdewith#[serde(deny_unknown_fields)]absent) will continue to work unchanged."
⚪ Nits
- Stale line numbers: References to
adapter.rs:131-152,138-143,138-152,148-152are ~20 lines off from the current code (the logic moved down due to added comments and thethread_idfield). Not blocking since the logic descriptions are accurate, but updating them would prevent confusion for readers checking the source. - §5 Scenario B visual gap: The example shows a blank line between the two
<sender_context>blocks, but in the actualVec<ContentBlock>array these would be separateContentBlockentries with no visual gap. Minor presentation issue — the structural meaning is clear regardless.
Verdict
APPROVE ✅
This is an exceptionally well-written ADR. The design decision is sound — reusing <sender_context> with adjacency-based attribution is simpler and more robust than the RFC MVP's wrapper-and-flatten approach. The compliance section and prohibited transformations list will serve as a durable reference for implementation PRs. The suggested changes above are non-blocking improvements.
Follow-up Suggestions (post-approval)After discussing this ADR in more depth, I have three suggestions for the implementation phase. These are not blockers for merging this ADR — the design direction is sound and should land as-is. These are guardrails for RFC #580 Phase 1. 1. Treat this ADR as a living document during Phase 1This ADR is thorough (350 lines, 8 compliance rules, 5 prohibitions), but the implementation PR for RFC #580 has not been opened yet. Real code has a way of surfacing edge cases that design docs miss. Suggestion: When Phase 1 implementation begins, if the implementer discovers a case the ADR did not anticipate, a follow-up PR to amend the ADR should be welcomed — not treated as a violation. The compliance rules in §8 are valuable as guardrails, but they should evolve with implementation experience rather than become immutable before any code is written. 2. Make observability metrics a Phase 1 must-haveThe ADR defines three metrics for monitoring token cost growth (§7 Negative consequences):
And a re-evaluation threshold (p95 × envelope tokens > 500 per dispatch). Without these metrics actually being implemented, the threshold will never trigger and the dedup discussion will never happen — even if token costs become a real problem in production. Suggestion: Track these three metrics as blocking items in the RFC #580 Phase 1 implementation PR, not as a Phase 2 follow-up. 3. Define Scenario D smoke test criteria before Phase 1The ADR acknowledges a behavior change for voice-only messages (STT transcripts move from before Suggestion: Before the Phase 1 implementation PR is opened, define a minimal smoke test matrix:
This ensures the rollback decision is based on concrete evidence, not subjective judgment. These three items could be tracked as acceptance criteria on RFC #580, or as a checklist in the Phase 1 implementation PR description. |
obrutjack
left a comment
There was a problem hiding this comment.
PR Review: #598
Summary
- Problem: RFC #580 MVP packing flattens
extra_blocksto a tail, destroying attachment ↔ message attribution (T1.4 / B1). - Approach: Reuse existing
<sender_context>template with additivetimestampfield; attachments attributed by array adjacency. - Risk level: Low (docs-only PR, no runtime changes)
Core Assessment
- Problem clearly stated: ✅
- Approach appropriate: ✅
- Alternatives considered: ✅ (§6 covers 5 alternatives with clear rejection rationale)
- Best approach for now: ✅
Source Code Verification
Verified against current main:
adapter.rsSenderContextstruct confirms notimestampfield — additive change is accurateadapter.rs:handle_messageconfirms transcript Text blocks prepended, image blocks appended — §3.5 / Scenario D behavior change description is accuratediscord.rsconfirms voice transcript usesextra_blocks.insert(0, ...)— consistent with ADR- Sibling ADR links (
multi-platform-adapters.md,custom-gateway.md) resolve ✅ - RFC #580 tracking reference resolves ✅
Review Summary
💬 Questions
- §3.2 — Gateway adapter uses
chrono::Utc::now().to_rfc3339()as best-effort timestamp. Does the current gateway inbound event schema (openab.gateway.event.v1) already carry a timestamp? If so, the ADR should note that the gateway adapter should prefer the event's own timestamp over broker receive time.
🔧 Suggested Changes
- §8 Compliance rule 1 lists "broker expanding Discord
<@123>mentions to@usernamestrings" as a prohibited transformation, butdiscord.rs'sresolve_mentions()already does this. Suggest either removing this counter-example or adding a caveat that mention resolution is an adapter-layer transformation (before{prompt}enters the broker), not subject to this rule. - §7 Negative — the observation threshold formula (
p95_batch_size × context_tokens_per_event > 500 tokens) mixes count and token units. Consider clarifying this is the per-dispatch envelope overhead in tokens.
⚪ Nits
- §3.2 —
slack_ts_to_iso8601(event.ts)doesn't exist in the current codebase. Consider marking it as a proposed helper to avoid confusion.
Verdict
COMMENT — requesting contributor response on the resolve_mentions contradiction (Suggested Change #1) before maintainer approval. No blockers.
Discord Discussion URL: https://discord.com/channels/1491295327620169908/1497977225314832536
Summary
Adds a standalone ADR (
docs/adr/batched-turn-packing.md) recording how a batched ACP turn is packed intoVec<ContentBlock>across the broker → ACP boundary, extracted from RFC #580 (Turn-boundary message batching).The ADR converges on repeating the existing per-arrival-event
<sender_context>template N times rather than introducing the<message index=N>wrapper schema the RFC MVP proposed. A single additivetimestampfield onSenderContext(schema staysopenab.sender.v1) gives arrival-event distinguishability and subsumes T2.j'sarrived_at_relativeproposal.Closes the attribution gap surfaced independently by:
extra_blockstail loses the attachment ↔ message linkWhat this PR does and does not include
pack_arrival_event/dispatch.rsimplementation referenced inside the ADR — that lands separately.Test plan
docs/adr/./multi-platform-adapters.mdand./custom-gateway.mdresolveTracking: #580