Skip to content

Agent options

Every option class the agent binds, its config section, and its defaults. Most are not exposed as Helm values — set those through extraEnv.

Durations are .NET TimeSpan strings ("00:10:00", not "10m").

Llm

The chat model, the embedding model, and what a single investigation is allowed to spend.

KeyTypeDefault
Llm:Providerstringgemini
Llm:Modelstringgemini-3.7-flash
Llm:PlanningModelstring?null — falls back to Model
Llm:EmbeddingProviderstringgemini
Llm:EmbeddingModelstringgemini-embedding-001
Llm:EmbeddingDimensionsint768
Llm:ApiKeystring?null — from the secret
Llm:EmbeddingApiKeystring?null
Llm:EmbeddingEndpointstring?null
Llm:Endpointstring?null
Llm:ApiVersionstring?null
Llm:PlanningStructuredOutputJsonSchema | JsonObjectJsonSchema
Llm:Temperaturedouble0.2
Llm:MaxOutputTokensint?8192
Llm:Pricingmap of id → priceeight Gemini entries

An unpriced model is charged at zero

Llm:Pricing maps a model id to InputPerMillionUsd / OutputPerMillionUsd. A model with no entry costs nothing as far as the budget is concerned, which switches the cost budget off rather than approximating it. Ship a price with the model, always.

The seeded Gemini prices are promotional and double on 2027-01-01. Nothing in the code knows that date.

JsonSchema is not universally supported

DeepSeek answers 400 "This response_format type is unavailable now". Phase 1 is unaffected, so the agent diagnoses correctly and then proposes nothing — indistinguishable in a run summary from an agent that considered acting and declined. JsonObject moves the schema into the prompt. It is the weaker mode and stays off by default, and it is safe only because nothing downstream takes the model's word for it: every cited finding id is verified, and an action missing a namespace, kind or name is dropped before an executor sees it.

Llm:Investigation — one investigation's budget

KeyTypeDefault
MaxStepsint12
MaxToolCallsint20
MaxWallClockTimeSpan00:10:00
MaxInputTokenslong400000
MaxCostUsddecimal0.50
MaxConsecutiveNoToolTurnsint2

A reserved concluding step lets a run that hits MaxSteps still write a finding. That rescue cannot land when the exhausted ceiling is tokens, because the concluding call resends the conversation — a known limitation, recorded in the backlog.

Llm:Budget — the rolling global budget, counted in Postgres

KeyTypeDefault
MaxTokensPerHourlong2000000
MaxCostUsdPerHourdecimal3.00
MaxCostUsdPerDaydecimal20.00
MaxCostUsdPerIncidentdecimal0.50
WarnAtUtilizationdouble0.80
RunawayHourlyHitsBeforeLatchint3
RunawayWindowTimeSpan1.00:00:00

Llm:Retry

KeyTypeDefault
Attemptsint5
InitialDelayTimeSpan00:00:01
MaxDelayTimeSpan00:00:30
ExpBasedouble2.0
Jitterdouble1.0

Llm:Tools — what a tool call may return

KeyTypeDefault
TimeoutTimeSpan00:00:20
MaxResultBytesint8192
MaxRawBytesint1000000
MaxQueryRangeTimeSpan7.00:00:00
RequireTimeRangebooltrue
RedactedArgumentNameslistseeded

Redaction covers arguments, not results

RedactedArgumentNames redacts tool arguments in logs and traces. It does not redact tool results — raw describe_pod and get_pod_logs output carries env vars, hostnames and log contents verbatim into evidence blobs. That is why recorded cassettes are not committed to the repository.

Policy

The default-deny policy engine. A pure function over facts passed in by the caller, which is what makes it exhaustively unit-testable.

KeyTypeDefault
AllowedNamespacesset[]act nowhere
ProtectedNamespacessetseeded, never actionable
AutoEnabledActionTypesset[]
ProtectedLabelsmapseeded
RequiredNamespaceLabelstringhephaisto.dev/destructive-actions-allowed
AllowSingleReplicaRestartLabelstringhephaisto.dev/allow-single-replica-restart
MaxPodsPerActionint10
MaxWorkloadFractiondouble0.5
MaxActionsPerIncidentint3
MaxActionsPerWorkloadPerHourint2
MaxActionsPerHourint10
MaxActionsPerDayint20
WorkloadCooldownTimeSpan00:15:00
MinPodAgeBeforeActionTimeSpan00:02:00
ClusterUnhealthyCeilingdouble0.3
RollbackFreshRevisionWindowTimeSpan00:30:00
RollbackPreviousHealthyMinimumTimeSpan01:00:00
MaintenanceWindowslist[]

ClusterUnhealthyCeiling is the circuit breaker: above that fraction of unhealthy workloads the agent stops acting, on the reasoning that a cluster-wide problem is not one a pod restart fixes.

Kubernetes

