Unified signal ingestion abstraction for operational intelligence.
omnisignal provides a unified interface for ingesting operational signals from various external systems (alerting, ticketing, security, monitoring). It follows the same architectural pattern as omnillm:
• Provider interface defines the contract for signal sources • Registry allows dynamic provider registration • Thick providers use official SDKs (PagerDuty, Jira, New Relic) • Thin providers use native HTTP for sources without Go SDKs
go get github.com/plexusone/omnisignalimport (
"github.com/plexusone/omnisignal"
_ "github.com/plexusone/omnisignal/provider/pagerduty" // Register PagerDuty
)
func main() {
provider, err := omnisignal.New("pagerduty", omnisignal.Config{
APIKey: os.Getenv("PAGERDUTY_API_KEY"),
})
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// Fetch incidents from the last 24 hours
signals, err := provider.Fetch(ctx, omnisignal.FetchOptions{
Since: time.Now().Add(-24 * time.Hour),
})
if err != nil {
log.Fatal(err)
}
for _, sig := range signals {
fmt.Printf("Signal: %s - %s (%s)\n", sig.ID, sig.Summary, sig.Severity)
}
}| Provider | Type | Import Path | SDK |
|---|---|---|---|
| PagerDuty | Alerting | omnisignal/provider/pagerduty |
go-pagerduty |
| Jira | Ticketing | omnisignal/provider/jira |
go-jira |
| Analyst | Market Intelligence | omnisignal/provider/analyst |
none (thin) |
| Competitive | Market Intelligence | omnisignal/provider/competitive |
none (thin) |
| Provider | Type | Import Path | SDK |
|---|---|---|---|
| New Relic | Monitoring | omni-newrelic/omnisignal |
newrelic-client-go |
| Aha | Product Feedback | grokify/aha-studio/omnisignal |
aha-go |
| Provider | Type | Priority |
|---|---|---|
| Zendesk | Ticketing | P1 |
| Datadog | Monitoring | P1 |
| Opsgenie | Alerting | P1 |
| ServiceNow | ITSM | P2 |
| Snyk | Security | P2 |
type Provider interface {
// Name returns the provider identifier
Name() string
// Fetch retrieves signals matching the given options
Fetch(ctx context.Context, opts FetchOptions) ([]signal.Signal, error)
// Subscribe opens a real-time stream of signals
Subscribe(ctx context.Context, opts SubscribeOptions) (<-chan signal.Signal, error)
// Capabilities returns what this provider supports
Capabilities() Capabilities
// Close releases any resources
Close() error
}type Config struct {
APIKey string // Primary authentication credential
APISecret string // Secondary credential (if required)
BaseURL string // Override default API endpoint
Timeout time.Duration // Request timeout
RetryMax int // Max retry attempts
Options map[string]any // Provider-specific options
}PagerDuty:
omnisignal.Config{
APIKey: "your-api-key",
}Jira:
omnisignal.Config{
BaseURL: "https://company.atlassian.net",
APIKey: "user@example.com", // Username
APISecret: "api-token", // API token
Options: map[string]any{
"projects": []string{"INFRA", "SUPPORT"},
},
}New Relic (via omni-newrelic):
omnisignal.Config{
APIKey: "NRAK-xxx",
Options: map[string]any{
"account_id": 12345,
"region": "US",
},
}Analyst (Gartner, Forrester, IDC — see docs):
omnisignal.Config{
Options: map[string]any{
"source": "gartner", // or "forrester", "idc", "custom"
},
}Competitive (win/loss and gaps from CRM — see docs):
omnisignal.Config{
Options: map[string]any{
"source": "salesforce", // or "hubspot", "clari", "gong", "custom"
"competitor_mappings": map[string]string{"Okta Inc": "competitor:okta"},
omnisignal.OptCustomerMappings: map[string]string{"Acme Corp": "customer:acme-001"},
omnisignal.OptMarketMappings: map[string]string{"IAM": "market:identity-governance"},
},
}Config provides typed accessors for reading provider Options:
func (c Config) GetOption(key string, defaultVal any) any
func (c Config) GetStringOption(key, defaultVal string) string
func (c Config) GetStringMap(key string) map[string]stringGetStringMap accepts both map[string]string and map[string]any (with string values), which makes it safe to use with config loaded from JSON/YAML as well as Go literals.
Well-known option keys carry cross-repo reference mappings from source system values (e.g., organization names) to MarketSpec typed refs (e.g., customer:acme-001):
| Constant | Key | Maps |
|---|---|---|
OptCustomerMappings |
customer_mappings |
Organization/account names → customer refs |
OptCapabilityMappings |
capability_mappings |
Components/labels → capability refs |
OptMarketMappings |
market_mappings |
Categories → market refs |
type FetchOptions struct {
Since time.Time // Filter signals after this time
Until time.Time // Filter signals before this time
Limit int // Maximum signals to return
Statuses []string // Filter by status
Severities []string // Filter by severity
Filters map[string]string // Provider-specific filters
}All providers normalize their data to signal-spec types:
type Signal struct {
ID string // Unique signal identifier
Type Type // support_ticket, alert, security_finding, etc.
Status Status // new, processing, mapped, archived
Source SourceSystem // Origin system details
Domain Domain // Category/subcategory
Severity Severity // critical, high, medium, low, info
Summary string // Brief description
Description string // Full content
Entities []Entity // Referenced system components
ObservedAt time.Time // When signal was observed
ReceivedAt time.Time // When signal was received
Tags []Tag // User-defined labels
Metadata map[string]any // Source-specific data
}package myprovider
import "github.com/plexusone/omnisignal"
func init() {
omnisignal.Register("myprovider", NewProvider, omnisignal.PriorityThick)
}
func NewProvider(cfg omnisignal.Config) (omnisignal.Provider, error) {
// Initialize your provider
return &Provider{config: cfg}, nil
}
type Provider struct {
config omnisignal.Config
}
func (p *Provider) Name() string { return "myprovider" }
func (p *Provider) Fetch(ctx context.Context, opts omnisignal.FetchOptions) ([]signal.Signal, error) {
// Fetch from your source and normalize to signal.Signal
}
func (p *Provider) Subscribe(ctx context.Context, opts omnisignal.SubscribeOptions) (<-chan signal.Signal, error) {
return nil, omnisignal.ErrNotSupported
}
func (p *Provider) Capabilities() omnisignal.Capabilities {
return omnisignal.Capabilities{
SupportsBatchFetch: true,
}
}
func (p *Provider) Close() error { return nil }Some sources (e.g., Aha Ideas, analyst findings, competitive deals) already represent a single aggregated data point rather than a raw event stream. Mark these signals as curated so the consolidation pipeline skips clustering and maps them directly to root causes:
sig.Metadata[omnisignal.MetaCurated] = true
// Or use the helper to check:
if omnisignal.IsCurated(sig.Metadata) {
// Skip clustering, map directly to a canonical signal
}See Metadata Conventions for the full raw vs. curated model.
The metrics package computes derived scores from signal sets via a pluggable formula registry:
import "github.com/plexusone/omnisignal/metrics"
// Built-in formulas: frustration, momentum, reach, urgency
result, err := metrics.Compute(ctx, "frustration", signals, metrics.Options{
Weights: map[string]float64{"support_ticket": 1.5},
})
// Or run every registered formula at once
results, errs := metrics.ComputeAll(ctx, signals, metrics.Options{})| Formula | Description |
|---|---|
frustration |
Weighted signal count multiplied by the age (in days) of the oldest signal |
momentum |
Count of signals observed within a trailing window (default 30 days) |
reach |
Count of distinct customer references across all signals |
urgency |
Sum of severity-weighted signal counts |
Weight overrides and window size can be loaded from JSON config and merged:
cfg, err := metrics.LoadConfig("metrics.json")
merged := defaultCfg.Merge(cfg)
result, err := metrics.Compute(ctx, "urgency", signals, merged.ToOptions())Custom formulas can be added via metrics.Register(metrics.NewFormula(name, description, computeFn)).
The consolidate package groups related raw signals into canonical root causes through a 5-stage pipeline: embed → cluster → summarize → review → attach.
import "github.com/plexusone/omnisignal/consolidate"
pipeline := consolidate.NewPipeline(
consolidate.WithEmbedder(consolidate.NewOmniLLMEmbedder(omnillmClient, consolidate.EmbedderConfig{
Model: "text-embedding-3-small",
})),
consolidate.WithSummarizer(consolidate.NewLLMSummarizer(omnillmClient, consolidate.SummarizerConfig{
Model: "gpt-4o-mini",
})),
consolidate.WithReviewer(consolidate.NewMemoryReviewer(consolidate.ReviewConfig{
AutoApproveThreshold: 5,
})),
consolidate.WithSimilarityThreshold(0.85),
)
result, err := pipeline.Process(ctx, signals)
// result.RootCauses, result.Clusters, result.Attached, result.Stats- Embed —
Embeddergenerates vector embeddings via OmniLLM (OmniLLMEmbedder) - Cluster — signals are grouped by cosine similarity (
Clusterer,IncrementalClusterer) - Summarize — an LLM generates a root cause title, description, and symptom patterns per cluster (
LLMSummarizer) - Review — optional human review queue with approve/reject and auto-approve threshold (
MemoryReviewer) - Attach — new signals are linked to existing root causes incrementally via
Pipeline.Attach
Curated signals skip embedding and clustering and map directly to root causes, preserving their existing aggregation.
- signal-spec - Canonical signal data model
- signal - Operational intelligence platform
- omnillm - Unified LLM abstraction
MIT