Skip to content
Tracon

ISkillScriptExecutor

Namespace Tracon · Assembly Tracon.Abstractions.dll

Starts a skill script that already passed every execution gate, and reports what it did. Register your own implementation to run scripts somewhere other than the Tracon host: a container, a micro-VM, or a remote runner service.

public interface ISkillScriptExecutor

What stays with Tracon. Before the call, Tracon decides whether a script may run: the feature switch, the tenant’s SkillScriptGrant and its content pin, the interpreter allow-list, the argument size and schema, a plain file name for a stored script, the content size limit for a file script, and the audit trail entry. A request that fails one of them never reaches an executor. Tracon then takes a concurrency slot and holds it until the call returns or the wait limit passes. After the call, it measures the duration, enforces the output limit again, and writes the span, the metric, and the tool call record. An executor cannot skip a gate, and it cannot write or suppress the audit entry.

What the executor owns. Where and how the script runs: the process, its operating-system identity, its file system, its network, and its CPU and memory limits. Tracon cannot see or check any of this. The script runs where your executor runs it. The built-in default starts a local process under the host’s own operating-system identity.

Lifetime: singleton. Tracon resolves one instance when it builds its script runner and calls it concurrently from many runs. An implementation must be thread-safe. Register it before or after AddTracon with AddSingleton; both orders win over the built-in default. RequireCustomBinding<ISkillScriptExecutor> stops the host when the built-in default is what resolves.

Tenant mode: expected tenant. The request carries the tenant whose grant opened the gates (SkillScriptExecutionRequest.TenantId). An implementation uses that value and never reads ITenantContext: the call can run on a thread that belongs to no request.

Delivery: no delivery guarantee applies. Tracon calls the executor once for each approved run and never retries. A failure is reported to the model as a failed tool call; the model may call the script again, and that new call passes every gate again.

Guarantee limit. Tracon does not isolate the script process and cannot verify that an executor does. Cancellation is cooperative: Tracon cancels the token when the caller stops or when SkillScriptExecutionRequest.Timeout plus a fixed grace period has passed, and then stops waiting. It does not force the work to stop. A script whose executor ignores the token can keep running after Tracon recorded it as timed out.

Register it with builder.Services.AddSingleton<ISkillScriptExecutor, RunnerServiceScriptExecutor> and keep UseSkillScripts: the gates, the grant, and the interpreter allow-list still decide what reaches the executor. With a remote executor an allow-list value can be a runtime name, for example o.Interpreters["py"] = "python-3.12".

public sealed class RunnerServiceScriptExecutor(System.Net.Http.HttpClient runner) : ISkillScriptExecutor
{
private static readonly JsonSerializerOptions Web = new(JsonSerializerDefaults.Web);
public async ValueTask<SkillScriptExecutionResult> ExecuteAsync(
SkillScriptExecutionRequest request, CancellationToken cancellationToken = default)
{
// The runner service gets the content, never a host path, and stops
// the script at the timeout. The tenant lets it pick an isolated runner.
using var response = await runner.PostAsync(
"runs",
System.Net.Http.Json.JsonContent.Create(new
{
tenant = request.TenantId,
runtime = request.Interpreter,
source = request.Content,
sourceSha256 = request.ContentSha256,
standardInput = request.ArgumentsJson,
timeoutSeconds = request.Timeout.TotalSeconds,
maxOutputBytes = request.MaxOutputBytes,
}),
cancellationToken);
response.EnsureSuccessStatusCode();
var outcome = JsonSerializer.Deserialize<RunnerOutcome>(
await response.Content.ReadAsStringAsync(cancellationToken), Web)
?? throw new InvalidOperationException("The runner service returned no outcome.");
return new SkillScriptExecutionResult
{
ExitCode = outcome.TimedOut ? null : outcome.ExitCode,
StandardOutput = outcome.StandardOutput,
StandardError = outcome.StandardError,
Truncated = outcome.Truncated,
TimedOut = outcome.TimedOut,
};
}
private sealed record RunnerOutcome(
int? ExitCode, string StandardOutput, string StandardError, bool Truncated, bool TimedOut);
}

ExecuteAsync(SkillScriptExecutionRequest, CancellationToken)

Section titled “ ExecuteAsync(SkillScriptExecutionRequest, CancellationToken)”

Runs one script and reports its outcome.

ValueTask<SkillScriptExecutionResult> ExecuteAsync(SkillScriptExecutionRequest request, CancellationToken cancellationToken = default)

request SkillScriptExecutionRequest

The script to run. Every gate already passed and the audit entry is already written when this method is called.

cancellationToken CancellationToken

Cancelled when the caller stops the run, or when the timeout plus the grace period has passed and Tracon stops waiting.

ValueTask<SkillScriptExecutionResult>

The outcome. When the script runs past SkillScriptExecutionRequest.Timeout, stop it and return SkillScriptExecutionResult.TimedOut set to true and no exit code. Keep at most SkillScriptExecutionRequest.MaxOutputBytes UTF-8 bytes of each output stream and set SkillScriptExecutionResult.Truncated when you cut one.

Any other exception fails the tool call: Tracon records it on the span, the metric, and the tool call record, and the model sees a failed call. Its message is not shown to the model or written to the tool call record.

OperationCanceledException

The caller cancelled cancellationToken. Do not report a caller’s cancellation as a timeout.