Files
promptkit/docs/roadmap/execution-controls.md

276 lines
16 KiB
Markdown

# Execution Controls For Coordinated Turns
Status: all four design decisions accepted; ready for API and staged
implementation planning from v0.22.0. No code changes are authorized by this
roadmap. Public names and signatures are not yet selected.
## Purpose
Enable applications using `Engine.Coordinate` to select a fresh timeout for each
continuation and retain the exact prepared request before generation. These are
execution capabilities useful for bounded operations and optional diagnostics,
without assumptions about prompt content or application workflows.
[Audita's request](audita-request.md) supplies the motivating requirements.
Audita already uses one child context across explicit preparation and execution,
and retains prepared details for diagnostics. The goal is to make those controls
available during coordinator-owned execution without requiring a downstream
replacement for PromptKit's turn loop.
PromptKit owns history assembly, execution, cancellation propagation, and
observations. Applications select budgets, interpret results, run validators,
schedule branches, and own diagnostic destinations, disclosure policy, and write
failure handling. This work adds no artifact store or reporting framework.
## Current Baseline
[PromptKit v0.22.0](../releases/v0.22.0.md) supplies captured exchanges,
continuation, sequential coordination, and explicit execution groups.
- The [coordinator](../../coordinate.go) attaches the current exchange and composes
request/coordinator observers, then calls `Engine.Run` with the parent context.
Its callback returns a request, not an execution context or prepared handle.
- Ordinary [run execution](../../internal/usecase/runner.go) resolves the target,
obtains logical admission, completes preparation, and generates and validates
the response. Admission is released before the next-turn callback.
- Explicit [prepared execution](../../prepared_execution.go) already supplies
detached `Details()` and executes the retained snapshot. Preparation and
execution are separate calls; preparation does not reserve backend capacity.
- Ordinary [observations](../internal/observation.md) are metadata-only. Ordinary
runs observe preparation failures; split preparation itself emits no execution
events. A claimed prepared handle starts its observation lifecycle at execution.
A timeout around the entire coordinator includes all turns and application
callbacks. Provider timeouts cover a different scope. Neither provides a fresh
whole-operation budget for each selected turn. Separately calling `Prepare`
would duplicate preparation and would not bind the inspected details to the
request actually executed. An exchange cannot supply a failed turn's prepared
request when execution returns no result.
## Target End State
The selected approach is optional per-request controls honored by ordinary `Run`
and therefore by `Coordinate`. The accepted decisions below specify their
lifecycle; the coordinator does not delegate execution to an application executor.
### Optional Operation Timeout
An application can select a duration for each requested continuation. When that
continuation starts, establish a fresh child deadline before preparation and use
it for the entire operation: preparation, admission waiting, retries, generation,
structural validation, and structural repair. Do not restart the budget between
phases or attempts.
Respect parent cancellation and earlier parent deadlines. A local timeout ends
the affected operation and coordination under the existing operational-error
rule; it does not cancel the parent or siblings. Release child-context resources
before returning to the application's next-turn callback. Assessment calls from
that callback retain their own application-selected contexts and budgets.
A completed operation does not become canceled merely because its child context
is cleaned up after execution. Preserve the existing coordinator behavior for
completed responses, parent cancellation before callback delivery, and the single
undelivered response. No automatic replay or new recovery policy is added.
No selected timeout means existing context behavior. This adds no framework-wide
recommended duration or mandatory cap. Additional-turn, transport-retry,
structural-repair, and provider-generation budgets keep their existing meanings.
Cancellation remains cooperative; a deadline does not forcibly terminate
application callbacks or injected collaborators that ignore their context.
### Opt-In Prepared Details Before Generation
Provide explicitly selected access to caller-owned details of the logical request
that will execute, after successful preparation and before its first generation
attempt. Reuse the existing prepared-details representation and credential
redaction where practical. Include assembled captured history, rendered follow-up
messages, caller-appended messages, and ordinary resolved execution metadata.
Use the execution's prepared snapshot rather than rendering or reading inputs
again. Retaining or modifying delivered details must not mutate the execution.
The application can retain them after provider failure or cancellation even when
no ordinary result or exchange is returned. If preparation fails, no complete
snapshot is required. Cancellation before notification does not promise delivery;
follow the context checks in [Decision 3](#3-what-is-the-prepared-details-callback-contract).
This access exposes logical prepared content, not raw HTTP envelopes or internal
structural-repair requests. Keep it separate from metadata-only observers and
execution-group summaries. It is not another next-turn callback: ordinary
execution may already hold backend admission at the notification point.
### Compatibility And Composition
Applications selecting neither capability retain existing behavior. Preserve
conversation assembly, capture, final-turn delivery, branching, and grouping
contracts. Request observers, coordinator observers, and explicitly selected
groups continue receiving ordinary execution facts without duplicate lifecycle
events or counting attempts twice.
Avoid changing the coordinator to an explicit public prepare/run sequence merely
to obtain details: that would change ordinary admission and preparation-failure
observation behavior. Reuse the execution path and its existing ownership
boundaries. Follow the accepted split-preparation and callback semantics below;
do not silently ignore a newly supplied option on one entry point.
## Work From The Baseline
1. **Design the public API.** Write small ordinary-run, coordinated-turn, and
split-preparation usage sketches under the accepted lifecycle below. Specify
omitted/zero/negative timeout handling using ordinary duration conventions,
and express the selected callback timing and cleanup in the public contract.
Choose names and signatures without adding a broader lifecycle-hook framework.
2. **Add operation-budget handling.** Establish and clean up the child scope at
the selected execution boundary. Preserve earlier parent deadlines and public
error identity through facade mapping and terminal observation classification.
Ensure cleanup does not affect the coordinator parent or subsequent callbacks.
3. **Expose the prepared snapshot.** Deliver detached public details from the
existing prepared state before generation. Cover both ordinary execution and
the selected prepared-execution lifecycle without a second preparation pass.
Preserve credential handling and release resources on cancellation or panic.
Avoid snapshot-copy work when the capability is not selected.
4. **Integrate and verify coordination.** Have ordinary execution honor the
selected controls on the callback-returned request under the accepted approach.
Preserve capture and observer composition; verify the next-turn callback runs
with the parent context after admission and child-budget cleanup.
5. **Document and demonstrate.** Update public GoDoc, the
[consumer guide](../consumers/pkg-promptkit.md), relevant internal lifecycle
documentation, and a focused offline example when implementation lands.
Show how applications retain diagnostics on failure and bound independent
assessment calls. Reconcile Audita's scenarios against the final API.
This is an additive runtime feature; no collection-format or ConfigKit changes
are needed. Keep implementation proportional to the two requested controls.
## Validation Targets
Use offline public-boundary tests and existing execution collaborators, with
controlled cancellation and synchronization rather than fragile timing margins.
Cover the consumer request's [acceptance scenarios](audita-request.md#acceptance-scenarios)
and the accepted lifecycle choices:
- Successive turns receive fresh budgets: their combined duration may exceed one
operation budget without exhausting either individual operation.
- For ordinary and coordinated runs, preparation, admission waiting, retries,
validation, and repair share the same operation deadline. Local expiry stops
coordination without canceling parent or sibling work; earlier parent
cancellation/deadlines remain effective.
- Next-turn assessment uses the parent context after release, including another
call to a backend with capacity one. Timeout identity survives public wrapping.
- Prepared details precede generation, contain the assembled continuation, and
remain available after later failure. Mutation of the delivered copy or source
changes after preparation do not change the executed snapshot.
- No duplicate preparation, provider call, notification per internal repair, or
observer lifecycle is introduced. Existing credential redaction remains intact.
- Preparation failure, cancellation around notification, and callback panic follow
the selected delivery rules and do not strand admission or retained resources.
- Split preparation uses its supplied context and retains the selected duration
for `RunPrepared`; idle time does not consume that execution budget. A shared
caller deadline can still bound both calls. Neither discarded nor unsuccessfully
claimed handles start execution notifications.
- The prepared notification receives detached details and the operation context,
has no error return, and runs synchronously within the operation budget.
- Local-budget expiry has a narrow inspectable identity while preserving standard
context matching; parent and provider deadlines are not mislabeled as local.
- Observers/groups retain ordinary facts on success and failure. No-option use,
completed-result delivery, and final-turn behavior remain compatible.
## Accepted Decisions
All four decisions below are accepted. No significant product decisions remain
open. Exact public names, signatures, timeout-value conventions, and the Go
representation of timeout identity remain for API and implementation planning.
### 1. Narrow Execution Controls Or Caller-Delegated Execution?
**Accepted approach:** add optional per-request execution controls honored by
ordinary `Run` and thus by `Coordinate`, including a timeout and
prepared-details callback. This keeps history, capture, admission, errors, and
observers in their existing owners and makes the capability useful outside
coordination. A caller can select different controls on each returned request;
an additional coordinator-wide configuration layer is not needed for the
demonstrated use case.
**Alternative considered, not selected:** let the coordinator delegate a turn
to a caller-provided executor or prepared-execution adapter. This would reuse
Audita's existing adapter and accommodate other execution customizations. It
also requires a larger contract for exchange assembly, capture, observer
composition, handle cleanup, and admission release. The narrow controls meet
the demonstrated need with fewer ownership questions; caller-delegated
execution is not part of this feature.
### 2. What Does A Timeout Mean For Split Preparation And Execution?
**Accepted approach:** bound each `Run` invocation from before preparation. For
`PrepareExecution`, retain the selected duration as an execution control and
apply it when `RunPrepared` starts; preparation itself continues using its
supplied context. Do not retain a live timer across an idle prepared handle.
Explicitly explain that this bounds the remaining execution, not preparation
already done. Callers needing one total budget across both calls can continue
passing the same caller-created deadline context, as Audita does today.
This preserves the distinction between executing now and preparing for later,
while letting a prepared handle carry the same selected execution controls. It
requires clear documentation: the duration alone does not retroactively cover
split preparation. `Prepare` and `ValidateRequest` likewise do not become timed
executions merely because they accept request-shaped input.
**Alternative considered, not selected:** start a budget during
`PrepareExecution` and carry its remaining deadline into `RunPrepared`. This
directly bounds preparation and execution together without caller context
plumbing. It also counts idle time, requires timer/context ownership and
discard semantics, and changes what a prepared handle retains. This is useful
if a consumer needs a whole prepared operation lifetime, but exceeds the
current coordinated-turn requirement.
### 3. What Is The Prepared-Details Callback Contract?
**Accepted approach:** a synchronous notification receiving the operation
context and a detached prepared value, with no error return. Invoke it once
after the snapshot is ready and before generation, only when execution proceeds
to that point. For prepared handles, retain the callback until execution and
clear it on claim/discard like the existing observer; inspection remains
available earlier through `Details()` without triggering the notification.
Check context before notification and again before generation. Include callback
time in the operation budget and retain ordinary cooperative cancellation and
panic propagation, with resource cleanup on unwind. Do not add background delivery
or stronger terminal-event guarantees. The application can retain details and
perform optional writes under its own policy; it need not turn a diagnostic write
failure into an inference failure.
Under the current ordinary run path this notification executes while logical
admission is held. Document that fact rather than promise the next-turn callback's
capacity-one nested-execution behavior. Do not prohibit arbitrary callback work;
applications must account for the held capacity and operation budget. Releasing
and reacquiring admission solely for notification is not necessary for this scope.
**Alternative considered, not selected:** let the callback return an error to
veto generation. This supports mandatory audit persistence or an application
pre-generation check, with ordinary cleanup and error propagation. It also
makes the callback an execution gate and requires failure classification
semantics absent from the optional-diagnostics request. The selected
notification satisfies that request; a generation veto would require a separate
demonstrated need.
### 4. How Precisely Should Local Timeout Errors Be Identified?
**Accepted approach:** preserve standard context matching and provide a narrow,
inspectable operation-timeout identity when this newly selected budget causes
termination. Keep parent cancellation/deadlines and provider-local failures
under their existing identities. The marker must survive public wrapping and
must not be inferred from any arbitrary `DeadlineExceeded` returned by a
collaborator. Choose its Go representation during API design; no new
error-report hierarchy or terminal-event category is implied.
This lets callers distinguish the requested local budget from an earlier parent
or provider deadline without retaining the internal child context. Define cause
selection around races using the child context's recorded cause; do not promise
perfect ordering of independently simultaneous cancellations.
**Alternative considered, not selected:** return only existing context errors
and let callers inspect their parent context. This is smaller and often
sufficient when the parent remains active. However, `DeadlineExceeded` alone
may also originate in a provider timeout, and a parent may become canceled
before the caller inspects it. That weaker attribution does not meet the
selected distinction as directly as a narrow operation-timeout identity.