Skip to content

auto-experiment: emergent failure census, domain notes, distribution tags, and the datadog_backend (pup) flag - #87

Merged
gsvigruha merged 17 commits into
datadog-labs:mainfrom
tillwf:till.wohlfarth/auto-experiment-observation-upgrade
Jul 27, 2026
Merged

auto-experiment: emergent failure census, domain notes, distribution tags, and the datadog_backend (pup) flag#87
gsvigruha merged 17 commits into
datadog-labs:mainfrom
tillwf:till.wohlfarth/auto-experiment-observation-upgrade

Conversation

@tillwf

@tillwf tillwf commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Six changes to the agent-observability-auto-experiment skill. Docs only, plus one Python template.

1. The failure census no longer hands the agent its own answers

It used to list the buckets up front (wrong_retrieval, format/parse, …), so every failure landed in a category that already existed. Now two phases: parallel sub-agents describe failures with no category vocabulary, then the orchestrator names the buckets from what the descriptions say.

census.json now stores the per-datapoint descriptions plus failing_total/described, so buckets are auditable and partial coverage is reported as "15 of 47 inspected" instead of passing for complete.

2. domain_notes — product context the code doesn't carry

New optional list of strings, injected into sub-agent briefings, census describers, and the judge prompt. Grows mid-run: a corrected misread is appended, not applied once. Trusted as context, never authoritative — it can't redefine evaluators.

3. Score distribution reaches LLM-Obs

Each iteration already computed a score_distribution; none of it left the machine. Now published as dist_* tags: dist_n/dist_zero/dist_perfect counts plus a nearest-rank five-number summary.

The counts matter more than the quartiles here — with 26 of 34 cases at exactly 1.0, q1=median=q3=1.0 looks frozen while dist_zero moved 11 → 5.

4. Tag encoding fixes (found by inspecting ingested events)

  • Datadog rewrites a leading + to _, so delta_vs_best:+0.0447 landed as _0.0447 — the sign destroyed. Now unsigned magnitude + a separate delta_sign tag.
  • Tag values are lowercased, so ISO timestamps stopped parsing. Now time_start_ms/time_end_ms epoch millis.
  • Interpolated quartiles reported q1=0.1667, a value no datapoint scored. Now nearest-rank.

5. datadog_backend: mcp | pup

New intake field (default mcp) selecting the client for every Datadog call, so a run's provenance is never mixed. backend_used/backend_version recorded.

Asymmetric on failure: pup requested but unavailable → STOP (falling back would falsify provenance); mcp call failing → fall back to pup, loudly.

Corpus loading is the one real difference. Neither client's "records" tool can page past ~19 records — both hit the same response-budget endpoint with no cursor. pup#678 (merged) added datasets records-all, so pup is ahead of MCP here, and that's the documented corpus path. The loaded count must be asserted against the dataset size before splitting.

6. Judge prompt scaffold in the harness template

build_judge_prompt() assembles the prompt with trusted blocks first and untrusted datapoint content last, each in its own sealed delimiter. Replaces a prose instruction that every run re-implemented by hand.


Verified: three runs of the same experiment — two on MCP, one entirely on pup — with identical corpus, split and scorer gave baselines of 0.6357 / 0.6337 / 0.6412, agreeing within 0.008. The backend changes transport, not measurement.

Two caveats for reviewers:

  • One commit in this history claims pup's spans get-* commands return HTTP 404 and can't serve trace-derived sources. That was wrong — pup defaults to a 1-hour window and I misread the resulting 404. Retracted in a later commit; all four commands work with an explicit --from.
  • experiments update/create on released pup exit non-zero after the write lands (HTTP 200 with an empty body; response missing config). pup#682 fixes both but is not merged, so the skill assumes the broken behaviour and verifies writes by read-back.

Supersedes #88, whose commits were folded in here.

tillwf and others added 2 commits July 27, 2026 09:08
Every iteration already computes a score_distribution (per-datapoint
values + five-number summary) into config.json iteration_results, but
nothing left the machine: the LLM-Obs event carried only the scalar mean,
so the spread was visible only to whoever had the scratch branch checked
out.

