Agent hub

A Bowire embedded in each service of a fleet is handy until someone has to find them all. The hub is one Bowire that knows the others: every agent registers with it and keeps the registration fresh, and the hub lists them with a link to each one's own workbench.

The hub does not proxy. Opening an agent opens that agent's workbench, with that agent's authentication — the hub never carries traffic to a service, so it never needs rights on one.

Running a hub

Any Bowire becomes a hub with one setting:

{
  "Bowire": {
    "Hub": {
      "Enabled": true,
      "Token": "…",
      "TrustedAgentPrefixes": [ "https://orders.internal/", "https://payments.internal/" ]
    }
  }
}
Setting Environment variable Effect
Bowire:Hub:Enabled BOWIRE_HUB_ENABLED Maps the hub under /hub and lists the agents under Sources of every workspace, each with a link to its workbench.
Bowire:Hub:Token BOWIRE_HUB_TOKEN Every /hub/* call needs Authorization: Bearer <token>, otherwise 401. Unset, the hub takes registrations only from loopback — anyone else could otherwise put links of their choosing into the list.
Bowire:Hub:TrustedAgentPrefixes — Set, an agent's workbench URL has to start with one of these, otherwise 400. A list, or one string separated by , or ;.

In your own host, app.MapBowireHub() maps the same endpoints without the setting.

Endpoint
POST /hub/agents Register or heartbeat. The same service name and instance id update one entry; the answer is the agent's id.
GET /hub/agents Every agent, with lastSeen and live.
GET /hub/agents/catalogue The live agents' services, in the shape the agent catalogue provider reads.
DELETE /hub/agents/{id} Deregister.

An agent is live while its last heartbeat is younger than three of its heartbeat intervals. After that it stays listed, greyed out, and after thirty intervals of silence it is dropped. The list lives in memory: agents push, so a restarted hub is whole again after one heartbeat.

Making a Bowire an agent

Point it at the hub:

{
  "Bowire": {
    "Agent": {
      "HubUrl": "https://bowire-hub.internal",
      "Token": "…",
      "ServiceName": "orders",
      "Tags": [ "env:prod", "region:eu", "parallel-executor" ]
    }
  }
}

or in code, on the options of MapBowire:

app.MapBowire(options =>
{
    options.Agent.HubUrl = "https://bowire-hub.internal";
    options.Agent.ServiceName = "orders";
    options.Agent.Tags.Add("env:prod");
});
Bowire:Agent:* Default
HubUrl — The hub's base URL, with or without the trailing /hub. Without it the Bowire is no agent.
Token — The hub's token.
ServiceName the application name The name the hub lists.
InstanceId the machine name Tells instances of one service apart.
Version, Owner — Shown in the hub.
Tags — Labels to filter on; parallel-executor offers this Bowire as an executor.
CallbackUrl the listening address plus the Bowire route Where the hub links to. Set it behind a reverse proxy.
HeartbeatSeconds 30 How often the registration is refreshed.

Once the host listens, the agent registers, then heartbeats; on shutdown it deregisters. The services it announces are its configured server URLs, or, in embedded mode, the host itself. A hub that is down or refuses is logged once and retried at every heartbeat — the agent never fails its host over the hub.

The hub as a catalogue

Another Bowire can take the fleet as its URL catalogue with the agent provider:

{
  "Bowire": {
    "Discovery": {
      "Catalogue": {
        "Provider": "agent",
        "Agent": { "HubUrl": "https://bowire-hub.internal", "BootstrapToken": "…" }
      }
    }
  }
}

The provider ships in Kuestenlogik.Bowire.Catalogue.Agent: bowire plugin install Kuestenlogik.Bowire.Catalogue.Agent for the CLI, or the package plus builder.Services.AddBowireAgentCatalogue(builder.Configuration) in your own host.

Each entry carries the tags agent:<id> and service:<name> next to the agent's own, so the catalogue filter narrows it to one agent or one service.

Executors for parallel sessions

In the hub's own workbench, the Parallel sessions dialog lists the live agents tagged parallel-executor under the Hosts field; a click adds one. The executor still decides what it runs — see Running an executor safely.