Quick reference

🚨 The portal hosts live in MeshWeaver.Plugins since 2026-08-26 (MeshWeaver#2293 β€” the GUI extraction). Memex.Portal.Monolith, Memex.AppHost and Memex.Portal.Distributed are under src/ in that repository, and so are the login pages (Memex.Portal.Gui). A core checkout on its own has no portal to run: following an older copy of this page from core alone gives a process with no login UI and no control views (#2367). Every command below assumes the standard sibling layout β€” MeshWeaver/ and MeshWeaver.Plugins/ side by side β€” which is what plugins' src/Directory.Build.props defaults MeshWeaverRoot to (../MeshWeaver); set -p:MeshWeaverRoot=<path> if yours differs.

When you change code in Memex.Portal.Distributed (or any project it references β€” MeshWeaver.AI, MeshWeaver.Graph, MeshWeaver.Hosting.Orleans, …), you have three ways to apply the change without touching the whole Aspire stack:

Aspire Stack β€” what each restart option touches Memex.AppHost (aspire run) Orchestrator β€” restart only when AppHost wiring changes Β· cost: 30–60 s Postgres container Survives all 3 options Blob storage container Survives all 3 options Dashboard / OTLP Survives all 3 options Memex.Portal.Distributed Hub state Β· AI workers Β· SignalR sessions β‘  dotnet watch Auto file-save Β· seconds β‘‘ Dashboard restart Resources β†’ β‹― β†’ Restart Β· ~10 s β‘’ Kill process Kill & Aspire auto-restarts Β· ~5 s

All three options restart only Memex.Portal.Distributed; containers, dashboard, and OTLP collector stay up.

Approach Typical cost When to use
dotnet watch --project ../MeshWeaver.Plugins/src/Memex.AppHost Seconds Default. File save triggers a per-resource restart automatically.
Dashboard UI: Resources β†’ β‹― β†’ Restart ~10 s Watch isn't running, or it missed a change.
Kill the portal process (pkill -f Memex.Portal.Distributed) ~5 s Last resort β€” dashboard hangs or AppHost is wedged.

Do NOT kill the whole aspire / Memex.AppHost process unless you changed AppHost wiring itself. A full restart costs 30–60 s, rebuilds every resource, re-launches the Postgres and blob-storage containers, and invalidates the dashboard's browser-token URL.


Starting Aspire

The image-based AppHost (deploy/aspire/Memex.Deploy.AppHost, and any customer AppHost on the published MeshWeaver.Aspire.Hosting.Memex package) declares its instance as a Deployment record β€” builder.AddMemex("memex").WithImage(…).WithPluginRepo(…)… β€” the same record the Helm chart renders from and the portal binds at boot (Deployment:Record). --record-out <path> writes the final record for the setup wizard or a Provision action. ConfiguringAnInstanceFromAspire.

Three modes β€” pick by whether you want to hold a terminal and whether you need a build:

# A. Interactive (foreground) β€” builds, holds the terminal, prints live status.
#    Registers with `aspire mcp` so MCP tools can drive it. Ctrl+C to stop.
aspire run --project ../MeshWeaver.Plugins/src/Memex.AppHost

# B. Background daemon β€” detaches and returns immediately; survives across shells.
#    `--no-build` reuses the existing binaries (fast β€” no rebuild). Drop --no-build
#    to build first. Also registers with `aspire mcp`.
aspire start --no-build --project ../MeshWeaver.Plugins/src/Memex.AppHost
aspire ps                 # list running AppHosts (Path Β· PID Β· dashboard URL)
aspire stop               # stop the background AppHost
aspire logs [<resource>]  # tail logs without the dashboard

# C. Visual Studio's "F5" path.
dotnet run --project ../MeshWeaver.Plugins/src/Memex.AppHost

🚨 aspire start --no-build reuses the LAST build. It's the fast way to bring the stack back up, but it does not pick up source changes β€” if you edited code, either drop --no-build, build the changed project first, or use one of the per-resource reloads below. Use --no-build when the binaries are already current (e.g. you just stopped Aspire to run a build and want it back up).

aspire run / aspire start print a one-shot dashboard token URL:

Dashboard:  https://localhost:17200/login?t=<TOKEN>

Open that URL in your browser. The token is per-process β€” restart Aspire and you get a new one. To skip token authentication in development, add the following to ../MeshWeaver.Plugins/src/Memex.AppHost/Properties/launchSettings.json:

"environmentVariables": {
  "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS": "true"
}

Visual Studio's F5 path does this implicitly via its launch profile, which is why it never prompts.


Full stop β†’ rebuild β†’ restart (clean cold-start after a code change)

Hot reload (Option 1) and the per-resource restarts (Options 2–3) are the default. But when you want a clean cold-start with new code β€” a non-trivial change spanning several referenced projects, or recovery from a wedged stack β€” use the explicit stop β†’ rebuild β†’ start cycle. The two steps people miss (and that cause the "I rebuilt but it still runs old code" / "my build won't compile, file in use" loops) are in bold:

On macOS/Linux (zsh/bash):

# 1. Stop Aspire β€” AND force-kill, because `aspire stop` can exit while leaving the
#    AppHost + portal processes alive:
aspire stop
pkill -f Memex.AppHost; pkill -f Memex.Portal.Distributed

# 2. 🚨 RELEASE THE BUILD LOCKS. Lingering MSBuild / VB-C# compiler-server nodes keep
#    obj/*.dll open AFTER the AppHost is gone β€” that is the cause of the rebuild failing with
#    "CS2012: cannot open '…/obj/…/X.dll' for writing" or "MSB3021: being used by another
#    process". Killing the portal is NOT enough; you must shut the build server too:
dotnet build-server shutdown

# 3. Rebuild the portal project (it lives in the sibling MeshWeaver.Plugins checkout; MeshWeaverRoot
#    resolves back to this repo). Building Memex.Portal.Distributed also rebuilds every project it
#    references here (MeshWeaver.AI, .Graph, .Mesh.Contract, …) and in plugins (.Blazor.Portal), so ALL your edits
#    compile β€” this doubles as your compile gate before bringing the stack up:
dotnet build ../MeshWeaver.Plugins/src/Memex.Portal.Distributed/Memex.Portal.Distributed.csproj --no-restore

# 4. Start fast, reusing the build from step 3 (no second compile):
aspire start --no-build --project ../MeshWeaver.Plugins/src/Memex.AppHost

On Windows (PowerShell), step 1's force-kill is instead:

Get-Process Memex.AppHost,Memex.Portal.Distributed,aspire -ErrorAction SilentlyContinue | Stop-Process -Force

Why --no-build is safe here: step 3 already produced current binaries for the portal and everything it references, so --no-build just launches them. The Postgres / blob containers are persistent and survive the stop, so the database is intact across the cycle.


Option 1 β€” Hot reload with dotnet watch

dotnet watch --project ../MeshWeaver.Plugins/src/Memex.AppHost

Save a file and Aspire detects the change, rebuilds the affected project, and restarts only that resource. The Postgres container, blob-storage container, and dashboard all stay up. Most edits apply within seconds.

What hot reload handles and what it doesn't:

Change type Behaviour
Razor component edits Applied in-place via Blazor hot reload β€” no restart.
Method body / addition changes Applied in-place by the runtime hot-reload engine.
Type / interface / DI-registration changes Triggers a resource restart (~5–10 s for the portal).
AppHost wiring changes Requires a full Aspire restart β€” dotnet watch cannot reload its own host.

Option 2 β€” Dashboard restart

Navigate to https://localhost:17200/ β†’ Resources tab β†’ find the resource (e.g. memex-portal-distributed) β†’ click the β‹― menu β†’ Restart.

This is equivalent to running dotnet build and relaunching the resource process. The hub state is wiped (action blocks drained, hosted hubs disposed), but the dashboard, OTLP collector, and every other resource stay up.

Use this when:


Option 3 β€” Process kill (last resort)

pkill -f Memex.Portal.Distributed                                                   # macOS / Linux
Get-Process Memex.Portal.Distributed -ErrorAction SilentlyContinue | Stop-Process -Force   # Windows

Aspire's resource watcher notices the exit and restarts the resource, typically within a few seconds. Use this when the dashboard UI is unresponsive (for example, a layout area is hung in JS and is blocking dashboard rendering), or when you want to confirm a clean cold-start.


What NOT to restart

Process Only restart if… Cost
Memex.AppHost You changed AppHost wiring 30–60 s + container relaunch
aspire (CLI) You're switching projects New dashboard token URL required
dcp Γ— N They crashed (pgrep -l dcp, or Get-Process dcp on Windows) AppHost may not recover; full restart needed
Postgres / blob containers You need a clean database Full Aspire restart + migration replay

When aspire mcp doesn't see your AppHost

If mcp__aspire__list_apphosts returns [] even though Aspire is running, the AppHost was started via dotnet run. Only the aspire CLI writes the discovery file the MCP server reads β€” both aspire run and aspire start do; dotnet run does not.

Fix β€” stop the current AppHost and restart with either CLI form:

aspire run --project ../MeshWeaver.Plugins/src/Memex.AppHost     # foreground
aspire start --project ../MeshWeaver.Plugins/src/Memex.AppHost   # background daemon

After this, MCP tools (list_resources, list_traces, list_structured_logs, execute_resource_command) all work against the running AppHost.


Logging triage from the dashboard

Navigate to https://localhost:17200/structuredlogs?level=info and filter by:

Filter What you find
category contains MessageHub Hub action-block events (HUB_HANDLE_START/END, FinishDelivery)
category contains MessageService Routing failures ("No handler found", "Could not deserialize")
category contains OrleansRoutingService Cross-grain dispatch warnings
category contains Hosting.Orleans.MessageHubGrain Grain activation, GrainDeliver IN/OUT
category contains GrainKeepAlive Heartbeat traffic β€” high volume suggests a stream leak
Message contains SLOW_DISPATCH Per-message latency > 500 ms (instrumented in MessageHub.HandleMessageAsync and OrleansRoutingService.DispatchObservable)
Message contains "Allocating agent" Chat starting β€” should be followed by [ThreadExec] lines
Message contains "Could not deserialize" Type-registry mismatch β€” see DebuggingMessageFlow.md

For distributed traces across resources, open https://localhost:17200/traces and click any trace to see span timing across the AppHost, Portal, and Postgres boundary.


Common gotchas

"No AppHost is currently running" from aspire mcp. The AppHost was started via dotnet run, not aspire run. Restart with the latter (see above).

Builds fail with MSB3021: cannot copy … being used by another process or CS2012: cannot open '…/obj/…/X.dll' for writing. A previous AppHost/portal and/or a lingering MSBuild / VB-C# compiler-server node is holding the binaries open. Killing the portal alone is often not enough β€” the build-server nodes survive it. Force-kill the processes and run dotnet build-server shutdown, then retry. See Full stop β†’ rebuild β†’ restart for the full cycle.

Portal's chat hangs at "Allocating agent…". Almost always one of two causes:

  1. The portal hub's TypeRegistry doesn't have AppendUserMessageResponse registered β€” the response arrives as RawJson and the original Observe never resolves. Fix: WithPortalConfiguration(c => { c.TypeRegistry.AddAITypes(); return c.AddData().WithGraphTypes(); }) in MemexConfiguration (already applied in MemexConfiguration.ConfigureMemexPortal).
  2. Multiple portal hubs created per page navigation (the per-DI-scope shape) β€” a chat response routes to an already-disposed transient portal. Fix: the per-user portal address in PortalApplication.

Both issues are post-mortemed in commit messages on the routing branch β€” see git log --grep "portal hub".

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.