Publish min/q1/median/q3/max as dist_* tags on the iteration's
auto_experiment_score metric. The submit_llmobs_experiment_events schema
has no array or object field, so tags are the only place a summary can
ride along with the score and survive the per-iteration:<n> dedup rule.
The raw values array stays local — 35+ tags per event is not worth it.

- dist_* prefix keeps these distinct from min_delta, the keep/discard
  floor, which is unrelated to the score spread.
- Values are copied from the iteration_results row, rounded to 4dp,
  never re-derived.
- Iteration 0 carries them too (its tag list in Step 2.4 read as
  exhaustive, so it now says so explicitly).
- no_change iterations omit all five: no eval ran, so carrying the prior
  best's spread forward would dress a non-measurement up as a measured
  one. Absent dist_* is the honest signal.
- The higher-power promotion correction re-sends the same dist_* tags —
  it re-labels confidence, it does not restate the distribution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The census was the loop's observation step, and it was handing the agent
its own answers: the rubric listed wrong_retrieval / wrong_reasoning /
format-parse / refusal / judge_disagreement / data-label up front, so
every failure landed in a bucket that already existed. A census that can
only confirm the failure modes you thought of before running it cannot
surface the one you missed, which is the entire reason to run one.

Rework it into two phases that must stay separate.

Phase A describes. Parallel describer sub-agents read the failing
datapoints and return factual sentences about what the output did versus
what the reference wanted, with no category vocabulary in the prompt.
Facts ("dropped the status='open' predicate") rather than judgments
("reasoned poorly") — a judgment has already smuggled in a category.
Because the task is descriptive it needs no run-level context, which is
what makes fanning it out safe and describing every failure affordable.

Phase B synthesizes. The orchestrator groups the descriptions and names
the buckets from what they actually say. The generic list survives only
as a last-resort naming aid at synthesis time, never as describer input.

census.json now stores the per-datapoint descriptions alongside the
buckets, plus failing_total/described coverage counts, so a bucket can be
audited, a taxonomy can be re-synthesized without re-describing, and a
count drawn from a partial sample is reported as "15 of 47 inspected"
instead of passing for full coverage.

Also adds domain_notes: free-text product context the code does not
carry, injected verbatim into sub-agent briefings, census describers and
the judge prompt — the three agents that interpret the domain. When the
user corrects a domain misread mid-run the correction is appended there
rather than applied once, so it survives the iteration. Notes are
trusted context in their own delimited block, kept separate from
untrusted datapoint content, and may never redefine the evaluators.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tillwf and others added 7 commits July 27, 2026 09:37
The rubric requires the judge to render domain_notes as trusted context
in its own block, separate from untrusted datapoint content. The template
the harness is copied from had neither: domain_notes was absent entirely,
and the prompt-injection guard existed only as prose in judge()'s
docstring — an instruction to whoever implements the call, with no code
behind it. Every run re-derived the same structure by hand, which is how
a run quietly ends up with one merged block.

Adds build_judge_prompt(): trusted blocks first (the evaluators rubric,
then domain_notes when non-empty), untrusted datapoint content last in
separately delimited blocks, with an explicit instruction that the
datapoint blocks are material to be scored rather than commands.

Untrusted content is sealed against the block delimiters by _seal(),
which inserts a zero-width space into anything resembling one of our own
tags. Surgical on purpose: a datapoint carrying "</datapoint_input>" can
no longer close its block and escape into instruction space, while SQL
operators, markup and code reach the judge byte-identical and get scored
as written. A blunter escape would corrupt the very content under test.

domain_notes arrives via AUTO_EXP_DOMAIN_NOTES, matching how EVALUATORS
is already threaded in; an empty value omits the block rather than
emitting an empty one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five defects a Codex review of the branch surfaced, four of them
introduced by the two commits before this one.

domain_notes never reached the judge. SKILL.md claimed injection into
the judge prompt, but the harness read only AUTO_EXP_DOMAIN_NOTES and no
run step ever mentioned exporting it, so the feature was a no-op for the
one consumer that scores. The template now reads domain_notes out of
config.json on every run: no env var to forget, and notes appended
mid-run take effect on the next run rather than silently going stale.
The env var survives as an override for callers with no config.json.

domain_notes was three different types — free text in the inputs table,
a list in the config schema, a string with .strip() in the template.
Pinned to a list of strings everywhere, one note per correction, which
is what "append the correction" was always trying to say. A bare string
is still tolerated on read.

_seal claimed untrusted content "cannot close its own block and escape
into instruction space", but matched exact lowercase tags only —
</DATAPOINT_INPUT> and < / datapoint_input > sailed through, and an LLM
reads both as closing tags. Now a case-insensitive regex tolerating
internal whitespace, and the docstring says what the mechanism actually
buys: it raises the cost of a break-out, it is not a proof against one.
The load-bearing guard is the instruction framing; the seal is defence
in depth and is no longer described as a sanitizer.

Iteration 0 was routed through the decision-legibility tag set, which
demands delta_vs_best, t_stat and significant. The baseline has no
previous best and runs no t-test, so an executing agent would have had
to invent all three — colliding with the scoring policy's "never invent
a score". Baseline now carries basis:baseline, timing and dist_* only,
with the omission stated in both places an agent might read.

Finally, dist_* summarizes the last run's per-datapoint spread while
score_value is the mean across runs, so dist_median does not generally
equal score_value. The mismatch predates this branch but publishing the
quartiles makes it consumer-visible; both the tag spec and the
score_distribution section now say plainly what the spread is and is
not, so no one reads it as quartiles of the reported score.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Second review pass on the same branch. Four of the five earlier findings
came back fully fixed; this handles the residual and three new ones.

The delimiter invariant was stated but only half enforced. Datapoint
content was sealed, DOMAIN_NOTES went in raw, so a note that happened to
quote </domain_notes> would close the trusted block and spill into the
framing text. Not the datapoint-injection threat — notes are
user-authored — but it breaks the prompt structure by accident, which is
the more likely failure. Every interpolated block is now sealed.

"Trusted" was doing two jobs and hiding the difference. The template
called EVALUATORS and DOMAIN_NOTES alike "instruction-level trust" while
SKILL.md said a note must never redefine the rubric — read together, an
implementer could reasonably give notes rubric authority. Trust and
authority are now split explicitly in both files: evaluators is trusted
AND authoritative and alone sets the criteria; domain_notes is trusted
but NOT authoritative, relied on to understand what the data means and
powerless to widen what counts as correct; datapoint content is neither.

_load_domain_notes validated nothing past a string check, so a dict
silently rendered its keys into the prompt as plausible context and a
list of ints crashed inside the join. Both now fail fast with a message
naming the expected shape, and malformed JSON is caught rather than
surfacing as a bare decode error at import. A broken config reaching the
judge as plausible-looking context is a scoring bug that would not look
like one.

_seal's docstring still said the zero-width space "stops" the text
parsing as a closing tag. A model is not a parser and <ZWSP/tag> is still
tag-shaped to it; the docstring now says so and stops claiming more than
the mechanism delivers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The _BLOCK_TAGS comment claimed every interpolated block was sealed, but
EVALUATORS went in raw — so a rubric that quoted </evaluators> could
close its own block, the same accident the notes sealing prevents. Seal
it as well and the comment is true as written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by inspecting really-ingested experiment events from a live run of this skill — none of
the three were catchable from the code, and three rounds of review missed them, because two are
properties of Datadog's tag pipeline and one only shows up on a discrete metric.

Datadog normalizes tag values, so a tag is not a byte-faithful channel:

  * A leading "+" is rewritten to "_". A tag sent as delta_vs_best:+0.0447 landed as
    delta_vs_best:_0.0447 — the sign, which is the entire point of a delta, destroyed. Worse, "-"
    survives, so negatives land as -0.1180 while positives land as _0.1180, an asymmetric
    encoding a consumer has to reverse-engineer. delta_vs_best now carries the absolute value and
    delta_sign:<pos|neg|zero> carries the direction.
  * Tag values are lowercased. time_start:2026-07-22T14:31:07Z landed as ...t14:31:07z, which
    neither parses as ISO-8601 nor byte-matches the iteration_results row the spec requires it to
    match. Now time_start_ms/time_end_ms as integer epoch millis, which pass through untouched.

