Architecture
Agent-Up has core runtime component areas plus product-specific installer entrypoints:
AgentUp.ServerAgentUp.DesktopAgentUp.CLIAgentUp.InstallerAppAgentUp.PackagingAgentUp.PackageSmoke- MCP clients
The Server is the single source of truth for runtime orchestration. Desktop, CLI, MCP clients, and future integrations are clients of the Server. Product-neutral installer, packaging, and package-smoke infrastructure lives in LocalInstaller.*; the Agent-Up installer entrypoints only register Agent-Up product configuration and delegate to that infrastructure.
Solution Layout
Project directories live directly at the repository root and are included in the root solution. Agent-Up does not use src/ or tests/ wrapper directories.
agent-up.sln
AgentUp.Server/
AgentUp.Server.csproj
AgentUp.Capabilities.Abstractions/
AgentUp.Capabilities.Abstractions.csproj
AgentUp.Capabilities.Common/
AgentUp.Capabilities.Common.csproj
AgentUp.Capabilities.Dotnet/
AgentUp.Capabilities.Dotnet.csproj
AgentUp.Capabilities.Docker/
AgentUp.Capabilities.Docker.csproj
AgentUp.Desktop/
AgentUp.Desktop.csproj
AgentUp.CLI/
AgentUp.CLI.csproj
AgentUp.InstallerApp/
AgentUp.InstallerApp.csproj
AgentUp.Packaging/
AgentUp.Packaging.csproj
AgentUp.PackageSmoke/
AgentUp.PackageSmoke.csproj
AgentUp.Server.Tests/
AgentUp.Server.Tests.csproj
AgentUp.Capabilities.Abstractions.Tests/
AgentUp.Capabilities.Abstractions.Tests.csproj
AgentUp.Capabilities.Common.Tests/
AgentUp.Capabilities.Common.Tests.csproj
AgentUp.Capabilities.Dotnet.Tests/
AgentUp.Capabilities.Dotnet.Tests.csproj
AgentUp.Capabilities.Docker.Tests/
AgentUp.Capabilities.Docker.Tests.csproj
AgentUp.Desktop.Tests/
AgentUp.Desktop.Tests.csproj
AgentUp.CLI.Tests/
AgentUp.CLI.Tests.csproj
AgentUp.Architecture.Tests/
AgentUp.Architecture.Tests.csproj
AgentUp.Tests/
AgentUp.Tests.csproj
The exact project list may evolve, but the ownership boundaries should remain stable. agent-up.sln references only Agent-Up projects; Agent-Up projects consume LocalInstaller through LocalInstaller.* NuGet packages pinned by $(LocalInstallerVersion). The LocalInstaller source, tests, samples, and localinstaller.sln live in the sibling LocalInstaller repository.
Code Organization
Every production .NET project uses capability-oriented vertical slices under Features/. Root project folders should contain only project entry/configuration files such as Program.cs, project files, Avalonia App.axaml files, and SDK/tooling-required files such as Properties/launchSettings.json.
Feature slice names should describe a product, customer, operator, or maintainer capability. Do not promote tiny technical mechanisms into top-level slices when they only support a larger capability. For example, installer payload selection, PATH planning, uninstall planning, and post-install validation belong inside the meaningful installation slice unless they grow into independently owned behavior.
Inside a feature slice, use only these type folders:
Models/for persistence or internal representation.Repositories/for storage abstraction and persistence access.Services/for domain-specific behavior and orchestration inside the slice.Controllers/for routing external calls to slice services.DTOs/for external data representation contracts.Providers/for low-level actions behind domain-specific interfaces, such as HTTP clients, command runners, file-system adapters, platform adapters, and Git readers.Interfaces/for justified slice-local interfaces.Factories/for object-selection or adapter-selection factories.
Avalonia UI projects may additionally use Views/ and ViewModels/ inside feature slices.
MCP is a protocol surface, not a feature slice. MCP tools and resources live in the owning feature slice's Controllers/ folder as thin protocol adapters over that slice's controllers/services. Cross-capability workspace management and context tools belong to the Orchestration slice. Slice-specific tools, such as commit queue tools, belong to their owning slice.
Tests must stay feature-sliced and use clear test-kind folders. Feature tests live under Features/<Slice>/<TestKind>/ with Unit/, Controller/, HTTP/, Repository/, Provider/, Headless/, or E2E/. Root-level test support folders are limited to documented support areas such as Support/, Fixtures/, Fake/, Architecture/, or root E2E/; test-kind names such as Controller/ must not appear at a test project root. Use Controller/ for slice-external communication boundaries such as controllers, command parsers, CLI command surfaces, MCP tools, and MCP resources. Unit/ tests must stay in memory and must not use real filesystem, process, socket, current-directory, or environment mutation APIs; use Repository/ or Provider/ for tests that verify real directory state, platform adapters, command providers, package writers/stagers, probes, or process-style command shapes.
AgentUp.Architecture.Tests owns executable architecture and review-hygiene rules. Use ArchUnitNET there for assembly/type dependency rules, and use narrowly scoped filesystem/source checks there for physical folder rules and generic source-quality rules that assembly analysis cannot see. Slices that receive same-project inbound traffic must expose a concrete *Controller boundary, and architecture rules should verify that controllers and services do not bypass sibling slice boundaries through implementation-folder imports.
Architecture rules also enforce review-prone structural limits deterministically: production source must not use nested types or nested delegates, controllers must not depend on providers/repositories/factories, controller methods must stay thin with low cyclomatic complexity and no loops/switch/try blocks, sibling-slice imports must use only target Controllers/ or DTOs/, and feature slices with controllers, services/models, or providers must have matching Controller/, Unit/, or Provider/ test-kind coverage. Existing coverage and cross-slice dependency gaps are tracked as explicit architecture-test debt baselines; do not add new entries to those baselines when adding or expanding a slice.
Review-hygiene rules should catch recurring patterns generically: generic or empty catch blocks, unsafe path construction, missing disposable ownership, sync-over-async in production startup/UI/composition paths, ambiguous timeout cancellation filters, static controller composition wrappers, and nondeterministic tests that skip coverage based on live platform privilege or filesystem state.
Do not add broad technical buckets such as root-level Controllers/, Services/, Models/, Http/, Commands/, or Git/. Put the code in the owning feature slice and then in the appropriate type folder.
If a low-level abstraction is genuinely used by multiple slices in the same project, put it under a project-level Shared/ folder with the same strict type-folder naming. Do not make one feature slice the hidden owner of generic command, file-system, platform, or network helpers that unrelated slices import directly.
Service And Provider Rule
Services own domain lifecycle and orchestration behind controllers. Services may call same-slice repositories, providers, factories, and models, but they must stay domain-specific.
Services must not contain low-level parsing, command construction, filesystem/archive operations, native tool invocation, environment lookup, HTTP/network mechanics, process execution, platform API calls, XML/manifest serialization mechanics, or string-scanning helpers for external tool output. Put that behavior behind same-slice Providers/ with names that describe the user/operator capability where practical, such as PackageCommandParser, DpkgDebPackageTool, WindowsWixPackagingTool, MacOsPackageArchiveProvider, or DockerPrerequisiteProvider.
Use Models/ for data definitions and pure internal representations that stay inside the slice, including generated manifest/script/XML text when the code is defining package or installer data rather than performing I/O. Use DTOs/ only for data crossing external or controller boundaries.
Provider interfaces are justified when they hide low-level providers from services, are faked by tests, or select runtime adapters. A service depending on IUbuntuPackageTool is acceptable; a service building new CommandSpec("dpkg-deb", ...) is not. A controller or service parsing raw string[] args is not acceptable; use a parser provider that returns a DTO/result.
Interface Rule
Do not add interfaces for 1:1 concrete mappings. An interface is justified only when tests create fakes, runtime code selects among multiple adapters, or the boundary intentionally hides low-level providers such as command execution, file systems, platform APIs, storage, or network calls. Place justified interfaces in the owning slice's Interfaces/ folder.
Slice Lifecycle
Project entrypoints such as Program.cs, host routes, CLI commands, MCP tools, and UI event handlers should enter a feature through Controllers/, either directly or through the project composition root that exposes those controllers. Controllers receive dependencies through constructors; they must not create services or providers. Keep controllers thin: they map external calls and DTO arguments to injected services. Controllers must not import providers, repositories, or factories directly, must not be static composition wrappers, and must not expose another slice's internal services, providers, repositories, factories, interfaces, or models as return types.
Services own domain lifecycle and orchestration behind controllers. Follow the service/provider rule above for the boundary between domain orchestration and low-level implementation.
Slices should not import another slice's internal Services/, Models/, Providers/, Interfaces/, Repositories/, Factories/, Tools/, Views/, or ViewModels/. Cross-slice communication should go through the target slice's Controllers/ boundary and exchange IDs or DTOs. If read-only access or low-level abstractions need a non-controller contract, move that contract to a project-level Shared/ folder instead of exposing a feature-owned interface.
Component Responsibilities
AgentUp.Capabilities.* projects define ecosystem adapters outside the Server's product slices. AgentUp.Capabilities.Abstractions is the stable contract for first-party and future external capability packages. AgentUp.Capabilities.Common owns shared catalog parsing, checksum validation, tool-cache layout, and install planning. First-party adapters such as AgentUp.Capabilities.Dotnet and AgentUp.Capabilities.Docker own ecosystem discovery, version reconciliation, validation, and launch planning.
LocalInstaller.App is the product-neutral Avalonia installer dashboard. It presents independent component management cards plus a standardized capability-module catalog and version-management UI. Capability modules provide data and validation metadata, not custom UI. The app owns its installer-facing catalog and installed-module contracts and must not take compile-time dependencies on AgentUp.Capabilities.* projects. Product entrypoints such as AgentUp.InstallerApp register typed LocalInstaller manifests through the fluent API and should keep Program.cs limited to product and installer-option configuration.
AgentUp.Server performs orchestration:
- Workspace registry.
- Process lifecycle.
- Port allocation.
- Docker lifecycle.
- Capability reconciliation and status.
- Browser lifecycle.
- Browser profile management.
- Event recording.
- Diagnostics and health monitoring.
- Playwright generation.
- MCP server.
- REST API.
AgentUp.Desktop displays state and browser sessions. It does not own runtime state.
AgentUp.CLI is a developer convenience wrapper. It forwards commands to the Server and owns no state.
LocalInstaller.Core owns testable installer prerequisite, component-selection, payload, adapter, progress, PATH, validation, and uninstall planning contracts. Native package assets consume or mirror those contracts.
Generic installer code must stay product-neutral. Architecture tests scan LocalInstaller.* source and allow literal agent-up, Agent-Up, and dev.agent-up product identity strings only in explicitly AgentUp* configuration files. Generic component categories such as Desktop, Server, CLI, and Tray are permitted infrastructure concepts, but generic installer services/providers must derive product names, slugs, service names, package names, and module paths from the active product manifest.
LocalInstaller.App owns the shared Avalonia installer dashboard. It delegates platform-specific execution to installer adapters. On NixOS the adapter is lookup-only: components and capability status can be inspected, but installs and version changes are made through NixOS or Home Manager configuration.
LocalInstaller.Packaging owns testable release artifact staging, package metadata generation, and orchestration of native packaging tools such as dpkg-deb, WiX, pkgbuild, and productbuild. AgentUp.Packaging is a thin product entrypoint that registers the Agent-Up package manifest through the LocalInstaller API.
Each installable executable owns a typed LocalInstaller artifact manifest in that executable project. InstallerApp, Packager, and Smoke entrypoints consume those manifests with UseProductManifest<T>(), InstallerApplication<T>(), and InstallerOption*<T>() calls. Multiple options may share a target category such as CLI or Server, but each option must have a globally unique artifact ID and payload directory; target aliases are only valid when they resolve to one registered option.
Packaging must expose package-owned request and product DTOs. It may map those DTOs to explicit platform installer contracts, but it must not depend on installer workflow product/session internals. Product identity values that become artifact names, path components, filenames, WiX identity, service names, server URLs, or command arguments must be validated at request or path-construction boundaries. Installer GUID seeds for fixed components, shortcuts, and bundles must include product identity, with any legacy unscoped compatibility path made explicit and covered by architecture tests.
LocalInstaller.Smoke owns the shared package and installed-service smoke validator used by CI smoke scripts. It validates the same abstract package, service, CLI, and uninstall properties through platform adapters while keeping shell scripts focused on selecting arguments and runner setup. AgentUp.PackageSmoke is a thin product entrypoint that registers the Agent-Up smoke manifest through the LocalInstaller API.
MCP clients are the primary automation clients. AI agents should use MCP directly instead of shelling through the CLI.
Boundary Rule
All orchestration belongs in the Server. Clients may request actions and render state, but they should not decide how workspaces, ports, processes, Docker, browsers, diagnostics, or event streams are managed.