Implement REIN-A-0002 ops_run claim loop and approach registry.
Add activity-core ops_run client, approach selection (FI/Binky/mail/agent), claim-loop worker with lease heartbeat, CLI run --from-ops-run and claim-loop, install units, and docs demoting issue-core to legacy external tickets. T05 timer cutover remains operator after five clean cycles.
This commit is contained in:
parent
9644202eb2
commit
8200a672ea
15 changed files with 1807 additions and 73 deletions
|
|
@ -69,6 +69,48 @@ def _cmd_profiles(_args: argparse.Namespace) -> int:
|
|||
|
||||
|
||||
def _cmd_poll(args: argparse.Namespace) -> int:
|
||||
source = getattr(args, "source", "issue-core") or "issue-core"
|
||||
if source in {"ops-run", "ops_run", "ops"}:
|
||||
from rein_aharness.claim_loop import process_one, poll_peek
|
||||
from rein_aharness.ops_run_client import OpsRunError
|
||||
|
||||
try:
|
||||
if args.no_claim:
|
||||
rows = poll_peek()
|
||||
print(
|
||||
json.dumps(
|
||||
{"source": "ops-run", "queue": "empty" if not rows else "open", "items": rows},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
result = process_one(
|
||||
dry_run=bool(getattr(args, "dry_run", False)),
|
||||
report_to_hub=False if getattr(args, "no_hub", False) else True,
|
||||
)
|
||||
except OpsRunError as exc:
|
||||
print(f"ops-run error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source": "ops-run",
|
||||
"claimed": result.claimed,
|
||||
"empty": result.empty,
|
||||
"run_id": result.run_id,
|
||||
"approach": result.approach,
|
||||
"ok": result.ok,
|
||||
"ops_state": result.ops_state,
|
||||
"reason": result.reason,
|
||||
"detail": result.detail,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
if result.empty:
|
||||
return 0
|
||||
return 0 if result.ok else 1
|
||||
|
||||
from rein_aharness.intake import IntakeError, IssueCoreClient, poll_next
|
||||
|
||||
try:
|
||||
|
|
@ -78,12 +120,13 @@ def _cmd_poll(args: argparse.Namespace) -> int:
|
|||
print(f"intake error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if result is None:
|
||||
print(json.dumps({"queue": "empty"}, indent=2))
|
||||
print(json.dumps({"source": "issue-core", "queue": "empty"}, indent=2))
|
||||
return 0
|
||||
issue, spec = result
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source": "issue-core",
|
||||
"issue_id": issue.issue_id,
|
||||
"state": issue.state,
|
||||
"title": issue.title,
|
||||
|
|
@ -99,12 +142,62 @@ def _cmd_poll(args: argparse.Namespace) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def _cmd_claim_loop(args: argparse.Namespace) -> int:
|
||||
import logging
|
||||
|
||||
from rein_aharness.claim_loop import run_claim_loop
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG if args.verbose else logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||
)
|
||||
return run_claim_loop(
|
||||
once=bool(args.once),
|
||||
interval_seconds=args.interval,
|
||||
report_to_hub=not args.no_hub,
|
||||
commit=not args.no_commit,
|
||||
dry_run=bool(args.dry_run),
|
||||
max_iterations=args.max_iterations,
|
||||
)
|
||||
|
||||
|
||||
def _cmd_run(args: argparse.Namespace) -> int:
|
||||
from rein_aharness.intake import IntakeError, IssueCoreClient, poll_next
|
||||
|
||||
issue_id: str | None = None
|
||||
client: IssueCoreClient | None = None
|
||||
|
||||
if getattr(args, "from_ops_run", False):
|
||||
from rein_aharness.claim_loop import process_one
|
||||
from rein_aharness.ops_run_client import OpsRunError
|
||||
|
||||
try:
|
||||
result = process_one(
|
||||
report_to_hub=not args.no_hub,
|
||||
commit=not getattr(args, "no_commit", False),
|
||||
)
|
||||
except OpsRunError as exc:
|
||||
print(f"ops-run error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source": "ops-run",
|
||||
"ok": bool(result.ok) if result.claimed else True,
|
||||
"empty": result.empty,
|
||||
"run_id": result.run_id,
|
||||
"approach": result.approach,
|
||||
"ops_state": result.ops_state,
|
||||
"reason": result.reason,
|
||||
"detail": result.detail,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
if result.empty:
|
||||
return 0
|
||||
return 0 if result.ok else 1
|
||||
|
||||
if args.from_issue_core:
|
||||
try:
|
||||
client = IssueCoreClient()
|
||||
|
|
@ -113,14 +206,14 @@ def _cmd_run(args: argparse.Namespace) -> int:
|
|||
print(f"intake error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if polled is None:
|
||||
print(json.dumps({"ok": True, "queue": "empty"}, indent=2))
|
||||
print(json.dumps({"ok": True, "source": "issue-core", "queue": "empty"}, indent=2))
|
||||
return 0
|
||||
issue, spec = polled
|
||||
issue_id = issue.issue_id
|
||||
else:
|
||||
if not args.task_file:
|
||||
print(
|
||||
"error: provide --task-file or --from-issue-core",
|
||||
"error: provide --task-file, --from-ops-run, or --from-issue-core",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
|
@ -160,6 +253,7 @@ def _cmd_run(args: argparse.Namespace) -> int:
|
|||
json.dumps(
|
||||
{
|
||||
"ok": result.ok,
|
||||
"source": "issue-core" if issue_id else "task-file",
|
||||
"committed": result.committed,
|
||||
"head_after": result.head_after,
|
||||
"persona_source": result.persona_source,
|
||||
|
|
@ -184,16 +278,26 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
run = sub.add_parser(
|
||||
"run",
|
||||
help="Execute one task from a JSON file or next issue-core emission",
|
||||
help="Execute one task: ops_run (primary), issue-core (legacy), or task file",
|
||||
)
|
||||
run_src = run.add_mutually_exclusive_group(required=True)
|
||||
run_src.add_argument("--task-file", help="Local JSON task-spec (dev path)")
|
||||
run_src.add_argument(
|
||||
"--from-ops-run",
|
||||
action="store_true",
|
||||
help="Claim one activity-core ops_run, select approach, execute, complete/fail",
|
||||
)
|
||||
run_src.add_argument(
|
||||
"--from-issue-core",
|
||||
action="store_true",
|
||||
help="Poll issue-core for one open harness-labeled task, claim, run, close",
|
||||
help="Legacy: poll issue-core for one open harness-labeled task, claim, run, close",
|
||||
)
|
||||
run.add_argument("--no-hub", action="store_true", help="Skip hub reporting")
|
||||
run.add_argument(
|
||||
"--no-commit",
|
||||
action="store_true",
|
||||
help="With --from-ops-run: approaches that support it skip git commit",
|
||||
)
|
||||
run.add_argument(
|
||||
"--no-metrics",
|
||||
action="store_true",
|
||||
|
|
@ -329,13 +433,51 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
poll = sub.add_parser(
|
||||
"poll",
|
||||
help="Peek/claim next open issue-core task labeled for the harness (no execute)",
|
||||
help="Peek/claim next task (default source=ops-run; issue-core is legacy)",
|
||||
)
|
||||
poll.add_argument(
|
||||
"--source",
|
||||
choices=["ops-run", "issue-core"],
|
||||
default="ops-run",
|
||||
help="ops-run = activity-core claim queue (primary); issue-core = legacy tickets",
|
||||
)
|
||||
poll.add_argument(
|
||||
"--no-claim",
|
||||
action="store_true",
|
||||
help="List/map only; do not set in_progress",
|
||||
help="List/map only; do not claim",
|
||||
)
|
||||
poll.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="With ops-run: claim then fail+reopen without executing",
|
||||
)
|
||||
poll.add_argument("--no-hub", action="store_true", help="Skip hub on execute")
|
||||
|
||||
claim_loop = sub.add_parser(
|
||||
"claim-loop",
|
||||
help="Continuously claim ops_runs, select approach, execute, complete/fail",
|
||||
)
|
||||
claim_loop.add_argument(
|
||||
"--once",
|
||||
action="store_true",
|
||||
help="Process at most one claim cycle then exit",
|
||||
)
|
||||
claim_loop.add_argument(
|
||||
"--interval",
|
||||
type=float,
|
||||
default=None,
|
||||
help="Seconds between empty-queue polls (default env AGENT_HARNESS_CLAIM_INTERVAL or 30)",
|
||||
)
|
||||
claim_loop.add_argument(
|
||||
"--max-iterations",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Stop after N cycles (tests / bounded runs)",
|
||||
)
|
||||
claim_loop.add_argument("--dry-run", action="store_true")
|
||||
claim_loop.add_argument("--no-hub", action="store_true")
|
||||
claim_loop.add_argument("--no-commit", action="store_true")
|
||||
claim_loop.add_argument("-v", "--verbose", action="store_true")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
|
|
@ -348,6 +490,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if args.command == "poll":
|
||||
return _cmd_poll(args)
|
||||
|
||||
if args.command == "claim-loop":
|
||||
return _cmd_claim_loop(args)
|
||||
|
||||
if args.command == "run":
|
||||
return _cmd_run(args)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue