Files
promptkit/docs/roadmap/implementation.md

648 lines
36 KiB
Markdown
Raw Normal View History

# 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
**Status:** Complete.
**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
**Status:** Complete. Capture state still needs to be carried through execution.
**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
**Status:** Complete.
**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
**Status:** Complete.
**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
**Status:** Complete.
**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
**Status:** Complete.
**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
**Status:** Complete; public tests await the observer composition wiring in Stage 9.
**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.