From 5cc3fa016824b2aaa323ca05cf9fda2e7610833f Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 14 Nov 2026 01:08:18 +0000 Subject: [PATCH] Plan conversation continuation and execution groups --- docs/roadmap/audita-request.md | 169 ++++++ docs/roadmap/conversation-continuation.md | 50 +- docs/roadmap/implementation.md | 633 ++++++++++++++++++++++ 3 files changed, 812 insertions(+), 40 deletions(-) create mode 100644 docs/roadmap/audita-request.md create mode 100644 docs/roadmap/implementation.md diff --git a/docs/roadmap/audita-request.md b/docs/roadmap/audita-request.md new file mode 100644 index 0000000..4dcd54b --- /dev/null +++ b/docs/roadmap/audita-request.md @@ -0,0 +1,169 @@ +# Audita request: conversation continuation and application-directed repair + +Status: proposed upstream features, requested by Audita. Public API design and +implementation remain for PromptKit to determine. + +This request is based on Audita's use of PromptKit v0.21.0. It proposes two +related capabilities: captured conversation continuations, followed by bounded +repair driven by application-supplied validation feedback. The goal is to move +reusable execution machinery into PromptKit and remove its local equivalents +from Audita. + +## Audita's workflow + +Audita polishes transcripts using module-specific proposal, validation, and +repair prompts: + +1. A proposal request supplies transcript segments and receives a structured + collection of proposed corrections. +2. Audita evaluates each correction using deterministic and LLM-backed + validators. Accepted corrections are retained for later application. +3. Each actionable rejection starts an independent repair branch containing the + original rendered request, the full final assistant response, and a follow-up + instruction rendered from that module's repair prompt definition. +4. The follow-up identifies one correction and supplies its current content, + available before-and-after transcript context, and validation findings. The + model can revise or withdraw that correction. This response uses a different + schema from the original collection of proposals. +5. A revision passes through the validators again. Further attempts retain that + branch's history and receive updated feedback. Acceptance, withdrawal, + exhaustion, or failure ends the branch; independent approvals are preserved. + +Audita owns the meaning of a correction, the validation rules, repair +eligibility, and eventual application to the transcript. Conversation handling +and the bounded revision lifecycle are useful beyond transcript processing. + +## Existing support and the remaining gap + +PromptKit already supports [structural output repair](../consumers/pkg-promptkit.md#repair-a-structured-result), +[appended messages](../consumers/pkg-promptkit.md#append-already-rendered-messages), +prepared execution, profiles, execution observations, and backend capacity +management. These allow Audita to implement the workflow today. + +Audita still captures exchanges, reconstructs the original prompt, verifies its +rendered prefix, assembles branch histories, renders follow-up instructions, +and coordinates repeated application validation and revision. The requested +APIs would make those responsibilities supported PromptKit capabilities. + +## 1. Captured conversations and independent continuations + +Provide a public way to capture a rendered request and its full final assistant +response, then continue that exchange without making the consumer reconstruct +the original prompt and check that its messages are unchanged. + +Audita needs to: + +- Preserve the original rendered message content, roles, and cache metadata + when continuing an exchange. +- Start multiple independent branches from the same exchange. A branch's later + instructions and responses must not alter the original exchange or another + branch's history. +- Render follow-up instructions using ordinary PromptKit prompt definitions and + variables, and append them to the selected branch. Audita's repair prompts + remain operator-replaceable through its existing PromptKit configuration. +- Select a follow-up profile and output contract independently of the initial + request. The first response may contain many items while a continuation + returns one revised item or a withdrawal. +- Retain the relationship between the original exchange, the follow-up prompt + definition, and each execution in inspection and observation data. + +An in-process capability is sufficient for Audita. Persistence, restartable +sessions, and a conversation database are not required by this request. The +existing [prepared execution handle](../../prepared_execution.go) is single-use; +the request does not assume that copying or reusing it creates a continuation. + +## 2. Bounded application-directed repair + +Build a reusable revision coordinator on the continuation capability. Audita +should supply its assessment and request-building behavior while PromptKit +handles the repeated execution lifecycle. + +The coordinator needs to support: + +- Starting with an already-assessed candidate and its rejection findings, so + Audita need not repeat the initial batch validation simply to enter repair. +- Application-supplied assessment of each revision, including deterministic + checks and checks that make further PromptKit calls. Findings may come from + multiple validators; their meaning and explanatory content remain with the + application. +- Application-selected follow-up prompts, variables, profiles, and response + contracts. Audita supplies the candidate and context, and interprets the + response as a revision or withdrawal. PromptKit need not prescribe an Audita + correction schema or a universal feedback format. +- Reassessment before a revised candidate can be accepted, with fresh feedback + for the next attempt and accumulated history confined to that branch. +- A caller-selected additional-revision budget and caller context, with clear + terminal outcomes covering acceptance, withdrawal, exhaustion, unavailable + results, and interruption. Callers retain decisions about repair eligibility + and whether an operational failure should terminate other work. +- Attempt observations, execution relationships, and available usage accounting + sufficient to report a repair sequence without recreating its lifecycle + tracking in Audita. + +The revision budget must remain distinct from transport retry and structural +repair budgets. A structurally valid response can still fail Audita's checks; +each revision generation can itself need structural repair. Accounting should +distinguish those layers and avoid double-counting shared initial work. + +Application validators must be able to call the same engine and backend without +being blocked by capacity retained by their parent operation. Coordinating +ordinary completed runs is one possible approach. PromptKit should determine +the implementation in light of its [execution and admission lifecycle](../internal/runner.md#run-flow). + +This is broader than exposing the current structural-repair hook: the response +schema can change between proposal and repair, validation may perform model +calls, and each rejected item has its own history and disposition. + +## Ownership boundary + +PromptKit would own captured exchanges, branch continuation, the bounded repair +lifecycle, and associated execution observations. Audita would retain: + +- Transcript segments, proposal identities, glossary and protected-term rules, + editorial judgments, and meaning checks. +- Validator selection and ordering, actionable-rejection policy, feedback + content, and module-specific proposal, validation, and repair prompts. +- Before-and-after transcript previews, preservation of accepted sibling + proposals, application ordering, and final transcript mutation. +- Application reports, debug artifact destinations, and CLI behavior. + +The separate idea of reconciling missing or duplicate items in batch responses +is outside this request. The two requested capabilities should be usable +through the supported public facade without an Audita dependency or a general +workflow framework. Exact types, callbacks, and configuration syntax are left +to PromptKit's API design. + +## Suggested delivery and acceptance scenarios + +Deliver conversation continuation first, allowing Audita to replace its capture +and replay machinery. Then add application-directed repair and replace the +corresponding local coordination. Existing prompt definitions and domain +validators should remain usable through the new APIs. + +The following scenarios would demonstrate that the capabilities meet Audita's +needs: + +1. An initial response contains three proposals. One is accepted; the other two + receive independent repair conversations containing the same original + exchange. Revising either branch leaves the accepted proposal and the other + branch unchanged. +2. A follow-up uses a separate prompt definition, profile, and single-item + response schema. Inspection identifies the follow-up and its originating + exchange. +3. A revision fails multiple application checks, receives their updated + feedback, and passes on a later attempt. An LLM-backed check can use the same + engine and backend even when backend capacity is limited to one. +4. Withdrawal, exhausted revision budget, unavailable validation, and + cancellation produce distinguishable outcomes. An unaccepted revision is + never returned as an accepted candidate, and a recoverable branch failure + leaves independent approvals available to the application. +5. A revision generation needs both a transport retry and structural repair. + Observations distinguish these from application-directed revision attempts + and expose the available usage without counting the original exchange once + per branch. + +For implementation reference, Audita currently owns these mechanics in +`internal/framework/llm/adapter.go`, +`internal/framework/structuredoutput/call.go`, and +`internal/framework/runner/repair.go`. These are examples of the consumer need, +not proposed PromptKit package boundaries or public API shapes. diff --git a/docs/roadmap/conversation-continuation.md b/docs/roadmap/conversation-continuation.md index 153a68d..1249a70 100644 --- a/docs/roadmap/conversation-continuation.md +++ b/docs/roadmap/conversation-continuation.md @@ -1,8 +1,9 @@ # Conversation Continuation And Execution Groups -Status: target behavior accepted; ready for API and staged implementation planning. -No implementation is authorized by this document. Exact public types and -signatures remain to be designed. Execution groups are independently deliverable. +Status: target behavior accepted; concrete API choices and implementation steps +are recorded in the [implementation plan](implementation.md). This document +defines the target behavior and responsibility boundaries. Execution groups are +independently usable. ## Purpose And Responsibility Boundary @@ -390,44 +391,13 @@ Applications retain their received results; the coordinator's termination value is not an archive. These examples should remain usable through the same generic API without manual history assembly or per-turn lifecycle bookkeeping. -## API Planning And Delivery +## API And Implementation Ownership -No product-level decisions remain open. The companion implementation plan should -settle exact public types and signatures before implementation, within the -accepted behavior above: - -- Capture opt-in syntax and exchange representation. Prefer a request option; - separate capture methods remain viable. Prefer a details accessor, while a - public value is viable if it preserves ownership and generic rendering. -- Continuation request shape and placement of immediate-parent metadata, using - the message assembly order defined above. -- Callback state, next-action types, normal outcomes, and the optional - undelivered-response result. -- Collector attachment, observer composition, and summary field representation. - Start with the observer-compatible API. Explicit group fields remain a fallback - only if concrete ordinary/prepared/coordinator/callback usage sketches expose - awkwardness; either approach uses existing observation machinery. - -Check the concrete API against both consumer sketches and ordinary and prepared -execution, including capture selection, exchange access, coordinator actions and -results, and observer composition. Keep these choices consistent across the -staged plan rather than leaving each implementation stage to choose its own -public contract. - -Deliver in separable stages: - -1. **Capture and independent continuation:** expose execution-derived exchanges, - reusable branches, fresh follow-up settings, parent metadata, and details. - This lets consumers remove reconstruction and prefix-verification machinery. -2. **Optional sequential coordinator:** build on continuation and ordinary - execution, validation, capacity, and observations. Refine both consumer - sketches against the concrete API. -3. **Optional execution groups, on an independent track:** provide collection, - composition, snapshots, and aggregation. This can ship separately and need - not wait for or delay continuation or coordination. - -These stages guide a subsequent implementation plan; they do not authorize code -changes or impose an ordering dependency on execution groups. +The [implementation plan](implementation.md) selects the concrete API and steps +for reaching this target. Both consumer sketches must remain expressible without +manual history assembly or per-turn lifecycle bookkeeping. Execution groups +remain independent of capture and coordination; the plan's ordering does not +introduce a product dependency between them. ## Acceptance Scenarios diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..6667133 --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,633 @@ +# Conversation Continuation And Execution Groups Implementation Plan + +## Objective And Sprint Rules + +Implement the accepted [feature roadmap](conversation-continuation.md), using +[Audita's request](audita-request.md) as consumer evidence. The feature roadmap +owns product behavior and boundaries; this plan selects the API and work order. +Existing structural-output repair remains a separate, unchanged capability. + +Each numbered stage is one Luna-sized implementation prompt. Execute them in +order during one end-to-end sprint. Intermediate stages need not compile or +constitute independently releasable changes. Do not build compatibility shims or +temporary implementations solely to make a stage green. Carry a concise record +of remaining wiring into the next stage; the final stage must leave the whole +repository coherent and validated. There are no intermediate human-review, +external-publication, provider-access, or dependency-release gates. + +Start with [development guidance](../development.md). Follow the applicable +[agent](../policy/agents.md), [architecture](../policy/architecture.md), +[testing](../policy/testing.md), and [documentation](../policy/documentation.md) +policies. Preserve the accepted [configuration boundary](../adr/0001-configuration-boundary.md). +This sprint needs no configuration format, dependency, Go minimum, release +tooling, or licensing changes. It does not authorize tagging or publication. + +Use real fast internal collaborators and replace only external boundaries in +tests. Select focused checks during stages and run the complete checks at the +end. Do not duplicate existing retry, repair, schema, or capacity test matrices. +Protect the new interactions with those mechanisms. + +## Current Baseline And Integration Points + +- The root facade in `engine.go` converts public requests, masks inherited + observation state, invokes the internal engine, maps errors, and finishes + observations after the runner releases admission. Preserve this boundary. +- `internal/usecase/runner.go` has shared resolution and completion preparation + phases. `completePreparationWithStructuredOutput` currently combines rendered + definition messages with appended messages and hashes the resulting prompt. + `executePreparedRun` is shared by ordinary and prepared execution and returns + the final `RawOutput`, including after exhausted structural repair. +- `internal/usecase/prepared_execution.go` retains a single-claim payload, + validation plan, direct credential, retry budget, and observer separately from + durable prepared details. Claims remain bound to the originating execution + owner. Details survive execution and discard. +- `internal/domain/message.go` owns message cloning and cache-control ownership. + Reuse it rather than inventing a second normalization contract. +- `observations.go`, `internal/domain/observation.go`, and + `internal/observation/recorder.go` own value-only events, run IDs, terminal + counters, and presence-aware usage. Terminal observations already include + completed work preceding operational failure. Do not total result usage or + attempt events on top of terminal totals. +- `internal/engine/engine.go` applies namespace views to new caller selections. + Captured history requires no original-engine lookups or namespace rewriting. +- Root conversion, `json.go`, formatting, and contract tests must account for + additions without changing existing timing, credential, or error behavior. + +## Selected Public API + +Implement these names and shapes in the root `promptkit` package. Code below +specifies additions and complete new types, not replacement definitions for +existing types. Keep all implementation machinery under `internal/`. + +### Capture, Continuation, And Ancestry + +```go +// Add to RunRequest. +CaptureExchange bool +ContinueFrom *Exchange `json:"-" yaml:"-"` + +// Add to RunResult. +Exchange *Exchange `json:"-" yaml:"-"` +ParentRunID string `json:"parent_run_id,omitempty"` + +// Add to PreparedRun. +ParentRunID string `json:"parent_run_id,omitempty"` + +// Add to ExecutionEvent, following its existing field/tag style. +ParentRunID string + +// Exchange has private state only. +type Exchange struct { /* private immutable snapshot reference */ } + +type ExchangeDetails struct { + RunID string `json:"run_id"` + ParentRunID string `json:"parent_run_id,omitempty"` + Messages []RenderedMessage `json:"messages"` +} + +func (e *Exchange) Details() ExchangeDetails +``` + +- `Run` with `CaptureExchange: true` returns `result.Exchange`. A non-nil + `ContinueFrom` supplies history and implies capture, regardless of the Boolean. + No separate continuation execution method is necessary. +- `Prepare`, `ValidateRequest`, and `PrepareExecution` accept the same request + fields and assemble the same effective messages. They create no completed + exchange. `PrepareExecution` retains capture selection and ancestry privately + for `RunPrepared`; plain preparation only returns its ordinary metadata. +- Nil `ContinueFrom` means no history. A non-nil, zero-value exchange is not a + captured exchange and fails as `ErrInvalidRequest` before source/model work. + `Details` on a nil or zero-value exchange returns a zero details value, + matching the prepared-handle accessor pattern. +- Details contain the complete logical history ending in the final assistant + content. They are detached and encodable with ordinary encoders. Give the + opaque handle constant `String` and `GoString` representations, following + `PreparedExecution`. Serializing ordinary results does not serialize the + capability or automatically disclose the additional conversation history; + callers explicitly encode `Details()` when desired. Do not add deserialization, + persistence, reconstruction, lookup, or lifecycle APIs. +- `ParentRunID` is the immediate source exchange's originating run ID, or empty + for initial runs. Expose it in prepared details, completed results, exchange + details, and ordinary observation events. Reuse the run ID already assigned to + the execution. It is metadata, not a provider request parameter, session ID, + group ID, or new source of execution settings. +- The follow-up's `PromptID`, definition hash, selected profile/backend, and input + hashes describe the new request. The rendered-prompt hash covers its complete + effective message sequence and selected session, using the existing hash + function. Do not hash capture flags or run identities into content equality. + +### Optional Coordinator + +```go +type ConversationTurn struct { + Exchange *Exchange + Result *RunResult + RemainingTurns int +} + +type CoordinateRequest struct { + Start *Exchange + AdditionalTurns int + Next func(context.Context, ConversationTurn) (*RunRequest, error) + Observer ExecutionObserver +} + +type CoordinationOutcome string + +const ( + CoordinationStopped CoordinationOutcome = "stopped" + CoordinationExhausted CoordinationOutcome = "exhausted" +) + +type CoordinationResult struct { + Outcome CoordinationOutcome `json:"outcome,omitempty"` + Undelivered *RunResult `json:"undelivered,omitempty"` +} + +func (e *Engine) Coordinate(ctx context.Context, req CoordinateRequest) (CoordinationResult, error) +``` + +- `Next` returning `(nil, nil)` stops. A non-nil request asks to continue; an + error stops with that error. No separate next-action enum or domain-outcome + taxonomy is needed. A zero request pointed to by a non-nil pointer is still + a continuation request, subject to ordinary validation if execution starts. +- `Start` must hold a captured exchange, `Next` must be non-nil, and + `AdditionalTurns` must be nonnegative; invalid values match `ErrInvalidRequest`. + A nil/uninitialized engine follows the existing `ErrInvalidConfig` convention. + Zero is a useful budget, not a default that expands into extra turns. +- Initial callback state has `Exchange: Start`, `Result: nil`, and the full + remaining budget. Later states contain the newly completed result and exchange + with the remaining start budget, including zero after the last permitted run. + The initial exchange is not presented as a newly delivered response. +- The coordinator copies each selected request value and sets its `ContinueFrom` + to the current exchange. It owns this one branch's progression, so a callback's + `ContinueFrom` does not redirect that branch. The rest of the request uses + ordinary rules. Callers wanting independent branches use `Run` themselves. +- Compose the request's observer first and the coordinator's observer second. + The latter selects all continuation runs, not the starting run or nested + callback calls. Applications explicitly compose additional group observers. +- On error leave `Outcome` empty. Set `Undelivered` only for a completed new + response withheld from callback invocation because cancellation was observed. + That result includes its exchange. A callback that was invoked has received + its response even if it errors or cancels. Return no result archive, latest + delivered response, failure-stage taxonomy, or coordinator accounting totals. +- Callbacks receive caller-owned public values. Retain the coordinator's current + immutable exchange separately, so replacing fields in delivered values cannot + rewrite retained history or the next continuation's ancestry. + +### Observer Composition And Execution Groups + +```go +func ComposeObservers(observers ...ExecutionObserver) ExecutionObserver + +// Zero value ready for use; contains private synchronization/state only. +type ExecutionGroup struct { /* private collector */ } + +func (g *ExecutionGroup) Observe(event ExecutionEvent) +func (g *ExecutionGroup) Snapshot() ExecutionGroupSummary + +type ExecutionGroupMember struct { + RunID string `json:"run_id"` + TerminalObserved bool `json:"terminal_observed"` +} + +type ExecutionGroupSummary struct { + Members []ExecutionGroupMember `json:"members"` + TerminalCoverageComplete bool `json:"terminal_coverage_complete"` + Attempts uint64 `json:"attempts"` + TransientRetries uint64 `json:"transient_retries"` + RepairCalls uint64 `json:"repair_calls"` + Usage ObservedTokenUsage `json:"usage"` +} +``` + +- Attach a group with `Observer: group.Observe`, or use `ComposeObservers` for + diagnostics and multiple groups. No request group field, implicit context + membership, constructor, registry, close, seal, or wait method is needed. +- Composition copies the supplied function slice, skips nil functions, and + invokes the remainder synchronously in supplied order. With no non-nil + functions return nil. Do not deduplicate functions or recover panics; a panic + prevents delivery to later observers. Closures retain their ordinary identity. +- Groups enroll a run when any ordinary event for that run is observed. The + first terminal event seen for that identity contributes counters and usage + exactly once, including when it is the first observed event. Repeated delivery + must not change totals or regress terminal coverage. Handling contradictory + caller-fabricated events is not a new public validation contract. +- Summary counters and token subtotals use terminal observations only. Pending + runs contribute no invented attempt counts or known zero usage. Members without + terminals remain identifiable. `TerminalCoverageComplete` says only that every + currently observed member has a terminal observation; it says nothing about + future members or application completion. +- For each token field, sum known terminal values; `Known` is the OR of terminal + `Known` flags and `Complete` is the AND of terminal `Complete` flags. These flags + describe received terminal subtotals; missing terminal coverage is separate. + If members exist but none has a terminal, usage is unknown/incomplete. An empty + group has complete terminal coverage and known, complete zero totals. Preserve + explicit provider zero and existing per-run failure-path usage semantics. +- Snapshots are detached and internally consistent under concurrent observation + and reads. There is no promised member ordering; tests compare by identity. + The mutex-bearing group must not be copied after first use, as with ordinary + synchronized Go values. Overlapping group totals are not generally additive. + +## Internal Implementation Decisions + +### Immutable History And Execution-Owned Messages + +Put the internal immutable exchange value and history operations in +`internal/domain/exchange.go`. The public handle privately references this value; +internal packages never import the root facade. Retain only history and the two +run identities, not engine owners, definitions, requests, credentials, validation +plans, results, admission leases, or observer/callback closures. + +Use a simple immutable parent-plus-turn representation: a parent exchange, +detached new-turn messages, the final assistant message, and a cached total +message count. Initial exchanges have no parent. New exchanges retain only their +new suffix rather than another flat copy of all ancestors. Flatten iteratively +into one correctly sized, caller/execution-owned destination when preparing a +provider request or returning details; do not recurse through arbitrary depth. +Reuse message/cache-control clone helpers at mutable boundaries. This is an +internal choice, not a public allocation or asymptotic guarantee. Avoid unrelated +history caches, ropes, eviction, limits, or pooling. + +Prepare the new prompt normally, then assemble history, new definition messages, +and appended messages in the accepted order before hashing. Captured history +is not normalized or rendered again. Save capture/parent state separately in the +private prepared operation/payload. Prepared metadata contains `ParentRunID` and +the already assembled messages, not the exchange capability or capture settings. +Pass the private capture state through the shared execution function. After +successful final validation processing, capture the new-turn suffix from the +executed snapshot and the exact final `RawOutput`; failed structural validation +is still a completed result. Do not capture internal repair instructions or +intermediate candidates. On an operational error returning no result, create no +exchange. Do not change underlying execution cancellation or partial-result +rules to recover an assistant message. + +### Coordinator Placement And Boundary Adapters + +Put the loop in `internal/usecase/coordinate.go`, not in root business logic. +Use one small internal function generic over request/result value types, with +typed callback and execution functions and a `*domain.Exchange` current state. +Its internal turn/termination values contain only the fields projected by the +public API. This lets the facade inject ordinary `Engine.Run` and the application +callback without importing facade types internally or converting an entire run +result to domain and back just for delivery. No generic public API is added. + +The execution adapter accepts `(context, currentExchange, selectedRequest)` and +returns `(completedResult, nextExchange, error)`. It creates a fresh public +exchange wrapper, copies the request, supplies the current history and composed +observer, and calls `e.Run`. Obtain the next internal exchange before delivering +the result to application code. The callback adapter projects a fresh public +wrapper and the current result into `ConversationTurn`. The shared loop does not +inspect results, interpret output, or access generation/capacity directly. + +Use this exact progression: + +1. With no completed response pending initially, check context before callback. +2. Invoke `Next` with the current exchange, pending result if any, and remaining + budget. Invocation marks that pending result delivered. +3. Check context again; cancellation wins over the callback's action/error. + Otherwise return a callback error unchanged, stop on nil request, or continue. +4. If a request was selected with no remaining budget, return exhaustion before + converting/preparing that request. Otherwise check context before execution, + consume one start from the budget, and call the execution adapter. +5. Return an execution error unchanged; retain no partial output. On a completed + result, update the private current exchange and repeat from step 1. + +At step 1, cancellation with a pending new completed response returns that +single response in `Undelivered`; cancellation before the initial callback +returns none. Callback errors and cancellation observed after invocation return +no undelivered response. Panics propagate naturally without recovery. These +context checks do not forcibly interrupt callbacks or eliminate races. + +Calling ordinary `Run` per turn ensures its logical admission and terminal +observation finish before `Next`. Do not wrap the whole coordinator in an +admission lease or an execution recorder. Nested calls remain independent. + +### Group Collector Placement And Cost + +Implement the collector in `internal/observation/group.go`, using domain events +and summary values. The root group is a thin adapter with private internal state; +root summary/member types are independent public declarations, not aliases. +Store a map keyed by run identity, a terminal-seen bit per member, and aggregate +terminal counters/usage under one mutex. Do not retain full event histories, +provider content, or per-attempt arrays. Event updates use map lookup rather than +rescanning all members. Snapshot copies the map's members and totals under the +lock without invoking callbacks. Observer composition is also internal logic +with thin root conversion; do not round-trip every event through both public and +domain representations simply to fan out callbacks. + +## Stage 1 — Implement Immutable Exchange Storage + +**Scope:** internal history value and ownership only. + +1. Add the internal exchange representation and construction/flattening helpers + described above. Keep fields private and expose only the internal accessors + needed for run identities, message count, capture, and detached details. +2. Clone only new mutable suffix data when capturing; safely share immutable + parents. Preserve all role, content, empty-content, and cache metadata values. +3. Add focused internal domain tests for initial history, a second turn, two + independent branches, and mutation of source messages/cache control and + returned copies. Include a many-turn flattening case without asserting a + specific private layout or allocation count. + +**Validation:** `go test ./internal/domain` when the package is buildable. +These tests own focused history behavior; the final suite verifies its assembly. + +## Stage 2 — Add Public Handles And History-Aware Preparation + +**Scope:** facade request conversion and the shared preparation path; generation +capture is the next stage. + +1. Add `Exchange`, `ExchangeDetails`, their accessor and opaque formatting, the + request fields, and the prepared/result/observation ancestry fields selected + above. Add private domain request and prepared-operation capture state. +2. Extend `toDomainRunRequest` to translate a valid handle and capture choice. + Reject non-nil empty handles as invalid requests; do not impose engine binding + on exchanges. Preserve existing appended-message validation and copying. +3. Update completion to prepend captured messages before newly rendered messages + and caller suffixes. Compute hashes only after final assembly, leaving the + follow-up's settings, session, profile selection, and schema rules ordinary. +4. Update `PreparedRun` conversion and JSON behavior for optional `ParentRunID`. + Keep capture/private history references out of returned prepared details. + `ValidateRequest` inherits this through its existing preparation delegation. +5. Extend preparation contract coverage to assert exact order, roles, cache + metadata, hash sensitivity to prior history, and unchanged definition hashes. + Check nil/empty handle behavior and that preparation performs no model work. + +**Validation:** focused root preparation/JSON tests and affected use-case tests. +Do not add a separate preparation or validation algorithm for continuation. + +## Stage 3 — Capture Ordinary And Prepared Executions + +**Scope:** completed-result creation and prepared payload lifetime. + +1. Carry capture/parent state through `preparedOperation` and + `preparedExecutionPayload` to the shared `executePreparedRun` path. Keep it + out of the retained details object so discard can release execution-only + references. Preserve claim ownership, validation-plan freezing, direct-key + clearing, observer identity, and retry budgets. +2. Create the exchange from the request actually executed and exact final response + after the ordinary execution has a result. Initial capture is opt-in; + continuation always captures. Attach it and `ParentRunID` in result conversion. +3. Capture final failed validation/repair exhaustion as well as valid results. + On generation, validation-operation, or repair-generation errors returning no + result, create no exchange and leave the source exchange reusable. +4. Keep `RunResult.Exchange` excluded from ordinary JSON/YAML and preserve existing + result timing marshal/unmarshal behavior. Details are the explicit content + encoding path. The exchange's private state stores no execution target, + credential, result archive, callback, or lease. +5. Add focused tests for default no-capture, ordinary capture, prepared capture, + discarded/failed-claim silence, final repaired content without repair chatter, + failed validation capture, and operational-error absence of a new exchange. + Reuse existing prepared lifecycle and mutating-client fixtures. + +**Validation:** targeted root capture, prepared-execution, and repair tests plus +`go test ./internal/usecase` as the touched package becomes coherent. + +## Stage 4 — Propagate Ancestry Through Ordinary Observations + +**Scope:** immediate-parent metadata without accounting changes. + +1. Add parent metadata to the internal recorder's event construction and the root + event projection. Set it before ordinary start delivery and before a prepared + start after successful claim. Preserve it when selection identities update. + A small internal setter before `Start` is sufficient; do not add context-based + ancestry inheritance or new run IDs. +2. Propagate parent identity through start, attempt, retry-delay, and terminal + events. Initial runs and unrelated nested calls keep an empty parent. Preserve + terminal error classification and public error identities. +3. Cover observed ordinary/prepared continuation, preparation silence, operational + failure after continuation selection, and an independent nested call using + existing observation fixtures. Verify no history/content enters events. + +**Validation:** focused observation and prepared-observation contract tests and +`go test ./internal/observation ./internal/generation`. + +## Stage 5 — Verify Independent Continuation At The Public Boundary + +**Scope:** assembled continuation behavior before adding coordination. + +1. Add an external-package contract test that captures an initial exchange and + executes two branches, then continues one branch again. Assert exact outgoing + messages and immediate ancestry without changing the initial or sibling data. +2. Continue the exchange through another engine, including a scoped view. Select + different follow-up prompt/profile/output settings and session rules, and + verify that neither previous settings nor original direct credentials leak + into the new execution. Use synthetic credentials, not real environment data. +3. Remove or alter original artifact files after initial capture; continuation + still uses captured history. New follow-up inputs retain ordinary reads and + errors. Exercise prepared follow-up freezing separately from exchange reuse. +4. Mutate details and received result fields and use a mutating injected client + to prove ownership. Encode details generically and check exact final assistant + content, including empty/whitespace content when the output contract allows it. +5. Verify overlapping continuation runs can share the source exchange under the + race detector. Compare behavior, not a required scheduling order. + +**Validation:** focused root continuation/capture tests under `-race`. This stage +must exercise ordinary shared preparation/execution rather than a mock history +reconstruction implemented by the test itself. + +## Stage 6 — Implement The Internal Sequential Coordinator + +**Scope:** the small generic loop and its deterministic state-machine tests. + +1. Add the internal loop and turn/termination values described above, injecting + typed callback and run functions. Do not add a generic workflow framework or + a new generation collaborator. +2. Implement initial callback delivery, turn-start consumption, final-response + delivery, explicit stop versus exhaustion, and first-error termination. +3. Implement context checks and the single undelivered-response slot exactly as + specified. Do not prepare an exhausted request or replay delivered work. +4. Unit-test zero turns, stop on last response versus request for another turn, + execution and callback errors preserving `errors.Is`/`errors.As`, cancellation + before initial callback, cancellation withholding a completed result, and + cancellation/error after callback invocation. Use deterministic hooks, not + sleeps. Add one panic propagation check without terminal-delivery promises. + +**Validation:** focused `go test ./internal/usecase -run Coordinate` (name tests +accordingly). The loop tests use cheap execution functions; provider behavior +remains covered by integration tests. + +## Stage 7 — Expose Coordination Through The Facade + +**Scope:** public types, argument checks, and ordinary-execution adapters. + +1. Add the selected coordinator types and `Engine.Coordinate`, with GoDoc for + callback state, budgets, cancellation, errors, and normal outcomes. +2. Validate coordinator arguments without looking up old resources. Project + callback state through fresh public handle wrappers. Preserve current private + exchange state independently of any callback mutation. +3. Supply the run adapter using `Engine.Run` with the receiving engine/view. + Set current history on a value copy; the coordinator does not mutate the + callback's `RunRequest`. Never add enclosing admission or an observer recorder. +4. Wire its observer selection to composition in Stage 9; do not build a second + temporary observer mechanism to make this stage independently complete. +5. Add basic public tests for invalid arguments, zero-budget initial stop, + ordinary continuation, callback error identity, and a callback modifying the + result/handle it received without changing subsequent retained history. + +**Validation:** focused coordinator tests when wiring is buildable. Carry the +explicit observer-composition dependency forward, without an intermediate gate. + +## Stage 8 — Verify Coordinator Capacity And Cancellation Boundaries + +**Scope:** the subtle assembled execution lifecycle. + +1. Configure one backend with concurrency one and waiting capacity zero. Have the + callback make a same-engine/backend validation run after receiving a turn. + Assert it succeeds, proving the logical admission lease as well as the active + permit was released. Use ordinary admission, not test-only capacity bypasses. +2. Deliver a structurally invalid completed response normally, including one + whose structural repairs were exhausted. The application callback decides + whether to stop or select a new request; PromptKit adds no acceptance meaning. +3. Exercise operational failure stopping one coordinator while a sibling exchange + remains usable. Verify typed provider/capacity errors cross unchanged. +4. Trigger cancellation after a completed run but before callback delivery using + a terminal observer; retain its result/exchange once as undelivered. Separately + cancel inside the callback and assert no replay or undelivered duplicate. +5. Check that internal retry/repair attempts create only one delivery for that + continuation and do not consume extra turns. Reuse existing deterministic + HTTP/repair fixtures, not live providers. + +**Validation:** focused root coordination tests under `-race`, with bounded test +contexts as failure guards, not timing-dependent synchronization. + +## Stage 9 — Add Observer Composition + +**Scope:** fan-out helper and final coordinator-observer wiring. + +1. Implement internal synchronous fan-out over a copied function slice and the + root `ComposeObservers` wrapper. Keep public/domain adapters proportionate; + a small internal generic callback-composition helper can operate directly on + either event value type without repeated conversion. +2. Honor nil filtering, supplied order, no function deduplication, and ordinary + panic propagation. The helper holds no lock while invoking application code. +3. Complete coordinator observer composition, selecting each request observer + before the coordinator observer. Do not change original callback request + values. Callback-initiated calls remain explicitly selected independently. +4. Test nil/empty composition, caller mutation of the original observer slice, + ordered delivery, early panic, and simultaneous independent executions. Do + not promise stronger callback ordering across different runs. + +**Validation:** focused composition and coordinator observer tests, including +the root observation-boundary tests. + +## Stage 10 — Implement Terminal-Based Group Aggregation + +**Scope:** internal collector, usage semantics, and concurrent snapshots. + +1. Add domain summary/member values and the zero-ready internal group collector + under `internal/observation`. Implement map enrollment, terminal deduplication, + incremental terminal counters, and detached snapshots under one mutex. +2. Implement the selected known/completeness and coverage rules for all five + token fields. Do not sum `RunResult.Usage`, reconstruct usage from attempt + events, or convert missing terminals into zero evidence. +3. Cover empty groups, members without terminals, terminal-only enrollment, + duplicate terminal and repeated ordinary events, explicit zeros, wholly + unknown fields, partially known terminal usage, and terminal failures retaining + earlier completed usage. Use small event values rather than an HTTP test for + every arithmetic combination. +4. Concurrently observe independent runs and read snapshots, checking each + snapshot's counts, coverage, and member identities agree. Mutate a returned + snapshot and prove later snapshots are independent. Do not require snapshot + member ordering or introspect locks. + +**Validation:** `go test -race ./internal/observation` with focused new group +tests. No background worker, callback invocation, or collector lifecycle is added. + +## Stage 11 — Expose Groups And Verify Explicit Membership + +**Scope:** thin public collector adapter and assembled accounting. + +1. Add the public group/member/summary types and root event/summary conversions. + A private zero-ready internal group field keeps the public zero value usable. + Document synchronization, snapshot ownership, and no-copy-after-use semantics. +2. Exercise `group.Observe` alone and composed with a diagnostic observer and a + second group. Cover ordinary runs without capture, captured runs, prepared + execution, coordinator continuations, and separately selected callback calls. +3. Assert preparation/discard/failed claim creates no membership. The initial + exchange is not automatically enrolled by coordination; unselected nested + calls remain absent. Parent metadata and group membership remain independent. +4. In one assembled scenario, select initial A and branches B/C, plus a validator + into overlapping groups. Include a built-in transient retry, structural + repair, and a failure after completed usage. Check counters and known subtotals + once per identity, including replay usage reported by the provider for B/C. +5. A composed observer panic may leave an observed member without a terminal. + Verify snapshots remain usable, expose missing coverage, and do not manufacture + completion. Snapshotting inside an observer is safe because group locks are + not held while calling other observers. + +**Validation:** focused root group/coordinator/observation tests under `-race`. +Reuse existing reliable-execution fixtures and avoid duplicating their complete +retry/repair matrices. + +## Stage 12 — Demonstrate The Consumer Workflows + +**Scope:** practical API use and one maintained offline example. + +1. Extend `examples/go-library/observe` and its semantic assertions to demonstrate + captured proposal history, a coordinator follow-up, an application-owned + assessment call, and explicit group selection alongside diagnostic observation. + Keep its deterministic transport and existing retry/usage protections; no paid + calls or real credentials are needed. +2. The example's assessment remains application policy, not a library validator + hook. Use a closure to retain assessed state, and show stop versus exhausted + budget without returning an application verdict from PromptKit. +3. Add concise consumer-guide examples for direct branching and prepared capture, + plus both roadmap sketches translated to the selected callback shape. The + analysis sketch records a new result then asks another question or returns nil. + The Audita-shaped sketch uses existing findings initially, assesses each new + result, and either returns nil or an ordinary follow-up request. Domain-specific + functions remain illustrative caller code. +4. Show group use with ordinary/prepared requests and `CoordinateRequest.Observer`, + including explicit selection for a nested validator and an independent group + summary. Demonstrate checking structural validation before schema-specific + decoding; stop/exhaustion does not imply content acceptance. + +**Validation:** `go test ./examples/go-library/...` and affected public example +tests. Review snippets against actual declarations; full copyable setup stays in +the maintained example rather than duplicated in prose. + +## Stage 13 — Finalize Contracts, Documentation, And Sprint Validation + +**Scope:** complete the target and remove sprint-only rough edges. + +1. Review every new public declaration and GoDoc against this plan and the feature + roadmap. Confirm ordinary APIs still use existing error, timing, prepared-claim, + credential, namespace, usage, and cancellation behavior. Verify result/details + serialization and constant opaque formatting without introducing a restartable + exchange format or redacting caller-authored message content. +2. Update the canonical consumer guide, `doc.go` where orientation needs it, and + affected internal runner, observation, and component-overview documents. + Document capacity release for conversation callbacks separately from ordinary + per-attempt observer callback constraints. Configuration and integration docs + need only changes necessary to describe the implemented message composition; + ancestry is not an upstream wire field or configuration setting. +3. Remove temporary helpers, placeholder implementations, unused experimental + API choices, and intermediate compile/test failures. Do not remove unrelated + planned/deferred work or publish a release in this sprint. Release-note/version + selection and roadmap retirement can follow during release preparation. +4. With the newest locally available Go toolchain selected as the development + guide prescribes, format changed Go files, then run: + + ```sh + go test -race ./... + go vet ./... + git diff --check + ``` + + The full suite includes the maintained examples; do not add a redundant + separate build or provider connectivity gate. Check added/changed local + documentation links, using the existing tooling where practical rather than + introducing another release validator. +5. Review the complete diff and Git status for unintended files or credentials. + Confirm no new dependency, workspace override, configuration parser, provider + parameter, or strengthened restriction/guarantee slipped into the work. + Commit completed units with plain-English messages under the agent policy. + +**Completion evidence:** the implemented facade supports ordinary/prepared +capture, cross-engine independent continuations, coordinator callbacks after +admission release, cancellation's single undelivered response, and independent +observer-based groups. Public behavior tests cover those boundaries and terminal +usage completeness; the complete offline race suite and vet pass. No product or +API decisions are deferred to an implementation stage.