from __future__ import annotations import json from dataclasses import asdict, dataclass from pathlib import Path from typing import Any @dataclass(frozen=True) class TaskPacket: objective: str scope: str repo: str branch_policy: str acceptance_tests: tuple[str, ...] commit_policy: str reporting_contract: str escalation_policy: str def to_dict(self) -> dict[str, Any]: payload = asdict(self) payload['acceptance_tests'] = list(self.acceptance_tests) return payload @classmethod def from_dict(cls, payload: dict[str, Any]) -> 'TaskPacket': acceptance_tests = payload.get('acceptance_tests', ()) if not isinstance(acceptance_tests, list): raise TaskPacketValidationError(['acceptance_tests must be a list']) packet = cls( objective=str(payload.get('objective', '')), scope=str(payload.get('scope', '')), repo=str(payload.get('repo', '')), branch_policy=str(payload.get('branch_policy', '')), acceptance_tests=tuple(str(test) for test in acceptance_tests), commit_policy=str(payload.get('commit_policy', '')), reporting_contract=str(payload.get('reporting_contract', '')), escalation_policy=str(payload.get('escalation_policy', '')), ) validate_task_packet(packet) return packet class TaskPacketValidationError(ValueError): def __init__(self, errors: list[str]): self.errors = errors super().__init__('; '.join(errors)) def validate_task_packet(packet: TaskPacket) -> None: errors: list[str] = [] for field_name in ( 'objective', 'scope', 'repo', 'branch_policy', 'commit_policy', 'reporting_contract', 'escalation_policy', ): if not getattr(packet, field_name).strip(): errors.append(f'{field_name} must not be empty') for index, test in enumerate(packet.acceptance_tests): if not test.strip(): errors.append(f'acceptance_tests contains an empty value at index {index}') if errors: raise TaskPacketValidationError(errors) def load_task_packet(path: str | Path) -> TaskPacket: packet_path = Path(path) payload = json.loads(packet_path.read_text(encoding='utf-8')) if not isinstance(payload, dict): raise TaskPacketValidationError(['task packet must be a JSON object']) return TaskPacket.from_dict(payload) def render_worker_prompt(packet: TaskPacket) -> str: lines = [ 'You are operating as a local worker node in a managed coding harness.', f'Objective: {packet.objective}', f'Scope: {packet.scope}', f'Repository: {packet.repo}', f'Branch policy: {packet.branch_policy}', f'Commit policy: {packet.commit_policy}', f'Reporting contract: {packet.reporting_contract}', f'Escalation policy: {packet.escalation_policy}', ] if packet.acceptance_tests: lines.append('Acceptance tests the manager will run after you finish:') lines.extend(f'- {command}' for command in packet.acceptance_tests) lines.append('Make the required code or documentation changes in the workspace and return a concise final answer describing what changed and any blockers.') return '\n'.join(lines)