>
The skill file stays in the publisher's repository.
Paste the copied command in a terminal. It installs this SKILL.md into that agent.
npx skills add metalbear-co/mirrord --skill mirrord-propagation-discovery -a cursor -y
>
mirrord dump to find an identifier mirrord can
filter traffic on: a header, a baggage or tracestate member, a tenant or user id, a trace id
(W3C, B3, X-Ray, Google, Jaeger), a JWT claim, a cookie, a path segment, or a JSON body field
that is present on every hop of a request chain, unique to one
developer's traffic, and settable by that developer. Runs mirror-only dumps on each service,
sends a tagged probe request at the edge or observes real traffic, scores the candidates, and
produces the matching http_filter (regex, jq on headers, path, method, body, or combinations)
plus a per-hop report of where each identifier is dropped. Installs the mirrord CLI through a
package manager when it is missing. Use when the user asks what to filter on, whether the
cluster already propagates a request id or trace context, how to isolate their own requests on
a shared staging cluster, how to filter on a JWT or cookie with mirrord, or wants to inspect
live traffic with mirrord dump. Changes no application code: propagation gaps go to the
mirrord-header-propagation skill.
metadata:
author: MetalBear
version: "1.0"mirrord routes a request to a developer's local process when the request matches an HTTP filter. On a shared cluster that filter has to pick out one developer's requests at every service the request passes through, so the value it matches on must already travel with the request across all hops. This skill finds that value in a live environment instead of assuming one: it dumps the traffic arriving at each service with
mirrord dump, checks which identifiers survive from the edge to the last hop, and turns the best one into amirrord.jsonfilter. The sister skillmirrord-header-propagationchanges code to makebaggagepropagate; this one changes nothing and tells you what already works.
IMPORTANT: Follow these rules for all operations in this skill.
mirrord dump copies traffic; the target keeps answering every request. Don't run a steal session or an unfiltered session against a shared target while scanning. The only cluster-side effect is the short-lived mirrord agent pod (or ephemeral container) each dump creates.mirrord.json, a ticket, chat, or a commit.kubectl get/describe, mirrord ls, mirrord dump. No kubectl apply/patch/delete, no Helm, no config changes to ingress or mesh.POST at the edge creates real records and real queue messages in that environment. Prefer a read-only endpoint that still fans out; when only a write exercises the flow, say so and use an obviously synthetic payload (a probe email, a test product).An identifier is usable as a mirrord filter when it is, on every hop in scope:
curl -H, the mirrord browser extension, a test client) or owns the identity it names (their own user or tenant).references/identifier-catalog.md ranks the usual candidates and lists what mirrord dump cannot see (queue consumers, TLS terminated in the pod, non-HTTP protocols, responses). mirrord filters match requests only: headers, path and query, method, and JSON bodies. A value that exists only in a response cannot be used.
Do the steps in order. Keep a running hop inventory (Step 2) and candidate table (Step 4); they become the final report.
kubectl config current-context && kubectl get pods -n <namespace> | head
mirrord --version
If mirrord is missing, say which command you will run and install it through the approved path for the platform:
brew install metalbear-co/mirrord/mirrord # macOS / Linux
choco install --pre mirrord # Windows
Anything else (no package manager, locked-down machines): point to the CLI install docs and let the user install a release binary. Version notes: port auto-detection in mirrord dump needs 3.191.0+, jq header filters (header_filter_jq / { "query" }) need 3.194.0+ on the CLI and the agent image, and the Operator (if installed) must be current enough for either. A local jq is optional but useful for testing jq programs before they go into the config.
Confirm with the user which namespace, which cluster, and which request flow they want to isolate (for example "a POST /v1/orders from the gateway through orders, inventory and billing"). If they want "all my traffic", you are looking for an identity-bound identifier; if they want "the requests I send from my terminal", an injectable one.
Build the inventory from the cluster, not from memory:
kubectl get ingress,gateway,httproute -A 2>/dev/null # where traffic enters
kubectl get svc,deploy,statefulset,rollout -n <namespace> # the services and their workloads
kubectl get svc <name> -n <namespace> -o jsonpath='{.spec.ports[*].targetPort}' # the port to dump
mirrord ls -n <namespace> # what mirrord can target
For each hop record: workload (deployment/<name>), namespace, HTTP port(s), and what calls it. Ask the user to fill the call order when the cluster doesn't make it obvious (a trace in their APM, a service map, or the code). Note hops a dump can't observe: queue consumers, pods that terminate TLS themselves, non-HTTP services. Those go into the report as "not observable with dump", with how to inspect them (see the catalog).
Keep the set small and representative: edge, every service on the flow, and the deepest one. Four to six hops is typical.
Load references/reading-dumps.md for the exact commands, the output format, and the probe script. In short:
mirrord dump on every hop at once, each writing to its own log in a temp directory; wait for Listening for traffic in every log; send one request at the edge carrying a value you'll recognise under all the carriers at once (baggage: mirrord-session=<probe>, tracestate: mirrord-session=<probe>, x-request-id: <probe>, a minted traceparent, and the developer's own bearer token when the flow is authenticated); wait a few seconds; stop the dumps. Pick endpoints that fan out through every hop, and send several probes if one path doesn't cover them all.Run the dumps with MIRRORD_PROGRESS_MODE=dumb so the Listening for traffic line survives redirection; the default spinner prints nothing to a file. Use -p <port> when auto-detection fails (couldn't detect ports on target). Use --context to pin the cluster. A deployment/ dump attaches to one pod. Check kubectl get deploy <name> -o jsonpath='{.spec.replicas}'; when it is more than one, dump every pod of that hop (-t pod/<name>, one log per pod named <hop>--<pod>.log, the stats script merges them), or the probe may land on a replica you are not watching. A hop you could not fully observe is unknown, never "dropped". If a dump shows TLS connections with hex payloads, that pod terminates TLS itself; record it as not observable and move on.
Run the helper over all logs (stdlib Python, prints names and counts, never full secrets):
python3 scripts/dump_stats.py --probe "$PROBE" "$DUMP_DIR"/*.log
It reports, per hop and per identifier (headers, baggage/tracestate members, traceparent trace-id, cookie names, JWT claim names from authorization or a cookie): presence, distinct values versus requests (U = unique per request, S = one stable value), which identifiers carried the probe value, and which have values shared by every hop. Fill the candidate table from it and check each candidate against the three properties. Anything in the dump you don't recognise, look up in references/carriers.md: it lists the vendor trace headers (Datadog, B3, X-Ray, Google, Jaeger, SkyWalking, Sentry, Instana, New Relic, Honeycomb), the older baggage names (correlation-context, uberctx-*, ot-baggage-*, baggage-*), the identity headers auth proxies inject (oauth2-proxy, IAP, ALB OIDC, Cloudflare Access, Azure Easy Auth), and the routing, lane and feature-flag headers teams add for canaries and sandboxes (Signadot sd-routing-key, Telepresence x-telepresence-id, x-env). A routing header the team already forwards everywhere is as good as baggage. Then apply the catalog's ranking:
baggage, tracestate, or a correlation-id header the ingress preserves.sub, email, tenant_id) or as an identity header set after auth (x-tenant-id, x-user-id).traceparent, or the B3 / X-Ray / Google / Jaeger equivalent the cluster's tracer uses.Three things that look usable but aren't:
x-request-id at hop 1 differs from what you sent: the value is unique and propagated, but not controllable. Only usable if the developer can read it back (from a response header) and that's acceptable for their workflow; otherwise drop it.traceparent, and probably with baggage and tracestate too, since they ride the same propagator.authorization disappears at an internal hop. The claim is usable up to that hop only.If no candidate covers every hop, pick the one covering the most hops from the edge inward and record where it stops. That boundary is the propagation work.
Load references/filter-recipes.md and use the simplest filter that matches the candidate:
header_filter regex (headers match case-insensitively as name: value).^baggage:\s*(.*,\s*)?mirrord-session={{ key }}\s*([,;].*)?$). A bare .*mirrord-session={{ key }}.* also matches {{ key }}2, which is another developer.{{ key }} goes into the regex verbatim, so keys are letters, digits, -, _. Emails and other punctuated values are compared in jq (. == "{{ key }}").header_filter_jq (top level) or { "query": ... } inside all_of/any_of. The program receives each header as a lowercase-named name: value string and must output true. Guard it with select(startswith("<name>: ")) and try ... catch false.all_of when the identity is per tenant rather than per developer (add the route or method). Don't add a ^user-agent: (?!kube-probe) entry: health checks carry no identifier so the filter already skips them, and that entry rejects internal requests sent without a user-agent.{{ key }} so the config can be committed, and set ports to the service's HTTP ports.Test jq programs locally first: jq -n --arg h 'authorization: Bearer <token>' '$h | <program>' must print true for the developer's token and nothing or false for others.
Prove the filter on the deepest hop it should reach, in mirror mode so nothing is taken from the target. Run a request recorder as the local process, not the real service: mirror mode copies the request, so the deployed service still handles it, and a real local handler would repeat its writes and publishes against the same staging dependencies. scripts/request_recorder.py binds the service's port, logs method, path and the identifier header, and answers 200:
mirrord exec -f .mirrord/mirrord.json --key <value> --target deployment/<deepest-hop> -n <namespace> -- python3 scripts/request_recorder.py --port <service port> --header baggage
Run it with MIRRORD_PROGRESS_MODE=dumb and wait for the Ready! line before sending traffic. Send one request at the edge with the identifier set to <value> and one with a different value. The local process must receive the first and not the second. If it receives neither, check ports, that the identifier really reaches this hop (the dump from Step 3), and that the agent version supports the filter type. Then the developer can switch "mode" to "steal" for real work. Point to the mirrord-config skill for the rest of the config and to the browser extension for browser-driven flows.
Every hop where the chosen identifier is missing is a propagation gap, not a filter problem. For each one record the service and the carrier that would fix it. The standard fix is forwarding baggage end to end with the mirrord-header-propagation skill, which detects existing OpenTelemetry/Datadog propagation and enables it before adding code. Queue hops get their filters from the mirrord-kafka skill (message_filter / jq_filter) once the same identifier is on the messages.
Delete the dump directory when done.
Always end with this report, in the conversation and saved as .mirrord/propagation-discovery.md next to the generated config (or wherever the user asks). references/report-template.md has a filled example; copy its shape. Header names and value shapes only, never captured values.
# Propagation discovery — <namespace>, <flow>
Filter on: <identifier> e.g. the mirrord-session member of the baggage header
Why: <reaches k of N services with the developer's value; who/what forwards it; what the developer does to set it>
## Services covered (k of N)
| Service | Port | How the identifier arrives | Dump evidence |
## Services not covered (N-k of N)
| Service | Port | What arrives instead | Why it is dropped | Fix (who) |
## Not observable with mirrord dump
| Hop | Reason | How to check | queue consumers, TLS in the pod, non-HTTP
## Candidates considered
| Identifier | <one column per service> | Unique | Controllable | Verdict |
## mirrord.json
<complete config: target, namespace, mode mirror, ports, http_filter with {{ key }}, probes excluded>
<the exec command and the curl header the developer sends>
## Next steps
<one line per gap: what to change, where, which skill>
Verified: <how, or "not verified: <why>">
Dumps: <window, probes, date; logs deleted>
Rules for the report:
mirrord.json is complete and runnable: target with namespace, mode: "mirror", ports, and the filter with {{ key }} and member boundaries. Write it to .mirrord/mirrord.json unless the user names another path, and say which target.path/ports to change for the other covered services.baggage plus the mirrord-header-propagation skill, and "Services not covered" lists each hop with what it would take.traceparent; the span-id changes every hop. Match the trace-id only.mirrord.json. Match a claim or a {{ key }}.steal while discovering, and don't verify without a filter on a shared target.x-request-id as controllable until a probe shows the ingress preserved your value.select(startswith("<name>: ")) first.x-forwarded-client-cert) or a client address (x-forwarded-for) for a developer identity; they name the workload or stop at the edge.mirrord-header-propagation.