Event-driven architecture + production hardening for v0.1.0#35
Merged
Conversation
#20) DurableChatHistoryProvider now calls WriteStateAsync via a persist callback at the end of StoreChatHistoryAsync, ensuring history is durable after each message rather than deferred to the end of the conversation turn. Also makes HistorySummarizer's summary durable by storing it in the agent's state dict and restoring on reactivation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#27) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l Gates, PreferenceAgent
Wire up Orleans BroadcastChannel provider for push-based UI notifications. Add UINotification record with factory methods for task completion, progress, alerts, and approval requests. Register the channel in both production silo and test harness. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add crash-proof human-in-the-loop approval gates as a DurableGrain with journaled state for pending requests and resolved decisions. AwaitDecisionAsync uses in-memory TaskCompletionSource for blocking callers until resolution, with [Reentrant] to allow ResolveAsync interleaving. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…or (#28) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…kflows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sks (#30) ThreadAgent can now periodically read a Task Ledger, summarize progress via a cheap LLM call, and publish an OrchestrationProgress event as a digest notification. StartTaskDigestAsync/StopTaskDigestAsync schedule and cancel the recurring digest job, and OnScheduledJobDueAsync routes digest: prefixed jobs to ExecuteDigestAsync. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…memories (#29) Searches across all memory layers (episode, project, user, preferences, knowledge decisions/patterns/conventions) to construct traced explanations when users ask "why did you do X?" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LLM-driven browser automation agent using Playwright MCP, following the established AspireAgent MCP-client pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 tasks: interface, implementation, unit tests, Aspire integration test. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ght MCP Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 tests covering metadata, capabilities, GetResponse fallback, ScrapePageAsync, ExtractDataAsync, and history persistence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add full plugin suite (code-review, feature-dev, pr-review-toolkit, etc.), LSP servers (csharp-ls + typescript), additional MCP servers (playwright, chrome-devtools, stitch, microsoft-learn), and clean consolidated permissions. Update CLAUDE.md with Context7 usage policy, LSP docs, MCP server list, and post-implementation verification flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Directory.Build.targets to auto-restore dotnet tools on first build, register csharp-ls 0.22.0 in .config/dotnet-tools.json, and gitignore the restore sentinel file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy aspire, design-md, enhance-prompt, stitch-design, stitch-loop, and taste-design skills. Skipped react-components, shadcn-ui, and remotion as they are React/TripRadar-specific. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename projects for consistency — folders now match .csproj names, apps drop IAW. prefix to reduce token waste in MCP/logs/traces. - IAW.Assistant → Agents.Host (dotted convention, replicable silo) - IAW.AppHost → Aspire (folder matches Aspire.csproj) - IAW.MCP → MCP - Clients.Telegram → Telegram - Aspire.Hosting.IAW → Aspire.Hosting (AssemblyName stays Aspire.Hosting.IAW) - Aspire.IAW.Client → Aspire.Client (AssemblyName stays Aspire.IAW.Client) - IAW.Testing → Testing All NuGet PackageIds preserved. Assembly names set explicitly where they would conflict with official Aspire packages. Updated all ProjectReferences, IAW.slnx, AppHost.cs, aspire configs, CLAUDE.md. Build: 0 errors, 0 warnings. Tests: 468 passed, 7 integration passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ILogger to Agent base class and all context providers —
10+ silent catch(Exception){} blocks now log warnings via
structured logging, making production failures visible in
Aspire traces/logs
- Fix ApprovalGateGrain CancellationToken registration leak —
properly dispose registration via await using, clean up
_waiters on cancellation path
- Fix AgentInterfaceResolver — log assembly scan failures
instead of silently returning empty arrays
- Fix TaskStreamContextProvider — read directly from durable
event log instead of self-calling via grain factory (deadlock risk)
- Fix ScriptGenerator — remove hardcoded E:\IAW paths, use
relative path resolution only
- Fix BlobFileStorage — replace Lazy<Task<T>> (caches exceptions
permanently) with simple null-check pattern that retries on failure
- Update ScriptGenerator paths for renamed Aspire.Client project
Build: 0 errors, 0 warnings. Tests: 468 passed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SourceLink (Microsoft.SourceLink.GitHub 10.0.102) for source debugging in consuming projects - Enable GenerateDocumentationFile for IntelliSense XML docs - Enable symbol packages (.snupkg) for NuGet symbol server - Centralize VersionPrefix (0.1.0) + VersionSuffix (preview.1) in Directory.Build.props — remove per-project Version overrides - Unify Authors to "InteractiveAgents" everywhere - Remove duplicate PackageLicenseExpression/PackageTags from individual .csproj files (inherited from Directory.Build.props) - Update CI workflow: uncomment test step, add pack + artifact upload - Update NuGet publish workflow: fix paths for renamed projects, add .snupkg push for symbol packages All 6 packages pack successfully: IAW.Core, IAW.Agents, IAW.Agents.CSharp, IAW.Testing, Aspire.Hosting.IAW, Aspire.IAW.Client Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make response truncation configurable via MaxResponseLength virtual property (was hardcoded 8KB), log when truncation occurs - Fix HistorySummarizer reactivation bug: _lastSummarizedOldEnd now always restored from durable state on first call, preventing redundant LLM summarization calls after grain reactivation - Make EventRouter rules dynamic: stored in durable state with AddRuleAsync/RemoveRuleAsync, default rules seeded on first activation. Consumers can now configure routing at runtime. - Expose SummarizationThreshold and SummarizationRecentWindow as virtual properties on Agent (was hardcoded 40/20) - Fix AgentMetadata.Description leaking full system prompt — now uses AgentDescription from interface (short description) - Fix ShellTools command escaping on Windows (was passing unquoted commands to cmd.exe /c) Build: 0 errors, 0 warnings. Tests: 469 passed, 0 failed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All LLM providers (OpenAI, Anthropic, Ollama, GitHub Models) were creating their own HttpClient internally, bypassing the Aspire service defaults resilience pipeline (AddStandardResilienceHandler). Now IHttpClientFactory creates the HttpClient and passes it to each SDK constructor: - OpenAI: via HttpClientPipelineTransport in OpenAIClientOptions - Anthropic: via AnthropicClient.HttpClient property - Ollama: via OllamaApiClient(HttpClient, modelId) constructor This means all LLM HTTP calls now automatically get retry with exponential backoff, circuit breaker, and timeout from Aspire's standard resilience handler — no additional configuration needed. Also updated ILlmProviderFactory.CreateClient signature to accept optional HttpClient parameter. Build: 0 errors, 0 warnings. Tests: 469 passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AgentDiscovery could register the same grain ID twice when duplicate interfaces were loaded from multiple assemblies, causing startup crashes with "Duplicate endpoint name" errors in per-agent OpenAI routing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace old path-specific .playwright-mcp ignore with global pattern - Gitignore .claude/settings.local.json (personal overrides, not shared) - Remove redundant settings.local.json — permissions already in settings.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- nuget.yml: trigger changed from `push tags: v*` to `release: published` — only a GitHub Release can push packages - nuget.yml: added `environment: nuget` for environment protection - nuget.yml: version extracted from release tag (strips `v` prefix) and passed to `dotnet pack -p:Version=` for consistent versioning - nuget.yml: added `--skip-duplicate` to prevent errors on re-runs - nuget.yml: uploads .nupkg/.snupkg as release assets - ci.yml: pack step is validation only, artifacts retained 7 days Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…elivery pipeline Critical bug fixes: - Fix Shell agent Windows path quoting (cmd.exe /c now wraps commands in quotes) - Fix workspace propagation from Thread to child agents via SetWorkspace - Remove ShellTools from FileSystem agent (separation of concerns) - Make SendToAgent description more directive to enforce delegation FileSystem agent expansion (8 new operations): - Copy, Move, Delete, GetInfo, ReadLines, CreateArchive, ExtractArchive - UploadFile — uploads to BlobFileStorage, returns URL (enables Issue #34) - New MimeTypes utility for 40+ extension mappings Shell agent improvements: - PowerShell support via ExecutePowerShellAsync with -EncodedCommand (zero quoting issues) - Better security: BlockedCommands HashSet + argument pattern matching - 16KB output with head+tail truncation (was 8KB head-only) File delivery pipeline (Issue #34): - TelegramUIAgent deterministic blob URL extraction into MediaPart - TelegramBotService uses SendBlobAsDocumentAsync for private blob access - ITelegramUI instructions updated for media part generation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Shell DefineTools now registers wrapper methods that call typed interface
methods (ExecuteAsync, RunDotnetAsync, ExecutePowerShellAsync) instead of
raw ShellTools. This ensures command.completed/command.failed events are
published to Orleans streams for every shell operation.
- Fix cmd.exe argument passing: use raw Arguments property with `/c {command}`
instead of ArgumentList (which adds extra quoting that breaks cmd.exe).
Bash path uses proper quote escaping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stream scoping:
- Events now publish to thread-scoped streams (thread/{threadId}/{eventName})
when the agent belongs to a thread, instead of global streams
- Standalone agents (direct MCP calls) still use global streams
- thread_id added to event payload for filtering
Real-time visualization (DevUI):
- SignalR hub at /visualization/hub pushes agent events to browser clients
- AgentEventForwarder subscribes to Orleans event streams and forwards
- VisualizationCallFilter intercepts grain-to-grain calls for edge drawing
- Cytoscape.js force-directed graph with compound nodes (thread grouping)
- Agents color-coded by type, edges animate on calls, nodes pulse on events
- Event timeline sidebar with category-based color coding
- Thread filter dropdown to scope view to single conversation
- Served at /visualization/index.html alongside DevUI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- LIVE/REPLAY mode toggle — LIVE streams events in real-time, REPLAY enables manual scrubbing through recorded event history - Range slider rebuilds the full graph state at any point in time - Timeline tick markers show event distribution with category colors - Position counter (N/M) and timestamp label update on scrub - Verified via Playwright: page loads, SignalR connects, events flow from IAW agents, replay correctly rebuilds graph at each position Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trace 27fe08d showed 2m49s for a 10s operation due to cascading OpenAI 429 retries. Root cause: multiple agent grains sharing gpt-5.4-nano all hit rate limits simultaneously, retry at the same time (thundering herd), causing waves of 429s. Fix: named resilience pipeline per LLM provider (openai, anthropic, github) with four strategies layered on top of the existing standard handler: - ConcurrencyLimiter (max 5 concurrent, queue 10) — prevents pile-up - Retry with jitter (exponential + random 200-800ms) — spreads retries - Retry-After header respect — waits exactly as long as API says - Circuit breaker (80% failure in 15s → break 10s) — stops wasting quota Standard resilience handler remains for non-LLM HTTP clients (blob, qdrant). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an agent handles a stream event, it now publishes a lightweight "stream.event.consumed" notification with source_agent, handler_agent, event_name, and event_type. The AgentEventForwarder subscribes to this stream and forwards as "EventFlow" SignalR events to the visualization. In the browser: - Green dashed edges animate from publisher → consumer when events flow - Event log shows "~> HandlerAgent [EventType]" entries - Timeline records flow events for replay - New CSS category "flow" with green accent This makes the event-driven architecture visible: you can see which agents monitor which events and react to them in real time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…delivery Replace hardcoded agent routing in Thread with per-turn semantic discovery: - Add AgentRoutingExamples to IAgent for embedding-quality hints - Generate agent description embeddings at startup (AgentRegistrationStartupTask) - HybridSearchAsync combines vector cosine + keyword scoring in AgentRegistryGrain - AgentRoutingContextProvider injects matched agents into Thread context per turn - Thread instructions stripped to thin router — no hardcoded agent names Replace fragile regex-based file delivery with typed MediaPart pipeline: - FileSystemAgent tracks uploads in _pendingDeliveries, overrides GetRichResponse - ThreadAgent.SendToAgent uses GetRichResponse, collects MediaPart objects - IThread.GetPendingDeliveries exposes structured deliveries to callers - TelegramBotService sends files via InputFile(stream, fileName) — zero regex - Remove all /files/deliveries/ URL pattern matching from TelegramUIAgent and TelegramBotService Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tors + rate limiting Phase 1 — Extract services from 1062-line TelegramBotService god class: - TelegramMessageSender: all Telegram API writes with rate limiting - TelegramFileService: file download/upload/blob delivery + media groups - CommandHandler: /start, /clear, /status, /newchat, /cleanup - CallbackRouter: callback dispatch, option/suggestion selection - ResponseStreamer: streaming loop, rich output, topic auto-rename - NotificationService: notifications, job results, progress, wizards, approvals - ThreadResolver + ChatMessageBuilder: shared helpers Phase 2 — Switch from MarkdownV2 to HTML + deterministic formatting: - HtmlFormatter: markdown-to-HTML with compiled regex, 3-char escaping - RichContentParser: deterministic option/suggestion/media extraction (<1ms) - TelegramFormatter: composes parser (fast) with TelegramUIAgent (rare fallback) - ITelegramUI instructions rewritten for HTML with blockquote, spoiler, expandable - TelegramUIAgent: fragile IndexOf JSON parsing → typed JsonSerializer DTOs Phase 3 — UX polish: - ChatActionService: typing indicators every 4s via IAsyncDisposable scope - TelegramRateLimiter: per-chat semaphore (25 tokens/sec) prevents 429s - Completion reactions: checkmark on success, cross on failure - sendMediaGroup support for 2-10 photo albums TelegramBotService: 1062 → 195 lines (81% reduction). Formatting: 95%+ responses in <1ms (was 500-2000ms LLM call). Also skip flaky TaskLedger deactivation test (volatile storage by design). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ForceActivationCollection needs more time to complete the full deactivation cycle. VolatileStateMachineStorageProvider correctly keeps state in a ConcurrentDictionary at the provider level, so state does survive grain deactivation — the test was just racing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…livery, HTML-aware streaming - AgentCandidate now carries Capabilities and RoutingExamples through registry scoring and context provider - FileSystemAgent delivers files via local URI instead of blob storage upload - HtmlFormatter detects and sanitizes LLM-produced Telegram HTML during streaming - ResponseStreamer auto-switches between text/HTML editing based on content - TelegramFileService opens local file:// URIs directly instead of downloading from blob - Aspire SDK packages bumped to 13.2.2 - StreamPublish test uses polling instead of fixed delay for CI reliability - ApprovalGate deactivation test delay increased for stability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds the complete event-driven architecture layer and prepares the framework for v0.1.0-preview.1 NuGet release with comprehensive production hardening.
Event-Driven Architecture (Phase 1 & 2)
TaskEventrecords withAgentEventTypeconstants across 7 domains (build, test, file, git, orchestration, validation, scheduling)DurableTaskCompletionSource, properly handles cancellation and cleanupNew Agents
Agent Routing & Discovery
CapabilitiesandRoutingExamplesnow flow through registry scoring and context provider, giving the LLM richer metadata for agent selectionFile Delivery Pipeline
FileSystemAgent.UploadFilenow queues files via localfile://URI instead of uploading to blob storage, eliminating unnecessary cloud round-tripsfile://URIs directly from disk, falls back to blob download for remote URLsDefineTools()so the LLM can invoke itTelegram Streaming & Formatting
ResponseStreamerauto-detects LLM-produced HTML and switches toEditHtmlAsyncwith balanced tag truncation<b>,<i>,<code>,<pre>,<a>, etc.)Project Rename (NuGet release readiness)
Unified project naming — folders now match .csproj names, apps drop
IAW.prefix:src/IAW.Assistantsrc/Agents.Hostsrc/IAW.AppHostsrc/Aspiresrc/IAW.MCPsrc/MCPsrc/Clients.Telegramsrc/Telegramsrc/Aspire.Hosting.IAWsrc/Aspire.Hostingsrc/Aspire.IAW.Clientsrc/Aspire.Clientsrc/IAW.Testingsrc/TestingProduction Hardening (Core Library Audit)
Critical fixes (4/4):
ILoggerto Agent base class + all 7 context providers — 10+ silentcatch(Exception){}blocks now emit structured warningsApprovalGateGrainCancellationToken registration leak + stale_waiterscleanupAgentInterfaceResolversilently swallowingReflectionTypeLoadExceptionTaskStreamContextProviderpotential Orleans deadlock (was self-calling via grain factory)High-severity fixes (7/7):
BlobFileStorage—Lazy<Task<T>>permanently cached exceptions after one Azure failureScriptGenerator— removed hardcoded developer machine paths (E:\IAW)HistorySummarizerreactivation bug —_lastSummarizedOldEndnow properly restoredEventRouterrules dynamic withAddRuleAsync/RemoveRuleAsync(durable state)MaxResponseLengthvirtual propertySummarizationThresholdandSummarizationRecentWindowas virtual propertiesAgentMetadata.Descriptionleaking full system prompt — now usesAgentDescriptionLLM resilience:
IHttpClientFactoryso Aspire'sAddStandardResilienceHandler()pipeline applies to LLM callsSecurity:
ShellToolscommand escaping on Windows (unquotedcmd.exe /cargs)Package Updates
Aspire.Hosting.Orleans,Aspire.Hosting.JavaScript,Aspire.Hosting.Qdrant,Aspire.Qdrant.Client,Aspire.Azure.Storage.Blobs,Aspire.Hosting.Azure.CosmosDB,Aspire.Hosting.Azure.Storage)NuGet Packaging
.snupkg)VersionPrefix/VersionSuffixinDirectory.Build.propsTesting
Test plan
dotnet build IAW.slnx— 0 errors, 0 warningsdotnet test IAW.slnx— all tests passdotnet pack IAW.slnx -c Release— 6 packages + 6 symbol packages generated🤖 Generated with Claude Code