Spaces:
Sleeping
Sleeping
File size: 7,025 Bytes
d5dc912 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | # SPDX-License-Identifier: MIT
"""bytecli agent — Agent lifecycle control (suspend, resume, tick, config)."""
from __future__ import annotations
import argparse
from typing import Any
from cli.client import ByteClient
from cli.config import CLIConfig
from cli.formatters import output, print_header, print_kv, print_success
def register(subparsers: argparse._SubParsersAction) -> None:
parser = subparsers.add_parser(
"agent",
help="Agent lifecycle and configuration",
description=(
"Control the agent's lifecycle (suspend, resume), force autonomy ticks, "
"and view or update the control plane configuration. These operations "
"affect the agent's global behavior across all channels."
),
epilog=(
"Examples:\n"
" bytecli agent suspend --reason 'deploying update'\n"
" bytecli agent resume\n"
" bytecli agent tick --force\n"
" bytecli agent config\n"
" bytecli agent config set autonomy_enabled true\n"
" bytecli agent config set budget_limit 5.00\n"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
sub = parser.add_subparsers(dest="agent_cmd", metavar="<action>")
# suspend
p_suspend = sub.add_parser(
"suspend",
help="Suspend the agent (stops all autonomous actions)",
description="Suspends the agent. While suspended, no autonomy ticks will execute and no automatic replies will be sent. POST /api/agent/suspend.",
)
p_suspend.add_argument(
"--reason",
default="",
help="Human-readable reason for suspension (e.g. 'maintenance', 'deploying')",
)
p_suspend.set_defaults(handler=_handle_suspend)
# resume
p_resume = sub.add_parser(
"resume",
help="Resume the agent (restarts autonomous actions)",
description="Resumes a previously suspended agent. Normal autonomy ticks and automatic replies will restart. POST /api/agent/resume.",
)
p_resume.add_argument("--reason", default="", help="Human-readable reason for resumption")
p_resume.set_defaults(handler=_handle_resume)
# tick
p_tick = sub.add_parser(
"tick",
help="Force an autonomy tick (executes pending goals)",
description="Forces an immediate autonomy tick cycle. The agent will evaluate all enabled goals and execute any that are due. POST /api/autonomy/tick.",
)
p_tick.add_argument(
"--force",
action="store_true",
help="Force execution even if no goals are due or conditions aren't met",
)
p_tick.set_defaults(handler=_handle_tick)
# config (show)
p_config = sub.add_parser(
"config",
help="Show or update control plane configuration",
description=(
"View the current control plane configuration (autonomy settings, budgets, "
"intervals, goals, suspension status). Use 'config set' to update individual keys."
),
epilog=(
"Settable keys include:\n"
" autonomy_enabled bool Enable/disable autonomy\n"
" budget_limit float Max cost budget\n"
" agent_suspended bool Suspend/resume flag\n"
" heartbeat_interval int Seconds between ticks\n"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p_config_sub = p_config.add_subparsers(dest="config_cmd", metavar="<action>")
p_config.set_defaults(handler=_handle_config_show)
# config set
p_set = p_config_sub.add_parser(
"set",
help="Update a config key (auto-detects type: bool/int/float/str)",
description="Update a single control plane config key. Values are auto-coerced: 'true'/'false' → bool, numbers → int/float, otherwise string. PUT /api/control-plane.",
)
p_set.add_argument("key", help="Config key to update (e.g. 'autonomy_enabled', 'budget_limit')")
p_set.add_argument(
"value",
help="New value (auto-detected type: 'true'→bool, '42'→int, '3.14'→float, else string)",
)
p_set.set_defaults(handler=_handle_config_set)
def _handle_suspend(args: argparse.Namespace, client: ByteClient, config: CLIConfig) -> None:
payload: dict[str, Any] = {}
if args.reason:
payload["reason"] = args.reason
data = client.post("/api/agent/suspend", payload)
output(data, json_mode=config.json_output, human_fn=lambda d: print_success("Agent suspended."))
def _handle_resume(args: argparse.Namespace, client: ByteClient, config: CLIConfig) -> None:
payload: dict[str, Any] = {}
if args.reason:
payload["reason"] = args.reason
data = client.post("/api/agent/resume", payload)
output(data, json_mode=config.json_output, human_fn=lambda d: print_success("Agent resumed."))
def _handle_tick(args: argparse.Namespace, client: ByteClient, config: CLIConfig) -> None:
payload = {"force": args.force}
data = client.post("/api/autonomy/tick", payload)
output(data, json_mode=config.json_output, human_fn=_human_tick)
def _human_tick(data: dict[str, Any]) -> None:
print_success("Autonomy tick executed.")
result = data.get("result", data)
goals = result.get("goals_processed", result.get("goals", []))
if goals:
print_header("Goals Processed")
for g in goals:
if isinstance(g, dict):
print_kv({"Goal": g.get("id", "—"), "Result": g.get("outcome", "—")})
else:
print_kv({"Goal": str(g)})
def _handle_config_show(args: argparse.Namespace, client: ByteClient, config: CLIConfig) -> None:
data = client.get("/api/control-plane")
output(data, json_mode=config.json_output, human_fn=_human_config)
def _human_config(data: dict[str, Any]) -> None:
cfg = data.get("config", data)
print_header("Control Plane Configuration")
# Show top-level config keys
display = {}
for key, value in cfg.items():
if key == "goals":
display["Goals Count"] = len(value) if isinstance(value, list) else value
elif isinstance(value, str | int | float | bool):
display[key] = value
print_kv(display)
def _handle_config_set(args: argparse.Namespace, client: ByteClient, config: CLIConfig) -> None:
key = args.key
raw_value = args.value
# Type coercion: try bool, int, float, then string
value: Any
if raw_value.lower() in ("true", "false"):
value = raw_value.lower() == "true"
else:
try:
value = int(raw_value)
except ValueError:
try:
value = float(raw_value)
except ValueError:
value = raw_value
payload = {key: value}
data = client.put("/api/control-plane", payload)
output(
data,
json_mode=config.json_output,
human_fn=lambda d: print_success(f"Config '{key}' updated to '{value}'."),
)
|