KeyTypeDefault
Enabledbooltrue
ClusterNamestringdefault
KubeconfigPath / KubeconfigContextstring?null
ReadableNamespaces / DeniedNamespacesset{}
SignalQueueCapacityint2048
RelistIntervalTimeSpan00:10:00
WatchTimeoutTimeSpan00:05:00
ReconnectBaseDelay / ReconnectMaxDelayTimeSpan00:00:01 / 00:01:00
StormThresholdint50
StormWindowTimeSpan00:00:30
StormAggregateIntervalTimeSpan00:01:00
RestartStormThresholdint3
RestartStormWindowTimeSpan00:10:00
ReadinessFlapThresholdint4
ReadinessFlapWindowTimeSpan00:10:00
LogTailLinesint2000
LogLimitBytesint4194304
MaxRowsint200
RbacModeEnforce | WarnOnlyEnforce

Kubernetes:Enabled=false is a demo setting

It skips the RBAC self-check and the watchers, and leaves the executor that refuses everything in place. An agent that watches nothing while reporting itself healthy is the worst failure mode this project has, so it logs at warning level on every start. It exists so the console can boot on a laptop with no kubeconfig.

Persistence

KeyTypeDefault
ConnectionStringstring?null
ConnectionStringNamestringhephaisto
AppConnectionStringNamestringhephaisto_app
ApplyMigrationsOnStartupboolfalse
CommandTimeoutTimeSpan00:00:30
EvidenceBlobRetentionTimeSpan30.00:00:00
LlmUsageRetentionTimeSpan30.00:00:00
NotificationRetentionTimeSpan30.00:00:00
RetentionSweepIntervalTimeSpan01:00:00
RetentionBatchSizeint1000
MaxAdmissionRetriesint3
SearchPoolFactorint8
SearchMinPoolint100

Two connection strings, deliberately. The serving role holds INSERT but not UPDATE or DELETE on audit_events — Postgres cannot restrain a table's owner, so audit immutability depends on the agent not being one.

Investigation

KeyTypeDefault
MaxOuterTurnsint8
MinConfidenceForPlandouble0.5
PlanningCostUsddecimal0.10
PlanningTimeoutTimeSpan00:01:30
PlanningMaxInputTokenslong200000
EvidenceBlobRetentionTimeSpan30.00:00:00
StallNudge, FinalConclusionNudge, OpeningMessagestringsee prompts

Investigation:Environment — the environment card

What the agent is told about your cluster. The shipped defaults are this repository's own dev cluster and should be overridden.

KeyTypeDefault
ClusterNamestringstudio-rancher-desktop
InScopeNamespaceslist["hephaisto-chaos"]
ProtectedNamespaceslist["hephaisto","hephaisto-obs","kube-system"]
DatasourceUidsmap{}
WorkloadOwnersmap{}
Noteslist[]

Ingest

KeyTypeDefault
ClusterNamestringstudio-rancher-desktop
BurstWindowTimeSpan00:05:00
FlapThresholdint3
FlapWindowTimeSpan01:00:00
FlapCooldownTimeSpan04:00:00
CorrelationWindowTimeSpan00:10:00
SelfNamespacesset["hephaisto","hephaisto-obs"]

Notifications

KeyTypeDefault
BaseUrlstring?null — required once any route exists
Routeslist[]
Webhook:Url, Webhook:SigningSecretstring?null
Teams:WorkflowUrlstring?null
GrafanaUrlstring?null
MaxPerChannelPerHourint60
CorrelationCooldownTimeSpan00:15:00
MaxAttemptsint8
FirstRetryDelayTimeSpan00:00:30
MaxRetryDelayTimeSpan00:30:00
DispatchIntervalTimeSpan00:00:10
DispatchBatchSizeint20
SendTimeoutTimeSpan00:00:10

A route is Channel, Events, MinSeverity (default Info) and Namespaces. Routing is additive only — there is no deny rule. See Notifications.

Grafana

KeyTypeDefault
McpUrlstring?null — no PromQL/LogQL tools without it
ServiceAccountTokenstring?null
Urlstring?null
AnnotationTokenstring?null
AnnotationTimeoutTimeSpan00:00:05
ToolCacheDurationTimeSpan00:00:30
ConnectTimeoutTimeSpan00:00:10

Alertmanager

Write-only, and the only thing written is a silence.

KeyTypeDefault
Urlstring?null
MaxDurationTimeSpan02:00:00
DefaultDurationTimeSpan00:30:00
TimeoutTimeSpan00:00:10

KillSwitch

KeyTypeDefault
SwitchDirectorystring?null
ModeFileNamestringmode
EmergencyStopFileNamestringkillSwitch
ModeEnvironmentVariablestringHEPHAISTO_MODE

Demo

KeyTypeDefault
Seedboolfalse
TranscriptPathstringDemo/transcripts

Refuses outright if the database already holds an incident, so it cannot overwrite anything.

AGPL-3.0-only · Source