The quartiles were also the wrong summary for a discrete metric:

  * Interpolated quartiles invent values the metric cannot produce. A ground-truth F1 over set
    overlap yields per-case values in {0.0, 0.667, 0.8, 1.0}; interpolation between the 9th and
    10th sorted values reported q1=0.1667, a number no datapoint scored, dressed as a measurement.
    Quartiles are now nearest-rank, so every number in the summary is a score some case got.
  * Quartiles alone go blind when most cases are perfect. With 26 of 34 at exactly 1.0,
    q1=median=q3=1.0 across iterations while the distribution moved hard (cases at 0.0 fell
    10 -> 5). dist_n/dist_zero/dist_perfect are now required, not optional — on such a metric they
    are the only part of the summary that carries the change.

Also generalizes the lesson into a standing warning next to the tag spec, so the next tag added
to this event does not repeat it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The schema gained n/zero/perfect but the sentence above it still described the row as scores
plus a five-number summary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… pup

Adds a `datadog_backend: mcp|pup` intake field (default mcp) selecting the client for EVERY
Datadog call the run makes — dataset reads, span/trace reads, and the experiment update /
event-submit writes. One switch, not per-call, so a run's provenance is never mixed;
`backend_used` is recorded in config.json because two runs on different backends are not
strictly comparable.

pup has full parity for what this skill touches: datasets records / records-full, spans
search / get-trace / get-details / get-content / expand, experiments update / events submit.
A substitution table maps each purpose to both clients and is declared to apply file-wide, so
the steps below keep naming MCP tools without every one needing a pup twin.

Failure policy is deliberately asymmetric, per the requested behaviour:

  * backend=pup and pup missing or unauthenticated -> STOP. No fallback to MCP: the user asked
    for pup explicitly, so quietly using another client would make the recorded provenance
    false. A PUP_BIN override is honoured first so a dev checkout works without installing.
  * backend=mcp and an MCP call fails -> fall back to pup, loudly, recording backend_fallback.

