Skip to content

feat(#5183): publish runner image with fullsend CLI and host-side run dependencies - #5201

Merged
waynesun09 merged 6 commits into
mainfrom
fix-5183-runner-image
Jul 17, 2026
Merged

feat(#5183): publish runner image with fullsend CLI and host-side run dependencies#5201
waynesun09 merged 6 commits into
mainfrom
fix-5183-runner-image

Conversation

@waynesun09

Copy link
Copy Markdown
Member

Summary

Publish ghcr.io/fullsend-ai/fullsend-runner on every version tag: a UBI 10-based image bundling the fullsend CLI (compiled from the tagged source) and every host-side dependency that fullsend run's pre-scripts, validation loop, and post-scripts invoke — so local runs get the same environment the composite action assembles in CI instead of failing on missing dependencies. Podman, the openshell-gateway, and the supervisor image stay on the host; the containerized CLI reaches the gateway over the network and sandboxes still spawn on the host.

Related Issue

Closes #5183

Changes

  • images/runner/Containerfile — multi-stage build:
    • Build stage on ubi10/go-toolset (anonymous pull from registry.access.redhat.com) cross-compiles fullsend with the same ldflags as GoReleaser, so fullsend --version reports the release version.
    • Runtime stage on plain ubi10/ubi (deliberately not go-toolset, which carries gcc/build deps and adds ~600 MB) with: OpenShell CLI pinned via .github/scripts/openshell-version.sh (extracted at build time, so Renovate's existing pin bumps flow through automatically), gh, gitleaks, pre-commit + gitlint, python3 + jsonschema, Go, git, jq, tar.
    • gcloud CLI included for the local-run guide's GCP credential bootstrap (fullsend itself never invokes it); runs on the system python3 with the bundled Python runtime and anthoscli stripped. Final size ~1.1 GB.
    • All third-party downloads version-pinned and SHA256-verified, matching the pins in images/sandbox and images/code where shared; base images digest-pinned.
  • .github/workflows/runner-image.yml — builds and pushes linux/amd64 + linux/arm64 on version tags and on images/runner/ changes; smoke-tests the full dependency surface on a loaded single-platform build. No latest from main pushes — latest always points at the newest release.
  • renovate.json — custom manager tracking GCLOUD_VERSION against gcr.io/google.com/cloudsdktool/google-cloud-cli tags. The SHA256 args are refreshed manually; a stale-hash bump PR fails the runner-image PR build's checksum verification, which blocks automerge.
  • docs/guides/user/running-agents-locally.md — new section on running the CLI from the container: mounts, --network=host gateway access on Linux, macOS limitation, and the gcloud auth flow in a browserless container (gcloud auth login --no-launch-browser, persisting ~/.config/gcloud via mount).
  • images/README.md — runner image section (explicitly not a sandbox image) and supply-chain table rows.
  • .dockerignore — trims the repo-root build context.

Testing

  • make lint passes (stage changes first, then run)
  • Local podman build of the image plus the same smoke test the workflow runs: all checks pass — fullsend reports the injected version, openshell 0.0.83 (matching the pin), gh 2.96.0, gitleaks 8.30.1, pre-commit 4.5.1, gitlint 0.19.1, jsonschema import, Go 1.26.0, Google Cloud SDK 576.0.0, git/jq/tar present
  • Tests added/updated for new or modified logic — no Go/script logic changed; the workflow's smoke-test step is the test surface for the image

Checklist

  • PR title follows Conventional Commits (correct type, ! for breaking changes)
  • Commits are signed off (DCO) — human and human-directed agent sessions only
  • I wrote this contribution myself and can explain all changes in it

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Site preview

Preview: https://337dcacb-site.fullsend-ai.workers.dev

Commit: 4e3897fe2f1bf01cfa3f086acd7ee4a33c627f18

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Publish fullsend-runner image with CLI and host-side run dependencies

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add a UBI10-based runner container image bundling the fullsend CLI and required host-side tools.
• Publish multi-arch images to GHCR on version tags, with a dependency smoke-test in CI.
• Document containerized local runs and wire Renovate to track the gcloud version pin.
Diagram

graph TD
  A{{"Tag push / PR"}} --> B["GitHub Actions: runner-image.yml"] --> C["Buildx (multi-arch)"] --> D[("GHCR fullsend-runner")]
  D --> E["Podman pull/run"] --> F["Runner container (fullsend CLI)"] --> G["OpenShell gateway (host)"] --> H["Sandboxes (host)"]
  subgraph Legend
    direction LR
    _ext{{"Trigger"}} ~~~ _job["CI job / step"] ~~~ _reg[("Registry")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep local installs as scripts (no runner image)
  • ➕ No large image distribution/storage cost
  • ➕ Avoids container networking/gateway reachability constraints (notably macOS)
  • ➖ Hard to keep exact version parity with CI; local runs keep failing on missing tools
  • ➖ More moving parts across OS/package managers; harder to reproduce/debug
2. Base the runner on an existing dev image (ubuntu/debian) with apt packages
  • ➕ Familiar package ecosystem; potentially simpler installs for some tools
  • ➖ Less aligned with the repo’s current UBI supply-chain approach and digest pinning
  • ➖ Many tools still require custom installs + checksums; parity/pinning work remains
3. Split the image into a minimal CLI image + optional “extras” layer
  • ➕ Smaller default image for users who only need core deps
  • ➕ Allows gcloud-heavy use cases to opt in
  • ➖ More tag coordination and documentation complexity
  • ➖ Higher risk of users picking the wrong image and reintroducing missing-deps failures

Recommendation: The PR’s approach (single runner image built from tagged source, with version pins + SHA verification and a CI smoke test) is the best fit for the goal: making local runs behave like CI. The main tradeoff is image size and the Linux-only networking path on macOS; those are already documented, and keeping one blessed image reduces user error compared to a split-image strategy.

Files changed (6) +510 / -2

Enhancement (1) +241 / -0
ContainerfileIntroduce multi-stage UBI10 runner image with pinned toolchain and checksums +241/-0

Introduce multi-stage UBI10 runner image with pinned toolchain and checksums

• Adds a multi-stage Containerfile that compiles the fullsend CLI from source (cross-compiling per TARGETARCH) and assembles a UBI10 runtime with pinned, SHA-verified downloads for OpenShell CLI, gh, gitleaks, Go, and gcloud, plus pip-installed pre-commit/gitlint/jsonschema. Sets the container entrypoint to fullsend and standardizes /work as the working directory for mounted repos.

images/runner/Containerfile

Documentation (2) +114 / -2
running-agents-locally.mdDocument running fullsend from the runner container (mounts, host networking, gcloud) +73/-0

Document running fullsend from the runner container (mounts, host networking, gcloud)

• Adds an alternative workflow for running the fullsend CLI from the published runner image. Documents required mounts, Linux --network=host gateway access, how to perform gcloud auth in a browserless container, and notes the current macOS limitation.

docs/guides/user/running-agents-locally.md

README.mdDescribe the runner image and add supply-chain/pinning entries +41/-2

Describe the runner image and add supply-chain/pinning entries

• Updates the images README to cover the new runner image as distinct from sandbox images. Adds documentation for tag semantics, build context, and supply-chain table rows for the runner’s base images and pinned tools.

images/README.md

Other (3) +155 / -0
.dockerignoreAdd repo-root .dockerignore to shrink runner build context +15/-0

Add repo-root .dockerignore to shrink runner build context

• Introduces a repo-root .dockerignore tailored for images/runner builds. Excludes large, irrelevant directories so the runner image build context only includes Go sources and the OpenShell version pin script.

.dockerignore

runner-image.ymlAdd CI to build/push fullsend-runner on tags and validate dependencies +130/-0

Add CI to build/push fullsend-runner on tags and validate dependencies

• Adds a workflow that builds a multi-arch runner image with Buildx and publishes to GHCR on semver tag pushes (and on main/PR changes under images/runner). Computes a GoReleaser-style version string for ldflags injection, and smoke-tests the container for required tool availability and correct fullsend --version output.

.github/workflows/runner-image.yml

renovate.jsonAdd Renovate custom manager for GCLOUD_VERSION pin in runner image +10/-0

Add Renovate custom manager for GCLOUD_VERSION pin in runner image

• Introduces a Renovate regex manager to track the GCLOUD_VERSION ARG in images/runner/Containerfile against docker tags. Explicitly documents that the architecture-specific SHA256 values must be refreshed manually and will be enforced by the image build checksum verification.

renovate.json

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@qodo-code-review

qodo-code-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (2)

Context used
✅ Compliance rules (platform): 54 rules

Grey Divider


Action required

1. openshell-gateway undefined early ✓ Resolved 📜 Skill insight ✧ Quality
Description
The new section introduces the term openshell-gateway without defining it inline or linking to the
glossary on its first occurrence. This can confuse readers and violates the guide jargon-definition
requirement.
Code

docs/guides/user/running-agents-locally.md[R72-75]

+You still need on the host: Podman, the `openshell-gateway` service (see
+[Install OpenShell](#install-openshell) — the gateway and sandboxes stay on
+the host; only the CLI moves into the container), GCP credentials, and a
+GitHub token.
Relevance

⭐⭐⭐ High

Team accepted adding brief definitions/explanations for new terms in this guide previously
(running-agents-locally improvements in PR665).

PR-#665

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062083 requires jargon be defined on first use; the added text references
openshell-gateway as a prerequisite without defining what it is or linking to a glossary entry at
that first mention.

docs/guides/user/running-agents-locally.md[72-75]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`openshell-gateway` is referenced in the newly added container workflow section without a first-use definition or glossary link.

## Issue Context
Compliance requires jargon to be defined on first use via a glossary link or inline definition.

## Fix Focus Areas
- docs/guides/user/running-agents-locally.md[72-75]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Container workflow not numbered 📜 Skill insight ✧ Quality
Description
The new container-based local-run procedure is described in prose with unstructured commands, rather
than as a numbered sequence of steps. This makes the guide non-compliant with the requirement that
procedures use ordered steps.
Code

docs/guides/user/running-agents-locally.md[R59-126]

+## Alternative: run the CLI from the container image
+
+Instead of downloading the fullsend binary and assembling its host-side
+dependencies yourself (gh, python3 + jsonschema, gitleaks, pre-commit, Go —
+tools the CI runners ship but your machine may not), you can run the CLI
+from the released runner image. It bundles the fullsend CLI and every
+host-side run dependency at the versions the release was tested with,
+including the pinned OpenShell CLI:
+
+```bash
+podman pull ghcr.io/fullsend-ai/fullsend-runner:latest
+```
+
+You still need on the host: Podman, the `openshell-gateway` service (see
+[Install OpenShell](#install-openshell) — the gateway and sandboxes stay on
+the host; only the CLI moves into the container), GCP credentials, and a
+GitHub token.
+
+Mount your OpenShell client config and the same paths you would pass to a
+native `fullsend run`, keeping identical paths inside the container so the
+flag values are unchanged. On Linux, `--network=host` lets the containerized
+CLI reach the gateway on `127.0.0.1` exactly like the native binary:
+
+```bash
+podman run --rm -it --network=host \
+  -v "$HOME/.config/openshell:/root/.config/openshell" \
+  -v /tmp/fullsend-ai_fullsend:/tmp/fullsend-ai_fullsend \
+  -v /tmp/target-repo:/tmp/target-repo \
+  -v "$PWD:/work" \
+  ghcr.io/fullsend-ai/fullsend-runner:latest \
+  run triage \
+    --fullsend-dir /tmp/fullsend-ai_fullsend/internal/scaffold/fullsend-repo/ \
+    --target-repo /tmp/target-repo/ \
+    --env-file fullsend-gcp.env \
+    --env-file fullsend-triage.env
+```
+
+The image's working directory is `/work`, so relative paths in `--env-file`
+(and relative `GOOGLE_APPLICATION_CREDENTIALS` inside env files) resolve
+against the mounted current directory. Pin a version with
+`ghcr.io/fullsend-ai/fullsend-runner:{version}` — image tags match release
+versions.
+
+The image also includes the `gcloud` CLI (pinned per release), so the
+[GCP credential setup](#get-google-cloud-platform-credentials) below can be
+done from inside the container. Two things differ from a desktop install:
+
+- **Interactive auth**: the container has no browser. Run
+  `gcloud auth login --no-launch-browser` — it prints a URL to open in a
+  browser on the host and prompts for the authorization code shown there.
+- **Auth persistence**: `gcloud` stores its credentials under
+  `~/.config/gcloud`, which is ephemeral inside the container. Mount it
+  from the host to keep the login across runs:
+
+```bash
+podman run --rm -it \
+  -v "$HOME/.config/gcloud:/root/.config/gcloud" \
+  -v "$PWD:/work" \
+  --entrypoint bash \
+  ghcr.io/fullsend-ai/fullsend-runner:latest
+# then inside: gcloud auth login --no-launch-browser, followed by the
+# service-account commands below; the key file lands in $PWD on the host.
+```
+
+The agent run itself does not need `gcloud` auth — it only reads the
+generated service-account key file via `GOOGLE_APPLICATION_CREDENTIALS`,
+so the `gcloud` mount is not required for `fullsend run`.
+
Relevance

⭐⭐⭐ High

Team repeatedly accepted converting prose procedures into numbered steps in docs guides (e.g.,
PR2663, PR2277).

PR-#2663
PR-#2277

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062079 requires procedures to use numbered steps, but the added container workflow
is presented as paragraphs followed by command blocks (e.g., podman pull and podman run) without
an ordered list.

docs/guides/user/running-agents-locally.md[59-126]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new section "Alternative: run the CLI from the container image" provides procedural guidance using prose paragraphs and free-floating command blocks, rather than numbered steps.

## Issue Context
Compliance requires that procedural content in guides be expressed as numbered (ordered) steps.

## Fix Focus Areas
- docs/guides/user/running-agents-locally.md[59-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Release moves dev tag ✓ Resolved 🐞 Bug ☼ Reliability
Description
The metadata config enables a raw dev tag for any non-PR run, including version-tag pushes, so a
release build will also push/update :dev to a release image. This makes :dev non-deterministic
and contradicts the comment that main builds carry a dev pseudo-version binary.
Code

.github/workflows/runner-image.yml[R69-78]

+          # No raw `latest` on main pushes (unlike the sandbox images):
+          # main builds carry a dev pseudo-version binary. The default
+          # `latest=auto` flavor still tags `latest` on non-prerelease
+          # semver tag pushes.
+          tags: |
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=sha,prefix=
+            type=raw,value=dev,enable=${{ github.event_name != 'pull_request' }}
+
Relevance

⭐⭐ Medium

No clear historical evidence on tagging dev for release builds vs main-only in
docker/metadata-action usage.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow’s own comment describes dev semantics for main builds, but the dev tag rule is
enabled whenever the event isn’t a PR, which includes tag pushes as well.

.github/workflows/runner-image.yml[69-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`type=raw,value=dev` is enabled for all non-PR events, which includes tag pushes. This causes release builds to also publish the `:dev` tag pointing at a release image.

## Issue Context
The workflow comment indicates `dev` is meant for non-release builds (e.g., main branch builds with a dev pseudo-version).

## Fix Focus Areas
- .github/workflows/runner-image.yml[69-78]

## Suggested fix
Change the `dev` tag enable condition to only run on the desired branch, e.g.:
- `enable=${{ github.ref == 'refs/heads/main' }}`
Or explicitly disable it for tag refs:
- `enable=${{ github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/tags/') }}`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unpinned jsonschema in image ✓ Resolved 🐞 Bug ☼ Reliability
Description
images/runner/Containerfile installs jsonschema>=4.18.0 rather than an exact version, so
rebuilding the same tag later can produce different images and validation behavior. This conflicts
with the Containerfile’s claim that tool versions are pinned explicitly.
Code

images/runner/Containerfile[R222-230]

+# pre-commit + gitlint (post-script hook runs) and jsonschema (validation
+# loop, ADR 0022). Versions match images/sandbox/Containerfile; the
+# jsonschema constraint mirrors the composite action's install step.
+ARG PRECOMMIT_VERSION=4.5.1
+ARG GITLINT_VERSION=0.19.1
+RUN pip install --no-cache-dir --break-system-packages \
+      "pre-commit==${PRECOMMIT_VERSION}" \
+      "gitlint-core==${GITLINT_VERSION}" \
+      "jsonschema>=4.18.0"
Relevance

⭐⭐ Medium

No prior accepted/rejected reviews found specifically requiring exact pip pin vs lower-bound in
Containerfiles.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Containerfile asserts that tooling is version-pinned, but the pip line uses a floating lower
bound; the same pattern exists in the composite action, meaning behavior can change over time
without source changes.

images/runner/Containerfile[64-67]
images/runner/Containerfile[222-230]
action.yml[289-292]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The runner image installs `jsonschema>=4.18.0` (range constraint), which allows version drift across rebuilds and undermines reproducibility.

## Issue Context
The Containerfile states tool versions are pinned explicitly, but jsonschema is installed with a lower-bound constraint.

## Fix Focus Areas
- images/runner/Containerfile[64-67]
- images/runner/Containerfile[222-230]

## Suggested fix
- Introduce an explicit pin, e.g. `ARG JSONSCHEMA_VERSION=4.xx.y` and install `jsonschema==${JSONSCHEMA_VERSION}`.
- Optionally add a Renovate regex manager for `JSONSCHEMA_VERSION` (similar to the GCLOUD pin) if you want automated bump PRs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Architecture restated in guide 📜 Skill insight ⚙ Maintainability
Description
The new guide section explains runtime architecture details (host gateway vs containerized CLI vs
host sandboxes) inline instead of linking to an architectural reference. This increases duplication
and risks drift from canonical architecture docs.
Code

docs/guides/user/running-agents-locally.md[R72-80]

+You still need on the host: Podman, the `openshell-gateway` service (see
+[Install OpenShell](#install-openshell) — the gateway and sandboxes stay on
+the host; only the CLI moves into the container), GCP credentials, and a
+GitHub token.
+
+Mount your OpenShell client config and the same paths you would pass to a
+native `fullsend run`, keeping identical paths inside the container so the
+flag values are unchanged. On Linux, `--network=host` lets the containerized
+CLI reach the gateway on `127.0.0.1` exactly like the native binary:
Relevance

⭐ Low

Similar “avoid duplication/link to canonical architecture” doc-drift suggestion was rejected for
this guide’s content in PR665.

PR-#665

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062081 requires guides to link to architecture references rather than restating
architectural concepts inline; the added paragraphs describe the host/container split and gateway
networking directly in the guide text.

docs/guides/user/running-agents-locally.md[72-80]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The guide includes an inline architectural explanation of how the containerized CLI interacts with the host `openshell-gateway` and where sandboxes run.

## Issue Context
Guides should link to architectural references (e.g., `docs/architecture.md` or ADRs) instead of restating architecture inline.

## Fix Focus Areas
- docs/guides/user/running-agents-locally.md[72-80]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Broken tag trigger pattern ✓ Resolved 🐞 Bug ≡ Correctness
Description
.github/workflows/runner-image.yml uses push.tags: "v[0-9]+.[0-9]+*", which is glob-style
matching (not regex), so the + characters are treated literally and normal tags like v1.2.3
won’t trigger the workflow. This can prevent runner images from being built/pushed on releases.
Code

.github/workflows/runner-image.yml[R4-10]

+  push:
+    tags:
+      - "v[0-9]+.[0-9]+*"
+    branches:
+      - main
+    paths:
+      - "images/runner/**"
Relevance

⭐ Low

Repo previously adopted same tag glob v[0-9]+.[0-9]+* for workflows; pattern accepted in PR901.

PR-#901

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow is configured to run on tag pushes, but the configured tag pattern includes +
characters which (under glob matching) are literal, so common tags like v1.2.3 will not match and
therefore won’t build/publish the image.

.github/workflows/runner-image.yml[3-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The runner-image workflow’s tag filter uses regex-like syntax (`+`) in a GitHub Actions tag glob. Because `+` is literal in glob matching, standard semver tags (e.g., `v1.2.3`) will not match and the release image won’t publish.

## Issue Context
This workflow is intended to publish the runner image on every version tag.

## Fix Focus Areas
- .github/workflows/runner-image.yml[4-10]

## Suggested fix
- Replace the tag filter with a glob that matches your release tags, e.g.:
 - `v*.*.*` (broad) or
 - `v[0-9]*.[0-9]*.[0-9]*` (still glob, but closer to semver)
- If you need strict semver validation, keep a broad glob and add an explicit validation/early-exit step (or a job-level `if:`) that checks the tag format in bash.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread .github/workflows/runner-image.yml
Comment thread images/runner/Containerfile Outdated
@waynesun09
waynesun09 force-pushed the fix-5183-runner-image branch from 6d439ca to 5a408a3 Compare July 16, 2026 15:59
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 3:59 PM UTC · Ended 3:59 PM UTC
Commit: 12bd957 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 4:00 PM UTC · Ended 4:06 PM UTC
Commit: 12bd957 · View workflow run →

… dependencies

Publish `ghcr.io/fullsend-ai/fullsend-runner` on every version tag: a
UBI 10-based image bundling the fullsend CLI (compiled from the tagged
source) and every host-side dependency that `fullsend run`'s pre-scripts,
validation loop, and post-scripts invoke — so local runs get the same
environment the composite action assembles in CI instead of failing on
missing dependencies. Podman, the openshell-gateway, and the supervisor
image stay on the host; the containerized CLI reaches the gateway over the
network and sandboxes still spawn on the host.

## Changes

- `images/runner/Containerfile` — multi-stage build: build stage on
  `ubi10/go-toolset` cross-compiles fullsend with the same ldflags as
  GoReleaser; runtime stage on plain `ubi10/ubi` with: OpenShell CLI
  pinned via `.github/scripts/openshell-version.sh`, gh, gitleaks,
  pre-commit + gitlint, python3 + jsonschema (pinned ==4.23.0, matching
  the sandbox image), Go, git, jq, tar. gcloud included for the local-run
  guide's GCP credential bootstrap. All third-party downloads
  version-pinned and SHA256-verified. Base images digest-pinned. Final
  size ~1.1 GB. Runs as root (local convenience wrapper, not a sandbox;
  rootless podman maps container root to the invoking host user).
- `.github/workflows/runner-image.yml` — smoke-test (single-platform
  load + full dependency-surface validation) runs BEFORE the multi-arch
  build-and-push, so a broken image never reaches the registry. Tags
  linux/amd64 + linux/arm64 on version tags and on `images/runner/`
  changes. Tag trigger aligned with release.yml (three-component semver).
  `:dev` tag scoped to `refs/heads/main` only so release builds never
  move it onto a release image. No `latest` from main — `latest` always
  points at the newest release.
- `renovate.json` — custom manager tracking `GCLOUD_VERSION` against
  `gcr.io/google.com/cloudsdktool/google-cloud-cli` tags (semver
  versioning constraint); SHA256 args refreshed manually.
- `docs/guides/user/running-agents-locally.md` — new section on running
  the CLI from the container image: mounts (including `/tmp/fullsend` for
  run artifacts), corrected gcloud auth flow (remote-bootstrap two-machine
  flow, not URL+code), macOS noted as untested with candidate path via
  `host.containers.internal`, SELinux `:z` guidance, sandbox-log
  diagnostics note. OpenShell prerequisite updated to 0.0.83.
- `images/README.md` — runner image section (explicitly not a sandbox
  image), supply-chain table rows with OpenShell trust-model note.
- `.dockerignore` — trims the repo-root build context; excludes
  credential/key file patterns as defense in depth.

Assisted-by: Claude (fix), Claude (review), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09
waynesun09 force-pushed the fix-5183-runner-image branch from 5a408a3 to 6f3691e Compare July 16, 2026 16:05
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:06 PM UTC · Completed 4:13 PM UTC
Commit: 6f3691e · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review

Findings

Medium

Low

  • [supply-chain] images/runner/Containerfile — OpenShell binary integrity relies on a checksums file fetched from the same GitHub release as the tarball (openshell-checksums-sha256.txt). Unlike gcloud, gh, gitleaks, and Go — which pin expected SHA256 hashes as ARG directives — OpenShell's verification is circular: if the release were compromised, both the tarball and checksums would be replaced. The limitation is documented in images/README.md supply-chain table. Pinning SHA256 hashes as ARGs would match the established pattern.
  • [version-drift] docs/guides/user/running-agents-locally.md:56 — The install example hardcodes OPENSHELL_VERSION=0.0.83 with a comment directing users to the pin file. This hardcoded value will silently drift out of sync with .github/scripts/openshell-version.sh as future releases bump the pin. The prerequisites table correctly links to the pin file rather than hardcoding.
  • [inline-comment-location] images/runner/Containerfile — The gcloud section places an ENV directive (CLOUDSDK_PYTHON) between the 'To update:' comment and the ARG declarations, deviating from the comment-then-ARGs-then-RUN pattern used by other tool sections in this Containerfile and the sandbox/code Containerfiles. The deviation has a functional justification (gcloud requires CLOUDSDK_PYTHON at install time).
  • [heading-capitalization] images/README.md:1 — The heading changed from # Sandbox Images to # Images. While the PR updates the intro paragraph to mention the runner image, the first sentence still leads with sandbox-centric language.
  • [error-message-style] .github/workflows/runner-image.yml — The smoke test uses mixed error message formats: a descriptive message with embedded values for the fullsend version check vs. FAIL: <tool> missing for all other checks. The version check arguably justifies the different format since it validates a version match rather than mere presence.
Previous run

Review

Findings

Medium

Low

  • [architectural-documentation-gap] AGENTS.md:110 — The "Sandbox image topology" section documents a two-image hierarchy for agent execution sandboxes. The runner image is a host-side CLI distribution container (not a sandbox), so its absence is a reasonable scope boundary. A brief note clarifying the runner image exists outside the sandbox hierarchy would be a useful addition.
  • [supply-chain] images/runner/Containerfile — OpenShell binary integrity relies on a checksums file fetched from the same GitHub release as the tarball (openshell-checksums-sha256.txt). Unlike gcloud, gh, gitleaks, and Go — which pin expected SHA256 hashes as ARG directives — OpenShell's verification is circular: if the release were compromised, both the tarball and checksums would be replaced. The limitation is documented in images/README.md supply-chain table. Pinning SHA256 hashes as ARGs would match the established pattern.

Labels: PR adds a new runner image (images/runner/), CI workflow (.github/workflows/), and documentation (docs/guides/).

Previous run (2)

Review

Findings

Medium

Low

  • [architectural-documentation-gap] AGENTS.md:110 — The "Sandbox image topology" section documents a two-image hierarchy for agent execution sandboxes. The runner image is a host-side CLI distribution container (not a sandbox), so its absence is a reasonable scope boundary. A brief note clarifying the runner image exists outside the sandbox hierarchy would be a useful addition.
  • [supply-chain] images/runner/Containerfile — OpenShell binary integrity relies on a checksums file fetched from the same GitHub release as the tarball (openshell-checksums-sha256.txt). Unlike gcloud, gh, gitleaks, and Go — which pin expected SHA256 hashes as ARG directives — OpenShell's verification is circular: if the release were compromised, both the tarball and checksums would be replaced. The limitation is documented in images/README.md supply-chain table. Pinning SHA256 hashes as ARGs would match the established pattern.
Previous run

Review — approve

PR: #5201feat(#5183): publish runner image with fullsend CLI and host-side run dependencies
Author: waynesun09 (member)

Summary

This PR introduces a new ghcr.io/fullsend-ai/fullsend-runner container image that bundles the fullsend CLI and every host-side run dependency, so local agent runs get the same environment the CI composite action assembles. The implementation is clean, well-documented, and follows the project's existing patterns.

Correctness

  • Multi-stage build is correct. The build stage cross-compiles fullsend using --platform=$BUILDPLATFORM with GOARCH=${TARGETARCH}, avoiding QEMU-emulated Go compilation. The mintcore go.mod is correctly copied before go mod download to satisfy the root module's replace directive.
  • Version pins match sibling images. Verified against images/sandbox/Containerfile and images/code/Containerfile: gitleaks 8.30.1, Go 1.26.0, pre-commit 4.5.1, gitlint 0.19.1, jsonschema 4.23.0 — all identical.
  • OpenShell version sourced from canonical pin file (.github/scripts/openshell-version.sh → 0.0.83), preventing drift from CI.
  • Smoke test gates before push. Single-platform build → full dependency validation → multi-arch push. A broken image cannot reach the registry under a release tag.
  • No push on PRs (push: ${{ github.event_name != 'pull_request' }}).
  • .dockerignore is safe. Only the runner image uses repo root as build context; existing sandbox/code builds use context: images/{sandbox,code} and are unaffected. No Go source code lives under excluded directories. Credential patterns (.env*, *.pem, *credentials*.json) are properly excluded.

Security

  • All third-party downloads are version-pinned and SHA256-verified per architecture (gcloud, OpenShell, gh, gitleaks, Go).
  • Base images are OCI digest-pinned (@sha256:...).
  • GitHub Actions are commit-SHA-pinned (not floating tags).
  • Workflow permissions are least-privilege (contents: read, packages: write).
  • The OpenShell checksums-file-from-same-release limitation is transparently documented in images/README.md.
  • Root user is appropriate — the image is a local convenience wrapper (not a sandbox), and rootless podman maps container root to the host user.

Intent & Coherence

  • Directly addresses issue Release a container image with the fullsend CLI and all host-side run dependencies pre-installed #5183. The PR scope is appropriate: new Containerfile, CI workflow, user-facing docs, images README, Renovate tracking, and .dockerignore.
  • feat prefix is correct — this is a new user-facing published artifact (container image users can podman pull).
  • No breaking changes, no ! needed — purely additive.
  • Architecture aligns with the sandbox image topology documented in AGENTS.md (runner image is explicitly not a sandbox image).

Style & Conventions

  • Containerfile follows POSIX-sh compatibility (no SHELL directive), consistent with podman OCI format.
  • Naming follows the existing pattern (fullsend-runner alongside fullsend-sandbox, fullsend-code).
  • Update instructions are embedded as comments in each version-pinned section.
  • Workflow structure mirrors sandbox-images.yml.

Documentation

  • OpenShell version in the local-run guide updated from 0.0.72 → 0.0.83, matching the pin file.
  • New "Alternative: run the CLI from the container image" section is comprehensive: covers Linux --network=host, volume mounts, SELinux notes, gcloud auth flows, and macOS limitations (honestly marked as untested).
  • images/README.md updated with runner image section and supply-chain table rows for the new dependencies.
  • VitePress sidebar already lists the modified page — no config update needed.

Renovate Integration

  • Custom regex manager for GCLOUD_VERSION tracking against gcr.io/google.com/cloudsdktool/google-cloud-cli Docker tags with semver versioning. The SHA256 manual-refresh limitation is documented and the build fails on checksum mismatch, which blocks automerge of stale-hash bumps.

No findings at low severity or above.


Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • .github/workflows/runner-image.yml
  • images/README.md
  • images/runner/Containerfile
Previous run (3)

Review

Findings

Medium

Low

  • [architectural-documentation-gap] AGENTS.md:110 — The "Sandbox image topology" section documents a two-image hierarchy for agent execution sandboxes. The runner image is a host-side CLI distribution container (not a sandbox), so its absence is a reasonable scope boundary. A brief note clarifying the runner image exists outside the sandbox hierarchy would be a useful addition.
  • [supply-chain] images/runner/Containerfile — OpenShell binary integrity relies on a checksums file fetched from the same GitHub release as the tarball (openshell-checksums-sha256.txt). Unlike gcloud, gh, gitleaks, and Go — which pin expected SHA256 hashes as ARG directives — OpenShell's verification is circular: if the release were compromised, both the tarball and checksums would be replaced. The limitation is documented in images/README.md supply-chain table. Pinning SHA256 hashes as ARGs would match the established pattern.
Previous run (4)

Review — approve

PR: #5201feat(#5183): publish runner image with fullsend CLI and host-side run dependencies
Author: waynesun09 (member)

Summary

This PR introduces a new ghcr.io/fullsend-ai/fullsend-runner container image that bundles the fullsend CLI and every host-side run dependency, so local agent runs get the same environment the CI composite action assembles. The implementation is clean, well-documented, and follows the project's existing patterns.

Correctness

  • Multi-stage build is correct. The build stage cross-compiles fullsend using --platform=$BUILDPLATFORM with GOARCH=${TARGETARCH}, avoiding QEMU-emulated Go compilation. The mintcore go.mod is correctly copied before go mod download to satisfy the root module's replace directive.
  • Version pins match sibling images. Verified against images/sandbox/Containerfile and images/code/Containerfile: gitleaks 8.30.1, Go 1.26.0, pre-commit 4.5.1, gitlint 0.19.1, jsonschema 4.23.0 — all identical.
  • OpenShell version sourced from canonical pin file (.github/scripts/openshell-version.sh → 0.0.83), preventing drift from CI.
  • Smoke test gates before push. Single-platform build → full dependency validation → multi-arch push. A broken image cannot reach the registry under a release tag.
  • No push on PRs (push: ${{ github.event_name != 'pull_request' }}).
  • .dockerignore is safe. Only the runner image uses repo root as build context; existing sandbox/code builds use context: images/{sandbox,code} and are unaffected. No Go source code lives under excluded directories. Credential patterns (.env*, *.pem, *credentials*.json) are properly excluded.

Security

  • All third-party downloads are version-pinned and SHA256-verified per architecture (gcloud, OpenShell, gh, gitleaks, Go).
  • Base images are OCI digest-pinned (@sha256:...).
  • GitHub Actions are commit-SHA-pinned (not floating tags).
  • Workflow permissions are least-privilege (contents: read, packages: write).
  • The OpenShell checksums-file-from-same-release limitation is transparently documented in images/README.md.
  • Root user is appropriate — the image is a local convenience wrapper (not a sandbox), and rootless podman maps container root to the host user.

Intent & Coherence

  • Directly addresses issue Release a container image with the fullsend CLI and all host-side run dependencies pre-installed #5183. The PR scope is appropriate: new Containerfile, CI workflow, user-facing docs, images README, Renovate tracking, and .dockerignore.
  • feat prefix is correct — this is a new user-facing published artifact (container image users can podman pull).
  • No breaking changes, no ! needed — purely additive.
  • Architecture aligns with the sandbox image topology documented in AGENTS.md (runner image is explicitly not a sandbox image).

Style & Conventions

  • Containerfile follows POSIX-sh compatibility (no SHELL directive), consistent with podman OCI format.
  • Naming follows the existing pattern (fullsend-runner alongside fullsend-sandbox, fullsend-code).
  • Update instructions are embedded as comments in each version-pinned section.
  • Workflow structure mirrors sandbox-images.yml.

Documentation

  • OpenShell version in the local-run guide updated from 0.0.72 → 0.0.83, matching the pin file.
  • New "Alternative: run the CLI from the container image" section is comprehensive: covers Linux --network=host, volume mounts, SELinux notes, gcloud auth flows, and macOS limitations (honestly marked as untested).
  • images/README.md updated with runner image section and supply-chain table rows for the new dependencies.
  • VitePress sidebar already lists the modified page — no config update needed.

Renovate Integration

  • Custom regex manager for GCLOUD_VERSION tracking against gcr.io/google.com/cloudsdktool/google-cloud-cli Docker tags with semver versioning. The SHA256 manual-refresh limitation is documented and the build fails on checksum mismatch, which blocks automerge of stale-hash bumps.

No findings at low severity or above.


Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • .github/workflows/runner-image.yml
  • images/README.md
  • images/runner/Containerfile

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 16, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:34 PM UTC · Completed 5:49 PM UTC
Commit: 109bdf3 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 16, 2026
The OpenShell version pin in .github/scripts/openshell-version.sh is
bumped automatically by Renovate. Hardcoding the version in the
prerequisites table and install section goes stale on every bump.

- Prerequisites table: link to the pin file instead of hardcoding
- Install section: note that Renovate manages the version and the
  snippet is an example — check the pin file at your release tag

Signed-off-by: Wayne Sun <gsun@redhat.com>
Assisted-by: Claude
@waynesun09
waynesun09 force-pushed the fix-5183-runner-image branch from 109bdf3 to 1ebd38a Compare July 16, 2026 21:46
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:47 PM UTC · Completed 9:57 PM UTC
Commit: 1ebd38a · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment component/runner Agent runner behavior and lifecycle component/ci CI pipelines and checks component/docs User-facing documentation and removed requires-manual-review Review requires human judgment labels Jul 16, 2026
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated
Comment thread docs/guides/user/running-agents-locally.md Outdated

@rh-hemartin rh-hemartin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want some changes

Move the container alternative section after the native run examples
instead of before them, trim it down to remove content already implied
by context (openshell-gateway definition, image pinning, gcloud-in-container
auth flow), and relocate platform-specific caveats to their proper sections
(macOS limitation to platform notes, GCP key mounting to the credentials
section) with forward links so readers aren't confused by early references.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:21 PM UTC · Ended 2:36 PM UTC
Commit: cc7a526 · View workflow run →

Built the runner image locally and ran fullsend run triage against it on
macOS to verify the existing caution note. Two issues confirmed:

- Bind-mounting /tmp/... paths fails outright (statfs: no such file or
  directory) — Podman Desktop's VM shares /Users, /private, and
  /var/folders via virtiofs but not the /tmp symlink target.
- With mounts fixed via /private/tmp/..., sandbox creation still fails
  with connection-refused: --network=host reaches the Podman VM's
  loopback, not the macOS host, and the gateway binds to 127.0.0.1 only
  so host.containers.internal doesn't help either.

Replace the "untested, try it" framing with the confirmed failure modes
so macOS readers aren't invited to debug a known dead end.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:37 PM UTC · Ended 2:38 PM UTC
Commit: cc7a526 · View workflow run →

Every reference to the container alternative now says "Linux only" (or
links to a header that does) before the reader clicks through, and the
macOS bail-out note moved to the first line of its section instead of
after two paragraphs of Linux-only setup instructions. Previously a
macOS reader had to read through the section pitch, prerequisites, and
part of the mount example before learning it doesn't apply to them.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Built and served the actual VitePress site locally to check the rendered
page. The "On this page" outline panel is a fixed 207px box with
text-overflow: ellipsis on a single line — the previous heading
("Alternative: run the CLI from the container image (Linux only)", 390px
wide) truncated well before the "(Linux only)" qualifier, which defeated
the point of adding it. Shortened to "Run from a container (Linux)"
(207px, exact fit, confirmed via scrollWidth == offsetWidth) so a macOS
reader scanning the outline sees the platform scope without opening the
section. Verified anchor links still resolve to the right heading and
land on the macOS bail-out line first.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:47 PM UTC · Completed 3:05 PM UTC
Commit: 4e3897f · View workflow run →

@rh-hemartin rh-hemartin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better, thanks.

@waynesun09
waynesun09 added this pull request to the merge queue Jul 17, 2026

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.


```bash
export OPENSHELL_VERSION=0.0.72
export OPENSHELL_VERSION=0.0.83 # check the pin file for the current version

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] version-drift

The install example hardcodes OPENSHELL_VERSION=0.0.83 with a comment directing users to the pin file. This hardcoded value will silently drift out of sync with .github/scripts/openshell-version.sh as future releases bump the pin. The prerequisites table correctly links to the pin file rather than hardcoding.

Suggested fix: Consider adding a Renovate custom manager or CI lint that keeps this example version in sync with the pin file.

Comment thread images/README.md
@@ -1,8 +1,10 @@
# Sandbox Images
# Images

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] heading-capitalization

The heading changed from '# Sandbox Images' to '# Images'. While the PR updates the intro paragraph to mention the runner image, the first sentence still leads with sandbox-centric language.

Merged via the queue into main with commit a2c9f73 Jul 17, 2026
19 checks passed
@waynesun09
waynesun09 deleted the fix-5183-runner-image branch July 17, 2026 15:06
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 3:08 PM UTC · Completed 3:30 PM UTC
Commit: 4e3897f · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #5201 — Runner image with fullsend CLI

PR #5201 published a new runner container image (ghcr.io/fullsend-ai/fullsend-runner) bundling the fullsend CLI and host-side dependencies. Human-authored by waynesun09 with 6 files changed (543 additions): a multi-stage Containerfile, CI workflow, user documentation, README updates, Renovate config, and .dockerignore. The review agent completed 4 review runs (2 cancelled due to force pushes). Human reviewer rh-hemartin requested changes on the documentation structure; the author addressed them in 4 follow-up commits and the PR merged ~24 hours after opening.

Key finding: complete disjointness between AI and human review

The review agent found 7 items (1 medium protected-path flag, 6 low: supply-chain circular checksum, version drift, ENV placement, heading mismatch, smoke-test style). The human reviewer found 8 items — all about documentation information architecture in the user guide: sections placed too early referencing unexplained concepts, verbose explanations where concise ones suffice, overly complex credential alternatives, and an untested macOS container workflow. Zero findings overlapped. The most impactful finding was the human’s challenge “Weren’t you running on macOS? Could you test this?” — which revealed the documented macOS container path was completely broken (two distinct bugs: bind-mount symlink resolution failure and network isolation to VM loopback).

Proposal filed

  1. Add documentation quality dimension for new user-facing contentfullsend-ai/agents — the docs-currency sub-agent evaluates staleness (whether code changes invalidated existing docs) but has no dimension for evaluating quality of newly added documentation.

Evidence for existing issues

  • #2199 (fact-check technical claims): The review agent praised the macOS container section while the human reviewer challenged whether it was tested, revealing it was non-functional. Direct evidence the review agent should flag untested documented procedures rather than accepting them.
  • #4960 (debounce rapid triggers): 13 fullsend dispatch runs fired in 22 minutes (2026-07-17 13:50–14:12 UTC) triggered by individual pull_request_review_comment events from the human reviewer. Each inline comment triggered a separate dispatch. The debounce scope should include review-comment events alongside push/force-push.
  • #3025 (post-approval findings): The review agent’s final run completed at 15:05 UTC, 4 minutes after merge-queue entry (15:01) and 14 minutes after human approval (14:51).

Autonomy observations

The review agent performed well on Containerfile and CI workflow dimensions — supply-chain verification, version pinning, and style findings were all appropriate and uncontested by the human reviewer. The human focused exclusively on the user-facing documentation guide and did not comment on infrastructure files. This is complementary coverage, not redundancy. However, the documentation quality gap means human review remains essential for PRs adding substantial new user-facing documentation.

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/ci CI pipelines and checks component/docs User-facing documentation component/runner Agent runner behavior and lifecycle requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release a container image with the fullsend CLI and all host-side run dependencies pre-installed

2 participants