Skip to content

161. Repo config limits what a run artifact publishes ​

Date: 2026-11-06

Amends: ADR 0021

Status ​

Accepted

Context ​

Every agent run uploads its whole output/ directory as the run artifact: fullsend-<agent> on GitHub Actions, and job artifacts on GitLab CI. Each run directory holds one iteration-<N>/ directory per iteration, with the raw conversation stream (output.jsonl), transcripts/ and the agent's output files. Next to them sit run-level files: metrics.json, run-telemetry.jsonl, eval-measurements.jsonl and security/findings.jsonl. The telemetry file carries conversation content when content capture is on (ADR 0050). Nothing skips the upload or narrows what it holds.

ADR 0021 keeps transcripts by default and relies on owner-scoped access. It lets an agent's own YAML opt out when the agent reads protected sources beyond the target repo. That opt-out was never built. Two facts change where the switch should live:

  • On a public repo, any signed-in user can download the artifact until it expires, so access is not owner-scoped.
  • Whether a run reads private data depends on where the run started, not on which agent runs. A repo can take work from an issue tracker that is not public, and a run started there reads that tracker's issue. A harness gives the sandbox access to such a tracker through an overlay on the run's source (ADR 0088). Jira is the first such tracker: the shipped code and triage harnesses attach read-only Jira access only when event.source.system == "jira". Others may follow.

The output scan masks secret literals. It does not remove personal data, customer names or private business text (#9122).

This ADR aims to let a repo owner stop publishing conversation content in the run artifact for runs started from a source that is not public.

Options ​

  • Key by provider as well as source, for example jira-ro, the read-only Jira provider. For the shipped agents, that provider key selects the same runs as the jira source, under a second, internal name. A custom agent that reads a private system on runs from every source can set content for the whole repo instead. Rejected.
  • A tracker-specific switch, such as jira.artifacts. Every new tracker would need its own key and code. Rejected.

Decision ​

We add an artifacts setting to config.yaml that limits what a run artifact publishes for each run source, so a public repo can take work from issue trackers that are not public without publishing what its agents read.

yaml
artifacts:
  content: all          # every run: all (default) | metadata | none
  by_source:
    jira: metadata      # runs started from a Jira issue
  • content applies to every run.
  • by_source applies to runs started from one source. Each key names where the work came from: github (a GitHub issue or PR), gitlab (a GitLab issue or MR), jira (a Jira issue), manual (a run you start by hand) or schedule (a scheduled run). It is the same value CEL triggers see as event.source.system. An adapter for another tracker adds its own source name, and that name works as a key with no change to this setting.
  • by_source looks only at where the run started, not at every private system the agent can read. A run started from github at all still publishes Jira text if its harness gives it Jira access on that run. A source level protects private data only when access to that data is attached per source, as in the shipped harnesses. Otherwise, set a stricter content for the whole repo.
LevelArtifact holds
allEverything in output/, as today. This is the default.
metadataOnly the run-level files: metrics.json, eval-measurements.jsonl, and run-telemetry.jsonl with conversation content removed. Every iteration-<N>/ directory, security/findings.jsonl and any other file is left out.
noneNo artifact is uploaded.
  • It lives in config.yaml, not in agent YAML. The setting applies the same way to every agent, so ADR 0080 puts it in config.yaml. This replaces ADR 0021's per-agent opt-out.
  • The stricter level wins. The runner picks one level per run: the stricter of content and the by_source entry for where the run started. A source with no entry gets the content level. none is stricter than metadata, which is stricter than all.
  • Keys are open strings. Config validation does not limit keys to today's sources, so a new source needs no schema change.
  • Layers merge per key. Across config.base.yaml and config.yaml, an explicit content overrides, and by_source merges per key. Lowering an effective level toward all is a relaxation under the ADR 0122 safety gate, so it needs an explicit declaration.
  • Metadata is an allowlist. metadata copies only the files named above. A file added to output/ later stays excluded until this list names it. security/findings.jsonl stays out: a finding's detail can quote tool output, and a copy with that field stripped would break the file's hash chain. It stays on the runner for local checks.
  • The telemetry copy drops captured content. In the staged run-telemetry.jsonl, every attribute and event that content capture adds (ADR 0050) is dropped. If a record can't be parsed or stripped, the file is left out.
  • Filtering happens after local use, and fails closed. The runner leaves output/ untouched, so fullsend eval-measure and post-scripts read the full run. A step after them builds a staged copy, and the runner uploads only that copy. If level resolution fails, the staged copy is empty and the runner never uploads the raw output/.
  • Content capture is unchanged. Exporting over OTLP to a backend the repo owner controls still works under every level, as in ADR 0050.

The rest of ADR 0021 stands: full transcripts by default, and credential scanning as an invariant check. Per-agent levels and artifact retention are out of scope.

Forge scope ​

  • GitHub: the existing upload step uploads the staged copy and skips the upload for none.
  • GitLab: the job artifact path points at the staged copy.
  • Forgejo: no run workflow yet; it uses the same staged copy when one lands.

All forges read the same config.yaml setting.

Consequences ​

  • A repo can withhold conversation content for runs from any source that is not public (Jira today, other trackers as adapters land) and keep all for the rest.
  • A custom agent that reads private data on runs from every source is covered only by content, which applies to the whole repo.
  • The retro agent, session resumption and follow-up workflows that download the artifact get no transcripts or security findings under metadata and no artifact under none, which the user guide must document.
  • This ADR adds no private store for the withheld files, and none is planned. The full output/ exists only on the runner and is gone when the job ends. To keep conversation content privately, export it over OTLP to a backend the repo owner controls (ADR 0050). A private store that later workflows read from is out of scope.
  • When the setting is unset, the artifact is the same as today.
  • Changing the level is a config.yaml change, so it goes through the repo's normal review and CODEOWNERS.