agentdrain

package
v0.86.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 13 Imported by: 0

README

agentdrain Package

Drain-style log template mining and anomaly scoring for structured agent pipeline events.

Overview

The agentdrain package implements an online log-template miner inspired by the Drain algorithm and adapts it to AgentEvent records emitted by agentic workflow stages. It converts structured events into deterministic token streams, normalizes variable values with regex-based masking, groups similar events into clusters, and returns a MatchResult that captures the matched template, extracted parameters, and similarity score.

The package is designed for two related tasks: training on known-good runs and anomaly analysis of new runs. Miner handles a single stream of events, while Coordinator manages one Miner per stage so templates from plan, tool_call, finish, and other stages do not interfere with each other. Persisted snapshots and embedded default weights allow models to be reused across runs instead of starting from an empty state every time.

Public API

Types
Type Kind Description
AgentEvent struct Structured event with a stage name and key/value fields to flatten, mask, and mine.
AnomalyDetector struct Scores MatchResult values against similarity and rarity thresholds.
AnomalyReport struct Summarizes anomaly flags, normalized score, and human-readable reason text.
Cluster struct Template cluster with ID, tokenized template, observation count, and optional stage.
Config struct Tuning parameters for masking, parse-tree depth, similarity threshold, and excluded fields.
Coordinator struct Routes events to one Miner per stage and persists combined weights.
MaskRule struct Regex substitution rule applied before tokenization.
Masker struct Compiled sequence of MaskRule values applied in order.
MatchResult struct Result of matching or creating a cluster, including template, params, and similarity.
Miner struct Concurrent single-stream Drain-style miner with training and analysis methods.
Snapshot struct Serializable miner state used by SaveJSON and LoadJSON.
SnapshotCluster struct Serializable form of a single Cluster within a Snapshot.
Functions
Function Signature Description
DefaultConfig func DefaultConfig() Config Returns the production default miner configuration and default masking rules.
FlattenEvent func FlattenEvent(evt AgentEvent, excludeFields []string) string Converts an event into deterministic key=value tokens with stage first and excluded fields omitted.
NewAnomalyDetector func NewAnomalyDetector(simThreshold float64, rareClusterThreshold int) (*AnomalyDetector, error) Validates thresholds and constructs an anomaly detector.
NewCoordinator func NewCoordinator(cfg Config, stages []string) (*Coordinator, error) Creates one stage-scoped miner for each supplied stage.
NewMasker func NewMasker(rules []MaskRule) (*Masker, error) Compiles masking regexes into a reusable masker.
NewMiner func NewMiner(cfg Config) (*Miner, error) Creates a miner with compiled mask rules, empty clusters, and a fresh parse tree.
StageSequence func StageSequence(events []AgentEvent) string Returns a space-separated sequence of event stages.
Tokenize func Tokenize(line string) []string Splits a masked line on whitespace boundaries.
Constants
Constant Type Value Description
AnomalyMaxScore untyped float64 2.0 Maximum raw anomaly score before normalization to [0,1].
AnomalyWeightLow untyped float64 0.7 Weight applied when a known template matches below the configured similarity threshold.
AnomalyWeightNew untyped float64 1.0 Weight applied when analysis creates a brand-new cluster.
AnomalyWeightRare untyped float64 0.3 Weight applied when the matched cluster size is at or below the rare-cluster threshold.

Usage Examples

cfg := agentdrain.DefaultConfig()
miner, err := agentdrain.NewMiner(cfg)
if err != nil {
	panic(err)
}

evt := agentdrain.AgentEvent{
	Stage:  "plan",
	Fields: map[string]string{"action": "start", "step": "1"},
}
result, err := miner.TrainEvent(evt)
if err != nil {
	panic(err)
}
fmt.Println(result.ClusterID)
cfg := agentdrain.DefaultConfig()
stages := []string{"plan", "tool_call", "finish"}
coord, err := agentdrain.NewCoordinator(cfg, stages)
if err != nil {
	panic(err)
}
if err := coord.LoadDefaultWeights(); err != nil {
	panic(err)
}

