-
Notifications
You must be signed in to change notification settings - Fork 0
251 lines (239 loc) · 10.4 KB
/
Copy pathreusable-agent.yml
File metadata and controls
251 lines (239 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# ─────────────────────────────────────────────────────────────────────────────
# claude-harness.yml — reusable workflow form of the AI single-entry contract.
# ─────────────────────────────────────────────────────────────────────────────
# DESIGN GOAL: OpenCI is the single pre-configured entrypoint into Claude.
# Callers only need to supply task + prompt/prompt-path + api-key.
# All MCP servers, CI permissions, tool whitelists, and Slack env are
# pre-configured here so every downstream service shares the same setup.
#
# Inputs / secrets / outputs MUST stay byte-identical to the composite — a
# caller can swap one for the other without changing wiring.
# ─────────────────────────────────────────────────────────────────────────────
name: reusable-agent
on:
workflow_call:
inputs:
openci-ref:
description: OpenCI ref to vendor for ./.openci/* references (default main; pin a tag like v2 in production).
required: false
type: string
default: main
runner:
description: Runner label to use for all jobs in this workflow. Defaults to ubuntu-latest for open-source compatibility.
required: false
type: string
default: ubuntu-latest
task:
type: string
required: true
prompt:
description: "Direct prompt text or slash command (e.g. /heartbeat). Takes precedence over prompt-path."
type: string
required: false
default: ""
prompt-path:
description: "Path to prompt file (relative to caller repo). Falls back to built-in skills/<task>/SKILL.md."
type: string
required: false
default: ""
context:
description: "JSON object with scalar fields. Keys become {{name}} placeholders during prompt rendering."
type: string
required: false
default: "{}"
model:
type: string
required: false
default: "claude-sonnet-4-5-20250929"
max-turns:
type: number
required: false
default: 10
system-prompt:
description: "Optional system prompt (passed as --system-prompt)."
type: string
required: false
default: ""
api-provider:
description: "API provider: anthropic | bedrock | vertex | foundry."
type: string
required: false
default: "anthropic"
timeout-minutes:
description: "Job timeout in minutes."
type: number
required: false
default: 15
extra-allowed-tools:
description: "Comma-separated list of extra tools to allow on top of the harness baseline."
type: string
required: false
default: ""
extra-disallowed-tools:
description: "Comma-separated list of tools to explicitly disallow."
type: string
required: false
default: ""
mcp-config:
description: "JSON string or path (relative to caller repo) for --mcp-config."
type: string
required: false
default: ""
use-sticky-comment:
description: "Use a single sticky comment for PR/Issue dedup."
type: boolean
required: false
default: true
extra-env:
description: "JSON object of additional env vars exposed to Claude's bash context."
type: string
required: false
default: "{}"
secrets:
api-key:
description: "Anthropic API key (required unless oauth-token / bedrock / vertex / foundry is used)."
required: false
oauth-token:
description: "Claude Code OAuth token (alternative to api-key)."
required: false
api-base-url:
description: "Custom Anthropic-compatible API base URL (optional)."
required: false
github-token:
description: "GitHub token for MCP tool access and CI data queries. Defaults to GITHUB_TOKEN."
required: false
slack-webhook:
description: "Slack webhook URL available to Claude as SLACK_WEBHOOK_URL env var."
required: false
outputs:
execution-file:
description: Claude Code execution output file path.
value: ${{ jobs.ai-task.outputs.execution-file }}
session-id:
description: Claude Code session id.
value: ${{ jobs.ai-task.outputs.session-id }}
structured-output:
description: Structured JSON output (when --json-schema is used).
value: ${{ jobs.ai-task.outputs.structured-output }}
prompt-source:
description: Where the prompt came from — direct | slash-command | caller | builtin.
value: ${{ jobs.ai-task.outputs.prompt-source }}
concurrency:
group: claude-harness-${{ github.run_id }}
cancel-in-progress: false
jobs:
preflight:
name: Preflight
runs-on: ${{ inputs.runner }}
timeout-minutes: 2
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2
with:
persist-credentials: false
- name: Check required credentials
env:
API_KEY: ${{ secrets.api-key }}
OAUTH_TOKEN: ${{ secrets.oauth-token }}
PROVIDER: ${{ inputs.api-provider }}
run: |
if [ -n "$API_KEY" ] || [ -n "$OAUTH_TOKEN" ]; then exit 0; fi
case "$PROVIDER" in
bedrock|vertex|foundry) exit 0 ;;
esac
echo "::error title=Missing credentials::Provide secrets.api-key OR secrets.oauth-token, OR set inputs.api-provider to bedrock/vertex/foundry."
exit 1
ai-task:
name: AI Task
needs: preflight
runs-on: ${{ inputs.runner }}
timeout-minutes: ${{ inputs.timeout-minutes }}
outputs:
execution-file: ${{ steps.harness.outputs.execution-file }}
session-id: ${{ steps.harness.outputs.session-id }}
structured-output: ${{ steps.harness.outputs.structured-output }}
prompt-source: ${{ steps.harness.outputs.prompt-source }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2
with:
persist-credentials: false
token: ${{ secrets.github-token || github.token }}
# Cross-repo reusable workflows resolve `./` against the calling repo's
# checkout, so a vanilla `./actions/_common/claude-harness` won't find
# OpenCI's composite when this workflow is called from EvolveCI etc.
# We therefore vendor OpenCI into `.openci/` at the ref the reusable
# workflow was invoked at, then reference the composite via a local
# path. Local refs satisfy verify-sha-consistency.
#
# Why parse `github.workflow_ref` instead of using `github.workflow_sha`:
# in a reusable workflow, `workflow_sha` resolves to the *caller's* HEAD
# SHA (e.g. EvolveCI's main), which doesn't exist in OpenCI. The
# `workflow_ref` context, however, is `OWNER/REPO/.github/workflows/<f>.yml@<ref>`
# for the *called* workflow, so we can extract the OpenCI ref directly.
- name: Determine OpenCI ref to vendor
id: openci_ref
shell: bash
env:
WORKFLOW_REF: ${{ github.workflow_ref }}
OPENCI_REF_INPUT: ${{ inputs.openci-ref }}
run: |
# Prefer caller's explicit `openci-ref` input. Otherwise inspect
# github.workflow_ref ('OWNER/REPO/.github/workflows/<f>.yml@<ref>'):
# if the called workflow is in YiAgent/OpenCI we can reuse its ref;
# for any other caller fall back to the input default ('main').
if [ -n "${OPENCI_REF_INPUT:-}" ] && [ "$OPENCI_REF_INPUT" != "main" ]; then
REF="$OPENCI_REF_INPUT"
else
REPO_PREFIX="${WORKFLOW_REF%%/.github*}"
if [ "$REPO_PREFIX" = "YiAgent/OpenCI" ]; then
REF="${WORKFLOW_REF##*@}"
else
REF="${OPENCI_REF_INPUT:-main}"
fi
fi
printf 'ref=%s\n' "$REF" >> "$GITHUB_OUTPUT"
echo "::notice title=OpenCI ref::$REF"
- name: Vendor OpenCI source for the harness composite
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2
with:
repository: YiAgent/OpenCI
ref: ${{ steps.openci_ref.outputs.ref }}
path: .openci
persist-credentials: false
- name: Run claude-harness composite
id: harness
uses: ./.openci/actions/_common/claude-harness
with:
task: ${{ inputs.task }}
prompt: ${{ inputs.prompt }}
prompt-path: ${{ inputs.prompt-path }}
context: ${{ inputs.context }}
model: ${{ inputs.model }}
max-turns: ${{ inputs.max-turns }}
system-prompt: ${{ inputs.system-prompt }}
api-key: ${{ secrets.api-key }}
oauth-token: ${{ secrets.oauth-token }}
api-base-url: ${{ secrets.api-base-url }}
use-bedrock: ${{ inputs.api-provider == 'bedrock' }}
use-vertex: ${{ inputs.api-provider == 'vertex' }}
use-foundry: ${{ inputs.api-provider == 'foundry' }}
github-token: ${{ secrets.github-token || github.token }}
slack-webhook: ${{ secrets.slack-webhook }}
extra-allowed-tools: ${{ inputs.extra-allowed-tools }}
extra-disallowed-tools: ${{ inputs.extra-disallowed-tools }}
mcp-config: ${{ inputs.mcp-config }}
use-sticky-comment: ${{ inputs.use-sticky-comment }}
extra-env: ${{ inputs.extra-env }}
# agent.yml is the leaf AI primitive. There are no task-specific reusable
# workflows in OpenCI — consumers compose data collection + agent.yml
# invocation in their own workflow files (see EvolveCI's
# agent-{daily,weekly,heartbeat,triage}.yml for the canonical pattern).