Content
<div align="center">
# ⚡ VeloxDev
**Build modern, AI-controllable workflow editors on any .NET GUI — WPF, Avalonia, WinUI, MAUI, WinForms, Razor, or Jalium.**
<!-- Supported GUI frameworks -->
[](https://learn.microsoft.com/dotnet/desktop/wpf/)
[](https://avaloniaui.net/)
[](https://learn.microsoft.com/windows/apps/winui/)
[](https://learn.microsoft.com/dotnet/maui/)
[](https://learn.microsoft.com/dotnet/desktop/winforms/)
[](https://learn.microsoft.com/aspnet/core/razor-components/)
[](https://github.com/VeryJokerJal/Jalium.UI)
[](https://www.nuget.org/packages/VeloxDev.Core/)
[](https://www.nuget.org/packages/VeloxDev.Core.Extension/)
[](LICENSE.txt)
[](https://github.com/Axvser/VeloxDev)
---
**📖 Wiki** — [Online (WASM)](https://axvser.github.io/VeloxDev.Docs/) · [Local](https://github.com/Axvser/VeloxDev.Docs/) — the online Wiki is a WebAssembly app, so its load speed depends on your network.
---
</div>
> **What this is** — a **node editor / node-graph / workflow-editor** framework for **.NET / C#**. Drag nodes, wire slots into links on a **zoomable, virtualized canvas**, drive the graph with a **compiled, pull-based execution engine**, gate structural edits behind **undo/redo**, and control it all through an **AI agent** (function calling + **MCP**). One model → **7 GUIs**: WPF · Avalonia · WinUI · MAUI · WinForms · Blazor · Jalium.
## ✨ What is VeloxDev?
VeloxDev gives .NET developers a complete foundation for building **interactive workflow editors** — the kind where users drag nodes, wire slots together, and watch data flow through a graph at runtime.
Three ideas hold the whole project together:
1. **One model, every GUI.** The workflow model, compile-time identity, runtime engine, serialization and the command/undo-redo stack live in `VeloxDev.Core` with **zero UI dependencies**. Platform adapters (WPF, Avalonia, WinUI, MAUI, WinForms, Razor, Jalium) supply the views plus the platform glue only they can provide — timers and frame pacers, thread marshalling, native value conversion. Your graph data and its execution semantics behave identically on every platform.
2. **A real execution engine, not just a canvas.** Besides the drag-and-drop surface, `CompilerEx` compiles any reachable sub-graph into a plan (linear chain / router branch / fan-out group) and drives it deterministically — including **reverse (Terminal) compilation**: ask "what would this node output?" and it computes just the ancestor cone that feeds it, with no controller needed. Fan-out branches run **concurrently as interleaved async operations, not as threads** — every branch starts on the caller's context and each `await` inside it yields to its siblings, so I/O-bound branches (a node invoking a process, say) overlap while the group stays on the host's `SynchronizationContext`. A branch that burns CPU still occupies the thread in turn: a compiled run shares one runtime blackboard, which is deliberately not thread-safe.
3. **AI is a first-class controller.** A 60+ function-calling *Workflow Agent* lets an LLM inspect, build and mutate graphs at runtime through natural language — with the same undo/redo, validation and lifecycle the GUI uses, plus optional **MCP** tool connectivity.
> **Why not just a WPF node editor?** Libraries like Nodify and NodeNetwork are excellent, but they are **WPF-only canvases** — they draw the graph and stop there. VeloxDev runs the same node graph on Avalonia, WinUI, MAUI, WinForms and Blazor, and layers on what a canvas alone cannot give you: a **compiled forward + reverse execution engine**, and an **AI agent** that edits and runs the graph through the same commands the GUI uses.
### The workflow system
| Layer | What it provides | Dependency |
|-------|-----------------|------------|
| ⛓️ **Workflow** | Tree / Node / Slot / Link templates with undo-redo, spatial indexing, deep-zoom canvas math, serialization, and a **compiled execution engine** (forward + reverse) | |
| 🤖 **Workflow Agent** | 60+ Function Calling tools — an AI can create nodes, wire slots, patch properties and run chains at runtime via natural language. Supports **MCP (Model Context Protocol)** for external tools/data. Ships bilingual (en/zh) embedded prompt docs so the agent knows exact tool semantics. | `VeloxDev.Core.Extension` |
### Other building blocks you can reuse
| Layer | What it provides | Dependency |
|-------|-----------------|------------|
| 🪶 **MVVM** | Source generators for observable properties and async, cancellable commands — keeps node ViewModels lightweight | |
| 🎞️ **Transition** | Cross-platform interpolation animation with easing & Fluent API — smooth visual feedback for workflow state changes | [Platform Adapter Package](#platform-adapter-packages) |
| 🎨 **Theme** | Runtime theme switching with animated transitions — instant visual identity for your editor | [Platform Adapter Package](#platform-adapter-packages) |
| 🌀 **AOP** | Generated aspect interfaces with runtime proxies — intercept node members for logging or validation without modifying business logic | |
| ⚙️ **Tickable** | Frame-driven lifecycle loop — tick-based node simulation or real-time graph execution | |
### Platform adapter packages
| Platform | Package | NuGet |
|----------|---------|-------|
| WPF | `VeloxDev.WPF` | [](https://www.nuget.org/packages/VeloxDev.WPF/) |
| Avalonia | `VeloxDev.Avalonia` | [](https://www.nuget.org/packages/VeloxDev.Avalonia/) |
| WinUI | `VeloxDev.WinUI` | [](https://www.nuget.org/packages/VeloxDev.WinUI/) |
| MAUI | `VeloxDev.MAUI` | [](https://www.nuget.org/packages/VeloxDev.MAUI/) |
| WinForms | `VeloxDev.WinForms` | [](https://www.nuget.org/packages/VeloxDev.WinForms/) |
| Razor | `VeloxDev.Razor` | [](https://www.nuget.org/packages/VeloxDev.Razor/) |
| Jalium | `VeloxDev.Jalium` | [](https://www.nuget.org/packages/VeloxDev.Jalium/) |
Adapter API docs: [WinForms](Src/Adapters/VeloxDev.WinForms/README.md) · [Razor](Src/Adapters/VeloxDev.Razor/README.md)
---
## 🧠 Core concepts (60 seconds)
- **A workflow is a Tree of Nodes.** Every Node is a ViewModel + a *Helper* (component/helper pattern). Nodes own **Slots**; slots are wired into **Links**. A slot has a *channel* (one/many, sender/receiver/both) that governs how connections are validated.
- **Views are templates over the model.** The adapter's `WorkflowSurface` hosts Node/Slot/Link views (plus grid decorator, minimap, ruler and tree views), virtualized against a spatial index so graphs with thousands of nodes stay fluid.
- **Structural edits are undoable commands.** Create/delete node or slot, connect/disconnect, selector changes and their cascades all flow through `IVeloxCommand` with a redo/undo pair — and the Agent tool layer uses the *same* commands the GUI does. Position and size are outside that history: `SetAnchor`/`SetSize` are commands with **no undo entry**, in the GUI and in the Agent alike.
- **Execution is compiled, then driven.** Nodes expose one receive entry `ReceiveCommand → Helper.ReceiveAsync(ITaskContext, ct)`. The engine (`RuntimeEngine`) compiles a sub-graph into segments once and then *pulls* data through them — nodes do not broadcast to each other on their own during a compiled run.
- **Phases are explicit.** Compile time assigns every node a fixed identity (`Order/ChainIndex/Offset`) and runs dataflow *validation* without data; runtime reuses the same `AccessAsync` gate with real payloads. A context hierarchy (`IContext → IAccessContext → ITaskContext`, plus `ICompileContext` / `IRuntimeContext`) describes every hand-off.
---
## ⚙️ Execution model — compile once, run deterministically
`CompilerViewModel` has **one** API, `CompileAsync(node, role)`:
```csharp
public enum CompileRole { Root, Terminal } // the node is a starter, or the result you want
```
| Role | Meaning | What it compiles |
|------|---------|------------------|
| `Root` | The node starts a run (e.g. a controller) | Its reachable sub-graph, walking **downstream** along `Targets` |
| `Terminal` | You want this node's result | Its **ancestor cone** — the producers feeding it, walked **backward** along `Sources` — starting automatically from the cone's entry frontier |
```csharp
var compiler = new CompilerViewModel();
// 1) Forward: run the whole chain from a controller
var graph = (await compiler.CompileAsync(controller, CompileRole.Root))[0];
var session = new RuntimeContext();
await new RuntimeEngine().RunAsync(graph, session);
Console.WriteLine(session.Data); // the chain's final payload
// 2) Reverse: compute just one node's value — no start node needed
var cone = (await compiler.CompileAsync(someNode, CompileRole.Terminal))[0];
var probe = new RuntimeContext { Target = someNode };
await new RuntimeEngine().RunAsync(cone, probe);
if (probe.TargetReached) Console.WriteLine(probe.Data); // that node's output
else Console.WriteLine("target NOT reached — no value fabricated");
```
The plan it produces is a small tree of segments: a **linear chain**, a **router branch** (a node implementing `ICompileTimeRouter`, Static or Dynamic), and **fan-out groups** (branches off the same source, driven concurrently as interleaved async operations) — and it is always acyclic. Looping is expressed as runtime *redirects* instead of graph cycles: when a node signals an error, the engine checks for `IRedirectable` and, if the node implements it, re-runs the graph toward the returned target under an internal retry limit. Redirects are therefore an extension point you implement on your own nodes — the engine supports them end to end, and the demo's Python node is the reference implementation (its script names the node to fall back to, by title, and the node resolves that to a compile order).
Three properties worth calling out, because they keep reverse compilation honest:
- **Branches are real, never bypassed.** Terminal compilation keeps a router's true `BranchSegment` behavior; it only compiles the branch that leads into the target's cone. If the router actually selects a *sibling* branch at runtime, the target is **not reached** — you get an explicit "… was NOT reached … No result was produced." outcome, never a fabricated value. (Agent tools surface this as a specific error message; set the router's selection to the right branch and retry.)
- **Joins aggregate by source.** A multi-input node receives an `IGroupData` — a read-only map keyed by its upstream node — so a join "waits for all inputs" however the fan-out's branches interleaved (the shared runtime session is intentionally not thread-safe, which is why a branch that burns CPU still takes the thread in turn rather than running beside its siblings).
- **`TargetReached` means "the target was driven", not "a value was produced".** The flag is set as the engine enters the target node, so read it together with the run status: a target that throws surfaces as a run error (`Run failed: …`), not as a reached target.
```csharp
// Minimal "node" — the generator wires INotifyPropertyChanged, slot lifecycle and commands.
[WorkflowBuilder.Node<MyNodeHelper>]
public partial class MyNodeViewModel
{
public MyNodeViewModel() => InitializeWorkflow();
[AgentContext(AgentLanguages.English, "Input slot (receiver)")]
[VeloxProperty] public partial MySlotViewModel InputSlot { get; set; }
[AgentContext(AgentLanguages.English, "Output slot (sender)")]
[VeloxProperty] public partial MySlotViewModel OutputSlot { get; set; }
[VeloxProperty] private string title = "My Node";
}
```
---
## 🤖 AI control
The **Workflow Agent** turns a workflow Tree into a tool surface for any `IChatClient` (Microsoft.Extensions.AI), so an LLM can inspect the graph, build nodes, connect slots, patch properties, and run — or reverse-compute — compiled chains:
```csharp
var scope = tree.AsAgentScope()
.WithAutoDiscovery(assemblyName: "MyApp")
.WithInteractionSafety(3) // confirm before destructive ops; present choices via tool
.WithSelectionHandler(ShowDialog)
.WithConfirmationHandler(ShowDialog);
var agent = chatClient.AsAIAgent(
instructions: scope.ProvideProgressiveContextPrompt(),
tools: scope.ProvideTools());
```
Highlights of the tool surface:
- **Inspect & mutate like the GUI** — `ListNodes`, `GetFullTopology`, `CreateNode`, `ConnectByProperty`, `PatchNodeProperties`, `SetEnumSlotCollection`, `Undo`/`Redo`, `MoveNode`, … every mutation dispatches the same component command the GUI dispatches, so the Agent and the GUI share one edit path — including the same undo semantics, i.e. the moves and property patches that create no undo entry.
- **Execute at three levels** — node-level (`ExecuteNode`), chain-level (`RunCompiledWorkflow`, Root role), and **result-level** (`GetNodeResult`, Terminal role). Plans can be read without running via `CompileWorkflow` / `CompileNodeResult`.
- **Gated by policy, not just prose** — node-execution tools are disabled until the host calls `WithAllowNodeExecution(true)`; generic command execution is allow-listed; interaction tools appear only when a selection/confirmation handler is wired. `MaxToolCalls`, `MaxReadToolCalls` and `MaxWriteToolCalls` bound a session.
- **Precision is baked into the prompt, in the host's language** — embedded (en/zh) prompt docs describe tool semantics, error/rejection handling, mount-before-operate and the exact "target not reached" contract, so the agent knows *before calling* what each tool does and what errors mean.
### 🔌 Connect MCP servers for external tooling
```csharp
var mcp = new McpScope()
.WithMcpRoot(".evn/mcp")
.WithSynchronizationContext(SynchronizationContext.Current);
var configs = new[]
{
// Local stdio server (npx)
new McpServerConfiguration
{
Name = "Filesystem",
RunMode = McpServerRunMode.Npx,
Package = "@modelcontextprotocol/server-filesystem",
Arguments = ["C:/data"],
},
// Remote server over Streamable HTTP (SSE fallback for legacy servers)
new McpServerConfiguration
{
Name = "Microsoft Learn",
RunMode = McpServerRunMode.Http,
Endpoint = "https://learn.microsoft.com/api/mcp",
Options = new { connectionTimeout = 30 },
// Header auth: Options = new { headers = new { Authorization = "Bearer <token>" } }
// OAuth 2.0: Options = new { oauth = new { clientId = "...", redirectUri = "...", scopes = new[] { "read" } } }
},
};
var mcpTools = await mcp.LoadAsync(configs);
var allTools = scope.ProvideTools().Concat(mcpTools).ToArray(); // merge into the agent
```
`McpScope` installs npm packages idempotently, manages stdio/HTTP transports, reports per-server failures without blocking the rest, and supports OAuth via `WithOAuthAuthorizationRedirect(...)`.
---
## 📦 Installation
Install one of the [platform adapter packages](#platform-adapter-packages) listed above and you get everything — workflow, execution engine, agent, animations and theming — wired up for that GUI.
### Generate a view suite from templates
Each adapter ships a `dotnet new` template pack that generates the full view suite — Node, Slot, Link, Tree, template selector, grid decorator and minimap — pre-wired to the model. WPF example (replace `MyApp` with your root namespace):
```powershell
dotnet new install VeloxDev.WPF.Templates
dotnet add package VeloxDev.WPF
dotnet new wpf-v-slot -n SlotView -ns MyApp.Views -o Views
dotnet new wpf-v-node -n NodeView -ns MyApp.Views -o Views
dotnet new wpf-v-link -n LinkView -ns MyApp.Views -o Views
dotnet new wpf-v-selector -n TemplateSelector -ns MyApp.Views -o Views
dotnet new wpf-v-decorator -n GridDecorator -ns MyApp.Views -o Views
dotnet new wpf-v-minimap -n MinimapOverlay -ns MyApp.Views -o Views
dotnet new wpf-v-tree -n TreeView -ns MyApp.Views -o Views
dotnet build
```
Avalonia, WinUI, MAUI, WinForms and Jalium suites expose the same style options (`jalium-v-*` for Jalium). Common style aliases:
| Template | Style aliases |
|----------|---------------|
| Node | `-bg` background, `-fg` foreground, `-bb` border brush, `-bt` border thickness, `-cr` corner radius |
| Slot | `-bg` background, `-sc` standby color, `-bc` border color, `-sp` SVG path data |
| Link | `-lc` line color, `-lt` line thickness |
| Tree | `-bg` background, `-bb` border brush, `-bt` border thickness, `-cr` corner radius |
| Grid decorator | `-bg` background, `-mic` minor color, `-mac` major color, `-ac` axis color, `-gs` spacing, `-mle` major interval, `-rb` ruler background, `-rtc` ruler tick color, `-rlc` ruler label color, `-rdc` ruler divider color |
| Minimap overlay | `-bg` background, `-bdr` border, `-nf` node fill, `-vs` viewport stroke |
All templates use `-ns` for the generated namespace.
---
## 🗂️ Repository Layout
```text
VeloxDev/
├── VeloxDev.slnx
├── Src/
│ ├── Core/
│ │ ├── VeloxDev.Core # Workflow model, generators, canvas math, CompilerEx engine
│ │ ├── VeloxDev.Core.Extension # Workflow Agent tools, MCP scope, runtime extensions
│ │ ├── VeloxDev.Core.Test # Unit tests (model / canvas / execution)
│ │ └── VeloxDev.Core.Extension.Test # Unit tests (agent tools / serialization / lifecycle)
│ ├── Adapters/
│ │ ├── VeloxDev.WPF · VeloxDev.Avalonia · VeloxDev.WinUI · VeloxDev.MAUI
│ │ ├── VeloxDev.WinForms · VeloxDev.Razor · VeloxDev.Jalium # one view layer per platform
│ ├── Generators/
│ │ └── VeloxDev.Core.Generator # Roslyn source generators (netstandard2.0)
│ └── Templates/ # dotnet new item template packs per GUI adapter
├── Examples/
│ ├── Workflow/ # WPF · Avalonia · WinUI · WinForms · MAUI · Razor · Jalium
│ │ # (+ "Trimmed" variants that compile no extraneous generator code)
│ │ # Common/Lib holds the shared demo node library (Controller, routers, python workers)
│ ├── MVVM/ # WPF · Avalonia
│ ├── Transition/ # WPF · Avalonia · WinUI · WinForms · MAUI · Razor · Jalium
│ ├── Theme/ · AOP/ # WPF · Avalonia
│ └── Tickable/ # WPF
└── Docs/
└── VeloxDev.Docs # Documentation site (WebAssembly: online/local wiki)
```
---
## 🧪 Tests & coverage
Two test suites live beside the source (`VeloxDev.Core.Test`, `VeloxDev.Core.Extension.Test`) and are deliberately fast (~500+ assertions with no I/O in the hot path):
```bash
dotnet test # or open VeloxDev.slnx and run in Visual Studio
# targeted:
dotnet test Src/Core/VeloxDev.Core.Test --filter "FullyQualifiedName~CompilerEx"
```
Coverage is collected with **coverlet** (`--collect:"XPlat Code Coverage"`). The `CompilerEx` execution engine — compile decomposition, runtime driving, redirects, joins, and reverse compilation — is covered end-to-end with self-contained contract tests (probe nodes, no UI/no demo dependency). A full XML-comment/language pass keeps the public API documented in English.
---
## ⚠️ Scope & status
Worth knowing before you depend on them:
- **Not AOT- or trim-safe, and it says so.** `VeloxDev.Core` declares `IsTrimmable=false`, and the animation path compiles expression trees at runtime (`Expression.Lambda(...).Compile()`). There are no `RequiresUnreferencedCode` / `DynamicallyAccessedMembers` annotations anywhere in it. (The `Examples/*/"Trimmed"` folders are *minimal* demos — the name is not about trimming configuration.)
- **Redirects are a contract, and one node ships with it.** The engine drives `IRedirectable` end to end; the demo's Python node is the reference implementation (its script names the node to fall back to and the node resolves that name to a compile order), and the demo graph uses it to send a run back when the data is too thin to trust. A redirect target must still be strictly backward, and a target inside a nested branch is out of reach — see the limitation noted with the demo graph.
- **Undo coverage is structural.** Node/slot create and delete, connect/disconnect, selector changes and their cascades are undoable; position, size and direct property patches are not.
- **Fan-out overlaps, but it is not thread parallelism** — branches are interleaved async operations on the host's `SynchronizationContext` (see above). A compiled run shares one runtime blackboard, which is intentionally not thread-safe, so a node that starts its own `Task.Run` is on its own.
- **Waiting, sleeping and offloading CPU work are node-body decisions, not engine features.** `ReceiveAsync` is async, so a timer is `await Task.Delay(...)` inside the node, an external signal is awaiting a `TaskCompletionSource`, and real parallelism for one branch is `Task.Run` — subject to the caveat above about not touching the shared session from that thread. Timing and threading therefore look absent from the engine's feature list rather than missing from it. What does *not* follow from this is a wait that outlives the process: a run in flight lives in memory, so a durable wait rides on the checkpoint — save the place, reload the graph, carry on — and `ExecutionCheckpoint.Rekey` is what makes the reload legal, since a restored graph has fresh node identities.
- **A run can be paused, watched, retried, checkpointed and resumed — off by default.** `RuntimeContext` takes an `IExecutionGate`, an `IExecutionObserver`, an `INodeRetryPolicy`, an `IExecutionErrorSink`, an `IExecutionCompensation` and an `IExecutionCheckpointStore`; with none configured a run behaves exactly as it did before they existed, down to the log lines and the number of node drives. What still has no shipped implementation is `IRedirectable` (a node's own fallback contract) — see the note above.
- **Animation overshoot is per-type today.** When an ease overshoots (Back/Elastic), numeric samplers extrapolate past the endpoint while the remaining samplers pin to it. Unifying the two is an open pass.
- **The one model / 7 GUIs seam is at data and geometry, not rendering.** The transition engine and the workflow surface math live in Core and each adapter contributes only a small pacer subclass, but the *view* layer (surface behaviour, virtualization window, pooling, link rendering) is implemented per platform.
---
## 📄 License
Released under the [MIT License](LICENSE.txt). © 2025 Axvser
Connection Info
You Might Also Like
No recommendations available