Skip to content

Make DigitalBrain observable and product-aware - #54

Merged
LeftTwixWand merged 20 commits into
masterfrom
product/observable-digitalbrain
Jul 28, 2026
Merged

Make DigitalBrain observable and product-aware#54
LeftTwixWand merged 20 commits into
masterfrom
product/observable-digitalbrain

Conversation

@LeftTwixWand

@LeftTwixWand LeftTwixWand commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • migrate the solution to xUnit v4 on Microsoft Testing Platform
  • add shared ServiceDefaults with structured OpenTelemetry logs, traces, metrics, service discovery, and GenAI instrumentation
  • make Gemma4 identify itself as DigitalBrain and describe the exact Gmail-to-Salesforce proposal boundary implemented by the assistant
  • stream AI responses, capture provider/model/token/finish telemetry, and let AppHost opt Development into sensitive prompt/response telemetry with ai.EnableSensitiveData
  • close the MCP loop with retry-safe command IDs, durable chat/transcript/journal tools, and owner-scoped active-neuron discovery without placement addresses
  • add the Flutter Chat, content-safe Activity, and live Brain workspace without presenting configured modules as provider readiness
  • replace the forbidden PowerShell verifier with an explicit cross-platform C# live product suite

Why

The product needed an observable end-to-end path whose claims come from live behavior rather than source configuration. A configured module or model does not prove provider credentials, authorization, or readiness, and source/reflection tests do not prove runtime behavior.

This change keeps journals as the causal source of truth, makes interrupted MCP chat calls safe to resume, and emits useful GenAI telemetry. Sensitive prompt/response capture remains off by default and is enabled explicitly by the product AppHost only when its environment is Development.

Impact

Users get an honest DigitalBrain identity, explicit external-action boundaries, durable retry behavior, and a Flutter workspace for Chat, Activity, and live Brain topology. Operators get correlated journal and OpenTelemetry evidence without exposing raw silo placement. Production telemetry remains content-safe by default; local Development can opt into prompt/response capture for diagnosis.

Validation

  • dotnet build DigitalBrain.slnx -c Release --verbosity minimal — exit 0
  • dotnet test DigitalBrain.slnx -c Release --no-build --verbosity minimal — 252 total, 250 passed, 2 explicit skips, 0 failed
  • dotnet test os/tests/DigitalBrain.OS.Product.Tests/DigitalBrain.OS.Product.Tests.csproj -c Release -- -explicit only — 1/1 passed against real Gemma/Aspire; verified streaming response, retry deduplication, transcript/journal correlation, owner-scoped introspection, token usage, finish reason, and Development prompt/response telemetry
  • Dart wire tests — 6 passed
  • Flutter package — analysis clean, 21 tests passed
  • Flutter shell — analysis clean, 16 tests passed, Windows Release build succeeded
  • git diff --check clean; no *.ps1 files remain

Adds a self-cleaning Aspire proof for resource health, the exact MCP catalog, a real Gemma4 turn, durable transcript correlation, and telemetry hygiene. Updates the repository status to the measured observable product state.\n\nNo unconsumed product surface was added; the verifier is linked from the contributor workflow. All capability claims were checked by the script's live successful run. The three pre-existing Flutter generated-file changes remain unstaged and untouched.
Adds responsive Chat, Activity, and Brain surfaces over one shared durable chat projection. Activity exposes sequence and command facts without message content, while Brain describes only the product's measured proposal-gated capabilities.\n\nEvery added surface has a current UI consumer. Claims were checked by Flutter/Dart analysis and tests, the Windows Release build, and the 253-test root MTP gate. The three pre-existing Flutter generated-file changes remain unstaged and untouched.
Consumers: topology polling, capability rendering, inspector state, activity facts, and pulse visualization all consume this slice today.

Verified: wire/client/shell analyses and tests, UI edge tests, Release root build and 255-test MTP gate, and Windows Release build.

Preserved: three pre-existing generated Windows Flutter files remain unstaged.
Consumers: the UI topology resolves an Aspire-projected client manifest and Flutter renders only explicit product capabilities.

Verified: 258-test root MTP gate, all client analyses/tests, Windows Release build, and live Gemma4 verification with six modules and two configured capabilities.

Preserved: three pre-existing generated Windows Flutter files remain unstaged.
Project model-specific client-safe LLM features so the Gemma4-backed assistant is advertised only when Gemma4 is actually configured. Cover non-Gemma rejection and Gemma composition projection.
Comment thread src/modules/ai/DigitalBrain.Modules.AI/Clients/AIClients.cs Fixed

@LeftTwixWand LeftTwixWand left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extreme multi-agent PR review — synthesis

Verdict: COMMENT (no P0/P1 after adversarial calibration)

Agents: 100/100 child agents completed (Phase1:10 + Phase2:60 + Phase3:24 + Phase4:6). Coordinator not counted.

Complete PR diff reviewed: master...product/observable-digitalbrain (83 files, +4182/−369, 10 commits, HEAD 89e42a6).

Findings posted after adversarial verification (≥2 validators + Phase-4 audits):

Pri ID Title
P2 F-005 Assistant always registers enrichment tool; catalog gates separately
P2 F-007 AddStandardResilienceHandler applies to MCP HttpClient factory clients
P2 F-008 Ollama HttpClient default ~100s under 5m ResponseTimeout
P2 F-011 send_chat_message mints CommandId server-side; not retry-safe after timeout
P3 F-001 UseOpenTelemetry does not pin EnableSensitiveData=false
P3 F-009 MCP max wait 180s vs neuron/agent LongRunning 5m
P3 F-010 verify-product.ps1 final aspire stop ignores exit code

Discarded candidates (representative): ~40+ raw Phase-2 candidates collapsed; 12 clustered IDs reduced further. Discarded includes: desktop SSE no-reconnect (pre-existing, not PR-introduced), list_active_neurons silo dump (pre-existing), Guid "N"/"n" casing (not a real bug), empty AssistantAnswered hang (ChatNeuron line not PR-changed; exposed by MCP wait but no tight PR anchor), enrichment “approval” overclaim (copy already says proposal-only).

Validation commands actually run:

  • gh auth status — OK (LeftTwixWand)
  • gh pr view 54 --json ... — OK
  • git rev-parse HEAD / git merge-base master HEAD / git diff --stat master...HEAD — OK
  • dotnet --version — 11.0.100-preview.6.26359.118
  • Static re-reads of AIClients, ServiceDefaults, DigitalBrainMcpTools, Assistant, ChatNeuron, verify-product.ps1
  • 100 agent tool-using reviews (read/grep/gh/git); no claim of green root dotnet test or live verify-product.ps1 in this review run

Checks not performed: full Release root gate; live Gemma4 turn; aspire resource health in this session.

Confirmation: Every posted finding survived multi-agent adversarial challenge and Phase-4 intersection (anchors + evidence + severity calibration). F-006 was rejected at Phase-4 line-anchor audit.

