1 · Scaffold and run
In this step you generate the project, understand what the scaffold produced, and run the host. No code is written by you yet — but by the end you’ll know exactly what the CLI gave you and where everything lives.
1.1 Generate the app
Section titled “1.1 Generate the app”From a working directory of your choice:
cephalon new Acme.Store --output ./Acme.Storecd ./Acme.StoreThe --output path becomes the solution root. Acme.Store.slnx is the modern solution file format used by .NET 10.
1.2 Read the layout
Section titled “1.2 Read the layout”Acme.Store/├── README.md # generated README explaining the layout├── Acme.Store.slnx # solution file├── Directory.Build.props # repo-wide MSBuild props├── Directory.Build.targets # repo-wide MSBuild targets├── Directory.Packages.props # centralised package versions├── global.json # pins the SDK band├── NuGet.config # points at .cephalon/packages first7 collapsed lines
├── nuget.config # workaround for case-insensitive filesystems├── Dockerfile # multi-stage Dockerfile├── .dockerignore├── compose.yaml # local docker compose├── otel-collector-config.yaml # OTLP collector preset├── .cephalon/│ └── packages/ # local NuGet feed for Cephalon.* packages├── deploy/ # 7 deployment targets├── src/│ ├── Acme.Store.Host/ # the executable│ └── Acme.Store.Modules.Health/ # a single starter module└── tests/ └── Acme.Store.Host.Tests/ # composition + behavior testsA few non-obvious notes:
Directory.Packages.propsuses centralised package management (CPM). Add new packages to that file, then reference them without version in individual.csprojfiles.global.jsonpins to the SDK band — bump it deliberately when you upgrade.- The
.cephalon/packages/folder is part of the NuGet feed chain. The generatedNuGet.configputs it first, so locally-staged packages override anything from the public feed.
1.3 Inspect Program.cs
Section titled “1.3 Inspect Program.cs”Open the host entry point:
using Cephalon.AspNetCore;using Cephalon.Engine;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Services .AddCephalonAspNetCore() .AddModulesFromAssemblies(typeof(Program).Assembly) .Build(builder);
app.MapCephalon();app.MapHealthChecks("/health");
app.Run();Two things to notice:
AddCephalonAspNetCore()registers the host adapter. It wires REST behavior support, OpenAPI/Scalar, the/engine/*routes, and the manifest endpoint.AddModulesFromAssemblies(...)asks the engine to discover modules from the listed assemblies. You can also load modules from explicit DLLs or package manifests — see Module authoring.
1.4 Inspect appsettings.json
Section titled “1.4 Inspect appsettings.json”{ "Engine": { "Id": "acme-store", "Data": { "IdStrategy": "Sfid", "Provider": null }, "Identity": { "Enabled": false }, "Tenancy": { "Enabled": false }, "Audit": { "Enabled": true }, "Messaging": { "Enabled": false }, "Transports": [ "RestApi" ] }, "Logging": { "LogLevel": { "Default": "Information" } }, "AllowedHosts": "*"}The Engine:* section is read by the engine at build time. Each subsection is a capability:
Data:IdStrategy: Sfid— generated apps default toSfid.Netsortable identifiers. You can switch toGuid,Long, or a custom strategy later.Identity:Enabled: false— turning this on activates the identity capability and requires a provider package.Tenancy:Enabled: false— same for multi-tenancy.Audit:Enabled: true— audit is enabled by default; theCephalon.Auditpackage handles recording.Messaging:Enabled: false— eventing is off until you opt in (we’ll turn it on in step 5).Transports: ["RestApi"]— REST is the only transport in this starter; we’ll add others later.
1.5 Run the host
Section titled “1.5 Run the host”dotnet run --project ./src/Acme.Store.HostThe first run will restore packages. After a few seconds you should see:
info: Cephalon.Engine[1001] Cephalon Engine started: acme-store modules: Acme.Store.Modules.Health (1.0.0) transports: RestApi capabilities: Audit manifest schema: v2info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:50001.6 Talk to the engine
Section titled “1.6 Talk to the engine”In a second shell:
curl http://localhost:5000/engine/manifestYou should see JSON with the engine id, the registered module, capabilities, and a manifest schema version. Try also:
curl http://localhost:5000/engine/runtimecurl http://localhost:5000/healthcurl http://localhost:5000/scalar/v1The last one opens the Scalar docs UI in the browser if you point at it — it’s the REST documentation surface the AspNetCore host wires automatically.
1.7 What the starter test project gives you
Section titled “1.7 What the starter test project gives you”Open the composition smoke test:
public sealed class CompositionSmokeTests{ [Fact] public async Task Host_composes_successfully() { await using var host = await TestHostFactory.CreateAsync(); var manifest = host.Services.GetRequiredService<IRuntimeManifest>();
manifest.EngineId.Should().Be("acme-store"); manifest.Modules.Should().NotBeEmpty(); manifest.Modules.Should().Contain(m => m.Name == "Acme.Store.Modules.Health"); }}That single test fails the moment something stops composing. It is the canonical “did I break it?” check. Keep it green at every commit.
What you should have now
Section titled “What you should have now”-
Acme.Storedirectory generated. - Host runs and prints the startup banner.
-
/engine/manifestreturns JSON. -
dotnet test ./tests/Acme.Store.Host.Testsis green.
Pitfalls we hit the first time
Section titled “Pitfalls we hit the first time”- The CLI prerelease flag. Forgetting
--prereleaseoncephalon newwill pull a stable version that doesn’t exist yet. The new command itself doesn’t require it, but the install does. - Port already in use. Set
ASPNETCORE_URLS=http://localhost:5050if:5000is taken. - Centralised package management. Don’t add
Version=to.csprojlines. Add the package toDirectory.Packages.propsinstead.
What to do next
Section titled “What to do next”- Step 2 → Add a domain module.
- Read Concepts → Module if the module shape is still unclear.
- Skim Reference → Architecture → Runtime contracts to see what the engine actually exposes.