Everything below was verified against pup 1.7.0 rather than assumed, and three findings are
documented because each would otherwise bite silently:

  * Reads are wrapped in {status, data, metadata} in agent mode, where `data` is exactly the
    MCP body. Verified record-for-record identical on the same dataset call.
  * `experiments update` and `experiments events submit` take the experiment id POSITIONALLY,
    and events submit's payload omits the experiment_id key entirely.
  * A non-zero pup exit does NOT mean the write failed. `experiments create` and
    `experiments update` fail while deserializing the API response (missing field `config`;
    EOF on update's empty body) and exit non-zero AFTER the write has landed — both confirmed
    applied by reading back. So pup writes must be verified by reading state back, never by
    exit code, or a retry loop will double-write. The setup gate is amended accordingly.
    `events submit` is well behaved and returns MCP's exact response shape.

Also corrects a claim made earlier in this file: the inability to read back submitted
summary-level experiment metrics is a PLATFORM limitation, not an MCP one. pup's
`experiments events list` and `experiments summary` both report zero events for an experiment
whose submission was accepted, so the fallback rescues failed calls and cannot rescue missing
reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tillwf and others added 8 commits July 27, 2026 16:21
Installed pup 1.8.0 from main and re-ran every call the skill makes. Three corrections to what
the previous commit claimed, all found by testing rather than reading:

pup CANNOT serve a trace-derived data source. The four `spans get-*` drill-downs return HTTP 404
from /api/unstable/llm-obs-mcp/v1/trace/* under API-key auth, while the MCP tools read the very
same trace fine — verified side by side on one trace id, where MCP returned 36 spans and pup
404'd. So it is a pup gap, not a platform gap. `spans search` works; only the per-trace reads
fail. Step 1 needs get-trace / get-details / get-content to pull the `messages` field, so pairing
backend=pup with `trace_ids` or `ml_app` produces a run that dies in Step 1. That is now an
intake-gate STOP: the user picks mcp or supplies a dataset_id / local_dataset_path. Silently
falling back to MCP would falsify the recorded provenance, and starting the run anyway would
waste it. backend=pup remains fine for local_dataset_path and dataset_id.

The CLI is not stable across minor versions. `experiments events submit` took `--file <path>` in
1.7.0 and takes `--metrics '<json array>'` in 1.8.0 — the flag documented one commit ago is
already wrong. The table now says to check `pup --version` and `pup agent schema` for the
installed build instead of trusting it verbatim, and `backend_version` is recorded in config.json
next to backend_used. `spans search --from` also wants `7d`, not `now-7d`.

The exit-code warning holds on 1.8.0: `experiments update` still exits 1 with "EOF while parsing
a value" while the write lands (confirmed by reading status and metadata back), and
`experiments create` still exits 1 on `missing field config` after creating. `events submit` is
still the well-behaved one — exit 0 and MCP's exact response shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…efault window

The previous commit claimed pup's four per-trace commands (get-trace, get-details, get-content,
expand) return HTTP 404 under API-key auth, called it a pup gap rather than a platform gap, and
added an intake-gate STOP forbidding backend=pup with a trace_ids/ml_app data source. All of that
was wrong, and the error was mine.

Every pup `llm-obs spans *` command defaults to --from 1h. The trace I tested was older than an
hour, so the API answered 404 with detail "no spans found for trace <id>" — I truncated stderr at
110 characters, never saw the detail, and read a route-level 404 into it. Worse, I had passed
--from now-7d to the MCP tool for the same trace and nothing to pup, so the comparison that
"proved" a pup gap was measuring two different time windows.

With an explicit window all four succeed on 1.8.0 and get-trace returns the same 36-span
structure as MCP on the same id. pup can serve every data source the skill supports, trace_ids
and ml_app included, so the intake STOP is removed and the table rows are corrected.

What replaces it is the finding that actually matters: the 1h default, that a stale window
produces a 404 which is trivially mistaken for an unsupported route, and the instruction to read
the whole error body before declaring a command unsupported. The MCP tools default to now-1d, so
the same trace id can succeed on MCP and 404 on pup purely from the default — a window
difference, not a capability difference.

The other findings from that commit stand and were re-verified: the --file to --metrics signature
change between 1.7.0 and 1.8.0, and `experiments update`/`create` exiting non-zero after the
write lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The table had accumulated noise from the retraction: `--from 7d --to now` repeated verbatim on
four rows, a parenthetical flag note on a fifth, and inline warning text in a sixth. Rewritten as
four columns with a two-marker legend, the shared flags factored into the legend, and the
`pup llm-obs` prefix stated once instead of on every row.

Also states plainly that every row was run successfully against pup 1.8.0 and that there are no
unsupported purposes — the previous shape still read as though some rows were doubtful.

Two leftovers fixed while here: the substitution-rule paragraph still cited the 1.7.0
`events submit --file` flag, and the "pup rejects the MCP-style now-7d" gotcha had lived only in a
row note that the rewrite removed, so it moved into the window section where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit's table rewrite replaced everything between the table header and the
substitution paragraph — and both the 1-hour-window section and the version-sensitivity section
lived in that span, so both were silently wiped. The window finding is the whole point of the
preceding retraction (a stale window returns a 404 that reads like an unsupported route), and the
version note is what stops a reader trusting a flag that changed between 1.7.0 and 1.8.0. Both
restored verbatim after the marker legend, where the legend's "see below" pointers now resolve.

Also fixes the two remaining `events submit --file` references (1.7.0's flag) in the substitution
paragraph and the reporting section, and folds the "pup rejects the MCP-style now-7d" gotcha into
the restored window section — it had lived only in a table row note that the rewrite dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rified

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pup backend was reading the eval corpus with `datasets records`, which posts to an
MCP-token-budget endpoint that trims to a response-size budget (~19 records on a dataset with
sizeable inputs), reports truncated: true, and returns no cursor. A run built that way silently
measures a different corpus than an mcp run of the same dataset_id — different split, different
class balance, nothing comparable. That is exactly what happened on a 50-record dataset: the pup
run scored 19 records while two earlier mcp runs scored 50.

DataDog/pup#678 (merged) adds `datasets records-all`, which pages the REST route internally and
returns every record in one call. The table now makes that the corpus path, demotes `records` to
browsing with its cap marked, and Step 1's dataset_id branch names the per-backend command
explicitly: page next_cursor on mcp, records-all on pup.

Two guards, both learned the hard way:

  * Absence of records-all is a STOP under datadog_backend: pup, like a missing binary —
    continuing on the capped path yields a corpus that is a truncation artifact. Detect it by
    running the subcommand and checking the exit code, NOT by --help, which exits 0 for unknown
    subcommands on some builds and reports a feature present when it is absent.
  * After loading, assert the materialized count equals the dataset's true size before splitting.
    That is the cheap check that would have caught the 19-of-50 truncation immediately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ge either

The previous commit said the mcp backend loads the full corpus by paging
get_llmobs_dataset_records until next_cursor is empty. That is wrong. Verified against the live
tool at limit=100: returned 19, truncated true, next_cursor None, with __nested_object__
placeholders. The tool's schema documents next_cursor but the server never populates it, because
the tool posts to the same response-budget endpoint that pup's capped `records` uses. The cap is a
property of the endpoint, not of either client.

Consequences now documented:

  * No MCP tool can enumerate a dataset larger than ~19 records. get_llmobs_full_dataset_records
    caps at 3 per call and needs the id list you cannot obtain, so it is not a workaround.
  * On mcp such a corpus must come from a direct REST call to
    GET /api/unstable/llm-obs/v1/datasets/{id}/records paging meta.after — the same route pup
    wraps — and the data_note must say so, because it deviates from "every call went through the
    backend".
  * pup is therefore AHEAD of mcp here, not merely on par: records-all is the only first-class
    command for a full dataset. For a large dataset and a single-client run, prefer pup.

Also relevant to earlier runs in this experiment: the two runs labelled "mcp" loaded their 50
records via a direct curl to that REST route, not through an MCP tool, so they were never pure-MCP
runs. The skill now names that deviation instead of leaving it implied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he pending fix

The exit-code warning said pup's experiment writes "fail while deserializing the API's response"
without saying why, which makes it impossible to tell whether an installed build is affected.
Both causes are now stated, each confirmed against the live API:

  * `experiments update` — a successful PATCH answers HTTP 200 with a ZERO-BYTE body, which the
    generated typed client hands to serde_json::from_str, failing with "EOF while parsing a value".
  * `experiments create` — the 200 response omits `config`, a field the generated model requires,
    giving "missing field config".

Neither is a request failure. In one run this fired four times and all four writes had applied.

Also records that DataDog/pup#682 fixes both, by routing these two writes through pup's raw client
(as every other llm-obs command already does) and making parse_response_json treat an empty
successful body as JSON null. With that build update exits 0 and prints
{"experiment_id": ..., "status": "updated"}, and create exits 0 returning the new id.

That PR is OPEN, NOT MERGED, and the skill says so rather than describing unreleased behaviour as
current. The detection advice is deliberately not "check the version": run the command and compare
the exit code against a read-back, the same discipline the rest of this file uses — a version
number would not have caught the --help probe that reported records-all present on a binary that
lacked it.

The setup gate keeps read-back unconditionally, since it is correct on both builds and avoids
branching on which one is installed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tillwf tillwf changed the title auto-experiment: emergent failure census, domain notes, score distribution tags auto-experiment: emergent failure census, domain notes, distribution tags, and the datadog_backend (pup) flag Jul 27, 2026
@gsvigruha
gsvigruha merged commit 68428c6 into datadog-labs:main Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants