-
-
Notifications
You must be signed in to change notification settings - Fork 6
Architecture
Navigation: Home | Supported Services | Probe YAML Reference | Match Rules | CLI Reference
Julius follows Go's standard project layout with a plugin-based architecture for extensible service detection.
cmd/julius/ CLI entrypoint (Cobra-based)
pkg/
runner/ Command implementations (probe, list, validate)
scanner/ HTTP client, response caching, model extraction
rules/ Match rule engine with plugin-style registration
probe/ Probe loader (embedded YAML + filesystem)
output/ Formatters (table, JSON, JSONL)
types/ Core data structures (Probe, Request, Result)
probes/ YAML probe definitions (one per service)
testdata/ Test fixtures
1. CLI parses arguments (target URLs, flags)
2. Probes loaded from embedded YAML (or filesystem via --probes-dir)
3. Targets normalized (scheme added, trailing slash removed)
4. For each target:
a. Probes sorted by port_hint relevance to target port
b. For each probe (concurrent, bounded by --concurrency):
i. Send HTTP requests defined in probe
ii. Check response cache (MD5 key) or execute request
iii. Apply match rules against response
iv. If matched: extract models (optional), build Augustus config (optional)
c. Results collected with mutex protection
5. Results sorted by specificity (highest first)
6. Output formatted (table/JSON/JSONL) and written
type Probe struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Category string `yaml:"category"`
PortHint int `yaml:"port_hint"`
Specificity int `yaml:"specificity"`
Require string `yaml:"require,omitempty"`
Requests []Request `yaml:"requests"`
Models *ModelsConfig `yaml:"models,omitempty"`
Augustus *AugustusConfig `yaml:"augustus,omitempty"`
}Key methods:
-
RequiresAll() bool-- Returns true ifrequire == "all" -
GetSpecificity() int-- Returns specificity, defaults to 50 if unset
type Request struct {
Type string `yaml:"type"`
Path string `yaml:"path"`
Method string `yaml:"method"`
Body string `yaml:"body,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
RawMatch []rules.RawRule `yaml:"match"`
}Key methods:
-
ApplyDefaults()-- Sets Type to"http"and Method to"GET"if empty -
GetRules() ([]rules.Rule, error)-- Converts raw YAML rules to typed Rule instances
type Rule interface {
Match(resp *http.Response, body []byte) bool
GetType() string
IsNegated() bool
}All rules embed BaseRule which provides Type and Not fields.
type Result struct {
Target string `json:"target"`
Service string `json:"service"`
MatchedRequest string `json:"matched_request"`
Category string `json:"category"`
Specificity int `json:"specificity"`
Models []string `json:"models,omitempty"`
GeneratorConfigs []GeneratorConfig `json:"generator_configs,omitempty"`
Error string `json:"error,omitempty"`
}Julius uses Go's errgroup for concurrent probe execution with a configurable concurrency limit:
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(scanner.concurrency) // default: 10
for _, probe := range probes {
g.Go(func() error {
// Execute probe against target
return nil
})
}
g.Wait()Results are collected into a shared slice protected by sync.Mutex.
Julius uses a two-level caching strategy to avoid redundant HTTP requests:
Level 1 -- MD5 Hash Deduplication:
Every request is hashed into a cache key combining the HTTP method, URL, sorted headers, and body:
func cacheKey(method, url string, headers http.Header, body []byte) string {
h := md5.New()
io.WriteString(h, method)
io.WriteString(h, url)
// ... sorted headers and body
return hex.EncodeToString(h.Sum(nil))
}Level 2 -- Singleflight Deduplication:
When multiple goroutines request the same cache key simultaneously, Go's singleflight.Group ensures only one HTTP request is made. All waiting goroutines receive the same result:
result, _, _ := s.inflight.Do(key, func() (interface{}, error) {
// Only one goroutine executes the actual HTTP request
return s.doRequest(req)
})Storage: Cached responses are stored in a sync.Map for thread-safe concurrent access.
Probe YAML files are embedded into the binary at compile time using Go's embed package. This makes Julius a single portable binary with no external file dependencies:
//go:embed probes/*.yaml
var embeddedProbes embed.FSThe --probes-dir flag overrides embedded probes with filesystem probes for development and custom probe sets.
Rules register themselves during package initialization using Go init() functions:
// pkg/rules/rule_body_contains.go
func init() {
Register("body.contains", NewBodyContainsRule)
}The registry maps type names to constructor functions. When a probe YAML file references a rule type, the registry looks up the constructor and creates a typed rule instance. This allows new rule types to be added without modifying central wiring code.
Before scanning, probes are sorted by relevance to the target's port. If a target specifies port 11434, the Ollama probe (with port_hint: 11434) is tried first. This optimization reduces the number of probes needed before finding a match.
Julius supports two match strategies per probe:
-
any(default):matchProbeAny()iterates through requests and returns on the first match. Efficient for probes with multiple detection paths. -
all:matchProbeAll()requires every request to match. Used when a single endpoint isn't distinctive enough (e.g., vLLM needs both/versionand/v1/models).
When a probe matches and defines a models section, Julius makes an additional HTTP request and applies a JQ expression to extract model names:
func extractModels(body []byte, jqExpr string) ([]string, error) {
var data interface{}
json.Unmarshal(body, &data)
query, _ := gojq.Parse(jqExpr)
// Execute and collect string results
}This uses the gojq library for JQ evaluation.
Julius supports three output formats via the pkg/output/ package:
| Format | Use Case |
|---|---|
table |
Human-readable terminal output via tablewriter |
json |
Structured JSON array for programmatic consumption |
jsonl |
One JSON object per line for streaming and piping |
Unit Tests:
- Rule matching logic in
pkg/rules/rules_test.go - Per-package test files
- Run with
go test ./...
Probe Validation:
-
julius validate ./probeschecks YAML structure and field constraints - Run during development and CI
Integration Testing:
- Manual testing against live LLM service instances
- Verbose mode (
-v) for debugging probe matches
Navigation: Home | Supported Services | Probe YAML Reference | Match Rules | CLI Reference