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
64 changes: 64 additions & 0 deletions docs/plans/mvp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# AgentOS.NET — the MVP

**One sentence:** an AI software team that turns a backlog into reviewed, tested, merged pull requests — on the
team's own computers, with evidence on every change and governance a company can sign off.

Everything below serves one loop. A feature that doesn't make this loop faster, safer or more visible waits.

```
Idea ──► spec + tickets with dependencies (Discovery: Requirement + Decomposition agents)
──► Autopilot — the Autopilot Orchestrator agent decides what runs next, within fixed rules
──► each ticket: a MAF workflow Coding → checks → Reviewer → Testing → merge
(red checks · conflicts · requested changes · failed acceptance → back to Coding, 3 rounds)
──► Try the app — the latest version running on your computer
──► Board · Autopilot log · Knowledge · Insights — what happened, why, and what it cost
```

## Who it is for (first)

- **A tech lead / small team** in a .NET or web shop on GitHub: wants the backlog built without babysitting agents.
- **A non-technical product owner** on the same team: describes ideas in plain words, answers the agents'
questions, tries the app — never opens a terminal.

## Why it wins

1. **Runs on the team's own machines** — each member's Claude / Codex subscription through a paired computer, in a
scoped sandbox (own folder, allow-listed commands, no force-push, no credential reads). $0 server API cost.
2. **Nothing untested is merged** — a Reviewer agent and a Testing agent check every change, and post their evidence
on the pull request; failures already on main get their own tickets.
3. **Measured** — every agent call: tokens (fresh · cache read · cache write · output), latency, outcome; per agent,
per model, per run (Insights).
4. **It asks, it doesn't guess** — stuck work is retried with guidance or turned into one precise question on the
ticket; your reply is read back and acted on.

**Proof:** VNChessAcademy (a multi-tenant teaching platform with a CCBridge xiangqi library) was built by this loop
on the lab — 25+ pull requests, most merged without a person touching the code.

## In the MVP (the dock)

| App | Role in the loop |
|---|---|
| **Home** | setup checklist (connect GitHub, connect a computer), recent activity |
| **Board** | Projects (Try the app) · New idea · Tasks · Computers · Autopilot · Knowledge · Activity · Files |
| **Insights** | runs, tokens, latency, cost (≈ API for subscription runs), budget |
| **Settings** | AI accounts, agent profiles, Autopilot (who decides, how a ticket runs), people, audit |

**Outside the MVP** (kept, not in the dock): the Workflow studio and the 5-agent pipeline it runs, Terminal, the
Thorough engine, Azure DevOps boards (partial), the Components catalog. Reachable with Ctrl+K / Activities.

## Before the first real users — enterprise gaps, in order

1. **Onboarding without a terminal** — "Connect GitHub" through a GitHub App (repo picker, no PAT), and a
downloadable helper for "Connect this computer" (today it is `dotnet run`).
2. **Data isolation, stage 2** — an empty tenant on a connection sees *nothing* (today: everything), with an explicit
platform scope for system workers; then an external security review.
3. **Reliability** — cloud workers so work doesn't depend on a laptop being awake (plan: `cloud-workers-plan`);
resumable ticket workflows (checkpoints) across restarts.
4. **Deploy** — `azd up` to the customer's Azure, an operations guide, backups.
5. **Product shell** — landing page, docs site, licensing.

## Definition of done (every change)

Enterprise level, as CLAUDE.md's owner directives: tests at every layer, metrics, plain-language UI with an a11y pass,
docs true, verified in the running app — and for anything that touches persistence, identity or real services, on
the full stack / the lab.
12 changes: 6 additions & 6 deletions src/AgentOs.Web/Components/Pages/InsightsApp.razor
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<header class="page-head">
<div>
<h1 class="page-title">Insights</h1>
<p class="page-sub">Runs, agent utilisation and LLM cost at a glance — the control plane for the pipeline.</p>
<p class="page-sub">What your AI team did, how long it took and what it cost — every agent run, by agent and by model.</p>
</div>
<div class="ins-head-actions">
<div class="seg" role="group" aria-label="Time range">
Expand All @@ -40,7 +40,7 @@
<div class="metrics">
<div class="metric">
<div class="v">@(_summary is null ? "—" : Num(_summary.RunCount))</div>
<div class="k">Pipeline runs</div>
<div class="k">Agent runs</div>
</div>
<div class="metric">
<div class="v">@SuccessLabel()</div>
Expand Down Expand Up @@ -85,10 +85,10 @@ else if (_summary is null || _summary.CallCount == 0)
<Panel Title="No activity yet">
<EmptyState Icon="graph"
Title="No runs recorded"
Hint="Open the Workflow app and run the pipeline — this dashboard fills in with charts as runs complete." />
Hint="Start a task on the Board — every Coding, Reviewer, Testing and Discovery run shows up here with its tokens, time and cost." />
<div class="ins-empty-cta">
<Btn Variant="Btn.ButtonVariant.Primary" OnClick="@(() => Open("workflow"))">
<Icon Name="graph" Size="14" /> Open Workflow
<Btn Variant="Btn.ButtonVariant.Primary" OnClick="@(() => Open("board"))">
<Icon Name="git-pull-request" Size="14" /> Open Board
</Btn>
</div>
</Panel>
Expand Down Expand Up @@ -141,7 +141,7 @@ else
</div>
}
</div>
<div class="ins-legend"><span class="ins-legend-dot" style="background:var(--accent)"></span> Pipeline runs / day</div>
<div class="ins-legend"><span class="ins-legend-dot" style="background:var(--accent)"></span> Agent runs / day</div>
}
</Panel>

Expand Down
7 changes: 4 additions & 3 deletions src/AgentOs.Web/Services/AppCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ namespace AgentOs.Web.Services;
/// <summary>The catalog of apps the AgentOS.NET shell can launch — built-ins plus any registered by plugins.</summary>
public static class AppCatalog
{
// Four apps are Pinned (the GNOME Dash): Home · Workflow · Board · Settings. Terminal and Files are tabs of
// Four apps are Pinned (the GNOME Dash) — the MVP loop (docs/plans/mvp.md): Home · Board · Insights · Settings. The
// Workflow studio (the 5-agent pipeline as a graph) is outside the MVP and stays one Activities / Ctrl+K search away. Terminal and Files are tabs of
// Board (Log, Files) and stay launchable on their own from Activities. Everything else is unpinned — still launchable via Activities search / deep
// links, and the admin/system surfaces are collected behind the Settings hub. Pinned defaults to
// true on the DesktopApp record, so unpinned apps MUST say Pinned: false explicitly.
Expand All @@ -23,9 +24,9 @@ public static class AppCatalog
// standalone Overview + Cost admin windows are folded in here.
// Home is the task-first landing (UX phase 2): describe a change → Run now; recent runs; setup checklist.
new("home", "Home", "house", "Describe a change and run the agents — recent runs and setup", "Agents", 1000, 760, Pinned: true, ComponentType: typeof(HomeApp), Color: "#3584e4"),
new("insights", "Insights", "chart", "Runs, agent utilisation and LLM cost — analytics + budget", "Agents", 1180, 760, Pinned: false, ComponentType: typeof(InsightsApp), Color: "#1c71d8"),
new("workflow", "Workflow", "graph", "Build & run the agent pipeline as a visual graph", "Agents", 1320, 840, Pinned: true, ComponentType: typeof(OrchestrationStudio), Color: "#9141ac"),
new("workflow", "Workflow", "graph", "Build & run the agent pipeline as a visual graph", "Agents", 1320, 840, Pinned: false, ComponentType: typeof(OrchestrationStudio), Color: "#9141ac"),
new("board", "Board", "git-pull-request", "Boards → tickets → AI sessions → PRs", "Agents", 1000, 660, Pinned: true, ComponentType: typeof(BoardApp), Color: "#2ec27e"),
new("insights", "Insights", "chart", "Runs, agent utilisation and LLM cost — analytics + budget", "Agents", 1180, 760, Pinned: true, ComponentType: typeof(InsightsApp), Color: "#1c71d8"),
new("settings", "Settings", "gear", "Model keys, agents, governance, people and personal preferences", "System", 1060, 700, Pinned: true, ComponentType: typeof(SettingsHub), Color: "#5e5c64"),
new("terminal", "Terminal", "terminal", "Live read-only tail of the runner's session feed", "Agents", 780, 520, Pinned: false, ComponentType: typeof(TerminalApp), Color: "#2b2b2e"),
new("files", "Files", "folder", "Browse a connected repo's files (read-only)", "Agents", 880, 600, Pinned: false, ComponentType: typeof(FilesApp), Color: "#4a86d8"),
Expand Down
21 changes: 21 additions & 0 deletions tests/AgentOs.E2E.Tests/AgentOsUi/DesktopNavigation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Open any app the way a person does when it isn't in the dock: Ctrl+K search, pick the app. The dock holds only the
// MVP apps (Home · Board · Insights · Settings); everything else — e.g. the Workflow studio — is one search away.

using System.Threading.Tasks;
using Microsoft.Playwright;

namespace AgentOs.E2E.Tests.AgentOsUi;

internal static class DesktopNavigation
{
/// <summary>Open <paramref name="appName"/> through the shell search and wait for its window to have focus.</summary>
public static async Task OpenAppAsync(this IPage page, string appName)
{
await page.Keyboard.PressAsync("Control+k");
var input = page.Locator(".ko-search-input");
await Assertions.Expect(input).ToBeFocusedAsync();
await input.PressSequentiallyAsync(appName);
await page.Locator(".ko-row", new() { HasTextString = appName }).First.ClickAsync();
await Assertions.Expect(page.Locator(".appwin.focused .appwin-title")).ToHaveTextAsync(appName);
}
}
2 changes: 1 addition & 1 deletion tests/AgentOs.E2E.Tests/AgentOsUi/LoginAndDesktopTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public async Task RightClick_OpensContextMenu_AtCursor_AndCloseOnOutsideClick()
// Scenario 3: clicking a Desktop icon opens an AppFrame for the corresponding app key.
[Theory]
[InlineData("Home")]
[InlineData("Workflow")]
[InlineData("Insights")]
[InlineData("Board")]
[InlineData("Settings")]
public async Task DockIcon_Click_OpensAppWindow(string title)
Expand Down
2 changes: 1 addition & 1 deletion tests/AgentOs.E2E.Tests/AgentOsUi/SettingsSearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task CtrlK_OpensSearch_EnterRunsTheTopAction_WorkflowKeepsItsOwnCtr
await Assertions.Expect(win.Locator(".prefs-h", new() { HasTextString = "Model keys" })).ToBeVisibleAsync();

// With the Workflow window focused, Ctrl+K is the builder's "Add a node" palette, not the shell search.
await _fx.Page.Locator(".dock-item[title=\"Workflow\"]").First.ClickAsync();
await _fx.Page.OpenAppAsync("Workflow");
await Assertions.Expect(_fx.Page.Locator(".appwin.focused .syn-toolbar")).ToBeVisibleAsync();
// The builder registers its key handler after its first render — wait for it, not just for the toolbar.
await _fx.Page.WaitForFunctionAsync("() => !!(window.agentosWorkflowKeys && window.agentosWorkflowKeys._handler)");
Expand Down
2 changes: 1 addition & 1 deletion tests/AgentOs.E2E.Tests/AgentOsUi/WindowLifecycleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public async Task Dock_PinnedApps_ShowRunningDot_WhenOpen()
await _fx.GotoDesktopAsync();

// 6 pinned apps in the Dash (GNOME Dash — no Start button).
foreach (var t in new[] { "Home", "Workflow", "Board", "Settings" })
foreach (var t in new[] { "Home", "Board", "Insights", "Settings" })
{
await Assertions.Expect(_fx.Page.Locator($".dock-item[title=\"{t}\"]")).ToBeVisibleAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private async Task<ILocator> OpenWorkflowAsync()
{
await _fx.LoginAsync();
await EnsureCircuitConnectedAsync();
await _fx.Page.Locator(".dock-item[title=\"Workflow\"]").First.ClickAsync();
await _fx.Page.OpenAppAsync("Workflow");
var win = _fx.Page.Locator(".appwin.focused");
await Assertions.Expect(win.Locator(".appwin-title")).ToHaveTextAsync("Workflow");
// The studio graph dropdown is populated once the component's circuit is interactive — wait for it
Expand Down
2 changes: 1 addition & 1 deletion tests/AgentOs.E2E.Tests/AgentOsUi/WorkflowAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private async Task<ILocator> OpenWorkflowAsync()
{
await _fx.GotoDesktopAsync();
await EnsureCircuitConnectedAsync();
await _fx.Page.Locator(".dock-item[title=\"Workflow\"]").First.ClickAsync();
await _fx.Page.OpenAppAsync("Workflow");
var win = _fx.Page.Locator(".appwin.focused");
await Assertions.Expect(win.Locator(".appwin-title")).ToHaveTextAsync("Workflow");
// The studio graph dropdown is populated once the component's circuit is interactive — wait for it
Expand Down
Loading