Skip to content

Commit fef392b

Browse files
authored
Run Docs Agent through the native executor (#423)
* Run Docs Agent through the native executor * Fix Docs Agent native workflow contract * Keep producer contract out of generic smoke suite
1 parent 5addf59 commit fef392b

3 files changed

Lines changed: 191 additions & 49 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,42 @@ jobs:
4141

4242
- name: Run ${{ matrix.label }}
4343
run: composer ${{ matrix.command }}
44+
45+
workflow-contracts:
46+
name: Workflow contracts
47+
runs-on: ubuntu-latest
48+
49+
steps:
50+
- uses: actions/checkout@v6.0.3
51+
52+
- name: Checkout Docs Agent producer
53+
uses: actions/checkout@v6.0.3
54+
with:
55+
repository: Automattic/docs-agent
56+
ref: 5344a4bfbda4a0553cc92636258e46a715b1c72d
57+
path: .docs-agent-producer
58+
59+
- name: Checkout WP Codebox producer schema
60+
uses: actions/checkout@v6.0.3
61+
with:
62+
repository: Automattic/wp-codebox
63+
ref: 54c2f9a7bc3cd1fe20055d496c83efcfb99afb41
64+
path: .wp-codebox-producer
65+
66+
- name: Set up PHP
67+
uses: shivammathur/setup-php@v2
68+
with:
69+
php-version: '8.1'
70+
coverage: none
71+
72+
- name: Validate native Docs Agent producer contract
73+
env:
74+
DOCS_AGENT_DIR: ${{ github.workspace }}/.docs-agent-producer
75+
WP_CODEBOX_DIR: ${{ github.workspace }}/.wp-codebox-producer
76+
run: php tests/docs-agent-native-workflow-contract.php
77+
78+
- name: Lint GitHub Actions workflows
79+
uses: docker://rhysd/actionlint:1.7.12
80+
with:
81+
# With no paths, actionlint discovers and reads every workflow file.
82+
args: -no-color

.github/workflows/docs-agent.yml

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,45 @@ on:
44
workflow_dispatch:
55
inputs:
66
prompt:
7-
description: Optional additional instruction for Docs Agent.
7+
description: Optional instruction appended to the technical documentation maintenance task.
88
required: false
9-
default: Generate or update Agents API documentation from repository source. Open a documentation pull request only when changes are needed.
10-
audience:
11-
description: Documentation lane to maintain.
12-
type: choice
13-
required: true
14-
options:
15-
- technical
16-
- user
17-
default: technical
18-
run_kind:
19-
description: Docs Agent run kind.
20-
type: choice
21-
required: true
22-
options:
23-
- maintenance
24-
- bootstrap
25-
default: maintenance
26-
openai_model:
27-
description: OpenAI model to use for Docs Agent.
28-
required: true
29-
default: gpt-5.5
30-
docs_agent_ref:
31-
description: Automattic/docs-agent ref to use for the Docs Agent recipe.
32-
required: true
33-
default: main
34-
base_ref:
35-
description: Base branch or ref for documentation pull requests.
36-
required: true
37-
default: main
38-
docs_branch:
39-
description: Stable branch reused for the Docs Agent pull request.
40-
required: true
41-
default: docs-agent/docs-upkeep
42-
writable_paths:
43-
description: Comma-separated paths Docs Agent may edit.
44-
required: true
45-
default: README.md,docs/**
9+
default: Generate or update Agents API developer documentation from repository source. Open a documentation pull request only when changes are needed.
4610

4711
permissions:
4812
contents: write
4913
pull-requests: write
5014
issues: write
5115

16+
concurrency:
17+
group: docs-agent-${{ github.repository }}
18+
cancel-in-progress: false
19+
5220
jobs:
5321
docs-agent:
54-
name: Maintain Documentation
55-
uses: Automattic/docs-agent/.github/workflows/maintain-docs.yml@main
22+
name: Maintain Technical Documentation
23+
uses: Automattic/docs-agent/.github/workflows/maintain-docs.yml@5344a4bfbda4a0553cc92636258e46a715b1c72d
5624
with:
57-
audience: ${{ inputs.audience }}
58-
run_kind: ${{ inputs.run_kind }}
59-
base_ref: ${{ inputs.base_ref }}
60-
docs_branch: ${{ inputs.docs_branch }}
61-
writable_paths: ${{ inputs.writable_paths }}
62-
model: ${{ inputs.openai_model }}
63-
docs_agent_ref: ${{ inputs.docs_agent_ref }}
25+
audience: technical
26+
run_kind: maintenance
27+
base_ref: main
28+
docs_branch: docs-agent/agents-api-docs-upkeep
29+
writable_paths: README.md,docs/**
6430
verification_commands: |
6531
[
32+
"composer test",
6633
"php tests/no-product-imports-smoke.php"
6734
]
35+
drift_checks: |
36+
[
37+
"git diff --check"
38+
]
6839
prompt: |
6940
${{ inputs.prompt }}
7041
7142
Target repository: Automattic/agents-api
7243
Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
7344
74-
Preserve the Agents API product-neutral boundary. Use Agents API as the product name in production and package documentation. Keep generated documentation focused on the public API substrate, repository source, developer contracts, setup, tests, and contributor workflows for the selected audience.
75-
secrets: inherit
45+
Maintain source-grounded developer documentation for the public, product-neutral Agents API substrate. Cover developer contracts, setup, tests, and contributor workflows using repository source as evidence.
46+
secrets:
47+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
48+
EXTERNAL_PACKAGE_SOURCE_POLICY: ${{ secrets.EXTERNAL_PACKAGE_SOURCE_POLICY }}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
/**
3+
* Validate the Docs Agent consumer against its pinned native producer chain.
4+
*
5+
* Run with DOCS_AGENT_DIR at Docs Agent #121's merge commit and WP_CODEBOX_DIR
6+
* at WP Codebox #1754's merge commit. This test intentionally fails without
7+
* both checkouts because it verifies the producer interfaces, not just copies
8+
* of their expected values in this repository.
9+
*
10+
* @package AgentsAPI\Tests
11+
*/
12+
13+
declare( strict_types=1 );
14+
15+
const AGENTS_API_DOCS_AGENT_REVISION = '5344a4bfbda4a0553cc92636258e46a715b1c72d';
16+
const AGENTS_API_WP_CODEBOX_REVISION = '54c2f9a7bc3cd1fe20055d496c83efcfb99afb41';
17+
18+
$root = dirname( __DIR__ );
19+
$failures = array();
20+
21+
function agents_api_docs_agent_contract_assert( bool $condition, string $message, array &$failures ): void {
22+
if ( ! $condition ) {
23+
$failures[] = $message;
24+
}
25+
}
26+
27+
function agents_api_docs_agent_contract_directory( string $name, array &$failures ): string {
28+
$directory = getenv( $name );
29+
if ( ! is_string( $directory ) || '' === $directory || ! is_dir( $directory ) ) {
30+
$failures[] = sprintf(
31+
'%1$s must point to the pinned producer checkout. Run DOCS_AGENT_DIR=/path/to/docs-agent WP_CODEBOX_DIR=/path/to/wp-codebox php tests/docs-agent-native-workflow-contract.php.',
32+
$name
33+
);
34+
return '';
35+
}
36+
37+
return rtrim( $directory, '/' );
38+
}
39+
40+
function agents_api_docs_agent_contract_read( string $directory, string $path, array &$failures ): string {
41+
$file = $directory . '/' . $path;
42+
if ( ! is_file( $file ) ) {
43+
$failures[] = "Missing producer file: {$path}.";
44+
return '';
45+
}
46+
47+
return (string) file_get_contents( $file );
48+
}
49+
50+
function agents_api_docs_agent_contract_revision( string $directory, string $expected, string $name, array &$failures ): void {
51+
$revision = trim( (string) shell_exec( 'git -C ' . escapeshellarg( $directory ) . ' rev-parse HEAD 2>/dev/null' ) );
52+
agents_api_docs_agent_contract_assert( $expected === $revision, "{$name} must be checked out at {$expected}; found {$revision}.", $failures );
53+
}
54+
55+
function agents_api_docs_agent_contract_match( string $content, string $pattern, string $message, array &$failures ): void {
56+
agents_api_docs_agent_contract_assert( 1 === preg_match( $pattern, $content ), $message, $failures );
57+
}
58+
59+
$docs_agent_dir = agents_api_docs_agent_contract_directory( 'DOCS_AGENT_DIR', $failures );
60+
$wp_codebox_dir = agents_api_docs_agent_contract_directory( 'WP_CODEBOX_DIR', $failures );
61+
62+
if ( $failures ) {
63+
fwrite( STDERR, "Docs Agent native workflow contract failed:\n- " . implode( "\n- ", $failures ) . "\n" );
64+
exit( 1 );
65+
}
66+
67+
agents_api_docs_agent_contract_revision( $docs_agent_dir, AGENTS_API_DOCS_AGENT_REVISION, 'Docs Agent', $failures );
68+
agents_api_docs_agent_contract_revision( $wp_codebox_dir, AGENTS_API_WP_CODEBOX_REVISION, 'WP Codebox', $failures );
69+
70+
$consumer_workflow = (string) file_get_contents( $root . '/.github/workflows/docs-agent.yml' );
71+
$docs_workflow = agents_api_docs_agent_contract_read( $docs_agent_dir, '.github/workflows/maintain-docs.yml', $failures );
72+
$schema_json = agents_api_docs_agent_contract_read( $wp_codebox_dir, 'contracts/run-agent-task-reusable-workflow-interface.v1.json', $failures );
73+
$schema = json_decode( $schema_json, true );
74+
75+
agents_api_docs_agent_contract_assert( is_array( $schema ), 'WP Codebox reusable-workflow schema must be valid JSON.', $failures );
76+
if ( is_array( $schema ) ) {
77+
agents_api_docs_agent_contract_assert( 'wp-codebox/reusable-workflow-interface/v1' === ( $schema['schema'] ?? null ), 'WP Codebox reusable-workflow schema version must match.', $failures );
78+
79+
foreach (
80+
array(
81+
'external_package_source' => array( true, 'string' ),
82+
'target_repo' => array( true, 'string' ),
83+
'prompt' => array( true, 'string' ),
84+
'writable_paths' => array( false, 'string' ),
85+
'verification_commands' => array( false, 'string' ),
86+
'drift_checks' => array( false, 'string' ),
87+
'success_requires_pr' => array( false, 'boolean' ),
88+
'access_token_repos' => array( false, 'string' ),
89+
'require_access_token' => array( false, 'boolean' ),
90+
'allowed_repos' => array( false, 'string' ),
91+
'run_agent' => array( false, 'boolean' ),
92+
'dry_run' => array( false, 'boolean' ),
93+
) as $input => $contract
94+
) {
95+
agents_api_docs_agent_contract_assert( $contract[0] === ( $schema['inputs'][ $input ]['required'] ?? null ), "WP Codebox {$input} required contract must match.", $failures );
96+
agents_api_docs_agent_contract_assert( $contract[1] === ( $schema['inputs'][ $input ]['type'] ?? null ), "WP Codebox {$input} type contract must match.", $failures );
97+
}
98+
99+
foreach ( array( 'OPENAI_API_KEY' => false, 'ACCESS_TOKEN' => false, 'EXTERNAL_PACKAGE_SOURCE_POLICY' => true ) as $secret => $required ) {
100+
agents_api_docs_agent_contract_assert( $required === ( $schema['secrets'][ $secret ]['required'] ?? null ), "WP Codebox {$secret} secret contract must match.", $failures );
101+
}
102+
}
103+
104+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*uses:\s*Automattic/docs-agent/\.github/workflows/maintain-docs\.yml@' . AGENTS_API_DOCS_AGENT_REVISION . '\s*$~m', 'Consumer must pin Docs Agent #121.', $failures );
105+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*audience:\s*technical\s*$~m', 'Consumer audience must be technical.', $failures );
106+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*run_kind:\s*maintenance\s*$~m', 'Consumer run kind must be maintenance.', $failures );
107+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*base_ref:\s*main\s*$~m', 'Consumer target base must be main.', $failures );
108+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*docs_branch:\s*docs-agent/agents-api-docs-upkeep\s*$~m', 'Consumer publication branch must be stable and exact.', $failures );
109+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*writable_paths:\s*README\.md,docs/\*\*\s*$~m', 'Consumer writable paths must be README.md,docs/**.', $failures );
110+
agents_api_docs_agent_contract_match( $consumer_workflow, '~"composer test"\s*,\s*"php tests/no-product-imports-smoke\.php"~s', 'Consumer verification commands must include the test chain.', $failures );
111+
agents_api_docs_agent_contract_match( $consumer_workflow, '~"git diff --check"~', 'Consumer drift check must run git diff --check.', $failures );
112+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*OPENAI_API_KEY:\s*\$\{\{ secrets\.OPENAI_API_KEY \}\}\s*$~m', 'Consumer must forward OPENAI_API_KEY.', $failures );
113+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*EXTERNAL_PACKAGE_SOURCE_POLICY:\s*\$\{\{ secrets\.EXTERNAL_PACKAGE_SOURCE_POLICY \}\}\s*$~m', 'Consumer must forward EXTERNAL_PACKAGE_SOURCE_POLICY.', $failures );
114+
agents_api_docs_agent_contract_assert( 0 === preg_match( '~secrets\.ACCESS_TOKEN|^\s*ACCESS_TOKEN:\s*~m', $consumer_workflow ), 'Consumer must not require or forward ACCESS_TOKEN.', $failures );
115+
116+
foreach ( array( 'contents: write', 'pull-requests: write', 'issues: write' ) as $permission ) {
117+
agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*' . preg_quote( $permission, '~' ) . '\s*$~m', "Consumer must grant {$permission} for built-in-token publication.", $failures );
118+
}
119+
120+
agents_api_docs_agent_contract_match( $docs_workflow, '~technical:maintenance\)\s+package_path="bundles/technical-docs-agent/native/technical-docs-maintenance-agent\.agent\.json"\s+agent_slug="technical-docs-maintenance-agent"\s+package_digest="sha256-bytes-v1:6057aad4eb7c5f0320ccfbce9da93a5fa1d3fc521478b5571ed81c28129325aa"\s+success_requires_pr=false~s', 'Docs Agent #121 technical maintenance lane must map to its exact native package.', $failures );
121+
agents_api_docs_agent_contract_match( $docs_workflow, '~uses:\s*Automattic/wp-codebox/\.github/workflows/run-agent-task\.yml@' . AGENTS_API_WP_CODEBOX_REVISION . '~', 'Docs Agent must pin WP Codebox #1754.', $failures );
122+
agents_api_docs_agent_contract_match( $docs_workflow, '~external_package_source:\s*\$\{\{ needs\.prepare\.outputs\.external_package_source \}\}.*?target_repo:\s*\$\{\{ github\.repository \}\}.*?writable_paths:\s*\$\{\{ inputs\.writable_paths \}\}.*?verification_commands:\s*\$\{\{ needs\.prepare\.outputs\.verification_commands \}\}.*?drift_checks:\s*\$\{\{ needs\.prepare\.outputs\.drift_checks \}\}.*?success_requires_pr:\s*\$\{\{ needs\.prepare\.outputs\.success_requires_pr == \'true\' \}\}.*?access_token_repos:\s*\$\{\{ github\.repository \}\}.*?require_access_token:\s*true.*?allowed_repos:\s*\'\["\$\{\{ github\.repository \}\}"\]\'~s', 'Docs Agent must preserve the external-package, target, writable, verification, publication, and access chain.', $failures );
123+
agents_api_docs_agent_contract_match( $docs_workflow, '~OPENAI_API_KEY:\s*\$\{\{ secrets\.OPENAI_API_KEY \}\}\s+ACCESS_TOKEN:\s*\$\{\{ github\.token \}\}\s+EXTERNAL_PACKAGE_SOURCE_POLICY:\s*\$\{\{ secrets\.EXTERNAL_PACKAGE_SOURCE_POLICY \}\}~s', 'Docs Agent must forward caller credentials and its built-in publication token.', $failures );
124+
125+
if ( $failures ) {
126+
fwrite( STDERR, "Docs Agent native workflow contract failed:\n- " . implode( "\n- ", $failures ) . "\n" );
127+
exit( 1 );
128+
}
129+
130+
fwrite( STDOUT, "Docs Agent native workflow contract passed.\n" );

0 commit comments

Comments
 (0)