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
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private static ControlPlaneDomainEnvelope CreateEnvelope()
Id = WorkspaceId.New(),
Name = "dotPilot",
RootPath = SyntheticWorkspaceRootPath,
BranchName = "codex/issue-22-domain-model",
BranchName = "main",
};

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 @@ -33,7 +33,7 @@ We will use these architectural defaults for implementation work going forward:
- `DotPilot.Runtime` for provider-independent runtime implementations and future host integration seams
- `DotPilot.Runtime.Host` for the embedded Orleans silo and desktop-only runtime-host lifecycle
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. Issue `#24` is implemented through a desktop-only `DotPilot.Runtime.Host` project that uses localhost clustering plus in-memory storage/reminders before any remote or durable topology is introduced.
4. Epic `#11` establishes the shared `ControlPlaneDomain` and `RuntimeCommunication` slices, and epic `#12` builds on that foundation through the `RuntimeFoundation` slice. Issue `#24` is implemented through a desktop-only `DotPilot.Runtime.Host` project that uses localhost clustering plus in-memory storage/reminders before any remote or durable topology is introduced.
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 @@ -86,7 +86,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.
- The embedded Orleans host can evolve without leaking server-only dependencies into browserwasm or the presentation project.

Expand Down
47 changes: 37 additions & 10 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 local-first runtime foundation for 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 local-first runtime foundation for epic `#12`.

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

Expand All @@ -10,15 +10,16 @@ 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, grain interfaces, traffic-policy snapshots, and session-archive contracts; [../DotPilot.Runtime/](../DotPilot.Runtime/) owns provider-independent runtime implementations such as the deterministic turn engine, `Microsoft Agent Framework` orchestration client, and local archive persistence; [../DotPilot.Runtime.Host/](../DotPilot.Runtime.Host/) owns the embedded Orleans host, explicit grain traffic policy, and initial grain implementations for desktop targets.
- **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 now sequences issues `#22`, `#23`, `#24`, `#25`, `#26`, and `#27` behind a stable contract surface instead of mixing runtime work into the Uno app.
- **Runtime-host slice boundary:** epic [#12](https://github.com/managedcode/dotPilot/issues/12) now builds on the epic `#11` foundation contracts through the `RuntimeFoundation` slice, which sequences issues `#22`, `#23`, `#24`, `#25`, `#26`, and `#27` behind a stable contract surface instead of mixing runtime work into the Uno app.
- **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 `DotPilot.Core`, `DotPilot.Runtime`, and `DotPilot.Runtime.Host` libraries, the embedded Orleans host for local desktop runtime state, and the automated validation boundaries around them.
- **In scope for the current repository state:** the Uno workbench shell, the `DotPilot.Core`, `DotPilot.Runtime`, and `DotPilot.Runtime.Host` libraries, the epic `#11` foundation-contract slices, the embedded Orleans host for local desktop runtime state, and the automated validation boundaries around them.
- **In scope for future implementation:** provider adapters, durable persistence beyond the current local session archive, telemetry, evaluation, Git tooling, and local runtimes.
- **Out of scope in the current slice:** remote workers, remote clustering, external durable storage providers, and cloud-only control-plane services.

Expand Down Expand Up @@ -126,11 +127,37 @@ flowchart TD
RuntimeSlice --> UiSlice
```

### Foundation contract slices for epic #11

```mermaid
flowchart TD
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 foundation slice for epic #12

```mermaid
flowchart TD
Epic["#12 Embedded agent runtime host"]
Foundation["#11 Foundation contracts"]
Domain["#22 Domain contracts"]
Comm["#23 Communication contracts"]
Host["#24 Embedded Orleans host"]
Expand All @@ -144,16 +171,16 @@ flowchart TD
HostSlice["DotPilot.Runtime.Host/Features/RuntimeFoundation"]
UiSlice["DotPilot runtime panel + banner"]

Epic --> Domain
Epic --> Comm
Foundation --> Domain
Foundation --> Comm
Domain --> DomainSlice
Comm --> CommunicationSlice
DomainSlice --> CommunicationSlice
CommunicationSlice --> CoreSlice
Epic --> Host
Epic --> MAF
Epic --> Policy
Epic --> Sessions
Domain --> DomainSlice
DomainSlice --> CommunicationSlice
CommunicationSlice --> CoreSlice
Comm --> CommunicationSlice
Host --> HostSlice
Policy --> HostSlice
Policy --> CoreSlice
Expand Down Expand Up @@ -208,7 +235,6 @@ flowchart LR
### Planning and decision docs

- `Solution governance` — [../AGENTS.md](../AGENTS.md)
- `Task plan` — [../epic-12-embedded-runtime.plan.md](../epic-12-embedded-runtime.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 @@ -274,6 +300,7 @@ 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 `#11` establishes the reusable contract and communication foundation before epic `#12` begins embedded runtime-host work.
- Epic `#12` now has a first local-first Orleans host cut in `DotPilot.Runtime.Host`, and it intentionally uses localhost clustering plus in-memory storage/reminders before any remote or durable runtime topology is introduced.
- The desktop runtime path now uses `Microsoft Agent Framework` for orchestration, while the browser path keeps the deterministic in-repo client for CI-safe coverage.
- `#26` currently uses an explicit traffic-policy catalog plus Mermaid graph output instead of `ManagedCode.Orleans.Graph`, because the public `ManagedCode.Orleans.Graph` package is pinned to Orleans `9.x` and is not compatible with this repository's Orleans `10.0.1` baseline.
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