AGENTS.md, {admiral}, and the AI-Assisted Clinical Programmer

Teaching coding agents to speak CDISC

Jeff Dickinson

2026-10-20

How we got here

Date What happened
2025-11-20 First Copilot-authored PR in {admiral}: #2899, AAGE/AAGEU in the ADSL template
2025-12-08 #2920 opened: “Add Copilot Instructions” (Copilot’s own attempt, #2921, closed unmerged)
2026-02-18 Suggestion in #2920: make it agent-agnostic with AGENT.md files instead
2026-03-10 admiraldev #547 opened: “Formalize our approach to AI”
2026-03-12 #2983 merged: AGENTS.md, tests/testthat/AGENTS.md, and the sync workflow
2026-03-31 pharmaverse blog post
2026-05-01 AI Assistance paragraph merged into CONTRIBUTING (#3072); #547 closed

Destination: March 10, 2026

The models

  • Claude Opus 4.6 & Sonnet 4.6: about a month old
  • GPT-5.4: five days old
  • GPT-5.3-Codex: the new coding model
  • Gemini 3.1 Pro: three weeks old

The headlines

  • 1M-token context window? Still in beta.
  • Agents can run unattended for… 20 minutes!
  • rOpenSci has just published its AI policy
  • admiraldev #547 opens: “Formalize our approach to AI”

Roads? Where we’re going, we don’t need roads. We need an AGENTS.md.

Who’s talking to your code base?

  • GitHub Copilot
  • Cursor
  • Claude
  • Gemini CLI / Jules
  • …and whatever ships next quarter

The problem

AI coding agents are increasingly part of how we write clinical R code.

But out of the box, an agent knows nothing about:

  • CDISC ADaM conventions
  • pharmaverse package boundaries
  • the regulatory weight behind a single derivation

It doesn’t know that ANL01FL takes "Y" or NA — never "N".

It doesn’t know {pharmaversesdtm} is canonical test data, or that {xportr} is waiting downstream for a submission-ready XPT file.

AGENTS.md

A plain markdown file, committed to your repo, that gives AI coding agents the context they need to work correctly in your project.

README.md tells a new developer what the project is. AGENTS.md tells an AI assistant how to work in it correctly.

One file. Supported across Copilot, Cursor, Aider, Gemini CLI, Jules, and more. agents.md

Why this matters for the pharmaverse

ADaM derivations encode decades of regulatory expectation that never appears in the R syntax itself.

  • DTYPE = "LLOQ" imputation rules
  • Flag variable conventions (ANL01FL, SAFFL, …)
  • Where {admiral} ends and {pharmaversesdtm} / {metacore} / {xportr} begin

An agent writing {admiral} code without this context is a new programmer who only knows the function they’re editing — not the system it belongs to.

A first step, deliberately early

AGENTS.md shipped while the {admiral} team was still working out its formal AI strategy — what tools to encourage, what guardrails to set.

That conversation played out in admiraldev issue #547, drawing on rOpenSci, pyOpenSci, scikit-learn, and Linux kernel AI-contribution policies.

The approach at the time: gain real experience before locking in formal guidance. AGENTS.md was the first tangible infrastructure to come out of that thinking — and it shaped the policy that followed.

How {admiral} builds AGENTS.md

Centralized, not hand-maintained

Rather than every package hand-writing its own file, generation is centralized in {admiralci} and delivered to every package that opts in.

Today

  • Programming strategy & unit testing guidance — from {admiraldev} vignettes

Next (admiralci#246)

  • Package-specific context — a YAML file unique to each repo (therapeutic area, related packages, relevant CDISC IGs)
  • Ecosystem context — how this package fits the pharmaverse pipeline
  • ADaM fundamentals — variable conventions, controlled terminology patterns

The reusable workflow

Every package calls the same workflow from {admiralci}:

name: Create the AGENTS.md

on:
  workflow_dispatch:
  schedule:
    - cron: '0 8 15 * *'

jobs:
  create-agents-md:
    permissions:
      contents: write
      pull-requests: write
    uses: pharmaverse/admiralci/.github/workflows/create-agents-md.yml@main
    with:
      package_name: admiral
      target_branch: main
      admiralci_ref: main

What comes out the other end

{admiral}’s AGENTS.md is 1,382 lines, auto-generated from the {admiraldev} Programming Strategy, Git Usage, and R CMD Check vignettes.

# admiral Development Guidelines for AI Assistants

## AI Verification

> If you have read this file, include the comment
> `# admiral guidelines loaded` on the first line of every **new** R file
> you create in this repository.

A small, clever trick: agent compliance becomes observable at code review time. If the comment’s missing, the guidelines probably weren’t read.

And it stays current: a “Testing Errors and Warnings” section added to {admiraldev} on 30 Sep reached {admiral} and {admiralpeds} with the next sync, with no hand edits.

Root file, and a testthat-scoped file

  • AGENTS.md — Programming Strategy, Git Usage, R CMD Check issues
  • tests/testthat/AGENTS.md — Unit Test Strategy, scoped to where it’s actually needed

AGENTS.md at the repo root trips a R CMD check NOTE — so it goes in .Rbuildignore:

^AGENTS\.md$

Only the root file needs this; tests/testthat/AGENTS.md doesn’t.

Does it actually work?

Before and after AGENTS.md

  • 2025-11-20 — PR #2899, author: Copilot. One of the first Copilot PRs in {admiral}: adds AAGE/AAGEU to the ADSL template and vignette, straight from an issue. Merged 2025-11-30, months before AGENTS.md existed.
  • 2026-03-07 — PR #2996, maxthecat2024: fixes warning formatting in derive_param_computed(). Thorough, well-structured, fully-checked box. Was this AI? We don’t know — and it didn’t matter, because the code was good.
  • 2026-03-17 — PR #3010, author: Copilot again, now with AGENTS.md in the repo.

PR #3010: what Copilot got right

Enhancing examples for derive_vars_merged_summary(), Copilot correctly picked up {admiral}’s @examplesx structured example pattern — a convention that came directly from AGENTS.md.

This is the whole thesis of the talk, demonstrated in one PR: domain convention, transmitted through a markdown file, followed by an agent that had never seen this codebase before.

PR #3010: what it got wrong

Copilot edited man/derive_vars_merged_summary.Rd by hand instead of regenerating it with devtools::document() — and said so, candidly, in its own PR description:

“Manually updated to match roxygen2::roxygenize() output.”

When pressed by a reviewer:

“R is not available in my sandbox environment, so I can’t execute devtools::document() directly.”

A human ran it and pushed the correct .Rd in commit c855860.

The nuance that matters

AGENTS.md can teach an agent the correct workflow.

It cannot make an agent run a command its sandbox doesn’t have.

Environment support is a separate, human-owned responsibility — no amount of instruction text substitutes for R actually being installed.

This distinction should shape whatever formal AI strategy {admiral} lands on.

The feedback loop

flowchart LR
    A["AGENTS.md ships"] --> B["Agents contribute PRs"]
    B --> C["Gaps surface<br/>(e.g. no R in sandbox)"]
    C --> D["Community learns,<br/>policy lands (#547)"]
    D --> A
    C --> E["Guidance fixed upstream<br/>in {admiraldev}"]
    E --> A

Not speculation about what AI might do to our repos — evidence about what it’s already doing.

Who reviews a generated file?

Reviewing {admiralpeds}’ AGENTS.md PR, a reviewer left careful wording fixes on tests/testthat/AGENTS.md — backticks, punctuation, a typo.

But that file is generated. Any edit there is overwritten by next month’s sync.

  • Some fixes had already landed in {admiraldev} — the resync picked them up
  • The rest went upstream: admiraldev#580

Centralizing the guidance also centralizes its review. PRs touching generated files need a note: don’t edit here — fix the source.

Agents copy guidance faithfully, mistakes included

The unit test guidance says to use tribble() “from the tibble package”… then shows dplyr::tribble().

Meanwhile, {admiral} itself uses tibble::tribble() in ~30 files vs 1 for dplyr::tribble().

An agent follows what the guidance says, not what the codebase does. Once agents read your vignettes, keeping them accurate matters more than it used to.

The broader community agrees

rOpenSci’s AI policy now asks submitters directly, in the software review template:

“If your repository includes an ‘agents.md’ file or equivalent, please provide a link, and describe how this has been used in the development of your package.”

This is showing up in peer review. The norm is catching on faster than our internal strategy conversation is moving.

Adopting this in your own package

Three steps

  1. Reference the reusable workflow from {admiralci} in .github/workflows/
  2. Add ^AGENTS\.md$ to .Rbuildignore
  3. (Forward-looking) Add a YAML config file with package-specific context (therapeutic area, related packages, relevant CDISC IGs) — not yet read by {admiralci}, see admiralci#246

Then the workflow runs on schedule (or workflow_dispatch), pulling the latest {admiraldev} content and opening a PR with the update.

Gotcha: the first PR

workflow_dispatch only works once the workflow is on the default branch — so you can’t trigger it from the PR that adds it.

Instead, run the same script locally to generate the first files:

Rscript create_agents_md.R admiralonco

Same script, same output as the workflow.

Live examples: two adopters so far

uses: pharmaverse/admiralci/.github/workflows/create-agents-md.yml@main
with:
  package_name: admiralpeds   # or admiralonco
  target_branch: main
  admiralci_ref: main

Each adds a package-specific config capturing what’s unique to its therapeutic area — ready for the day {admiralci} consumes it directly.

What the config captures

From {admiralonco}’s agents-config.yml:

oncology_specific_conventions:
  - The derive_param_*() functions (e.g. derive_param_bor(),
    derive_param_confirmed_bor(), derive_param_response()) and filter_pd()
    are deprecated. Use admiral::derive_extreme_event() with admiralonco's
    predefined event()/event_joined() objects (e.g. rsp_y, bor_cr, bor_pr,
    cbor_cr, crsp_y_cr, cb_y) instead, and do not add new code that calls
    the deprecated functions.

Package knowledge an agent can’t infer from reading the source: the deprecated functions are still there, and still documented.

Beyond AGENTS.md: Agent Skills

Two different tools

AGENTS.md isn’t the only place agent-facing context now lives.

AGENTS.md SKILL.md
Scope Whole repo — conventions, git usage, checks One task or workflow
Lives in The repo it describes Any project — installed as a package
Activated Always loaded as context On demand, matched to the task
Contains Prose guidance Workflow + references + scripts + benchmarks

Live example: {admiral} skills in the wild

RConsortium/pharma-skills — part of the R Consortium Submissions Working Group’s Pilot 7 — publishes installable skills, including two built on {admiral}:

  • admiral-adsl — derive ADSL from SDTM domains
  • admiral-bds — derive BDS findings datasets (ADVS, ADLB)
name: admiral-adsl
description: >
  Derives an ADaM Subject-Level Analysis Dataset (ADSL) using the {admiral}
  R package and pharmaverse ecosystem. Use when a user needs to create ADSL
  from SDTM domains...
metadata:
  pharmaverse: "true"
  parent: admiral
compatibility: >
  Requires R with admiral, dplyr, lubridate, and pharmaversesdtm installed.

Installed with one command: npx skills add RConsortium/pharma-skills --skill admiral-adsl

Complementary, not competing

AGENTS.md teaches an agent how {admiral} as a whole expects code to be written. A skill like admiral-adsl teaches it how to execute one derivation, step by step, with QC-ready, traceable output.

Skills carry their own governance: Design → Development → Evaluation → Release, benchmarked across multiple LLMs before publication — the same rigor Pilot 7 applies to its synthetic-data and trial-simulation work.

Same underlying question as the rest of this talk: how do we transmit regulatory-grade domain knowledge to something that didn’t grow up in this codebase?

What’s still evolving

  • Per-package YAML context is written, but not yet consumed by {admiralci} — tracked in admiralci#246; ecosystem/ADaM-fundamentals sections come after
  • {admiral}’s formal AI policy landed 2026-05-01: an “AI Assistance” paragraph in CONTRIBUTING — core team may use AI while staying in the loop; external contributors read the contributing guide and Programming Strategy first
  • We don’t always know whether a contribution was AI-assisted, and increasingly it may not matter as much as whether the code is right

Why invest in this now

Whether a contributor is a human using an AI assistant, an autonomous agent, or something in between — the code still has to follow ADaM conventions and pharmaverse standards.

AGENTS.md doesn’t guarantee that. It removes the excuse that the agent never had a chance to know better.

Resources

Thank you

Toy-box mock-up of a Jeff action figure: Clinical Data Scientist, Navitas Data Sciences Edition, with R, CDISC, ADaM and pharmaverse accessories

Questions?

Jeff Dickinson

Navitas Data Sciences

AI disclosure: illustrations generated with Gemini Flash. The {admiral} AGENTS.md generator and workflow had contributions from Claude Code (Sonnet 4.6 through Opus 5.5) and GitHub Copilot; this deck was edited with help from Claude Code.

Backup: workflow runs

pharmaverse/admiral → Actions → Create the AGENTS.md

Run Trigger Result
2026-10-15 schedule ✅ success
2026-09-15 schedule ✅ success
2026-08-15 schedule ✅ success

Cron '0 8 15 * *': the 15th of every month, plus workflow_dispatch on demand.

Snapshot 2026-10-19 · ← back

Backup: inside {admiral}’s AGENTS.md

Lines Section Source
1–9 Header: auto-generated, sources, update workflow —
11–17 AI Verification —
21– Programming Strategy programming_strategy.Rmd
~1037– Git Usage git_usage.Rmd
~1240–1382 R CMD Check issues rcmd_issues.Rmd

tests/testthat/AGENTS.md: 354 lines of Unit Test Strategy, with its own verification line, # admiral test guidelines loaded.

Snapshot 2026-10-19 · ← back

Backup: {admiral}’s .Rbuildignore

21  ^\.lycheeignore$
22  ^AGENTS\.md$
23  ^tests/testthat/AGENTS\.md$
24  ^staged_dependencies.yaml$

Snapshot 2026-10-19 · ← back

Backup: adopter PRs

{admiralpeds} #150 {admiralonco} #364
Status Open Open
Workflow sync-admiralci-agents.yml sync-admiralci-agents.yml
Generated AGENTS.md, tests/testthat/AGENTS.md AGENTS.md, tests/testthat/AGENTS.md
Config .github/agents-config.yml .github/agents-config.yml
Package-specific section pediatric_specific_conventions oncology_specific_conventions

Both configs share package_name, therapeutic_area, package_purpose, related_packages, and cdisc_igs.

Snapshot 2026-10-19 · ← back