feat: instance manifest, tool profiles, metrics, and budget enforcement
Land HARNESS-WP-0001 T01/T02/T04/T05: extend ADR-005 schedule.yml with harness fields, named tool-profile registry, ADR-004 metrics writes, and BudgetTracker wiring. CLI gains validate/profiles; task-file path kept.
This commit is contained in:
parent
16f5c29c08
commit
4144eba160
17 changed files with 1387 additions and 60 deletions
|
|
@ -1,21 +1,86 @@
|
|||
"""CLI: execute exactly one task spec."""
|
||||
"""CLI: validate instance manifests and execute exactly one task."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from agent_harness.manifest import (
|
||||
ManifestError,
|
||||
load_manifest_for_repo,
|
||||
manifest_path,
|
||||
validate_manifest,
|
||||
)
|
||||
from agent_harness.profiles import list_profiles
|
||||
from agent_harness.runner import run_task
|
||||
from agent_harness.taskspec import TaskSpec, TaskSpecError
|
||||
|
||||
|
||||
def _cmd_validate(args: argparse.Namespace) -> int:
|
||||
target = Path(args.target).expanduser().resolve()
|
||||
path = manifest_path(target)
|
||||
if not path.is_file():
|
||||
print(f"error: no instance manifest at {path}", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
manifest = load_manifest_for_repo(target)
|
||||
except ManifestError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
errors = validate_manifest(
|
||||
manifest, require_harness_fields=bool(args.strict)
|
||||
)
|
||||
if errors:
|
||||
print(f"invalid: {path}", file=sys.stderr)
|
||||
for err in errors:
|
||||
print(f" - {err}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
enabled = manifest.enabled_agents()
|
||||
print(f"ok: {path}")
|
||||
print(f" version: {manifest.version}")
|
||||
if manifest.timezone:
|
||||
print(f" timezone: {manifest.timezone}")
|
||||
if manifest.harness is not None:
|
||||
print(f" harness major: {manifest.harness}")
|
||||
print(f" agents: {len(manifest.agents)} ({len(enabled)} enabled)")
|
||||
for agent in manifest.agents:
|
||||
flags = []
|
||||
if not agent.enabled:
|
||||
flags.append("disabled")
|
||||
if agent.tool_profile:
|
||||
flags.append(f"profile={agent.tool_profile}")
|
||||
if agent.lane:
|
||||
flags.append(f"lane={agent.lane}")
|
||||
if agent.budget is not None:
|
||||
flags.append(f"budget={agent.budget}")
|
||||
suffix = f" [{', '.join(flags)}]" if flags else ""
|
||||
print(f" - {agent.name}: {agent.cadence}{suffix}")
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_profiles(_args: argparse.Namespace) -> int:
|
||||
for profile in list_profiles():
|
||||
print(f"{profile.name}\tlane={profile.lane}\t{profile.description}")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="executor-worker")
|
||||
parser = argparse.ArgumentParser(prog="agent-harness")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
run = sub.add_parser("run", help="Execute one task from a JSON spec file")
|
||||
run.add_argument("--task-file", required=True)
|
||||
run.add_argument("--no-hub", action="store_true", help="Skip hub reporting")
|
||||
run.add_argument(
|
||||
"--no-metrics",
|
||||
action="store_true",
|
||||
help="Skip writing .kaizen/metrics in the target repo",
|
||||
)
|
||||
|
||||
scan = sub.add_parser(
|
||||
"mail-scan", help="Deterministic company-mailbox scan (no LLM session)"
|
||||
)
|
||||
|
|
@ -23,11 +88,33 @@ def main(argv: list[str] | None = None) -> int:
|
|||
scan.add_argument("--config", default="integrations/mailbox-binky-company.yml")
|
||||
scan.add_argument("--out", default="mailmeta/reports")
|
||||
scan.add_argument("--no-hub", action="store_true", help="Skip hub reporting")
|
||||
|
||||
validate = sub.add_parser(
|
||||
"validate",
|
||||
help="Validate instance manifest (.kaizen/schedule.yml + harness fields)",
|
||||
)
|
||||
validate.add_argument(
|
||||
"--target",
|
||||
default=".",
|
||||
help="Consuming repo root (default: cwd)",
|
||||
)
|
||||
validate.add_argument(
|
||||
"--strict",
|
||||
action="store_true",
|
||||
help="Require lane, tool_profile, and harness pin on every enabled agent",
|
||||
)
|
||||
|
||||
sub.add_parser("profiles", help="List named tool profiles")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.command == "mail-scan":
|
||||
from pathlib import Path
|
||||
if args.command == "validate":
|
||||
return _cmd_validate(args)
|
||||
|
||||
if args.command == "profiles":
|
||||
return _cmd_profiles(args)
|
||||
|
||||
if args.command == "mail-scan":
|
||||
from agent_harness.mailscan import run_mail_scan
|
||||
|
||||
result = run_mail_scan(
|
||||
|
|
@ -56,7 +143,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||
print(f"invalid task spec: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
result = run_task(spec, report_to_hub=not args.no_hub)
|
||||
result = run_task(
|
||||
spec,
|
||||
report_to_hub=not args.no_hub,
|
||||
write_metrics=not args.no_metrics,
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
|
|
@ -64,6 +155,10 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"committed": result.committed,
|
||||
"head_after": result.head_after,
|
||||
"persona_source": result.persona_source,
|
||||
"tool_profile": result.tool_profile,
|
||||
"budget_tokens": result.budget_tokens,
|
||||
"tokens_spent": result.tokens_spent,
|
||||
"execution_time_s": round(result.execution_time_s, 3),
|
||||
"reason": result.reason,
|
||||
},
|
||||
indent=2,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue