diff --git a/.github/workflows/maintain-docs.yml b/.github/workflows/maintain-docs.yml index 03a6ff5..98b0bd3 100644 --- a/.github/workflows/maintain-docs.yml +++ b/.github/workflows/maintain-docs.yml @@ -2,7 +2,7 @@ name: Maintain Docs env: # Advance this revision and all package digests together when package bytes change. - DOCS_AGENT_PACKAGE_REVISION: a39d9db230eb9e0b72ed84465f4d61bd8dda1bab + DOCS_AGENT_PACKAGE_REVISION: a51e79ac698610177852170332a1135a9c315951 # Advance independently when the post-command completion validator changes. DOCS_AGENT_COMPLETION_CONTRACT_REVISION: b136f9bc84e6f0ad9d5d0d5cdbb3df9c62e382e2 @@ -153,7 +153,7 @@ jobs: technical:bootstrap) package_path="bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json" agent_slug="technical-docs-bootstrap-agent" - package_digest="sha256-bytes-v1:0230e0e3fd8a4f045a545407d3d01c22df537b05f031260e29d6b531285b7839" + package_digest="sha256-bytes-v1:124924f004474d5296294a3f2c99285d0848c46ed622926b1b283ed018b2bcf0" lane_requires_pr=true default_prompt="Bootstrap source-grounded developer documentation that helps developers understand, use, and extend the code in the repository." ;; diff --git a/README.md b/README.md index 332d261..b9fa144 100644 --- a/README.md +++ b/README.md @@ -230,11 +230,11 @@ Docs Agent has one canonical architecture: five standalone native Agents API pac - `bundles/user-docs-agent/native/user-docs-maintenance-agent.agent.json` - `bundles/skills-agent/native/skills-maintenance-agent.agent.json` -These five `.agent.json` files are the complete executable package surface. Each package is the sole executable instruction authority for its lane. The reusable workflow maps `audience` and `run_kind` to exactly one package and its canonical agent slug; it does not select a separate manifest, flow, pipeline, or memory envelope. Every descriptor uses the package-source revision `a39d9db230eb9e0b72ed84465f4d61bd8dda1bab`, independently of the revision that invokes the reusable workflow, and supplies a byte-level `sha256-bytes-v1` digest. +These five `.agent.json` files are the complete executable package surface. Each package is the sole executable instruction authority for its lane. The reusable workflow maps `audience` and `run_kind` to exactly one package and its canonical agent slug; it does not select a separate manifest, flow, pipeline, or memory envelope. Every descriptor uses the package-source revision `a51e79ac698610177852170332a1135a9c315951`, independently of the revision that invokes the reusable workflow, and supplies a byte-level `sha256-bytes-v1` digest. Package updates advance the package-source revision and all five declared digests atomically. The immutable-source validator reads each package blob from that Git revision, recomputes its digest and canonical slug, and rejects a descriptor that does not match those historical bytes. -All five packages support direct import through `wp_agent_import_runtime_bundles()` and retain the source-grounded workspace-only editing boundary. They intentionally have no required write-tool gate: deterministic report/diff postconditions reject no-op writes while allowing honest no-change maintenance. +All five packages support direct import through `wp_agent_import_runtime_bundles()` and retain the source-grounded workspace-only editing boundary. Technical bootstrap has an Agents API completion gate that requires a workspace documentation write before it can finish, so a fresh surface cannot complete as prose only. Maintenance and the other no-change-capable lanes intentionally have no required write-tool gate; deterministic report/diff postconditions continue to reject no-op writes while allowing honest no-change maintenance. ### Compatibility Impact diff --git a/bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json b/bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json index e64dc4b..9cdf18c 100644 --- a/bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json +++ b/bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json @@ -2,8 +2,8 @@ "schema_version": 1, "bundle_slug": "technical-docs-bootstrap-agent", "bundle_version": "0.1.0", - "source_ref": "Automattic/docs-agent@3da1b8076359db9bf9f4ee7dadcc3932c080ed71", - "source_revision": "3da1b8076359db9bf9f4ee7dadcc3932c080ed71", + "source_ref": "Automattic/docs-agent@7b67d341b531288bb2a1847d8d9b09096519a867", + "source_revision": "7b67d341b531288bb2a1847d8d9b09096519a867", "agent": { "agent_slug": "technical-docs-bootstrap-agent", "agent_name": "Technical Docs Bootstrap Agent", @@ -30,12 +30,23 @@ "workspace_git_status", "workspace_git_diff" ], - "tool_call_rules": [] + "tool_call_rules": [ + { + "id": "require-bootstrap-workspace-write", + "require_tool_use": true, + "min_tool_calls": 1, + "require_one_of": [ + "workspace_write", + "workspace_edit", + "workspace_apply_patch" + ] + } + ] }, "meta": { "source_type": "runtime-agent-package", "source_package": "technical-docs-agent", - "source_version": "3da1b8076359db9bf9f4ee7dadcc3932c080ed71", + "source_version": "7b67d341b531288bb2a1847d8d9b09096519a867", "role": "technical-docs-bootstrap", "runtime": "agents-api-native-chat", "default_provider": "openai", diff --git a/ci/docs-agent-runner-recipe.json b/ci/docs-agent-runner-recipe.json index 719f5af..6052ad0 100644 --- a/ci/docs-agent-runner-recipe.json +++ b/ci/docs-agent-runner-recipe.json @@ -5,7 +5,7 @@ "docsAgent": { "externalPackageSource": { "repository": "Automattic/docs-agent", - "revision": "a39d9db230eb9e0b72ed84465f4d61bd8dda1bab", + "revision": "a51e79ac698610177852170332a1135a9c315951", "path": "bundles/technical-docs-agent/native/technical-docs-maintenance-agent.agent.json", "digest": "sha256-bytes-v1:975c7b0a0a7aff52897c52be5ac903a7fb110ea3c33e16227f8694c74c932519" }, diff --git a/examples/runner-recipe.example.json b/examples/runner-recipe.example.json index 4b8c568..3a1504b 100644 --- a/examples/runner-recipe.example.json +++ b/examples/runner-recipe.example.json @@ -5,7 +5,7 @@ "docsAgent": { "externalPackageSource": { "repository": "Automattic/docs-agent", - "revision": "a39d9db230eb9e0b72ed84465f4d61bd8dda1bab", + "revision": "a51e79ac698610177852170332a1135a9c315951", "path": "bundles/technical-docs-agent/native/technical-docs-maintenance-agent.agent.json", "digest": "sha256-bytes-v1:975c7b0a0a7aff52897c52be5ac903a7fb110ea3c33e16227f8694c74c932519" }, diff --git a/tests/native-agent-import.php b/tests/native-agent-import.php index dae1d00..6a8d711 100644 --- a/tests/native-agent-import.php +++ b/tests/native-agent-import.php @@ -36,6 +36,8 @@ public function get_error_data(): mixed { $passes = 0; require_once $agents_api_dir . '/tests/agents-api-smoke-helpers.php'; +require_once $agents_api_dir . '/src/Runtime/class-wp-agent-message.php'; +require_once $agents_api_dir . '/src/Runtime/class-wp-agent-tool-call-gate.php'; agents_api_smoke_require_module(); do_action( 'init' ); @@ -69,7 +71,21 @@ public function get_error_data(): mixed { $rules = is_array( $config['tool_call_rules'] ?? null ) ? $config['tool_call_rules'] : array(); agents_api_smoke_assert_equals( true, in_array( 'workspace_write', $config['enabled_tools'] ?? array(), true ), "{$slug} preserves the workspace write tool", $failures, $passes ); - agents_api_smoke_assert_equals( array(), $rules, "{$slug} permits clean evidence-backed no-change completion", $failures, $passes ); + if ( 'technical-docs-bootstrap-agent' === $slug ) { + $gate = AgentsAPI\AI\WP_Agent_Tool_Call_Gate::from_config( $rules ); + agents_api_smoke_assert_equals( true, $gate instanceof AgentsAPI\AI\WP_Agent_Tool_Call_Gate, "{$slug} creates a completion gate for a fresh documentation surface", $failures, $passes ); + + $fresh_surface = array( AgentsAPI\AI\WP_Agent_Message::text( 'user', 'The workspace has no docs/README.md. Create the caller-required documentation paths.' ) ); + $blocked = $gate instanceof AgentsAPI\AI\WP_Agent_Tool_Call_Gate ? $gate->evaluate_completion( $fresh_surface ) : array(); + agents_api_smoke_assert_equals( false, $blocked['allowed'] ?? null, "{$slug} blocks prose-only completion on a fresh documentation surface", $failures, $passes ); + agents_api_smoke_assert_equals( 'require-bootstrap-workspace-write', $blocked['context']['rule_id'] ?? '', "{$slug} identifies the required bootstrap write gate", $failures, $passes ); + + $after_write = array_merge( $fresh_surface, array( AgentsAPI\AI\WP_Agent_Message::toolCall( '', 'workspace_write', array( 'path' => 'docs/README.md' ), 1, array( 'tool_call_id' => 'bootstrap-docs-index' ) ) ) ); + $allowed = $gate instanceof AgentsAPI\AI\WP_Agent_Tool_Call_Gate ? $gate->evaluate_completion( $after_write ) : array(); + agents_api_smoke_assert_equals( true, $allowed['allowed'] ?? null, "{$slug} accepts completion after a required workspace write", $failures, $passes ); + } else { + agents_api_smoke_assert_equals( array(), $rules, "{$slug} permits clean evidence-backed no-change completion", $failures, $passes ); + } $chat = AgentsAPI\AI\Channels\WP_Agent_Default_Chat_Handler::execute( array( 'agent' => $slug, 'message' => 'Verify native registration.' ) ); agents_api_smoke_assert_equals( 'agents_chat_provider_required', $chat instanceof WP_Error ? $chat->get_error_code() : '', "{$slug} resolves through the default native chat handler", $failures, $passes ); diff --git a/tests/native-agent.validate-package-spec.json b/tests/native-agent.validate-package-spec.json index 04b4807..8d1c4e1 100644 --- a/tests/native-agent.validate-package-spec.json +++ b/tests/native-agent.validate-package-spec.json @@ -16,7 +16,7 @@ "technical-docs-bootstrap-agent.agent.json": { "package": "technical-docs-agent", "agent_slug": "technical-docs-bootstrap-agent", - "source_revision": "3da1b8076359db9bf9f4ee7dadcc3932c080ed71", + "source_revision": "7b67d341b531288bb2a1847d8d9b09096519a867", "required_tools": [ "get_github_file", "workspace_read", @@ -26,6 +26,12 @@ "workspace_git_diff" ], "permitted_workspace_tools": ["workspace_show", "workspace_ls", "workspace_read", "workspace_grep", "workspace_write", "workspace_edit", "workspace_apply_patch", "workspace_git_status", "workspace_git_diff"], + "required_tool_call_rule": { + "id": "require-bootstrap-workspace-write", + "require_tool_use": true, + "min_tool_calls": 1, + "require_one_of": ["workspace_write", "workspace_edit", "workspace_apply_patch"] + }, "instructions_must_contain": [ "living documentation", "understand, use, and extend", diff --git a/tests/reusable-workflow-empty-called-workflow-context.fixture.json b/tests/reusable-workflow-empty-called-workflow-context.fixture.json index 3de1351..789b382 100644 --- a/tests/reusable-workflow-empty-called-workflow-context.fixture.json +++ b/tests/reusable-workflow-empty-called-workflow-context.fixture.json @@ -3,5 +3,5 @@ "called_workflow_context": { "job_workflow_sha": "" }, - "expected_package_revision": "a39d9db230eb9e0b72ed84465f4d61bd8dda1bab" + "expected_package_revision": "a51e79ac698610177852170332a1135a9c315951" } diff --git a/tests/validate-docs-agent-packages.php b/tests/validate-docs-agent-packages.php index 69a9463..44a1bf3 100644 --- a/tests/validate-docs-agent-packages.php +++ b/tests/validate-docs-agent-packages.php @@ -45,7 +45,7 @@ $assert( 'docs-agent/runner-recipe/v1' === ( $recipe['schema'] ?? null ), 'Runner recipe must use the portable Docs Agent runner recipe schema.' ); $expected_package_source = array( 'repository' => 'Automattic/docs-agent', - 'revision' => 'a39d9db230eb9e0b72ed84465f4d61bd8dda1bab', + 'revision' => 'a51e79ac698610177852170332a1135a9c315951', 'path' => 'bundles/technical-docs-agent/native/technical-docs-maintenance-agent.agent.json', 'digest' => 'sha256-bytes-v1:975c7b0a0a7aff52897c52be5ac903a7fb110ea3c33e16227f8694c74c932519', ); @@ -217,7 +217,12 @@ ); $assert( ( $native_assertions['permitted_workspace_tools'] ?? array() ) === $workspace_tools, "Native package {$native_file} workspace tools do not match its permitted workspace tool policy." ); - $assert( array() === ( $config['tool_call_rules'] ?? null ), "Native package {$native_file} must allow evidence-backed no-change completion without a no-op write." ); + $required_tool_call_rule = $native_assertions['required_tool_call_rule'] ?? null; + if ( is_array( $required_tool_call_rule ) ) { + $assert( in_array( $required_tool_call_rule, $config['tool_call_rules'] ?? array(), true ), "Native package {$native_file} must enforce its required tool-call rule." ); + } else { + $assert( array() === ( $config['tool_call_rules'] ?? null ), "Native package {$native_file} must allow evidence-backed no-change completion without a no-op write." ); + } // Runner-neutral boundary: no agent-owned publication tools. foreach ( $native_spec['forbidden_publication_tools'] ?? array() as $forbidden_tool ) { diff --git a/tests/validate-external-native-package-sources.php b/tests/validate-external-native-package-sources.php index b6a67cc..f5aaf31 100644 --- a/tests/validate-external-native-package-sources.php +++ b/tests/validate-external-native-package-sources.php @@ -30,9 +30,9 @@ return $contents; }; -$package_revision = 'a39d9db230eb9e0b72ed84465f4d61bd8dda1bab'; +$package_revision = 'a51e79ac698610177852170332a1135a9c315951'; $packages = array( - 'technical:bootstrap' => array( 'path' => 'bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json', 'slug' => 'technical-docs-bootstrap-agent', 'digest' => '0230e0e3fd8a4f045a545407d3d01c22df537b05f031260e29d6b531285b7839' ), + 'technical:bootstrap' => array( 'path' => 'bundles/technical-docs-agent/native/technical-docs-bootstrap-agent.agent.json', 'slug' => 'technical-docs-bootstrap-agent', 'digest' => '124924f004474d5296294a3f2c99285d0848c46ed622926b1b283ed018b2bcf0' ), 'technical:maintenance' => array( 'path' => 'bundles/technical-docs-agent/native/technical-docs-maintenance-agent.agent.json', 'slug' => 'technical-docs-maintenance-agent', 'digest' => '975c7b0a0a7aff52897c52be5ac903a7fb110ea3c33e16227f8694c74c932519' ), 'user:bootstrap' => array( 'path' => 'bundles/user-docs-agent/native/user-docs-bootstrap-agent.agent.json', 'slug' => 'user-docs-bootstrap-agent', 'digest' => 'a3ada797a125f638364caac43004bbb47fcc8c7bdc89021f25fcfcdc4c2d502f' ), 'user:maintenance' => array( 'path' => 'bundles/user-docs-agent/native/user-docs-maintenance-agent.agent.json', 'slug' => 'user-docs-maintenance-agent', 'digest' => '747de10fdb52d7c1543b404030e0e0a2604b6496e95d50143149f72c975a98fb' ), diff --git a/tests/validate-wp-codebox-run-agent-task-contract.php b/tests/validate-wp-codebox-run-agent-task-contract.php index 986938f..a1a9ab7 100644 --- a/tests/validate-wp-codebox-run-agent-task-contract.php +++ b/tests/validate-wp-codebox-run-agent-task-contract.php @@ -233,7 +233,7 @@ $assert( isset( $caller_inputs['external_package_source'] ), 'Docs Agent must provide the required external package descriptor.' ); $assert( ! isset( $caller_inputs['agent_bundle'] ), 'Docs Agent must not pass the removed agent_bundle input.' ); -$assert( str_contains( $workflow, 'DOCS_AGENT_PACKAGE_REVISION: a39d9db230eb9e0b72ed84465f4d61bd8dda1bab' ), 'Docs Agent must use the fixed native package source revision.' ); +$assert( str_contains( $workflow, 'DOCS_AGENT_PACKAGE_REVISION: a51e79ac698610177852170332a1135a9c315951' ), 'Docs Agent must use the fixed native package source revision.' ); $assert( ! str_contains( $workflow, 'github.job_workflow_sha' ), 'Docs Agent must not depend on unavailable called-workflow provenance.' ); preg_match( "/runtime_sources='(?[^']+)'/", $workflow, $runtime_sources_match );