Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace DotPilot.Runtime.Features.RuntimeFoundation;
public sealed class RuntimeFoundationCatalog : IRuntimeFoundationCatalog
{
private const string EpicSummary =
"Issue #12 is staged into isolated contracts, communication, host, and orchestration slices so the Uno workbench can stay presentation-only.";
"Issue #12 builds on the #11 foundation contracts and stages host and orchestration work behind isolated runtime slices so the Uno app stays presentation-only.";
private const string DeterministicProbePrompt =
"Summarize the runtime foundation readiness for a local-first session that may require approval.";
private const string DeterministicClientStatusSummary = "Always available for in-repo and CI validation.";
Expand Down
4 changes: 2 additions & 2 deletions DotPilot.Tests/ControlPlaneDomainContractsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ private static ControlPlaneDomainEnvelope CreateEnvelope()
{
Id = WorkspaceId.New(),
Name = "dotPilot",
RootPath = "/Users/ksemenenko/Developer/dotPilot",
BranchName = "codex/issue-22-domain-model",
RootPath = "/repo/dotPilot",
BranchName = "codex/epic-11-foundation-contracts",
};

var codingAgent = new AgentProfileDescriptor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace DotPilot.Tests.Features.ControlPlaneDomain;

public sealed class ControlPlaneIdentifierContractTests
{
[Test]
public void NewlyCreatedIdentifiersUseVersionSevenTokens()
{
IReadOnlyList<string> identifiers =
[
WorkspaceId.New().ToString(),
AgentProfileId.New().ToString(),
SessionId.New().ToString(),
FleetId.New().ToString(),
ProviderId.New().ToString(),
ModelRuntimeId.New().ToString(),
ToolCapabilityId.New().ToString(),
ApprovalId.New().ToString(),
ArtifactId.New().ToString(),
TelemetryRecordId.New().ToString(),
EvaluationId.New().ToString(),
];

identifiers.Should().OnlyContain(identifier => identifier.Length == 32);
identifiers.Should().OnlyContain(identifier => identifier[12] == '7');
}

[Test]
public void DefaultDescriptorsExposeSerializationSafeDefaults()
{
var workspace = new WorkspaceDescriptor();
var agent = new AgentProfileDescriptor();
var fleet = new FleetDescriptor();
var tool = new ToolCapabilityDescriptor();
var provider = new ProviderDescriptor();
var runtime = new ModelRuntimeDescriptor();
var session = new SessionDescriptor();
var approval = new SessionApprovalRecord();
var artifact = new ArtifactDescriptor();
var telemetry = new TelemetryRecord();
var evaluation = new EvaluationRecord();

workspace.Name.Should().BeEmpty();
workspace.RootPath.Should().BeEmpty();
workspace.BranchName.Should().BeEmpty();
agent.Name.Should().BeEmpty();
agent.ToolCapabilityIds.Should().BeEmpty();
agent.Tags.Should().BeEmpty();
fleet.Name.Should().BeEmpty();
fleet.AgentProfileIds.Should().BeEmpty();
tool.Name.Should().BeEmpty();
tool.DisplayName.Should().BeEmpty();
tool.Tags.Should().BeEmpty();
provider.DisplayName.Should().BeEmpty();
provider.CommandName.Should().BeEmpty();
provider.StatusSummary.Should().BeEmpty();
provider.Status.Should().Be(ProviderConnectionStatus.Unavailable);
provider.SupportedToolIds.Should().BeEmpty();
runtime.DisplayName.Should().BeEmpty();
runtime.EngineName.Should().BeEmpty();
runtime.Status.Should().Be(ProviderConnectionStatus.Unavailable);
runtime.SupportedModelFamilies.Should().BeEmpty();
session.Title.Should().BeEmpty();
session.Phase.Should().Be(SessionPhase.Plan);
session.ApprovalState.Should().Be(ApprovalState.NotRequired);
session.AgentProfileIds.Should().BeEmpty();
approval.State.Should().Be(ApprovalState.Pending);
approval.RequestedAction.Should().BeEmpty();
approval.RequestedBy.Should().BeEmpty();
artifact.Name.Should().BeEmpty();
artifact.RelativePath.Should().BeEmpty();
telemetry.Name.Should().BeEmpty();
telemetry.Summary.Should().BeEmpty();
evaluation.Outcome.Should().Be(EvaluationOutcome.NeedsReview);
evaluation.Summary.Should().BeEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace DotPilot.Tests.Features.RuntimeCommunication;

public sealed class DeterministicAgentRuntimeClientContractTests
{
private const string ApprovalPrompt = "Execute the local-first flow and request approval before changing files.";

[Test]
public async Task ExecuteAsyncReturnsSucceededResultWithoutProblemForPlanMode()
{
var client = new DeterministicAgentRuntimeClient();

var result = await client.ExecuteAsync(CreateRequest("Plan the contract foundation rollout.", AgentExecutionMode.Plan), CancellationToken.None);

result.IsSuccess.Should().BeTrue();
result.IsFailed.Should().BeFalse();
result.HasProblem.Should().BeFalse();
result.Value.Should().NotBeNull();
result.Value!.NextPhase.Should().Be(SessionPhase.Plan);
}

[Test]
public async Task ExecuteAsyncTreatsApprovalPauseAsASuccessfulStateTransition()
{
var client = new DeterministicAgentRuntimeClient();

var result = await client.ExecuteAsync(CreateRequest(ApprovalPrompt, AgentExecutionMode.Execute), CancellationToken.None);

result.IsSuccess.Should().BeTrue();
result.HasProblem.Should().BeFalse();
result.Value.Should().NotBeNull();
result.Value!.NextPhase.Should().Be(SessionPhase.Paused);
result.Value.ApprovalState.Should().Be(ApprovalState.Pending);
}

[TestCase(ProviderConnectionStatus.Unavailable, RuntimeCommunicationProblemCode.ProviderUnavailable, 503)]
[TestCase(ProviderConnectionStatus.RequiresAuthentication, RuntimeCommunicationProblemCode.ProviderAuthenticationRequired, 401)]
[TestCase(ProviderConnectionStatus.Misconfigured, RuntimeCommunicationProblemCode.ProviderMisconfigured, 424)]
[TestCase(ProviderConnectionStatus.Outdated, RuntimeCommunicationProblemCode.ProviderOutdated, 412)]
public async Task ExecuteAsyncMapsProviderStatesToTypedProblems(
ProviderConnectionStatus providerStatus,
RuntimeCommunicationProblemCode expectedCode,
int expectedStatusCode)
{
var client = new DeterministicAgentRuntimeClient();

var result = await client.ExecuteAsync(
CreateRequest("Run the provider-independent runtime flow.", AgentExecutionMode.Execute, providerStatus),
CancellationToken.None);

result.IsFailed.Should().BeTrue();
result.HasProblem.Should().BeTrue();
result.Value.Should().BeNull();
result.Problem.Should().NotBeNull();
result.Problem!.HasErrorCode(expectedCode).Should().BeTrue();
result.Problem.StatusCode.Should().Be(expectedStatusCode);
}

[Test]
public async Task ExecuteAsyncReturnsOrchestrationProblemForUnsupportedExecutionModes()
{
var client = new DeterministicAgentRuntimeClient();

var result = await client.ExecuteAsync(
CreateRequest("Use an invalid execution mode.", (AgentExecutionMode)999),
CancellationToken.None);

result.IsFailed.Should().BeTrue();
result.HasProblem.Should().BeTrue();
result.Value.Should().BeNull();
result.Problem.Should().NotBeNull();
result.Problem!.HasErrorCode(RuntimeCommunicationProblemCode.OrchestrationUnavailable).Should().BeTrue();
result.Problem.StatusCode.Should().Be(503);
}

private static AgentTurnRequest CreateRequest(
string prompt,
AgentExecutionMode mode,
ProviderConnectionStatus providerStatus = ProviderConnectionStatus.Available)
{
return new AgentTurnRequest(SessionId.New(), AgentProfileId.New(), prompt, mode, providerStatus);
}
}
4 changes: 2 additions & 2 deletions docs/ADR/ADR-0003-vertical-slices-and-ui-only-uno-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ We will use these architectural defaults for implementation work going forward:
- `DotPilot.Core` for contracts, typed identifiers, and public slice interfaces
- `DotPilot.Runtime` for provider-independent runtime implementations and future host integration seams
3. Feature code must be organized as vertical slices under `Features/<FeatureName>/...`, not as shared horizontal `Services`, `Models`, or `Helpers` buckets.
4. Epic `#12` starts with a `RuntimeFoundation` slice that sequences issues `#22`, `#23`, `#24`, and `#25` behind a stable contract surface before live Orleans or provider integration.
4. Epic `#11` establishes the shared `ControlPlaneDomain` and `RuntimeCommunication` slices, and epic `#12` builds on that foundation for runtime-host and orchestration work.
5. CI-safe agent-flow verification must use a deterministic in-repo runtime client as a first-class implementation of the same public contracts, not a mock or hand-wired test double.
6. Tests that require real `Codex`, `Claude Code`, or `GitHub Copilot` toolchains may run only when the corresponding toolchain is available; their absence must not weaken the provider-independent baseline.

Expand Down Expand Up @@ -81,7 +81,7 @@ CI does not guarantee those toolchains, so the repo would lose an honest agent-f

- The Uno app gets cleaner and stays focused on operator-facing concerns.
- Future slices can land without merging unrelated feature logic into shared buckets.
- Contracts for `#12` become reusable across UI, runtime, and tests.
- Contracts from epic `#11` become reusable across UI, runtime, and tests before epic `#12` begins live runtime integration.
- CI keeps a real provider-independent verification path through the deterministic runtime client.

### Negative
Expand Down
57 changes: 43 additions & 14 deletions docs/Architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture Overview

Goal: give humans and agents a fast map of the active `DotPilot` solution, the current `Uno Platform` shell, the workbench foundation for epic `#13`, the Toolchain Center for epic `#14`, and the vertical-slice runtime foundation that starts epic `#12`.
Goal: give humans and agents a fast map of the active `DotPilot` solution, the current `Uno Platform` shell, the foundation contracts from epic `#11`, the workbench foundation for epic `#13`, the Toolchain Center for epic `#14`, and the runtime-host backlog that builds on those contracts in epic `#12`.

This file is the required start-here architecture map for non-trivial tasks.

Expand All @@ -10,15 +10,14 @@ This file is the required start-here architecture map for non-trivial tasks.
- **Presentation boundary:** [../DotPilot/](../DotPilot/) is now the presentation host only. It owns XAML, routing, desktop startup, and UI composition, while non-UI feature logic moves into separate DLLs.
- **Workbench boundary:** epic [#13](https://github.com/managedcode/dotPilot/issues/13) is landing as a `Workbench` slice that will provide repository navigation, file inspection, artifact and log inspection, and a unified settings shell without moving that behavior into page code-behind.
- **Toolchain Center boundary:** epic [#14](https://github.com/managedcode/dotPilot/issues/14) now lives as a `ToolchainCenter` slice. [../DotPilot.Core/Features/ToolchainCenter](../DotPilot.Core/Features/ToolchainCenter) defines the readiness, diagnostics, configuration, action, and polling contracts; [../DotPilot.Runtime/Features/ToolchainCenter](../DotPilot.Runtime/Features/ToolchainCenter) probes local provider CLIs for `Codex`, `Claude Code`, and `GitHub Copilot`; the Uno app surfaces the slice through the settings shell.
- **Foundation contract boundary:** epic [#11](https://github.com/managedcode/dotPilot/issues/11) is represented through [../DotPilot.Core/Features/ControlPlaneDomain](../DotPilot.Core/Features/ControlPlaneDomain) and [../DotPilot.Core/Features/RuntimeCommunication](../DotPilot.Core/Features/RuntimeCommunication). These slices define the shared agent/session/tool model and the `ManagedCode.Communication` result/problem language that later runtime work reuses.
- **Runtime foundation boundary:** [../DotPilot.Core/](../DotPilot.Core/) owns issue-aligned contracts, typed identifiers, and public slice interfaces; [../DotPilot.Runtime/](../DotPilot.Runtime/) owns provider-independent runtime implementations such as the deterministic test client, toolchain probing, and future embedded-host integration points.
- **Domain slice boundary:** issue [#22](https://github.com/managedcode/dotPilot/issues/22) now lives in `DotPilot.Core/Features/ControlPlaneDomain`, which defines the shared agent, session, fleet, provider, runtime, approval, artifact, telemetry, and evaluation model that later slices reuse.
- **Communication slice boundary:** issue [#23](https://github.com/managedcode/dotPilot/issues/23) lives in `DotPilot.Core/Features/RuntimeCommunication`, which defines the shared `ManagedCode.Communication` result/problem language for runtime public boundaries.
- **First implementation slice:** epic [#12](https://github.com/managedcode/dotPilot/issues/12) is represented locally through the `RuntimeFoundation` slice, which sequences issues `#22`, `#23`, `#24`, and `#25` behind a stable contract surface instead of mixing runtime work into the Uno app.
- **Embedded runtime backlog boundary:** epic [#12](https://github.com/managedcode/dotPilot/issues/12) now builds on the epic `#11` foundation contracts through the `RuntimeFoundation` slice instead of treating issues `#22` and `#23` as runtime-host work.
- **Automated verification:** [../DotPilot.Tests/](../DotPilot.Tests/) covers API-style and contract flows through the new DLL boundaries; [../DotPilot.UITests/](../DotPilot.UITests/) covers the visible workbench flow, Toolchain Center, and runtime-foundation UI surface. Provider-independent flows must pass in CI through deterministic or environment-agnostic checks, while provider-specific checks can run only when the matching toolchain is available.

## Scoping

- **In scope for the current repository state:** the Uno workbench shell, the new `DotPilot.Core` and `DotPilot.Runtime` libraries, the runtime-foundation slice, and the automated validation boundaries around them.
- **In scope for the current repository state:** the Uno workbench shell, the `DotPilot.Core` and `DotPilot.Runtime` libraries, the epic `#11` foundation-contract slices, the runtime-foundation planning surface for epic `#12`, and the automated validation boundaries around them.
- **In scope for future implementation:** embedded Orleans hosting, `Microsoft Agent Framework`, provider adapters, persistence, telemetry, evaluation, Git tooling, and local runtimes.
- **Out of scope in the current slice:** full Orleans hosting, live provider execution, remote workers, and cloud-only control-plane services.

Expand All @@ -35,7 +34,7 @@ flowchart LR
Adr3["ADR-0003 vertical slices + UI-only app"]
Feature["agent-control-plane-experience.md"]
Toolchains["toolchain-center.md"]
Plan["vertical-slice-runtime-foundation.plan.md"]
Plan["epic-11-foundation-contracts.plan.md"]
Ui["DotPilot Uno UI host"]
Core["DotPilot.Core contracts"]
Runtime["DotPilot.Runtime services"]
Expand Down Expand Up @@ -123,31 +122,60 @@ flowchart TD
RuntimeSlice --> UiSlice
```

### Runtime foundation slice for epic #12
### Foundation contract slices for epic #11

```mermaid
flowchart TD
Epic["#12 Embedded agent runtime host"]
Epic["#11 Desktop control-plane foundation"]
Domain["#22 Domain contracts"]
Comm["#23 Communication contracts"]
DomainSlice["DotPilot.Core/Features/ControlPlaneDomain"]
CommunicationSlice["DotPilot.Core/Features/RuntimeCommunication"]
RuntimeContracts["DotPilot.Core/Features/RuntimeFoundation"]
DeterministicClient["DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient"]
Tests["DotPilot.Tests contract coverage"]

Epic --> Domain
Epic --> Comm
Domain --> DomainSlice
Comm --> CommunicationSlice
DomainSlice --> RuntimeContracts
CommunicationSlice --> RuntimeContracts
CommunicationSlice --> DeterministicClient
DomainSlice --> DeterministicClient
DeterministicClient --> Tests
RuntimeContracts --> Tests
```

### Runtime-host backlog slices for epic #12

```mermaid
flowchart TD
Epic["#12 Embedded agent runtime host"]
Host["#24 Embedded Orleans host"]
MAF["#25 Agent Framework runtime"]
Policy["#26 Grain traffic policy"]
Sessions["#27 Session persistence and resume"]
Foundation["#11 Foundation contracts"]
DomainSlice["DotPilot.Core/Features/ControlPlaneDomain"]
CommunicationSlice["DotPilot.Core/Features/RuntimeCommunication"]
CoreSlice["DotPilot.Core/Features/RuntimeFoundation"]
RuntimeSlice["DotPilot.Runtime/Features/RuntimeFoundation"]
UiSlice["DotPilot runtime panel + banner"]

Epic --> Domain
Epic --> Comm
Foundation --> DomainSlice
Foundation --> CommunicationSlice
Epic --> Host
Epic --> MAF
Domain --> DomainSlice
Epic --> Policy
Epic --> Sessions
DomainSlice --> CommunicationSlice
CommunicationSlice --> CoreSlice
Comm --> CommunicationSlice
Host --> RuntimeSlice
MAF --> RuntimeSlice
Policy --> CoreSlice
Sessions --> CoreSlice
Sessions --> RuntimeSlice
CoreSlice --> UiSlice
RuntimeSlice --> UiSlice
```
Expand Down Expand Up @@ -185,7 +213,7 @@ flowchart LR
### Planning and decision docs

- `Solution governance` — [../AGENTS.md](../AGENTS.md)
- `Task plan` — [../vertical-slice-runtime-foundation.plan.md](../vertical-slice-runtime-foundation.plan.md)
- `Task plan` — [../epic-11-foundation-contracts.plan.md](../epic-11-foundation-contracts.plan.md)
- `Primary architecture decision` — [ADR-0001](./ADR/ADR-0001-agent-control-plane-architecture.md)
- `Vertical-slice solution decision` — [ADR-0003](./ADR/ADR-0003-vertical-slices-and-ui-only-uno-app.md)
- `Feature spec` — [Agent Control Plane Experience](./Features/agent-control-plane-experience.md)
Expand Down Expand Up @@ -237,7 +265,8 @@ flowchart LR

- The Uno app must remain a presentation-only host instead of becoming a dump for runtime logic.
- Feature work should land as vertical slices with isolated contracts and implementations, not as shared horizontal folders.
- Epic `#12` starts with contracts, sequencing, deterministic runtime coverage, and UI exposure before live Orleans or provider integration.
- Epic `#11` establishes the reusable contract and communication foundation before epic `#12` begins embedded runtime-host work.
- Epic `#12` builds on that foundation instead of re-owning issues `#22` and `#23`.
- Epic `#14` makes external-provider toolchain readiness explicit before session creation, so install, auth, diagnostics, and configuration state stays visible instead of being inferred later.
- CI must stay meaningful without external provider CLIs by using the in-repo deterministic runtime client.
- Real provider checks may run only when the corresponding toolchain is present and discoverable.
Expand Down
3 changes: 2 additions & 1 deletion docs/Features/control-plane-domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ flowchart LR

## Verification

- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter FullyQualifiedName~ControlPlaneDomain`
- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj`
- `dotnet test DotPilot.slnx`

## Dependencies

- Parent epic: [#12](https://github.com/managedcode/dotPilot/issues/12)
- Parent epic: [#11](https://github.com/managedcode/dotPilot/issues/11)
- Runtime communication follow-up: [#23](https://github.com/managedcode/dotPilot/issues/23)
- Embedded host follow-up: [#24](https://github.com/managedcode/dotPilot/issues/24)
- Agent Framework follow-up: [#25](https://github.com/managedcode/dotPilot/issues/25)
1 change: 1 addition & 0 deletions docs/Features/runtime-communication-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ flowchart LR

## Verification

- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter FullyQualifiedName~RuntimeCommunication`
- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj`
- `dotnet test DotPilot.slnx`

Expand Down
Loading