agento11y-harbor 0.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ SPDX-License-Identifier: Apache-2.0
2
+
3
+ See /LICENSE at repository root for full license text.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: agento11y-harbor
3
+ Version: 0.20.0
4
+ Summary: Harbor live evaluation and job import for the Grafana Agent Observability Python SDK
5
+ License: SPDX-License-Identifier: Apache-2.0
6
+
7
+ See /LICENSE at repository root for full license text.
8
+
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: agento11y>=0.21.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=8.2.0; extra == "dev"
15
+ Dynamic: license-file
16
+
17
+ # Harbor integration
18
+
19
+ `agento11y-harbor` publishes [Harbor](https://github.com/harbor-framework/harbor)
20
+ evaluations to Agent Observability in Grafana Cloud. You can publish a job
21
+ while it runs, import a job that already finished, or run explicit Harbor
22
+ trials from Python.
23
+
24
+ ## Install
25
+
26
+ Install the package in the same Python environment as Harbor, so Harbor can
27
+ discover the `agento11y` job plugin. With a `uv` tool install:
28
+
29
+ ```bash
30
+ uv tool install harbor --with-executables-from agento11y-harbor
31
+ ```
32
+
33
+ Or with `pip`, in Harbor's environment:
34
+
35
+ ```bash
36
+ pip install agento11y-harbor
37
+ ```
38
+
39
+ The package needs `agento11y` 0.20.0 or later and a Harbor release that
40
+ supports job plugins (`harbor run --plugin`).
41
+
42
+ ## Configure Grafana Cloud
43
+
44
+ Set these environment variables before you run or import a job:
45
+
46
+ - `AGENTO11Y_ENDPOINT`: your Grafana Cloud Agent Observability endpoint.
47
+ - `AGENTO11Y_AUTH_TOKEN` and, when your stack requires it,
48
+ `AGENTO11Y_AUTH_TENANT_ID`: your Grafana Cloud ingestion credentials.
49
+ - `AGENTO11Y_GRAFANA_URL`: your Grafana Cloud stack URL, for experiment links.
50
+ - `AGENTO11Y_SERVICE_ACCOUNT_TOKEN`: a Grafana service account token. You need
51
+ it only to publish a suite checkpoint with `--publish-suite`.
52
+
53
+ Refer to the [Grafana Cloud setup guide](https://grafana.com/docs/grafana-cloud/machine-learning/agent-observability/get-started/grafana-cloud/)
54
+ for the credentials. `agento11y harbor` also loads the credentials that
55
+ `agento11y login` saved.
56
+
57
+ ## Publish a job while it runs
58
+
59
+ Run Harbor through the `agento11y` CLI. Every argument after `--` goes to
60
+ `harbor run` unchanged:
61
+
62
+ ```bash
63
+ agento11y harbor -- \
64
+ --dataset terminal-bench@2.0 \
65
+ --agent codex \
66
+ --model openai/gpt-5
67
+ ```
68
+
69
+ `agento11y harbor run -- ...` is the same command. The launcher adds
70
+ `--plugin agento11y`, so `harbor run --plugin agento11y ...` works too. Pass
71
+ plugin options with `--plugin-kwarg`, for example
72
+ `--plugin-kwarg pass_threshold=0.5`. The options are `name`, `experiment_id`,
73
+ `main_reward` (default `reward`), `pass_threshold` (default `1.0`), and
74
+ `upload_artifacts` (default `true`).
75
+
76
+ The plugin creates one experiment per agent, model, and dataset. Each trial
77
+ appears when Harbor starts its agent, after the environment and agent setup,
78
+ so the experiment records the agent version Harbor detects. A trial whose
79
+ setup fails appears when it ends. When the trial ends, the plugin attaches its
80
+ rewards, duration, tokens, cost, errors, and verifier artifacts. The main
81
+ reward is the pass/fail verdict, and other rewards are diagnostics.
82
+
83
+ If your agent publishes its own conversation, put its ID in
84
+ `AgentContext.metadata["agento11y.conversation_id"]`, and the trial links to
85
+ it. Otherwise, the plugin imports the trial's ATIF trajectory as a
86
+ conversation. ATIF import creates generations, not traces. Each tool call's
87
+ arguments keep their first 4 KiB, and each step's prompt and response keep
88
+ their first 1 MiB, so one large tool result can't fail the import.
89
+
90
+ ## Import a completed job
91
+
92
+ Publish a Harbor job directory that already finished. Harbor writes jobs to
93
+ `jobs/` in the directory you ran it from, unless you pass `--jobs-dir`:
94
+
95
+ ```bash
96
+ agento11y harbor export jobs/<job-name>
97
+ ```
98
+
99
+ The importer reads Harbor's `result.json`, ATIF, reward, and verifier files.
100
+ It doesn't need Harbor installed. Run `agento11y harbor export --help` for the
101
+ flags:
102
+
103
+ - `--experiment-id`: the base experiment ID. The default is the Harbor job ID.
104
+ - `--suite-id`: the suite ID for the imported cases. The default is derived
105
+ from the dataset. On its own, it doesn't publish anything.
106
+ - `--publish-suite subset|replace`: store the job's tasks as a published suite
107
+ checkpoint before the import. `subset` adds and updates cases. `replace` also
108
+ removes stored cases that aren't in the job.
109
+ - `--main-reward`, `--pass-threshold`, and `--no-artifacts`: the same options
110
+ as the plugin.
111
+
112
+ From Python, call `publish_harbor_job`:
113
+
114
+ ```python
115
+ from agento11y.experiments import TestSuitesClient
116
+ from agento11y_harbor import publish_harbor_job
117
+
118
+ published = publish_harbor_job(
119
+ "jobs/my-job",
120
+ test_suites_client=TestSuitesClient(),
121
+ suite_publication_policy="subset",
122
+ )
123
+ for run in published.runs:
124
+ print(run.url)
125
+ ```
126
+
127
+ ## Experiment IDs
128
+
129
+ The plugin and the importer use the same experiment ID:
130
+ `<experiment_id or Harbor job ID>-<candidate hash>`. The hash comes from the
131
+ agent name, model, and dataset, so it's the same for every run of the same
132
+ candidate.
133
+
134
+ A finalized experiment can't change. When you import a job, a candidate whose
135
+ experiment is already finalized, either live or by an earlier import, is
136
+ skipped with a warning, and the import fails if every candidate is. Pass a new
137
+ `--experiment-id` to publish the job as separate experiments. Resuming a
138
+ Harbor job also reuses the job ID, so pass a new `experiment_id` plugin option
139
+ when the job's experiments already finalized.
140
+
141
+ ## Errors and retries
142
+
143
+ Publishing problems never stop the Harbor job. When a trial can't be
144
+ published, or one of its files can't be read, that trial is marked errored and
145
+ the rest of the job continues. The plugin logs a warning. The errored trial's
146
+ rewards stay diagnostic.
147
+
148
+ An agent timeout or non-zero exit doesn't error a trial when Harbor's verifier
149
+ still produced the main reward: like Harbor's own metrics, the reward decides
150
+ pass or fail, and the agent error is kept in the score metadata. Any other
151
+ Harbor exception, or a missing main reward, errors the trial.
152
+
153
+ Any errored trial finalizes its experiment as failed, and the experiment error
154
+ lists the errored trials. An importer skips a trial whose `result.json` it
155
+ can't read and logs a warning. Each candidate is imported on its own, so one
156
+ that fails to publish doesn't stop the others. The export command lists it and
157
+ exits with status 1, and rerunning the command publishes only what's missing.
158
+
159
+ If the plugin can't create an experiment, the candidate's next trial tries
160
+ again. Once it exists, the experiment is finalized as failed, because it's
161
+ missing the trials that started before then. If it never exists, the plugin logs
162
+ how many trials it left out when the job ends. Either way, export the job
163
+ directory to publish every trial, with a new `--experiment-id` if the
164
+ experiment exists.
165
+
166
+ If the Harbor process is interrupted, for example with Ctrl-C, the plugin marks
167
+ trials without a result errored and finalizes the experiments as failed. To
168
+ publish the finished trials, export the job directory with a new
169
+ `--experiment-id`.
170
+
171
+ Harbor retries a failed trial under the same trial name. The plugin keeps one
172
+ trial per name and publishes only the final attempt, so a failure that passes
173
+ on retry counts once, as a pass. The importer sees the same result, because
174
+ Harbor deletes the files of superseded attempts. Separate attempts from
175
+ `--n-attempts` are separate trials. Their attempt numbers follow the order
176
+ their agents start live and trial-name order on import, so compare them by experiment, not by
177
+ attempt number.
178
+
179
+ ## Run trials from Python
180
+
181
+ `run_harbor` runs explicit Harbor `TrialConfig` objects for one agent and
182
+ model, one case at a time. It needs Harbor installed:
183
+
184
+ ```python
185
+ from agento11y_harbor import run_harbor
186
+
187
+ run = await run_harbor(suite, configs, name="Harbor regression", agent_env=otel_env)
188
+ print(run.url)
189
+ ```
190
+
191
+ `configs` maps each suite case ID to a `harbor.models.trial.config.TrialConfig`.
192
+ The runner adds `agent_env`, `TRACEPARENT`, `TRACESTATE`, and the
193
+ `AGENTO11Y_*` trial identity to each agent's environment. A Python agent can
194
+ continue the trace and read the trial with `TrialRef.from_env()`:
195
+
196
+ ```python
197
+ import os
198
+ from opentelemetry import context, propagate
199
+
200
+ carrier = {key.lower(): os.environ[key] for key in ("TRACEPARENT", "TRACESTATE") if key in os.environ}
201
+ token = context.attach(propagate.extract(carrier))
202
+ try:
203
+ run_instrumented_agent()
204
+ finally:
205
+ context.detach(token)
206
+ ```
207
+
208
+ The live plugin can't add environment variables to agents, because Harbor
209
+ creates each agent before the plugin's hooks run. With the plugin, link
210
+ conversations through `AgentContext.metadata` instead.
211
+
212
+ `run_harbor` doesn't retry trials or run them concurrently. Use
213
+ `agento11y harbor` for Harbor's scheduler, retries, and datasets.
@@ -0,0 +1,197 @@
1
+ # Harbor integration
2
+
3
+ `agento11y-harbor` publishes [Harbor](https://github.com/harbor-framework/harbor)
4
+ evaluations to Agent Observability in Grafana Cloud. You can publish a job
5
+ while it runs, import a job that already finished, or run explicit Harbor
6
+ trials from Python.
7
+
8
+ ## Install
9
+
10
+ Install the package in the same Python environment as Harbor, so Harbor can
11
+ discover the `agento11y` job plugin. With a `uv` tool install:
12
+
13
+ ```bash
14
+ uv tool install harbor --with-executables-from agento11y-harbor
15
+ ```
16
+
17
+ Or with `pip`, in Harbor's environment:
18
+
19
+ ```bash
20
+ pip install agento11y-harbor
21
+ ```
22
+
23
+ The package needs `agento11y` 0.20.0 or later and a Harbor release that
24
+ supports job plugins (`harbor run --plugin`).
25
+
26
+ ## Configure Grafana Cloud
27
+
28
+ Set these environment variables before you run or import a job:
29
+
30
+ - `AGENTO11Y_ENDPOINT`: your Grafana Cloud Agent Observability endpoint.
31
+ - `AGENTO11Y_AUTH_TOKEN` and, when your stack requires it,
32
+ `AGENTO11Y_AUTH_TENANT_ID`: your Grafana Cloud ingestion credentials.
33
+ - `AGENTO11Y_GRAFANA_URL`: your Grafana Cloud stack URL, for experiment links.
34
+ - `AGENTO11Y_SERVICE_ACCOUNT_TOKEN`: a Grafana service account token. You need
35
+ it only to publish a suite checkpoint with `--publish-suite`.
36
+
37
+ Refer to the [Grafana Cloud setup guide](https://grafana.com/docs/grafana-cloud/machine-learning/agent-observability/get-started/grafana-cloud/)
38
+ for the credentials. `agento11y harbor` also loads the credentials that
39
+ `agento11y login` saved.
40
+
41
+ ## Publish a job while it runs
42
+
43
+ Run Harbor through the `agento11y` CLI. Every argument after `--` goes to
44
+ `harbor run` unchanged:
45
+
46
+ ```bash
47
+ agento11y harbor -- \
48
+ --dataset terminal-bench@2.0 \
49
+ --agent codex \
50
+ --model openai/gpt-5
51
+ ```
52
+
53
+ `agento11y harbor run -- ...` is the same command. The launcher adds
54
+ `--plugin agento11y`, so `harbor run --plugin agento11y ...` works too. Pass
55
+ plugin options with `--plugin-kwarg`, for example
56
+ `--plugin-kwarg pass_threshold=0.5`. The options are `name`, `experiment_id`,
57
+ `main_reward` (default `reward`), `pass_threshold` (default `1.0`), and
58
+ `upload_artifacts` (default `true`).
59
+
60
+ The plugin creates one experiment per agent, model, and dataset. Each trial
61
+ appears when Harbor starts its agent, after the environment and agent setup,
62
+ so the experiment records the agent version Harbor detects. A trial whose
63
+ setup fails appears when it ends. When the trial ends, the plugin attaches its
64
+ rewards, duration, tokens, cost, errors, and verifier artifacts. The main
65
+ reward is the pass/fail verdict, and other rewards are diagnostics.
66
+
67
+ If your agent publishes its own conversation, put its ID in
68
+ `AgentContext.metadata["agento11y.conversation_id"]`, and the trial links to
69
+ it. Otherwise, the plugin imports the trial's ATIF trajectory as a
70
+ conversation. ATIF import creates generations, not traces. Each tool call's
71
+ arguments keep their first 4 KiB, and each step's prompt and response keep
72
+ their first 1 MiB, so one large tool result can't fail the import.
73
+
74
+ ## Import a completed job
75
+
76
+ Publish a Harbor job directory that already finished. Harbor writes jobs to
77
+ `jobs/` in the directory you ran it from, unless you pass `--jobs-dir`:
78
+
79
+ ```bash
80
+ agento11y harbor export jobs/<job-name>
81
+ ```
82
+
83
+ The importer reads Harbor's `result.json`, ATIF, reward, and verifier files.
84
+ It doesn't need Harbor installed. Run `agento11y harbor export --help` for the
85
+ flags:
86
+
87
+ - `--experiment-id`: the base experiment ID. The default is the Harbor job ID.
88
+ - `--suite-id`: the suite ID for the imported cases. The default is derived
89
+ from the dataset. On its own, it doesn't publish anything.
90
+ - `--publish-suite subset|replace`: store the job's tasks as a published suite
91
+ checkpoint before the import. `subset` adds and updates cases. `replace` also
92
+ removes stored cases that aren't in the job.
93
+ - `--main-reward`, `--pass-threshold`, and `--no-artifacts`: the same options
94
+ as the plugin.
95
+
96
+ From Python, call `publish_harbor_job`:
97
+
98
+ ```python
99
+ from agento11y.experiments import TestSuitesClient
100
+ from agento11y_harbor import publish_harbor_job
101
+
102
+ published = publish_harbor_job(
103
+ "jobs/my-job",
104
+ test_suites_client=TestSuitesClient(),
105
+ suite_publication_policy="subset",
106
+ )
107
+ for run in published.runs:
108
+ print(run.url)
109
+ ```
110
+
111
+ ## Experiment IDs
112
+
113
+ The plugin and the importer use the same experiment ID:
114
+ `<experiment_id or Harbor job ID>-<candidate hash>`. The hash comes from the
115
+ agent name, model, and dataset, so it's the same for every run of the same
116
+ candidate.
117
+
118
+ A finalized experiment can't change. When you import a job, a candidate whose
119
+ experiment is already finalized, either live or by an earlier import, is
120
+ skipped with a warning, and the import fails if every candidate is. Pass a new
121
+ `--experiment-id` to publish the job as separate experiments. Resuming a
122
+ Harbor job also reuses the job ID, so pass a new `experiment_id` plugin option
123
+ when the job's experiments already finalized.
124
+
125
+ ## Errors and retries
126
+
127
+ Publishing problems never stop the Harbor job. When a trial can't be
128
+ published, or one of its files can't be read, that trial is marked errored and
129
+ the rest of the job continues. The plugin logs a warning. The errored trial's
130
+ rewards stay diagnostic.
131
+
132
+ An agent timeout or non-zero exit doesn't error a trial when Harbor's verifier
133
+ still produced the main reward: like Harbor's own metrics, the reward decides
134
+ pass or fail, and the agent error is kept in the score metadata. Any other
135
+ Harbor exception, or a missing main reward, errors the trial.
136
+
137
+ Any errored trial finalizes its experiment as failed, and the experiment error
138
+ lists the errored trials. An importer skips a trial whose `result.json` it
139
+ can't read and logs a warning. Each candidate is imported on its own, so one
140
+ that fails to publish doesn't stop the others. The export command lists it and
141
+ exits with status 1, and rerunning the command publishes only what's missing.
142
+
143
+ If the plugin can't create an experiment, the candidate's next trial tries
144
+ again. Once it exists, the experiment is finalized as failed, because it's
145
+ missing the trials that started before then. If it never exists, the plugin logs
146
+ how many trials it left out when the job ends. Either way, export the job
147
+ directory to publish every trial, with a new `--experiment-id` if the
148
+ experiment exists.
149
+
150
+ If the Harbor process is interrupted, for example with Ctrl-C, the plugin marks
151
+ trials without a result errored and finalizes the experiments as failed. To
152
+ publish the finished trials, export the job directory with a new
153
+ `--experiment-id`.
154
+
155
+ Harbor retries a failed trial under the same trial name. The plugin keeps one
156
+ trial per name and publishes only the final attempt, so a failure that passes
157
+ on retry counts once, as a pass. The importer sees the same result, because
158
+ Harbor deletes the files of superseded attempts. Separate attempts from
159
+ `--n-attempts` are separate trials. Their attempt numbers follow the order
160
+ their agents start live and trial-name order on import, so compare them by experiment, not by
161
+ attempt number.
162
+
163
+ ## Run trials from Python
164
+
165
+ `run_harbor` runs explicit Harbor `TrialConfig` objects for one agent and
166
+ model, one case at a time. It needs Harbor installed:
167
+
168
+ ```python
169
+ from agento11y_harbor import run_harbor
170
+
171
+ run = await run_harbor(suite, configs, name="Harbor regression", agent_env=otel_env)
172
+ print(run.url)
173
+ ```
174
+
175
+ `configs` maps each suite case ID to a `harbor.models.trial.config.TrialConfig`.
176
+ The runner adds `agent_env`, `TRACEPARENT`, `TRACESTATE`, and the
177
+ `AGENTO11Y_*` trial identity to each agent's environment. A Python agent can
178
+ continue the trace and read the trial with `TrialRef.from_env()`:
179
+
180
+ ```python
181
+ import os
182
+ from opentelemetry import context, propagate
183
+
184
+ carrier = {key.lower(): os.environ[key] for key in ("TRACEPARENT", "TRACESTATE") if key in os.environ}
185
+ token = context.attach(propagate.extract(carrier))
186
+ try:
187
+ run_instrumented_agent()
188
+ finally:
189
+ context.detach(token)
190
+ ```
191
+
192
+ The live plugin can't add environment variables to agents, because Harbor
193
+ creates each agent before the plugin's hooks run. With the plugin, link
194
+ conversations through `AgentContext.metadata` instead.
195
+
196
+ `run_harbor` doesn't retry trials or run them concurrently. Use
197
+ `agento11y harbor` for Harbor's scheduler, retries, and datasets.
@@ -0,0 +1,6 @@
1
+ """Harbor job import for Grafana Agent Observability."""
2
+
3
+ from .integration import PublishedHarborJob, PublishedHarborRun, publish_harbor_job
4
+ from .runner import run_harbor
5
+
6
+ __all__ = ["PublishedHarborJob", "PublishedHarborRun", "publish_harbor_job", "run_harbor"]
@@ -0,0 +1,114 @@
1
+ """Command-line bridge used by ``agento11y harbor``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import shutil
8
+ import sys
9
+ from collections.abc import Sequence
10
+
11
+ from agento11y.experiments import TestSuitesClient
12
+
13
+ from .integration import publish_harbor_job
14
+
15
+
16
+ def _export(argv: Sequence[str]) -> int:
17
+ parser = argparse.ArgumentParser(
18
+ prog="agento11y harbor export",
19
+ description="Publish a completed Harbor job directory as one experiment per agent/model candidate.",
20
+ )
21
+ parser.add_argument("job_dir", help="Harbor job directory that contains the job result.json")
22
+ parser.add_argument(
23
+ "--experiment-id",
24
+ default="",
25
+ help="Base experiment ID (default: the Harbor job ID). A candidate hash is appended.",
26
+ )
27
+ parser.add_argument("--name", default="", help="Base experiment name (default: Harbor <job directory name>)")
28
+ parser.add_argument(
29
+ "--suite-id",
30
+ default="",
31
+ help="Test suite ID for the imported cases (default: derived from the Harbor dataset). "
32
+ "Combine with --publish-suite to store the cases in that suite.",
33
+ )
34
+ parser.add_argument(
35
+ "--publish-suite",
36
+ choices=("subset", "replace"),
37
+ default=None,
38
+ help="Publish the job's tasks as a stored suite checkpoint before importing. "
39
+ "subset adds or updates cases; replace also removes cases missing from the job. "
40
+ "Needs AGENTO11Y_GRAFANA_URL and AGENTO11Y_SERVICE_ACCOUNT_TOKEN.",
41
+ )
42
+ parser.add_argument("--main-reward", default="reward", help="Reward that decides pass or fail (default: reward)")
43
+ parser.add_argument(
44
+ "--pass-threshold",
45
+ type=float,
46
+ default=1.0,
47
+ help="Minimum main reward that counts as a pass (default: 1.0)",
48
+ )
49
+ parser.add_argument("--no-artifacts", action="store_true", help="Skip uploading trajectories and verifier logs")
50
+ args = parser.parse_args(list(argv))
51
+ try:
52
+ published = publish_harbor_job(
53
+ args.job_dir,
54
+ experiment_id=args.experiment_id,
55
+ experiment_name=args.name,
56
+ suite_id=args.suite_id,
57
+ test_suites_client=TestSuitesClient() if args.publish_suite else None,
58
+ suite_publication_policy=args.publish_suite,
59
+ main_reward=args.main_reward,
60
+ pass_threshold=args.pass_threshold,
61
+ upload_artifacts=not args.no_artifacts,
62
+ )
63
+ except ValueError as error:
64
+ print(f"agento11y: {error}", file=sys.stderr)
65
+ return 1
66
+ for run in published.runs:
67
+ print(run.url or run.experiment_id)
68
+ for label in published.skipped:
69
+ print(f"agento11y: skipped {label}: its experiment is already finalized", file=sys.stderr)
70
+ for failure in published.failed:
71
+ print(f"agento11y: could not publish {failure}", file=sys.stderr)
72
+ if published.failed:
73
+ print("agento11y: rerun the same command to publish only the missing experiments", file=sys.stderr)
74
+ return 1
75
+ return 0
76
+
77
+
78
+ def _run(argv: Sequence[str]) -> int:
79
+ harbor = shutil.which("harbor")
80
+ if harbor is None:
81
+ print("agento11y: Harbor is not installed or is not on PATH", file=sys.stderr)
82
+ print("Install Harbor, then retry this command.", file=sys.stderr)
83
+ return 1
84
+ args = list(argv)
85
+ if args[:1] == ["--"]:
86
+ args = args[1:]
87
+ plugins = [args[index + 1] for index, arg in enumerate(args[:-1]) if arg == "--plugin"]
88
+ plugins.extend(arg.removeprefix("--plugin=") for arg in args if arg.startswith("--plugin="))
89
+ if "agento11y" not in plugins:
90
+ args = ["--plugin", "agento11y", *args]
91
+ os.execvpe(harbor, [harbor, "run", *args], os.environ)
92
+ return 0
93
+
94
+
95
+ def main(argv: Sequence[str] | None = None) -> int:
96
+ args = list(sys.argv[1:] if argv is None else argv)
97
+ if not args or args[0] in {"-h", "--help", "help"}:
98
+ print(
99
+ "Usage:\n"
100
+ " agento11y harbor [run] -- <harbor run arguments>\n"
101
+ " agento11y harbor export <job-directory> [flags]\n"
102
+ "\n"
103
+ "Run 'agento11y harbor export --help' for export flags."
104
+ )
105
+ return 0
106
+ if args[0] == "export":
107
+ return _export(args[1:])
108
+ if args[0] == "run":
109
+ args = args[1:]
110
+ return _run(args)
111
+
112
+
113
+ def console_main() -> None:
114
+ raise SystemExit(main())