evt := agentdrain.AgentEvent{
	Stage:  "tool_call",
	Fields: map[string]string{"tool": "bash", "status": "ok"},
}
result, report, err := coord.AnalyzeEvent(evt)
if err != nil {
	panic(err)
}
fmt.Println(result.Template, report.AnomalyScore)
flat := agentdrain.FlattenEvent(
	agentdrain.AgentEvent{
		Stage: "tool_call",
		Fields: map[string]string{
			"tool":       "search",
			"query":      "foo",
			"session_id": "abc123",
			"latency_ms": "42",
		},
	},
	[]string{"session_id"},
)
// flat == "stage=tool_call latency_ms=42 query=foo tool=search"

Design Decisions

FlattenEvent MUST emit deterministic output: the stage= token is first when present, remaining keys are sorted alphabetically, and excluded fields are omitted. This keeps clustering stable across map iteration order and allows persisted weights to be reused reliably.

AnalyzeEvent performs inference before updating training state, then trains on the same event and scores the result against the matched or created cluster. New-template anomalies and low-similarity anomalies are intentionally mutually exclusive: a brand-new cluster is already anomalous without also being labeled low similarity.

Coordinator SHOULD be used when events belong to semantically different stages. Each stage receives its own miner so templates from unrelated phases do not merge into the same cluster space. LoadSnapshots MAY create new stage miners when snapshots contain stages that were not part of the original constructor input.

The package embeds default trained weights in data/default_weights.json. Callers MAY use LoadDefaultWeights to start from a pre-trained baseline instead of training from scratch.

Dependencies

Internal dependencies include pkg/logger for debug logging, pkg/setutil for exclusion-set membership, and pkg/sliceutil for slice and map helpers. External dependencies are limited to the Go standard library.

Thread Safety

Miner and Coordinator are safe for concurrent use. Miner protects mutable state with an internal sync.RWMutex; training, analysis, and load paths acquire write locks, while cluster snapshots and persistence reads acquire read locks. Coordinator protects its stage-to-miner map with its own sync.RWMutex and delegates per-stage concurrency to each Miner.


This specification is automatically maintained by the spec-extractor workflow.

Documentation

Index

Constants

View Source
const (
	AnomalyWeightNew  = 1.0
	AnomalyWeightLow  = 0.7
	AnomalyWeightRare = 0.3
	AnomalyMaxScore   = 2.0
)

Scoring weights used by Analyze. Exported so tests can reference them directly and stay in sync with production logic at compile time.

Variables

This section is empty.

Functions

func FlattenEvent

func FlattenEvent(evt AgentEvent, excludeFields []string) string

FlattenEvent converts an AgentEvent into a deterministic string suitable for template mining. Field keys are sorted alphabetically; fields listed in excludeFields are omitted. The result looks like:

stage=tool_call key1=val1 key2=val2

func StageSequence

func StageSequence(events []AgentEvent) string

StageSequence converts a slice of AgentEvents into a space-separated string of their stage names, e.g. "plan tool_call tool_result finish".

func Tokenize

func Tokenize(line string) []string

Tokenize splits a log line on whitespace and returns the individual tokens.

Types

type AgentEvent

type AgentEvent struct {
	// Stage identifies the pipeline stage (e.g., "plan", "tool_call", "finish").
	Stage string
	// Fields contains the key-value pairs parsed from the log line.
	Fields map[string]string
}

AgentEvent is a structured log event emitted by an agent pipeline stage.

type AnomalyDetector

type AnomalyDetector struct {
	// contains filtered or unexported fields
}

AnomalyDetector evaluates match results and produces AnomalyReports.

func NewAnomalyDetector

func NewAnomalyDetector(simThreshold float64, rareClusterThreshold int) (*AnomalyDetector, error)

