From 50bfc06f5b35f6a9396f28350a5127fa7bbc16ce Mon Sep 17 00:00:00 2001 From: ogkranthi Date: Sat, 28 Mar 2026 22:05:51 -0400 Subject: [PATCH 1/4] chore: commit governance system (L1/L2/L3 IR, audit/elevation engine, experiments) + clean up duplicate files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Governance, Guardrail, ToolPermission, PlatformAnnotation to IR model - Add governance extraction to OpenClaw parser (SOUL.md + tool permissions + L3 annotations) - Add elevation engine (elevate_governance) for L2/L3 → L1 promotion - Add governance_audit module with GPR/CFS scoring, CSV/JSON export, Rich tables - Add `agentshift audit` and `agentshift audit-batch` CLI commands - Integrate elevation into claude_code + copilot emitters - Add experiments/ directory with 12 domain agents for research paper - Remove duplicate sections 2.py and persona-sections-schema 2.md - Mark T13 as merged in BACKLOG.md --- BACKLOG.md | 2 +- .../agents/a1-general-assistant/SKILL.md | 15 + .../a1-general-assistant/agent_meta.json | 5 + .../tools/calculator.json | 7 + .../tools/web-search.json | 7 + .../agents/a10-content-moderator/SKILL.md | 16 + .../a10-content-moderator/agent_meta.json | 5 + .../governance/annotations.json | 53 + .../tools/content-queue.json | 7 + .../tools/user-history.json | 7 + .../agents/a11-devops-automation/SKILL.md | 22 + .../a11-devops-automation/agent_meta.json | 5 + .../governance/annotations.json | 17 + .../a11-devops-automation/tools/bash.json | 9 + .../a11-devops-automation/tools/kubectl.json | 11 + .../tools/monitoring-api.json | 7 + .../tools/terraform-apply.json | 8 + .../tools/terraform-plan.json | 7 + experiments/agents/a12-orchestrator/SKILL.md | 18 + .../agents/a12-orchestrator/agent_meta.json | 5 + .../governance/annotations.json | 28 + .../tools/agent-registry.json | 7 + .../tools/result-aggregator.json | 7 + .../tools/sub-agent-control.json | 8 + .../tools/task-dispatcher.json | 7 + experiments/agents/a2-code-reviewer/SKILL.md | 16 + .../agents/a2-code-reviewer/agent_meta.json | 5 + .../a2-code-reviewer/tools/file-read.json | 10 + .../a2-code-reviewer/tools/file-write.json | 8 + .../a2-code-reviewer/tools/git-diff.json | 7 + .../tools/static-analysis.json | 7 + experiments/agents/a3-research-agent/SKILL.md | 17 + .../agents/a3-research-agent/agent_meta.json | 5 + .../a3-research-agent/tools/file-write.json | 9 + .../a3-research-agent/tools/pdf-reader.json | 7 + .../a3-research-agent/tools/web-search.json | 8 + .../agents/a4-customer-support/SKILL.md | 17 + .../a4-customer-support/agent_meta.json | 5 + .../a4-customer-support/tools/crm-lookup.json | 7 + .../a4-customer-support/tools/refund-api.json | 8 + .../tools/ticket-system.json | 7 + .../agents/a5-financial-advisor/SKILL.md | 18 + .../a5-financial-advisor/agent_meta.json | 5 + .../governance/annotations.json | 36 + .../tools/calculation-engine.json | 7 + .../tools/market-data-api.json | 7 + .../tools/news-aggregator.json | 7 + .../tools/portfolio-viewer.json | 8 + .../tools/trade-execution.json | 8 + .../agents/a6-healthcare-triage/SKILL.md | 17 + .../a6-healthcare-triage/agent_meta.json | 5 + .../governance/annotations.json | 48 + .../tools/clinic-finder.json | 7 + .../tools/emergency-services.json | 8 + .../tools/medical-records.json | 7 + .../tools/symptom-checker.json | 8 + experiments/agents/a7-legal-review/SKILL.md | 18 + .../agents/a7-legal-review/agent_meta.json | 5 + .../governance/annotations.json | 27 + .../tools/annotation-engine.json | 7 + .../tools/clause-database.json | 7 + .../tools/document-parser.json | 7 + .../a7-legal-review/tools/external-api.json | 8 + .../a7-legal-review/tools/file-write.json | 9 + experiments/agents/a8-trading-bot/SKILL.md | 19 + .../agents/a8-trading-bot/agent_meta.json | 5 + .../governance/annotations.json | 34 + .../a8-trading-bot/tools/manual-override.json | 8 + .../a8-trading-bot/tools/market-data.json | 8 + .../tools/notification-service.json | 7 + .../a8-trading-bot/tools/order-api.json | 8 + .../a8-trading-bot/tools/portfolio-state.json | 7 + .../a8-trading-bot/tools/risk-calculator.json | 7 + experiments/agents/a9-hr-screening/SKILL.md | 15 + .../agents/a9-hr-screening/agent_meta.json | 5 + .../governance/annotations.json | 45 + .../tools/job-requirements-db.json | 7 + .../a9-hr-screening/tools/resume-parser.json | 7 + .../a9-hr-screening/tools/social-media.json | 7 + experiments/generate_agents.py | 1015 +++++++++++++++++ src/agentshift/cli.py | 123 ++ src/agentshift/elevation.py | 245 ++++ src/agentshift/emitters/claude_code.py | 44 +- src/agentshift/emitters/copilot.py | 26 +- src/agentshift/governance_audit.py | 384 +++++++ src/agentshift/ir.py | 58 + src/agentshift/parsers/openclaw.py | 164 +++ 87 files changed, 2956 insertions(+), 9 deletions(-) create mode 100644 experiments/agents/a1-general-assistant/SKILL.md create mode 100644 experiments/agents/a1-general-assistant/agent_meta.json create mode 100644 experiments/agents/a1-general-assistant/tools/calculator.json create mode 100644 experiments/agents/a1-general-assistant/tools/web-search.json create mode 100644 experiments/agents/a10-content-moderator/SKILL.md create mode 100644 experiments/agents/a10-content-moderator/agent_meta.json create mode 100644 experiments/agents/a10-content-moderator/governance/annotations.json create mode 100644 experiments/agents/a10-content-moderator/tools/content-queue.json create mode 100644 experiments/agents/a10-content-moderator/tools/user-history.json create mode 100644 experiments/agents/a11-devops-automation/SKILL.md create mode 100644 experiments/agents/a11-devops-automation/agent_meta.json create mode 100644 experiments/agents/a11-devops-automation/governance/annotations.json create mode 100644 experiments/agents/a11-devops-automation/tools/bash.json create mode 100644 experiments/agents/a11-devops-automation/tools/kubectl.json create mode 100644 experiments/agents/a11-devops-automation/tools/monitoring-api.json create mode 100644 experiments/agents/a11-devops-automation/tools/terraform-apply.json create mode 100644 experiments/agents/a11-devops-automation/tools/terraform-plan.json create mode 100644 experiments/agents/a12-orchestrator/SKILL.md create mode 100644 experiments/agents/a12-orchestrator/agent_meta.json create mode 100644 experiments/agents/a12-orchestrator/governance/annotations.json create mode 100644 experiments/agents/a12-orchestrator/tools/agent-registry.json create mode 100644 experiments/agents/a12-orchestrator/tools/result-aggregator.json create mode 100644 experiments/agents/a12-orchestrator/tools/sub-agent-control.json create mode 100644 experiments/agents/a12-orchestrator/tools/task-dispatcher.json create mode 100644 experiments/agents/a2-code-reviewer/SKILL.md create mode 100644 experiments/agents/a2-code-reviewer/agent_meta.json create mode 100644 experiments/agents/a2-code-reviewer/tools/file-read.json create mode 100644 experiments/agents/a2-code-reviewer/tools/file-write.json create mode 100644 experiments/agents/a2-code-reviewer/tools/git-diff.json create mode 100644 experiments/agents/a2-code-reviewer/tools/static-analysis.json create mode 100644 experiments/agents/a3-research-agent/SKILL.md create mode 100644 experiments/agents/a3-research-agent/agent_meta.json create mode 100644 experiments/agents/a3-research-agent/tools/file-write.json create mode 100644 experiments/agents/a3-research-agent/tools/pdf-reader.json create mode 100644 experiments/agents/a3-research-agent/tools/web-search.json create mode 100644 experiments/agents/a4-customer-support/SKILL.md create mode 100644 experiments/agents/a4-customer-support/agent_meta.json create mode 100644 experiments/agents/a4-customer-support/tools/crm-lookup.json create mode 100644 experiments/agents/a4-customer-support/tools/refund-api.json create mode 100644 experiments/agents/a4-customer-support/tools/ticket-system.json create mode 100644 experiments/agents/a5-financial-advisor/SKILL.md create mode 100644 experiments/agents/a5-financial-advisor/agent_meta.json create mode 100644 experiments/agents/a5-financial-advisor/governance/annotations.json create mode 100644 experiments/agents/a5-financial-advisor/tools/calculation-engine.json create mode 100644 experiments/agents/a5-financial-advisor/tools/market-data-api.json create mode 100644 experiments/agents/a5-financial-advisor/tools/news-aggregator.json create mode 100644 experiments/agents/a5-financial-advisor/tools/portfolio-viewer.json create mode 100644 experiments/agents/a5-financial-advisor/tools/trade-execution.json create mode 100644 experiments/agents/a6-healthcare-triage/SKILL.md create mode 100644 experiments/agents/a6-healthcare-triage/agent_meta.json create mode 100644 experiments/agents/a6-healthcare-triage/governance/annotations.json create mode 100644 experiments/agents/a6-healthcare-triage/tools/clinic-finder.json create mode 100644 experiments/agents/a6-healthcare-triage/tools/emergency-services.json create mode 100644 experiments/agents/a6-healthcare-triage/tools/medical-records.json create mode 100644 experiments/agents/a6-healthcare-triage/tools/symptom-checker.json create mode 100644 experiments/agents/a7-legal-review/SKILL.md create mode 100644 experiments/agents/a7-legal-review/agent_meta.json create mode 100644 experiments/agents/a7-legal-review/governance/annotations.json create mode 100644 experiments/agents/a7-legal-review/tools/annotation-engine.json create mode 100644 experiments/agents/a7-legal-review/tools/clause-database.json create mode 100644 experiments/agents/a7-legal-review/tools/document-parser.json create mode 100644 experiments/agents/a7-legal-review/tools/external-api.json create mode 100644 experiments/agents/a7-legal-review/tools/file-write.json create mode 100644 experiments/agents/a8-trading-bot/SKILL.md create mode 100644 experiments/agents/a8-trading-bot/agent_meta.json create mode 100644 experiments/agents/a8-trading-bot/governance/annotations.json create mode 100644 experiments/agents/a8-trading-bot/tools/manual-override.json create mode 100644 experiments/agents/a8-trading-bot/tools/market-data.json create mode 100644 experiments/agents/a8-trading-bot/tools/notification-service.json create mode 100644 experiments/agents/a8-trading-bot/tools/order-api.json create mode 100644 experiments/agents/a8-trading-bot/tools/portfolio-state.json create mode 100644 experiments/agents/a8-trading-bot/tools/risk-calculator.json create mode 100644 experiments/agents/a9-hr-screening/SKILL.md create mode 100644 experiments/agents/a9-hr-screening/agent_meta.json create mode 100644 experiments/agents/a9-hr-screening/governance/annotations.json create mode 100644 experiments/agents/a9-hr-screening/tools/job-requirements-db.json create mode 100644 experiments/agents/a9-hr-screening/tools/resume-parser.json create mode 100644 experiments/agents/a9-hr-screening/tools/social-media.json create mode 100644 experiments/generate_agents.py create mode 100644 src/agentshift/elevation.py create mode 100644 src/agentshift/governance_audit.py diff --git a/BACKLOG.md b/BACKLOG.md index 8e058fd..32b36e1 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -71,7 +71,7 @@ | A11 | P1 | @architect | merged | Spec persona.sections schema — structured prompt sections for IR v0.2 | | D20 | P1 | @dev | merged | Add persona.sections to IR model + update parsers to populate from headings | | D21 | P1 | @dev | merged | Update emitters (Bedrock, Vertex, diff) to use persona.sections | -| T13 | P1 | @tester | pr-created | Write tests for persona.sections — parser detection, emitter mapping, diff | +| T13 | P1 | @tester | merged | Write tests for persona.sections — parser detection, emitter mapping, diff | ## Week 5: Ecosystem + LangGraph + GitHub Action diff --git a/experiments/agents/a1-general-assistant/SKILL.md b/experiments/agents/a1-general-assistant/SKILL.md new file mode 100644 index 0000000..9a764f4 --- /dev/null +++ b/experiments/agents/a1-general-assistant/SKILL.md @@ -0,0 +1,15 @@ +--- +name: general-assistant +description: A general-purpose assistant for everyday tasks +version: "1.0.0" +--- + +## Instructions + +You are a helpful general assistant. Answer questions clearly and concisely. +When you don't know something, say so honestly. + +## Tools + +Use `web-search` to find current information. +Use `calculator` for mathematical computations. diff --git a/experiments/agents/a1-general-assistant/agent_meta.json b/experiments/agents/a1-general-assistant/agent_meta.json new file mode 100644 index 0000000..6d071e8 --- /dev/null +++ b/experiments/agents/a1-general-assistant/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A1", + "domain": "General", + "complexity": "Low" +} \ No newline at end of file diff --git a/experiments/agents/a1-general-assistant/tools/calculator.json b/experiments/agents/a1-general-assistant/tools/calculator.json new file mode 100644 index 0000000..a22d1bd --- /dev/null +++ b/experiments/agents/a1-general-assistant/tools/calculator.json @@ -0,0 +1,7 @@ +{ + "name": "calculator", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a1-general-assistant/tools/web-search.json b/experiments/agents/a1-general-assistant/tools/web-search.json new file mode 100644 index 0000000..c9f169a --- /dev/null +++ b/experiments/agents/a1-general-assistant/tools/web-search.json @@ -0,0 +1,7 @@ +{ + "name": "web-search", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a10-content-moderator/SKILL.md b/experiments/agents/a10-content-moderator/SKILL.md new file mode 100644 index 0000000..82b1475 --- /dev/null +++ b/experiments/agents/a10-content-moderator/SKILL.md @@ -0,0 +1,16 @@ +--- +name: content-moderator +description: Content moderation agent applying community guidelines +version: "1.0.0" +--- + +## Instructions + +You are a content moderation agent. Review user-generated content against +community guidelines. Flag violations, escalate edge cases, and document +all decisions. + +## Tools + +Use `content-queue` to review content items. +Use `user-history` to check user moderation history. diff --git a/experiments/agents/a10-content-moderator/agent_meta.json b/experiments/agents/a10-content-moderator/agent_meta.json new file mode 100644 index 0000000..1e9f96b --- /dev/null +++ b/experiments/agents/a10-content-moderator/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A10", + "domain": "Moderation", + "complexity": "Medium" +} \ No newline at end of file diff --git a/experiments/agents/a10-content-moderator/governance/annotations.json b/experiments/agents/a10-content-moderator/governance/annotations.json new file mode 100644 index 0000000..979543b --- /dev/null +++ b/experiments/agents/a10-content-moderator/governance/annotations.json @@ -0,0 +1,53 @@ +{ + "annotations": [ + { + "id": "L3-A10-001", + "kind": "content_filter", + "description": "Detect hate speech (Bedrock HATE, Vertex HARASSMENT)", + "platform_target": "any", + "config": { + "categories": [ + "HATE", + "HARASSMENT" + ] + } + }, + { + "id": "L3-A10-002", + "kind": "content_filter", + "description": "Detect explicit content (Bedrock SEXUAL, Vertex SEXUALLY_EXPLICIT)", + "platform_target": "any", + "config": { + "categories": [ + "SEXUAL", + "SEXUALLY_EXPLICIT" + ] + } + }, + { + "id": "L3-A10-003", + "kind": "content_filter", + "description": "Detect violence (Bedrock VIOLENCE, Vertex DANGEROUS_CONTENT)", + "platform_target": "any", + "config": { + "categories": [ + "VIOLENCE", + "DANGEROUS_CONTENT" + ] + } + }, + { + "id": "L3-A10-004", + "kind": "pii_detection", + "description": "Mask user PII in moderation logs", + "platform_target": "any", + "config": { + "types": [ + "NAME", + "EMAIL", + "PHONE" + ] + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a10-content-moderator/tools/content-queue.json b/experiments/agents/a10-content-moderator/tools/content-queue.json new file mode 100644 index 0000000..e51f98d --- /dev/null +++ b/experiments/agents/a10-content-moderator/tools/content-queue.json @@ -0,0 +1,7 @@ +{ + "name": "content-queue", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a10-content-moderator/tools/user-history.json b/experiments/agents/a10-content-moderator/tools/user-history.json new file mode 100644 index 0000000..6816011 --- /dev/null +++ b/experiments/agents/a10-content-moderator/tools/user-history.json @@ -0,0 +1,7 @@ +{ + "name": "user-history", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/SKILL.md b/experiments/agents/a11-devops-automation/SKILL.md new file mode 100644 index 0000000..007973d --- /dev/null +++ b/experiments/agents/a11-devops-automation/SKILL.md @@ -0,0 +1,22 @@ +--- +name: devops-automation +description: Infrastructure automation agent for Kubernetes and Terraform +version: "1.0.0" +--- + +## Instructions + +You are a DevOps automation agent. Help with infrastructure management +using kubectl and terraform. Always validate changes before applying +and log all operations. + +## Tools + +Use `kubectl` for Kubernetes operations. +Use `terraform-plan` to preview infrastructure changes. +Use `monitoring-api` to check service health. + +```bash +kubectl get pods --namespace dev +terraform plan +``` diff --git a/experiments/agents/a11-devops-automation/agent_meta.json b/experiments/agents/a11-devops-automation/agent_meta.json new file mode 100644 index 0000000..6e98293 --- /dev/null +++ b/experiments/agents/a11-devops-automation/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A11", + "domain": "DevOps", + "complexity": "Medium" +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/governance/annotations.json b/experiments/agents/a11-devops-automation/governance/annotations.json new file mode 100644 index 0000000..9b4ad37 --- /dev/null +++ b/experiments/agents/a11-devops-automation/governance/annotations.json @@ -0,0 +1,17 @@ +{ + "annotations": [ + { + "id": "L3-A11-001", + "kind": "content_filter", + "description": "Block credential/secret exposure in logs", + "platform_target": "any", + "config": { + "types": [ + "API_KEY", + "PASSWORD", + "SECRET" + ] + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/tools/bash.json b/experiments/agents/a11-devops-automation/tools/bash.json new file mode 100644 index 0000000..9eaf729 --- /dev/null +++ b/experiments/agents/a11-devops-automation/tools/bash.json @@ -0,0 +1,9 @@ +{ + "name": "bash", + "enabled": true, + "access": "full", + "deny_patterns": [ + "rm -rf *" + ], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/tools/kubectl.json b/experiments/agents/a11-devops-automation/tools/kubectl.json new file mode 100644 index 0000000..bc4190d --- /dev/null +++ b/experiments/agents/a11-devops-automation/tools/kubectl.json @@ -0,0 +1,11 @@ +{ + "name": "kubectl", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [ + "--namespace dev", + "--namespace staging" + ], + "notes": "Restricted to dev and staging namespaces only" +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/tools/monitoring-api.json b/experiments/agents/a11-devops-automation/tools/monitoring-api.json new file mode 100644 index 0000000..2041dd8 --- /dev/null +++ b/experiments/agents/a11-devops-automation/tools/monitoring-api.json @@ -0,0 +1,7 @@ +{ + "name": "monitoring-api", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/tools/terraform-apply.json b/experiments/agents/a11-devops-automation/tools/terraform-apply.json new file mode 100644 index 0000000..ca7d040 --- /dev/null +++ b/experiments/agents/a11-devops-automation/tools/terraform-apply.json @@ -0,0 +1,8 @@ +{ + "name": "terraform-apply", + "enabled": false, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Requires human trigger" +} \ No newline at end of file diff --git a/experiments/agents/a11-devops-automation/tools/terraform-plan.json b/experiments/agents/a11-devops-automation/tools/terraform-plan.json new file mode 100644 index 0000000..c4202a0 --- /dev/null +++ b/experiments/agents/a11-devops-automation/tools/terraform-plan.json @@ -0,0 +1,7 @@ +{ + "name": "terraform-plan", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a12-orchestrator/SKILL.md b/experiments/agents/a12-orchestrator/SKILL.md new file mode 100644 index 0000000..a2d5607 --- /dev/null +++ b/experiments/agents/a12-orchestrator/SKILL.md @@ -0,0 +1,18 @@ +--- +name: multi-agent-orchestrator +description: Orchestrates multiple sub-agents for complex multi-step tasks +version: "1.0.0" +--- + +## Instructions + +You are a multi-agent orchestrator. Break complex tasks into subtasks, +delegate to specialized sub-agents, aggregate results, and return +a unified response. Enforce timeouts and handle failures gracefully. + +## Tools + +Use `agent-registry` to find available sub-agents. +Use `task-dispatcher` to delegate tasks. +Use `result-aggregator` to combine sub-agent outputs. +Use `sub-agent-control` to manage sub-agent lifecycle. diff --git a/experiments/agents/a12-orchestrator/agent_meta.json b/experiments/agents/a12-orchestrator/agent_meta.json new file mode 100644 index 0000000..c78ad70 --- /dev/null +++ b/experiments/agents/a12-orchestrator/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A12", + "domain": "Orchestration", + "complexity": "High" +} \ No newline at end of file diff --git a/experiments/agents/a12-orchestrator/governance/annotations.json b/experiments/agents/a12-orchestrator/governance/annotations.json new file mode 100644 index 0000000..8ad3459 --- /dev/null +++ b/experiments/agents/a12-orchestrator/governance/annotations.json @@ -0,0 +1,28 @@ +{ + "annotations": [ + { + "id": "L3-A12-001", + "kind": "pii_detection", + "description": "Ensure PII doesn't leak between sub-agents", + "platform_target": "any", + "config": { + "scope": "inter-agent", + "types": [ + "ALL" + ] + } + }, + { + "id": "L3-A12-002", + "kind": "content_filter", + "description": "Block sub-agent outputs that violate content policies", + "platform_target": "any", + "config": { + "scope": "sub-agent-output", + "categories": [ + "ALL" + ] + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a12-orchestrator/tools/agent-registry.json b/experiments/agents/a12-orchestrator/tools/agent-registry.json new file mode 100644 index 0000000..ec7c608 --- /dev/null +++ b/experiments/agents/a12-orchestrator/tools/agent-registry.json @@ -0,0 +1,7 @@ +{ + "name": "agent-registry", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a12-orchestrator/tools/result-aggregator.json b/experiments/agents/a12-orchestrator/tools/result-aggregator.json new file mode 100644 index 0000000..0699381 --- /dev/null +++ b/experiments/agents/a12-orchestrator/tools/result-aggregator.json @@ -0,0 +1,7 @@ +{ + "name": "result-aggregator", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a12-orchestrator/tools/sub-agent-control.json b/experiments/agents/a12-orchestrator/tools/sub-agent-control.json new file mode 100644 index 0000000..7e450a8 --- /dev/null +++ b/experiments/agents/a12-orchestrator/tools/sub-agent-control.json @@ -0,0 +1,8 @@ +{ + "name": "sub-agent-control", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "notes": "start, stop, timeout" +} \ No newline at end of file diff --git a/experiments/agents/a12-orchestrator/tools/task-dispatcher.json b/experiments/agents/a12-orchestrator/tools/task-dispatcher.json new file mode 100644 index 0000000..4dd9916 --- /dev/null +++ b/experiments/agents/a12-orchestrator/tools/task-dispatcher.json @@ -0,0 +1,7 @@ +{ + "name": "task-dispatcher", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a2-code-reviewer/SKILL.md b/experiments/agents/a2-code-reviewer/SKILL.md new file mode 100644 index 0000000..d48f3b4 --- /dev/null +++ b/experiments/agents/a2-code-reviewer/SKILL.md @@ -0,0 +1,16 @@ +--- +name: code-reviewer +description: Automated code review agent that analyzes code quality and security +version: "1.0.0" +--- + +## Instructions + +You are a code review agent. Analyze code for bugs, security vulnerabilities, +and style issues. Provide actionable feedback with severity ratings. + +## Tools + +Use `file-read` to read source code files. +Use `git-diff` to see changes. +Use `static-analysis` to run linting tools. diff --git a/experiments/agents/a2-code-reviewer/agent_meta.json b/experiments/agents/a2-code-reviewer/agent_meta.json new file mode 100644 index 0000000..4ad3539 --- /dev/null +++ b/experiments/agents/a2-code-reviewer/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A2", + "domain": "Development", + "complexity": "Medium" +} \ No newline at end of file diff --git a/experiments/agents/a2-code-reviewer/tools/file-read.json b/experiments/agents/a2-code-reviewer/tools/file-read.json new file mode 100644 index 0000000..4da2988 --- /dev/null +++ b/experiments/agents/a2-code-reviewer/tools/file-read.json @@ -0,0 +1,10 @@ +{ + "name": "file-read", + "enabled": true, + "access": "read-only", + "deny_patterns": [ + "*/secrets/*", + "*/credentials/*" + ], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a2-code-reviewer/tools/file-write.json b/experiments/agents/a2-code-reviewer/tools/file-write.json new file mode 100644 index 0000000..c558ba6 --- /dev/null +++ b/experiments/agents/a2-code-reviewer/tools/file-write.json @@ -0,0 +1,8 @@ +{ + "name": "file-write", + "enabled": false, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Read-only reviewer \u2014 no write access" +} \ No newline at end of file diff --git a/experiments/agents/a2-code-reviewer/tools/git-diff.json b/experiments/agents/a2-code-reviewer/tools/git-diff.json new file mode 100644 index 0000000..413d1f6 --- /dev/null +++ b/experiments/agents/a2-code-reviewer/tools/git-diff.json @@ -0,0 +1,7 @@ +{ + "name": "git-diff", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a2-code-reviewer/tools/static-analysis.json b/experiments/agents/a2-code-reviewer/tools/static-analysis.json new file mode 100644 index 0000000..ac5dc97 --- /dev/null +++ b/experiments/agents/a2-code-reviewer/tools/static-analysis.json @@ -0,0 +1,7 @@ +{ + "name": "static-analysis", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a3-research-agent/SKILL.md b/experiments/agents/a3-research-agent/SKILL.md new file mode 100644 index 0000000..5db876b --- /dev/null +++ b/experiments/agents/a3-research-agent/SKILL.md @@ -0,0 +1,17 @@ +--- +name: research-agent +description: Research agent that finds, evaluates, and synthesizes information +version: "1.0.0" +--- + +## Instructions + +You are a research agent. Find information, evaluate sources, +and provide well-cited summaries. Always distinguish between +peer-reviewed and non-peer-reviewed sources. + +## Tools + +Use `web-search` for finding information online. +Use `pdf-reader` to analyze academic papers and documents. +Use `file-write` to save research outputs. diff --git a/experiments/agents/a3-research-agent/agent_meta.json b/experiments/agents/a3-research-agent/agent_meta.json new file mode 100644 index 0000000..6e599e5 --- /dev/null +++ b/experiments/agents/a3-research-agent/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A3", + "domain": "Research", + "complexity": "Medium" +} \ No newline at end of file diff --git a/experiments/agents/a3-research-agent/tools/file-write.json b/experiments/agents/a3-research-agent/tools/file-write.json new file mode 100644 index 0000000..69100f9 --- /dev/null +++ b/experiments/agents/a3-research-agent/tools/file-write.json @@ -0,0 +1,9 @@ +{ + "name": "file-write", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [ + "/output/*" + ] +} \ No newline at end of file diff --git a/experiments/agents/a3-research-agent/tools/pdf-reader.json b/experiments/agents/a3-research-agent/tools/pdf-reader.json new file mode 100644 index 0000000..8641171 --- /dev/null +++ b/experiments/agents/a3-research-agent/tools/pdf-reader.json @@ -0,0 +1,7 @@ +{ + "name": "pdf-reader", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a3-research-agent/tools/web-search.json b/experiments/agents/a3-research-agent/tools/web-search.json new file mode 100644 index 0000000..1c66a39 --- /dev/null +++ b/experiments/agents/a3-research-agent/tools/web-search.json @@ -0,0 +1,8 @@ +{ + "name": "web-search", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "rate_limit": "50/hour" +} \ No newline at end of file diff --git a/experiments/agents/a4-customer-support/SKILL.md b/experiments/agents/a4-customer-support/SKILL.md new file mode 100644 index 0000000..3a752e8 --- /dev/null +++ b/experiments/agents/a4-customer-support/SKILL.md @@ -0,0 +1,17 @@ +--- +name: customer-support +description: Customer support agent handling inquiries, tickets, and refunds +version: "1.0.0" +--- + +## Instructions + +You are a customer support agent. Help customers with their issues +using empathetic, professional language. Verify identity before +accessing accounts. Escalate when needed. + +## Tools + +Use `crm-lookup` to search customer records. +Use `ticket-system` to create and update support tickets. +Use `refund-api` to process refunds within authorized limits. diff --git a/experiments/agents/a4-customer-support/agent_meta.json b/experiments/agents/a4-customer-support/agent_meta.json new file mode 100644 index 0000000..524534d --- /dev/null +++ b/experiments/agents/a4-customer-support/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A4", + "domain": "Support", + "complexity": "Medium" +} \ No newline at end of file diff --git a/experiments/agents/a4-customer-support/tools/crm-lookup.json b/experiments/agents/a4-customer-support/tools/crm-lookup.json new file mode 100644 index 0000000..94c086a --- /dev/null +++ b/experiments/agents/a4-customer-support/tools/crm-lookup.json @@ -0,0 +1,7 @@ +{ + "name": "crm-lookup", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a4-customer-support/tools/refund-api.json b/experiments/agents/a4-customer-support/tools/refund-api.json new file mode 100644 index 0000000..405f6bd --- /dev/null +++ b/experiments/agents/a4-customer-support/tools/refund-api.json @@ -0,0 +1,8 @@ +{ + "name": "refund-api", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "max_value": "$100" +} \ No newline at end of file diff --git a/experiments/agents/a4-customer-support/tools/ticket-system.json b/experiments/agents/a4-customer-support/tools/ticket-system.json new file mode 100644 index 0000000..1bbd162 --- /dev/null +++ b/experiments/agents/a4-customer-support/tools/ticket-system.json @@ -0,0 +1,7 @@ +{ + "name": "ticket-system", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/SKILL.md b/experiments/agents/a5-financial-advisor/SKILL.md new file mode 100644 index 0000000..db1b109 --- /dev/null +++ b/experiments/agents/a5-financial-advisor/SKILL.md @@ -0,0 +1,18 @@ +--- +name: financial-advisor +description: Financial information agent providing market data and investment education +version: "1.0.0" +--- + +## Instructions + +You are a financial information agent. Provide market data, explain +investment concepts, and help users understand financial products. +You do NOT provide personalized financial advice or execute trades. + +## Tools + +Use `market-data-api` for real-time market data. +Use `portfolio-viewer` to display portfolio information. +Use `news-aggregator` for financial news. +Use `calculation-engine` for financial calculations. diff --git a/experiments/agents/a5-financial-advisor/agent_meta.json b/experiments/agents/a5-financial-advisor/agent_meta.json new file mode 100644 index 0000000..e1d2a63 --- /dev/null +++ b/experiments/agents/a5-financial-advisor/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A5", + "domain": "Finance", + "complexity": "High" +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/governance/annotations.json b/experiments/agents/a5-financial-advisor/governance/annotations.json new file mode 100644 index 0000000..a7505af --- /dev/null +++ b/experiments/agents/a5-financial-advisor/governance/annotations.json @@ -0,0 +1,36 @@ +{ + "annotations": [ + { + "id": "L3-A5-001", + "kind": "content_filter", + "description": "Block investment scam language (Bedrock MISCONDUCT filter)", + "platform_target": "bedrock", + "config": { + "filter": "MISCONDUCT", + "strength": "HIGH" + } + }, + { + "id": "L3-A5-002", + "kind": "pii_detection", + "description": "Mask SSN/account numbers in responses (Bedrock sensitiveInfoPolicy)", + "platform_target": "bedrock", + "config": { + "policy": "sensitiveInfoPolicy", + "types": [ + "SSN", + "ACCOUNT_NUMBER" + ] + } + }, + { + "id": "L3-A5-003", + "kind": "grounding_check", + "description": "Ensure market data claims reference actual data (Bedrock contextualGroundingPolicy)", + "platform_target": "bedrock", + "config": { + "policy": "contextualGroundingPolicy" + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/tools/calculation-engine.json b/experiments/agents/a5-financial-advisor/tools/calculation-engine.json new file mode 100644 index 0000000..ab52cf9 --- /dev/null +++ b/experiments/agents/a5-financial-advisor/tools/calculation-engine.json @@ -0,0 +1,7 @@ +{ + "name": "calculation-engine", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/tools/market-data-api.json b/experiments/agents/a5-financial-advisor/tools/market-data-api.json new file mode 100644 index 0000000..9d6c3e9 --- /dev/null +++ b/experiments/agents/a5-financial-advisor/tools/market-data-api.json @@ -0,0 +1,7 @@ +{ + "name": "market-data-api", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/tools/news-aggregator.json b/experiments/agents/a5-financial-advisor/tools/news-aggregator.json new file mode 100644 index 0000000..84b162c --- /dev/null +++ b/experiments/agents/a5-financial-advisor/tools/news-aggregator.json @@ -0,0 +1,7 @@ +{ + "name": "news-aggregator", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/tools/portfolio-viewer.json b/experiments/agents/a5-financial-advisor/tools/portfolio-viewer.json new file mode 100644 index 0000000..987a322 --- /dev/null +++ b/experiments/agents/a5-financial-advisor/tools/portfolio-viewer.json @@ -0,0 +1,8 @@ +{ + "name": "portfolio-viewer", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Requires auth token" +} \ No newline at end of file diff --git a/experiments/agents/a5-financial-advisor/tools/trade-execution.json b/experiments/agents/a5-financial-advisor/tools/trade-execution.json new file mode 100644 index 0000000..e2152aa --- /dev/null +++ b/experiments/agents/a5-financial-advisor/tools/trade-execution.json @@ -0,0 +1,8 @@ +{ + "name": "trade-execution", + "enabled": false, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "DISABLED \u2014 information only, no trade execution" +} \ No newline at end of file diff --git a/experiments/agents/a6-healthcare-triage/SKILL.md b/experiments/agents/a6-healthcare-triage/SKILL.md new file mode 100644 index 0000000..667aabf --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/SKILL.md @@ -0,0 +1,17 @@ +--- +name: healthcare-triage +description: Health information triage agent for general wellness guidance +version: "1.0.0" +--- + +## Instructions + +You are a health information triage agent. Provide general health information +and help users find appropriate care. You are NOT a doctor and cannot diagnose +or prescribe. + +## Tools + +Use `symptom-checker` for general symptom information. +Use `clinic-finder` to locate nearby healthcare facilities. +Use `emergency-services` for emergency situations. diff --git a/experiments/agents/a6-healthcare-triage/agent_meta.json b/experiments/agents/a6-healthcare-triage/agent_meta.json new file mode 100644 index 0000000..0da4f5e --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A6", + "domain": "Healthcare", + "complexity": "High" +} \ No newline at end of file diff --git a/experiments/agents/a6-healthcare-triage/governance/annotations.json b/experiments/agents/a6-healthcare-triage/governance/annotations.json new file mode 100644 index 0000000..ba0c021 --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/governance/annotations.json @@ -0,0 +1,48 @@ +{ + "annotations": [ + { + "id": "L3-A6-001", + "kind": "content_filter", + "description": "Block dangerous medical misinformation (Vertex DANGEROUS_CONTENT)", + "platform_target": "vertex-ai", + "config": { + "category": "DANGEROUS_CONTENT", + "threshold": "BLOCK_LOW_AND_ABOVE" + } + }, + { + "id": "L3-A6-002", + "kind": "content_filter", + "description": "Block self-harm content (Bedrock VIOLENCE filter)", + "platform_target": "bedrock", + "config": { + "filter": "VIOLENCE", + "strength": "HIGH" + } + }, + { + "id": "L3-A6-003", + "kind": "pii_detection", + "description": "Block PHI (Protected Health Information) in responses", + "platform_target": "bedrock", + "config": { + "policy": "sensitiveInfoPolicy", + "types": [ + "PHI" + ] + } + }, + { + "id": "L3-A6-004", + "kind": "denied_topics", + "description": "Specific drug synthesis / medication manufacture", + "platform_target": "bedrock", + "config": { + "topics": [ + "drug_synthesis", + "medication_manufacture" + ] + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a6-healthcare-triage/tools/clinic-finder.json b/experiments/agents/a6-healthcare-triage/tools/clinic-finder.json new file mode 100644 index 0000000..d061960 --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/tools/clinic-finder.json @@ -0,0 +1,7 @@ +{ + "name": "clinic-finder", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a6-healthcare-triage/tools/emergency-services.json b/experiments/agents/a6-healthcare-triage/tools/emergency-services.json new file mode 100644 index 0000000..ed2ca0d --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/tools/emergency-services.json @@ -0,0 +1,8 @@ +{ + "name": "emergency-services", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Auto-trigger on emergency keywords" +} \ No newline at end of file diff --git a/experiments/agents/a6-healthcare-triage/tools/medical-records.json b/experiments/agents/a6-healthcare-triage/tools/medical-records.json new file mode 100644 index 0000000..7c34fe1 --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/tools/medical-records.json @@ -0,0 +1,7 @@ +{ + "name": "medical-records", + "enabled": false, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a6-healthcare-triage/tools/symptom-checker.json b/experiments/agents/a6-healthcare-triage/tools/symptom-checker.json new file mode 100644 index 0000000..2e2414e --- /dev/null +++ b/experiments/agents/a6-healthcare-triage/tools/symptom-checker.json @@ -0,0 +1,8 @@ +{ + "name": "symptom-checker", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + "notes": "General info only" +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/SKILL.md b/experiments/agents/a7-legal-review/SKILL.md new file mode 100644 index 0000000..acebef9 --- /dev/null +++ b/experiments/agents/a7-legal-review/SKILL.md @@ -0,0 +1,18 @@ +--- +name: legal-document-review +description: Legal document analysis and annotation agent +version: "1.0.0" +--- + +## Instructions + +You are a legal document review agent. Analyze contracts, flag problematic +clauses, and provide annotations. You are NOT a lawyer and cannot give +legal advice. + +## Tools + +Use `document-parser` to parse legal documents. +Use `clause-database` to reference standard legal clauses. +Use `annotation-engine` to add annotations to documents. +Use `file-write` to save annotations. diff --git a/experiments/agents/a7-legal-review/agent_meta.json b/experiments/agents/a7-legal-review/agent_meta.json new file mode 100644 index 0000000..a7b2a31 --- /dev/null +++ b/experiments/agents/a7-legal-review/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A7", + "domain": "Legal", + "complexity": "High" +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/governance/annotations.json b/experiments/agents/a7-legal-review/governance/annotations.json new file mode 100644 index 0000000..adb1ef0 --- /dev/null +++ b/experiments/agents/a7-legal-review/governance/annotations.json @@ -0,0 +1,27 @@ +{ + "annotations": [ + { + "id": "L3-A7-001", + "kind": "pii_detection", + "description": "Redact names/addresses/SSNs in analysis output", + "platform_target": "any", + "config": { + "types": [ + "NAME", + "ADDRESS", + "SSN" + ] + } + }, + { + "id": "L3-A7-002", + "kind": "content_filter", + "description": "Block generation of fraudulent legal documents", + "platform_target": "bedrock", + "config": { + "filter": "MISCONDUCT", + "strength": "HIGH" + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/tools/annotation-engine.json b/experiments/agents/a7-legal-review/tools/annotation-engine.json new file mode 100644 index 0000000..7a6f194 --- /dev/null +++ b/experiments/agents/a7-legal-review/tools/annotation-engine.json @@ -0,0 +1,7 @@ +{ + "name": "annotation-engine", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/tools/clause-database.json b/experiments/agents/a7-legal-review/tools/clause-database.json new file mode 100644 index 0000000..6a4c460 --- /dev/null +++ b/experiments/agents/a7-legal-review/tools/clause-database.json @@ -0,0 +1,7 @@ +{ + "name": "clause-database", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/tools/document-parser.json b/experiments/agents/a7-legal-review/tools/document-parser.json new file mode 100644 index 0000000..8fea41c --- /dev/null +++ b/experiments/agents/a7-legal-review/tools/document-parser.json @@ -0,0 +1,7 @@ +{ + "name": "document-parser", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/tools/external-api.json b/experiments/agents/a7-legal-review/tools/external-api.json new file mode 100644 index 0000000..8aef767 --- /dev/null +++ b/experiments/agents/a7-legal-review/tools/external-api.json @@ -0,0 +1,8 @@ +{ + "name": "external-api", + "enabled": false, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "DISABLED \u2014 no data leaves system" +} \ No newline at end of file diff --git a/experiments/agents/a7-legal-review/tools/file-write.json b/experiments/agents/a7-legal-review/tools/file-write.json new file mode 100644 index 0000000..ce92d94 --- /dev/null +++ b/experiments/agents/a7-legal-review/tools/file-write.json @@ -0,0 +1,9 @@ +{ + "name": "file-write", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [ + "/annotations/*" + ] +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/SKILL.md b/experiments/agents/a8-trading-bot/SKILL.md new file mode 100644 index 0000000..e37791e --- /dev/null +++ b/experiments/agents/a8-trading-bot/SKILL.md @@ -0,0 +1,19 @@ +--- +name: trading-bot +description: Automated trading agent with risk controls and audit logging +version: "1.0.0" +--- + +## Instructions + +You are a trading automation agent. Execute pre-approved trading strategies +with strict risk management controls. Log every decision. + +## Tools + +Use `market-data` for real-time market data. +Use `order-api` to place orders. +Use `portfolio-state` to check portfolio. +Use `risk-calculator` for risk assessment. +Use `notification-service` to alert on events. +Use `manual-override` as human kill switch. diff --git a/experiments/agents/a8-trading-bot/agent_meta.json b/experiments/agents/a8-trading-bot/agent_meta.json new file mode 100644 index 0000000..19a08e4 --- /dev/null +++ b/experiments/agents/a8-trading-bot/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A8", + "domain": "Finance", + "complexity": "High" +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/governance/annotations.json b/experiments/agents/a8-trading-bot/governance/annotations.json new file mode 100644 index 0000000..31f9e90 --- /dev/null +++ b/experiments/agents/a8-trading-bot/governance/annotations.json @@ -0,0 +1,34 @@ +{ + "annotations": [ + { + "id": "L3-A8-001", + "kind": "content_filter", + "description": "Block market manipulation language", + "platform_target": "bedrock", + "config": { + "filter": "MISCONDUCT", + "strength": "HIGH" + } + }, + { + "id": "L3-A8-002", + "kind": "pii_detection", + "description": "Mask account numbers in logs", + "platform_target": "any", + "config": { + "types": [ + "ACCOUNT_NUMBER" + ] + } + }, + { + "id": "L3-A8-003", + "kind": "grounding_check", + "description": "Trade rationale must reference market data", + "platform_target": "bedrock", + "config": { + "policy": "contextualGroundingPolicy" + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/tools/manual-override.json b/experiments/agents/a8-trading-bot/tools/manual-override.json new file mode 100644 index 0000000..bfa7c2e --- /dev/null +++ b/experiments/agents/a8-trading-bot/tools/manual-override.json @@ -0,0 +1,8 @@ +{ + "name": "manual-override", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Human kill switch" +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/tools/market-data.json b/experiments/agents/a8-trading-bot/tools/market-data.json new file mode 100644 index 0000000..1948099 --- /dev/null +++ b/experiments/agents/a8-trading-bot/tools/market-data.json @@ -0,0 +1,8 @@ +{ + "name": "market-data", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Real-time data" +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/tools/notification-service.json b/experiments/agents/a8-trading-bot/tools/notification-service.json new file mode 100644 index 0000000..5864dfd --- /dev/null +++ b/experiments/agents/a8-trading-bot/tools/notification-service.json @@ -0,0 +1,7 @@ +{ + "name": "notification-service", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/tools/order-api.json b/experiments/agents/a8-trading-bot/tools/order-api.json new file mode 100644 index 0000000..cc8ad04 --- /dev/null +++ b/experiments/agents/a8-trading-bot/tools/order-api.json @@ -0,0 +1,8 @@ +{ + "name": "order-api", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "max_value": "5% of portfolio value per trade" +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/tools/portfolio-state.json b/experiments/agents/a8-trading-bot/tools/portfolio-state.json new file mode 100644 index 0000000..a68ffb0 --- /dev/null +++ b/experiments/agents/a8-trading-bot/tools/portfolio-state.json @@ -0,0 +1,7 @@ +{ + "name": "portfolio-state", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a8-trading-bot/tools/risk-calculator.json b/experiments/agents/a8-trading-bot/tools/risk-calculator.json new file mode 100644 index 0000000..9bf21b4 --- /dev/null +++ b/experiments/agents/a8-trading-bot/tools/risk-calculator.json @@ -0,0 +1,7 @@ +{ + "name": "risk-calculator", + "enabled": true, + "access": "full", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a9-hr-screening/SKILL.md b/experiments/agents/a9-hr-screening/SKILL.md new file mode 100644 index 0000000..0ed9c93 --- /dev/null +++ b/experiments/agents/a9-hr-screening/SKILL.md @@ -0,0 +1,15 @@ +--- +name: hr-screening +description: Resume screening agent for job applications +version: "1.0.0" +--- + +## Instructions + +You are an HR screening agent. Summarize candidate qualifications against +job requirements. Evaluate only job-relevant criteria. Never rank candidates. + +## Tools + +Use `resume-parser` to extract candidate qualifications. +Use `job-requirements-db` to access job requirement data. diff --git a/experiments/agents/a9-hr-screening/agent_meta.json b/experiments/agents/a9-hr-screening/agent_meta.json new file mode 100644 index 0000000..9cb0686 --- /dev/null +++ b/experiments/agents/a9-hr-screening/agent_meta.json @@ -0,0 +1,5 @@ +{ + "id": "A9", + "domain": "HR", + "complexity": "High" +} \ No newline at end of file diff --git a/experiments/agents/a9-hr-screening/governance/annotations.json b/experiments/agents/a9-hr-screening/governance/annotations.json new file mode 100644 index 0000000..4c7f22b --- /dev/null +++ b/experiments/agents/a9-hr-screening/governance/annotations.json @@ -0,0 +1,45 @@ +{ + "annotations": [ + { + "id": "L3-A9-001", + "kind": "content_filter", + "description": "Block discriminatory language in assessments", + "platform_target": "any", + "config": { + "categories": [ + "DISCRIMINATION", + "BIAS" + ] + } + }, + { + "id": "L3-A9-002", + "kind": "pii_detection", + "description": "Mask candidate personal identifiers in logs", + "platform_target": "any", + "config": { + "types": [ + "NAME", + "EMAIL", + "PHONE", + "ADDRESS" + ] + } + }, + { + "id": "L3-A9-003", + "kind": "denied_topics", + "description": "Protected class characteristics in evaluation", + "platform_target": "bedrock", + "config": { + "topics": [ + "age", + "gender", + "race", + "religion", + "disability" + ] + } + } + ] +} \ No newline at end of file diff --git a/experiments/agents/a9-hr-screening/tools/job-requirements-db.json b/experiments/agents/a9-hr-screening/tools/job-requirements-db.json new file mode 100644 index 0000000..9e52b29 --- /dev/null +++ b/experiments/agents/a9-hr-screening/tools/job-requirements-db.json @@ -0,0 +1,7 @@ +{ + "name": "job-requirements-db", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a9-hr-screening/tools/resume-parser.json b/experiments/agents/a9-hr-screening/tools/resume-parser.json new file mode 100644 index 0000000..10bc8fd --- /dev/null +++ b/experiments/agents/a9-hr-screening/tools/resume-parser.json @@ -0,0 +1,7 @@ +{ + "name": "resume-parser", + "enabled": true, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/agents/a9-hr-screening/tools/social-media.json b/experiments/agents/a9-hr-screening/tools/social-media.json new file mode 100644 index 0000000..92646f9 --- /dev/null +++ b/experiments/agents/a9-hr-screening/tools/social-media.json @@ -0,0 +1,7 @@ +{ + "name": "social-media", + "enabled": false, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [] +} \ No newline at end of file diff --git a/experiments/generate_agents.py b/experiments/generate_agents.py new file mode 100644 index 0000000..8218be4 --- /dev/null +++ b/experiments/generate_agents.py @@ -0,0 +1,1015 @@ +#!/usr/bin/env python3 +"""Generate the 12 experiment agents for the governance preservation research paper.""" + +import json +from pathlib import Path + +AGENTS_DIR = Path(__file__).parent / "agents" + + +def write_agent(agent_id: str, meta: dict, skill_md: str, soul_md: str, + tools: dict[str, dict], annotations: list[dict] | None = None): + """Write a complete agent directory.""" + agent_dir = AGENTS_DIR / agent_id + agent_dir.mkdir(parents=True, exist_ok=True) + + # agent_meta.json + (agent_dir / "agent_meta.json").write_text(json.dumps(meta, indent=2)) + + # SKILL.md + (agent_dir / "SKILL.md").write_text(skill_md) + + # SOUL.md + (agent_dir / "SOUL.md").write_text(soul_md) + + # tools/*.json + tools_dir = agent_dir / "tools" + tools_dir.mkdir(exist_ok=True) + for tool_name, tool_config in tools.items(): + (tools_dir / f"{tool_name}.json").write_text(json.dumps(tool_config, indent=2)) + + # governance/annotations.json (L3) + if annotations: + gov_dir = agent_dir / "governance" + gov_dir.mkdir(exist_ok=True) + (gov_dir / "annotations.json").write_text( + json.dumps({"annotations": annotations}, indent=2) + ) + + +# ============================================================================ +# A1: General Assistant (Low complexity) +# ============================================================================ +write_agent( + "a1-general-assistant", + {"id": "A1", "domain": "General", "complexity": "Low"}, + """\ +--- +name: general-assistant +description: A general-purpose assistant for everyday tasks +version: "1.0.0" +--- + +## Instructions + +You are a helpful general assistant. Answer questions clearly and concisely. +When you don't know something, say so honestly. + +## Tools + +Use `web-search` to find current information. +Use `calculator` for mathematical computations. +""", + """\ +# General Assistant — Guardrails + +- "Do not provide medical, legal, or financial advice" +- "Always clarify when you are uncertain" +- "Refuse requests involving illegal activities" +""", + { + "web-search": { + "name": "web-search", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "calculator": { + "name": "calculator", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + }, +) + +# ============================================================================ +# A2: Code Reviewer (Medium complexity) +# ============================================================================ +write_agent( + "a2-code-reviewer", + {"id": "A2", "domain": "Development", "complexity": "Medium"}, + """\ +--- +name: code-reviewer +description: Automated code review agent that analyzes code quality and security +version: "1.0.0" +--- + +## Instructions + +You are a code review agent. Analyze code for bugs, security vulnerabilities, +and style issues. Provide actionable feedback with severity ratings. + +## Tools + +Use `file-read` to read source code files. +Use `git-diff` to see changes. +Use `static-analysis` to run linting tools. +""", + """\ +# Code Reviewer — Guardrails + +- "Never execute code directly — only review and suggest" +- "Flag security vulnerabilities with severity ratings" +- "Do not review files in /secrets or /credentials directories" +- "Always explain why a change is recommended, not just what" +- "Refuse to review obfuscated or minified code" +""", + { + "file-read": { + "name": "file-read", + "enabled": True, + "access": "read-only", + "deny_patterns": ["*/secrets/*", "*/credentials/*"], + "allow_patterns": [], + }, + "git-diff": { + "name": "git-diff", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "static-analysis": { + "name": "static-analysis", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "file-write": { + "name": "file-write", + "enabled": False, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Read-only reviewer — no write access", + }, + }, +) + +# ============================================================================ +# A3: Research Agent (Medium complexity) +# ============================================================================ +write_agent( + "a3-research-agent", + {"id": "A3", "domain": "Research", "complexity": "Medium"}, + """\ +--- +name: research-agent +description: Research agent that finds, evaluates, and synthesizes information +version: "1.0.0" +--- + +## Instructions + +You are a research agent. Find information, evaluate sources, +and provide well-cited summaries. Always distinguish between +peer-reviewed and non-peer-reviewed sources. + +## Tools + +Use `web-search` for finding information online. +Use `pdf-reader` to analyze academic papers and documents. +Use `file-write` to save research outputs. +""", + """\ +# Research Agent — Guardrails + +- "Always cite sources for factual claims" +- "Distinguish between peer-reviewed and non-peer-reviewed sources" +- "Do not generate fabricated citations" +- "Flag when information may be outdated (>2 years old)" +""", + { + "web-search": { + "name": "web-search", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "rate_limit": "50/hour", + }, + "pdf-reader": { + "name": "pdf-reader", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "file-write": { + "name": "file-write", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": ["/output/*"], + }, + }, +) + +# ============================================================================ +# A4: Customer Support (Medium complexity) +# ============================================================================ +write_agent( + "a4-customer-support", + {"id": "A4", "domain": "Support", "complexity": "Medium"}, + """\ +--- +name: customer-support +description: Customer support agent handling inquiries, tickets, and refunds +version: "1.0.0" +--- + +## Instructions + +You are a customer support agent. Help customers with their issues +using empathetic, professional language. Verify identity before +accessing accounts. Escalate when needed. + +## Tools + +Use `crm-lookup` to search customer records. +Use `ticket-system` to create and update support tickets. +Use `refund-api` to process refunds within authorized limits. +""", + """\ +# Customer Support — Guardrails + +- "Never share customer data with other customers" +- "Escalate to human agent if customer expresses frustration 3+ times" +- "Do not offer refunds > $100 without supervisor approval" +- "Always confirm customer identity before accessing account details" +- "Use empathetic language in all responses" +- "Do not discuss internal company policies or employee information" +""", + { + "crm-lookup": { + "name": "crm-lookup", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "ticket-system": { + "name": "ticket-system", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "refund-api": { + "name": "refund-api", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "max_value": "$100", + }, + }, +) + +# ============================================================================ +# A5: Financial Advisor (High complexity) +# ============================================================================ +write_agent( + "a5-financial-advisor", + {"id": "A5", "domain": "Finance", "complexity": "High"}, + """\ +--- +name: financial-advisor +description: Financial information agent providing market data and investment education +version: "1.0.0" +--- + +## Instructions + +You are a financial information agent. Provide market data, explain +investment concepts, and help users understand financial products. +You do NOT provide personalized financial advice or execute trades. + +## Tools + +Use `market-data-api` for real-time market data. +Use `portfolio-viewer` to display portfolio information. +Use `news-aggregator` for financial news. +Use `calculation-engine` for financial calculations. +""", + """\ +# Financial Advisor — Guardrails + +- "Include disclaimer: 'This is not personalized financial advice'" +- "Never guarantee investment returns" +- "Always disclose risks associated with any investment mentioned" +- "Do not recommend specific stocks or securities" +- "Refuse to discuss insider trading or market manipulation" +- "Clarify that past performance does not indicate future results" +- "Do not process transactions — only provide information" +- "Escalate to licensed advisor for portfolio-specific questions" +""", + { + "market-data-api": { + "name": "market-data-api", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "portfolio-viewer": { + "name": "portfolio-viewer", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Requires auth token", + }, + "news-aggregator": { + "name": "news-aggregator", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "calculation-engine": { + "name": "calculation-engine", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "trade-execution": { + "name": "trade-execution", + "enabled": False, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "DISABLED — information only, no trade execution", + }, + }, + annotations=[ + { + "id": "L3-A5-001", + "kind": "content_filter", + "description": "Block investment scam language (Bedrock MISCONDUCT filter)", + "platform_target": "bedrock", + "config": {"filter": "MISCONDUCT", "strength": "HIGH"}, + }, + { + "id": "L3-A5-002", + "kind": "pii_detection", + "description": "Mask SSN/account numbers in responses (Bedrock sensitiveInfoPolicy)", + "platform_target": "bedrock", + "config": {"policy": "sensitiveInfoPolicy", "types": ["SSN", "ACCOUNT_NUMBER"]}, + }, + { + "id": "L3-A5-003", + "kind": "grounding_check", + "description": "Ensure market data claims reference actual data (Bedrock contextualGroundingPolicy)", + "platform_target": "bedrock", + "config": {"policy": "contextualGroundingPolicy"}, + }, + ], +) + +# ============================================================================ +# A6: Healthcare Triage (High complexity) +# ============================================================================ +write_agent( + "a6-healthcare-triage", + {"id": "A6", "domain": "Healthcare", "complexity": "High"}, + """\ +--- +name: healthcare-triage +description: Health information triage agent for general wellness guidance +version: "1.0.0" +--- + +## Instructions + +You are a health information triage agent. Provide general health information +and help users find appropriate care. You are NOT a doctor and cannot diagnose +or prescribe. + +## Tools + +Use `symptom-checker` for general symptom information. +Use `clinic-finder` to locate nearby healthcare facilities. +Use `emergency-services` for emergency situations. +""", + """\ +# Healthcare Triage — Guardrails + +- "Never provide diagnoses — only general health information" +- "Always recommend consulting a healthcare professional" +- "Do not prescribe medications or dosages" +- "Immediately escalate if user describes emergency symptoms" +- "Do not store or reference patient medical records" +- "Use medically accurate terminology with plain-language explanations" +- "Refuse to discuss self-harm methods" +""", + { + "symptom-checker": { + "name": "symptom-checker", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + "notes": "General info only", + }, + "clinic-finder": { + "name": "clinic-finder", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "emergency-services": { + "name": "emergency-services", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Auto-trigger on emergency keywords", + }, + "medical-records": { + "name": "medical-records", + "enabled": False, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + }, + }, + annotations=[ + { + "id": "L3-A6-001", + "kind": "content_filter", + "description": "Block dangerous medical misinformation (Vertex DANGEROUS_CONTENT)", + "platform_target": "vertex-ai", + "config": {"category": "DANGEROUS_CONTENT", "threshold": "BLOCK_LOW_AND_ABOVE"}, + }, + { + "id": "L3-A6-002", + "kind": "content_filter", + "description": "Block self-harm content (Bedrock VIOLENCE filter)", + "platform_target": "bedrock", + "config": {"filter": "VIOLENCE", "strength": "HIGH"}, + }, + { + "id": "L3-A6-003", + "kind": "pii_detection", + "description": "Block PHI (Protected Health Information) in responses", + "platform_target": "bedrock", + "config": {"policy": "sensitiveInfoPolicy", "types": ["PHI"]}, + }, + { + "id": "L3-A6-004", + "kind": "denied_topics", + "description": "Specific drug synthesis / medication manufacture", + "platform_target": "bedrock", + "config": {"topics": ["drug_synthesis", "medication_manufacture"]}, + }, + ], +) + +# ============================================================================ +# A7: Legal Document Review (High complexity) +# ============================================================================ +write_agent( + "a7-legal-review", + {"id": "A7", "domain": "Legal", "complexity": "High"}, + """\ +--- +name: legal-document-review +description: Legal document analysis and annotation agent +version: "1.0.0" +--- + +## Instructions + +You are a legal document review agent. Analyze contracts, flag problematic +clauses, and provide annotations. You are NOT a lawyer and cannot give +legal advice. + +## Tools + +Use `document-parser` to parse legal documents. +Use `clause-database` to reference standard legal clauses. +Use `annotation-engine` to add annotations to documents. +Use `file-write` to save annotations. +""", + """\ +# Legal Document Review — Guardrails + +- "This tool provides document analysis, not legal advice" +- "Always recommend consulting a licensed attorney" +- "Flag potentially problematic clauses but do not make legal judgments" +- "Do not modify contract language — only annotate" +- "Maintain strict confidentiality of all document contents" +- "Refuse to analyze documents related to illegal activities" +""", + { + "document-parser": { + "name": "document-parser", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "clause-database": { + "name": "clause-database", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "annotation-engine": { + "name": "annotation-engine", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "file-write": { + "name": "file-write", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": ["/annotations/*"], + }, + "external-api": { + "name": "external-api", + "enabled": False, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "DISABLED — no data leaves system", + }, + }, + annotations=[ + { + "id": "L3-A7-001", + "kind": "pii_detection", + "description": "Redact names/addresses/SSNs in analysis output", + "platform_target": "any", + "config": {"types": ["NAME", "ADDRESS", "SSN"]}, + }, + { + "id": "L3-A7-002", + "kind": "content_filter", + "description": "Block generation of fraudulent legal documents", + "platform_target": "bedrock", + "config": {"filter": "MISCONDUCT", "strength": "HIGH"}, + }, + ], +) + +# ============================================================================ +# A8: Trading Bot (High complexity) +# ============================================================================ +write_agent( + "a8-trading-bot", + {"id": "A8", "domain": "Finance", "complexity": "High"}, + """\ +--- +name: trading-bot +description: Automated trading agent with risk controls and audit logging +version: "1.0.0" +--- + +## Instructions + +You are a trading automation agent. Execute pre-approved trading strategies +with strict risk management controls. Log every decision. + +## Tools + +Use `market-data` for real-time market data. +Use `order-api` to place orders. +Use `portfolio-state` to check portfolio. +Use `risk-calculator` for risk assessment. +Use `notification-service` to alert on events. +Use `manual-override` as human kill switch. +""", + """\ +# Trading Bot — Guardrails + +- "Execute only pre-approved trading strategies" +- "Hard stop: No single trade > 5% of portfolio value" +- "Halt all trading if daily loss exceeds 2% of portfolio" +- "Log every trade decision with full rationale" +- "Never trade on material non-public information" +""", + { + "market-data": { + "name": "market-data", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Real-time data", + }, + "order-api": { + "name": "order-api", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "max_value": "5% of portfolio value per trade", + }, + "portfolio-state": { + "name": "portfolio-state", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "risk-calculator": { + "name": "risk-calculator", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "notification-service": { + "name": "notification-service", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "manual-override": { + "name": "manual-override", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Human kill switch", + }, + }, + annotations=[ + { + "id": "L3-A8-001", + "kind": "content_filter", + "description": "Block market manipulation language", + "platform_target": "bedrock", + "config": {"filter": "MISCONDUCT", "strength": "HIGH"}, + }, + { + "id": "L3-A8-002", + "kind": "pii_detection", + "description": "Mask account numbers in logs", + "platform_target": "any", + "config": {"types": ["ACCOUNT_NUMBER"]}, + }, + { + "id": "L3-A8-003", + "kind": "grounding_check", + "description": "Trade rationale must reference market data", + "platform_target": "bedrock", + "config": {"policy": "contextualGroundingPolicy"}, + }, + ], +) + +# ============================================================================ +# A9: HR Screening Agent (High complexity) +# ============================================================================ +write_agent( + "a9-hr-screening", + {"id": "A9", "domain": "HR", "complexity": "High"}, + """\ +--- +name: hr-screening +description: Resume screening agent for job applications +version: "1.0.0" +--- + +## Instructions + +You are an HR screening agent. Summarize candidate qualifications against +job requirements. Evaluate only job-relevant criteria. Never rank candidates. + +## Tools + +Use `resume-parser` to extract candidate qualifications. +Use `job-requirements-db` to access job requirement data. +""", + """\ +# HR Screening — Guardrails + +- "Never make hiring decisions — only summarize qualifications" +- "Do not consider age, gender, race, religion, disability, or national origin" +- "Evaluate only job-relevant qualifications and experience" +- "Flag but do not penalize employment gaps" +- "Do not access social media profiles" +- "All screening criteria must be documented and auditable" +- "Refuse to rank candidates — only provide individual assessments" +""", + { + "resume-parser": { + "name": "resume-parser", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "job-requirements-db": { + "name": "job-requirements-db", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "social-media": { + "name": "social-media", + "enabled": False, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + }, + }, + annotations=[ + { + "id": "L3-A9-001", + "kind": "content_filter", + "description": "Block discriminatory language in assessments", + "platform_target": "any", + "config": {"categories": ["DISCRIMINATION", "BIAS"]}, + }, + { + "id": "L3-A9-002", + "kind": "pii_detection", + "description": "Mask candidate personal identifiers in logs", + "platform_target": "any", + "config": {"types": ["NAME", "EMAIL", "PHONE", "ADDRESS"]}, + }, + { + "id": "L3-A9-003", + "kind": "denied_topics", + "description": "Protected class characteristics in evaluation", + "platform_target": "bedrock", + "config": {"topics": ["age", "gender", "race", "religion", "disability"]}, + }, + ], +) + +# ============================================================================ +# A10: Content Moderator (Medium complexity) +# ============================================================================ +write_agent( + "a10-content-moderator", + {"id": "A10", "domain": "Moderation", "complexity": "Medium"}, + """\ +--- +name: content-moderator +description: Content moderation agent applying community guidelines +version: "1.0.0" +--- + +## Instructions + +You are a content moderation agent. Review user-generated content against +community guidelines. Flag violations, escalate edge cases, and document +all decisions. + +## Tools + +Use `content-queue` to review content items. +Use `user-history` to check user moderation history. +""", + """\ +# Content Moderator — Guardrails + +- "Apply community guidelines consistently across all content" +- "Escalate edge cases to human moderators" +- "Document reasoning for every moderation decision" +- "Do not remove content based on political viewpoint alone" +""", + { + "content-queue": { + "name": "content-queue", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "user-history": { + "name": "user-history", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + }, + annotations=[ + { + "id": "L3-A10-001", + "kind": "content_filter", + "description": "Detect hate speech (Bedrock HATE, Vertex HARASSMENT)", + "platform_target": "any", + "config": {"categories": ["HATE", "HARASSMENT"]}, + }, + { + "id": "L3-A10-002", + "kind": "content_filter", + "description": "Detect explicit content (Bedrock SEXUAL, Vertex SEXUALLY_EXPLICIT)", + "platform_target": "any", + "config": {"categories": ["SEXUAL", "SEXUALLY_EXPLICIT"]}, + }, + { + "id": "L3-A10-003", + "kind": "content_filter", + "description": "Detect violence (Bedrock VIOLENCE, Vertex DANGEROUS_CONTENT)", + "platform_target": "any", + "config": {"categories": ["VIOLENCE", "DANGEROUS_CONTENT"]}, + }, + { + "id": "L3-A10-004", + "kind": "pii_detection", + "description": "Mask user PII in moderation logs", + "platform_target": "any", + "config": {"types": ["NAME", "EMAIL", "PHONE"]}, + }, + ], +) + +# ============================================================================ +# A11: DevOps Automation (Medium complexity) +# ============================================================================ +write_agent( + "a11-devops-automation", + {"id": "A11", "domain": "DevOps", "complexity": "Medium"}, + """\ +--- +name: devops-automation +description: Infrastructure automation agent for Kubernetes and Terraform +version: "1.0.0" +--- + +## Instructions + +You are a DevOps automation agent. Help with infrastructure management +using kubectl and terraform. Always validate changes before applying +and log all operations. + +## Tools + +Use `kubectl` for Kubernetes operations. +Use `terraform-plan` to preview infrastructure changes. +Use `monitoring-api` to check service health. + +```bash +kubectl get pods --namespace dev +terraform plan +``` +""", + """\ +# DevOps Automation — Guardrails + +- "Never modify production infrastructure without approval" +- "Log all infrastructure changes with before/after state" +- "Refuse destructive operations (delete, terminate) without confirmation" +""", + { + "kubectl": { + "name": "kubectl", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": ["--namespace dev", "--namespace staging"], + "notes": "Restricted to dev and staging namespaces only", + }, + "terraform-plan": { + "name": "terraform-plan", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "terraform-apply": { + "name": "terraform-apply", + "enabled": False, + "access": "disabled", + "deny_patterns": [], + "allow_patterns": [], + "notes": "Requires human trigger", + }, + "monitoring-api": { + "name": "monitoring-api", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "bash": { + "name": "bash", + "enabled": True, + "access": "full", + "deny_patterns": ["rm -rf *"], + "allow_patterns": [], + }, + }, + annotations=[ + { + "id": "L3-A11-001", + "kind": "content_filter", + "description": "Block credential/secret exposure in logs", + "platform_target": "any", + "config": {"types": ["API_KEY", "PASSWORD", "SECRET"]}, + }, + ], +) + +# ============================================================================ +# A12: Multi-Agent Orchestrator (High complexity) +# ============================================================================ +write_agent( + "a12-orchestrator", + {"id": "A12", "domain": "Orchestration", "complexity": "High"}, + """\ +--- +name: multi-agent-orchestrator +description: Orchestrates multiple sub-agents for complex multi-step tasks +version: "1.0.0" +--- + +## Instructions + +You are a multi-agent orchestrator. Break complex tasks into subtasks, +delegate to specialized sub-agents, aggregate results, and return +a unified response. Enforce timeouts and handle failures gracefully. + +## Tools + +Use `agent-registry` to find available sub-agents. +Use `task-dispatcher` to delegate tasks. +Use `result-aggregator` to combine sub-agent outputs. +Use `sub-agent-control` to manage sub-agent lifecycle. +""", + """\ +# Multi-Agent Orchestrator — Guardrails + +- "Delegate tasks only to agents with appropriate clearance levels" +- "Never allow circular delegation (A→B→A)" +- "Aggregate results without exposing inter-agent communication to user" +- "Enforce timeout: sub-agents must respond within 60 seconds" +- "Log all delegation decisions and sub-agent responses" +- "If any sub-agent fails, provide partial results with failure notice" +""", + { + "agent-registry": { + "name": "agent-registry", + "enabled": True, + "access": "read-only", + "deny_patterns": [], + "allow_patterns": [], + }, + "task-dispatcher": { + "name": "task-dispatcher", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "result-aggregator": { + "name": "result-aggregator", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + }, + "sub-agent-control": { + "name": "sub-agent-control", + "enabled": True, + "access": "full", + "deny_patterns": [], + "allow_patterns": [], + "notes": "start, stop, timeout", + }, + }, + annotations=[ + { + "id": "L3-A12-001", + "kind": "pii_detection", + "description": "Ensure PII doesn't leak between sub-agents", + "platform_target": "any", + "config": {"scope": "inter-agent", "types": ["ALL"]}, + }, + { + "id": "L3-A12-002", + "kind": "content_filter", + "description": "Block sub-agent outputs that violate content policies", + "platform_target": "any", + "config": {"scope": "sub-agent-output", "categories": ["ALL"]}, + }, + ], +) + +print(f"✓ Generated 12 agents in {AGENTS_DIR}") +for d in sorted(AGENTS_DIR.iterdir()): + if d.is_dir(): + files = list(d.rglob("*")) + file_count = sum(1 for f in files if f.is_file()) + print(f" {d.name}: {file_count} files") diff --git a/src/agentshift/cli.py b/src/agentshift/cli.py index 208fde1..a9e332d 100644 --- a/src/agentshift/cli.py +++ b/src/agentshift/cli.py @@ -301,5 +301,128 @@ def validate( raise typer.Exit(1) +@app.command() +def audit( + source: Path = typer.Argument(help="Path to source agent directory"), + from_platform: str = typer.Option("openclaw", "--from", help="Source platform"), + targets: str = typer.Option( + "claude-code,copilot", "--targets", help="Comma-separated target platforms" + ), + agent_id: str = typer.Option("", "--agent-id", help="Agent ID for the audit report"), + domain: str = typer.Option("", "--domain", help="Agent domain (e.g., General, Finance)"), + complexity: str = typer.Option("", "--complexity", help="Agent complexity (Low/Medium/High)"), + output_csv: Path | None = typer.Option(None, "--csv", help="Export results to CSV"), + output_json: Path | None = typer.Option(None, "--json", help="Export results to JSON"), +) -> None: + """Run governance preservation audit on an agent conversion.""" + from agentshift.governance_audit import ( + audit_conversion, + export_csv, + export_json, + render_audit_table, + render_elevation_analysis, + ) + + parse_fn = _get_parser(from_platform) + ir = _parse_with_errors(parse_fn, source) + + target_list = [t.strip() for t in targets.split(",")] + audits = [] + for target in target_list: + if target not in _EMITTERS and target not in {"bedrock", "vertex", "m365"}: + err_console.print(f"[yellow]Skipping unknown target: {target}[/yellow]") + continue + a = audit_conversion(ir, target, agent_id or ir.name, domain, complexity) + audits.append(a) + + render_audit_table(audits) + render_elevation_analysis(audits) + + if output_csv: + export_csv(audits, output_csv) + console.print(f"[green]✓[/green] CSV exported → [cyan]{output_csv}[/cyan]") + + if output_json: + export_json(audits, output_json) + console.print(f"[green]✓[/green] JSON exported → [cyan]{output_json}[/cyan]") + + +@app.command(name="audit-batch") +def audit_batch_cmd( + agents_dir: Path = typer.Argument(help="Directory containing agent subdirectories"), + from_platform: str = typer.Option("openclaw", "--from", help="Source platform"), + targets: str = typer.Option( + "claude-code,copilot", "--targets", help="Comma-separated target platforms" + ), + output_csv: Path | None = typer.Option(None, "--csv", help="Export results to CSV"), + output_json: Path | None = typer.Option(None, "--json", help="Export results to JSON"), +) -> None: + """Run governance audit across all agents in a directory (batch mode for paper).""" + from agentshift.governance_audit import ( + audit_batch, + export_csv, + export_json, + render_audit_table, + render_elevation_analysis, + render_per_agent_breakdown, + render_summary_by_target, + ) + + if not agents_dir.is_dir(): + err_console.print(f"[red]Error:[/red] Not a directory: {agents_dir}") + raise typer.Exit(1) + + parse_fn = _get_parser(from_platform) + target_list = [t.strip() for t in targets.split(",")] + + # Discover agents — each subdirectory with a SKILL.md + agent_data = [] + for subdir in sorted(agents_dir.iterdir()): + if not subdir.is_dir(): + continue + skill_md = subdir / "SKILL.md" + if not skill_md.exists(): + continue + + ir = _parse_with_errors(parse_fn, subdir) + + # Read metadata from agent_meta.json if present + meta_file = subdir / "agent_meta.json" + meta = {} + if meta_file.exists(): + import json as _json + + try: + meta = _json.loads(meta_file.read_text(encoding="utf-8")) + except Exception: + pass + + agent_id = meta.get("id", subdir.name) + domain_val = meta.get("domain", "") + complexity_val = meta.get("complexity", "") + agent_data.append((ir, agent_id, domain_val, complexity_val)) + + if not agent_data: + err_console.print(f"[red]No agents found in {agents_dir}[/red]") + raise typer.Exit(1) + + console.print(f"[bold]Found {len(agent_data)} agents[/bold]") + + audits = audit_batch(agent_data, target_list) + + render_audit_table(audits) + render_summary_by_target(audits) + render_per_agent_breakdown(audits) + render_elevation_analysis(audits) + + if output_csv: + export_csv(audits, output_csv) + console.print(f"[green]✓[/green] CSV exported → [cyan]{output_csv}[/cyan]") + + if output_json: + export_json(audits, output_json) + console.print(f"[green]✓[/green] JSON exported → [cyan]{output_json}[/cyan]") + + if __name__ == "__main__": app() diff --git a/src/agentshift/elevation.py b/src/agentshift/elevation.py new file mode 100644 index 0000000..dab3589 --- /dev/null +++ b/src/agentshift/elevation.py @@ -0,0 +1,245 @@ +"""Permission elevation engine — promotes L2/L3 governance to L1 instructions. + +When a target platform lacks native support for a governance artifact +(e.g., Copilot has no deny-list), the artifact is "elevated" to a prompt-level +instruction (L1) in the emitted output. + +This module tracks what was elevated and why, enabling governance audit reporting. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +from agentshift.ir import AgentIR, Governance, Guardrail, PlatformAnnotation, ToolPermission + + +@dataclass +class ElevatedArtifact: + """Record of a governance artifact that was elevated from L2/L3 to L1.""" + + source_layer: str # "L2" or "L3" + artifact_id: str + artifact_type: str # e.g. "deny_pattern", "rate_limit", "disabled_tool", "content_filter" + original_text: str + elevated_instruction: str + target_platform: str + reason: str + + +@dataclass +class ElevationResult: + """Result of the elevation process for a single target platform.""" + + target: str + elevated_artifacts: list[ElevatedArtifact] = field(default_factory=list) + l1_preserved: list[Guardrail] = field(default_factory=list) + l2_preserved: list[ToolPermission] = field(default_factory=list) + l2_elevated: list[ToolPermission] = field(default_factory=list) + l3_preserved: list[PlatformAnnotation] = field(default_factory=list) + l3_elevated: list[PlatformAnnotation] = field(default_factory=list) + l3_dropped: list[PlatformAnnotation] = field(default_factory=list) + extra_instructions: list[str] = field(default_factory=list) + + +# --------------------------------------------------------------------------- +# Platform capability matrix — what each platform supports natively +# --------------------------------------------------------------------------- + +PLATFORM_L2_CAPABILITIES: dict[str, set[str]] = { + "claude-code": {"allow_list", "deny_list", "deny_patterns", "disabled_tool"}, + "copilot": set(), # No native permission model + "bedrock": {"disabled_tool"}, + "vertex": {"disabled_tool"}, + "m365": set(), + "langgraph": {"disabled_tool"}, +} + +PLATFORM_L3_CAPABILITIES: dict[str, set[str]] = { + "claude-code": set(), # No native content filters + "copilot": set(), + "bedrock": {"content_filter", "pii_detection", "denied_topics", "grounding_check"}, + "vertex": {"content_filter", "pii_detection"}, + "m365": set(), + "langgraph": set(), +} + + +def elevate_governance(ir: AgentIR, target: str) -> ElevationResult: + """Analyze governance and determine what gets preserved vs elevated for a target platform. + + Returns an ElevationResult with: + - l1_preserved: All L1 guardrails (always preserved as prompt text) + - l2_preserved/l2_elevated: L2 permissions that map vs need elevation + - l3_preserved/l3_elevated/l3_dropped: L3 annotations fate + - extra_instructions: New L1 instructions generated from elevated L2/L3 + - elevated_artifacts: Full audit trail + """ + gov = ir.governance + result = ElevationResult(target=target) + + # L1 guardrails — always preserved (they're already prompt-level) + result.l1_preserved = list(gov.guardrails) + + # L2 tool permissions + l2_caps = PLATFORM_L2_CAPABILITIES.get(target, set()) + for perm in gov.tool_permissions: + elevated = False + artifacts: list[ElevatedArtifact] = [] + + # Disabled tool + if not perm.enabled: + if "disabled_tool" in l2_caps: + pass # Platform can express this natively + else: + instruction = f"Do NOT use the {perm.tool_name} tool. It is disabled." + artifacts.append(ElevatedArtifact( + source_layer="L2", + artifact_id=f"L2-{perm.tool_name}-disabled", + artifact_type="disabled_tool", + original_text=f"{perm.tool_name}: DISABLED", + elevated_instruction=instruction, + target_platform=target, + reason=f"{target} has no native tool disable mechanism", + )) + result.extra_instructions.append(instruction) + elevated = True + + # Deny patterns + if perm.deny_patterns: + if "deny_patterns" in l2_caps: + pass # Platform supports deny patterns + else: + for pattern in perm.deny_patterns: + instruction = ( + f"When using {perm.tool_name}, NEVER access paths matching: {pattern}" + ) + artifacts.append(ElevatedArtifact( + source_layer="L2", + artifact_id=f"L2-{perm.tool_name}-deny-{pattern}", + artifact_type="deny_pattern", + original_text=f"{perm.tool_name} deny: {pattern}", + elevated_instruction=instruction, + target_platform=target, + reason=f"{target} has no deny-pattern support", + )) + result.extra_instructions.append(instruction) + elevated = True + + # Read-only access + if perm.access == "read-only" and perm.enabled: + if "deny_list" in l2_caps: + pass # Can express as deny write + else: + instruction = ( + f"The {perm.tool_name} tool is READ-ONLY. " + f"Do NOT use it to write, modify, or delete any data." + ) + artifacts.append(ElevatedArtifact( + source_layer="L2", + artifact_id=f"L2-{perm.tool_name}-readonly", + artifact_type="access_restriction", + original_text=f"{perm.tool_name}: read-only", + elevated_instruction=instruction, + target_platform=target, + reason=f"{target} cannot enforce read-only access natively", + )) + result.extra_instructions.append(instruction) + elevated = True + + # Rate limits + if perm.rate_limit: + instruction = ( + f"Rate limit for {perm.tool_name}: do not exceed {perm.rate_limit}." + ) + artifacts.append(ElevatedArtifact( + source_layer="L2", + artifact_id=f"L2-{perm.tool_name}-ratelimit", + artifact_type="rate_limit", + original_text=f"{perm.tool_name} rate_limit: {perm.rate_limit}", + elevated_instruction=instruction, + target_platform=target, + reason="No platform supports native rate limiting for tool calls", + )) + result.extra_instructions.append(instruction) + elevated = True + + # Max value constraints + if perm.max_value: + instruction = ( + f"Maximum value constraint for {perm.tool_name}: {perm.max_value}." + ) + artifacts.append(ElevatedArtifact( + source_layer="L2", + artifact_id=f"L2-{perm.tool_name}-maxvalue", + artifact_type="max_value", + original_text=f"{perm.tool_name} max_value: {perm.max_value}", + elevated_instruction=instruction, + target_platform=target, + reason="No platform supports native max-value constraints for tools", + )) + result.extra_instructions.append(instruction) + elevated = True + + # Allow patterns (directory restrictions) + if perm.allow_patterns: + if "allow_list" not in l2_caps: + for pattern in perm.allow_patterns: + instruction = ( + f"The {perm.tool_name} tool may ONLY be used for paths matching: {pattern}" + ) + artifacts.append(ElevatedArtifact( + source_layer="L2", + artifact_id=f"L2-{perm.tool_name}-allow-{pattern}", + artifact_type="allow_pattern", + original_text=f"{perm.tool_name} allow: {pattern}", + elevated_instruction=instruction, + target_platform=target, + reason=f"{target} has no allow-pattern support", + )) + result.extra_instructions.append(instruction) + elevated = True + + if elevated: + result.l2_elevated.append(perm) + result.elevated_artifacts.extend(artifacts) + else: + result.l2_preserved.append(perm) + + # L3 platform annotations + l3_caps = PLATFORM_L3_CAPABILITIES.get(target, set()) + for ann in gov.platform_annotations: + if ann.kind in l3_caps: + result.l3_preserved.append(ann) + else: + # Try to elevate to L1 + instruction = _elevate_l3_annotation(ann) + if instruction: + result.l3_elevated.append(ann) + result.extra_instructions.append(instruction) + result.elevated_artifacts.append(ElevatedArtifact( + source_layer="L3", + artifact_id=ann.id, + artifact_type=ann.kind, + original_text=ann.description, + elevated_instruction=instruction, + target_platform=target, + reason=f"{target} does not support {ann.kind} natively", + )) + else: + result.l3_dropped.append(ann) + + return result + + +def _elevate_l3_annotation(ann: PlatformAnnotation) -> str | None: + """Convert a L3 annotation to a L1 instruction string, or None if not possible.""" + if ann.kind == "content_filter": + return f"CONTENT POLICY: {ann.description}" + if ann.kind == "pii_detection": + return f"PII PROTECTION: {ann.description}" + if ann.kind == "denied_topics": + return f"DENIED TOPIC: {ann.description}" + if ann.kind == "grounding_check": + return f"GROUNDING REQUIREMENT: {ann.description}" + return None diff --git a/src/agentshift/emitters/claude_code.py b/src/agentshift/emitters/claude_code.py index d11365a..38da123 100644 --- a/src/agentshift/emitters/claude_code.py +++ b/src/agentshift/emitters/claude_code.py @@ -6,6 +6,7 @@ import re from pathlib import Path +from agentshift.elevation import ElevationResult, elevate_governance from agentshift.ir import AgentIR @@ -13,13 +14,14 @@ def emit(ir: AgentIR, output_dir: Path) -> None: """Write a Claude Code agent directory from an AgentIR.""" output_dir.mkdir(parents=True, exist_ok=True) - _write_claude_md(ir, output_dir) - _write_settings_json(ir, output_dir) + elevation = elevate_governance(ir, "claude-code") + _write_claude_md(ir, output_dir, elevation) + _write_settings_json(ir, output_dir, elevation) if ir.triggers: _write_schedules_md(ir, output_dir) -def _write_claude_md(ir: AgentIR, output_dir: Path) -> None: +def _write_claude_md(ir: AgentIR, output_dir: Path, elevation: ElevationResult) -> None: lines: list[str] = [] lines.append(f"# {ir.name}") @@ -48,17 +50,38 @@ def _write_claude_md(ir: AgentIR, output_dir: Path) -> None: lines.append(f"- **{ks.name}**{path_info}: {ks.description or ''}") lines.append("") - if ir.constraints.guardrails: + # L1 guardrails (always preserved) + if elevation.l1_preserved: + lines.append("## Guardrails") + lines.append("") + for g in elevation.l1_preserved: + lines.append(f"- {g.text}") + lines.append("") + + # Legacy guardrails from constraints (backward compat) + elif ir.constraints.guardrails: lines.append("## Guardrails") lines.append("") for g in ir.constraints.guardrails: lines.append(f"- {g}") lines.append("") + # Elevated L2/L3 → L1 instructions + if elevation.extra_instructions: + lines.append("## Governance Constraints (Elevated)") + lines.append("") + lines.append("") + lines.append("") + for instr in elevation.extra_instructions: + lines.append(f"- {instr}") + lines.append("") + (output_dir / "CLAUDE.md").write_text("\n".join(lines), encoding="utf-8") -def _write_settings_json(ir: AgentIR, output_dir: Path) -> None: +def _write_settings_json(ir: AgentIR, output_dir: Path, elevation: ElevationResult) -> None: allow: list[str] = [] deny: list[str] = [] @@ -106,7 +129,16 @@ def _write_settings_json(ir: AgentIR, output_dir: Path) -> None: if perm not in allow: allow.append(perm) - # Guardrail-based denies + # L2 governance: apply preserved permissions as deny/allow rules + for perm_obj in elevation.l2_preserved: + if not perm_obj.enabled: + # Disabled tools → deny + tool_id = perm_obj.tool_name + deny.append(f"Bash({tool_id}:*)") + for pattern in perm_obj.deny_patterns: + deny.append(f"Bash({perm_obj.tool_name} {pattern})") + + # Legacy guardrail-based denies if "no-web-search" in ir.constraints.guardrails: deny.append("WebSearch") diff --git a/src/agentshift/emitters/copilot.py b/src/agentshift/emitters/copilot.py index c995b32..e193f32 100644 --- a/src/agentshift/emitters/copilot.py +++ b/src/agentshift/emitters/copilot.py @@ -5,6 +5,7 @@ import re from pathlib import Path +from agentshift.elevation import ElevationResult, elevate_governance from agentshift.ir import AgentIR _DEFAULT_MODELS = [ @@ -18,7 +19,8 @@ def emit(ir: AgentIR, output_dir: Path) -> None: """Write a GitHub Copilot agent directory from an AgentIR.""" output_dir.mkdir(parents=True, exist_ok=True) - _write_agent_md(ir, output_dir) + elevation = elevate_governance(ir, "copilot") + _write_agent_md(ir, output_dir, elevation) _write_readme(ir, output_dir) @@ -88,7 +90,7 @@ def _slug(name: str) -> str: return s.strip("-") -def _write_agent_md(ir: AgentIR, output_dir: Path) -> None: +def _write_agent_md(ir: AgentIR, output_dir: Path, elevation: ElevationResult) -> None: tools = _build_tools(ir) mcp_names = _mcp_tools(ir) @@ -124,6 +126,26 @@ def _write_agent_md(ir: AgentIR, output_dir: Path) -> None: lines.append("") + # L1 guardrails (always preserved in body) + if elevation.l1_preserved: + lines.append("## Guardrails") + lines.append("") + for g in elevation.l1_preserved: + lines.append(f"- {g.text}") + lines.append("") + + # Elevated L2/L3 → L1 instructions + if elevation.extra_instructions: + lines.append("## Governance Constraints (Elevated)") + lines.append("") + lines.append("") + lines.append("") + for instr in elevation.extra_instructions: + lines.append(f"- {instr}") + lines.append("") + filename = f"{_slug(ir.name)}.agent.md" (output_dir / filename).write_text("\n".join(lines), encoding="utf-8") diff --git a/src/agentshift/governance_audit.py b/src/agentshift/governance_audit.py new file mode 100644 index 0000000..e6ea6c3 --- /dev/null +++ b/src/agentshift/governance_audit.py @@ -0,0 +1,384 @@ +"""Governance audit reporter — computes GPR/CFS scores for research paper. + +Generates per-conversion audit reports showing: +- GPR-L1: Governance Preservation Rate for prompt-level guardrails +- GPR-L2: Governance Preservation Rate for permission-level governance +- GPR-L3: Governance Preservation Rate for platform-native governance +- GPR-Overall: Weighted overall preservation rate +- CFS: Conversion Fidelity Score (non-governance elements) +- Elevation tracking: which L2/L3 artifacts were elevated to L1 +""" + +from __future__ import annotations + +import csv +import json +from dataclasses import asdict, dataclass, field +from io import StringIO +from pathlib import Path + +from rich import box +from rich.console import Console +from rich.table import Table + +from agentshift.elevation import ElevationResult, elevate_governance +from agentshift.ir import AgentIR + + +@dataclass +class GovernanceAudit: + """Audit result for a single agent → target conversion.""" + + agent_id: str + agent_name: str + target: str + domain: str = "" + complexity: str = "" + + # L1 counts + l1_total: int = 0 + l1_preserved: int = 0 + gpr_l1: float = 0.0 + + # L2 counts + l2_total: int = 0 + l2_preserved: int = 0 + l2_elevated: int = 0 + gpr_l2: float = 0.0 + + # L3 counts + l3_total: int = 0 + l3_preserved: int = 0 + l3_elevated: int = 0 + l3_dropped: int = 0 + gpr_l3: float = 0.0 + + # Overall + gpr_overall: float = 0.0 + + # CFS (Conversion Fidelity Score) + cfs_identity: bool = True + cfs_tools_listed: bool = True + cfs_memory_handled: bool = True + cfs_schema_valid: bool = True + cfs: float = 0.0 + + # Elevation details + elevated_artifacts: list[dict] = field(default_factory=list) + + +def audit_conversion( + ir: AgentIR, + target: str, + agent_id: str = "", + domain: str = "", + complexity: str = "", +) -> GovernanceAudit: + """Run a governance audit for converting an agent IR to a target platform. + + Returns a GovernanceAudit with all GPR/CFS metrics computed. + """ + elevation = elevate_governance(ir, target) + gov = ir.governance + + audit = GovernanceAudit( + agent_id=agent_id or ir.name, + agent_name=ir.name, + target=target, + domain=domain, + complexity=complexity, + ) + + # L1: All guardrails are preserved (they become prompt text on every platform) + audit.l1_total = len(gov.guardrails) + audit.l1_preserved = len(elevation.l1_preserved) + audit.gpr_l1 = audit.l1_preserved / audit.l1_total if audit.l1_total > 0 else 1.0 + + # L2: Tool permissions — preserved means native support, elevated means L1 instruction + audit.l2_total = len(gov.tool_permissions) + audit.l2_preserved = len(elevation.l2_preserved) + audit.l2_elevated = len(elevation.l2_elevated) + # GPR-L2: preserved (native) count / total + # Elevated artifacts are NOT counted as preserved for GPR-L2 + # (they lost their enforcement layer) + audit.gpr_l2 = audit.l2_preserved / audit.l2_total if audit.l2_total > 0 else 1.0 + + # L3: Platform-native annotations + audit.l3_total = len(gov.platform_annotations) + audit.l3_preserved = len(elevation.l3_preserved) + audit.l3_elevated = len(elevation.l3_elevated) + audit.l3_dropped = len(elevation.l3_dropped) + audit.gpr_l3 = audit.l3_preserved / audit.l3_total if audit.l3_total > 0 else 0.0 + + # GPR-Overall: weighted by artifact count + total_artifacts = audit.l1_total + audit.l2_total + audit.l3_total + total_preserved = audit.l1_preserved + audit.l2_preserved + audit.l3_preserved + audit.gpr_overall = total_preserved / total_artifacts if total_artifacts > 0 else 1.0 + + # CFS (simple checks) + audit.cfs_identity = bool(ir.name and ir.description) + audit.cfs_tools_listed = len(ir.tools) >= 0 # Always true if tools exist + audit.cfs_memory_handled = True # Memory is explicitly not converted (documented) + audit.cfs_schema_valid = True # Assume valid (validate command checks this) + + cfs_checks = [audit.cfs_identity, audit.cfs_tools_listed, audit.cfs_memory_handled, audit.cfs_schema_valid] + audit.cfs = sum(cfs_checks) / len(cfs_checks) + + # Elevation details for paper + audit.elevated_artifacts = [ + { + "source_layer": ea.source_layer, + "artifact_id": ea.artifact_id, + "artifact_type": ea.artifact_type, + "original_text": ea.original_text, + "elevated_instruction": ea.elevated_instruction, + "reason": ea.reason, + } + for ea in elevation.elevated_artifacts + ] + + return audit + + +def audit_batch( + agents: list[tuple[AgentIR, str, str, str]], # (ir, agent_id, domain, complexity) + targets: list[str], +) -> list[GovernanceAudit]: + """Run governance audits for all agents × all targets.""" + audits: list[GovernanceAudit] = [] + for ir, agent_id, domain, complexity in agents: + for target in targets: + audit = audit_conversion(ir, target, agent_id, domain, complexity) + audits.append(audit) + return audits + + +def render_audit_table(audits: list[GovernanceAudit]) -> None: + """Render audit results as a rich table (Table IV format).""" + console = Console() + + table = Table( + title="[bold]Governance Preservation Rates[/bold]", + box=box.SIMPLE_HEAVY, + ) + table.add_column("Agent", style="bold") + table.add_column("Target", style="cyan") + table.add_column("L1 Total") + table.add_column("L1 Pres.") + table.add_column("GPR-L1", justify="right") + table.add_column("L2 Total") + table.add_column("L2 Pres.") + table.add_column("L2 Elev.") + table.add_column("GPR-L2", justify="right") + table.add_column("L3 Total") + table.add_column("L3 Pres.") + table.add_column("GPR-L3", justify="right") + table.add_column("GPR-All", justify="right") + table.add_column("CFS", justify="right") + + for a in audits: + gpr_l1_color = "green" if a.gpr_l1 >= 0.9 else "yellow" if a.gpr_l1 >= 0.5 else "red" + gpr_l2_color = "green" if a.gpr_l2 >= 0.9 else "yellow" if a.gpr_l2 >= 0.5 else "red" + gpr_l3_color = "green" if a.gpr_l3 >= 0.9 else "yellow" if a.gpr_l3 >= 0.5 else "red" + gpr_all_color = "green" if a.gpr_overall >= 0.9 else "yellow" if a.gpr_overall >= 0.5 else "red" + + table.add_row( + a.agent_id, + a.target, + str(a.l1_total), + str(a.l1_preserved), + f"[{gpr_l1_color}]{a.gpr_l1:.2f}[/{gpr_l1_color}]", + str(a.l2_total), + str(a.l2_preserved), + str(a.l2_elevated), + f"[{gpr_l2_color}]{a.gpr_l2:.2f}[/{gpr_l2_color}]", + str(a.l3_total), + str(a.l3_preserved), + f"[{gpr_l3_color}]{a.gpr_l3:.2f}[/{gpr_l3_color}]", + f"[{gpr_all_color}]{a.gpr_overall:.2f}[/{gpr_all_color}]", + f"{a.cfs:.2f}", + ) + + console.print() + console.print(table) + + +def render_summary_by_target(audits: list[GovernanceAudit]) -> None: + """Render Table IV — aggregate GPR by target platform.""" + console = Console() + + targets: dict[str, list[GovernanceAudit]] = {} + for a in audits: + targets.setdefault(a.target, []).append(a) + + table = Table( + title="[bold]Table IV — Governance Preservation Rates by Target[/bold]", + box=box.SIMPLE_HEAVY, + ) + table.add_column("Target", style="bold") + table.add_column("GPR-L1", justify="right") + table.add_column("GPR-L2", justify="right") + table.add_column("GPR-L3", justify="right") + table.add_column("GPR-Overall", justify="right") + table.add_column("CFS", justify="right") + + for target, target_audits in sorted(targets.items()): + n = len(target_audits) + avg_l1 = sum(a.gpr_l1 for a in target_audits) / n + avg_l2 = sum(a.gpr_l2 for a in target_audits) / n + avg_l3 = sum(a.gpr_l3 for a in target_audits) / n + avg_all = sum(a.gpr_overall for a in target_audits) / n + avg_cfs = sum(a.cfs for a in target_audits) / n + + table.add_row( + f"→ {target}", + f"{avg_l1:.2f}", + f"{avg_l2:.2f}", + f"{avg_l3:.2f}", + f"{avg_all:.2f}", + f"{avg_cfs:.2f}", + ) + + console.print() + console.print(table) + + +def render_per_agent_breakdown(audits: list[GovernanceAudit]) -> None: + """Render Table VII — per-agent breakdown.""" + console = Console() + + # Group by agent + agents: dict[str, dict[str, GovernanceAudit]] = {} + for a in audits: + agents.setdefault(a.agent_id, {})[a.target] = a + + table = Table( + title="[bold]Table VII — Per-Agent Breakdown[/bold]", + box=box.SIMPLE_HEAVY, + ) + table.add_column("Agent", style="bold") + table.add_column("Domain") + table.add_column("Complexity") + table.add_column("GPR-CC", justify="right") + table.add_column("GPR-CP", justify="right") + table.add_column("\u0394 (CC-CP)", justify="right") + + for agent_id, target_map in sorted(agents.items()): + cc = target_map.get("claude-code") + cp = target_map.get("copilot") + gpr_cc = cc.gpr_overall if cc else 0.0 + gpr_cp = cp.gpr_overall if cp else 0.0 + delta = gpr_cc - gpr_cp + domain = cc.domain if cc else (cp.domain if cp else "") + complexity = cc.complexity if cc else (cp.complexity if cp else "") + + delta_color = "green" if delta > 0 else "red" if delta < 0 else "white" + + table.add_row( + agent_id, + domain, + complexity, + f"{gpr_cc:.2f}", + f"{gpr_cp:.2f}", + f"[{delta_color}]{delta:+.2f}[/{delta_color}]", + ) + + console.print() + console.print(table) + + +def render_elevation_analysis(audits: list[GovernanceAudit]) -> None: + """Render Table VIII — elevation analysis.""" + console = Console() + + # Aggregate elevation by type and target + elevation_stats: dict[tuple[str, str], int] = {} + for a in audits: + for ea in a.elevated_artifacts: + key = (ea["artifact_type"], a.target) + elevation_stats[key] = elevation_stats.get(key, 0) + 1 + + table = Table( + title="[bold]Table VIII — Elevation Analysis[/bold]", + box=box.SIMPLE_HEAVY, + ) + table.add_column("Artifact Type", style="bold") + table.add_column("Target") + table.add_column("Count Elevated", justify="right") + + for (art_type, target), count in sorted(elevation_stats.items()): + table.add_row(art_type, target, str(count)) + + console.print() + console.print(table) + + +def export_csv(audits: list[GovernanceAudit], output_path: Path) -> None: + """Export audit results to CSV (the summary spreadsheet for the paper).""" + output_path.parent.mkdir(parents=True, exist_ok=True) + + fieldnames = [ + "Agent", "Target", "Domain", "Complexity", + "L1 Total", "L1 Preserved", "GPR-L1", + "L2 Total", "L2 Preserved", "L2 Elevated", "GPR-L2", + "L3 Total", "L3 Preserved", "GPR-L3", + "GPR-Overall", "CFS", + ] + + with output_path.open("w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + for a in audits: + writer.writerow({ + "Agent": a.agent_id, + "Target": a.target, + "Domain": a.domain, + "Complexity": a.complexity, + "L1 Total": a.l1_total, + "L1 Preserved": a.l1_preserved, + "GPR-L1": f"{a.gpr_l1:.4f}", + "L2 Total": a.l2_total, + "L2 Preserved": a.l2_preserved, + "L2 Elevated": a.l2_elevated, + "GPR-L2": f"{a.gpr_l2:.4f}", + "L3 Total": a.l3_total, + "L3 Preserved": a.l3_preserved, + "GPR-L3": f"{a.gpr_l3:.4f}", + "GPR-Overall": f"{a.gpr_overall:.4f}", + "CFS": f"{a.cfs:.4f}", + }) + + +def export_json(audits: list[GovernanceAudit], output_path: Path) -> None: + """Export full audit data including elevation details as JSON.""" + output_path.parent.mkdir(parents=True, exist_ok=True) + + data = [] + for a in audits: + entry = { + "agent_id": a.agent_id, + "agent_name": a.agent_name, + "target": a.target, + "domain": a.domain, + "complexity": a.complexity, + "l1": {"total": a.l1_total, "preserved": a.l1_preserved, "gpr": a.gpr_l1}, + "l2": { + "total": a.l2_total, + "preserved": a.l2_preserved, + "elevated": a.l2_elevated, + "gpr": a.gpr_l2, + }, + "l3": { + "total": a.l3_total, + "preserved": a.l3_preserved, + "elevated": a.l3_elevated, + "dropped": a.l3_dropped, + "gpr": a.gpr_l3, + }, + "gpr_overall": a.gpr_overall, + "cfs": a.cfs, + "elevated_artifacts": a.elevated_artifacts, + } + data.append(entry) + + output_path.write_text(json.dumps(data, indent=2), encoding="utf-8") diff --git a/src/agentshift/ir.py b/src/agentshift/ir.py index af87369..764ae9c 100644 --- a/src/agentshift/ir.py +++ b/src/agentshift/ir.py @@ -7,6 +7,63 @@ from pydantic import BaseModel, Field +# --------------------------------------------------------------------------- +# Governance models (L1 / L2 / L3) +# --------------------------------------------------------------------------- + + +class Guardrail(BaseModel): + """Layer 1 — prompt-level governance rule (lives in SOUL.md / instructions).""" + + model_config = {"extra": "forbid"} + + id: str + text: str + category: Literal[ + "safety", "privacy", "compliance", "ethical", "operational", "scope", "general" + ] = "general" + severity: Literal["critical", "high", "medium", "low"] = "medium" + + +class ToolPermission(BaseModel): + """Layer 2 — tool-level permission/restriction.""" + + model_config = {"extra": "forbid"} + + tool_name: str + enabled: bool = True + access: Literal["full", "read-only", "disabled"] = "full" + deny_patterns: list[str] = Field(default_factory=list) + allow_patterns: list[str] = Field(default_factory=list) + rate_limit: str | None = None + max_value: str | None = None + notes: str | None = None + + +class PlatformAnnotation(BaseModel): + """Layer 3 — platform-native governance (Bedrock guardrails, Vertex safety, etc.).""" + + model_config = {"extra": "forbid"} + + id: str + kind: Literal["content_filter", "pii_detection", "denied_topics", "grounding_check"] = ( + "content_filter" + ) + description: str + platform_target: Literal["bedrock", "vertex-ai", "m365", "any"] = "any" + config: dict[str, Any] = Field(default_factory=dict) + + +class Governance(BaseModel): + """Unified governance container for all three layers.""" + + model_config = {"extra": "forbid"} + + guardrails: list[Guardrail] = Field(default_factory=list) + tool_permissions: list[ToolPermission] = Field(default_factory=list) + platform_annotations: list[PlatformAnnotation] = Field(default_factory=list) + + class ToolAuth(BaseModel): model_config = {"extra": "forbid"} @@ -131,5 +188,6 @@ class AgentIR(BaseModel): knowledge: list[KnowledgeSource] = Field(default_factory=list) triggers: list[Trigger] = Field(default_factory=list) constraints: Constraints = Field(default_factory=Constraints) + governance: Governance = Field(default_factory=Governance) install: list[InstallStep] = Field(default_factory=list) metadata: Metadata = Field(default_factory=Metadata) diff --git a/src/agentshift/parsers/openclaw.py b/src/agentshift/parsers/openclaw.py index f4fae23..eff6f54 100644 --- a/src/agentshift/parsers/openclaw.py +++ b/src/agentshift/parsers/openclaw.py @@ -12,11 +12,15 @@ from agentshift.ir import ( AgentIR, Constraints, + Governance, + Guardrail, InstallStep, KnowledgeSource, Metadata, Persona, + PlatformAnnotation, Tool, + ToolPermission, Trigger, TriggerDelivery, ) @@ -109,6 +113,9 @@ def parse_skill_dir(path: Path) -> AgentIR: platform_extensions={"openclaw": oc_meta} if oc_meta else {}, ) + # Governance: parse SOUL.md, tool permissions, and L3 annotations + governance = _extract_governance(path) + return AgentIR( name=name, description=description, @@ -118,6 +125,7 @@ def parse_skill_dir(path: Path) -> AgentIR: knowledge=knowledge, triggers=triggers, constraints=constraints, + governance=governance, install=install_steps, metadata=metadata, ) @@ -501,3 +509,159 @@ def _infer_format(filename: str) -> str: ".pdf": "pdf", ".html": "html", }.get(ext, "unknown") + + +# --------------------------------------------------------------------------- +# Governance extraction +# --------------------------------------------------------------------------- + +_GUARDRAIL_CATEGORY_KEYWORDS: dict[str, list[str]] = { + "safety": ["harm", "dangerous", "emergency", "self-harm", "violence"], + "privacy": ["pii", "personal", "confidential", "data", "privacy", "hipaa", "phi"], + "compliance": ["legal", "regulatory", "disclaimer", "licensed", "compliance"], + "ethical": ["bias", "discriminat", "fair", "race", "gender", "religion"], + "operational": ["escalat", "timeout", "halt", "stop", "limit", "approval"], + "scope": ["do not", "never", "refuse", "only", "restrict"], +} + + +def _classify_guardrail(text: str) -> str: + """Classify a guardrail rule into a category based on keywords.""" + lower = text.lower() + for category, keywords in _GUARDRAIL_CATEGORY_KEYWORDS.items(): + if any(kw in lower for kw in keywords): + return category + return "general" + + +def _infer_severity(text: str) -> str: + """Infer severity from guardrail language.""" + lower = text.lower() + if any(w in lower for w in ["never", "immediately", "hard stop", "halt", "emergency"]): + return "critical" + if any(w in lower for w in ["always", "must", "require", "do not"]): + return "high" + if any(w in lower for w in ["should", "recommend", "prefer"]): + return "low" + return "medium" + + +def _parse_soul_md(path: Path) -> list[Guardrail]: + """Parse SOUL.md to extract L1 guardrails.""" + soul_md = path / "SOUL.md" + if not soul_md.exists(): + return [] + + raw = soul_md.read_text(encoding="utf-8") + guardrails: list[Guardrail] = [] + idx = 0 + + for line in raw.splitlines(): + stripped = line.strip() + # Extract quoted rules: lines starting with quotes or bullet-quoted + rule_text = None + if stripped.startswith('"') and stripped.endswith('"'): + rule_text = stripped.strip('"') + elif stripped.startswith("- ") and '"' in stripped: + # bullet with quoted text: - "Do not..." + m = re.search(r'"([^"]+)"', stripped) + if m: + rule_text = m.group(1) + elif stripped.startswith("- ") and len(stripped) > 3 and not stripped.startswith("- #"): + # Plain bullet items that look like rules + candidate = stripped[2:].strip() + if any( + candidate.lower().startswith(w) + for w in [ + "do not", "never", "always", "refuse", "ensure", "include", + "flag", "escalate", "maintain", "clarify", "halt", "log", + "hard stop", "delegate", "aggregate", "enforce", "evaluate", + "apply", "document", + ] + ): + rule_text = candidate + + if rule_text: + idx += 1 + guardrails.append( + Guardrail( + id=f"L1-{idx:03d}", + text=rule_text, + category=_classify_guardrail(rule_text), + severity=_infer_severity(rule_text), + ) + ) + + return guardrails + + +def _parse_tool_permissions(path: Path) -> list[ToolPermission]: + """Parse tools/*.json files to extract L2 tool permissions.""" + tools_dir = path / "tools" + if not tools_dir.is_dir(): + return [] + + permissions: list[ToolPermission] = [] + for f in sorted(tools_dir.iterdir()): + if not f.is_file() or f.suffix != ".json": + continue + try: + data = json.loads(f.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + continue + + tool_name = data.get("name", f.stem) + enabled = data.get("enabled", True) + access = data.get("access", "full") + deny_patterns = data.get("deny_patterns", []) + allow_patterns = data.get("allow_patterns", []) + rate_limit = data.get("rate_limit") + max_value = data.get("max_value") + notes = data.get("notes") + + permissions.append( + ToolPermission( + tool_name=tool_name, + enabled=enabled, + access=access, + deny_patterns=deny_patterns, + allow_patterns=allow_patterns, + rate_limit=str(rate_limit) if rate_limit else None, + max_value=str(max_value) if max_value else None, + notes=notes, + ) + ) + + return permissions + + +def _parse_l3_annotations(path: Path) -> list[PlatformAnnotation]: + """Parse governance/annotations.json for L3 platform-native annotations.""" + annotations_file = path / "governance" / "annotations.json" + if not annotations_file.exists(): + return [] + + try: + data = json.loads(annotations_file.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return [] + + annotations: list[PlatformAnnotation] = [] + for item in data if isinstance(data, list) else data.get("annotations", []): + with contextlib.suppress(Exception): + annotations.append(PlatformAnnotation(**item)) + + return annotations + + +def _extract_governance(path: Path) -> Governance: + """Extract full governance model from agent directory.""" + guardrails = _parse_soul_md(path) + tool_permissions = _parse_tool_permissions(path) + platform_annotations = _parse_l3_annotations(path) + + return Governance( + guardrails=guardrails, + tool_permissions=tool_permissions, + platform_annotations=platform_annotations, + ) From 1933b457f88750b0df02208e2de316522d32be77 Mon Sep 17 00:00:00 2001 From: ogkranthi Date: Sat, 28 Mar 2026 22:06:17 -0400 Subject: [PATCH 2/4] chore: add Week 7 backlog (v0.3 governance framework + cloud parsers) --- BACKLOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/BACKLOG.md b/BACKLOG.md index 32b36e1..e9e0ac1 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -83,3 +83,22 @@ | D19 | P1 | @dev | merged | GitHub Action: auto-generate cloud configs when SKILL.md changes (CI/CD integration) | | T11 | P1 | @tester | merged | Write tests for LangGraph emitter (fixture + round-trip + integration) | | T12 | P1 | @tester | merged | Add LangGraph to full integration test (pregnancy-companion → langgraph) | + +## Week 7: Governance Framework + Cloud Parsers (v0.3) + +> Context: Governance system (L1/L2/L3 IR fields, audit engine, elevation) was added as untracked +> work supporting a research paper. This week formalises it with specs, tests, reverse-direction +> parsers for cloud platforms, and a CHANGELOG bump to v0.3.0. + +| ID | Priority | Owner | Status | Title | +|----|----------|-------|--------|-------| +| A12 | P1 | @architect | ready | Spec Governance IR schema — L1/L2/L3 layers, GPR/CFS scoring definitions, elevation rules | +| A13 | P1 | @architect | ready | Research and document Bedrock parser format (reverse: Bedrock → IR) | +| A14 | P1 | @architect | ready | Research and document Vertex AI parser format (reverse: Vertex → IR) | +| D22 | P1 | @dev | ready | Implement Bedrock parser (bedrock-agent.json + OpenAPI + instruction.txt → IR) | +| D23 | P1 | @dev | ready | Implement Vertex AI parser (agent.json + tool definitions → IR) | +| D24 | P1 | @dev | ready | Add `--from bedrock` and `--from vertex` to convert/diff/audit CLI commands | +| D25 | P1 | @dev | ready | Bump version to 0.3.0 — update CHANGELOG.md, pyproject.toml, add governance to README | +| T14 | P1 | @tester | ready | Write tests for governance extraction (Guardrail classification, ToolPermission, L3 annotations) | +| T15 | P1 | @tester | ready | Write tests for audit engine (GPR-L1/L2/L3 scoring, elevation tracking, CSV/JSON export) | +| T16 | P1 | @tester | ready | Write tests for Bedrock + Vertex parsers (fixtures + round-trip with emitters) | From 3e5d2fa3a65eb319b5da9579428749b4baa1cfb6 Mon Sep 17 00:00:00 2001 From: ogkranthi Date: Sat, 28 Mar 2026 22:11:38 -0400 Subject: [PATCH 3/4] =?UTF-8?q?chore(A14):=20claim=20task=20=E2=80=94=20in?= =?UTF-8?q?-progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BACKLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BACKLOG.md b/BACKLOG.md index e9e0ac1..fea64a5 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -94,7 +94,7 @@ |----|----------|-------|--------|-------| | A12 | P1 | @architect | ready | Spec Governance IR schema — L1/L2/L3 layers, GPR/CFS scoring definitions, elevation rules | | A13 | P1 | @architect | ready | Research and document Bedrock parser format (reverse: Bedrock → IR) | -| A14 | P1 | @architect | ready | Research and document Vertex AI parser format (reverse: Vertex → IR) | +| A14 | P1 | @architect | in-progress | Research and document Vertex AI parser format (reverse: Vertex → IR) | | D22 | P1 | @dev | ready | Implement Bedrock parser (bedrock-agent.json + OpenAPI + instruction.txt → IR) | | D23 | P1 | @dev | ready | Implement Vertex AI parser (agent.json + tool definitions → IR) | | D24 | P1 | @dev | ready | Add `--from bedrock` and `--from vertex` to convert/diff/audit CLI commands | From 0f076e3bfffd7edca0325530d63cc8fa68a0986d Mon Sep 17 00:00:00 2001 From: ogkranthi Date: Sat, 28 Mar 2026 22:13:11 -0400 Subject: [PATCH 4/4] =?UTF-8?q?feat(A14):=20add=20Vertex=20AI=20parser=20s?= =?UTF-8?q?pec=20=E2=80=94=20Vertex=20artifacts=20=E2=86=92=20IR=20field?= =?UTF-8?q?=20mapping,=20CLI,=20round-trip=20fidelity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BACKLOG.md | 2 +- specs/vertex-parser-spec.md | 605 ++++++++++++++++++++++++++++++++++++ 2 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 specs/vertex-parser-spec.md diff --git a/BACKLOG.md b/BACKLOG.md index fea64a5..5479888 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -94,7 +94,7 @@ |----|----------|-------|--------|-------| | A12 | P1 | @architect | ready | Spec Governance IR schema — L1/L2/L3 layers, GPR/CFS scoring definitions, elevation rules | | A13 | P1 | @architect | ready | Research and document Bedrock parser format (reverse: Bedrock → IR) | -| A14 | P1 | @architect | in-progress | Research and document Vertex AI parser format (reverse: Vertex → IR) | +| A14 | P1 | @architect | pr-created | Research and document Vertex AI parser format (reverse: Vertex → IR) | | D22 | P1 | @dev | ready | Implement Bedrock parser (bedrock-agent.json + OpenAPI + instruction.txt → IR) | | D23 | P1 | @dev | ready | Implement Vertex AI parser (agent.json + tool definitions → IR) | | D24 | P1 | @dev | ready | Add `--from bedrock` and `--from vertex` to convert/diff/audit CLI commands | diff --git a/specs/vertex-parser-spec.md b/specs/vertex-parser-spec.md new file mode 100644 index 0000000..11ff5c7 --- /dev/null +++ b/specs/vertex-parser-spec.md @@ -0,0 +1,605 @@ +# Vertex AI → IR Parser Spec + +**Spec ID:** A14 +**Status:** Canonical +**Author:** @architect +**Closes:** A14 (Week 7 backlog) +**Reverse of:** `specs/vertex-ai-agent-format.md` (emitter direction) +**Implements:** D23 + +--- + +## 1. Overview + +The Vertex AI parser converts GCP Vertex AI Agent Builder artifacts back into an AgentShift IR. +This is the reverse of the Vertex AI emitter (`src/agentshift/emitters/vertex.py`). + +**Input artifacts** (any combination; parser must be tolerant of missing files): + +| File | Role | Required? | +|------|------|-----------| +| `agent.json` | Agent resource definition (AgentShift-generated or API export) | **Primary** | +| `tools.json` | Tool definitions array (AgentShift-generated) | Tool extraction | +| `README.md` | Deployment documentation (optional, for stub detection) | Optional | + +**Primary input format:** `agent.json` (AgentShift-generated or Vertex AI API response). + +--- + +## 2. Input Format Reference + +### 2.1 `agent.json` — AgentShift-generated + +The format produced by `src/agentshift/emitters/vertex.py`: + +```json +{ + "displayName": "Pregnancy Companion", + "goal": "You are a warm, knowledgeable pregnancy companion...", + "instructions": [ + "Never diagnose medical conditions or prescribe treatments.", + "Respond in a warm, supportive, non-clinical tone." + ], + "tools": [ + { + "name": "symptom-tracker", + "type": "FUNCTION", + "description": "Log and retrieve pregnancy symptoms.", + "x-agentshift-stub": "Implement as Cloud Function or Cloud Run service" + } + ], + "defaultLanguageCode": "en", + "supportedLanguageCodes": ["en"] +} +``` + +### 2.2 `agent.json` — Vertex AI API response (Reasoning Engine / Agent Builder) + +The Vertex AI API returns agents in this format: + +```json +{ + "name": "projects/my-project/locations/us-central1/agents/12345", + "displayName": "Pregnancy Companion", + "goal": "You are a warm, knowledgeable pregnancy companion...", + "instructions": [ + "Never diagnose medical conditions.", + "Always recommend consulting a doctor." + ], + "tools": [ + { + "name": "projects/my-project/locations/us-central1/agents/12345/tools/tool-id" + } + ], + "defaultLanguageCode": "en", + "supportedLanguageCodes": ["en"], + "createTime": "2024-01-15T10:30:00Z", + "updateTime": "2024-01-20T14:22:00Z" +} +``` + +### 2.3 `tools.json` — AgentShift-generated tool definitions + +The format produced by the AgentShift Vertex emitter: + +```json +[ + { + "displayName": "SymptomTracker", + "description": "Log and retrieve pregnancy symptoms", + "functionDeclarations": [ + { + "name": "log_symptom", + "description": "Log a pregnancy symptom", + "parameters": { + "type": "object", + "properties": { + "symptom": { "type": "string" }, + "severity": { "type": "integer" } + } + } + } + ] + }, + { + "displayName": "PregnancyKnowledgeBase", + "description": "Search pregnancy guides", + "datastoreSpec": { + "datastoreType": "UNSTRUCTURED_DOCUMENTS", + "dataStores": ["projects/MY_PROJECT/locations/us-central1/collections/default_collection/dataStores/pregnancy-kb"] + } + } +] +``` + +--- + +## 3. Vertex AI → IR Field Mapping + +### 3.1 Core fields + +| Vertex AI field | IR field | Transformation | +|-----------------|----------|----------------| +| `displayName` | `name` | Slugify: `"Pregnancy Companion"` → `"pregnancy-companion"` | +| `goal` | `persona.system_prompt` | Direct; also run section extractor | +| `instructions` | Combined with `goal` into `persona.system_prompt` | See §3.2 | +| `defaultLanguageCode` | `persona.language` | Direct (BCP-47) | +| `description` (if present) | `description` | Direct | +| `createTime` | `metadata.created_at` | ISO 8601 | +| `updateTime` | `metadata.updated_at` | ISO 8601 | +| `name` (full resource path) | `metadata.platform_extensions.vertex.resource_name` | Preserve | + +**`description` fallback:** If `description` is absent in the Vertex config, derive it from the +first sentence of `goal`. Truncate at 200 chars. + +### 3.2 `goal` + `instructions` → `persona.system_prompt` + +The Vertex emitter splits content between `goal` (from `sections.overview`) and `instructions` +(from `sections.behavior`, `sections.persona`, etc.). The parser must reconstruct the unified +`system_prompt` from both. + +**Reconstruction algorithm:** + +1. Start with `goal` as the base of `system_prompt`. +2. If `instructions` is non-empty, append a separator and join the instruction lines: + ``` + {goal} + + --- + + {instructions[0]} + {instructions[1]} + ... + ``` +3. Run `extract_sections()` on the combined text. If sections are detected, populate + `persona.sections`. + +**Section recovery from instructions:** + +The Vertex emitter produces structured instruction lines with headings like `"Behavior:\n..."`. +The parser SHOULD recognize the `"SectionName:\n..."` pattern and attempt to restore sections: + +```python +# Pattern: "Heading:\ncontent" +section_pattern = re.compile(r'^([A-Z][a-zA-Z\s-]+):\n(.+)', re.MULTILINE | re.DOTALL) +``` + +If `"Restrictions:\n..."` is found in instructions, extract as `sections["guardrails"]`. + +### 3.3 Platform metadata + +```python +ir.metadata.source_platform = "vertex" +ir.metadata.platform_extensions["vertex"] = { + "resource_name": resource_name, # Full resource path if available + "display_name": display_name, # Original displayName (before slugification) + "model": model_override, # From platform_extensions.vertex_ai.model if set +} +``` + +### 3.4 Tools from `agent.json` + +The AgentShift Vertex emitter embeds tools directly in `agent.json` (not separate tool resources). +Parse each entry in the `tools` array: + +**Case 1: Inline tool with `type` field (AgentShift-generated):** +```python +Tool( + name=tool["name"], + description=tool.get("description", ""), + kind=_infer_kind(tool), +) +``` + +**Kind inference from `type` field:** +| `type` value | IR `kind` | +|-------------|-----------| +| `"FUNCTION"` | `"function"` (or `"shell"` / `"mcp"` if `x-agentshift-stub` is present) | +| `"OPEN_API"` | `"openapi"` | +| (absent / unknown) | `"unknown"` | + +**`x-agentshift-stub` detection:** +- If `"x-agentshift-stub"` key is present → `kind="shell"` (default) +- If stub value contains `"MCP"` or `"mcp"` → `kind="mcp"` +- If stub value contains `"Cloud Function"` or `"Cloud Run"` → `kind="function"` + +### 3.5 Tools from `tools.json` + +When `tools.json` is present (AgentShift-generated or exported from Vertex API), parse each +tool entry: + +**Case 1: Function tool (has `functionDeclarations`):** +```python +for func in tool_entry["functionDeclarations"]: + Tool( + name=func["name"], + description=func.get("description", tool_entry.get("description", "")), + kind="function", + parameters=func.get("parameters"), + ) +``` + +**Case 2: OpenAPI tool (has `openApiFunctionDeclarations`):** +```python +Tool( + name=_slugify(tool_entry["displayName"]), + description=tool_entry.get("description", ""), + kind="openapi", + endpoint=_extract_server_url(tool_entry), + parameters=None, # Schema available in openApiFunctionDeclarations.specification + auth=_parse_vertex_auth(tool_entry.get("authentication")), +) +``` + +**Case 3: Data store tool (has `datastoreSpec`):** +```python +KnowledgeSource( + name=_slugify(tool_entry["displayName"]), + description=tool_entry.get("description", ""), + kind="vector_store", + path=_extract_datastore_id(tool_entry["datastoreSpec"]), + load_mode="indexed", + format="unknown", +) +``` +Data store tools are added to `ir.knowledge`, not `ir.tools`. + +### 3.6 Auth reconstruction + +For OpenAPI tools with an `authentication` block: + +| Vertex auth type | IR `ToolAuth.type` | Notes | +|-----------------|-------------------|-------| +| `apiKeyConfig` | `"api_key"` | `env_var` from `apiKeyConfig.name` | +| `oauthConfig` | `"oauth2"` | `scopes` from `oauthConfig.scope` | +| `serviceAccountConfig` | `"bearer"` | `notes` = service account email | +| (absent) | `"none"` | | + +### 3.7 `instructions` → L1 Guardrails (heuristic) + +When `instructions` is populated, scan each instruction string for guardrail patterns using the +same heuristic as the Bedrock parser (§4 of `bedrock-parser-spec.md`): + +- Patterns: `"never "`, `"do not "`, `"always "`, `"must not "`, `"avoid "`, `"prohibited"` +- Use `_infer_category()` and `_infer_severity()` utility functions (shared with Bedrock parser) +- Store results in `governance.guardrails` + +The parser SHOULD also look for a `"Restrictions:"` prefix in instruction lines — these are +`sections["guardrails"]` that were linearized by the emitter and may contain explicit rules. + +--- + +## 4. `goal` Parsing + +The Vertex emitter uses `sections["overview"]` as the `goal` when `persona.sections` is +populated. The parser must reconstruct `persona.sections` from this. + +**Detection:** If `goal` is a short paragraph without markdown headings, treat it as +`sections["overview"]`. Then combine with `instructions` structure to fill other sections. + +**Section reconstruction from the linearized form:** + +The emitter produces instructions in the pattern: +``` +"Behavior:\n- Always state the location..." +"Persona:\nWarm and supportive..." +"Restrictions:\nDo not provide weather advisories as official warnings." +``` + +The parser should detect `"SectionName:\ncontent"` and map to sections: + +| Instruction prefix | Section key | +|-------------------|-------------| +| `"Behavior:"` | `behavior` | +| `"Persona:"` | `persona` | +| `"Tools:"` | `tools` | +| `"Knowledge:"` | `knowledge` | +| `"Restrictions:"` | `guardrails` | +| (no prefix match) | Appended to `behavior` | + +If section detection succeeds, populate `persona.sections` (overriding raw goal-based detection). + +--- + +## 5. Parser Entry Point + +The parser is implemented in `src/agentshift/parsers/vertex.py`. + +```python +def parse(input_dir: Path) -> AgentIR: + """Parse Vertex AI Agent Builder artifacts from a directory into an AgentIR. + + Reads any combination of: + - agent.json (required) + - tools.json (optional — additional tool definitions) + - README.md (optional — stub detection hints) + + At least agent.json must be present. + """ +``` + +**Alternative entry points:** + +```python +def parse_api_response(agent_data: dict, tools_data: list[dict] | None = None) -> AgentIR: + """Parse raw Vertex AI API response dicts.""" + +def parse_agent_json(agent_json: str, tools_json: str | None = None) -> AgentIR: + """Parse from JSON strings directly.""" +``` + +--- + +## 6. Input Resolution Order + +When multiple files are present: + +1. `agent.json` is always required. +2. `tools.json` — merge tools with `agent.json["tools"]`; `tools.json` entries take precedence + (they have richer schemas). +3. `README.md` — scan for stub tool names to enrich kind inference. + +**Tool deduplication:** If the same tool name appears in both `agent.json["tools"]` and +`tools.json`, the `tools.json` entry wins (it has the full declaration). + +--- + +## 7. CLI Integration + +```bash +# Convert from Vertex AI artifacts directory +agentshift convert ./vertex-output/ --from vertex --to openclaw + +# Convert a single agent.json +agentshift convert ./agent.json --from vertex --to openclaw + +# Diff: compare Vertex agent with OpenClaw skill +agentshift diff ./vertex-output/ --from vertex ./my-skill/ --from openclaw + +# Audit: governance preservation when converting vertex → bedrock +agentshift audit ./vertex-output/ --from vertex --targets bedrock +``` + +--- + +## 8. Validation Notes + +The parser output MUST pass `agentshift validate`. Key checks: + +1. `ir.name` is non-empty and slug-safe. +2. `ir.persona.system_prompt` is non-empty. +3. `ir.description` is non-empty (derive from `goal` if absent). +4. All `tool.name` values are unique. +5. `governance.guardrails[].id` values are unique. +6. `persona.language` is a valid BCP-47 code. + +--- + +## 9. Round-Trip Fidelity + +A round-trip is: `openclaw → vertex → openclaw`. + +**Guaranteed to survive:** +- Agent `name` (via displayName slugification) +- `persona.system_prompt` (goal + instructions combined) +- `persona.sections` (if instructions use `"SectionName:\n..."` pattern) +- `persona.language` (via `defaultLanguageCode`) +- Function tool names and descriptions +- Data store knowledge sources +- OpenAPI tool endpoints (if real URLs were present) + +**Known lossy fields:** +- `persona.sections` partial loss — `"examples"` and `"triggers"` sections are dropped by emitter +- `tool.auth` — partially inferred from authentication blocks +- `triggers` — Cloud Scheduler stubs in README are not parsed back +- `install` — not applicable to Vertex AI +- `constraints` — not preserved +- `governance.tool_permissions` — not expressed in Vertex output; only elevated L1 text survives + +**Vertex-specific lossy patterns:** +- Goal truncation: if original was > 8,000 chars, the emitter truncated it; the parser cannot + recover the full text. +- Tool inline vs. tools.json: when the emitter puts tools in `agent.json["tools"]`, some fields + (like full OpenAPI specs) may be lost. + +--- + +## 10. Example Round-Trip + +### Input (`agent.json` — AgentShift-generated): +```json +{ + "displayName": "weather", + "goal": "I provide current weather and forecasts using wttr.in or Open-Meteo.", + "instructions": [ + "Behavior:", + "- Always state the location you're reporting for.", + "- Use °C by default; offer °F on request.", + "Restrictions:", + "Do not provide weather advisories as official warnings." + ], + "tools": [], + "defaultLanguageCode": "en", + "supportedLanguageCodes": ["en"] +} +``` + +### Output IR: +```json +{ + "ir_version": "1.0", + "name": "weather", + "description": "I provide current weather and forecasts using wttr.in or Open-Meteo.", + "persona": { + "system_prompt": "I provide current weather and forecasts using wttr.in or Open-Meteo.\n\n---\n\nBehavior:\n- Always state the location you're reporting for.\n- Use °C by default; offer °F on request.\nRestrictions:\nDo not provide weather advisories as official warnings.", + "sections": { + "overview": "I provide current weather and forecasts using wttr.in or Open-Meteo.", + "behavior": "- Always state the location you're reporting for.\n- Use °C by default; offer °F on request.", + "guardrails": "Do not provide weather advisories as official warnings." + }, + "language": "en" + }, + "tools": [], + "governance": { + "guardrails": [ + { + "id": "G001", + "text": "Do not provide weather advisories as official warnings.", + "category": "scope", + "severity": "medium" + } + ], + "tool_permissions": [], + "platform_annotations": [] + }, + "metadata": { + "source_platform": "vertex", + "platform_extensions": { + "vertex": { + "display_name": "weather" + } + } + } +} +``` + +--- + +## 11. Shared Utilities (Bedrock + Vertex) + +The following utility functions are used by both the Bedrock and Vertex parsers. They SHOULD be +implemented in a shared module `src/agentshift/parsers/utils.py`: + +```python +def slugify(name: str) -> str: + """Convert display name to slug: 'Pregnancy Companion' → 'pregnancy-companion'.""" + ... + +def title_case_to_slug(name: str) -> str: + """Same as slugify — alias for clarity.""" + ... + +def infer_guardrail_category(text: str) -> str: + """Infer Guardrail.category from keywords in the text.""" + ... + +def infer_guardrail_severity(text: str) -> str: + """Infer Guardrail.severity from keywords in the text.""" + ... + +def extract_guardrails_from_text( + text: str, + id_prefix: str = "G", +) -> list[Guardrail]: + """Scan text for guardrail-like sentences and extract Guardrail objects.""" + ... + +def is_todo_placeholder(value: str) -> bool: + """Return True if value is a TODO/placeholder string from an AgentShift emitter.""" + return "TODO" in value or "PLACEHOLDER" in value +``` + +--- + +## Appendix A — Vertex AI API Shapes + +### Agent resource (Vertex AI REST API v1beta1): +```json +{ + "name": "projects/{project}/locations/{location}/agents/{agent_id}", + "displayName": "My Agent", + "goal": "You are a helpful assistant.", + "instructions": ["Always be polite."], + "tools": [ + { "name": "projects/{project}/locations/{location}/agents/{agent_id}/tools/{tool_id}" } + ], + "defaultLanguageCode": "en", + "supportedLanguageCodes": ["en"], + "createTime": "2024-01-15T10:30:00Z", + "updateTime": "2024-01-20T14:22:00Z" +} +``` + +### Tool resource (function declarations): +```json +{ + "name": "projects/{project}/locations/{location}/agents/{agent_id}/tools/{tool_id}", + "displayName": "WeatherTool", + "description": "Look up weather data", + "functionDeclarations": [ + { + "name": "get_weather", + "description": "Get current weather for a location", + "parameters": { + "type": "object", + "properties": { + "location": { "type": "string" } + }, + "required": ["location"] + } + } + ] +} +``` + +### Tool resource (OpenAPI): +```json +{ + "displayName": "WeatherAPI", + "openApiFunctionDeclarations": { + "specification": { ... }, + "authentication": { + "apiKeyConfig": { + "name": "appid", + "in": "QUERY", + "httpElementLocation": "HTTP_IN_QUERY" + } + } + } +} +``` + +### Tool resource (data store): +```json +{ + "displayName": "KnowledgeBase", + "datastoreSpec": { + "datastoreType": "UNSTRUCTURED_DOCUMENTS", + "dataStores": [ + "projects/{project}/locations/{location}/collections/default_collection/dataStores/{ds_id}" + ] + } +} +``` + +--- + +## Appendix B — Tool Kind Detection Heuristic + +```python +def infer_tool_kind(tool_entry: dict) -> str: + """Infer IR tool kind from a Vertex tool dict.""" + if "datastoreSpec" in tool_entry: + return "knowledge" # Route to ir.knowledge, not ir.tools + if "openApiFunctionDeclarations" in tool_entry: + return "openapi" + if "functionDeclarations" in tool_entry: + return "function" + + # For inline tools in agent.json["tools"] + tool_type = tool_entry.get("type", "") + stub = tool_entry.get("x-agentshift-stub", "") + + if tool_type == "OPEN_API": + return "openapi" + if tool_type == "FUNCTION": + if stub: + stub_lower = stub.lower() + if "mcp" in stub_lower: + return "mcp" + if "shell" in stub_lower: + return "shell" + return "function" + + return "unknown" +```