Skip to content
AgentEnsemble AgentEnsemble
Get Started

Operational Profiles

AgentEnsemble v3.0.0 introduces operational profiles for pre-planned capacity adjustments in anticipation of known load changes. A NetworkProfile defines per-ensemble capacity targets and shared memory pre-load directives that can be applied manually, via the directive system, or on a schedule.


// Define a profile for a busy weekend
NetworkProfile weekendProfile = NetworkProfile.builder()
.name("sporting-event-weekend")
.ensemble("front-desk", Capacity.replicas(4).maxConcurrent(50))
.ensemble("kitchen", Capacity.replicas(3).maxConcurrent(100))
.preload("kitchen", "inventory", "Extra beer and ice stocked")
.build();
// Apply it
ProfileApplier applier = new ProfileApplier(sharedMemoryRegistry, message -> broadcast(message));
applier.apply(weekendProfile);

A NetworkProfile bundles a name, per-ensemble capacity targets, and pre-load directives into a single deployable unit:

NetworkProfile profile = NetworkProfile.builder()
.name("sporting-event-weekend")
.ensemble("front-desk", Capacity.replicas(4).maxConcurrent(50))
.ensemble("kitchen", Capacity.replicas(3).maxConcurrent(100))
.ensemble("maintenance", Capacity.replicas(2).maxConcurrent(20))
.preload("kitchen", "inventory", "Extra beer and ice stocked")
.preload("front-desk", "alerts", "High occupancy expected this weekend")
.build();
MethodDescription
name(String)Profile name (required, must not be blank)
ensemble(String, Capacity)Capacity target for a named ensemble
preload(String, String, String)Pre-load content into a shared memory scope

The Capacity record defines replica count and concurrency limits for an ensemble. It uses a fluent API:

// 4 replicas, 50 max concurrent tasks
Capacity active = Capacity.replicas(4).maxConcurrent(50);
// Scale to zero (dormant)
Capacity dormant = Capacity.replicas(0).dormant(true).build();
// Default maxConcurrent (10) with explicit build
Capacity minimal = Capacity.replicas(1).build();
FieldTypeDefaultDescription
replicasintTarget replica count (0 for scale-to-zero)
maxConcurrentint10Maximum concurrent tasks per replica
dormantbooleanfalseWhether the ensemble should be dormant

Pre-load directives inject content into shared memory scopes when a profile is applied. This seeds ensembles with context before the anticipated load arrives.

NetworkProfile profile = NetworkProfile.builder()
.name("conference-week")
.ensemble("kitchen", Capacity.replicas(3).maxConcurrent(100))
.preload("kitchen", "inventory", "Extra stock for 500 conference attendees")
.preload("front-desk", "events", "Tech conference check-in Monday 8am-12pm")
.build();

Each preload(ensembleName, scope, content) creates a PreloadDirective that stores a MemoryEntry into the named shared memory scope during profile application. The scope must be registered in the SharedMemoryRegistry.


The ProfileApplier orchestrates profile application in two steps:

  1. Execute pre-load directives — store content into shared memory scopes
  2. Broadcast a ProfileAppliedMessage — notify all connected ensembles
SharedMemoryRegistry smRegistry = new SharedMemoryRegistry();
smRegistry.register("inventory", SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.EVENTUAL)
.build());
ProfileApplier applier = new ProfileApplier(
smRegistry,
message -> broadcastToNetwork(message));
applier.apply(weekendProfile);

If a pre-load directive references a scope that is not registered, the applier logs a warning and skips it.


The NetworkProfileDirectiveHandler integrates profiles with the directive system, enabling profile application via APPLY_PROFILE control plane directives:

Map<String, NetworkProfile> profiles = Map.of(
"weekend", weekendProfile,
"normal", normalProfile);
NetworkProfileDirectiveHandler handler =
new NetworkProfileDirectiveHandler(applier, profiles);
directiveDispatcher.registerHandler("APPLY_PROFILE", handler);

When the ensemble receives an APPLY_PROFILE directive with a profile name as its value, the handler looks up the profile and applies it:

// This directive triggers weekendProfile
Directive directive = Directive.of("APPLY_PROFILE", "weekend");

Unknown profile names are logged as warnings and ignored.


The ProfileScheduler applies profiles on a recurring schedule:

ProfileScheduler scheduler = new ProfileScheduler(applier);
// Apply the weekend profile every 7 days, starting in 2 hours
scheduler.schedule(weekendProfile,
Duration.ofHours(2), // initial delay
Duration.ofDays(7)); // interval
// Apply a one-shot profile after a delay
scheduler.scheduleOnce(normalProfile, Duration.ofDays(3));
// Shutdown when done
scheduler.close();
MethodDescription
schedule(profile, initialDelay, interval)Apply at a fixed interval
scheduleOnce(profile, delay)Apply once after a delay
close()Cancel all scheduled tasks and shut down

The scheduler runs on a daemon thread and catches exceptions from individual applications to prevent one failure from stopping the schedule.


When a profile is applied, a ProfileAppliedMessage is broadcast to all connected ensemble dashboards:

{
"type": "profile_applied",
"profileName": "sporting-event-weekend",
"capacities": {
"front-desk": { "replicas": 4, "maxConcurrent": 50, "dormant": false },
"kitchen": { "replicas": 3, "maxConcurrent": 100, "dormant": false }
},
"appliedAt": "2026-03-28T14:30:00Z"
}

Consumers of this message can use the capacity targets to trigger actual scaling (e.g., adjusting Kubernetes HPA targets or replica counts).