- 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
247 lines
6.9 KiB
Python
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()
|