{"id":51791134,"url":"https://github.com/kelexine/agentdetect","last_synced_at":"2026-07-20T22:34:56.817Z","repository":{"id":371033879,"uuid":"1294909131","full_name":"kelexine/agentdetect","owner":"kelexine","description":"Detect which AI agent harness is running this process (env-var only) + optional OpenTelemetry propagation.","archived":false,"fork":false,"pushed_at":"2026-07-12T21:15:27.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-20T22:34:51.151Z","etag":null,"topics":["agents","ai","claude-code","env","github","harness"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/agentdetect","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kelexine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-09T08:36:08.000Z","updated_at":"2026-07-12T21:15:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kelexine/agentdetect","commit_stats":null,"previous_names":["kelexine/agentdetect"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kelexine/agentdetect","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelexine%2Fagentdetect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelexine%2Fagentdetect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelexine%2Fagentdetect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelexine%2Fagentdetect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kelexine","download_url":"https://codeload.github.com/kelexine/agentdetect/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelexine%2Fagentdetect/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35702321,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"ssl_error","status_checked_at":"2026-07-20T02:08:09.736Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai","claude-code","env","github","harness"],"created_at":"2026-07-20T22:34:52.033Z","updated_at":"2026-07-20T22:34:56.811Z","avatar_url":"https://github.com/kelexine.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# agentdetect\n\nA pure Rust library that answers one question: **is this process running\nunder an AI agent harness, and if so, which one?**\n\nWhen an agent harness (Claude Code, Cursor, Codex, etc.) spawns a shell to\nrun a command, it sets an env var identifying itself — `CLAUDE_CODE=1`,\n`CURSOR_TRACE_ID=…`, `CODEX_SANDBOX=1`, etc. agentdetect reads those\nvariables and tells you which harness is active.\n\n## The core use case: bit-flip output switching\n\nThe primary purpose is the **bit-flip pattern** — switch output format\nbased on whether the caller is a human or an agent:\n\n```\n┌─────────────────────────────┐\n│  Is an agent harness active? │\n└──────────────┬──────────────┘\n         ┌─────┴─────┐\n        NO           YES\n         │             │\n         ▼             ▼\n  ┌────────────┐  ┌────────────────────┐\n  │  Human in  │  │  Agent harness     │\n  │  terminal  │  │  detected          │\n  │            │  │                    │\n  │  → Pretty  │  │  → Machine-readable│\n  │    output  │  │    output (TSV)    │\n  └────────────┘  └────────────────────┘\n```\n\nThis is exactly how [loc-rs](https://github.com/kelexine/loc-rs) uses it:\nwhen a human runs `loc`, they get a coloured summary table; when Claude\nCode runs `loc`, it gets TSV with `# Agent-Detected: claude-code` so the\nagent can parse the output efficiently.\n\n```rust\nif agentdetect::is_agent() {\n    // Agent harness is active — emit machine-readable output.\n    let d = agentdetect::detect().unwrap();\n    println!(\"# Agent-Detected: {}\", d.agent.id);\n    println!(\"metric\\tvalue\");\n    // ... TSV data ...\n} else {\n    // Human terminal — emit pretty output.\n    // ... coloured table ...\n}\n```\n\n## Env vars are the ONLY detection surface\n\nagentdetect reads the process environment.  A harness spawning a\nshell sets an env var on the process tree it spawns — that is the *entire*\nsignal. I case of Claude Code that will be `CLAUDE_CODE,=1`\n\n## Optional: propagation (CLI → API)\n\nWhen you're building BOTH a CLI AND the API it talks to, and you want the\nAPI to know which agent is calling, enable the `http` feature and use the\n[`propagation`](./agentdetect/src/propagation.rs) module:\n\n1. **CLI side**: detect the agent via env vars, then write the identity\n   onto your outgoing request via `agentdetect::propagation::inject` —\n   which sets the `x-agentdetect-agent` / `x-agentdetect-confidence`\n   headers **we define and control**.\n2. **API side**: your middleware (e.g. `agentdetect-tower`'s\n   `AgentDetectLayer`) reads those same headers via\n   `agentdetect::propagation::read` and reconstructs a `Detection`.\n\nThis is NOT third-party `User-Agent` sniffing — the header is ours, written\nonly by agentdetect-using code, read only by agentdetect-using code.\n\n## Optional: OpenTelemetry (feature-gated)\n\nThe `otel` feature adds span enrichment and metric emission for the\n**secondary use case**: you're building a CLI that speaks to a public API,\nand you want to track which agents are calling, how often, success rate,\n% of traffic, top-N.  This is a feature on top of the core detection — the\ncore library has zero non-std dependencies.\n\n\u003e Built on the detection patterns from\n\u003e [`loc-rs`](https://github.com/kelexine/loc-rs) — same compile-time\n\u003e `const` registry, same priority-ordered scan, same `AI_AGENT` / `AGENT`\n\u003e fallback — but as a reusable library with the bit-flip primitive,\n\u003e optional propagation, and optional OpenTelemetry emission.\n\n## Workspace members\n\n| Crate | What it is | Use case |\n|-------|------------|----------|\n| [`agentdetect`](./agentdetect) | Core detection library (env-var only) | Drop into any Rust code that needs to know if an agent is running |\n| [`agentdetect-tower`](./agentdetect-tower) | Tower middleware | Read the propagation header on the API side + emit OTel |\n\n## Detection model\n\nA [`Detection`] carries not just *which* agent was found but:\n\n- **`Confidence`** (`High` / `Medium` / `Low`) — dedicated harness marker\n  beats standard `AI_AGENT` / `AGENT` channel beats unrecognised value.\n- **`SourceKind`** (`EnvVar` / `Propagated`) — direct detection vs\n  reconstructed from a propagated header on the API side.\n- **Evidence trail** (`raw_signals`) — every matching signal is retained,\n  so you can audit \"why was this classified as Claude Code?\".\n\n## Supported harnesses (23)\n\n`antigravity`, `augment-cli`, `cline`, `cowork`, `claude-code`, `codex`,\n`crush`, `gemini-cli`, `github-copilot`, `goose`, `hermes-agent`,\n`kilo-code`, `kiro`, `openclaw`, `opencode`, `pi`, `replit`, `trae`,\n`warp`, `zed`, `cursor-cli`, `cursor`, `devin`.\n\nEach carries:\n\n- Static identity (`id`, `pretty_label`, `family`, `repo_url`, `docs_url`,\n  `description`).\n- One or more env-var checks (presence / exact / prefix patterns).\n- A `HarnessFamily` for vendor-level grouping (Anthropic, OpenAI, Google,\n  GitHub, ByteDance, Cognition, Charm, Cursor, Block, Replit, AWS, Nous\n  Research, Warp, Zed, Augment, Community, Other).\n\nTo list every harness at runtime:\n\n```rust\nfor \u0026key in agentdetect::AgentHarnessKey::ALL {\n    let info = key.info();\n    println!(\"{:\u003c15} | {:\u003c20} | {}\", key.id(), info.pretty_label, info.family);\n}\n```\n\n## Feature flags\n\n### agentdetect\n\n| Feature | Default? | Pulls in | Purpose |\n|---------|----------|----------|---------|\n| `http`  | no       | `http` v1 | Propagation helpers (`inject` / `read` for the `x-agentdetect-*` headers) |\n| `otel`  | no       | `opentelemetry` v0.27 | OTel attribute / span / metric emission |\n\nBoth are optional — the pure detection core has zero non-std dependencies.\n\n### agentdetect-tower\n\n| Feature | Default? | Purpose |\n|---------|----------|---------|\n| `otel`  | yes      | OpenTelemetry span enrichment + metric emission |\n\n(`http` is always required — the middleware reads the propagation header.)\n\n## Examples\n\n### agentdetect\n\n| Example | What it shows |\n|---------|---------------|\n| `bit_flip`          | **the canonical use case** — switch between pretty and TSV output based on agent detection |\n| `basic`             | env-var detection from the current process (detailed output) |\n| `cli_to_api`        | full round-trip: CLI detects → injects header → API reads → OTel |\n| `otel_demo`         | span attrs + metric labels + emission for an env-var detection |\n| `axum_middleware`   | API-side middleware reading the propagation header |\n\n```bash\n# The canonical bit-flip demo:\ncargo run --example bit_flip -p agentdetect                # human → pretty\nCLAUDE_CODE=1 cargo run --example bit_flip -p agentdetect  # agent → TSV\nANTIGRAVITY_AGENT=1 cargo run --example bit_flip -p agentdetect\n\n# Full CLI → API round-trip:\ncargo run --example cli_to_api -p agentdetect --features \"http otel\"\nCLAUDE_CODE=1 cargo run --example cli_to_api -p agentdetect --features \"http otel\"\n\ncargo run --example basic -p agentdetect\ncargo run --example otel_demo -p agentdetect --features otel\ncargo run --example axum_middleware -p agentdetect --features \"http otel\"\n```\n\n### agentdetect-tower\n\n| Example | What it shows |\n|---------|---------------|\n| `tower_demo`        | Tower layer reading the propagation header + emitting OTel |\n\n```bash\ncargo run --example tower_demo -p agentdetect-tower --all-features\n```\n\n## Project layout\n\n```\n.\n├── Cargo.toml              # Workspace root\n├── .github/workflows/\n│   └── ci.yml              # fmt + clippy + feature-combo test matrix + MSRV + publish dry-run\n├── agentdetect/            # Core library (env-var detection only)\n│   ├── src/\n│   │   ├── lib.rs          # Public API + re-exports\n│   │   ├── pattern.rs      # EnvPattern (const fn matcher)\n│   │   ├── registry.rs     # Static registry of 23 harnesses (env vars only)\n│   │   ├── detection.rs    # Detection, AgentInfo, Confidence, SourceKind, RawSignal\n│   │   ├── detect.rs       # is_agent / detect_from_env (the only detection surface)\n│   │   ├── propagation.rs  # inject / read for the x-agentdetect-* headers (feature-gated)\n│   │   └── otel.rs         # OpenTelemetry attributes, span enrichment, metrics\n│   ├── examples/           # 5 examples\n│   └── tests/              # Integration tests\n└── agentdetect-tower/      # Tower middleware (reads propagation header)\n    ├── src/lib.rs          # AgentDetectLayer + AgentDetectMiddleware + extractor\n    └── examples/tower_demo.rs   # Tower service demo\n```\n\n## CI\n\nGitHub Actions workflow (`.github/workflows/ci.yml`) runs on every push /\nPR to `main`:\n\n- **fmt + clippy + docs** — `cargo fmt --check`, `cargo clippy --all-features -- -D warnings`, `cargo doc -- -D warnings`.\n- **test matrix** — Rust stable + beta, with all feature combinations.\n- **MSRV** — builds + clippy pass on Rust 1.85.\n- **package** — `cargo publish --dry-run` validates packaging.\n\n## Why no behavioral detection?\n\nagentdetect intentionally does NOT classify \"this looks like an agent\"\nbased on behavioral patterns (request rate, payload shape, model string,\netc.).  Every detection is grounded in an explicit signal set by the\nharness itself (env var).  This makes detection explainable and impossible\nto silently drift — if you can't see the signal, you don't classify the\nrequest.\n\n## License\n\nMIT.\n\n[OpenTelemetry]: https://opentelemetry.io\n[`Detection`]: https://docs.rs/agentdetect/latest/agentdetect/detection/struct.Detection.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelexine%2Fagentdetect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkelexine%2Fagentdetect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelexine%2Fagentdetect/lists"}