NewAnomalyDetector creates an AnomalyDetector with the given thresholds.

func (*AnomalyDetector) Analyze

func (d *AnomalyDetector) Analyze(result *MatchResult, isNew bool, cluster *Cluster) *AnomalyReport

Analyze produces an AnomalyReport for a match result.

  • isNew indicates the line created a brand-new cluster.
  • cluster is the cluster that was matched or created.

type AnomalyReport

type AnomalyReport struct {
	// IsNewTemplate is true when the log line produced a brand-new log cluster.
	IsNewTemplate bool
	// LowSimilarity is true when the best match score was below the configured threshold.
	LowSimilarity bool
	// RareCluster is true when the matched cluster has been seen fewer times than the rare threshold.
	RareCluster bool
	// AnomalyScore is a weighted composite score in the range [0, 1].
	AnomalyScore float64
	// Reason is a human-readable description of all anomalies that were detected.
	Reason string
}

AnomalyReport describes anomalies detected for a log line.

type Cluster

type Cluster struct {
	// ID is the unique cluster identifier.
	ID int
	// Template is the tokenized log template with wildcards at variable positions.
	Template []string
	// Size is the number of log lines that have been assigned to this cluster.
	Size int
	// Stage identifies which agent stage generated this cluster.
	Stage string
}

Cluster represents a group of log lines that share the same template.

type Config

type Config struct {
	// Depth controls how many levels of the parse tree are used.
	Depth int
	// SimThreshold is the minimum similarity score (0–1) required to match an existing cluster.
	SimThreshold float64
	// MaxChildren limits the number of children per internal tree node.
	MaxChildren int
	// ParamToken is the wildcard string inserted where tokens differ across log lines.
	ParamToken string
	// RareClusterThreshold marks clusters with size ≤ this value as rare.
	RareClusterThreshold int
	// MaskRules are applied before tokenization to normalize variable parts of log lines.
	MaskRules []MaskRule
	// ExcludeFields lists AgentEvent field keys that are omitted when flattening events.
	ExcludeFields []string
}

Config holds tuning parameters for the Drain log template miner.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config pre-loaded with sensible production defaults.

type Coordinator

type Coordinator struct {
	// contains filtered or unexported fields
}

Coordinator manages one Miner per agent pipeline stage.

func NewCoordinator

func NewCoordinator(cfg Config, stages []string) (*Coordinator, error)

NewCoordinator creates a Coordinator with one Miner for each provided stage name.

func (*Coordinator) AllClusters

func (c *Coordinator) AllClusters() map[string][]Cluster

AllClusters returns a map from stage name to the list of clusters in that miner.

func (*Coordinator) AnalyzeEvent

func (c *Coordinator) AnalyzeEvent(evt AgentEvent) (*MatchResult, *AnomalyReport, error)

AnalyzeEvent routes the event to the correct stage miner and returns both the match result and an anomaly report.

func (*Coordinator) LoadDefaultWeights

func (c *Coordinator) LoadDefaultWeights() error

LoadDefaultWeights restores all stage miners from the embedded default weights file (pkg/agentdrain/data/default_weights.json). When the file is empty or contains only an empty JSON object the call is a no-op and returns nil.

Update the default weights by running:

gh aw logs --train --output <dir>

and copying the resulting drain3_weights.json to pkg/agentdrain/data/default_weights.json, then rebuilding the binary.

func (*Coordinator) LoadSnapshots

func (c *Coordinator) LoadSnapshots(snapshots map[string][]byte) error

LoadSnapshots restores each stage miner from the provided JSON bytes map. Stages that are not present in snapshots retain their current state.

func (*Coordinator) LoadWeightsJSON

func (c *Coordinator) LoadWeightsJSON(data []byte) error

LoadWeightsJSON restores all stage miners from a combined JSON blob produced by SaveWeightsJSON.

func (*Coordinator) SaveSnapshots

func (c *Coordinator) SaveSnapshots() (map[string][]byte, error)

SaveSnapshots serializes each stage miner's state and returns a map from stage name to JSON bytes.

func (*Coordinator) SaveWeightsJSON

func (c *Coordinator) SaveWeightsJSON() ([]byte, error)

SaveWeightsJSON serializes all stage snapshots into a single combined JSON blob. The result can be written to pkg/agentdrain/data/default_weights.json and committed to embed it as the default starting weights for future runs.

func (*Coordinator) TrainEvent

func (c *Coordinator) TrainEvent(evt AgentEvent) (*MatchResult, error)

TrainEvent routes the event to the miner responsible for evt.Stage. Returns an error when the stage has no associated miner.

type MaskRule

type MaskRule struct {
	// Name is a human-readable identifier for the rule.
	Name string
	// Pattern is the regular expression to match.
	Pattern string
	// Replacement is the string substituted for each match.
	Replacement string
}

MaskRule describes a regex substitution applied to log lines before processing.

type Masker

type Masker struct {
	// contains filtered or unexported fields
}

Masker applies a sequence of regex substitution rules to normalize log lines.

func NewMasker

func NewMasker(rules []MaskRule) (*Masker, error)

NewMasker compiles the given MaskRules into a Masker ready for use. Returns an error if any pattern fails to compile.

func (*Masker) Mask

func (m *Masker) Mask(line string) string

Mask applies all mask rules in order and returns the transformed line.

type MatchResult

type MatchResult struct {
	// ClusterID is the ID of the matched or newly created cluster.
	ClusterID int
	// Template is the space-joined template string.
	Template string
	// Params holds the actual token values at wildcard positions.
	Params []string
	// Similarity is the fraction of non-wildcard positions that matched exactly.
	Similarity float64
	// Stage is the agent stage associated with the matched cluster.
	Stage string
}

MatchResult is returned after processing a log line through the miner.

type Miner

type Miner struct {
	// contains filtered or unexported fields
}

Miner is a concurrent Drain-style log template miner. Use NewMiner to create an instance.

func NewMiner

func NewMiner(cfg Config) (*Miner, error)

NewMiner creates a Miner from the given Config.

func (*Miner) AnalyzeEvent

func (m *Miner) AnalyzeEvent(evt AgentEvent) (*MatchResult, *AnomalyReport, error)

AnalyzeEvent performs inference on the event, builds an AnomalyReport, and then calls TrainEvent to update the miner. Returns the match result and report.

func (*Miner) Clusters

func (m *Miner) Clusters() []Cluster

Clusters returns a snapshot of all known clusters.

func (*Miner) LoadJSON

func (m *Miner) LoadJSON(data []byte) error

LoadJSON restores miner state from JSON bytes produced by SaveJSON. The existing state is replaced; the parse tree is rebuilt from the snapshot.

func (*Miner) SaveJSON

func (m *Miner) SaveJSON() ([]byte, error)

SaveJSON serializes the miner's current state to JSON bytes.

func (*Miner) Train

func (m *Miner) Train(line string) (*MatchResult, error)

Train processes a raw log line, updates the miner state, and returns the match result. It is safe to call from multiple goroutines.

func (*Miner) TrainEvent

func (m *Miner) TrainEvent(evt AgentEvent) (*MatchResult, error)

TrainEvent flattens the AgentEvent and calls Train.

type Snapshot

type Snapshot struct {
	Config   Config            `json:"config"`
	Clusters []SnapshotCluster `json:"clusters"`
	NextID   int               `json:"next_id"`
}

Snapshot is the serializable representation of a Miner's state.

type SnapshotCluster

type SnapshotCluster struct {
	ID       int      `json:"id"`
	Template []string `json:"template"`
	Size     int      `json:"size"`
	Stage    string   `json:"stage"`
}

SnapshotCluster is the serializable form of a single Cluster.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL