Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@
## SEO and Component Usage
- Prefer Blazor components over raw HTML to enhance maintainability and reusability.
- Implement SEO and GEO optimization support by using semantic headings (H1, H2) while minimizing direct HTML usage.
- Prioritize root-cause fixes over brute-force styling overrides and adhere to global Blazor/Fluent UI conventions.
- Prioritize root-cause fixes over brute-force styling overrides and adhere to global Blazor/Fluent UI conventions. Verify behavior against current component rendering.
- Use Blazor/Fluent component properties over CSS for layout/styling because CSS is harder and more brittle to maintain.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,28 @@ The Playbook *concept* and a Playbook *run* are persisted as two separate kinds
See `docs/governance/playbook-workflow-types.md` ("Persistence: Playbook Catalog vs. Playbook
Execution") for the full entity model.

### Repeatability: Rerun / Recall / Replay

Every `Run*PlaybookCommand` accepts a `PlaybookReplayMode` (from
`Goodtocode.Agents.Playbook.Execution`) plus an optional `SourceExecutionId`:

- **Rerun** (default) — a fresh execution: Collect, Evaluate, and Record all run against new
input. This is the only mode available on a Playbook's first execution.
- **Recall** — re-renders the prior execution's Finding with no new Collect or Evaluate work at
all, even if the live data source has since changed. Only Record re-runs, against the recalled
Finding.
- **Replay** — reuses the prior execution's Evidence (skips Collect) but re-runs Evaluate and
Record, to verify the governed result reproduces exactly from the same Evidence.

Each `Rerun` persists the stage's typed Evidence/Finding as JSON
(`PlaybookExecutionEntity.EvidenceJson`/`FindingJson`) so a later Recall/Replay request can
reconstruct them. The 3 Blazor Playbook pages expose "Rerun", "Recall", and "Replay" controls
(with inline explanations of what each does) next to the latest execution's result; selecting
one re-renders the same result panel in place (no before/after comparison view) — see
`docs/governance/playbook-workflow-types.md` ("Repeatability: Rerun / Recall / Replay") for the
full mechanism, including how the SQL Statistics (`PlaybookExecutor`) and Taxonomy/Essay (MAF
graph) workflows each implement it.

These three Playbooks are exposed both as REST endpoints (`api/v{version}/my/playbooks/{sql-statistics,taxonomy,essay}`
to run, `api/v{version}/playbooks` to manage the catalog) and as Blazor pages under `/playbooks/*`
in Presentation.Web, in addition to being proven through `Tests.Integration` — see each feature
Expand Down
53 changes: 53 additions & 0 deletions docs/governance/playbook-workflow-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,59 @@ answers: "what does this Playbook's Evaluate step always do?" (`PlaybookStepEnti
versus "what did this Playbook's Evaluate step produce the last time someone ran it?"
(`PlaybookExecutionEntity.EvaluateOutput`).

## Repeatability: Rerun / Recall / Replay

The Repeatability pillar of governance is made directly actionable through
`Goodtocode.Agents.Playbook.Execution.PlaybookReplayMode`, which every `Run*PlaybookCommand`
(`RunSqlStatisticsPlaybookCommand`, `RunTaxonomyPlaybookCommand`, `RunEssayPlaybookCommand`)
accepts alongside an optional `SourceExecutionId`:

| Mode | Collect | Evaluate | Record | Use case |
|------|---------|----------|--------|----------|
| `Rerun` (default) | Runs | Runs | Runs | Normal execution; also the only mode on a Playbook's first run. |
| `Recall` | Skipped | Skipped | Re-renders the prior Finding | "Show me exactly what happened last time," including after live data has since changed. |
| `Replay` | Skipped (reuses prior Evidence) | Re-runs | Runs | "Prove this Evaluate/Record logic reproduces the governed result from the same Evidence," without re-collecting from a (possibly now-different) live source. |

**How prior evidence/finding is persisted and resolved.** Every `Rerun` persists the stage's
typed `TEvidence` and `TFinding` as `PlaybookExecutionEntity.EvidenceJson`/`FindingJson`
(`System.Text.Json` serialized), in addition to the existing plain-string
`CollectOutput`/`EvaluateOutput`. When a request arrives with `ReplayMode` of `Recall` or
`Replay`, the handler calls the shared `PlaybookReplaySourceResolver`
(`Core.Application/Playbooks/Persistence`) to look up the prior `PlaybookExecutionEntity` — by
`SourceExecutionId` if supplied, otherwise the caller's most recent execution of that Playbook —
deserializes `EvidenceJson`/`FindingJson` back into `TEvidence`/`TFinding`, and constructs a
`PlaybookReplayContext<TEvidence, TFinding>` to pass to the runner. A prior execution that
predates this feature (no `EvidenceJson`/`FindingJson` persisted) cannot be recalled or replayed
and raises a `CustomConflictException`.

**Two different replay mechanisms, same contract.** The three example workflows reach
Recall/Replay through two different code paths, both driven by the same
`PlaybookReplayContext<TEvidence, TFinding>`:

- **SQL Statistics** (no MAF) calls the package's own replay-aware
`PlaybookExecutor<...>.ExecuteAsync(definition, input, replayContext, ct, recorder)` overload
directly — the package itself understands how to skip Collect/Evaluate against a supplied
replay context.
- **Taxonomy and Essay** (MAF 3-node graph) use a repo-owned
`PlaybookWorkflowGraphExecutor<...>.ExecuteAsync` that branches on `replayContext?.Mode` before
touching the MAF graph at all: `Rerun` builds and runs the full `WorkflowBuilder` graph as
before; `Recall`/`Replay` bypass the MAF graph entirely and call only the remaining stage
tool(s) directly (`Recall` → `IRecordStepTool<,>.RecordAsync` against the prior Finding;
`Replay` → `IEvaluateStepTool<,>.EvaluateAsync` then `IRecordStepTool<,>.RecordAsync` against
the prior Evidence), while still invoking the same `IPlaybookStepActivityRecorder` calls the
graph nodes would have made, so governance capture parity is preserved even though the MAF
graph itself never runs for those two modes.

**UI surface.** Each of the 3 Playbook pages
(`Presentation.Web/Features/Playbooks/*PlaybookPage.razor`) shows "Rerun", "Recall", and "Replay"
controls (`PlaybookReplayControls.razor`) alongside the latest execution's result panel, each with
an inline explanation of what it does so the distinction is clear without reading this document.
Selecting any control re-invokes the same page's run command with the corresponding `ReplayMode`
and the latest execution's id as `SourceExecutionId` (ignored server-side for `Rerun`), using the
latest execution's own `CollectInput` rather than requiring the user to retype it. The result
panel re-renders in place with the rerun/recalled/replayed result — there is no before/after
comparison view by design, to keep the UI simple for a quick-start template.

## Unified Collect / Evaluate / Record Shape

All three example workflows deliberately follow one common input/criteria/output pattern, so a
Expand Down
25 changes: 21 additions & 4 deletions docs/product/features/feature-playbook-essay.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,31 @@ current governance gap (every score is still captured, attributed, and auditable

## UI Changes
The `EssayPlaybookPage` (`Presentation.Web/Features/Playbooks`) runs this Playbook from an essay
text input, then renders the shared `PlaybookResultPanel`: a Collect/Evaluate/Record toggle, a
summary card, and a step-detail panel. The step-detail panel fetches this Playbook's catalog
entry (`GetPlaybookByKeyAsync("essay")`) to show each step's persisted `Description` and
text input, then renders the shared `PlaybookResultPanel`: "Rerun"/"Recall"/"Replay"
repeatability controls (`PlaybookReplayControls`, each with an inline explanation of what it
does), a Collect/Evaluate/Record toggle, a summary card, and a
step-detail panel. The step-detail panel fetches this Playbook's catalog entry
(`GetPlaybookByKeyAsync("essay")`) to show each step's persisted `Description` and
`ActionDefinition` (the rubric-scoring prompt) alongside that run's dynamic input/output.

## Repeatability (Recall / Replay)
Since Collect and Record are deterministic but Evaluate is agentic, Replay is the most useful
mode here: it re-runs the rubric-scoring prompt against the exact same collected essay evidence,
proving the Evaluate prompt reproduces an equivalent scorecard without needing the (free)
deterministic Collect stage to run again. Recall skips both Collect and Evaluate, re-rendering
the prior scorecard at zero cost. Like Taxonomy, `EssayEvaluationRunner` passes the
`PlaybookReplayContext<EssayEvidence, EssayScorecardFinding>` through to the repo-owned
`PlaybookWorkflowGraphExecutor<...>`, which bypasses the MAF 3-node graph for Recall/Replay and
calls only the remaining stage tool(s) directly against the recalled Evidence/Finding, while
still emitting the same governance activity-recorder calls the graph nodes would have made. See
`docs/governance/playbook-workflow-types.md` ("Repeatability: Rerun / Recall / Replay") for the
full mechanism shared across all three Playbooks.

## API Changes
- `POST api/v{version}/my/playbooks/essay` (`RunMyEssayPlaybookCommand`) executes the Playbook
and persists a `PlaybookExecutionEntity` for the run.
and persists a `PlaybookExecutionEntity` for the run. Accepts an optional `ReplayMode`
(`Rerun`/`Recall`/`Replay`, default `Rerun`) and `SourceExecutionId` to recall or replay a prior
execution instead of collecting fresh input.
- The catalog itself is queryable/creatable/updatable/deletable via the shared, unsecured
`api/v{version}/playbooks` CRUD endpoints (`PlaybookCatalogEndpoints`): list, get by id, get by
key, create, update general info, update one CER step, delete.
Expand Down
29 changes: 21 additions & 8 deletions docs/product/features/feature-playbook-sql-statistics.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,29 @@ are inclusive upper bounds.

## UI Changes
The `SqlStatisticsPlaybookPage` (`Presentation.Web/Features/Playbooks`) runs this Playbook from a
database-name input, then renders the shared `PlaybookResultPanel`: a Collect/Evaluate/Record
toggle, a summary card, and a step-detail panel. The step-detail panel fetches this Playbook's
catalog entry (`GetPlaybookByKeyAsync("sql-statistics")`) to show each step's persisted
`Description` and `ActionDefinition` (the actual statistics query) alongside that run's dynamic
input/output.
database-name input, then renders the shared `PlaybookResultPanel`: "Rerun"/"Recall"/"Replay"
repeatability controls (`PlaybookReplayControls`, each with an inline explanation of what it
does), a Collect/Evaluate/Record toggle, a summary card, and a
step-detail panel. The step-detail panel fetches this Playbook's catalog entry
(`GetPlaybookByKeyAsync("sql-statistics")`) to show each step's persisted `Description` and
`ActionDefinition` (the actual statistics query) alongside that run's dynamic input/output.

## Repeatability (Recall / Replay)
Because every stage of this Playbook is deterministic, Recall and Replay are low-stakes by
design — re-evaluating or re-recording the same Evidence always reproduces the same Finding. This
Playbook exercises the package's own replay path directly: `SqlStatisticsClassificationRunner`
calls `PlaybookExecutor<...>.ExecuteAsync(definition, input, replayContext, ct, recorder)` when a
`PlaybookReplayContext<SqlDatabaseStatisticsEvidence, SqlDatabaseSizeFinding>` is supplied (built
from the prior run's persisted `EvidenceJson`/`FindingJson`), rather than going through a
repo-owned graph executor like the other two Playbooks. See
`docs/governance/playbook-workflow-types.md` ("Repeatability: Rerun / Recall / Replay") for the
full mechanism shared across all three Playbooks.

## API Changes
- `POST api/v{version}/my/playbooks/sql-statistics` (`RunMySqlStatisticsPlaybookCommand`) executes
the Playbook and persists a `PlaybookExecutionEntity` for the run.
the Playbook and persists a `PlaybookExecutionEntity` for the run. Accepts an optional
`ReplayMode` (`Rerun`/`Recall`/`Replay`, default `Rerun`) and `SourceExecutionId` to recall or
replay a prior execution instead of collecting fresh input.
- The catalog itself is queryable/creatable/updatable/deletable via the shared, unsecured
`api/v{version}/playbooks` CRUD endpoints (`PlaybookCatalogEndpoints`): list, get by id, get by
key, create, update general info, update one CER step, delete.
Expand Down Expand Up @@ -173,8 +187,7 @@ size metrics are operational metadata, not regulated or personal data.
versioned bands before relying on this in production.

## Out Of Scope
- Exposing this Playbook via a chat tool, REST endpoint, or UI page.
- Persisting results by default (persistence is opt-in via `SavePlaybookMaterializationCommand`).
- Exposing this Playbook via a chat tool.
- Any LLM-driven reasoning — that is intentionally the role of the other two example Playbooks.

## Definition Of Done
Expand Down
26 changes: 22 additions & 4 deletions docs/product/features/feature-playbook-taxonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,33 @@ already enforced; term-extraction *quality* is not yet separately evaluated).

## UI Changes
The `TaxonomyPlaybookPage` (`Presentation.Web/Features/Playbooks`) runs this Playbook from a
free-text input, then renders the shared `PlaybookResultPanel`: a Collect/Evaluate/Record toggle,
a summary card, and a step-detail panel. The step-detail panel fetches this Playbook's catalog
entry (`GetPlaybookByKeyAsync("taxonomy")`) to show each step's persisted `Description` and
free-text input, then renders the shared `PlaybookResultPanel`: "Rerun"/"Recall"/"Replay"
repeatability controls (`PlaybookReplayControls`, each with an inline explanation of what it
does), a Collect/Evaluate/Record toggle, a summary card, and a
step-detail panel. The step-detail panel fetches this Playbook's catalog entry
(`GetPlaybookByKeyAsync("taxonomy")`) to show each step's persisted `Description` and
`ActionDefinition` (the extraction/classification prompts) alongside that run's dynamic
input/output.

## Repeatability (Recall / Replay)
Because every stage of this Playbook is agentic, Recall and Replay matter most here: Recall lets
a user see the exact prior Finding without spending any tokens, and Replay proves the
Evaluate/Record prompts reproduce the same classification from the same (recalled) extracted
terms, without re-running the more token-heavy Collect stage. Unlike SQL Statistics, this
Playbook's `TaxonomyClassificationRunner` does not call the package's `PlaybookExecutor`
directly — it passes the `PlaybookReplayContext<TaxonomyEvidence, TaxonomyFinding>` through to the
repo-owned `PlaybookWorkflowGraphExecutor<...>`, which bypasses the MAF 3-node graph entirely for
Recall/Replay and instead calls only the remaining stage tool(s)
(`IRecordStepTool<,>`/`IEvaluateStepTool<,>`, resolved to `TaxonomyRecordAgentTool`/
`TaxonomyEvaluateAgentTool`) directly against the recalled Evidence/Finding, while still emitting
the same governance activity-recorder calls the graph nodes would have made. See `docs/governance/playbook-workflow-types.md` ("Repeatability: Rerun / Recall / Replay") for
the full mechanism shared across all three Playbooks.

## API Changes
- `POST api/v{version}/my/playbooks/taxonomy` (`RunMyTaxonomyPlaybookCommand`) executes the
Playbook and persists a `PlaybookExecutionEntity` for the run.
Playbook and persists a `PlaybookExecutionEntity` for the run. Accepts an optional `ReplayMode`
(`Rerun`/`Recall`/`Replay`, default `Rerun`) and `SourceExecutionId` to recall or replay a prior
execution instead of collecting fresh input.
- The catalog itself is queryable/creatable/updatable/deletable via the shared, unsecured
`api/v{version}/playbooks` CRUD endpoints (`PlaybookCatalogEndpoints`): list, get by id, get by
key, create, update general info, update one CER step, delete.
Expand Down
4 changes: 3 additions & 1 deletion src/Core.Application/Playbooks/Essay/EvaluateEssayCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ namespace Goodtocode.AgentFramework.Core.Application.Playbooks.Essay;
public interface IEssayEvaluationRunner
{
Task<PlaybookExecutionResult<EssayEvidence, EssayRubricFinding, EssayScorecardMaterialization>> EvaluateAsync(
string essayText, CancellationToken cancellationToken);
string essayText,
CancellationToken cancellationToken,
PlaybookReplayContext<EssayEvidence, EssayRubricFinding>? replayContext = null);
}

/// <summary>
Expand Down
39 changes: 34 additions & 5 deletions src/Core.Application/Playbooks/Essay/RunEssayPlaybookCommand.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
using Goodtocode.AgentFramework.Core.Application.Playbooks.Persistence;
using Goodtocode.Agents.Playbook.Execution;

namespace Goodtocode.AgentFramework.Core.Application.Playbooks.Essay;

/// <summary>
/// API-facing command for running the Essay Rubric Evaluation example playbook directly against
/// raw essay text (no chat-message dependency), projecting the result into the shared
/// <see cref="PlaybookExecutionResultDto"/> shape used by every example playbook's "run" endpoint.
/// Distinct from <see cref="EvaluateEssayCommand"/>, which resolves essay text from an existing
/// chat message.
/// chat message. <see cref="ReplayMode"/> selects the repeatability behavior: <c>Rerun</c>
/// (default) executes fresh against <see cref="EssayText"/>; <c>Recall</c> and <c>Replay</c>
/// ignore <see cref="EssayText"/> and instead rehydrate the prior execution identified by
/// <see cref="SourceExecutionId"/> (or the user's latest execution, if omitted).
/// </summary>
public sealed class RunEssayPlaybookCommand : IRequest<PlaybookExecutionResultDto>
public sealed class RunEssayPlaybookCommand : UserScopedRequest, IRequest<PlaybookExecutionResultDto>
{
public required string EssayText { get; init; }
public PlaybookReplayMode ReplayMode { get; init; } = PlaybookReplayMode.Rerun;
public string? SourceExecutionId { get; init; }
}

public sealed class RunEssayPlaybookCommandHandler(
ISender sender,
IAgentFrameworkContext context,
IEssayEvaluationRunner runner,
EssayStageSummarySelector summarySelector)
: IRequestHandler<RunEssayPlaybookCommand, PlaybookExecutionResultDto>
Expand All @@ -22,9 +31,29 @@ public async Task<PlaybookExecutionResultDto> Handle(RunEssayPlaybookCommand req
{
ArgumentNullException.ThrowIfNull(request);

var result = await runner.EvaluateAsync(request.EssayText, cancellationToken);
var dto = PlaybookExecutionResultDtoFactory.CreateFrom(request.EssayText, result, summarySelector);
await Persistence.PlaybookExecutionPersister.SaveAsync(sender, dto, cancellationToken);
string essayText;
PlaybookReplayContext<EssayEvidence, EssayRubricFinding>? replayContext = null;

if (request.ReplayMode == PlaybookReplayMode.Rerun)
{
essayText = request.EssayText;
}
else
{
var source = await PlaybookReplaySourceResolver.ResolveAsync(
context, "essay", request.UserContext.OwnerId, request.UserContext.TenantId,
request.SourceExecutionId, cancellationToken);

essayText = source.CollectInput;
var priorEvidence = System.Text.Json.JsonSerializer.Deserialize<EssayEvidence>(source.EvidenceJson!)!;
var priorFinding = System.Text.Json.JsonSerializer.Deserialize<EssayRubricFinding>(source.FindingJson!)!;
replayContext = new PlaybookReplayContext<EssayEvidence, EssayRubricFinding>(
request.ReplayMode, source.Id.ToString(), priorEvidence, priorFinding);
}

var result = await runner.EvaluateAsync(essayText, cancellationToken, replayContext);
var dto = PlaybookExecutionResultDtoFactory.CreateFrom(essayText, result, summarySelector);
dto.ExecutionId = await Persistence.PlaybookExecutionPersister.SaveAsync(sender, dto, cancellationToken);
return dto;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public static Task<Guid> SaveAsync(ISender sender, PlaybookExecutionResultDto dt
CollectOutput = dto.CollectSummary,
EvaluateOutput = dto.EvaluateSummary,
RecordOutput = dto.RecordSummary,
EvidenceJson = dto.EvidenceJson,
FindingJson = dto.FindingJson,
StartedUtc = dto.StartedUtc,
CompletedUtc = dto.CompletedUtc
}, cancellationToken);
Expand Down
Loading
Loading