Skip to content

feat(scaffold): replace triage harness with external agent registry - #2750

Closed
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:worktree-pr-b-external-triage
Closed

feat(scaffold): replace triage harness with external agent registry#2750
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:worktree-pr-b-external-triage

Conversation

@ggallen

@ggallen ggallen commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

Completes the triage agent extraction (Phase 2) by removing the triage harness from the scaffold entirely and registering it in an external agent registry.

  • Adds an external agent registry to baseurl.go — external agents have a pinned commit SHA and content hash, so HarnessWrappersLayer generates wrappers pointing directly to fullsend-ai/agents (no stub file, no double base: indirection)
  • Updates HarnessNames() to merge scaffold harness names with external agent registry entries, so "triage" remains discoverable without a placeholder file
  • Deletes all triage-specific scaffold files (agent prompt, env, policy, schema, harness, pre/post scripts) — they now live in the external repo
  • Keeps skills/issue-labels/SKILL.md (shared with the review harness) and .github/workflows/triage.yml (thin workflow caller)
  • Updates validate-output-schema-test.sh to use prioritize schema instead of deleted triage schema
  • Updates docs/agents/triage.md source link to point to external repo

Depends on PR #2733 (merged) and PR #2751 (merged).

Test plan

  • go test ./internal/scaffold/... — all pass
  • go test ./internal/harness/... — all pass
  • go test ./internal/layers/... — all pass
  • go test ./... — full suite passes
  • make script-test — all pass

🤖 Generated with Claude Code

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Scaffold: replace triage harness with pinned external fullsend-ai/agents reference

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add an external-agent registry with pinned commit SHA and content hash for triage.
• Replace the embedded triage harness with a minimal stub that preserves harness enumeration.
• Update wrapper generation and scaffold/harness tests to treat external agents as stubs.
Diagram

graph TD
  A(["Scaffold embed"]) --> B["harness/triage.yaml stub"] --> C(["HarnessWrappersLayer"]) --> D["baseurl.go externalAgents"] --> E{{"fullsend-ai/agents"}}
  C --> F["Generated triage wrapper"]

  subgraph Legend
    direction LR
    _svc(["Module/Layer"]) ~~~ _file["File"] ~~~ _ext{{"External repo"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep triage harness fully embedded in scaffold
  • ➕ Single-repo source of truth; no external pin/update mechanism
  • ➕ Offline/airgapped installs work without external fetch
  • ➖ Scaffold repo continues carrying large triage-specific payload (prompt/policy/schema/scripts)
  • ➖ Harder to evolve triage independently of CLI release cadence
2. Track external agents by tag/release instead of pinned commit SHA
  • ➕ Human-friendly versions (e.g., v1.2.3) and simpler audit trail
  • ➕ Can coordinate updates via release notes
  • ➖ Requires tag immutability discipline; tags can be moved
  • ➖ Still needs integrity strategy; hash must correspond to the tagged artifact
3. Vendor external agents via submodule/subtree sync
  • ➕ Keeps content in-tree for review while preserving upstream repo ownership
  • ➕ No runtime fetch dependency; wrapper generation can remain local
  • ➖ More operational overhead (sync workflows, conflict resolution)
  • ➖ Still bloats this repo with triage payload over time

Recommendation: The chosen approach (external-agent registry with pinned commit SHA + sha256, plus a stub to preserve HarnessNames()) is the best tradeoff for Phase 2 extraction: it decouples triage iteration from CLI releases while retaining integrity and deterministic wrapper output. If this pattern expands beyond triage, consider adding lightweight tooling to update the pinned SHA/hash to reduce manual maintenance and review risk.

Files changed (9) +132 / -126

Enhancement (1) +38 / -6
baseurl.goIntroduce external agent registry and pinned hash behavior +38/-6

Introduce external agent registry and pinned hash behavior

• Adds an ExternalAgent registry for harnesses hosted outside the scaffold with pinned commit SHA, path, and SHA-256 content hash. Updates HarnessBaseURL and HarnessContentHash to special-case external agents (ignore caller commit SHA; return pinned URL/hash).

internal/scaffold/baseurl.go

Refactor (2) +6 / -45
triage.yamlReplace embedded triage harness with external base stub +6/-42

Replace embedded triage harness with external base stub

• Replaces the full triage harness definition with a minimal stub that points directly at the external triage harness URL (including sha256 fragment) while preserving role/slug and keeping a doc reference for discoverability.

internal/scaffold/fullsend-repo/harness/triage.yaml

scaffold.goStop marking triage scripts as scaffold executables +0/-3

Stop marking triage scripts as scaffold executables

• Removes triage-specific scripts from the executableFiles allowlist, consistent with triage assets no longer being embedded in the scaffold.

internal/scaffold/scaffold.go

Tests (6) +88 / -75
scaffold_integration_test.goSwitch integration coverage from triage to code harness; skip external stubs +30/-24

Switch integration coverage from triage to code harness; skip external stubs

• Updates the wrapper/base merge test to use code.yaml instead of triage.yaml and adjusts expected inherited fields (agent/image/policy/env/skills). Adds skips in scaffold-template tests for harnesses marked as external agents, and extends content-hash testing to validate external-agent URL behavior.

internal/harness/scaffold_integration_test.go

harnesswrappers_test.goExpect triage wrapper base to point at fullsend-ai/agents +1/-1

Expect triage wrapper base to point at fullsend-ai/agents

• Updates wrapper-generation assertions so the triage wrapper’s base URL uses the external agents repository rather than the scaffold repository.

internal/layers/harnesswrappers_test.go

workflows_test.goUpdate embedded asset expectations after triage prompt removal +1/-1

Update embedded asset expectations after triage prompt removal

• Adjusts managed-vendored content expectations to no longer require the embedded triage prompt, validating presence of remaining embedded agent assets (e.g., code.md).

internal/layers/workflows_test.go

baseurl_test.goTest external-agent URL and hash behavior +39/-10

Test external-agent URL and hash behavior

• Updates baseline tests to use code harness for scaffold-hosted behavior, and adds coverage ensuring external agents ignore provided commit SHA while returning pinned URLs and pinned hashes. Adds a dedicated IsExternalAgent test.

internal/scaffold/baseurl_test.go

scaffold_test.goUpdate scaffold content and harness validation for external stubs +14/-36

Update scaffold content and harness validation for external stubs

• Removes expectations for deleted triage prompt/env/policy/schema/scripts, and drops direct content assertions for triage prompt/schema. Updates harness validation to LoadRaw external stubs and only require base/role, while keeping full Load validation for scaffold-hosted harnesses.

internal/scaffold/scaffold_test.go

vendormanifest_test.goAdjust vendored-path expectations after triage prompt removal +3/-3

Adjust vendored-path expectations after triage prompt removal

• Updates vendoring tests to stop expecting the embedded triage agent markdown in defaults mirrors, aligning vendored asset enumeration with the new external-agent pattern.

internal/scaffold/vendormanifest_test.go

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Site preview

Preview: https://de0721a3-site.fullsend-ai.workers.dev

Commit: 5e38928486fc75983678721a515a8543f2721afb

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:29 PM UTC · Completed 1:42 PM UTC
Commit: 6c3fe63 · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 58 rules

Grey Divider


Action required

1. External triage URL blocked 🐞 Bug ≡ Correctness
Description
HarnessBaseURL("triage", ...) now produces a base URL under
https://raw.githubusercontent.com/fullsend-ai/agents/, but the default org config allowlist does not
include that prefix. As a result, triage wrapper harnesses will fail base composition with "base URL
is not in allowed_remote_resources" at runtime.
Code

internal/scaffold/baseurl.go[R18-58]

+// ExternalAgent describes an agent harness hosted outside the fullsend scaffold.
+type ExternalAgent struct {
+	URLPrefix   string // raw.githubusercontent.com base, e.g. "https://raw.githubusercontent.com/fullsend-ai/agents/"
+	CommitSHA   string // pinned commit in the external repo
+	HarnessPath string // path prefix within the repo, e.g. "harness/"
+	ContentHash string // SHA-256 hex digest of the remote harness file
+}
+
+// externalAgents maps harness names to agent definitions hosted in standalone repos.
+var externalAgents = map[string]ExternalAgent{
+	"triage": {
+		URLPrefix:   "https://raw.githubusercontent.com/fullsend-ai/agents/",
+		CommitSHA:   "b929ce3d411bf45e01e1459361221bdee6803912",
+		HarnessPath: "harness/",
+		ContentHash: "16d87496f672dab3fda3a448477ee85b67cdfb7cec625563fbeecb65dc0ce0cc",
+	},
+}
+
+// IsExternalAgent reports whether the named harness is hosted outside the scaffold.
+func IsExternalAgent(name string) bool {
+	_, ok := externalAgents[name]
+	return ok
+}
+
var (
	validHarnessName = regexp.MustCompile(`^[a-z][a-z0-9_-]*$`)
	validCommitSHA   = regexp.MustCompile(`^[0-9a-f]{40}$`)
)

-// HarnessBaseURL returns the raw.githubusercontent.com URL for a scaffold
-// harness template at a specific commit SHA. The URL does not include an
-// integrity hash fragment — use HarnessBaseURLWithHash for that.
+// HarnessBaseURL returns the raw.githubusercontent.com URL for a harness
+// template at a specific commit SHA. For external agents the commitSHA
+// parameter is ignored — the pinned SHA from the external registry is used.
+// The URL does not include an integrity hash fragment — use
+// HarnessBaseURLWithHash for that.
func HarnessBaseURL(harnessName, commitSHA string) (string, error) {
	if !validHarnessName.MatchString(harnessName) {
		return "", fmt.Errorf("invalid harness name %q: must match %s", harnessName, validHarnessName.String())
	}
+	if ext, ok := externalAgents[harnessName]; ok {
+		return ext.URLPrefix + ext.CommitSHA + "/" + ext.HarnessPath + harnessName + ".yaml", nil
+	}
Relevance

⭐⭐⭐ High

Team enforces allowed_remote_resources; defaults updated for triage migration before (#2733, #1555).
Missing prefix likely fixed.

PR-#2733
PR-#1555
PR-#2690

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Wrappers are generated from HarnessBaseURLWithHash; for triage this now resolves to the external
agents repo URL, but base URL resolution rejects any URL not matching the org allowlist. The default
allowlist currently omits the agents repo prefix, so triage base resolution will error at runtime.

internal/scaffold/baseurl.go[18-63]
internal/layers/harnesswrappers.go[89-127]
internal/harness/compose.go[277-292]
internal/config/config.go[119-135]

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

### Issue description
Triage harness wrappers now reference an external base URL under `https://raw.githubusercontent.com/fullsend-ai/agents/…`, but the default `allowed_remote_resources` (org config) does not allow this prefix. This will cause triage runs to fail during base composition.

### Issue Context
- `HarnessWrappersLayer` always generates wrapper files with `base: <HarnessBaseURLWithHash(...)>`.
- Base URL fetching enforces prefix allowlisting and hard-fails when the URL is not in `allowed_remote_resources`.
- `HarnessBaseURL("triage", ...)` now returns an `agents/`-hosted URL via the external agent registry.

### Fix Focus Areas
- internal/config/config.go[119-135]
- internal/config/config_test.go[46-67]

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



Remediation recommended

2. Schema test references deleted file 🐞 Bug ⚙ Maintainability
Description
validate-output-schema-test.sh still hardcodes triage-result.schema.json, but this PR deletes that
schema from the scaffold. Running the script will fail immediately due to the missing schema path.
Code

internal/scaffold/fullsend-repo/schemas/triage-result.schema.json[L1-20]

-{
-  "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "triage-result.schema.json",
-  "title": "Triage Agent Result",
-  "description": "Structured output from the triage agent, validated by the harness before the post-script runs (ADR 0022).",
-  "type": "object",
-  "additionalProperties": false,
-  "required": ["action", "reasoning", "comment"],
-  "properties": {
-    "action": {
-      "type": "string",
-      "enum": ["insufficient", "duplicate", "sufficient", "prerequisites", "question"]
-    },
-    "reasoning": {
-      "type": "string",
-      "minLength": 1
-    },
-    "comment": {
-      "type": "string",
-      "minLength": 1,
Relevance

⭐⭐⭐ High

They regularly update validate-output-schema-test.sh when schemas change; similar maintenance
accepted (#1167, #337).

PR-#1167
PR-#337
PR-#279

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test script’s SCHEMA path is still set to the deleted triage schema location, while the
scaffold’s expected embedded files list no longer includes any triage schema file.

internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh[9-12]
internal/scaffold/scaffold_test.go[52-92]

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

### Issue description
`internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh` references `../schemas/triage-result.schema.json`, but the triage schema is removed from the scaffold in this PR. The test script should either be updated to use an existing schema (and matching fixtures) or be removed if triage schema validation now lives entirely in the external agents repo.

### Issue Context
This script is a developer-facing harness/schema validation test helper; after this PR it points at a non-existent file.

### Fix Focus Areas
- internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh[9-12]
- internal/scaffold/scaffold_test.go[52-92]

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


Grey Divider

Qodo Logo

Comment on lines +18 to +58
// ExternalAgent describes an agent harness hosted outside the fullsend scaffold.
type ExternalAgent struct {
URLPrefix string // raw.githubusercontent.com base, e.g. "https://raw.githubusercontent.com/fullsend-ai/agents/"
CommitSHA string // pinned commit in the external repo
HarnessPath string // path prefix within the repo, e.g. "harness/"
ContentHash string // SHA-256 hex digest of the remote harness file
}

// externalAgents maps harness names to agent definitions hosted in standalone repos.
var externalAgents = map[string]ExternalAgent{
"triage": {
URLPrefix: "https://raw.githubusercontent.com/fullsend-ai/agents/",
CommitSHA: "b929ce3d411bf45e01e1459361221bdee6803912",
HarnessPath: "harness/",
ContentHash: "16d87496f672dab3fda3a448477ee85b67cdfb7cec625563fbeecb65dc0ce0cc",
},
}

// IsExternalAgent reports whether the named harness is hosted outside the scaffold.
func IsExternalAgent(name string) bool {
_, ok := externalAgents[name]
return ok
}

var (
validHarnessName = regexp.MustCompile(`^[a-z][a-z0-9_-]*$`)
validCommitSHA = regexp.MustCompile(`^[0-9a-f]{40}$`)
)

// HarnessBaseURL returns the raw.githubusercontent.com URL for a scaffold
// harness template at a specific commit SHA. The URL does not include an
// integrity hash fragment — use HarnessBaseURLWithHash for that.
// HarnessBaseURL returns the raw.githubusercontent.com URL for a harness
// template at a specific commit SHA. For external agents the commitSHA
// parameter is ignored — the pinned SHA from the external registry is used.
// The URL does not include an integrity hash fragment — use
// HarnessBaseURLWithHash for that.
func HarnessBaseURL(harnessName, commitSHA string) (string, error) {
if !validHarnessName.MatchString(harnessName) {
return "", fmt.Errorf("invalid harness name %q: must match %s", harnessName, validHarnessName.String())
}
if ext, ok := externalAgents[harnessName]; ok {
return ext.URLPrefix + ext.CommitSHA + "/" + ext.HarnessPath + harnessName + ".yaml", nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. External triage url blocked 🐞 Bug ≡ Correctness

HarnessBaseURL("triage", ...) now produces a base URL under
https://raw.githubusercontent.com/fullsend-ai/agents/, but the default org config allowlist does not
include that prefix. As a result, triage wrapper harnesses will fail base composition with "base URL
is not in allowed_remote_resources" at runtime.
Agent Prompt
### Issue description
Triage harness wrappers now reference an external base URL under `https://raw.githubusercontent.com/fullsend-ai/agents/…`, but the default `allowed_remote_resources` (org config) does not allow this prefix. This will cause triage runs to fail during base composition.

### Issue Context
- `HarnessWrappersLayer` always generates wrapper files with `base: <HarnessBaseURLWithHash(...)>`.
- Base URL fetching enforces prefix allowlisting and hard-fails when the URL is not in `allowed_remote_resources`.
- `HarnessBaseURL("triage", ...)` now returns an `agents/`-hosted URL via the external agent registry.

### Fix Focus Areas
- internal/config/config.go[119-135]
- internal/config/config_test.go[46-67]

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

@ggallen
ggallen force-pushed the worktree-pr-b-external-triage branch from 6c3fe63 to eed1373 Compare June 29, 2026 13:41
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [missing-authorization] N/A — No linked issue or ADR authorizes this work. The PR body references predecessor PRs feat(config): add triage-agent to default allowed_remote_resources #2733 and fix(config): rename triage-agent to agents in allowlist #2751 (both merged), establishing a multi-PR extraction effort, but there is no issue tracking the overall triage extraction plan. Non-trivial architectural changes benefit from explicit authorization.
    Remediation: Link to the authorizing issue or file one documenting the triage extraction plan.

  • [scope-classification-mismatch] N/A — The PR introduces the concept of an 'external agent registry' (new ExternalAgent struct, IsExternalAgent(), modified HarnessNames() merge logic) and removes 1625 lines. While the new code is compact (~30 lines of Go), this establishes a new pattern for how harnesses are resolved. Consider whether this warrants a lightweight ADR documenting the external agent registry pattern.

  • [architectural-coherence] internal/scaffold/baseurl.go:18 — The ExternalAgent registry hardcodes a single entry for 'triage' pointing to fullsend-ai/agents with a pinned commit SHA and content hash. ADR-0038 envisions URL-based composition via user-controlled config. The relationship between this hardcoded registry (build-time constants for default agents) and ADR-0038's user-facing URL composition should be clarified.

  • [content integrity] internal/scaffold/baseurl.go:217 — The ContentHash for external agents is a static string compiled into the binary. There is no test or mechanism verifying that the hardcoded hash (16d87496...) actually matches the content at the pinned commit (b929ce3d...) in the external repo. For embedded harnesses, TestHarnessContentHash_MatchesEmbeddedContent independently computes and verifies the hash — no equivalent exists for external agents. A typo or stale hash would cause silent runtime failures at fetch time.
    Remediation: Add a CI check or test (e.g., with a //go:build network tag) that fetches the external harness at the pinned SHA and verifies the digest matches.

  • [stale-doc] docs/ADRs/0045-forge-portable-harness-schema.md — ADR 0045 contains multiple illustrative examples referencing deleted triage scaffold paths: agents/triage.md (line 285), env/triage.env (line 329), schemas/triage-result.schema.json (line 208), and triage harness URLs (lines 376, 409, 416). While these are historical examples in a decision record, updating them would improve clarity.

  • [stale-doc] docs/guides/dev/cli-internals.md:473 — An illustrative code snippet showing the executableFiles map references scripts/pre-triage.sh, which was deleted in this PR. This developer-facing guide would mislead contributors.

  • [incomplete-doc] docs/agents/README.md:5 — States 'The set of default agents is defined by the YAML files in internal/scaffold/fullsend-repo/harness/' — this is now incomplete since triage is registered via the externalAgents map in baseurl.go, not via an embedded YAML file.

  • [missing-doc] docs/agents/triage.md — The source link was updated to point to fullsend-ai/agents, but there is no documentation explaining the external agent registry concept — how it works, which agents are external vs. internal, and what this means for versioning.

  • [stale-doc] docs/superpowers/specs/2026-06-11-triage-prerequisites-design.md:136 — References internal/scaffold/fullsend-repo/agents/triage.md as an implementation target. Since this spec is recent and may still be actively referenced, the stale path could confuse developers.


Labels: PR modifies harness resolution infrastructure and extracts the triage agent to an external registry.

Previous run

Review

Reason: stale-head

The review agent reviewed commit 6c3fe63e42d8dfc0c377042e43b1861cd43f1115 but the PR HEAD is now eed13731d0183d1707e1840b843c95d5d7fce14a. This review was discarded to avoid approving unreviewed code.

@fullsend-ai-review

Copy link
Copy Markdown

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 29, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 1:46 PM UTC · Ended 1:56 PM UTC
Commit: ea2ca95 · View workflow run →

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The triage agent now lives in fullsend-ai/agents. Instead of keeping
a stub harness in the scaffold, the external agent registry in
baseurl.go is the single source of truth: HarnessNames() merges
scaffold harnesses with external agents, and HarnessWrappersLayer
generates wrappers pointing directly to the external repo.

- Add ExternalAgent registry to baseurl.go with pinned commit SHA
  and content hash for the triage harness
- Delete triage harness and all triage-specific scaffold files
  (agent prompt, env, policy, schema, pre/post scripts)
- Update HarnessNames() to merge scaffold and external agent names
- Remove post-triage-test.sh from Makefile script-test target
- Update validate-output-schema-test.sh to use prioritize schema
  instead of deleted triage schema
- Update docs/agents/triage.md source link to external repo
- Update all tests for the new external agent pattern

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@ggallen
ggallen force-pushed the worktree-pr-b-external-triage branch from a51e657 to 5e38928 Compare June 29, 2026 13:56
@ggallen ggallen changed the title feat(scaffold): replace triage harness with external agent reference feat(scaffold): replace triage harness with external agent registry Jun 29, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:59 PM UTC · Completed 2:16 PM UTC
Commit: 5e38928 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment component/harness Agent harness, config, and skills loading agent/triage Triage agent labels Jun 29, 2026
@ggallen

ggallen commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

Closing in favor of config-driven agent registry approach (see ADR branch: https://github.com/ggallen/fullsend/tree/adr-config-driven-agents). The hardcoded external agent registry this PR introduced will be replaced by agent registration in org/per-repo config.

@ggallen ggallen closed this Jun 29, 2026
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 29, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ❌ Failure · Started 2:53 PM UTC · Completed 3:01 PM UTC
Commit: 5e38928 · View workflow run →

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

Labels

agent/triage Triage agent component/harness Agent harness, config, and skills loading requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant