Documentation
¶
Overview ¶
Package intent implements governance R3 — the intent-alignment policy check.
The MUST from the governance framework: every action is evaluated against a policy that considers both the action itself AND its alignment with the stated agent intent. Forge's pre-R3 policy evaluation (guardrails, egress, admission) covered the "action itself" half — this package fills the second half with a per-tool- call cosine-similarity check between the user's stated intent and the tool call the LLM is about to make.
Wire shape:
On tasks/send entry, the A2A handler calls Engine.RegisterIntent with the task ID + the first user message text. The engine computes and caches the intent embedding once per task.
On every BeforeToolExec hook, the runner calls Engine.Score with the task ID + tool description + tool-input JSON. The engine embeds the concatenated action text (cached per hash), computes cosine similarity against the intent embedding, compares against the configured thresholds, and returns a Result.
The hook consumer emits the Result as an `intent_alignment` audit event and, on DecisionDeny, aborts the tool call.
Fail-closed posture: when Config.Enabled is true, an unavailable embedder (config error, transient network) causes Score to return DecisionDeny with reason="embedder unavailable". Governance- critical: silent bypass is not a valid failure mode.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Enabled turns the check on. Default false. When false, the hook
// short-circuits — no embedder calls, no audit emit.
Enabled bool
// Threshold is the SOFT similarity floor. Scores strictly below
// threshold but at-or-above HardThreshold produce DecisionWarn.
// Sensible default 0.5 — operators SHOULD start warn-only for a
// sprint to gather the score distribution before turning deny on.
Threshold float64
// HardThreshold is the HARD floor. Scores strictly below produce
// DecisionDeny. Set equal to Threshold to disable the warn tier;
// set to a negative value (e.g. -1) to run warn-only during the
// initial rollout. Sensible default 0.3.
HardThreshold float64
// CacheSize is the max number of action-side embeddings to
// remember in the LRU. Zero disables the action cache (still
// caches the per-task intent). 1024 is a reasonable default.
CacheSize int
// IntentTTL controls how long a task's intent embedding is
// kept in memory before it's evicted. Zero → 1 hour default.
IntentTTL time.Duration
}
Config carries the operator-tunable knobs for the alignment check. Populated from forge.yaml security.intent_alignment. See docs/security/intent-alignment.md.
type Decision ¶
type Decision int
Decision is the alignment engine's contribution to the policy evaluation. Maps 1:1 to runtime.PolicyDecision but the intent package doesn't import runtime (avoid a cycle: intent → runtime via memory → intent later); the runner adapts.
const ( // DecisionAllow — score at or above the soft threshold. DecisionAllow Decision = iota // DecisionWarn — score below soft but at-or-above hard threshold. // The runner emits the audit event and lets the tool call proceed; // operators tail the audit stream to tune. DecisionWarn // DecisionDeny — score below hard threshold, or the engine // couldn't produce a score (fail-closed on embedder error). // The runner aborts the tool call. DecisionDeny )
type DriftConfig ¶
type DriftConfig struct {
// Enabled turns the analyzer on. When false, RecordAndCheck is
// a no-op and no drift signals fire.
Enabled bool
// Window is the number of most-recent scores considered by the
// rolling-mean test. Must be ≥ 2 — a window of 1 can't
// distinguish "trending down" from "just low."
Window int
// DriftThreshold is the mean-score floor. When the rolling
// window mean falls strictly below this value, drift enters
// the "mean_below_threshold" state.
DriftThreshold float64
// MonotoneN, when non-zero, additionally trips drift when the
// last N scores are strictly decreasing (even if the mean is
// still above DriftThreshold). Catches slow-boil patterns
// where each individual step is small but cumulative drift is
// large. Zero disables the monotone check.
MonotoneN int
}
DriftConfig configures the R7 (#214) rolling-window drift signal. Where the R3 alignment check is a per-call policy gate, drift is longitudinal telemetry — it watches the sequence of alignment scores accumulating for a task and flags trends that suggest the agent is progressively wandering from the stated intent.
func (DriftConfig) Validate ¶
func (c DriftConfig) Validate() error
Validate returns an error when DriftConfig would produce nonsensical decisions. Called at Engine construction so runners fail startup rather than at first RecordAndCheck.
type DriftSignal ¶
type DriftSignal struct {
// Severity is the human-readable classification. One of
// "mean_below_threshold", "monotone_decrease", "both", or
// "recovered" (transition OUT of drift).
Severity string
// Mean is the rolling-window mean of the last Window scores at
// the moment the signal fired. Included on both entry and
// recovery transitions for the audit event.
Mean float64
// Window is the number of scores in the mean.
Window int
// Transition is "entered" for on-entry signals or "recovered"
// for the exit signal. Makes SIEM queries "when did I go into
// drift?" trivial.
Transition string
}
DriftSignal describes a drift-state transition. Populated on engine.Score's return value when a state change happens on that call; nil otherwise. The runner emits an `intent_drift` audit event iff Signal != nil.
State-transition emission (rather than "emit every call while in drift") keeps the audit stream from flooding — one event when the task first crosses into drift, one when it recovers.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the per-runtime intent-alignment coordinator. Safe for concurrent use — RegisterIntent + Score fire from independent goroutines (one per A2A request handler + one per hook fire).
func New ¶
New constructs an Engine with just the R3 alignment check. The R7 drift analyzer stays off. embedder may be nil when cfg.Enabled is false; enabling without an embedder returns an error.
func NewWithDrift ¶
NewWithDrift constructs an Engine with the R3 alignment check AND the R7 rolling-window drift analyzer. drift.Enabled requires cfg.Enabled to be true (drift is derived from alignment scores).
func (*Engine) Enabled ¶
Enabled reports whether the engine is armed. Runners check this to short-circuit hook wiring on unconfigured deployments.
func (*Engine) Forget ¶
Forget removes the intent embedding AND any drift-analyzer state for the given task. Called from the A2A handler at session_end so long-running processes don't retain per-task state indefinitely.
func (*Engine) RegisterIntent ¶
RegisterIntent records the stated intent for a task. Called from the A2A handler on tasks/send entry. Idempotent per task ID — the FIRST call wins; subsequent calls for the same task are no-ops so mid-conversation user messages don't overwrite the original intent.
func (*Engine) Score ¶
Score computes the alignment between the previously-registered stated intent for taskID and the current action (tool call).
actionText is the concatenation of the tool's description and the LLM-supplied args JSON — chosen over tool name because tool names are arbitrary handles (`fn_42`) that don't reflect semantics, while descriptions carry what the tool DOES and args carry the specific values.
Returns DecisionDeny when:
- The engine is enabled but no intent was registered for taskID (guards against a hook firing before the A2A handler set up the task).
- The embedder call fails or returns zero vectors.
- The score is strictly below cfg.HardThreshold.
When cfg.Enabled == false, returns DecisionAllow with Score=NaN so the caller can distinguish "not checked" from "checked and passed at 1.0."
type Result ¶
type Result struct {
// Score is the cosine similarity ∈ [-1, 1]. NaN when the engine
// couldn't produce a value (embedder error, missing intent).
Score float64
// Decision is the alignment engine's verdict.
Decision Decision
// Reason is a short human-readable classification — surfaced on
// the audit event and, on Deny, returned as the tool-exec error.
Reason string
// Drift is non-nil ONLY on the tool call that transitions the
// task into or out of drift (R7 / #214). The hook consumer
// emits an `intent_drift` audit event when set. State-transition
// semantics keep the audit stream from flooding across long
// stretches of drift.
Drift *DriftSignal
}
Result carries the outcome of one Score call. Surfaced as fields on the `intent_alignment` audit event.