agent-governance 1.0.4__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.
- agent_governance/__init__.py +49 -0
- agent_governance/agentctl.py +945 -0
- agent_governance/contracts/__init__.py +0 -0
- agent_governance/contracts/output_packet.schema.json +88 -0
- agent_governance/contracts/task_packet.schema.json +74 -0
- agent_governance/init.py +1049 -0
- agent_governance/update_check.py +310 -0
- agent_governance-1.0.4.dist-info/METADATA +133 -0
- agent_governance-1.0.4.dist-info/RECORD +12 -0
- agent_governance-1.0.4.dist-info/WHEEL +5 -0
- agent_governance-1.0.4.dist-info/entry_points.txt +2 -0
- agent_governance-1.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.error
|
|
9
|
+
import urllib.request
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from datetime import datetime, timedelta, timezone
|
|
12
|
+
from importlib import metadata
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from packaging.version import InvalidVersion, Version
|
|
17
|
+
|
|
18
|
+
from agent_governance import PACKAGE_NAME, __version__
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import tomllib # type: ignore
|
|
22
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
23
|
+
import tomli as tomllib # type: ignore
|
|
24
|
+
|
|
25
|
+
DEFAULT_TTL_HOURS = 24
|
|
26
|
+
ERROR_TTL_HOURS = 6
|
|
27
|
+
PYPI_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class CacheEntry:
|
|
32
|
+
checked_at_utc: str
|
|
33
|
+
installed_version: str
|
|
34
|
+
latest_version: str
|
|
35
|
+
source: str
|
|
36
|
+
etag: str | None = None
|
|
37
|
+
last_error: str | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_mode(cli_mode: str | None, env: dict[str, str]) -> str:
|
|
41
|
+
env_mode = env.get("AGENT_UPDATE_CHECK")
|
|
42
|
+
if env_mode:
|
|
43
|
+
return env_mode
|
|
44
|
+
return cli_mode or "auto"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_ci(env: dict[str, str]) -> bool:
|
|
48
|
+
return env.get("CI", "").lower() == "true"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_time(value: str) -> datetime | None:
|
|
52
|
+
if not value:
|
|
53
|
+
return None
|
|
54
|
+
if value.endswith("Z"):
|
|
55
|
+
value = value[:-1] + "+00:00"
|
|
56
|
+
try:
|
|
57
|
+
return datetime.fromisoformat(value)
|
|
58
|
+
except ValueError:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def format_time(dt: datetime) -> str:
|
|
63
|
+
return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def read_cache(cache_path: Path) -> CacheEntry | None:
|
|
67
|
+
try:
|
|
68
|
+
if not cache_path.exists():
|
|
69
|
+
return None
|
|
70
|
+
data = json.loads(cache_path.read_text())
|
|
71
|
+
except (json.JSONDecodeError, OSError):
|
|
72
|
+
return None
|
|
73
|
+
if not isinstance(data, dict):
|
|
74
|
+
return None
|
|
75
|
+
return CacheEntry(
|
|
76
|
+
checked_at_utc=data.get("checked_at_utc", ""),
|
|
77
|
+
installed_version=data.get("installed_version", ""),
|
|
78
|
+
latest_version=data.get("latest_version", ""),
|
|
79
|
+
source=data.get("source", ""),
|
|
80
|
+
etag=data.get("etag"),
|
|
81
|
+
last_error=data.get("last_error"),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def write_cache(cache_path: Path, entry: CacheEntry) -> None:
|
|
86
|
+
payload = {
|
|
87
|
+
"checked_at_utc": entry.checked_at_utc,
|
|
88
|
+
"installed_version": entry.installed_version,
|
|
89
|
+
"latest_version": entry.latest_version,
|
|
90
|
+
"source": entry.source,
|
|
91
|
+
"etag": entry.etag,
|
|
92
|
+
"last_error": entry.last_error,
|
|
93
|
+
}
|
|
94
|
+
try:
|
|
95
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
cache_path.write_text(json.dumps(payload, indent=2, sort_keys=True))
|
|
97
|
+
except OSError:
|
|
98
|
+
try:
|
|
99
|
+
fallback = fallback_cache_path()
|
|
100
|
+
fallback.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
fallback.write_text(json.dumps(payload, indent=2, sort_keys=True))
|
|
102
|
+
except OSError:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_cache_path(env: dict[str, str]) -> Path:
|
|
107
|
+
cache_root = env.get("XDG_CACHE_HOME")
|
|
108
|
+
if cache_root:
|
|
109
|
+
return Path(cache_root) / "agent_governance" / "update_check.json"
|
|
110
|
+
return Path.home() / ".cache" / "agent_governance" / "update_check.json"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def fallback_cache_path() -> Path:
|
|
114
|
+
return Path.home() / ".agent_governance" / "cache" / "update_check.json"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def should_check(
|
|
118
|
+
now: datetime,
|
|
119
|
+
env: dict[str, str],
|
|
120
|
+
cache: CacheEntry | None,
|
|
121
|
+
mode: str,
|
|
122
|
+
ci: bool,
|
|
123
|
+
interactive: bool,
|
|
124
|
+
) -> tuple[bool, str]:
|
|
125
|
+
mode = mode or "auto"
|
|
126
|
+
if mode == "off":
|
|
127
|
+
return False, "mode off"
|
|
128
|
+
if mode == "auto":
|
|
129
|
+
if ci:
|
|
130
|
+
return False, "ci auto disabled"
|
|
131
|
+
if not interactive:
|
|
132
|
+
return False, "non-interactive auto disabled"
|
|
133
|
+
if mode == "verbose":
|
|
134
|
+
if ci:
|
|
135
|
+
return False, "ci verbose disabled"
|
|
136
|
+
if not interactive:
|
|
137
|
+
return False, "non-interactive verbose disabled"
|
|
138
|
+
if mode == "on":
|
|
139
|
+
return True, "forced on"
|
|
140
|
+
|
|
141
|
+
if not cache:
|
|
142
|
+
return True, "no cache"
|
|
143
|
+
|
|
144
|
+
checked_at = parse_time(cache.checked_at_utc)
|
|
145
|
+
if not checked_at:
|
|
146
|
+
return True, "cache timestamp invalid"
|
|
147
|
+
|
|
148
|
+
installed_version, _source = get_installed_version(env)
|
|
149
|
+
if cache.installed_version and cache.installed_version != installed_version:
|
|
150
|
+
return True, "installed version changed"
|
|
151
|
+
|
|
152
|
+
ttl_hours = ERROR_TTL_HOURS if cache.last_error else DEFAULT_TTL_HOURS
|
|
153
|
+
if now - checked_at < timedelta(hours=ttl_hours):
|
|
154
|
+
return False, "cache fresh"
|
|
155
|
+
return True, "cache stale"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _source_root_from_env(env: dict[str, str]) -> Path | None:
|
|
159
|
+
root = env.get("AGENT_GOVERNANCE_ROOT")
|
|
160
|
+
if root:
|
|
161
|
+
return Path(root).resolve()
|
|
162
|
+
for parent in Path(__file__).resolve().parents:
|
|
163
|
+
if (parent / "pyproject.toml").exists() or (parent / "VERSION").exists():
|
|
164
|
+
return parent
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _version_from_pyproject(root: Path) -> str | None:
|
|
169
|
+
pyproject = root / "pyproject.toml"
|
|
170
|
+
if not pyproject.exists():
|
|
171
|
+
return None
|
|
172
|
+
try:
|
|
173
|
+
data = tomllib.loads(pyproject.read_text())
|
|
174
|
+
except tomllib.TOMLDecodeError:
|
|
175
|
+
return None
|
|
176
|
+
if not isinstance(data, dict):
|
|
177
|
+
return None
|
|
178
|
+
project = data.get("project", {})
|
|
179
|
+
if isinstance(project, dict):
|
|
180
|
+
version = project.get("version")
|
|
181
|
+
if isinstance(version, str) and version.strip():
|
|
182
|
+
return version.strip()
|
|
183
|
+
tool = data.get("tool", {})
|
|
184
|
+
if isinstance(tool, dict):
|
|
185
|
+
poetry = tool.get("poetry", {})
|
|
186
|
+
if isinstance(poetry, dict):
|
|
187
|
+
version = poetry.get("version")
|
|
188
|
+
if isinstance(version, str) and version.strip():
|
|
189
|
+
return version.strip()
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _version_from_source(root: Path) -> str | None:
|
|
194
|
+
version = _version_from_pyproject(root)
|
|
195
|
+
if version:
|
|
196
|
+
return version
|
|
197
|
+
version_path = root / "VERSION"
|
|
198
|
+
if version_path.exists():
|
|
199
|
+
value = version_path.read_text().strip()
|
|
200
|
+
if value:
|
|
201
|
+
return value
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_installed_version(env: dict[str, str]) -> tuple[str, str]:
|
|
206
|
+
try:
|
|
207
|
+
return metadata.version(PACKAGE_NAME), "metadata"
|
|
208
|
+
except metadata.PackageNotFoundError:
|
|
209
|
+
root = _source_root_from_env(env)
|
|
210
|
+
if root:
|
|
211
|
+
version = _version_from_source(root)
|
|
212
|
+
if version:
|
|
213
|
+
return version, "source"
|
|
214
|
+
return __version__, "unknown"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def compare_versions(installed: str, latest: str) -> int:
|
|
218
|
+
try:
|
|
219
|
+
installed_v = Version(installed)
|
|
220
|
+
latest_v = Version(latest)
|
|
221
|
+
except InvalidVersion:
|
|
222
|
+
return 0
|
|
223
|
+
if latest_v.is_prerelease and not installed_v.is_prerelease:
|
|
224
|
+
return 0
|
|
225
|
+
if latest_v > installed_v:
|
|
226
|
+
return 1
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def fetch_latest(
|
|
231
|
+
cache: CacheEntry | None, installed_version: str
|
|
232
|
+
) -> tuple[str | None, str | None, str | None]:
|
|
233
|
+
headers = {
|
|
234
|
+
"User-Agent": f"{PACKAGE_NAME}/{installed_version}",
|
|
235
|
+
}
|
|
236
|
+
if cache and cache.etag:
|
|
237
|
+
headers["If-None-Match"] = cache.etag
|
|
238
|
+
req = urllib.request.Request(PYPI_URL, headers=headers)
|
|
239
|
+
try:
|
|
240
|
+
with urllib.request.urlopen(req, timeout=2.5) as resp:
|
|
241
|
+
if resp.status == 304 and cache:
|
|
242
|
+
return cache.latest_version, cache.etag, None
|
|
243
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
244
|
+
latest = payload.get("info", {}).get("version")
|
|
245
|
+
if not isinstance(latest, str):
|
|
246
|
+
return None, None, "invalid version"
|
|
247
|
+
etag = resp.headers.get("ETag")
|
|
248
|
+
return latest, etag, None
|
|
249
|
+
except urllib.error.HTTPError as exc:
|
|
250
|
+
if exc.code == 304 and cache:
|
|
251
|
+
return cache.latest_version, cache.etag, None
|
|
252
|
+
return None, None, f"http error {exc.code}"
|
|
253
|
+
except Exception as exc: # pragma: no cover - network errors
|
|
254
|
+
return None, None, str(exc)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def maybe_check(
|
|
258
|
+
root: Path,
|
|
259
|
+
mode: str,
|
|
260
|
+
env: dict[str, str],
|
|
261
|
+
policy: str | None,
|
|
262
|
+
out: Any = sys.stderr,
|
|
263
|
+
) -> None:
|
|
264
|
+
if policy == "off":
|
|
265
|
+
if mode == "verbose":
|
|
266
|
+
out.write("update check disabled by policy\n")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
now = datetime.now(timezone.utc)
|
|
270
|
+
cache_path = get_cache_path(env)
|
|
271
|
+
cache = read_cache(cache_path)
|
|
272
|
+
ci = is_ci(env)
|
|
273
|
+
interactive = sys.stderr.isatty()
|
|
274
|
+
should_run, reason = should_check(now, env, cache, mode, ci, interactive)
|
|
275
|
+
if not should_run:
|
|
276
|
+
if mode == "verbose":
|
|
277
|
+
out.write(f"update check skipped: {reason}\n")
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
installed_version, _source = get_installed_version(env)
|
|
281
|
+
latest, etag, error = fetch_latest(cache, installed_version)
|
|
282
|
+
if error or not latest:
|
|
283
|
+
entry = CacheEntry(
|
|
284
|
+
checked_at_utc=format_time(now),
|
|
285
|
+
installed_version=installed_version,
|
|
286
|
+
latest_version=cache.latest_version if cache else "",
|
|
287
|
+
source="pypi",
|
|
288
|
+
etag=etag or (cache.etag if cache else None),
|
|
289
|
+
last_error=error or "unknown error",
|
|
290
|
+
)
|
|
291
|
+
write_cache(cache_path, entry)
|
|
292
|
+
if mode == "verbose":
|
|
293
|
+
out.write(f"update check failed: {entry.last_error}\n")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
entry = CacheEntry(
|
|
297
|
+
checked_at_utc=format_time(now),
|
|
298
|
+
installed_version=installed_version,
|
|
299
|
+
latest_version=latest,
|
|
300
|
+
source="pypi",
|
|
301
|
+
etag=etag,
|
|
302
|
+
last_error=None,
|
|
303
|
+
)
|
|
304
|
+
write_cache(cache_path, entry)
|
|
305
|
+
|
|
306
|
+
if compare_versions(installed_version, latest) > 0:
|
|
307
|
+
out.write(
|
|
308
|
+
f"{PACKAGE_NAME} update available: installed {installed_version}, "
|
|
309
|
+
f"latest {latest} (source: pypi).\n"
|
|
310
|
+
)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-governance
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Agent governance tooling
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pyyaml>=6.0
|
|
8
|
+
Requires-Dist: jsonschema>=4.21
|
|
9
|
+
Requires-Dist: packaging>=22
|
|
10
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
11
|
+
|
|
12
|
+
# Bounded Agent Framework
|
|
13
|
+
|
|
14
|
+
This repository defines a **repo-local, enforceable framework** for using AI sub-agents as bounded workers.
|
|
15
|
+
|
|
16
|
+
This is **not** an application.
|
|
17
|
+
This is **not** an orchestration service.
|
|
18
|
+
This is **not** an autonomy experiment.
|
|
19
|
+
|
|
20
|
+
This is infrastructure.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What this system is
|
|
25
|
+
|
|
26
|
+
- Agents are **roles defined in Markdown**
|
|
27
|
+
- Agents are **instantiated per model call**
|
|
28
|
+
- Nothing is persistent
|
|
29
|
+
- Nothing runs automatically
|
|
30
|
+
- Nothing is trusted without artifacts
|
|
31
|
+
|
|
32
|
+
The framework exists to:
|
|
33
|
+
|
|
34
|
+
- force correct task decomposition
|
|
35
|
+
- prevent scope creep
|
|
36
|
+
- require evidence for completion
|
|
37
|
+
- make agent behavior reproducible and auditable
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Core invariants
|
|
42
|
+
|
|
43
|
+
1. **Repo-local**
|
|
44
|
+
- All agent rules, schemas, and tools live in the repo
|
|
45
|
+
- No systemwide installs
|
|
46
|
+
- No hidden dependencies
|
|
47
|
+
|
|
48
|
+
2. **Separation of concerns**
|
|
49
|
+
- Agent roles (Markdown) change frequently
|
|
50
|
+
- Schemas change occasionally
|
|
51
|
+
- Tooling changes rarely
|
|
52
|
+
|
|
53
|
+
3. **Artifacts over prose**
|
|
54
|
+
- Diffs, logs, commands, tests are required
|
|
55
|
+
- Narrative text is secondary
|
|
56
|
+
|
|
57
|
+
4. **Orchestrator ≠ implementer**
|
|
58
|
+
- Orchestrator plans and validates
|
|
59
|
+
- Workers produce changes
|
|
60
|
+
- Single-writer rule per scope
|
|
61
|
+
|
|
62
|
+
5. **No schema, no run**
|
|
63
|
+
- Tasks and outputs must validate
|
|
64
|
+
- Invalid packets are rejected
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Repository layout (expected)
|
|
69
|
+
|
|
70
|
+
agents/
|
|
71
|
+
roles/
|
|
72
|
+
contracts/
|
|
73
|
+
checklists/
|
|
74
|
+
tools/
|
|
75
|
+
logs/
|
|
76
|
+
reports/
|
|
77
|
+
adr/
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Usage summary
|
|
82
|
+
|
|
83
|
+
1. Create a task packet (YAML)
|
|
84
|
+
2. Validate it against schema
|
|
85
|
+
3. Run the agent with role + task
|
|
86
|
+
4. Collect output packet + artifacts
|
|
87
|
+
5. Validate output
|
|
88
|
+
6. Gate before merge
|
|
89
|
+
|
|
90
|
+
Tooling baseline: use `agent-governance>=1.0.4`.
|
|
91
|
+
|
|
92
|
+
## Repo introspection
|
|
93
|
+
|
|
94
|
+
`agentctl init` scans the current repo to generate a policy overlay plus an
|
|
95
|
+
evidence-backed report. It never edits repo files unless `--write` is set, and
|
|
96
|
+
it never calls an LLM.
|
|
97
|
+
|
|
98
|
+
## Bootstrap policy block
|
|
99
|
+
|
|
100
|
+
Use `agentctl bootstrap` to author or update the AGENTS.md policy block from the
|
|
101
|
+
canonical role registry. It is preview-only unless `--write` is provided.
|
|
102
|
+
|
|
103
|
+
What it never does:
|
|
104
|
+
|
|
105
|
+
- no network access
|
|
106
|
+
- no repo mutation (except writing the three outputs when `--write` is set)
|
|
107
|
+
- no heuristic guesses without a cited file + line range
|
|
108
|
+
|
|
109
|
+
Outputs (when `--write`):
|
|
110
|
+
|
|
111
|
+
- `.agents/generated/AGENTS.repo.overlay.yaml`
|
|
112
|
+
- `.agents/generated/init_report.md`
|
|
113
|
+
- `.agents/generated/init_facts.json`
|
|
114
|
+
|
|
115
|
+
At runtime, the orchestrator merges the overlay with the base policy to add
|
|
116
|
+
repo-specific verify commands and risk paths before dispatching work. This lets
|
|
117
|
+
one global role set adapt to local tooling without changing the core policy.
|
|
118
|
+
|
|
119
|
+
Deterministic + auditable:
|
|
120
|
+
|
|
121
|
+
- each detected fact includes a source file and line range
|
|
122
|
+
- output ordering is stable for diff-friendly review
|
|
123
|
+
- dry-runs show planned writes without touching disk
|
|
124
|
+
|
|
125
|
+
## Update checks
|
|
126
|
+
|
|
127
|
+
The CLI can optionally warn if a newer `agent_governance` version exists.
|
|
128
|
+
Defaults: once per 24h, skipped in CI, and never blocks command execution.
|
|
129
|
+
|
|
130
|
+
Disable with `--update-check=off` or `AGENT_UPDATE_CHECK=off`. Repos may set
|
|
131
|
+
`update_check: off` in `agents/repo_profile.yaml` to fully disable network use.
|
|
132
|
+
|
|
133
|
+
This system replaces trust with structure.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
agent_governance/__init__.py,sha256=gVJvP56TC_hU6GYFMwuQE4vys120FBs21LzyBkFvhFY,1494
|
|
2
|
+
agent_governance/agentctl.py,sha256=7ouKYGek4GiHGCGkm_ghChosTR7mt1PFe4RMPw1Q8XQ,30982
|
|
3
|
+
agent_governance/init.py,sha256=6yGQZ9OUhbwI6RgTezj-d0Ku0s0EBm6xbF5WXDlIVLQ,34127
|
|
4
|
+
agent_governance/update_check.py,sha256=R-pudcL5RThA0V01-CqIJiQnXK6KZVVB8K80-ti7WuQ,9453
|
|
5
|
+
agent_governance/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
agent_governance/contracts/output_packet.schema.json,sha256=zAwfreDQm5vmLvAdLcVr8uC_HOnRMU_M5Ji6AyemjRo,2088
|
|
7
|
+
agent_governance/contracts/task_packet.schema.json,sha256=4zUqV3QUUbV0zpyOpZV_1vZE-zNamyFxPs8KH2gQpg4,1664
|
|
8
|
+
agent_governance-1.0.4.dist-info/METADATA,sha256=IFx6V3Ghnkn8jkuITIquF6KmpO6m18ZH2nmfSsgtPnE,3448
|
|
9
|
+
agent_governance-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
agent_governance-1.0.4.dist-info/entry_points.txt,sha256=iSbWaXVz3_Sc0TDsgpgAA3qh7Jk5qSmxVfaBuQJTnvs,60
|
|
11
|
+
agent_governance-1.0.4.dist-info/top_level.txt,sha256=s1hxHyuJLAc3on5R5N8JCE0kI5z8_vMYKTTAWiax1eQ,17
|
|
12
|
+
agent_governance-1.0.4.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_governance
|