AI 资讯
Distributed Tracing: Following a Request Across Microservices
Distributed Tracing: Following a Request Across Microservices A practical guide to distributed tracing as an architectural discipline — why single-service logging and metrics stop being sufficient once a request crosses many services, how a trace actually reconstructs a request's journey, trace analysis techniques for diagnosing latency and failures, and the specific propagation challenges microservice systems built from this series' REST, gRPC, and messaging guides need to solve. Table of Contents Introduction The Problem Distributed Tracing Solves Anatomy of a Distributed Trace Propagation Across Every Boundary a Request Crosses The Span Tree as a Diagnostic Tool Root Cause Analysis Using Traces Service Maps and Dependency Discovery Latency Analysis Patterns Sampling Strategy for Production Systems Tracing Across Synchronous and Asynchronous Boundaries Tracing Third-Party and Uninstrumented Dependencies Trace-Driven Testing and SLOs Common Pitfalls Quick Reference Table Conclusion Introduction Distributed tracing is the practice of reconstructing a single logical request's complete journey as it travels across every service, database call, and message it touches in a microservice system — not just observing one service in isolation, but stitching together a coherent, end-to-end picture of what actually happened, in what order, and how long each part took. This guide builds directly on this series' OpenTelemetry guide (which covers the mechanics of spans, trace context, and instrumentation) to focus specifically on distributed tracing as an architectural discipline: why it becomes necessary the moment a system splits into multiple services, and how to actually use traces to diagnose real production problems. Trace: "Checkout" (poor total latency: 1,840ms) ├── API Gateway (5ms) ├── OrderService.PlaceOrder (1,820ms) ← the vast majority of the time is HERE │ ├── SQL INSERT (12ms) │ ├── gRPC call to InventoryService (45ms) │ └── HTTP call to PaymentService (1,740ms) ←
AI 资讯
Building an offline-first travel app in .NET MAUI (on-device OCR, currency & maps, no backend)
A build note from Horizon Software , a one-person Android studio. WanderWallet is a travel budget app, and the whole thing runs on the phone: no account, no backend, no cloud. Here's how the parts that look like they need a server actually work without one. The one constraint that shaped everything WanderWallet has a single non-negotiable rule: it has to work with no signal. You're three countries into a trip, your phone's in airplane mode to dodge roaming charges, and you still need to know whether you're on budget. That one requirement quietly makes most of the architectural decisions for you — no login, no server round-trips, and every feature that would normally lean on a cloud API has to earn its keep another way. The stack is deliberately boring: .NET MAUI (Android-first), CommunityToolkit.Mvvm , sqlite-net-pcl for storage, and SkiaSharp for anything I draw myself. Everything the app records lives in a local SQLite database on the device and nowhere else. "Backup" is a file you export and keep — there's no server to back up to . The three features people assume need a backend turned out to be the most interesting to build, precisely because they don't. 1. Currency conversion that survives airplane mode A travel budget app that can't convert currencies offline is useless at exactly the moment you need it. So rates aren't fetched on demand. Whenever the app happens to have a connection it refreshes exchange rates for ~155 currencies and caches the whole table locally . From then on every conversion is local arithmetic — a connection only ever buys you a fresher table, never the ability to convert. The design decision that took me longest to get right: capture the conversion immutably, at entry time. Each expense stores the original amount, its original currency, the converted home-currency amount, and the exact rate used — and that rate is never recalculated: public class Expense { public double Amount { get ; set ; } // in OriginalCurrency public string Origina
AI 资讯
User Connectivity: Making the System Scale with Event Hub Partitions, ACA, and KEDA
Part 3 of the User Connectivity Architecture series. Introduction The first post in this series described the pattern: a heartbeat on a timer, an Event Hub, a worker writing sessions into Redis, and Redis key expiration driving facility online/offline status. One detail matters later. The heartbeat interval is not hard-coded in the client. The API tells the client when to call next, and the default is 30 seconds. The second post covered two years of running that in production. This post is about the month it stopped working. In January 2026 our heartbeat traffic went from boring to terrifying and stayed there for about four weeks. This is the story of what broke, why the original design had a ceiling we never noticed, and the changes that fixed it: more Event Hub partitions, Azure Container Apps, and KEDA . The Storm A normal day looked like this: 51,000-58,000 heartbeats per hour , hour after hour Roughly 15-16 events per second at idle Flat, predictable, forgettable On January 5, around 7:00 AM PST , it stopped being flat. Time (PST) Heartbeats/hour Baseline ~57,000 12:00 PM 80,005 1:00 PM 216,351 5:00 PM 343,480 9:00 PM 466,760 That is eight times normal event volume in a single hour, and it was still climbing. Events were only half the story. SignalR connection counts told the other half. At the worst of it we were holding roughly eleven times the connections we normally maintain, and every one of those was a browser session we had to track, keep alive, and report status for. It did not spike and recover. It stayed elevated for weeks while we hunted for the cause. When we finally found it, the answer was almost funny: 507 zombie sessions that never ended, running months-old cached client code, and a single user account responsible for 33% of all our token API traffic . One account. Eight times the load. Four weeks. What Eight Times Load Actually Did Here is the part that matters, and it has nothing to do with the number itself. Our Event Hub had one partition. I
AI 资讯
The AI Has Hands
Ask Crysta — the AI agent on CrystaCode.ai — to switch the site to dark mode, and the site actually turns dark. Ask it to show the login popup, and the modal actually opens. The model doesn't just answer anymore; it operates the UI. But here's the thing: the LLM lives on the server , and the UI lives in the browser . A model can't click buttons. So how do you give a remote brain hands? The answer is a pattern we ended up calling the Client Driver Skill : function calling, with SignalR as the hand. The Problem The first version of our chat was a one-way street. The model could say "sure, I'll take you to the plans page" — and then nothing happened. The answer was text; the UI was deaf. You have two classic options: The client polls the server for commands (ugly, wasteful, feels like 2010) The server pushes commands to the client (real-time, instant, exactly what SignalR is for) We went with the push. The flow became: the model calls a function → the function runs on the server → the server pushes a typed command over SignalR → the client executes it. How It Works (The Full Loop) [Browser] [Server] | | | 1. "switch to dark mode" | | ---- InvokeAsync ---------> | | | 2. Brain runs, model sees | | the UpdateSiteTheme tool | | 3. Model calls the function | | (function calling) | | 4. Push to the exact tab: | <--- ChangeSiteTheme ------ | Clients.Client(connId) | 5. ThemeService flips it | | 6. "Site theme changed..." | 5b. return value re-enters | | the model's context | <--- chat answer ---------- | User types "switch to dark mode" → the Blazor client calls the hub The server session runs the brain; the model sees a tool called UpdateSiteTheme The model decides the user wants dark mode and calls the function The server pushes ChangeSiteTheme(DarkMode) to the exact browser connection The client applies the theme and re-renders The function's return value goes back into the model's context, so the AI knows the theme changed and confirms it in the chat 1) The Client Regist
AI 资讯
My Shell Scripts Speak C# Now
Every couple of weeks I need a twenty-line program. Find what's bloating a build agent's disk, dedupe a CSV, hash-check a folder. For fifteen years the honest answer to "which language?" was not C# — by the time I'd done mkdir , dotnet new console , and named yet another throwaway csproj, the moment had passed. So those little jobs went to bash or Python, and I grumbled quietly every time. .NET 10 removed the ritual. You write one .cs file and run it. I'd been meaning to check how well this actually holds up for real scripts, so this week I did — nothing fancy, one Linux container and a stopwatch. One file, no project Here's biggest.cs , a small utility that lists the largest files under a directory. The whole program is this one file — no csproj anywhere: # !/ usr / bin / env dotnet # : package Humanizer @ 3.0 . 10 using Humanizer ; var root = args . Length > 0 ? args [ 0 ] : "." ; var top = args . Length > 1 && int . TryParse ( args [ 1 ], out var n ) ? n : 10 ; var files = new DirectoryInfo ( root ) . EnumerateFiles ( "*" , new EnumerationOptions { RecurseSubdirectories = true , IgnoreInaccessible = true , AttributesToSkip = FileAttributes . ReparsePoint }) . OrderByDescending ( f => f . Length ) . Take ( top ) . ToList (); foreach ( var f in files ) { var size = f . Length . Bytes (). Humanize ( "#.#" ); var age = ( DateTime . UtcNow - f . LastWriteTimeUtc ). Humanize (); Console . WriteLine ( $" { size , 10 } { f . FullName } (modified { age } ago)" ); } Two lines are new. #:package Humanizer@3.0.10 is a NuGet reference written as a directive, right in the source. The shebang we'll get to in a minute. Everything else is the C# you already write, top-level statements and all. $ dotnet run biggest.cs -- ~/.dotnet 5 Top 5 files under /root/.dotnet: 37.6 MB .../FSharp.Compiler.Service.dll (modified 46 seconds ago) 18.7 MB .../Microsoft.CodeAnalysis.CSharp.dll (modified 46 seconds ago) 18.7 MB .../Roslyn/bincore/Microsoft.CodeAnalysis.CSharp.dll (modified 45 seconds
AI 资讯
On-premise RAG without GPU, cloud, or Docker: five lessons that cost me a week each
Every RAG tutorial I've read makes the same two assumptions: you have a GPU, and you can call a cloud API. For the environments I build for, both assumptions are wrong. I work on health information systems in the public sector. The stack has to run inside institutional infrastructure — no data leaves the network — and the hardware I get is whatever the procurement cycle produced two years ago. In practice that means Windows Server, CPU only, and open-weight models running locally. So I built a RAG stack that runs entirely on-premise, no GPU, no cloud, no Docker. It's open source at github.com/psychohub/rag-onpremise : ASP.NET Core 9 for orchestration, Ollama for local inference, Qdrant for vectors, Python for the ingest pipeline, Mistral 7B as the LLM, nomic-embed-text for embeddings. Getting it into production took longer than the design did, because five things broke that no tutorial had warned me about. This is the field report. The environment, and why it matters Before the lessons, it's worth being precise about the constraint, because it changes what "good" looks like. The stack has to run on a Windows Server, not a Linux workstation. Docker is not available on many of the target machines — either because it wasn't approved, because GPO policies restrict it, or because ops teams already run everything as Windows services and adding a container runtime is a new operational surface nobody wants to own. GPUs are aspirational. In the meantime, you have CPU inference and you have to make it work. None of this is exotic. It's the default reality in a lot of public sector, healthcare, and legacy enterprise environments. It's also the reality most RAG content on the internet quietly assumes away. The overall shape of the system: Documents (PDF / Word / Excel) │ ▼ [ Python ingest ] ├─ Text extraction (pdfplumber, python-docx, openpyxl) ├─ Chunking (500 tokens, 50 overlap) ├─ Embeddings (nomic-embed-text via Ollama) └─ Store (Qdrant, cosine similarity) │ User query │ │
AI 资讯
Upgrade .NET 8 to .NET 10 Without Breaking Your API Contract
If I need to upgrade .NET 8 to .NET 10 , I treat the work as an API contract migration, not a project-file edit. A service can compile, pass unit tests, and still surprise consumers with a changed JSON shape, status code, authentication response, or OpenAPI document. That risk matters now because Microsoft has confirmed that .NET 8 and .NET 9 reach end of support on November 10, 2026 . .NET 10 and C# 14 are the current stable releases, and .NET 10 is the supported LTS destination. Why the deadline changes my upgrade order My first step is inventory, not retargeting. I list every deployable project, test project, global.json , container base image, CI SDK pin, and Microsoft package reference. dotnet --list-sdks shows what a machine can build; dotnet --info shows what the current environment actually resolves. If that inventory needs more detail, my older guide to dotnet sdk check is a useful starting point. For APIs still on .NET 8, the broader Web API setup and security checklist can help identify behavior worth protecting before the move. I then separate the migration into three changes: SDK and target framework, NuGet dependencies, and runtime infrastructure. Keeping those changes visible makes a failure easier to locate. A giant dependency-refresh commit may be quick to create, but it is hard to diagnose. Upgrade .NET 8 to .NET 10 behind contract tests Before changing net8.0 , I add a small set of tests around the endpoints consumers cannot tolerate changing. I care about observable behavior: status codes, content types, required JSON names, and authentication boundaries. I avoid asserting an entire serialized string because harmless property ordering can make that test noisy. Here is a focused xUnit test for a Minimal API: using System.Net ; using System.Text.Json ; using Microsoft.AspNetCore.Mvc.Testing ; using Xunit ; public sealed class ProductContractTests ( WebApplicationFactory < Program > factory ) : IClassFixture < WebApplicationFactory < Program >> { [
AI 资讯
C# Crash Course for Beginners
Hey everyone, I'm excited to share my brand-new C# Crash Course for Beginners on YouTube! 🎉 For those of you who are new here, I'm Amir, a software developer who enjoys learning new technologies and creating programming tutorials that are practical, beginner-friendly, and straight to the point. If you've been thinking about learning C#, this course is the perfect place to start. C# is one of the most popular programming languages in the world and is widely used for desktop applications, web development with ASP.NET, cloud services, game development with Unity, and enterprise software. Combined with the power of the .NET ecosystem, it provides an excellent foundation for building modern applications. In this one-hour crash course, we'll start from the very beginning by setting up the .NET development environment and learning the essential command-line tools. From there, we'll gradually build our understanding of the language through practical demonstrations and live coding examples. Throughout the course, you'll learn: How to install and configure the .NET SDK Using the .NET CLI and .NET Script Variables and data types String interpolation Arithmetic, comparison, and logical operators Conditional statements Loops Methods and functions Arrays and collections Lists, Dictionaries, and HashSets LINQ fundamentals Classes, objects, and Object-Oriented Programming (OOP) Records and modern C# features Pattern Matching You'll also get a preview of a real-world application that we'll build together in a future tutorial series, showing how these concepts come together in an actual project. This course focuses on building a strong understanding of C# fundamentals. Topics such as asynchronous programming with async and await are intentionally left for a dedicated tutorial, where we can explore them properly with practical examples. Whether you're completely new to programming or coming from another language like Java, Python, JavaScript, Go, or Rust, I hope this course helps make
AI 资讯
The one seam, shown: Inline up close
In post 10 I closed the composition-versus-coherence question with one paragraph: I kept strict object-scoping, reserved an Inline operator for later, and rejected automatic reach-down. That was true, and it was too fast. A reader told me the reserved operator was not clear from a sentence, which is fair. A design record that asserts a decision without showing it is not really a record. So here is the seam, worked out. First, the good news that made this only one seam and not ten: composition and coherence mostly do not collide. Composition substitutes complex types ; coherence binds scalar facets ; those are disjoint kinds of member. A collection gives one persona per element. Draw order falls out of the eager construction rule from post 7. And the resolver pipeline I pre-paid for back in post 4 turned out to be coherence's host. The two threads layer cleanly almost everywhere. Almost. The discontinuity Coherence is object-scoped (post 5). That one rule has a consequence that only shows up once you have composition encouraging you to split a type across nested objects: moving a facet into a child changes whether it coheres. // Flat: Email is a Person facet, so it coheres with the name. Customer { FirstName , LastName , Email } // -> "Maria", "Gonzalez", "maria.gonzalez@..." // Decomposed: Contact is its own scope. A lone Email there does not activate a // persona (one corroborating member, no name anchor), so it is a plain, unrelated email. Customer { FirstName , LastName , Contact : ContactInfo { Email } } // -> "Maria", "Gonzalez", "rwilson@..." Same three fields, same intent, different result, decided entirely by which object they live on. That is the discontinuity. The options A, strict: object-scoping stays. The decomposed email does not cohere. Maximally predictable. The gap is the already-deferred cross-entity work. B, reach-down: a child with no entity of its own is absorbed into the parent scope. The email coheres. But "absorbed or not" now depends on hidd
AI 资讯
RockPlayer: Building a Modern Music Player with Angular, ASP.NET Core, Redis, and YouTube
Hello everyone! After publishing my Machine Learning with ML.NET series, I decided to turn the recommendation model into a complete application. In this new series, we build RockPlayer, a rock music player that combines modern software architecture, ASP.NET Core, Angular 22, Redis, and YouTube integration. Each article focuses on a different part of the project: 🎵 1. Introducing RockPlayer An overview of the project, its goals, and the overall architecture. https://devfullstack.net/blog/introducing-rockplayer 🔌 2. Adapters: Isolating the YouTube Provider Using the Adapter pattern to decouple the application from the YouTube integration. https://devfullstack.net/blog/adapters-isolating-the-youtube-provider ⚡ 3. No Database: Caching Lookups with Redis Using Redis to cache search results instead of storing external data in a database. https://devfullstack.net/blog/no-database-caching-lookups-with-redis 🅰️ 4. Angular 22 in Practice Applying modern Angular 22 features to build the user interface. https://devfullstack.net/blog/angular-22-in-practice 🚀 5. Building the RockPlayer API Building the API that orchestrates the application. https://devfullstack.net/blog/building-the-rockplayer-api ▶️ 6. The YouTube Adapter: Finding and Playing the Song Implementing the YouTube integration to search for and play songs. https://devfullstack.net/blog/the-youtube-adapter-finding-and-playing-the-song 🎧 7. RockPlayer in Angular 22: Onboarding Setting up the Angular application and organizing the project structure. https://devfullstack.net/blog/rockplayer-in-angular-22-onboarding 🎸 8. RockPlayer: Putting It All Together Bringing all the components together into a complete application. https://devfullstack.net/blog/rockplayer-putting-it-all-together I hope this series is useful for developers interested in software architecture, .NET, and Angular. See you there!
AI 资讯
What You Can Do With C#
Let's get the joke out of the way, because you're going to hear it within four minutes of telling anyone you're learning C#: "Oh, C#? Isn't that just Microsoft Java?" Yes. Kind of. A little. Here's the actual story. Back around 2000, Microsoft wanted a modern, garbage-collected, object-oriented language for their shiny new .NET platform. Java existed and was extremely popular. Microsoft had previously shipped their own version of Java, Sun sued them into the sea, and the whole thing ended in tears and lawyers. So Microsoft did the very sensible, very corporate thing: they hired Anders Hejlsberg , the man who built Turbo Pascal and Delphi, and said: "make us a Java, but ours, and don't get us sued." He did. And then he kept improving it for twenty-five years while Java spent a decade arguing about whether it should add lambdas. So calling C# "Microsoft Java" today is like calling a smartphone "a Microsoft telegraph." Technically, you can trace the lineage. It is also extremely funny to the person being insulted, which is the only thing that matters. So, what can you actually do with this thing? More than you'd think. Let's take the tour. First, the obligatory Hello World Every language tour is legally required to start here. C#'s has changed a lot, which tells you something about the language's whole vibe. The old way, circa 2005, was a ceremony: using System ; namespace MyFirstApp { class Program { static void Main ( string [] args ) { Console . WriteLine ( "Hello, world!" ); } } } Eleven lines to say hello. You needed a namespace , a class , a Main method with a specific signature, and the kind of static void incantation that makes beginners quietly close the tab and go learn Python instead. The modern way (C# 9 and later) is this: Console . WriteLine ( "Hello, world!" ); That's the whole program. The compiler quietly puts all the ceremony back for you behind the scenes. This is C# in a nutshell: it grew up in a buttoned-up enterprise suit, and over twenty years it
AI 资讯
600 Filters and a 414: The New QUERY Method in .NET 10
A product search, a filter list that kept growing, and a status code I hadn't seen in years. Filters went in the query string, the way they always do. That held up fine until someone saved a "filter set" with a few hundred SKUs in it and the endpoint started answering with 414. I rebuilt a small version of it to find the exact wall. Same search, filters as repeated ?sku= values, count going up in steps of a hundred: 1) GET with filters in the URL 100 filters | request line 1534 bytes | 200 OK 200 filters | request line 3034 bytes | 200 OK 300 filters | request line 4534 bytes | 200 OK 400 filters | request line 6034 bytes | 200 OK 500 filters | request line 7534 bytes | 200 OK 600 filters | request line 9034 bytes | 414 RequestUriTooLong Kestrel's default max request line is 8 KB, and the request line is the method plus the URL plus the HTTP version. Somewhere between 500 and 600 filters, my URL stopped being a URL. Every fix I knew was a compromise. A body on GET is undefined by spec and some proxies quietly drop it. POST works, but POST announces "this might change something", so caches skip it, gateways won't auto-retry it, and anyone reading your API docs has to guess whether POST /search is actually a search. Cramming the filters into a header is the kind of idea that sounds clever for about a day. The method that was missing RFC 10008 defines QUERY , and it's exactly the thing that spot in the matrix was waiting for. The body carries the query. The method is safe and idempotent, so it can be retried after a dropped connection without anyone panicking. Responses are cacheable, and the spec is explicit that the cache key has to be built from "the request content and related metadata". There's also a nice touch on the response side: Content-Location can point at a URL where those exact results can be fetched with a plain GET. The one-line version I keep giving people: it's a GET with a body, and that's the entire point. Wiring it up in ASP.NET Core 10 .NET 10 shi
AI 资讯
Apache Airflow com .NET 10: dispare e monitore DAGs
Introdução Integrar Apache Airflow com .NET 10 não significa portar o orquestrador, reescrever DAGs em C# ou executar o runtime Python dentro da aplicação. A solução correta é manter o Airflow responsável por criar, agendar e monitorar workflows e fazer o serviço .NET consumir sua API REST pública. O serviço autentica, dispara um DAG Run com parâmetros, guarda o identificador retornado e consulta o estado até receber success , failed ou canceled . Essa separação preserva o papel de cada tecnologia e cria um contrato claro entre a aplicação transacional e a plataforma de dados. Neste guia, eu vou implementar esse fluxo de ponta a ponta usando .NET 10 , HttpClient , autenticação JWT e a API /api/v2 do Apache Airflow 3.3 . O exemplo não se limita a um POST : ele gera um dag_run_id rastreável, serializa conf corretamente, reutiliza o token até perto da expiração, renova a credencial após uma resposta 401 , aplica timeout ao monitoramento e propaga cancelamento. Também vou expor a integração por uma Minimal API, para que outro sistema possa iniciar o processo sem conhecer os detalhes do Airflow. O nome atual da plataforma da Microsoft é .NET 10 , e não “.NET Core 10”. A marca “.NET Core” foi usada até a versão 3.1; desde o .NET 5, o produto unificado passou a se chamar apenas .NET. Essa diferença não altera o código, mas evita confusão ao procurar documentação, imagens de container e pacotes compatíveis. O cenário prático será um serviço de pedidos que solicita a execução do DAG etl_vendas . A configuração enviada contém a data de referência e um identificador de correlação. O Airflow continua executando tarefas Python, SQL, containers ou jobs distribuídos; o C# apenas controla o ciclo de vida da execução pela fronteira HTTP. ℹ️ Informação: no Airflow 3, os endpoints públicos estáveis ficam sob /api/v2 . Rotas internas de UI não são um contrato de integração e podem mudar conforme o frontend. Pré-requisitos Para acompanhar o exemplo, você precisa do .NET 10 SDK , do Dock
AI 资讯
How to Configure keyVaultReferenceIdentity in Azure App Service?
Overview This guide shows you how to fix a critical Azure App Service configuration issue where the keyVaultReferenceIdentity property is hidden from the Azure Portal but required for accessing Key Vault secrets. Symptoms Developers encountering this issue typically observe: Key Vault references returning empty values instead of secret content Configuration entries showing "Not Resolved" error messages Application settings failing to fetch secret values from Key Vault Authentication errors when attempting to access protected secrets 401/403 errors from App Service attempting to validate Key Vault access Why This Happens Azure App Service uses Managed Identity authentication to access Key Vault secrets, but the keyVaultReferenceIdentity property is deliberately hidden from standard Azure Portal interfaces. This property only exists at the Azure Resource Manager (ARM) level, making it invisible through the typical Azure management UI. Technical Architecture App Service → Managed Identity → Azure AD → Key Vault Access Policy → Secret Store App Service attempts to authenticate using its assigned Managed Identity Azure needs explicit permission through the keyVaultReferenceIdentity property This permission exists only in the underlying ARM configuration Without this configuration, the authentication chain breaks Key Vault references resolve to empty values or error messages Why Portal Visibility is Limited Microsoft implements this design choice for several reasons: Security : Keeps identity-to-Key Vault mappings out of standard management interfaces Simplicity : Prevents accidental misconfigurations that could cause security issues Audit Trail : Ensures all identity configurations go through proper change management Resource Provider : Some properties require ARM-level configuration for consistency Prerequisites Required Azure Resources Azure Subscription : Active subscription with appropriate permissions Azure App Service : Existing Linux or Windows App Service User-As
AI 资讯
Decided is not done: taking stock before adding more
The first session ended at post 10. The design did not. I came back to it and, before writing a single new decision, asked the least glamorous question a solo project can ask itself: how far along is this, really, and what would it take to call it ready for someone else to review in depth? The answer was more useful than I expected, because it forced a distinction I had been blurring: a settled mechanism is not a hardened design . The fork: promote now, or hold and harden Composition was already reconciled into the binding docs. Coherence had seventeen recorded decisions covering the whole load-bearing core: binding, determinism, precedence, persona content, gender, explainability, detection, activation, the explicit accessor, and a second entity proving the abstraction generalizes. It was tempting to call that reviewable and promote it too. A: promote coherence into the binding docs now. 17 decisions, self-consistent, composition already went. Looks done. B: hold. The mechanism is settled, but the seams between features are not. Harden first, promote second. I took B. The tell was that I could not yet answer a reviewer's most obvious question, "what happens when a composed child is itself a person," without pointing at an open fork. A design you cannot stress at the seams is decided, not done. What "hardened" actually means The value of taking stock was turning a vague "almost there" into a concrete, finite list. Three passes stand between the current state and an in-depth review: 1. Cross-feature interaction pass. Where correctness bugs hide once two features exist. Composition x coherence is done (next post). Uniqueness, null-probability, and locale remain. 2. Surface-enumeration pass. Collect every public member the design has accumulated into one list to accept or cut. Public surface is locked, so this is the gate that matters most. 3. Consistency re-read. Read all the decisions straight through for contradictions and stale cross-references, the kind that creep
AI 资讯
Testing Microsoft Agent Framework Applications
This is Part 18 of my series on the Microsoft Agent Framework. You can read the original post over on lukaswalter.dev . In the previous article , we looked at observability for agents. The main idea was to make a run visible as a chain of model calls, tool calls, approvals, and workflow events. Testing starts from the same idea. An agent run is not one answer string. It is a small application flow with several boundaries: user input -> prompt and context -> model request -> model response -> tool selection -> tool arguments -> tool execution -> structured result or final answer -> routing or workflow state If the only test is an end-to-end prompt against a live model, all of those boundaries are mixed together. When the test fails, you do not know whether the problem is the prompt, the model, the tool schema, the router, the workflow, or the real dependency behind the tool. The solution is not to pretend that an LLM is deterministic. The solution is to test each boundary at the level where it is deterministic, then add a smaller number of evaluation-style tests for behavior that genuinely depends on the model. This post covers: fake model clients tool contract tests structured output tests routing tests workflow tests eval-style regression checks The examples use xUnit-style assertions, but the testing approach does not depend on xUnit. The snippets focus on the relevant testing boundary and omit some application-specific factory and workflow setup. Do not start with the live model A live model test is useful. It is also expensive, slow, sometimes flaky, and difficult to diagnose. That makes it a poor replacement for normal unit and integration tests. I use a testing pyramid for agent applications: evals realistic model and user examples application integration tests agent + tools + storage + workflow boundaries deterministic component tests fake model client, tools, schemas, routing The bottom layer should be the largest. It should catch ordinary programming mistak
AI 资讯
How I replaced if statements with a Dictionary delegate in C#
Let's say you need to implement a feature that returns a different package based on the user-provided coupon code. So you start with a model: public record Package { public int Id { get ; set ; } public string Name { get ; set ; } public double Price { get ; set ; } } And you write a function that returns a different package based on the coupon code: private static Package GetPackageFromCoupon ( string coupon ) { if ( coupon == "ABC" ) { return new Package { Id = 1 , Name = "PS5 Controller" , Price = 50.00 }; } if ( coupon == "EBC" ) { return new Package { Id = 2 , Name = "Iphone X" , Price = 200.00 }; } if ( coupon == "DDD" ) { return new Package { Id = 3 , Name = "X7 Mouse" , Price = 20.00 }; } return new Package { Id = 1000 , Name = "Soda" , Price = 1.00 }; } And invoke it from your main method: internal class Program { static void Main ( string [] args ) { var package = GetPackageFromCoupon ( "ABC" ); Console . WriteLine ( package ); Console . ReadLine (); } } Quick test Provide expected parameters and inspect the results. "ABC" => Package { Id = 1 , Name = PS5 Controller , Price = 50 } "EBC" => Package { Id = 2 , Name = Iphone X , Price = 200 } "DDD" => Package { Id = 3 , Name = X7 Mouse , Price = 20 } Works as expected. Also, if you enter something that doesn't exist: "a" => Package { Id = 1000 , Name = Soda , Price = 1 } The Problem What if you need to add more coupon codes and return different variations of the Package object? Well, it's gonna get pretty messy very soon. Quick solution - Dictionary Rather than writing every possible variation in the if block, create a dictionary where the key is the coupon code and the value is the Package: private static readonly Dictionary < string , Package > _packages = new () { [ "ABC" ] = new Package { Id = 1 , Name = "PS5 Controller" , Price = 50.00 }, [ "EBC" ] = new Package { Id = 2 , Name = "Iphone X" , Price = 200.00 }, [ "DDD" ] = new Package { Id = 3 , Name = "X7 Mouse" , Price = 20.00 }, }; The next step is to
AI 资讯
I Built a CLI to Use Free Web-Based AI Chatbots for Real Development Work — No API Keys, No Extensions
I wanted to use web-based AI chatbots — Claude, Gemini, ChatGPT, Qwen — for actual development work, not just Q&A. The free tiers are generous, and I didn't want to be locked into a single coding agent or pay for API access just to get an assistant to touch my code. But the moment you try to actually use a web chat for real dev work, you hit the same wall every time: you either paste in your whole codebase manually every session, or you give up and reach for a paid extension with an API key behind it. So I built AI Bridge — a CLI tool that bridges a local codebase and any browser-based AI chatbot. No API keys, no extensions running in the background, no vendor lock-in. You pack your code, paste it into whichever AI chat you're using, and apply the changes back with a command. Why not just use Copilot, Cursor, or an API key? Coding agent apps and API-based tools work, but they come with tradeoffs I wanted to avoid: Web-based AI chat plans are usually more generous on the free tier than API usage I'm not locked into one provider — I can switch models mid-project depending on which one is handling a task better There's no background agent or extension — it's just a CLI and whatever chat tab I already have open The tradeoff is that browser chats don't have direct filesystem access. AI Bridge closes that gap without turning it into full manual copy-pasting. How it works There are two modes, depending on project size. Simple Mode is for projects small enough to fit in a single prompt. You pack the codebase, upload it along with a couple of prompt templates, and apply the AI's response back to your files: dotnet tool install --global Tools.AIBridge cd /path/to/your-project ai-bridge init ai-bridge pack # Upload ai-bridge/1-SimpleMode/*.md + the generated context files to your AI # Copy the AI's response, then: ai-bridge apply --paste Advanced Mode is for larger codebases, where uploading everything every time burns tokens and adds noise. Instead, you generate a one-time in
AI 资讯
What 18 months building a self-hosted media server taught me about playback
Project: https://quven.tv/ Security model: https://quven.tv/security/ For the last 18 months, I have been building Quven, a self-hosted media server for personal movie, TV, and documentary libraries. I started with a seemingly simple goal: let people keep their media on their own hardware while giving them a polished client experience. Playback quickly became the hardest part. A media server does not simply send a video file to a screen. It has to understand the source, the client, the network, and the user's choices, then select a playback path without making any of that complexity feel visible. These are some of the lessons I learned. 1. "Can this file play?" is the wrong question The real question is whether a particular client can play a particular combination of: container; video codec and profile; audio codec and channel layout; subtitle format; resolution, bitrate, and frame rate; HDR format; network conditions. A client might support the video codec but not the audio track. A browser might decode the video but require a different container. Enabling an image-based subtitle can turn an otherwise direct-playable file into a video transcode. Playback compatibility is therefore not a boolean property of a file. It is a negotiation between the source and the active client. 2. Direct play should be the preferred outcome, not a promise Direct play preserves the original file and avoids unnecessary server work. When the client supports the selected combination, it is usually the best path. But forcing direct play at all costs produces a worse experience. A high-bitrate file may technically be supported while still exceeding the available connection. A selected subtitle might require burning into the video. A television may accept a container while rejecting one of its audio formats. The practical hierarchy I settled on is: Direct play when the complete source is compatible. Remux when the streams are compatible but the container is not. Transcode only what must chan
AI 资讯
building enterprise multi-agent workflows in .net with mistral
most people know Mistral for its chat models. the part i find more interesting for enterprise work is the Agents API : persistent agents with instructions and tools, stateful conversations you can resume, built-in connectors (web search, code interpreter, document library), and handoffs so one agent can delegate to another. the .net story stops short of this. the community sdks (tghamm's is genuinely good) cover chat completions, embeddings and function calling. they don't cover the agentic layer. so if you're a .net shop that wants to build a multi-agent workflow on mistral, you're writing raw http. i didn't want to, so i built Mistral.Agents.Net . here's the design and the one wire-format detail that cost me a debugging session. agents, not just completions a chat completion is stateless: you send messages, you get a reply, you manage all the history yourself. an agent is a stored object with instructions and tools, and a conversation is a stateful thread you can continue by id. that difference matters for enterprise workflows, where a "session" spans many turns and you want the platform to hold the state. var agent = await client . CreateAgentAsync ( new CreateAgentRequest { Model = "mistral-medium-latest" , Name = "Financial Analyst" , Instructions = "Use the code interpreter for math and web search for current facts." , Tools = { AgentTool . CodeInterpreter (), AgentTool . WebSearch () }, }); using var turn = await client . StartConversationAsync ( new StartConversationRequest { AgentId = agent . Id , Inputs = "what was 15% of last quarter's revenue if it was 12.4M?" , }); Console . WriteLine ( turn . OutputText ); the response isn't a single message. it's a list of outputs: tool executions, message chunks, function calls, handoffs. the library gives you OutputText for the common case and Outputs for the raw stream, plus a Root JsonElement escape hatch for anything the typed model doesn't cover yet. same philosophy i used for the serpapi and elevenlabs clients: