Skip to content

Latest commit

 

History

History
83 lines (61 loc) · 2.78 KB

File metadata and controls

83 lines (61 loc) · 2.78 KB

client-signals

Computes coarse, privacy-safe signals that help estimate whether a CLI process is being driven by a human or an AI agent: terminal attachment, a coarse parent-process bucket (node/python/shell/other), a cooperative agent marker (self-declared via FLY_INVOKED_BY, or passively detected from known agent-harness environment variables), and CI detection.

These are meant to be combined into an estimate with confidence, never treated as per-request certainty, and never used for gating, blocking, rate-limiting, or auth decisions.

Usage

sig := clientsignals.DetectOnce()

httpClient := &http.Client{
    Transport: sig.WrapTransport(http.DefaultTransport),
}

ClientSignalsTransport (returned by Signals.WrapTransport) attaches Fly-Client-* headers and a (interactive=...; parent=...; agent=...) User-Agent suffix to every request it forwards. Detection happens once, at the point you call Detect()/DetectOnce() — never per request.

This library isn't Fly.io-specific — the Fly header prefix is just the default. Use Signals.WrapTransportWithPrefix to use your own:

httpClient := &http.Client{
    Transport: sig.WrapTransportWithPrefix(http.DefaultTransport, "Acme"),
}
// -> Acme-Client-Interactive, Acme-Client-Parent, ...

For traffic that doesn't go through an http.RoundTripper at all — e.g. a WebSocket handshake built and sent by hand — use Signals.ApplyHeaders (or ApplyHeadersWithPrefix) to set the same headers directly on an http.Header:

header := http.Header{}
sig.ApplyHeaders(header) // or sig.ApplyHeadersWithPrefix(header, "Acme")
conn, _, err := dialer.DialContext(ctx, wsURL, header)

Inspecting signals

go run ./cmd

prints the currently-detected signals as JSON.

Privacy

  • Only approved, finite values are ever emitted (see Signals field docs).
  • Agent detection is presence/exact-value based; secret-shaped environment variables are never read or forwarded.
  • FLY_INVOKED_BY (and the cross-tool AGENT convention) are sanitized and length-capped before being emitted anywhere.

See docs/signals.md for the reasoning behind each field in Signals and its known reliability caveats (parent-process detection especially is noisier than it looks), and docs/markers.md for the full rationale behind the known agent markers table (markers.go) — where each entry came from, its confidence, and how to add new ones.

Cutting a Release

If you have write access to this repo, you can ship a release with:

scripts/bump_version.sh

Or a prerelease with:

scripts/bump_version.sh prerel

The release and notes will be created automatically via Github Actions. Follow along in: https://github.com/superfly/client-signals/actions/workflows/release.yml