Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
91188b9
Added the new error states, as well as an enum to define the behavior…
nkleiner Apr 28, 2026
4c3679f
Error handling updates
nkleiner Apr 29, 2026
25ce589
Merge branch 'Develop' into UserDefinableErrorHandling
nkleiner May 8, 2026
454cc83
Settings menu now has a functionable selection process for handling e…
nkleiner May 12, 2026
f49f299
Working towards integrating the new settings into the loop. First wit…
nkleiner May 12, 2026
fc27ce1
Spacing change
nkleiner May 12, 2026
6d6186c
Working on adding some general settings
nkleiner May 14, 2026
6edeb8e
Forgot some additional changes
nkleiner May 14, 2026
8460a5e
Some cleanup and additional UI for general settings
nkleiner May 14, 2026
8cc1d74
Most error handling cases are now working. Only outlier left is the "…
nkleiner May 14, 2026
1f417d7
Basic implementation of the user decision request for when commands f…
nkleiner May 15, 2026
a792cb8
Updates to solidify execution logic in the new "state based" executio…
nkleiner May 18, 2026
d9b1fc1
Updates to support durations in settings values
nkleiner May 18, 2026
612daa3
Updated entity configurations
nkleiner May 18, 2026
3f1c39f
Updated system settings to handle new duration type gracefully
nkleiner May 18, 2026
7ddad89
Working towards support for batch planning
nkleiner Jun 1, 2026
201a0a8
Merge branch 'Develop' into UserDefinableErrorHandling
nkleiner Jun 1, 2026
78eee8d
Merge branch 'Develop' into UserDefinableErrorHandling
nkleiner Jun 3, 2026
a83c5b0
Fixed some tests
nkleiner Jun 3, 2026
271774b
Fixed some bugs in the retry command logic. Added notifications to co…
nkleiner Jun 4, 2026
0564bc1
Updates to make the notification stream a bit more stable and thread …
nkleiner Jun 4, 2026
6531f6c
Report status consistently
nkleiner Jun 4, 2026
4519ca9
Migration updates
nkleiner Jun 4, 2026
f555277
Fixed some tests
nkleiner Jun 4, 2026
e0ef999
Fixed the waiting for user not working
nkleiner Jun 4, 2026
cb18639
Fixed duplicate entity config. Fixed visual bug in execution screen.
nkleiner Jun 4, 2026
66cdacf
Provide status code to plan requests
nkleiner Jun 10, 2026
62b0035
Merge branch 'Develop' into BatchPlanning
nkleiner Jun 11, 2026
ef7e72a
Merge branch 'Develop' into UserDefinableErrorHandling
nkleiner Jun 17, 2026
824d734
Working on campaign executor
nkleiner Jun 17, 2026
153cdc6
Updated toolkit version
nkleiner Jun 18, 2026
c593475
Merge branch 'Develop' into UserDefinableErrorHandling
nkleiner Jun 18, 2026
7d521f7
Refactored the planning helper class to move towards supporting batch…
nkleiner Jun 22, 2026
63699fa
Merge branch 'Develop' into BatchPlanning
nkleiner Jun 22, 2026
e04633e
Missed these, oops
nkleiner Jun 22, 2026
4da1be1
Merge branch 'UserDefinableErrorHandling' into BatchPlanning
nkleiner Jun 22, 2026
e1c357b
Additional changes to support batch planning in ARES
nkleiner Jun 23, 2026
189d545
Updated for list of status codes for planning rather than a single one.
nkleiner Jun 23, 2026
156518e
Updated datamodel version
nkleiner Jun 23, 2026
73414de
Lil typo
Babreakfast Jun 29, 2026
77a983f
Addressed some review comments
nkleiner Jun 29, 2026
3eaf054
Merge branch 'BatchPlanning' of https://github.com/AFRL-ARES/ARES int…
nkleiner Jun 29, 2026
85abef3
Merge branch 'Develop' into BatchPlanning
nkleiner Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Ares.Core.Grpc/Services/AutomationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,18 @@ public override Task<Empty> SetReplicateRate(ReplicateRate request, ServerCallCo

public override Task<ReplicateRate> GetReplicateRate(Empty request, ServerCallContext? context)
{
return Task.FromResult(new ReplicateRate { ReplicateRate_ = _executionManager.ReplanRate });
return Task.FromResult(new ReplicateRate { ReplicateRate_ = _executionManager.ReplicateRate });
}

public override Task<Empty> SetPlanningBatchSize(PlanningBatchSize request, ServerCallContext? context)
{
_executionManager.UpdateBatchPlanningSize(request.BatchSize);
return Task.FromResult(new Empty());
}

public override Task<PlanningBatchSize> GetPlanningBatchSize(Empty request, ServerCallContext? context)
{
return Task.FromResult(new PlanningBatchSize { BatchSize = _executionManager.PlanningBatchSize });
}

public override Task<Empty> SetAnalysisResultStopCondition(AnalysisResultCondition request, ServerCallContext? context)
Expand Down
4 changes: 4 additions & 0 deletions Ares.Core.Tests/Execution/CampaignExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public void SetUp()
It.IsAny<IEnumerable<Parameter>>(),
It.IsAny<IEnumerable<Ares.Datamodel.Analyzing.Analysis>>(),
It.IsAny<IEnumerable<ExperimentOverview>>(),
It.IsAny<int>(),
It.IsAny<List<PlanStatusCode>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(true);

Expand Down Expand Up @@ -218,6 +220,8 @@ public async Task Replan_Composes_And_Executes_Experiment_Again()
It.IsAny<IEnumerable<Parameter>>(),
It.IsAny<IEnumerable<Ares.Datamodel.Analyzing.Analysis>>(),
It.IsAny<IEnumerable<ExperimentOverview>>(),
It.IsAny<int>(),
It.IsAny<List<PlanStatusCode>>(),
It.IsAny<CancellationToken>()), Times.Once);
}

Expand Down
8 changes: 8 additions & 0 deletions Ares.Core/Execution/Enums/ExperimentLoopOutcome.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Ares.Core.Execution.Enums;

public enum ExperimentLoopOutcome
{
Succeeded,
Failed,
Canceled
}
15 changes: 15 additions & 0 deletions Ares.Core/Execution/Enums/ExperimentPhase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Ares.Core.Execution.Enums;

public enum ExperimentPhase
{
Initialize,
Plan,
Compose,
Execute,
Analyze,
Retry,
Replan,
Complete,
Failed,
Canceled
}
19 changes: 14 additions & 5 deletions Ares.Core/Execution/ExecutionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public async Task<bool> CanRun()
var startConditions = await Task.WhenAll(startConditionTasks);
return startConditions.All(condition => condition?.Success ?? true);
}
public int ReplanRate { get; private set; } = 1;

public async Task Start(string executionNotes, List<AresCampaignTag> campaignTags)
{
var err = await CheckCampaignStartPrerequisites();
Expand All @@ -73,7 +73,8 @@ public async Task Start(string executionNotes, List<AresCampaignTag> campaignTag
executor.UpdateCampaignTags(campaignTags);

executor.StopConditions.AddRange(CampaignStopConditions);
executor.ReplanRate = ReplanRate;
executor.ReplicateRate = ReplicateRate;
executor.BatchPlanningSize = PlanningBatchSize;
_executionControlTokenSource = new ExecutionControlTokenSource();
CampaignExecutionSummary campaignExecutionSummary;
ExecutionStartTime = DateTime.UtcNow;
Expand Down Expand Up @@ -111,7 +112,7 @@ public void Resume()
public async Task<string> CheckCampaignStartPrerequisites()
{
if(_activeCampaignTemplateStore.CampaignTemplate is null)
return "CampaignTemplate was not assigned to the active template store.";
return "Campaign Template was not assigned to the active template store.";

if(!CampaignStopConditions.Any())
return "The Campaign has no stop conditions, please set a stop condition before starting campaign.";
Expand Down Expand Up @@ -142,7 +143,12 @@ public bool EnsureParameterAssignment()

public void UpdateReplicateRate(int newRate)
{
ReplanRate = newRate;
ReplicateRate = newRate;
}

public void UpdateBatchPlanningSize(int newBatchSize)
{
PlanningBatchSize = newBatchSize;
}

public void SubmitUserDecision(ErrorHandling decision)
Expand Down Expand Up @@ -171,8 +177,11 @@ private async Task StoreCompletedCampaign(CampaignExecutionSummary result)
{
throw;
}

}

public DateTime? ExecutionStartTime { get; set; }

public int ReplicateRate { get; private set; } = 1;

public int PlanningBatchSize { get; private set; } = 1;
}
84 changes: 48 additions & 36 deletions Ares.Core/Execution/Executors/CampaignExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Ares.Core.Device.State.Logging;
using Ares.Core.Exceptions;
using Ares.Core.Execution.ControlTokens;
using Ares.Core.Execution.Enums;
using Ares.Core.Execution.Executors.Composers;
using Ares.Core.Execution.Extensions;
using Ares.Core.Execution.Safety;
Expand All @@ -13,6 +14,7 @@
using Ares.Core.Settings;
using Ares.Datamodel;
using Ares.Datamodel.Analyzing;
using Ares.Datamodel.Planning;
using Ares.Datamodel.Templates;
using Google.Protobuf.WellKnownTypes;
using Microsoft.Extensions.Logging;
Expand All @@ -22,28 +24,7 @@
namespace Ares.Core.Execution.Executors;

public class CampaignExecutor : ICampaignExecutor
{
private enum ExperimentPhase
{
Initialize,
Plan,
Compose,
Execute,
Analyze,
Retry,
Replan,
Complete,
Failed,
Canceled
}

private enum ExperimentLoopOutcome
{
Succeeded,
Failed,
Canceled
}

{
private readonly IExecutionReporter _executionReporter;
private readonly ISubject<CampaignExecutionStatus> _executionStatusSubject;
private readonly ICommandComposer<ExperimentTemplate, ExperimentExecutor> _experimentComposer;
Expand All @@ -66,6 +47,7 @@ private enum ExperimentLoopOutcome
private ExperimentTemplate? _currentExperimentTemplate = null;
private int _experimentCount = 0;
private TaskCompletionSource<ErrorHandling>? _userDecisionSource;
private List<PlanStatusCode> _latestPlanStatusCodes = new List<PlanStatusCode>();

internal CampaignExecutor(ICommandComposer<ExperimentTemplate, ExperimentExecutor> experimentComposer,
IPlanningHelper planningHelper,
Expand Down Expand Up @@ -226,8 +208,13 @@ private async Task NotifyCampaignStart()
return (true, startupSummary);
}

private async Task<ExperimentLoopOutcome> ExecuteExperimentLoop(string campaignPath, List<Analysis> analyses, List<ExperimentExecutionSummary> experimentSummaries, ExecutionControlToken token, ExperimentExecutionSummary startupSummary)
private async Task<ExperimentLoopOutcome> ExecuteExperimentLoop(string campaignPath,
List<Analysis> analyses,
List<ExperimentExecutionSummary> experimentSummaries,
ExecutionControlToken token,
ExperimentExecutionSummary startupSummary)
{
_latestPlanStatusCodes = new List<PlanStatusCode>();
var currentPhase = ExperimentPhase.Initialize;
var currentExperimentPath = "";
var failedExperimentRetryCount = 0;
Expand Down Expand Up @@ -317,7 +304,7 @@ private async Task<ExperimentPhase> PrepareExperiment(List<Analysis> analyses, L
if(!_currentExperimentTemplate.IsResolved())
{
_logger.LogTrace("Experiment has not been resolved, ARES will now begin the planning process.");
if(analyses.Count % ReplanRate == 0)
if(analyses.Count % ReplicateRate == 0)
{
if(!await PlanExperiment(analyses, _currentExperimentTemplate, experimentSummaries, token))
{
Expand Down Expand Up @@ -397,9 +384,17 @@ private async Task<ExperimentPhase> ExecuteCurrentExperiment(string currentExper
.SelectMany(s => s.CommandSummaries)
.FirstOrDefault(c => !c.Result.Success);

return failedCommandSummary is null
? ExperimentPhase.Analyze
: await HandleError(failedCommandSummary, token);
if(failedCommandSummary is null)
{
_latestPlanStatusCodes.Add(PlanStatusCode.PlanAccepted);
return ExperimentPhase.Analyze;
}

else
{
UpdatePlanStatus(failedCommandSummary.StatusCode);
return await HandleError(failedCommandSummary, token);
}
}

private async Task<ExperimentPhase> AnalyzeCurrentExperiment(ExperimentExecutionSummary startupSummary, List<Analysis> analyses, List<ExperimentExecutionSummary> experimentSummaries, ExecutionControlToken token)
Expand Down Expand Up @@ -479,6 +474,7 @@ private async Task<ExperimentPhase> HandleError(CommandExecutionSummary cmdSumma

var decisionSource = new TaskCompletionSource<ErrorHandling>(TaskCreationOptions.RunContinuationsAsynchronously);
_userDecisionSource = decisionSource;

try
{
errorHandling = await decisionSource.Task.WaitAsync(token.CancellationToken);
Expand Down Expand Up @@ -516,14 +512,25 @@ private async Task<ExperimentPhase> HandleError(CommandExecutionSummary cmdSumma
}
}


private void UpdatePlanStatus(CommandStatusCode failCode)
{
if(failCode == CommandStatusCode.OutOfRange || failCode == CommandStatusCode.ParametersUnachievable)
_latestPlanStatusCodes.Add(PlanStatusCode.PlanUnachievable);

else
_latestPlanStatusCodes.Add(PlanStatusCode.PlanFailed);
}

private async Task<bool> PlanExperiment(List<Analysis> analyses,
ExperimentTemplate currentExperimentTemplate,
List<ExperimentExecutionSummary> experimentSummaries,
ExecutionControlToken token)
{
Status.PlannerState = PlannerState.PlanningInProgress;
ReportCampaignStatus();
_logger.LogTrace("Analyses count is {count} and replan rate {rate}", analyses.Count(), ReplanRate);
_logger.LogTrace("Analyses count is {count} and replan rate {rate}", analyses.Count(), ReplicateRate);


var metadata = new RequestMetadata
{
Expand All @@ -540,6 +547,8 @@ private async Task<bool> PlanExperiment(List<Analysis> analyses,
currentExperimentTemplate.GetAllPlannedParameters(),
analyses,
experimentSummaries.Select(es => es.ExperimentOverview),
BatchPlanningSize,
_latestPlanStatusCodes,
token.CancellationToken);

if(!resolveSuccess)
Expand Down Expand Up @@ -579,7 +588,7 @@ private async Task<bool> PlanExperiment(List<Analysis> analyses,
// The following are top level checks for analysis failure in case the
// failure is not properly handled on the Analysis itself
// which also has support for "success" and "error" message
if (analysis is null || analysis.Result == float.NaN)
if(analysis is null || analysis.Result == float.NaN)
{
Status.AnalysisState = AnalysisState.AnalysisError;
await _notifier.Notify("Analysis Failure", $"Analysis was reported as successful, but no actual analysis was provided. {analysis?.ErrorString ?? "No error string provided"}", NotificationSeverityEnum.Error);
Expand Down Expand Up @@ -725,9 +734,9 @@ private static bool IsAwaitingResponse(ExperimentExecutionStatus status)
.Any(step => step.CommandExecutionStatuses
.Any(cmd => cmd.State == ExecutionState.AwaitingUser));

private async Task<ExperimentExecutorResult> GenerateExperimentExecutor(ExperimentTemplate template,
IEnumerable<Analysis> analyses,
IEnumerable<ExperimentOverview> previousExperiments,
private async Task<ExperimentExecutorResult> GenerateExperimentExecutor(ExperimentTemplate template,
IEnumerable<Analysis> analyses,
IEnumerable<ExperimentOverview> previousExperiments,
CancellationToken cancellationToken)
{
var result = new ExperimentExecutorResult();
Expand All @@ -737,11 +746,11 @@ private async Task<ExperimentExecutorResult> GenerateExperimentExecutor(Experime
if(!experimentTemplate.IsResolved())
{
_logger.LogTrace("Experiment was not resolved");
if(analyses.Count() % ReplanRate == 0)
if(analyses.Count() % ReplicateRate == 0)
{
Status.PlannerState = PlannerState.PlanningInProgress;
ReportCampaignStatus();
_logger.LogTrace("Analyses count is {count} and replan rate {rate}", analyses.Count(), ReplanRate);
_logger.LogTrace("Analyses count is {count} and replan rate {rate}", analyses.Count(), ReplicateRate);

var metadata = new RequestMetadata
{
Expand All @@ -757,7 +766,9 @@ private async Task<ExperimentExecutorResult> GenerateExperimentExecutor(Experime
metadata,
experimentTemplate.GetAllPlannedParameters(),
analyses,
previousExperiments,
previousExperiments,
BatchPlanningSize,
_latestPlanStatusCodes,
cancellationToken);

if(!resolveSuccess)
Expand Down Expand Up @@ -840,7 +851,8 @@ private async Task PostExperimentExecution(ExperimentExecutionSummary summary)

public CampaignTemplate Template { get; }
public IList<IStopCondition> StopConditions { get; } = [];
public double ReplanRate { get; set; } = 1;
public int ReplicateRate { get; set; } = 1;
public int BatchPlanningSize { get; set; } = 1;
public string? ExecutionNotes { get; set; }
public List<AresCampaignTag> CampaignTags { get; set; } = [];
public IObservable<CampaignExecutionStatus> ExperimentStatusObservable { get; }
Expand Down
1 change: 0 additions & 1 deletion Ares.Core/Execution/Executors/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ public async Task<CommandExecutionSummary> Execute(ExecutionControlToken token,

Status.Result = result.Result;
_stateSubject.OnNext(Status);
_stateSubject.OnCompleted();

return ExecutorSummaryHelpers.CreateCommandExecutionSummary(Template, result, timeStarted, DateTime.UtcNow);
}
Expand Down
3 changes: 2 additions & 1 deletion Ares.Core/Execution/Executors/ExecutorSummaryHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public static CommandExecutionSummary CreateCommandExecutionSummary(CommandTempl
CommandDescription = template.Metadata.Description,
CommandName = template.Metadata.Name,
StatusCode = deviceResult?.StatusCode ?? CommandStatusCode.StatusUnspecified
};
};


if(template.HasOutputVarName)
commandExecutionSummary.VarName = template.OutputVarName;
Expand Down
1 change: 1 addition & 0 deletions Ares.Core/Execution/Executors/ExperimentExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public ExperimentExecutor(ExperimentTemplate template, IExecutor<StepExecutionSu

Status.StepExecutionStatuses.AddRange(experimentStepExecutors.Select(executor => executor.Status));


var experimentStepExecutionObservation = experimentStepExecutors.Select(executor =>
{
return executor.ExperimentStatusObservable.Select(_ =>
Expand Down
4 changes: 2 additions & 2 deletions Ares.Core/Execution/Executors/ICampaignExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Ares.Core.Execution.Executors;
public interface ICampaignExecutor : IExecutor<CampaignExecutionSummary, CampaignExecutionStatus>
{
IList<IStopCondition> StopConditions { get; }
double ReplanRate { get; set; }
int ReplicateRate { get; set; }
int BatchPlanningSize { get; set; }
void UpdateExecutionNotes(string executionNotes);

void UpdateCampaignTags(List<AresCampaignTag> campaignTags);

void SubmitUserDecision(ErrorHandling decision);
Expand Down
14 changes: 12 additions & 2 deletions Ares.Core/Execution/IExecutionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ public interface IExecutionManager
public IList<IStopCondition> CampaignStopConditions { get; }

/// <summary>
/// A double value that determines how often a campaign will re-plan it's experiment, defaults to one
/// A int value that determines how often a campaign will re-plan it's experiment, defaults to one
/// </summary>
public int ReplanRate { get; }
public int ReplicateRate { get; }

/// <summary>
/// An int value that determines how many experiments are planned for per planning request
/// </summary>
public int PlanningBatchSize { get; }

/// <summary>
/// Indicates whether the currently loaded campaign has all the prerequisites in order to start and run
Expand Down Expand Up @@ -55,6 +60,11 @@ public interface IExecutionManager
void UpdateReplicateRate(int newRate);

/// <summary>
/// Updates the batch planning size
/// </summary>
/// <param name="batchSize"></param>
void UpdateBatchPlanningSize(int batchSize);

/// Submits a user decision for how to handle an error that has occurred during execution
/// </summary>
/// <param name="decision"></param>
Expand Down
2 changes: 2 additions & 0 deletions Ares.Core/Planning/IPlanningHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Task<bool> TryResolveParameters(IEnumerable<PlannerAllocation> plannerAllocation
IEnumerable<Parameter> parameters,
IEnumerable<Analysis> seedAnalyses,
IEnumerable<ExperimentOverview> seedExperiments,
int batchSize,
List<PlanStatusCode> codes,
CancellationToken cancellationToken);

/// <summary>
Expand Down
Loading
Loading