Skip to content

Commit 226e537

Browse files
authored
Merge pull request #14 from valory-xyz/feat/multi-agent-middleware
Feat: Quickstart
2 parents 4408dbf + 030cdf4 commit 226e537

18 files changed

+1959
-15
lines changed

operate/cli.py

+160-2
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,16 @@
4242

4343
from operate import services
4444
from operate.account.user import UserAccount
45-
from operate.constants import KEY, KEYS, OPERATE, SERVICES
45+
from operate.constants import KEY, KEYS, OPERATE_HOME, SERVICES
4646
from operate.ledger.profiles import DEFAULT_NEW_SAFE_FUNDS_AMOUNT
4747
from operate.operate_types import Chain, DeploymentStatus, LedgerType
48+
from operate.quickstart.analyse_logs import analyse_logs
49+
from operate.quickstart.claim_staking_rewards import claim_staking_rewards
50+
from operate.quickstart.reset_password import reset_password
51+
from operate.quickstart.reset_staking import reset_staking
52+
from operate.quickstart.run_service import run_service
53+
from operate.quickstart.stop_service import stop_service
54+
from operate.quickstart.terminate_on_chain_service import terminate_service
4855
from operate.services.health_checker import HealthChecker
4956
from operate.wallet.master import MasterWalletManager
5057

