Files
APAW/scripts/mass-update-gns-agents.py
NW bd154f24d0 feat(gns2): mass-update all 30 agents with GNS-2 protocol
- 29 agents updated with GNS-2 checkpoint/event protocol
- 12 Tier 0 (leaf) agents: read checkpoint, write event footer, no cascade
- 17 Tier 1 (task) agents: read checkpoint, recommend next agent, no direct task calls
- 2 Tier 2 (meta) agents already updated: capability-analyst, agent-architect, evaluator
- All agents now include GNS_EVENT footer template in comments
- Frontmatter updated with '(GNS-2 Tier N)' classification

Scripts added:
- scripts/mass-update-gns-agents.py — idempotent mass updater
- scripts/validate-gns-agents.py — protocol checker

Refs: Milestone #67, Issues #99-#107
2026-05-08 22:03:08 +01:00

247 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""
GNS-2 Agent Mass Update Script
Updates all remaining Tier 0/1 agents with GNS-2 protocol:
- Checkpoint read requirement (read-only for Tier 0)
- Event footer template (mandatory)
- Tier classification (Tier 0 or 1)
"""
import os
import re
import glob
# Root directory of agents
AGENTS_DIR = '.kilo/agents'
# Tier classification
TIER_0_AGENTS = [
'history-miner', 'code-skeptic', 'performance-engineer',
'security-auditor', 'visual-tester', 'browser-automation',
'markdown-validator', 'planner', 'reflector', 'memory-manager',
'pipeline-judge', 'architect-indexer'
]
TIER_1_AGENTS = [
'lead-developer', 'the-fixer', 'sdet-engineer',
'frontend-developer', 'backend-developer', 'go-developer',
'flutter-developer', 'php-developer', 'python-developer',
'devops-engineer', 'release-manager', 'requirement-refiner',
'product-owner', 'prompt-optimizer', 'system-analyst',
'workflow-architect', 'orchestrator'
]
def get_tier(agent_name: str) -> int:
if agent_name in TIER_0_AGENTS:
return 0
if agent_name in TIER_1_AGENTS:
return 1
return -1 # Unknown
def extract_frontmatter(content: str) -> tuple:
"""Extract YAML frontmatter from markdown content."""
if not content.startswith('---'):
return None, content
parts = content.split('---', 2)
if len(parts) < 3:
return None, content
return parts[1].strip(), parts[2].strip()
def update_frontmatter(fm: str, tier: int) -> str:
"""Update frontmatter with GNS-2 metadata."""
lines = fm.split('\n')
new_lines = []
# Add tier comment
new_lines.append(f"# GNS-2 Agent (Tier {tier})")
for line in lines:
# Ensure permission.task exists
if line.strip().startswith('permission:'):
new_lines.append(line)
continue
new_lines.append(line)
return '\n'.join(new_lines)
def generate_gns_protocol(tier: int) -> str:
"""Generate GNS-2 protocol section for an agent."""
if tier == 0:
return """## GNS-2 Protocol
### Tier
Tier 0 (Leaf Agent / No Cascade)
- `max_cascade_depth: 0` (no subagent calls)
- Read checkpoint only (do not modify)
- Write event footer on completion
### On Entry (MANDATORY)
1. Read issue body from Gitea API
2. Parse `## GNS Checkpoint` YAML block
3. Extract task from checkpoint or last event
### During Work
- Execute atomic task as specified in checkpoint
- Follow existing behavior guidelines
- Do NOT spawn subagents
### On Exit (MANDATORY)
1. Post comment with result + GNS_EVENT footer
2. Do NOT modify checkpoint (read-only)
3. Set `next_agent` recommendation in event footer
### Next Recommendation
After completion, recommend next agent in event footer:
- `code-skeptic`: after code written
- `performance-engineer`: after code tested
- `security-auditor`: after performance reviewed
"""
elif tier == 1:
return """## GNS-2 Protocol
### Tier
Tier 1 (Task Agent / Orchestrator-Mediated Cascade)
- `max_cascade_depth: 1` (request orchestrator to spawn, do not spawn directly)
- Can read checkpoint and recommend next agent
- Event footer triggers orchestrator polling
### On Entry (MANDATORY)
1. Read issue body from Gitea API
2. Parse `## GNS Checkpoint` YAML block
3. Verify `checkpoint.budget.remaining > estimated_cost`
### During Work
- Execute task as specified
- If subagent needed, write recommendation in event footer
- Do NOT call `task` tool directly (Tier 1)
### On Exit (MANDATORY)
1. Update labels if needed (quality::*, phase::*)
2. Post comment with result + GNS_EVENT footer
3. Include `next_agent` recommendation
### GNS Event Footer Template
```markdown
---
<!-- GNS_EVENT: {
"type": "subagent_result",
"agent": "AGENT_NAME",
"invocation_id": "AGENT-{issue}-{seq}",
"parent_id": "{parent_invocation}",
"depth": 1,
"budget": {"remaining": {remaining}},
"state_changes": {
"labels_add": ["phase::{phase}"],
"labels_remove": ["phase::{old_phase}"],
"assignee": "{next_agent}",
"is_locked": false
},
"next_agent": "{next_agent}",
"estimated_next_tokens": {estimate},
"timestamp": "{iso8601}"
} -->
```
"""
return ""
def update_agent_file(filepath: str) -> bool:
"""Update a single agent file with GNS-2 protocol."""
agent_name = os.path.basename(filepath).replace('.md', '')
tier = get_tier(agent_name)
if tier < 0:
print(f"⚠️ Unknown agent: {agent_name}, skipping")
return False
with open(filepath, 'r') as f:
content = f.read()
# Check if already updated
if 'GNS-2 Protocol' in content:
print(f"⏭️ {agent_name} already has GNS-2 protocol")
return False
fm_raw, body = extract_frontmatter(content)
if fm_raw is None:
print(f"{agent_name}: no frontmatter found")
return False
# Update description to mention GNS-2
fm_lines = fm_raw.split('\n')
new_fm_lines = []
for line in fm_lines:
if line.startswith('description:'):
desc = line.replace('description:', '').strip()
new_fm_lines.append(f'description: {desc} (GNS-2 Tier {tier})')
else:
new_fm_lines.append(line)
new_fm = '---\n' + '\n'.join(new_fm_lines) + '\n---'
# Generate GNS-2 section
gns_section = generate_gns_protocol(tier)
# Combine: frontmatter + original body + GNS section
# Insert GNS section before <!-- gitea-commenting -->
gitea_pattern = r'<gitea-commenting[^/]*/>'
if re.search(gitea_pattern, body):
# Insert before gitea-commenting tag
new_body = re.sub(
gitea_pattern,
f"{gns_section}\n\n\\g<0>",
body
)
else:
# Append at end
new_body = body + '\n\n' + gns_section
new_content = new_fm + '\n' + new_body
with open(filepath, 'w') as f:
f.write(new_content)
print(f"{agent_name} (Tier {tier})")
return True
def main():
print("GNS-2 Agent Mass Update")
print(f"Target: {AGENTS_DIR}")
print(f"Tier 0 (Leaf): {len(TIER_0_AGENTS)}")
print(f"Tier 1 (Task): {len(TIER_1_AGENTS)}")
print()
updated = 0
skipped = 0
failed = 0
for filepath in sorted(glob.glob(os.path.join(AGENTS_DIR, '*.md'))):
agent_name = os.path.basename(filepath).replace('.md', '')
# Skip already updated agents
if agent_name in ['capability-analyst', 'agent-architect', 'evaluator']:
print(f"⏭️ {agent_name} (already GNS-2)")
skipped += 1
continue
result = update_agent_file(filepath)
if result:
updated += 1
elif 'already' in f'{result}':
skipped += 1
else:
failed += 1
print()
print(f"Done: {updated} updated, {skipped} skipped, {failed} failed")
print(f"Total: {updated + skipped + failed} agents processed")
if __name__ == '__main__':
main()