agentops-accelerator 0.3.0__py3-none-any.whl
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.
- agentops/__init__.py +10 -0
- agentops/__main__.py +6 -0
- agentops/agent/__init__.py +12 -0
- agentops/agent/_legacy_ids.py +92 -0
- agentops/agent/analyzer.py +207 -0
- agentops/agent/checks/__init__.py +1 -0
- agentops/agent/checks/catalog.py +880 -0
- agentops/agent/checks/errors.py +279 -0
- agentops/agent/checks/foundry_config.py +75 -0
- agentops/agent/checks/latency.py +84 -0
- agentops/agent/checks/opex.py +157 -0
- agentops/agent/checks/opex_workspace.py +874 -0
- agentops/agent/checks/posture.py +36 -0
- agentops/agent/checks/posture_rules/__init__.py +53 -0
- agentops/agent/checks/posture_rules/content_filter.py +59 -0
- agentops/agent/checks/posture_rules/diagnostics.py +74 -0
- agentops/agent/checks/posture_rules/local_auth.py +55 -0
- agentops/agent/checks/posture_rules/managed_identity.py +59 -0
- agentops/agent/checks/posture_rules/network.py +68 -0
- agentops/agent/checks/regression.py +78 -0
- agentops/agent/checks/release_readiness.py +182 -0
- agentops/agent/checks/safety.py +247 -0
- agentops/agent/checks/spec_conformance.py +375 -0
- agentops/agent/cockpit.py +5159 -0
- agentops/agent/config.py +240 -0
- agentops/agent/findings.py +113 -0
- agentops/agent/history.py +142 -0
- agentops/agent/knowledge/__init__.py +182 -0
- agentops/agent/knowledge/waf-checklist.csv +39 -0
- agentops/agent/llm_assist/__init__.py +16 -0
- agentops/agent/llm_assist/_base.py +124 -0
- agentops/agent/llm_assist/_bundle_rule.py +154 -0
- agentops/agent/llm_assist/_client.py +347 -0
- agentops/agent/llm_assist/_dataset_rules.py +191 -0
- agentops/agent/llm_assist/_engine.py +106 -0
- agentops/agent/llm_assist/_prompt_rules.py +291 -0
- agentops/agent/llm_assist/_spec_rules.py +235 -0
- agentops/agent/production_telemetry.py +430 -0
- agentops/agent/report.py +207 -0
- agentops/agent/server/__init__.py +1 -0
- agentops/agent/server/app.py +84 -0
- agentops/agent/server/auth.py +94 -0
- agentops/agent/server/chat.py +44 -0
- agentops/agent/server/protocol.py +72 -0
- agentops/agent/sources/__init__.py +1 -0
- agentops/agent/sources/azure_monitor.py +523 -0
- agentops/agent/sources/azure_resources.py +602 -0
- agentops/agent/sources/foundry_control.py +174 -0
- agentops/agent/sources/results_history.py +494 -0
- agentops/agent/sources/spec_detectors/__init__.py +42 -0
- agentops/agent/sources/spec_detectors/_base.py +58 -0
- agentops/agent/sources/spec_detectors/agents_md.py +75 -0
- agentops/agent/sources/spec_detectors/spec_kit.py +172 -0
- agentops/agent/time_range.py +117 -0
- agentops/cli/__init__.py +1 -0
- agentops/cli/app.py +4823 -0
- agentops/core/__init__.py +1 -0
- agentops/core/agentops_config.py +592 -0
- agentops/core/config_loader.py +22 -0
- agentops/core/evaluators.py +480 -0
- agentops/core/release_evidence.py +56 -0
- agentops/core/results.py +117 -0
- agentops/mcp/__init__.py +10 -0
- agentops/mcp/server.py +232 -0
- agentops/pipeline/__init__.py +8 -0
- agentops/pipeline/cloud_results.py +189 -0
- agentops/pipeline/cloud_runner.py +901 -0
- agentops/pipeline/comparison.py +108 -0
- agentops/pipeline/diagnostics.py +51 -0
- agentops/pipeline/invocations.py +535 -0
- agentops/pipeline/official_eval.py +414 -0
- agentops/pipeline/orchestrator.py +775 -0
- agentops/pipeline/prompt_deploy.py +377 -0
- agentops/pipeline/publisher.py +121 -0
- agentops/pipeline/reporter.py +202 -0
- agentops/pipeline/runtime.py +409 -0
- agentops/pipeline/thresholds.py +84 -0
- agentops/services/__init__.py +1 -0
- agentops/services/cicd.py +720 -0
- agentops/services/eval_analysis.py +848 -0
- agentops/services/evidence_pack.py +757 -0
- agentops/services/initializer.py +86 -0
- agentops/services/preflight.py +470 -0
- agentops/services/setup_wizard.py +709 -0
- agentops/services/skills.py +643 -0
- agentops/services/trace_promotion.py +300 -0
- agentops/services/workflow_analysis.py +1129 -0
- agentops/templates/.gitignore +15 -0
- agentops/templates/__init__.py +1 -0
- agentops/templates/agent-server/Dockerfile +23 -0
- agentops/templates/agent-server/README.md +61 -0
- agentops/templates/agent-server/main.bicep +94 -0
- agentops/templates/agent.yaml +87 -0
- agentops/templates/agentops.yaml +58 -0
- agentops/templates/foundry.svg +71 -0
- agentops/templates/icon.png +0 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-dev-azd.yml +118 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-dev.yml +73 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-prod-azd.yml +141 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-prod.yml +94 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-prompt-agent.yml +167 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-qa-azd.yml +118 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-qa.yml +68 -0
- agentops/templates/pipelines/azuredevops/agentops-pr-prompt-agent.yml +210 -0
- agentops/templates/pipelines/azuredevops/agentops-pr.yml +155 -0
- agentops/templates/pipelines/azuredevops/agentops-watchdog.yml +106 -0
- agentops/templates/project.gitignore +36 -0
- agentops/templates/sample-traces.jsonl +3 -0
- agentops/templates/skills/agentops-agent/SKILL.md +137 -0
- agentops/templates/skills/agentops-config/SKILL.md +113 -0
- agentops/templates/skills/agentops-dataset/SKILL.md +84 -0
- agentops/templates/skills/agentops-eval/SKILL.md +189 -0
- agentops/templates/skills/agentops-report/SKILL.md +71 -0
- agentops/templates/skills/agentops-workflow/SKILL.md +471 -0
- agentops/templates/smoke.jsonl +3 -0
- agentops/templates/waf-checklist.README.md +84 -0
- agentops/templates/waf-checklist.csv +22 -0
- agentops/templates/workflows/agentops-deploy-dev-azd.yml +166 -0
- agentops/templates/workflows/agentops-deploy-dev.yml +187 -0
- agentops/templates/workflows/agentops-deploy-prod-azd.yml +183 -0
- agentops/templates/workflows/agentops-deploy-prod.yml +171 -0
- agentops/templates/workflows/agentops-deploy-prompt-agent.yml +197 -0
- agentops/templates/workflows/agentops-deploy-qa-azd.yml +156 -0
- agentops/templates/workflows/agentops-deploy-qa.yml +145 -0
- agentops/templates/workflows/agentops-pr-prompt-agent.yml +210 -0
- agentops/templates/workflows/agentops-pr.yml +148 -0
- agentops/templates/workflows/agentops-watchdog.yml +122 -0
- agentops/utils/__init__.py +1 -0
- agentops/utils/azd_env.py +435 -0
- agentops/utils/azure_endpoints.py +62 -0
- agentops/utils/colors.py +47 -0
- agentops/utils/dotenv_loader.py +105 -0
- agentops/utils/foundry_discovery.py +229 -0
- agentops/utils/logging.py +59 -0
- agentops/utils/telemetry.py +554 -0
- agentops/utils/yaml.py +36 -0
- agentops_accelerator-0.3.0.dist-info/METADATA +278 -0
- agentops_accelerator-0.3.0.dist-info/RECORD +142 -0
- agentops_accelerator-0.3.0.dist-info/WHEEL +5 -0
- agentops_accelerator-0.3.0.dist-info/entry_points.txt +2 -0
- agentops_accelerator-0.3.0.dist-info/licenses/LICENSE +21 -0
- agentops_accelerator-0.3.0.dist-info/top_level.txt +1 -0
agentops/__init__.py
ADDED
agentops/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""AgentOps Watchdog Agent.
|
|
2
|
+
|
|
3
|
+
The watchdog agent reads three signal sources (AgentOps eval history,
|
|
4
|
+
Azure Monitor / App Insights traces, Foundry control plane), runs a
|
|
5
|
+
set of checks over the gathered data, and produces a Markdown findings
|
|
6
|
+
report. It is exposed both as a CLI (``agentops doctor``) and as
|
|
7
|
+
a Copilot Extension HTTP server (``agentops agent serve``).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from agentops.agent.findings import Finding, Severity
|
|
11
|
+
|
|
12
|
+
__all__ = ["Finding", "Severity"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Legacy id / category aliases for the WAF-aligned rename.
|
|
2
|
+
|
|
3
|
+
This module is the single auditable home for the
|
|
4
|
+
``genaiops`` -> ``operational_excellence`` / ``genaiops.*`` ->
|
|
5
|
+
``opex.*`` rename. It exists to soften the upgrade for users with
|
|
6
|
+
existing ``agent.yaml`` files that reference the old names; the
|
|
7
|
+
canonical surface of every other module already uses the new names.
|
|
8
|
+
|
|
9
|
+
The shim is read-only: it rewrites legacy keys in memory at config
|
|
10
|
+
load time and emits a one-shot deprecation warning per process. No
|
|
11
|
+
persistent dual surface.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import List, Optional
|
|
18
|
+
|
|
19
|
+
log = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# Legacy category key -> canonical category key.
|
|
22
|
+
LEGACY_CATEGORY_ALIASES = {
|
|
23
|
+
"genaiops": "operational_excellence",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Legacy finding / rule id prefix -> canonical prefix.
|
|
27
|
+
# Any id that starts with the left-hand string is rewritten by
|
|
28
|
+
# substituting the right-hand string for the matching prefix.
|
|
29
|
+
LEGACY_ID_PREFIXES = (
|
|
30
|
+
("genaiops.", "opex."),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_warned_categories: set[str] = set()
|
|
34
|
+
_warned_ids: set[str] = set()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def canonical_category(value: str) -> str:
|
|
38
|
+
"""Return the canonical category key for ``value``.
|
|
39
|
+
|
|
40
|
+
If ``value`` is a legacy alias, emit a one-shot deprecation
|
|
41
|
+
warning and return the canonical key. Unknown values are
|
|
42
|
+
returned unchanged.
|
|
43
|
+
"""
|
|
44
|
+
if not isinstance(value, str):
|
|
45
|
+
return value
|
|
46
|
+
key = value.strip().lower()
|
|
47
|
+
canonical = LEGACY_CATEGORY_ALIASES.get(key)
|
|
48
|
+
if canonical is None:
|
|
49
|
+
return value
|
|
50
|
+
if key not in _warned_categories:
|
|
51
|
+
_warned_categories.add(key)
|
|
52
|
+
log.warning(
|
|
53
|
+
"agentops doctor: category '%s' has been renamed to '%s' "
|
|
54
|
+
"(WAF-AI alignment). Update your config / CLI flag; the "
|
|
55
|
+
"legacy name will be removed in a future release.",
|
|
56
|
+
key,
|
|
57
|
+
canonical,
|
|
58
|
+
)
|
|
59
|
+
return canonical
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def canonical_id(value: str) -> str:
|
|
63
|
+
"""Return the canonical finding-id for ``value``.
|
|
64
|
+
|
|
65
|
+
If ``value`` starts with a legacy prefix, rewrite it and emit a
|
|
66
|
+
one-shot deprecation warning. Unknown values are returned
|
|
67
|
+
unchanged.
|
|
68
|
+
"""
|
|
69
|
+
if not isinstance(value, str):
|
|
70
|
+
return value
|
|
71
|
+
for legacy, canonical in LEGACY_ID_PREFIXES:
|
|
72
|
+
if value.startswith(legacy):
|
|
73
|
+
rewritten = canonical + value[len(legacy):]
|
|
74
|
+
if value not in _warned_ids:
|
|
75
|
+
_warned_ids.add(value)
|
|
76
|
+
log.warning(
|
|
77
|
+
"agentops doctor: finding/rule id '%s' has been "
|
|
78
|
+
"renamed to '%s' (WAF-AI alignment). Update your "
|
|
79
|
+
"config; the legacy id will be removed in a "
|
|
80
|
+
"future release.",
|
|
81
|
+
value,
|
|
82
|
+
rewritten,
|
|
83
|
+
)
|
|
84
|
+
return rewritten
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def canonicalize_id_list(values: Optional[List[str]]) -> List[str]:
|
|
89
|
+
"""Apply :func:`canonical_id` element-wise. Preserves order."""
|
|
90
|
+
if not values:
|
|
91
|
+
return list(values or [])
|
|
92
|
+
return [canonical_id(v) for v in values]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Analyzer orchestration for the watchdog agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, TypeVar
|
|
10
|
+
|
|
11
|
+
from agentops.agent.checks.errors import run_errors_check
|
|
12
|
+
from agentops.agent.checks.foundry_config import run_foundry_config_check
|
|
13
|
+
from agentops.agent.checks.latency import run_latency_check
|
|
14
|
+
from agentops.agent.checks.opex_workspace import run_opex_workspace_check
|
|
15
|
+
from agentops.agent.checks.opex import run_opex_check
|
|
16
|
+
from agentops.agent.checks.posture import run_posture_check
|
|
17
|
+
from agentops.agent.checks.regression import run_regression_check
|
|
18
|
+
from agentops.agent.checks.release_readiness import run_release_readiness_check
|
|
19
|
+
from agentops.agent.checks.safety import run_safety_check
|
|
20
|
+
from agentops.agent.checks.spec_conformance import run_spec_conformance_check
|
|
21
|
+
from agentops.agent.llm_assist import run_llm_assist_check
|
|
22
|
+
from agentops.agent.llm_assist._spec_rules import (
|
|
23
|
+
run_spec_implementation_gap_rule,
|
|
24
|
+
)
|
|
25
|
+
from agentops.agent.config import AgentConfig
|
|
26
|
+
from agentops.agent.findings import Category, Finding, Severity
|
|
27
|
+
from agentops.agent.sources.azure_monitor import (
|
|
28
|
+
AzureMonitorPayload,
|
|
29
|
+
collect_azure_monitor,
|
|
30
|
+
)
|
|
31
|
+
from agentops.agent.sources.azure_resources import (
|
|
32
|
+
AzureResourcesPayload,
|
|
33
|
+
collect_azure_resources,
|
|
34
|
+
)
|
|
35
|
+
from agentops.agent.sources.foundry_control import (
|
|
36
|
+
FoundryControlPayload,
|
|
37
|
+
collect_foundry_control,
|
|
38
|
+
)
|
|
39
|
+
from agentops.agent.sources.results_history import (
|
|
40
|
+
ResultsHistory,
|
|
41
|
+
collect_results_history,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
_T = TypeVar("_T")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class AnalysisResult:
|
|
49
|
+
findings: List[Finding] = field(default_factory=list)
|
|
50
|
+
history: Optional[ResultsHistory] = None
|
|
51
|
+
monitor: Optional[AzureMonitorPayload] = None
|
|
52
|
+
foundry: Optional[FoundryControlPayload] = None
|
|
53
|
+
resources: Optional[AzureResourcesPayload] = None
|
|
54
|
+
diagnostics: Dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
workspace: Optional[Path] = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def max_severity(self) -> Optional[Severity]:
|
|
59
|
+
if not self.findings:
|
|
60
|
+
return None
|
|
61
|
+
return max(f.severity for f in self.findings)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _normalize_categories(
|
|
65
|
+
categories: Optional[Iterable[str]],
|
|
66
|
+
) -> Optional[Set[Category]]:
|
|
67
|
+
if categories is None:
|
|
68
|
+
return None
|
|
69
|
+
from agentops.agent._legacy_ids import canonical_category
|
|
70
|
+
|
|
71
|
+
out: Set[Category] = set()
|
|
72
|
+
for c in categories:
|
|
73
|
+
if not c:
|
|
74
|
+
continue
|
|
75
|
+
normalized = canonical_category(c.strip().lower())
|
|
76
|
+
try:
|
|
77
|
+
out.add(Category(normalized))
|
|
78
|
+
except ValueError:
|
|
79
|
+
continue
|
|
80
|
+
return out or None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def analyze(
|
|
84
|
+
workspace: Path,
|
|
85
|
+
config: AgentConfig,
|
|
86
|
+
*,
|
|
87
|
+
categories: Optional[Iterable[str]] = None,
|
|
88
|
+
exclude_rules: Optional[Iterable[str]] = None,
|
|
89
|
+
progress: Optional[Callable[[str], None]] = None,
|
|
90
|
+
) -> AnalysisResult:
|
|
91
|
+
"""Run every configured source + check and return the merged result.
|
|
92
|
+
|
|
93
|
+
``categories`` (when provided) limits the findings to the listed
|
|
94
|
+
:class:`Category` values. ``exclude_rules`` is forwarded to the
|
|
95
|
+
posture check to skip individual WAF rule ids on top of any
|
|
96
|
+
exclusions configured in ``agent.yaml``.
|
|
97
|
+
"""
|
|
98
|
+
notify = progress or (lambda _msg: None)
|
|
99
|
+
notify("doctor: collecting local history, Azure Monitor, and Foundry control plane")
|
|
100
|
+
|
|
101
|
+
with ThreadPoolExecutor(max_workers=3, thread_name_prefix="agentops-doctor") as pool:
|
|
102
|
+
monitor_future = pool.submit(
|
|
103
|
+
_timed,
|
|
104
|
+
lambda: collect_azure_monitor(config.sources.azure_monitor, config.lookback_days),
|
|
105
|
+
)
|
|
106
|
+
foundry_future = pool.submit(
|
|
107
|
+
_timed,
|
|
108
|
+
lambda: collect_foundry_control(config.sources.foundry_control),
|
|
109
|
+
)
|
|
110
|
+
history_future = pool.submit(
|
|
111
|
+
_timed,
|
|
112
|
+
lambda: collect_results_history(
|
|
113
|
+
workspace,
|
|
114
|
+
config.sources.results_history,
|
|
115
|
+
foundry_config=config.sources.foundry_control,
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
monitor, monitor_seconds = _finish_source("Azure Monitor", monitor_future, notify)
|
|
120
|
+
foundry, foundry_seconds = _finish_source("Foundry control plane", foundry_future, notify)
|
|
121
|
+
history, history_seconds = _finish_source("results history", history_future, notify)
|
|
122
|
+
|
|
123
|
+
resources_started = time.perf_counter()
|
|
124
|
+
resources = collect_azure_resources(
|
|
125
|
+
config.sources.azure_resources,
|
|
126
|
+
workspace=workspace,
|
|
127
|
+
project_endpoint=(foundry.diagnostics or {}).get("endpoint"),
|
|
128
|
+
)
|
|
129
|
+
resources_seconds = time.perf_counter() - resources_started
|
|
130
|
+
notify(f"doctor: source Azure resources complete ({resources_seconds:.1f}s)")
|
|
131
|
+
notify("doctor: running readiness checks")
|
|
132
|
+
|
|
133
|
+
posture_config = config.checks.posture
|
|
134
|
+
if exclude_rules:
|
|
135
|
+
merged = list(posture_config.exclude_rules) + [
|
|
136
|
+
r.strip() for r in exclude_rules if r and r.strip()
|
|
137
|
+
]
|
|
138
|
+
posture_config = posture_config.model_copy(update={"exclude_rules": merged})
|
|
139
|
+
|
|
140
|
+
findings: List[Finding] = []
|
|
141
|
+
findings.extend(run_regression_check(history, config.checks.regression))
|
|
142
|
+
findings.extend(run_latency_check(history, monitor, config.checks.latency))
|
|
143
|
+
findings.extend(run_errors_check(monitor, foundry, config.checks.errors))
|
|
144
|
+
findings.extend(run_safety_check(history, config.checks.safety, monitor, foundry))
|
|
145
|
+
findings.extend(run_posture_check(resources, posture_config))
|
|
146
|
+
findings.extend(run_opex_workspace_check(workspace))
|
|
147
|
+
findings.extend(run_opex_check(history, config.checks.opex))
|
|
148
|
+
findings.extend(run_release_readiness_check(workspace, history, foundry))
|
|
149
|
+
findings.extend(
|
|
150
|
+
run_spec_conformance_check(
|
|
151
|
+
workspace, config.checks.operational_excellence.spec_conformance
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
findings.extend(run_foundry_config_check(foundry))
|
|
155
|
+
findings.extend(
|
|
156
|
+
run_llm_assist_check(workspace, config.checks.llm_assist, foundry)
|
|
157
|
+
)
|
|
158
|
+
findings.extend(
|
|
159
|
+
run_spec_implementation_gap_rule(
|
|
160
|
+
workspace,
|
|
161
|
+
config.checks.llm_assist,
|
|
162
|
+
config.checks.operational_excellence.spec_conformance,
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
allowed = _normalize_categories(categories)
|
|
167
|
+
if allowed is not None:
|
|
168
|
+
findings = [f for f in findings if f.category in allowed]
|
|
169
|
+
|
|
170
|
+
findings.sort(key=lambda f: (-f.severity.rank, f.category.value, f.id))
|
|
171
|
+
|
|
172
|
+
return AnalysisResult(
|
|
173
|
+
findings=findings,
|
|
174
|
+
history=history,
|
|
175
|
+
monitor=monitor,
|
|
176
|
+
foundry=foundry,
|
|
177
|
+
resources=resources,
|
|
178
|
+
diagnostics={
|
|
179
|
+
"results_history": history.diagnostics,
|
|
180
|
+
"azure_monitor": monitor.diagnostics,
|
|
181
|
+
"foundry_control": foundry.diagnostics,
|
|
182
|
+
"azure_resources": resources.diagnostics,
|
|
183
|
+
"source_timings_seconds": {
|
|
184
|
+
"results_history": round(history_seconds, 3),
|
|
185
|
+
"azure_monitor": round(monitor_seconds, 3),
|
|
186
|
+
"foundry_control": round(foundry_seconds, 3),
|
|
187
|
+
"azure_resources": round(resources_seconds, 3),
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
workspace=workspace,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _timed(fn: Callable[[], _T]) -> tuple[_T, float]:
|
|
195
|
+
started = time.perf_counter()
|
|
196
|
+
value = fn()
|
|
197
|
+
return value, time.perf_counter() - started
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _finish_source(
|
|
201
|
+
label: str,
|
|
202
|
+
future: Future[tuple[_T, float]],
|
|
203
|
+
progress: Callable[[str], None],
|
|
204
|
+
) -> tuple[_T, float]:
|
|
205
|
+
value, seconds = future.result()
|
|
206
|
+
progress(f"doctor: source {label} complete ({seconds:.1f}s)")
|
|
207
|
+
return value, seconds
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Watchdog agent checks."""
|