completed update. If authentication or a provider is unavailable, report that limitation.
""";

protected override IReadOnlyList<CapabilityTool> Tools =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Assistant always registers enrichment tool; catalog gates separately

Trigger: Any composition with OSBehaviorsModule but without Google/Salesforce modules or ConfiguredFeatures (or future optional provider install).

Impact: Model always receives �nrich_account_from_email and system text claiming that external action. BrainCapabilityCatalog only advertises �ccount-enrichment.gmail-salesforce-description when modules + features match — dual capability truth.

Why permitted: PR-added Instructions hardcode the tool as always available; Tools is an unconditional list. Catalog is UI-only and never consulted by Agent.Respond.

Smallest fix: Gate Tools/Instructions with the same module/feature predicates used by BrainCapabilityCatalog (without importing Flutter.Http), or compose separate assistants for product vs thin graphs.

builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] AddStandardResilienceHandler applies to MCP factory HttpClients

Trigger: Product silo calls AddServiceDefaults(); Google/Salesforce open MCP sessions via IHttpClientFactory.CreateClient(""DigitalBrain.Mcp"") after services.AddHttpClient().

Impact: Standard resilience defaults (including retries on all HTTP methods and short total/attempt timeouts) wrap outbound MCP POSTs. Non-idempotent tool calls can be double-submitted on transient 5xx/timeouts.

Why permitted: New ServiceDefaults uses ConfigureHttpClientDefaults without a named MCP client that removes or retunes resilience.

Smallest fix: Register AddHttpClient(McpRuntime.HttpClientName) with RemoveAllResilienceHandlers() (or disable unsafe-method retries and set MCP-appropriate timeouts). Keep generic defaults for other clients.

var tag = configuration[$"{ConfigurationRoot}:Ollama:{modelName}:Model"] ?? defaultTag;

return new OllamaApiClient(endpointUri, tag);
return new ChatClientBuilder(new OllamaApiClient(endpointUri, tag))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Ollama client keeps default ~100s HTTP timeout under 5m grain budget

Trigger: Product chat uses Gemma4 via
ew OllamaApiClient(endpointUri, tag) while IAgent.Respond declares [ResponseTimeout(NeuronCallTimeouts.LongRunning)] (5 minutes). Non-streaming GetResponseAsync is bounded by HttpClient.Timeout default (100s).

Impact: Slow model generations fail at the HTTP layer before the declared Orleans response budget applies; core chat path can fail on long but valid turns.

Why permitted: PR wraps Ollama in ChatClientBuilder/UseOpenTelemetry but still constructs OllamaApiClient without an HttpClient whose Timeout matches LongRunning.

Smallest fix: Pass
ew HttpClient { BaseAddress = endpointUri, Timeout = TimeSpan.FromMinutes(5) } (or slightly higher) into OllamaApiClient.

JournalKind.Outgoing,
afterSequence: 0);
var commandId = CommandId.New();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] send_chat_message mints CommandId server-side; not retry-safe after timeout

Trigger: Client/operator retries the tool after a wait timeout (or transport drop) once IChat.Send has already committed UserMessaged.

Impact: Each call does CommandId.New() then Send. A retry creates a second durable user turn and can start a second model chain, while the first turn may still complete.

Why permitted: Tool API has no client-supplied command/idempotency key; timeout only aborts the journal poll after Send.

Smallest fix: Accept optional commandId; if that command’s UserMessaged already exists, skip Send and only wait. Document that retries without a command id are not idempotent.


return new OllamaApiClient(endpointUri, tag);
return new ChatClientBuilder(new OllamaApiClient(endpointUri, tag))
.UseOpenTelemetry(sourceName: $"{TelemetrySource}.{modelName}")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] UseOpenTelemetry does not pin EnableSensitiveData = false

Trigger: Process env OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true (or equivalent) with OTLP export enabled.

Impact: MEAI can export gen_ai.input.messages / gen_ai.output.messages (and related sensitive tags), violating the product content-hygiene rule that GenAI telemetry excludes prompt/response bodies. Defaults are safe; the pin is missing defense-in-depth.

Why permitted: Both Ollama and OpenAI pipelines call .UseOpenTelemetry(sourceName: …) with no configure callback.

Smallest fix: .UseOpenTelemetry(sourceName: …, configure: static c => c.EnableSensitiveData = false) on both builders. Keep verify-product attribute absence check.

[Description("User prompt for Llama 3.2")] string prompt)
private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(50);
private const int DefaultTimeoutSeconds = 120;
private const int MaximumTimeoutSeconds = 180;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] MCP max wait (180s) is shorter than neuron/agent LongRunning (5m)

Trigger: Model+tool turn legally runs between 181s and 300s under NeuronCallTimeouts.LongRunning.

Impact: send_chat_message throws TimeoutException while the grain turn may still complete and journal AssistantResponded later — false timeout relative to the product long-running budget.

Why permitted: MaximumTimeoutSeconds = 180 is independent of NeuronCallTimeouts.LongRunning = ""00:05:00"".

Smallest fix: Align maximum (and default, if desired) with LongRunning, or document explicitly that MCP wait is a stricter operator cap than the grain budget.

Comment thread scripts/verify-product.ps1 Outdated
Pop-Location

if ($started -and -not $KeepRunning) {
& aspire stop --apphost $appHost --non-interactive

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Final �spire stop ignores exit code

Trigger: �spire stop fails after a green proof ($started true, not -KeepRunning).

Impact: Script can exit success while AppHost (and fixed MCP port 5000 / Flutter process) remains running, breaking the “self-cleaning oracle” contract and the next local run.

Why permitted: Proof steps use Invoke-Checked; the inally stop is bare & aspire stop without $LASTEXITCODE handling.

Smallest fix: On the success path, check stop’s exit code (without masking an earlier try failure). Fail the script if stop fails after a successful proof.

@LeftTwixWand

Copy link
Copy Markdown
Contributor Author

Agreed repair plan

After reviewing the diff, current checks, and the updated requirements, this is the implementation plan:

  1. Use a genuinely streaming GenAI path with complete telemetry

    • Change Agent.Respond to consume GetStreamingResponseAsync, preserve the tool-calling loop, and aggregate the streamed updates into the final ChatResponse without losing usage or finish-reason data.
    • Adapt the IAW StreamingUsageChatClientExtensions approach so OpenAI-compatible streaming requests send stream_options.include_usage = true; retain Ollama's native final usage update instead of applying provider-specific options indiscriminately.
    • Configure OpenTelemetryChatClient.EnableSensitiveData = hostEnvironment.IsDevelopment(): enabled in Development and explicitly disabled in every other environment.
    • Add deterministic and live verification for provider/model, finish reason, positive input/output token counts, duration/token metrics, and trace ancestry whose db.correlation matches the durable journal. Verify captured input/output content in Development and its absence outside Development.
  2. Align long-running limits at five minutes

    • Use one shared five-minute budget for the Ollama HTTP client, MCP wait default/max, Orleans long-running response, model/resource startup, and live-verifier turn waits.
    • Keep HTTP-client ownership and disposal explicit while making this change.
  3. Create one source of truth for capabilities

    • Project configured features to both silo and client resources.
    • Use one shared capability resolver for Assistant instructions/tools and the UI manifest.
    • Cover full and partial feature compositions with tests.
  4. Make chat and MCP retries safe

    • Accept a caller-supplied CommandId and deduplicate atomically inside ChatNeuron; a retry must resume waiting for the existing durable turn rather than create another one.
    • Exempt the named MCP HttpClient from unsafe standard retries and inappropriate default timeouts.
  5. Repair verification and repository compliance

    • Stabilize AppHost cleanup; the current framework check failed during fixture cleanup and then hung until cancellation.
    • Remove the forbidden scripts/verify-product.ps1 and replace it with an explicit cross-platform .NET/Microsoft Testing Platform live verifier. Update README/CLAUDE references and claims accordingly.
    • Run the root .NET suite, Dart tests, Flutter tests, Windows build, and the explicit live product proof before resolving review threads with evidence.

Existing thread disposition

  • Keep and fix F-005, F-007, F-008, F-009, and F-011.
  • Rework F-001 to Development=true, every other environment=false.
  • F-010 is superseded by removal of the forbidden PowerShell script.
  • Resolve the CA2000 bot finding as a false positive: the registered telemetry wrapper owns and disposes its inner chat client.
  • The missing commit-message grill answers are a separate history decision because correcting them requires an explicitly approved history rewrite/force-push.

Collapse needlessly chopped method/ctor/new argument lists across product,
OS, and fixture C#. Long or intentional multi-line forms left alone.
Formatting only; no behavior changes.
public static string FlutterShellDirectory => Path.Combine(
FlutterClientDirectory,
"shell");
public static string FlutterShellDirectory => Path.Combine(FlutterClientDirectory, "shell");
Comment thread os/tests/DigitalBrain.OS.Host.Tests/FixtureExclusivity.cs Fixed
Comment thread os/tests/DigitalBrain.OS.Host.Tests/FixtureExclusivity.cs Fixed
Stream model turns with GenAI usage telemetry and AppHost-controlled sensitive capture. Make MCP chat retries idempotent and introspection owner-scoped. Remove inferred capabilities, mirror tests, and the forbidden PowerShell verifier; replace the latter with an explicit live product proof.

No-consumer check: none; every addition is consumed by the AppHost, MCP flow, or live product path.

Unverified-claim check: none; Release build, the 252-test root gate, the explicit Gemma product proof, and all Dart/Flutter gates were run.

Unowned-change check: preserved the user's formatting pass; unchanged generated Flutter files were not staged.
@LeftTwixWand

Copy link
Copy Markdown
Contributor Author

Applied the repair in c3bee8178997db17b662ea898ee50644be3c3485.

What changed:

  • Removed the forbidden scripts/verify-product.ps1; replaced it with an explicit cross-platform C# live product suite.
  • Switched agent turns to streaming and added GenAI OpenTelemetry for Ollama/OpenAI, including OpenAI streaming usage capture.
  • Added ai.EnableSensitiveData; the AppHost now sets it from builder.Environment.IsDevelopment(). It is projected only to the silo, remains false by default, and the Development launch profile makes local prompt/response capture explicit.
  • Raised model/MCP operation budgets to five minutes and removed automatic HTTP resilience retries from unsafe MCP POST calls.
  • Made send_chat_message retryable via caller command IDs with durable deduplication; active-neuron discovery is owner-scoped and no longer exposes silo addresses.
  • Removed the inferred capability catalog/UI (General assistant, Gmail → Salesforce) because configuration flags did not prove provider credentials, authorization, or readiness.
  • Removed source/reflection/DI tests that only restated configuration; retained or added tests at real behavior and projection boundaries.

Fresh verification on the committed tree:

  • dotnet build DigitalBrain.slnx -c Release --verbosity minimal — exit 0.
  • dotnet test DigitalBrain.slnx -c Release --no-build --verbosity minimal — 252 total, 250 passed, 0 failed, 2 explicit skips.
  • dotnet test os/tests/DigitalBrain.OS.Product.Tests/DigitalBrain.OS.Product.Tests.csproj -c Release -- -explicit only — 1/1 passed against real Gemma/Aspire. It verified streaming response, retry deduplication, transcript/journal correlation, owner-scoped introspection, token counts, finish reason, and Development prompt/response telemetry.
  • Dart wire: 6/6; Flutter package: analyze clean and 21/21; Flutter shell: analyze clean, 16/16, Windows Release build succeeded.
  • git diff --check clean; repository contains no tracked or untracked *.ps1 files.

Comment on lines +64 to +68
var http = new HttpClient
{
BaseAddress = endpointUri,
Timeout = RequestTimeout,
};
Timeout = RequestTimeout,
};

return new ChatClientBuilder(new OllamaApiClient(http, tag))
Comment on lines +78 to +80
catch
{
}
Comment on lines +99 to +101
catch
{
}
Comment on lines +78 to +80
catch
{
}
Comment on lines +99 to +101
catch
{
}
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null
&& !File.Exists(Path.Combine(directory.FullName, "DigitalBrain.slnx")))
Give journal watch calls the same long-running Orleans response contract as journal reads, and allow AppHost graph cleanup the five-minute resource budget used by startup and operations.

No-consumer check: none; the existing SSE journal path and AppHost fixture cleanup consume these boundaries.

Unverified-claim check: none; both owning suites and the exact solution-wide CI command were run.

Unowned-change check: none.
Delete the redundant per-resource stop-command pass. DistributedApplication.StopAsync and DisposeAsync own graph teardown; individual stop commands are test actions and returned false failures on Linux CI even when the graph and exclusive lease were released.

No-consumer check: none; this removes an unneeded layer rather than adding surface.

Unverified-claim check: none; the host suite passed twice and the exact 252-test CI command passed.

Unowned-change check: none.
@LeftTwixWand

Copy link
Copy Markdown
Contributor Author

CI follow-up after the review fixes:

  • Linux exposed the default 30-second Orleans response boundary on streaming Watch calls. Both watch contracts now declare the shared five-minute long-running timeout.
  • The host fixture was redundantly invoking per-resource stop commands before stopping and disposing the Aspire graph. That command path failed for the silo. Cleanup now has one owner: DistributedApplication.StopAsync plus DisposeAsync, under the five-minute cleanup budget.

Verification on final SHA 93fefc8d5143dbe06c81907aa32a311df4783730:

  • Local Release solution test: 252 total, 250 passed, 0 failed, 2 skipped.
  • Host exclusivity suite: 6/6 passed twice back-to-back.
  • GitHub Actions run 30378490192: passed (3m10s).

The branch is clean and the remote head matches this SHA.

@LeftTwixWand
LeftTwixWand marked this pull request as ready for review July 28, 2026 16:55
@LeftTwixWand
LeftTwixWand merged commit e90b7b4 into master Jul 28, 2026
2 checks passed
@LeftTwixWand
LeftTwixWand deleted the product/observable-digitalbrain branch July 28, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant