You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: document the event broker and the notification channel
The README and wiki described one kind of event. There are two, and conflating
them is the mistake the docs should prevent: a notification reports on a run and
may be lost at the cost of a stale dashboard, while a domain event causes a run
and may not be lost at all.
README gains an Events section covering both channels, the two new endpoints,
Runtime.Notify, INotifyingWorkflow, transient token streams, and the LLM pricing
configuration.
The wiki's events section now separates the two, and documents emitting custom
notifications, the notification policy and its two safety rules, transient
events, and LLM telemetry. A new "Event broker and event-driven workflows"
section covers publishing, triggers, waits, topic matching, delivery scope and
the transport table.
Extension points gain contracts for custom brokers and subscription stores,
including the three obligations a transport has to meet and why exactly-once
resumption belongs to the store rather than the wire.
Troubleshooting gains the six questions this feature will actually generate,
including the two whose answer is "that is by design": llm.delta is absent from
event history because it is never stored, and AwaitingInput is what a parked
wait looks like.
Copy file name to clipboardExpand all lines: README.md
+103-6Lines changed: 103 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Abacus Run
2
2
3
-
Abacus Run is a .NET workflow runtime and HTTP host for durable, observable workflow instances. It provides workflow version resolution, bounded concurrency, retries, checkpoints, approvals, event history, server-sent events, cancellation, reruns, and redacted audit/logging surfaces.
3
+
Abacus Run is a .NET workflow runtime and HTTP host for durable, observable workflow instances. It provides workflow version resolution, bounded concurrency, retries, checkpoints, approvals, event history, server-sent events, a topic event broker for event-driven pipelines, cancellation, reruns, and redacted audit/logging surfaces.
4
4
5
5
The runtime is built on Microsoft Agent Framework workflows. Stores are exposed through interfaces so the in-memory implementation can be replaced by durable persistence without changing workflow definitions.
6
6
@@ -163,7 +163,8 @@ public static WorkflowHostBuilder AddAbacus(this IServiceCollection services, IC
163
163
164
164
if (configuration["Abacus:Redis:ConnectionString"] is { Length: >0 } redis)
| Razor Pages, SQL Server stores, Redis event bus and broker, startup wiring | your service (`Abacus.Run.Service`) |
202
203
203
204
The library carries no Razor, MVC, Entity Framework, or Redis dependency, and an architecture test in the integration suite fails the build if one drifts back in. Splitting a UI host out later is therefore a matter of moving Razor and infrastructure projects, not of untangling the runtime.
204
205
@@ -301,6 +302,86 @@ mismatched name returns `404`. `?section=plan,output` narrows the response to na
301
302
-`GET /instances/{id}/approvals`
302
303
-`POST /approvals/{approvalId}/decision`
303
304
305
+
### Domain events
306
+
307
+
-`POST /events`
308
+
-`GET /subscriptions`
309
+
310
+
Publish a message to a topic, and list what is listening or waiting. See [Events](#events).
311
+
312
+
## Events
313
+
314
+
Two kinds of events share the word and almost nothing else.
315
+
316
+
A **notification** describes what a run is doing — keyed by instance, ordered by a gapless sequence,
317
+
delivered to whoever is watching. It never affects execution; lose one and a dashboard is briefly out
318
+
of date.
319
+
320
+
A **domain event** describes what happened in the business — keyed by topic, routed to whoever
321
+
declared interest, and it *causes* execution; lose one and work that should have happened never does.
322
+
323
+
That asymmetry is why they are built differently: notifications are best-effort fan-out over a
324
+
durable log, while broker delivery is a durable state transition. A domain event may cause a
325
+
notification; a notification may never cause work.
326
+
327
+
### Notifications from a node
328
+
329
+
`Runtime.Notify` puts a workflow-defined event on the instance's stream, and is nullable in the same
330
+
way `Runtime.Audit` is:
331
+
332
+
```csharp
333
+
if (Runtime.Notifyis { } notify)
334
+
{
335
+
awaitnotify.NotifyAsync("documents.scanned", new { count=3 }, cancellationToken);
336
+
}
337
+
// → event: custom.documents.scanned
338
+
```
339
+
340
+
The `custom.` prefix is applied by the runtime and cannot be opted out of, so a workflow can never
341
+
shadow a framework event, and a consumer can filter the whole class on the prefix.
342
+
343
+
A definition controls what its runs emit by implementing `INotifyingWorkflow` — `Minimal`,
344
+
`Lifecycle` or `Standard`, overridable per node in both directions, plus the custom names it declares
345
+
for the catalog API. Terminal events are never suppressible, and filtering happens before a sequence
346
+
number is taken, so the gapless sequence that `Last-Event-ID` catch-up depends on stays intact.
347
+
348
+
An `LlmExecutor` emits one `llm.completed` per call carrying model, prompt version, tokens, cost,
349
+
latency and finish reason. Streamed tokens (`llm.delta`, opt-in per node via `StreamDeltas`) are
350
+
**transient**: fanned out live, never stored, and written without an SSE `id:`, so a reconnecting
351
+
client never waits for a chunk that no longer exists.
352
+
353
+
### Event-driven workflows
354
+
355
+
A workflow publishes to a topic, and another workflow either starts because of it or wakes up
Events record what the runtime did. An audit record answers the separate question of why a run's
@@ -399,7 +480,23 @@ Options are read from the `WorkflowHost` configuration section. For example:
399
480
400
481
The default host uses in-memory instance, event, log, approval, checkpoint, blob, audit, and audit-record stores. Treat this configuration as development-oriented until durable store implementations are supplied.
401
482
402
-
Set `Abacus:SqlServer:ConnectionString` to enable the EF Core SQL Server stores and `Abacus:Redis:ConnectionString` to enable Redis Streams and control messages. `AddAbacus` keeps the in-memory stores when these settings are absent.
483
+
Set `Abacus:SqlServer:ConnectionString` to enable the EF Core SQL Server stores and `Abacus:Redis:ConnectionString` to enable Redis Streams, control messages, and the cross-service event broker. `AddAbacus` keeps the in-memory stores and the in-process broker when these settings are absent.
484
+
485
+
`Abacus:Llm:Pricing` turns token counts into cost on `llm.completed` and into a drift signal:
A model with no entry reports `null` rather than zero, and unpriced samples are excluded from the cost baseline — "we do not know" and "it was free" are different facts, and conflating them would mask a later cost rise.
403
500
404
501
`Abacus:AuditRecords:ConnectionString` points the SQLite audit-record store at its database file and
405
502
defaults to `Data Source=./data/abacus-audit.db`. The directory is created and the migrations applied
@@ -410,7 +507,7 @@ at startup.
410
507
| Project | Responsibility |
411
508
| --- | --- |
412
509
|`src/Abacus.Run`| Headless framework: workflow runtime, dispatch, executors, middleware, in-memory store defaults, and HTTP API endpoints |
413
-
|`src/Abacus.Run.Service`| Deployable host: control-plane UI, SQL Server stores, Redis event bus, the SQLite audit-record store, startup wiring, and the example workflow |
510
+
|`src/Abacus.Run.Service`| Deployable host: control-plane UI, SQL Server stores, Redis event bus and event broker, the SQLite audit-record store, startup wiring, and the example workflow |
414
511
|`tests/Abacus.Run.UnitTests`| Unit coverage for runtime behavior; references the library only |
415
512
|`tests/Abacus.Run.IntegrationTests`| HTTP, control-plane, and architecture-boundary coverage against the real host |
416
513
|`tests/Abacus.Run.ChaosTests`| Failure and lifecycle resilience coverage |
0 commit comments