Client Grain Tracking: Knowing Every User in FabrCore
FabrCore has always tracked agents created through a client grain. That answered the agent question: which assistants, workers, and specialists belong to a user? The missing half was the user question: which client grains have appeared in the system at all?
The Gap
In FabrCore, a ClientGrain is the user-facing actor. It owns the connection to the agent cluster, receives responses through streams, creates agents under a user handle, and persists the list of agents it created.
That per-client agent list is useful, but it does not give operators a central list of users. If a user connects and never creates an agent, or if an admin wants to audit all user handles that have touched the cluster, agent tracking alone is not enough.
The Registry Is Now the User Source of Truth
Client grains now register themselves in the same management registry used for agent lifecycle tracking. The entry shape stays simple: an AgentInfo record with EntityType.Client, AgentType set to Client, and the client grain key stored as both Key and Handle.
Key: "user1"
AgentType: "Client"
Handle: "user1"
Status: Active | Deactivated
EntityType: Client
When the grain activates, FabrCore marks the user active. When it deactivates, FabrCore marks the entry deactivated. The registration remains fail-open, matching agent registration behavior: registry errors are logged, but they do not block client activation.
Separate Agent and User Views
The diagnostics surface now keeps agents and users distinct. Agent queries return only EntityType.Agent. User queries return only EntityType.Client. That matters because shared-agent discovery, dashboards, and automation should not accidentally treat a user handle as an agent handle.
| Endpoint | What it returns |
|---|---|
GET /fabrcoreapi/Diagnostics/agents | Only agent registry entries |
GET /fabrcoreapi/Diagnostics/users | Only client grain/user entries |
?status=active | Entries currently marked active |
?status=deactivated | Entries that were seen and later deactivated |
The SDK mirrors that split with GetAgentsAsync() and GetUsersAsync().
User History Is Preserved
The cleanup job still purges old deactivated agents. That keeps the agent registry from collecting stale worker records forever. Users are different. A user handle is part of the system's audit surface, so client entries are retained unless a future explicit user-purge workflow removes them.
This gives teams a durable answer to operational questions like:
- Which user handles have ever connected to this FabrCore cluster?
- Which users are active right now?
- Which users have deactivated client grains but should remain visible for audit or reporting?
Why This Matters
Multi-agent systems quickly become multi-user systems. Operators need to inspect agent health, but they also need to know who owns the handles that agents are created under. Client grain tracking closes that loop without adding a separate user store.
FabrCore keeps the model intentionally small: agents and clients share a registry, but the public APIs separate them by entity type. The result is less custom bookkeeping for teams building Blazor, API, or console hosts on top of FabrCore.