@@ -75,7 +82,7 @@ def __init__(
7582
) -> None:
7683
"""Initialize object."""
7784
super().__init__()
78-
self._path = (home or (Path.cwd() / OPERATE)).resolve()
85+
self._path = (home or OPERATE_HOME).resolve()
7986
self._services = self._path / SERVICES
8087
self._keys = self._path / KEYS
8188
self._master_key = self._path / KEY
@@ -936,6 +943,157 @@ def _daemon(
936943
server.run()
937944

938945

946+
@_operate.command(name="quickstart")
947+
def qs_start(
948+
config: Annotated[str, params.String(help="Quickstart config file path")],
949+
attended: Annotated[
950+
str, params.String(help="Run in attended/unattended mode (default: true")
951+
] = "true",
952+
) -> None:
953+
"""Quickstart."""
954+
os.environ["ATTENDED"] = attended.lower()
955+
operate = OperateApp()
956+
operate.setup()
957+
run_service(operate=operate, config_path=config)
958+
959+
960+
@_operate.command(name="quickstop")
961+
def qs_stop(
962+
config: Annotated[str, params.String(help="Quickstart config file path")],
963+
) -> None:
964+
"""Quickstart."""
965+
operate = OperateApp()
966+
operate.setup()
967+
stop_service(operate=operate, config_path=config)
968+
969+
970+
@_operate.command(name="terminate")
971+
def qs_terminate(
972+
config: Annotated[str, params.String(help="Quickstart config file path")],
973+
attended: Annotated[
974+
str, params.String(help="Run in attended/unattended mode (default: true")
975+
] = "true",
976+
) -> None:
977+
"""Terminate service."""
978+
os.environ["ATTENDED"] = attended.lower()
979+
operate = OperateApp()
980+
operate.setup()
981+
terminate_service(operate=operate, config_path=config)
982+
983+
984+
@_operate.command(name="claim")
985+
def qs_claim(
986+
config: Annotated[str, params.String(help="Quickstart config file path")],
987+
attended: Annotated[
988+
str, params.String(help="Run in attended/unattended mode (default: true")
989+
] = "true",
990+
) -> None:
991+
"""Quickclaim staking rewards."""
992+
os.environ["ATTENDED"] = attended.lower()
993+
operate = OperateApp()
994+
operate.setup()
995+
claim_staking_rewards(operate=operate, config_path=config)
996+
997+
998+
@_operate.command(name="reset-staking")
999+
def qs_reset_staking(
1000+
config: Annotated[str, params.String(help="Quickstart config file path")],
1001+
attended: Annotated[
1002+
str, params.String(help="Run in attended/unattended mode (default: true")
1003+
] = "true",
1004+
) -> None:
1005+
"""Reset staking."""
1006+
os.environ["ATTENDED"] = attended.lower()
1007+
operate = OperateApp()
1008+
operate.setup()
1009+
reset_staking(operate=operate, config_path=config)
1010+
1011+
1012+
@_operate.command(name="reset-password")
1013+
def qs_reset_password(
1014+
attended: Annotated[
1015+
str, params.String(help="Run in attended/unattended mode (default: true")
1016+
] = "true",
1017+
) -> None:
1018+
"""Reset password."""
1019+
os.environ["ATTENDED"] = attended.lower()
1020+
operate = OperateApp()
1021+
operate.setup()
1022+
reset_password(operate=operate)
1023+
1024+
1025+
@_operate.command(name="analyse-logs")
1026+
def qs_analyse_logs( # pylint: disable=too-many-arguments
1027+
config: Annotated[str, params.String(help="Quickstart config file path")],
1028+
from_dir: Annotated[
1029+
str,
1030+
params.String(
1031+
help="Path to the logs directory. If not provided, it is auto-detected.",
1032+
default="",
1033+
),
1034+
],
1035+
agent: Annotated[
1036+
str,
1037+
params.String(
1038+
help="The agent name to analyze (default: 'aea_0').", default="aea_0"
1039+
),
1040+
],
1041+
reset_db: Annotated[
1042+
bool,
1043+
params.Boolean(
1044+
help="Use this flag to disable resetting the log database.", default=False
1045+
),
1046+
],
1047+
start_time: Annotated[
1048+
str,
1049+
params.String(help="Start time in `YYYY-MM-DD H:M:S,MS` format.", default=""),
1050+
],
1051+
end_time: Annotated[
1052+
str, params.String(help="End time in `YYYY-MM-DD H:M:S,MS` format.", default="")
1053+
],
1054+
log_level: Annotated[
1055+
str,
1056+
params.String(
1057+
help="Logging level. (INFO, DEBUG, WARNING, ERROR, CRITICAL)",
1058+
default="INFO",
1059+
),
1060+
],
1061+
period: Annotated[int, params.Integer(help="Period ID.", default="")],
1062+
round: Annotated[ # pylint: disable=redefined-builtin
1063+
str, params.String(help="Round name.", default="")
1064+
],
1065+
behaviour: Annotated[str, params.String(help="Behaviour name filter.", default="")],
1066+
fsm: Annotated[
1067+
bool, params.Boolean(help="Print only the FSM execution path.", default=False)
1068+
],
1069+
include_regex: Annotated[
1070+
str, params.String(help="Regex pattern to include in the result.", default="")
1071+
],
1072+
exclude_regex: Annotated[
1073+
str, params.String(help="Regex pattern to exclude from the result.", default="")
1074+
],
1075+
) -> None:
1076+
"""Analyse the logs of an agent."""
1077+
operate = OperateApp()
1078+
operate.setup()
1079+
analyse_logs(
1080+
operate=operate,
1081+
config_path=config,
1082+
from_dir=from_dir,
1083+
agent=agent,
1084+
reset_db=reset_db,
1085+
start_time=start_time,
1086+
end_time=end_time,
1087+
log_level=log_level,
1088+
period=period,
1089+
round=round,
1090+
behaviour=behaviour,
1091+
fsm=fsm,
1092+
include_regex=include_regex,
1093+
exclude_regex=exclude_regex,
1094+
)
1095+
1096+
9391097
def main() -> None:
9401098
"""CLI entry point."""
9411099
run(cli=_operate)

operate/constants.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919

2020
"""Constants."""
2121

22+
from pathlib import Path
23+
24+
2225
OPERATE = ".operate"
26+
OPERATE_HOME = Path.cwd() / OPERATE
2327
CONFIG = "config.json"
2428
SERVICES = "services"
2529
KEYS = "keys"
@@ -31,12 +35,20 @@
3135
KEYS_JSON = "keys.json"
3236
DOCKER_COMPOSE_YAML = "docker-compose.yaml"
3337
SERVICE_YAML = "service.yaml"
38+
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
3439

3540
ON_CHAIN_INTERACT_TIMEOUT = 120.0
3641
ON_CHAIN_INTERACT_RETRIES = 10
3742
ON_CHAIN_INTERACT_SLEEP = 3.0
3843

3944
HEALTH_CHECK_URL = "http://127.0.0.1:8716/healthcheck" # possible DNS issues on windows so use IP address
40-
45+
SAFE_WEBAPP_URL = "https://app.safe.global/home?safe=gno:"
4146
TM_CONTROL_URL = "http://localhost:8080"
42-
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
47+
IPFS_ADDRESS = "https://gateway.autonolas.tech/ipfs/f01701220{hash}"
48+
49+
# TODO: These links may break in the future, use a more robust approach
50+
MECH_CONTRACT_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/mech/refs/tags/v0.8.0/packages/valory/contracts/agent_mech/build/AgentMech.json"
51+
STAKING_TOKEN_INSTANCE_ABI_PATH = "https://raw.githubusercontent.com/valory-xyz/trader/refs/tags/v0.23.0/packages/valory/contracts/staking_token/build/StakingToken.json" # nosec
52+
MECH_ACTIVITY_CHECKER_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/autonolas-staking-programmes/refs/heads/main/abis/0.8.25/SingleMechActivityChecker.json"
53+
SERVICE_REGISTRY_TOKEN_UTILITY_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/open-autonomy/refs/tags/v0.18.4/packages/valory/contracts/service_registry_token_utility/build/ServiceRegistryTokenUtility.json" # nosec
54+
MECH_AGENT_FACTORY_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/ai-registry-mech/main/abis/0.8.25/AgentFactory.json"

operate/ledger/profiles.py

+17
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@
8787
"pearl_beta_5": "0x4Abe376Fda28c2F43b84884E5f822eA775DeA9F4",
8888
"pearl_beta_6": "0x6C6D01e8eA8f806eF0c22F0ef7ed81D868C1aB39",
8989
"pearl_beta_mech_marketplace": "0xDaF34eC46298b53a3d24CBCb431E84eBd23927dA",
90+
"quickstart_beta_hobbyist": "0x389B46c259631Acd6a69Bde8B6cEe218230bAE8C",
91+
"quickstart_beta_hobbyist_2": "0x238EB6993b90a978ec6AAD7530d6429c949C08DA",
92+
"quickstart_beta_expert": "0x5344B7DD311e5d3DdDd46A4f71481bD7b05AAA3e",
93+
"quickstart_beta_expert_2": "0xb964e44c126410df341ae04B13aB10A985fE3513",
94+
"quickstart_beta_expert_3": "0x80faD33Cadb5F53f9D29F02Db97D682E8b101618",
95+
"quickstart_beta_expert_4": "0xaD9d891134443B443D7F30013c7e14Fe27F2E029",
96+
"quickstart_beta_expert_5": "0xE56dF1E563De1B10715cB313D514af350D207212",
97+
"quickstart_beta_expert_6": "0x2546214aEE7eEa4bEE7689C81231017CA231Dc93",
98+
"quickstart_beta_expert_7": "0xD7A3C8b975f71030135f1a66e9e23164d54fF455",
99+
"quickstart_beta_expert_8": "0x356C108D49C5eebd21c84c04E9162de41933030c",
100+
"quickstart_beta_expert_9": "0x17dBAe44BC5618Cc254055b386A29576b4F87015",
101+
"quickstart_beta_expert_10": "0xB0ef657b8302bd2c74B6E6D9B2b4b39145b19c6f",
102+
"quickstart_beta_expert_11": "0x3112c1613eAC3dBAE3D4E38CeF023eb9E2C91CF7",
103+
"quickstart_beta_expert_12": "0xF4a75F476801B3fBB2e7093aCDcc3576593Cc1fc",
104+
"quickstart_beta_expert_15_mech_marketplace": "0x88eB38FF79fBa8C19943C0e5Acfa67D5876AdCC1",
105+
"quickstart_beta_expert_16_mech_marketplace": "0x6c65430515c70a3f5E62107CC301685B7D46f991",
106+
"mech_marketplace": "0x998dEFafD094817EF329f6dc79c703f1CF18bC90",
90107
},
91108
Chain.OPTIMISTIC: {
92109
"optimus_alpha": "0x88996bbdE7f982D93214881756840cE2c77C4992",

operate/quickstart/analyse_logs.py

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# ------------------------------------------------------------------------------
2+
#
3+
# Copyright 2023-2025 Valory AG
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# ------------------------------------------------------------------------------
18+
"""Quickstart script to run log analysis."""
19+
20+
21+
import json
22+
import os
23+
import subprocess # nosec
24+
import sys
25+
from pathlib import Path
26+
from typing import List, TYPE_CHECKING, Union
27+
28+
from operate.constants import DEPLOYMENT
29+
30+
31+
if TYPE_CHECKING:
32+
from operate.cli import OperateApp
33+
34+
35+
def find_build_directory(config_file: Path, operate: "OperateApp") -> Path:
36+
"""Find the appropriate build directory of the configured service."""
37+
with open(config_file, "r") as f:
38+
config = json.load(f)
39+
config_service_hash = config.get("hash")
40+
41+
services = operate.service_manager()._get_all_services()
42+
for service in services:
43+
if service.hash == config_service_hash:
44+
build_dir = service.path / DEPLOYMENT
45+
if not build_dir.exists():
46+
print(f"{config.get('name')} not deployed.")
47+
sys.exit(1)
48+
return build_dir
49+
50+
print(f"{config.get('name')} not found.")
51+
sys.exit(1)
52+
53+
54+
def run_analysis(logs_dir: Path, **kwargs: str) -> None:
55+
"""Run the log analysis command."""
56+
command: List[Union[str, Path]] = [
57+
"poetry",
58+
"run",
59+
"autonomy",
60+
"analyse",
61+
"logs",
62+
"--from-dir",
63+
logs_dir,
64+
]
65+
if "agent" in kwargs:
66+
command.extend(["--agent", kwargs["agent"]])
67+
if "reset_db" in kwargs:
68+
command.extend(["--reset-db"])
69+
if "start_time" in kwargs:
70+
command.extend(["--start-time", kwargs["start_time"]])
71+
if "end_time" in kwargs:
72+
command.extend(["--end-time", kwargs["end_time"]])
73+
if "log_level" in kwargs:
74+
command.extend(["--log-level", kwargs["log_level"]])
75+
if "period" in kwargs:
76+
command.extend(["--period", kwargs["period"]])
77+
if "round" in kwargs:
78+
command.extend(["--round", kwargs["round"]])
79+
if "behaviour" in kwargs:
80+
command.extend(["--behaviour", kwargs["behaviour"]])
81+
if "fsm" in kwargs:
82+
command.extend(["--fsm"])
83+
if "include_regex" in kwargs:
84+
command.extend(["--include-regex", kwargs["include_regex"]])
85+
if "exclude_regex" in kwargs:
86+
command.extend(["--exclude-regex", kwargs["exclude_regex"]])
87+
88+
try:
89+
subprocess.run(command, check=True) # nosec
90+
print("Analysis completed successfully.")
91+
except subprocess.CalledProcessError as e:
92+
print(f"Command failed with exit code {e.returncode}")
93+
sys.exit(e.returncode)
94+
except FileNotFoundError:
95+
print("Poetry or autonomy not found. Ensure they are installed and accessible.")
96+
sys.exit(1)
97+
98+
99+
def analyse_logs(
100+
operate: "OperateApp",
101+
config_path: str,
102+
**kwargs: str,
103+
) -> None:
104+
"""Run the log analysis command."""
105+
config_file = Path(config_path)
106+
if not config_file.exists():
107+
print(f"Config file '{config_file}' not found.")
108+
sys.exit(1)
109+
110+
# Auto-detect the logs directory
111+
build_dir = find_build_directory(config_file, operate)
112+
logs_dir = build_dir / "persistent_data" / "logs"
113+
if not os.path.exists(logs_dir):
114+
print(f"Logs directory '{logs_dir}' not found.")
115+
sys.exit(1)
116+
117+
# Run the analysis
118+
run_analysis(logs_dir, **kwargs)

0 commit comments

Comments
 (0)