agent-operating-substrate 0.1.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.
- agent_operating_substrate-0.1.0.dist-info/METADATA +344 -0
- agent_operating_substrate-0.1.0.dist-info/RECORD +29 -0
- agent_operating_substrate-0.1.0.dist-info/WHEEL +5 -0
- agent_operating_substrate-0.1.0.dist-info/entry_points.txt +2 -0
- agent_operating_substrate-0.1.0.dist-info/top_level.txt +1 -0
- aos/__init__.py +65 -0
- aos/__main__.py +7 -0
- aos/autopsy.py +177 -0
- aos/blackboard.py +114 -0
- aos/ci.py +201 -0
- aos/cli.py +772 -0
- aos/curator.py +143 -0
- aos/daemon.py +121 -0
- aos/discovery.py +72 -0
- aos/enforcer.py +374 -0
- aos/engine.py +110 -0
- aos/executor.py +80 -0
- aos/fleet.py +160 -0
- aos/harness.py +151 -0
- aos/hook.py +112 -0
- aos/identity.py +205 -0
- aos/ingest.py +251 -0
- aos/matcher.py +125 -0
- aos/mcp.py +188 -0
- aos/mesh.py +155 -0
- aos/models.py +107 -0
- aos/packs.py +408 -0
- aos/ui.py +3205 -0
- aos/validator.py +123 -0
aos/cli.py
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"""CLI interface for the Agent Operating Substrate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from aos.autopsy import inscribe_candidate, promote_candidate, synthesize_candidate_rule
|
|
10
|
+
from aos.blackboard import post_event, read_events
|
|
11
|
+
from aos.ci import (
|
|
12
|
+
install_ci_workflow,
|
|
13
|
+
review_pr_diff,
|
|
14
|
+
run_ci_check,
|
|
15
|
+
uninstall_ci_workflow,
|
|
16
|
+
)
|
|
17
|
+
from aos.curator import curate_substrate
|
|
18
|
+
from aos.daemon import SubstrateDaemon
|
|
19
|
+
from aos.engine import RuleEngine
|
|
20
|
+
from aos.enforcer import auto_fix_file, enforce_all
|
|
21
|
+
from aos.executor import execute_and_autopsy
|
|
22
|
+
from aos.fleet import (
|
|
23
|
+
export_fleet_bundle,
|
|
24
|
+
import_fleet_bundle,
|
|
25
|
+
list_fleet_rules,
|
|
26
|
+
publish_to_fleet,
|
|
27
|
+
sync_from_fleet,
|
|
28
|
+
)
|
|
29
|
+
from aos.harness import sync_harnesses
|
|
30
|
+
from aos.hook import install_git_hook, run_pre_commit_check, uninstall_git_hook
|
|
31
|
+
from aos.mcp import run_mcp_server
|
|
32
|
+
from aos.mesh import simulate_mesh_cycle
|
|
33
|
+
from aos.packs import install_pack, list_available_packs, recommend_packs, uninstall_pack
|
|
34
|
+
from aos.ui import start_ui_server
|
|
35
|
+
|
|
36
|
+
DEFAULT_CONFIG_YAML = """version: 1
|
|
37
|
+
substrate:
|
|
38
|
+
path: ".agents/substrate"
|
|
39
|
+
auto_archive_days: 90
|
|
40
|
+
blackboard:
|
|
41
|
+
path: ".agents/blackboard"
|
|
42
|
+
event_log: ".agents/blackboard/events.jsonl"
|
|
43
|
+
daemon:
|
|
44
|
+
poll_interval_seconds: 5
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cmd_init(args: argparse.Namespace) -> int:
|
|
49
|
+
"""Initialize substrate directory layout and default configuration."""
|
|
50
|
+
root = Path(args.root)
|
|
51
|
+
dirs = [
|
|
52
|
+
root / ".agents" / "substrate" / "active",
|
|
53
|
+
root / ".agents" / "substrate" / "candidate",
|
|
54
|
+
root / ".agents" / "substrate" / "archive",
|
|
55
|
+
root / ".agents" / "blackboard" / "intents",
|
|
56
|
+
root / ".agents" / "blackboard" / "critiques",
|
|
57
|
+
]
|
|
58
|
+
for d in dirs:
|
|
59
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
|
|
61
|
+
config_path = root / ".agents" / "config.yaml"
|
|
62
|
+
if not config_path.exists():
|
|
63
|
+
config_path.write_text(DEFAULT_CONFIG_YAML, encoding="utf-8")
|
|
64
|
+
print(f"Created config: {config_path}")
|
|
65
|
+
|
|
66
|
+
event_log = root / ".agents" / "blackboard" / "events.jsonl"
|
|
67
|
+
if not event_log.exists():
|
|
68
|
+
event_log.touch()
|
|
69
|
+
print(f"Created event stream: {event_log}")
|
|
70
|
+
|
|
71
|
+
if not getattr(args, "bare", False):
|
|
72
|
+
recommended = recommend_packs(root_dir=root)
|
|
73
|
+
for p in recommended:
|
|
74
|
+
install_pack(p, root_dir=root, promote=True)
|
|
75
|
+
print(f"Installed recommended rule packs: {', '.join(recommended)}")
|
|
76
|
+
|
|
77
|
+
print("Substrate initialized under .agents/")
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def cmd_rules_list(args: argparse.Namespace) -> int:
|
|
82
|
+
"""List rules matching the requested status filter."""
|
|
83
|
+
engine = RuleEngine(root_dir=args.root)
|
|
84
|
+
rules = engine.get_rules(status=args.status)
|
|
85
|
+
|
|
86
|
+
if not rules:
|
|
87
|
+
status_label = f" with status '{args.status}'" if args.status else ""
|
|
88
|
+
print(f"No rules found{status_label}.")
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
print(f"Found {len(rules)} rule(s):")
|
|
92
|
+
for r in rules:
|
|
93
|
+
loc = f" ({r.file_path})" if r.file_path else ""
|
|
94
|
+
print(f"[{r.status.upper()}] {r.id} (v{r.version}){loc}")
|
|
95
|
+
print(f" Statement: {r.invariant.statement}")
|
|
96
|
+
print(f" Enforcement: {r.invariant.enforcement}")
|
|
97
|
+
if r.scope.paths:
|
|
98
|
+
print(f" Paths: {', '.join(r.scope.paths)}")
|
|
99
|
+
if r.scope.languages:
|
|
100
|
+
print(f" Languages: {', '.join(r.scope.languages)}")
|
|
101
|
+
print()
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_rules_check(args: argparse.Namespace) -> int:
|
|
106
|
+
"""Check which active invariant rules match target files."""
|
|
107
|
+
engine = RuleEngine(root_dir=args.root)
|
|
108
|
+
matches_by_file = engine.match_files(args.files, status="active")
|
|
109
|
+
|
|
110
|
+
total_matches = 0
|
|
111
|
+
for file_path, matched_rules in matches_by_file.items():
|
|
112
|
+
print(f"File: {file_path}")
|
|
113
|
+
if not matched_rules:
|
|
114
|
+
print(" No active invariant rules matched.")
|
|
115
|
+
else:
|
|
116
|
+
for rule in matched_rules:
|
|
117
|
+
total_matches += 1
|
|
118
|
+
print(f" Matched Rule: [{rule.id}] {rule.invariant.statement}")
|
|
119
|
+
print(f" Enforcement: {rule.invariant.enforcement}")
|
|
120
|
+
print(f" Rationale: {rule.invariant.rationale}")
|
|
121
|
+
print()
|
|
122
|
+
|
|
123
|
+
if getattr(args, "fix", False):
|
|
124
|
+
total_fixed = 0
|
|
125
|
+
for f in args.files:
|
|
126
|
+
fixes = auto_fix_file(f, root_dir=args.root)
|
|
127
|
+
if fixes:
|
|
128
|
+
total_fixed += len(fixes)
|
|
129
|
+
print(f"Auto-fixed {f}:")
|
|
130
|
+
for fix in fixes:
|
|
131
|
+
print(f" - {fix}")
|
|
132
|
+
if total_fixed > 0:
|
|
133
|
+
print(f"Successfully applied {total_fixed} automated fix(es).")
|
|
134
|
+
|
|
135
|
+
if args.enforce:
|
|
136
|
+
violations = enforce_all(args.files, root_dir=args.root)
|
|
137
|
+
if violations:
|
|
138
|
+
print("Violations detected:")
|
|
139
|
+
for v in violations:
|
|
140
|
+
loc = f":{v.line_number}" if v.line_number else ""
|
|
141
|
+
print(f"- [{v.rule_id}] {v.file_path}{loc}: {v.message}")
|
|
142
|
+
return 1
|
|
143
|
+
|
|
144
|
+
return 0 if total_matches == 0 else 1 if args.fail_on_match else 0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def cmd_enforce(args: argparse.Namespace) -> int:
|
|
148
|
+
"""Deterministically validate files against all active rules."""
|
|
149
|
+
if getattr(args, "fix", False):
|
|
150
|
+
total_fixed = 0
|
|
151
|
+
for f in args.files:
|
|
152
|
+
fixes = auto_fix_file(f, root_dir=args.root)
|
|
153
|
+
if fixes:
|
|
154
|
+
total_fixed += len(fixes)
|
|
155
|
+
print(f"Auto-fixed {f}:")
|
|
156
|
+
for fix in fixes:
|
|
157
|
+
print(f" - {fix}")
|
|
158
|
+
if total_fixed > 0:
|
|
159
|
+
print(f"Successfully applied {total_fixed} automated fix(es).")
|
|
160
|
+
|
|
161
|
+
violations = enforce_all(args.files, root_dir=args.root)
|
|
162
|
+
if not violations:
|
|
163
|
+
print("All files passed invariant verification.")
|
|
164
|
+
return 0
|
|
165
|
+
|
|
166
|
+
print(f"Detected {len(violations)} invariant violation(s):")
|
|
167
|
+
for v in violations:
|
|
168
|
+
loc = f":{v.line_number}" if v.line_number else ""
|
|
169
|
+
print(f"- [{v.rule_id}] {v.file_path}{loc}: {v.message} ({v.enforcement})")
|
|
170
|
+
return 1
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cmd_sync(args: argparse.Namespace) -> int:
|
|
174
|
+
"""Sync active substrate rules into target agent harness configurations."""
|
|
175
|
+
harness_list = [h.strip() for h in args.harnesses.split(",")] if args.harnesses else None
|
|
176
|
+
synced = sync_harnesses(root_dir=args.root, harnesses=harness_list)
|
|
177
|
+
print(f"Synced {len(synced)} agent harness configuration(s):")
|
|
178
|
+
for name, path in synced.items():
|
|
179
|
+
print(f"- {name}: {path}")
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def cmd_hook_install(args: argparse.Namespace) -> int:
|
|
184
|
+
"""Install AOS pre-commit git hook."""
|
|
185
|
+
hook_path = install_git_hook(root_dir=args.root)
|
|
186
|
+
print(f"Installed AOS pre-commit hook at {hook_path}")
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def cmd_hook_uninstall(args: argparse.Namespace) -> int:
|
|
191
|
+
"""Uninstall AOS pre-commit git hook."""
|
|
192
|
+
removed = uninstall_git_hook(root_dir=args.root)
|
|
193
|
+
if removed:
|
|
194
|
+
print("Uninstalled AOS pre-commit hook.")
|
|
195
|
+
else:
|
|
196
|
+
print("No AOS hook found to uninstall.")
|
|
197
|
+
return 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def cmd_hook_run(args: argparse.Namespace) -> int:
|
|
201
|
+
"""Run pre-commit check on staged files."""
|
|
202
|
+
return run_pre_commit_check(root_dir=args.root)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def cmd_mcp(args: argparse.Namespace) -> int:
|
|
206
|
+
"""Start standard Model Context Protocol (MCP) server over stdio."""
|
|
207
|
+
run_mcp_server(root_dir=args.root)
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def cmd_daemon_start(args: argparse.Namespace) -> int:
|
|
212
|
+
"""Start background autonomous auditor and curator daemon."""
|
|
213
|
+
daemon = SubstrateDaemon(
|
|
214
|
+
root_dir=args.root,
|
|
215
|
+
poll_interval=args.interval,
|
|
216
|
+
curation_interval=args.curation_interval,
|
|
217
|
+
)
|
|
218
|
+
if args.once:
|
|
219
|
+
reactions = daemon.tick()
|
|
220
|
+
print(f"Daemon executed single tick: generated {len(reactions)} reaction(s).")
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
print(f"Starting AOS background daemon (polling every {args.interval}s)...")
|
|
224
|
+
daemon.run()
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def cmd_mesh_simulate(args: argparse.Namespace) -> int:
|
|
229
|
+
"""Simulate unprompted peer-to-peer agent mesh coordination."""
|
|
230
|
+
result = simulate_mesh_cycle(
|
|
231
|
+
root_dir=args.root,
|
|
232
|
+
intent_id=args.intent_id or "",
|
|
233
|
+
description=args.desc,
|
|
234
|
+
target_files=args.files,
|
|
235
|
+
)
|
|
236
|
+
print(f"Peer Mesh Simulation: Intent '{result.intent_id}' - Status: {result.status.upper()}")
|
|
237
|
+
print(f"Emitted {len(result.events)} event(s) to blackboard:")
|
|
238
|
+
for ev in result.events:
|
|
239
|
+
print(f"- [{ev.type}] From: {ev.sender}")
|
|
240
|
+
return 0 if result.status == "converged" else 1
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def cmd_ci_run(args: argparse.Namespace) -> int:
|
|
244
|
+
"""Run CI pipeline check on modified files."""
|
|
245
|
+
report = run_ci_check(
|
|
246
|
+
files=args.files,
|
|
247
|
+
base_ref=args.base,
|
|
248
|
+
auto_sync=args.auto_sync,
|
|
249
|
+
root_dir=args.root,
|
|
250
|
+
)
|
|
251
|
+
print(report.to_markdown())
|
|
252
|
+
return 0 if report.status == "passed" else 1
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def cmd_ci_review(args: argparse.Namespace) -> int:
|
|
256
|
+
"""Analyze a pull request diff and generate inline review comments."""
|
|
257
|
+
if getattr(args, "diff", None):
|
|
258
|
+
diff_text = Path(args.diff).read_text(encoding="utf-8")
|
|
259
|
+
else:
|
|
260
|
+
diff_text = sys.stdin.read()
|
|
261
|
+
result = review_pr_diff(diff_text, root_dir=args.root)
|
|
262
|
+
print(f"PR Review Status: {result['status'].upper()} ({result['comments_count']} comment(s))")
|
|
263
|
+
for c in result["comments"]:
|
|
264
|
+
print(f"- {c['file_path']}:{c['line_number']} [{c['rule_id']}]: {c['message']}")
|
|
265
|
+
return 0 if result["status"] == "approved" else 1
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def cmd_ci_install(args: argparse.Namespace) -> int:
|
|
269
|
+
"""Install GitHub Actions invariant verification workflow."""
|
|
270
|
+
wf_path = install_ci_workflow(root_dir=args.root)
|
|
271
|
+
print(f"Installed GitHub Actions workflow: {wf_path}")
|
|
272
|
+
return 0
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def cmd_ci_uninstall(args: argparse.Namespace) -> int:
|
|
276
|
+
"""Remove GitHub Actions invariant verification workflow."""
|
|
277
|
+
removed = uninstall_ci_workflow(root_dir=args.root)
|
|
278
|
+
if removed:
|
|
279
|
+
print("Uninstalled GitHub Actions workflow.")
|
|
280
|
+
else:
|
|
281
|
+
print("No GitHub Actions workflow found to uninstall.")
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def cmd_fleet_list(args: argparse.Namespace) -> int:
|
|
287
|
+
"""List rules in enterprise fleet ledger."""
|
|
288
|
+
rules = list_fleet_rules(fleet_db_path=args.db)
|
|
289
|
+
if not rules:
|
|
290
|
+
print("Fleet ledger empty.")
|
|
291
|
+
return 0
|
|
292
|
+
print(f"Found {len(rules)} fleet rule(s):")
|
|
293
|
+
for r in rules:
|
|
294
|
+
print(f"- [{r['status'].upper()}] {r['id']} (Origin: {r['origin_repo']}, Published: {r['published_at']})")
|
|
295
|
+
return 0
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def cmd_fleet_publish(args: argparse.Namespace) -> int:
|
|
299
|
+
"""Publish an active rule to enterprise fleet ledger."""
|
|
300
|
+
ok = publish_to_fleet(
|
|
301
|
+
rule_id=args.rule_id,
|
|
302
|
+
repo_root=args.root,
|
|
303
|
+
origin_repo_id=args.repo_id,
|
|
304
|
+
fleet_db_path=args.db,
|
|
305
|
+
)
|
|
306
|
+
if ok:
|
|
307
|
+
print(f"Published rule '{args.rule_id}' to fleet ledger at {args.db}")
|
|
308
|
+
return 0
|
|
309
|
+
print(f"Failed: rule '{args.rule_id}' not found in active substrate.", file=sys.stderr)
|
|
310
|
+
return 1
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def cmd_fleet_sync(args: argparse.Namespace) -> int:
|
|
314
|
+
"""Sync fleet rules into local substrate."""
|
|
315
|
+
count = sync_from_fleet(repo_root=args.root, fleet_db_path=args.db)
|
|
316
|
+
print(f"Synced {count} rule(s) from fleet ledger into .agents/substrate/active/")
|
|
317
|
+
return 0
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def cmd_fleet_export(args: argparse.Namespace) -> int:
|
|
321
|
+
"""Export fleet rules to a portable JSON bundle file."""
|
|
322
|
+
count = export_fleet_bundle(fleet_db_path=args.db, output_path=args.output)
|
|
323
|
+
print(f"Exported {count} fleet rule(s) to bundle at {args.output}")
|
|
324
|
+
return 0
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def cmd_fleet_import(args: argparse.Namespace) -> int:
|
|
328
|
+
"""Import fleet rules from a bundle file."""
|
|
329
|
+
try:
|
|
330
|
+
count = import_fleet_bundle(bundle_path=args.bundle, fleet_db_path=args.db)
|
|
331
|
+
print(f"Imported {count} rule(s) from bundle into fleet ledger at {args.db}")
|
|
332
|
+
return 0
|
|
333
|
+
except Exception as e:
|
|
334
|
+
print(f"Error importing fleet bundle: {e}", file=sys.stderr)
|
|
335
|
+
return 1
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def cmd_exec(args: argparse.Namespace) -> int:
|
|
340
|
+
"""Execute command with automatic failure autopsy synthesis."""
|
|
341
|
+
code, autopsy_path = execute_and_autopsy(
|
|
342
|
+
args.cmd,
|
|
343
|
+
root_dir=args.root,
|
|
344
|
+
auto_inscribe=not args.no_autopsy,
|
|
345
|
+
)
|
|
346
|
+
return code
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def cmd_pack_list(args: argparse.Namespace) -> int:
|
|
350
|
+
"""List available curated invariant packs."""
|
|
351
|
+
packs = list_available_packs()
|
|
352
|
+
print("Available Curated Invariant Packs:")
|
|
353
|
+
for name, count in packs.items():
|
|
354
|
+
print(f"- {name} ({count} rules)")
|
|
355
|
+
return 0
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def cmd_pack_install(args: argparse.Namespace) -> int:
|
|
359
|
+
"""Install a curated invariant pack."""
|
|
360
|
+
try:
|
|
361
|
+
installed = install_pack(
|
|
362
|
+
args.pack_name,
|
|
363
|
+
root_dir=args.root,
|
|
364
|
+
promote=not args.candidate,
|
|
365
|
+
)
|
|
366
|
+
status_str = "candidate" if args.candidate else "active"
|
|
367
|
+
print(f"Installed {len(installed)} rule(s) into {status_str} substrate:")
|
|
368
|
+
for p in installed:
|
|
369
|
+
print(f"- {p}")
|
|
370
|
+
return 0
|
|
371
|
+
except ValueError as e:
|
|
372
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
373
|
+
return 1
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def cmd_pack_uninstall(args: argparse.Namespace) -> int:
|
|
377
|
+
"""Uninstall a curated invariant pack by archiving or removing its rules."""
|
|
378
|
+
try:
|
|
379
|
+
uninstalled = uninstall_pack(
|
|
380
|
+
args.pack_name,
|
|
381
|
+
root_dir=args.root,
|
|
382
|
+
archive=not args.delete,
|
|
383
|
+
)
|
|
384
|
+
action_str = "deleted" if args.delete else "archived"
|
|
385
|
+
print(f"Uninstalled {len(uninstalled)} rule(s) ({action_str}):")
|
|
386
|
+
for p in uninstalled:
|
|
387
|
+
print(f"- {p}")
|
|
388
|
+
return 0
|
|
389
|
+
except ValueError as e:
|
|
390
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
391
|
+
return 1
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def cmd_ui(args: argparse.Namespace) -> int:
|
|
395
|
+
"""Launch local web visualizer dashboard."""
|
|
396
|
+
start_ui_server(port=args.port, root_dir=args.root)
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def cmd_autopsy(args: argparse.Namespace) -> int:
|
|
401
|
+
"""Conduct a failure autopsy and inscribe candidate or active rule."""
|
|
402
|
+
candidate = synthesize_candidate_rule(
|
|
403
|
+
rule_id=args.id,
|
|
404
|
+
statement=args.statement,
|
|
405
|
+
rationale=args.rationale,
|
|
406
|
+
paths=args.paths,
|
|
407
|
+
languages=args.languages,
|
|
408
|
+
incident_id=args.incident,
|
|
409
|
+
inscribing_agent=args.agent,
|
|
410
|
+
)
|
|
411
|
+
saved_path = inscribe_candidate(candidate, substrate_dir=Path(args.root) / ".agents" / "substrate")
|
|
412
|
+
print(f"Candidate rule inscribed: {saved_path}")
|
|
413
|
+
|
|
414
|
+
if args.promote:
|
|
415
|
+
promoted_path = promote_candidate(
|
|
416
|
+
rule_id=args.id,
|
|
417
|
+
peer_agent="consensus-evaluator",
|
|
418
|
+
substrate_dir=Path(args.root) / ".agents" / "substrate",
|
|
419
|
+
)
|
|
420
|
+
print(f"Rule promoted to active: {promoted_path}")
|
|
421
|
+
|
|
422
|
+
return 0
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def cmd_curate(args: argparse.Namespace) -> int:
|
|
426
|
+
"""Execute evolutionary pruning and subsumption on active rules."""
|
|
427
|
+
actions = curate_substrate(
|
|
428
|
+
substrate_dir=Path(args.root) / ".agents" / "substrate",
|
|
429
|
+
dry_run=args.dry_run,
|
|
430
|
+
)
|
|
431
|
+
mode = " (DRY RUN)" if args.dry_run else ""
|
|
432
|
+
if not actions:
|
|
433
|
+
print(f"Substrate clean: zero curation actions required{mode}.")
|
|
434
|
+
return 0
|
|
435
|
+
|
|
436
|
+
print(f"Executed {len(actions)} curation action(s){mode}:")
|
|
437
|
+
for act in actions:
|
|
438
|
+
print(f"- [{act.action_type}] Rule '{act.rule_id}': {act.reason}")
|
|
439
|
+
return 0
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def cmd_blackboard_list(args: argparse.Namespace) -> int:
|
|
443
|
+
"""List events posted to the local blackboard."""
|
|
444
|
+
events = read_events(root_dir=args.root, event_type=args.type, sender=args.sender)
|
|
445
|
+
if not events:
|
|
446
|
+
print("No blackboard events found.")
|
|
447
|
+
return 0
|
|
448
|
+
|
|
449
|
+
print(f"Found {len(events)} event(s):")
|
|
450
|
+
for ev in events:
|
|
451
|
+
print(f"[{ev.timestamp}] ({ev.type}) From: {ev.sender} (ID: {ev.event_id})")
|
|
452
|
+
print(f" Payload: {json.dumps(ev.payload)}")
|
|
453
|
+
return 0
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def cmd_ingest(args: argparse.Namespace) -> int:
|
|
457
|
+
"""Scan repository conventions and synthesize tailor-made invariants."""
|
|
458
|
+
from aos.ingest import ingest_repository, scan_repository_conventions
|
|
459
|
+
root = Path(args.root)
|
|
460
|
+
if args.dry_run:
|
|
461
|
+
report = scan_repository_conventions(root_dir=root)
|
|
462
|
+
print(f"Detected languages: {', '.join(report.detected_languages) or 'none'}")
|
|
463
|
+
print(f"Detected frameworks: {', '.join(report.detected_frameworks) or 'none'}")
|
|
464
|
+
print(f"Scanned config files: {', '.join(report.scanned_config_files) or 'none'}")
|
|
465
|
+
print(f"Found instruction files: {', '.join(report.existing_instruction_files) or 'none'}")
|
|
466
|
+
print(f"Recommended packs: {', '.join(report.recommended_packs) or 'none'}")
|
|
467
|
+
print(f"\nProposed {len(report.synthesized_rules)} invariant rule(s):")
|
|
468
|
+
for r in report.synthesized_rules:
|
|
469
|
+
print(f"- [{r['id']}] {r['statement']}")
|
|
470
|
+
return 0
|
|
471
|
+
|
|
472
|
+
install_packs_flag = getattr(args, "install_packs", False)
|
|
473
|
+
report = ingest_repository(
|
|
474
|
+
root_dir=root,
|
|
475
|
+
auto_promote=args.promote,
|
|
476
|
+
install_recommended_packs=install_packs_flag,
|
|
477
|
+
)
|
|
478
|
+
status_label = "active" if args.promote else "candidate"
|
|
479
|
+
print(f"Ingested {len(report.synthesized_rules)} rule(s) as {status_label}:")
|
|
480
|
+
for r in report.synthesized_rules:
|
|
481
|
+
print(f"- [{r['id']}] {r['statement']}")
|
|
482
|
+
if install_packs_flag and report.recommended_packs:
|
|
483
|
+
print(f"Installed recommended packs: {', '.join(report.recommended_packs)}")
|
|
484
|
+
return 0
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def cmd_identity_list(args: argparse.Namespace) -> int:
|
|
488
|
+
"""List autonomous agent identities and supervisory mailboxes."""
|
|
489
|
+
from aos.identity import list_identities
|
|
490
|
+
identities = list_identities(root_dir=args.root)
|
|
491
|
+
if not identities:
|
|
492
|
+
print("No autonomous agent identities in vault. Run 'aos identity create' to initialize.")
|
|
493
|
+
return 0
|
|
494
|
+
print(f"Found {len(identities)} autonomous agent identity/identities in vault:")
|
|
495
|
+
for ident in identities:
|
|
496
|
+
print(f"- Persona: {ident.get('persona')}")
|
|
497
|
+
print(f" Address: {ident.get('address')}")
|
|
498
|
+
print(f" Webmail: {ident.get('webmail_url')} (Supervision: Login with email & password)")
|
|
499
|
+
print()
|
|
500
|
+
return 0
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def cmd_identity_create(args: argparse.Namespace) -> int:
|
|
504
|
+
"""Create a free programmatic mailbox for an autonomous agent persona."""
|
|
505
|
+
from aos.identity import create_agent_account
|
|
506
|
+
ident = create_agent_account(persona=args.persona, password=args.password, root_dir=args.root)
|
|
507
|
+
print("=" * 70)
|
|
508
|
+
print("AUTONOMOUS AGENT IDENTITY CREATED")
|
|
509
|
+
print("=" * 70)
|
|
510
|
+
print(f"Persona: {ident['persona']}")
|
|
511
|
+
print(f"Email Address: {ident['address']}")
|
|
512
|
+
print(f"Password: {ident['password']}")
|
|
513
|
+
print(f"Webmail Login: {ident['webmail_url']}")
|
|
514
|
+
print("Supervision: Saved to .agents/vault/identities.json (gitignored).")
|
|
515
|
+
print("=" * 70)
|
|
516
|
+
return 0
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def cmd_identity_inbox(args: argparse.Namespace) -> int:
|
|
520
|
+
"""Check incoming messages for an agent persona."""
|
|
521
|
+
from aos.identity import fetch_agent_messages
|
|
522
|
+
try:
|
|
523
|
+
messages = fetch_agent_messages(persona_or_address=args.persona, root_dir=args.root)
|
|
524
|
+
except Exception as exc:
|
|
525
|
+
print(f"Error reading inbox: {exc}")
|
|
526
|
+
return 1
|
|
527
|
+
if not messages:
|
|
528
|
+
print(f"Inbox for '{args.persona}' is empty.")
|
|
529
|
+
return 0
|
|
530
|
+
print(f"Found {len(messages)} message(s) for '{args.persona}':")
|
|
531
|
+
for m in messages:
|
|
532
|
+
sender = m.get("from", {}).get("address", "unknown")
|
|
533
|
+
print(f"- ID: {m.get('id')} | From: {sender} | Subject: {m.get('subject')}")
|
|
534
|
+
print(f" Snippet: {m.get('intro')}")
|
|
535
|
+
return 0
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
539
|
+
parser = argparse.ArgumentParser(
|
|
540
|
+
prog="aos",
|
|
541
|
+
description="Agent Operating Substrate: Stigmergic runtime for autonomous coding agents.",
|
|
542
|
+
)
|
|
543
|
+
parser.add_argument(
|
|
544
|
+
"--root",
|
|
545
|
+
default=".",
|
|
546
|
+
help="Repository root directory (defaults to current directory).",
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
550
|
+
|
|
551
|
+
# aos init
|
|
552
|
+
p_init = subparsers.add_parser("init", help="Initialize .agents/ substrate in target repository.")
|
|
553
|
+
p_init.add_argument("--bare", action="store_true", help="Initialize empty substrate without installing default packs.")
|
|
554
|
+
p_init.set_defaults(func=cmd_init)
|
|
555
|
+
|
|
556
|
+
# aos rules
|
|
557
|
+
p_rules = subparsers.add_parser("rules", help="Manage and inspect substrate invariant rules.")
|
|
558
|
+
rules_sub = p_rules.add_subparsers(dest="rules_command", required=True)
|
|
559
|
+
|
|
560
|
+
p_rules_list = rules_sub.add_parser("list", help="List discovered substrate rules.")
|
|
561
|
+
p_rules_list.add_argument(
|
|
562
|
+
"--status",
|
|
563
|
+
choices=["active", "candidate", "archive"],
|
|
564
|
+
default=None,
|
|
565
|
+
help="Filter rules by status.",
|
|
566
|
+
)
|
|
567
|
+
p_rules_list.set_defaults(func=cmd_rules_list)
|
|
568
|
+
|
|
569
|
+
p_rules_check = rules_sub.add_parser("check", help="Check target files against active rules.")
|
|
570
|
+
p_rules_check.add_argument("files", nargs="+", help="File paths to check.")
|
|
571
|
+
p_rules_check.add_argument(
|
|
572
|
+
"--enforce",
|
|
573
|
+
action="store_true",
|
|
574
|
+
help="Perform deterministic content validation.",
|
|
575
|
+
)
|
|
576
|
+
p_rules_check.add_argument(
|
|
577
|
+
"--fail-on-match",
|
|
578
|
+
action="store_true",
|
|
579
|
+
help="Return non-zero exit code if any active rule matches.",
|
|
580
|
+
)
|
|
581
|
+
p_rules_check.add_argument(
|
|
582
|
+
"--fix",
|
|
583
|
+
action="store_true",
|
|
584
|
+
help="Automatically remediate safe invariant violations.",
|
|
585
|
+
)
|
|
586
|
+
p_rules_check.set_defaults(func=cmd_rules_check)
|
|
587
|
+
|
|
588
|
+
# aos enforce
|
|
589
|
+
p_enforce = subparsers.add_parser("enforce", help="Enforce active invariant rules deterministically.")
|
|
590
|
+
p_enforce.add_argument("files", nargs="+", help="Files to inspect.")
|
|
591
|
+
p_enforce.add_argument(
|
|
592
|
+
"--fix",
|
|
593
|
+
action="store_true",
|
|
594
|
+
help="Automatically remediate safe invariant violations.",
|
|
595
|
+
)
|
|
596
|
+
p_enforce.set_defaults(func=cmd_enforce)
|
|
597
|
+
|
|
598
|
+
# aos sync
|
|
599
|
+
p_sync = subparsers.add_parser("sync", help="Sync active rules to agent harness configurations.")
|
|
600
|
+
p_sync.add_argument("--harnesses", default=None, help="Comma-separated harness names (cursor,copilot,windsurf).")
|
|
601
|
+
p_sync.set_defaults(func=cmd_sync)
|
|
602
|
+
|
|
603
|
+
# aos hook
|
|
604
|
+
p_hook = subparsers.add_parser("hook", help="Manage universal git pre-commit hook.")
|
|
605
|
+
hook_sub = p_hook.add_subparsers(dest="hook_command", required=True)
|
|
606
|
+
|
|
607
|
+
p_hook_inst = hook_sub.add_parser("install", help="Install git pre-commit hook.")
|
|
608
|
+
p_hook_inst.set_defaults(func=cmd_hook_install)
|
|
609
|
+
|
|
610
|
+
p_hook_uninst = hook_sub.add_parser("uninstall", help="Uninstall git pre-commit hook.")
|
|
611
|
+
p_hook_uninst.set_defaults(func=cmd_hook_uninstall)
|
|
612
|
+
|
|
613
|
+
p_hook_run = hook_sub.add_parser("run", help="Run pre-commit check on staged files.")
|
|
614
|
+
p_hook_run.set_defaults(func=cmd_hook_run)
|
|
615
|
+
|
|
616
|
+
# aos mcp
|
|
617
|
+
p_mcp = subparsers.add_parser("mcp", help="Launch Model Context Protocol (MCP) stdio server.")
|
|
618
|
+
p_mcp.set_defaults(func=cmd_mcp)
|
|
619
|
+
|
|
620
|
+
# aos daemon
|
|
621
|
+
p_daemon = subparsers.add_parser("daemon", help="Manage background autonomous daemon.")
|
|
622
|
+
daemon_sub = p_daemon.add_subparsers(dest="daemon_command", required=True)
|
|
623
|
+
p_daemon_start = daemon_sub.add_parser("start", help="Start background daemon process.")
|
|
624
|
+
p_daemon_start.add_argument("--interval", type=float, default=2.0, help="Polling interval in seconds.")
|
|
625
|
+
p_daemon_start.add_argument("--curation-interval", type=float, default=300.0, help="Curation interval in seconds.")
|
|
626
|
+
p_daemon_start.add_argument("--once", action="store_true", help="Execute single polling cycle and exit.")
|
|
627
|
+
p_daemon_start.set_defaults(func=cmd_daemon_start)
|
|
628
|
+
|
|
629
|
+
# aos mesh
|
|
630
|
+
p_mesh = subparsers.add_parser("mesh", help="Simulate autonomous peer-to-peer agent mesh.")
|
|
631
|
+
mesh_sub = p_mesh.add_subparsers(dest="mesh_command", required=True)
|
|
632
|
+
p_mesh_sim = mesh_sub.add_parser("simulate", help="Run unprompted multi-agent cycle.")
|
|
633
|
+
p_mesh_sim.add_argument("--intent-id", default=None, help="Optional intent identifier.")
|
|
634
|
+
p_mesh_sim.add_argument("--desc", default="Refactor geometric normal calculation", help="Intent description.")
|
|
635
|
+
p_mesh_sim.add_argument("--files", nargs="*", default=None, help="Target file paths.")
|
|
636
|
+
p_mesh_sim.set_defaults(func=cmd_mesh_simulate)
|
|
637
|
+
|
|
638
|
+
# aos ci
|
|
639
|
+
p_ci = subparsers.add_parser("ci", help="CI pipeline integration commands.")
|
|
640
|
+
ci_sub = p_ci.add_subparsers(dest="ci_command", required=True)
|
|
641
|
+
p_ci_run = ci_sub.add_parser("run", help="Run CI verification on modified files.")
|
|
642
|
+
p_ci_run.add_argument("--base", default="origin/main", help="Base ref branch.")
|
|
643
|
+
p_ci_run.add_argument("--auto-sync", action="store_true", help="Sync harness files before check.")
|
|
644
|
+
p_ci_run.add_argument("--files", nargs="*", default=None, help="Specific files to evaluate.")
|
|
645
|
+
p_ci_run.set_defaults(func=cmd_ci_run)
|
|
646
|
+
|
|
647
|
+
p_ci_rev = ci_sub.add_parser("review", help="Review pull request diff and generate inline comments.")
|
|
648
|
+
p_ci_rev.add_argument("--diff", help="Path to unified diff file (reads from stdin if omitted).")
|
|
649
|
+
p_ci_rev.set_defaults(func=cmd_ci_review)
|
|
650
|
+
|
|
651
|
+
p_ci_inst = ci_sub.add_parser("install", help="Install GitHub Actions guardrail workflow.")
|
|
652
|
+
p_ci_inst.set_defaults(func=cmd_ci_install)
|
|
653
|
+
|
|
654
|
+
p_ci_uninst = ci_sub.add_parser("uninstall", help="Remove GitHub Actions guardrail workflow.")
|
|
655
|
+
p_ci_uninst.set_defaults(func=cmd_ci_uninstall)
|
|
656
|
+
|
|
657
|
+
# aos fleet
|
|
658
|
+
p_fleet = subparsers.add_parser("fleet", help="Enterprise cross-repository fleet invariant mesh.")
|
|
659
|
+
fleet_sub = p_fleet.add_subparsers(dest="fleet_command", required=True)
|
|
660
|
+
|
|
661
|
+
p_fleet_list = fleet_sub.add_parser("list", help="List rules in organization fleet ledger.")
|
|
662
|
+
p_fleet_list.add_argument("--db", default=".agents/fleet.db", help="Path to fleet database.")
|
|
663
|
+
p_fleet_list.set_defaults(func=cmd_fleet_list)
|
|
664
|
+
|
|
665
|
+
p_fleet_pub = fleet_sub.add_parser("publish", help="Publish local rule to fleet ledger.")
|
|
666
|
+
p_fleet_pub.add_argument("rule_id", help="Rule identifier to publish.")
|
|
667
|
+
p_fleet_pub.add_argument("--repo-id", default="local-repo", help="Origin repository identifier.")
|
|
668
|
+
p_fleet_pub.add_argument("--db", default=".agents/fleet.db", help="Path to fleet database.")
|
|
669
|
+
p_fleet_pub.set_defaults(func=cmd_fleet_publish)
|
|
670
|
+
|
|
671
|
+
p_fleet_sync = fleet_sub.add_parser("sync", help="Pull active rules from fleet ledger.")
|
|
672
|
+
p_fleet_sync.add_argument("--db", default=".agents/fleet.db", help="Path to fleet database.")
|
|
673
|
+
p_fleet_sync.set_defaults(func=cmd_fleet_sync)
|
|
674
|
+
|
|
675
|
+
p_fleet_exp = fleet_sub.add_parser("export", help="Export fleet ledger to portable JSON bundle.")
|
|
676
|
+
p_fleet_exp.add_argument("--db", default=".agents/fleet.db", help="Path to fleet database.")
|
|
677
|
+
p_fleet_exp.add_argument("--output", default=".agents/fleet-bundle.json", help="Target output bundle path.")
|
|
678
|
+
p_fleet_exp.set_defaults(func=cmd_fleet_export)
|
|
679
|
+
|
|
680
|
+
p_fleet_imp = fleet_sub.add_parser("import", help="Import fleet bundle into local ledger.")
|
|
681
|
+
p_fleet_imp.add_argument("bundle", help="Path to fleet bundle file to import.")
|
|
682
|
+
p_fleet_imp.add_argument("--db", default=".agents/fleet.db", help="Path to fleet database.")
|
|
683
|
+
p_fleet_imp.set_defaults(func=cmd_fleet_import)
|
|
684
|
+
|
|
685
|
+
# aos exec
|
|
686
|
+
p_exec = subparsers.add_parser("exec", help="Execute command with autonomous failure autopsy.")
|
|
687
|
+
p_exec.add_argument("--no-autopsy", action="store_true", help="Disable automatic candidate inscription on failure.")
|
|
688
|
+
p_exec.add_argument("cmd", nargs=argparse.REMAINDER, help="Command to execute.")
|
|
689
|
+
p_exec.set_defaults(func=cmd_exec)
|
|
690
|
+
|
|
691
|
+
# aos pack
|
|
692
|
+
p_pack = subparsers.add_parser("pack", help="Manage curated invariant rule packs.")
|
|
693
|
+
pack_sub = p_pack.add_subparsers(dest="pack_command", required=True)
|
|
694
|
+
|
|
695
|
+
p_pack_list = pack_sub.add_parser("list", help="List available curated invariant packs.")
|
|
696
|
+
p_pack_list.set_defaults(func=cmd_pack_list)
|
|
697
|
+
|
|
698
|
+
p_pack_inst = pack_sub.add_parser("install", help="Install a curated invariant pack.")
|
|
699
|
+
p_pack_inst.add_argument("pack_name", help="Name of the pack (security-owasp, python-clean-architecture).")
|
|
700
|
+
p_pack_inst.add_argument("--candidate", action="store_true", help="Install as candidate rather than active.")
|
|
701
|
+
p_pack_inst.set_defaults(func=cmd_pack_install)
|
|
702
|
+
|
|
703
|
+
p_pack_uninst = pack_sub.add_parser("uninstall", help="Uninstall a curated invariant pack.")
|
|
704
|
+
p_pack_uninst.add_argument("pack_name", help="Name of the pack to uninstall.")
|
|
705
|
+
p_pack_uninst.add_argument("--delete", action="store_true", help="Permanently delete rules instead of archiving.")
|
|
706
|
+
p_pack_uninst.set_defaults(func=cmd_pack_uninstall)
|
|
707
|
+
|
|
708
|
+
# aos ui
|
|
709
|
+
p_ui = subparsers.add_parser("ui", help="Start local web visualizer dashboard.")
|
|
710
|
+
p_ui.add_argument("--port", type=int, default=8484, help="HTTP port to listen on.")
|
|
711
|
+
p_ui.set_defaults(func=cmd_ui)
|
|
712
|
+
|
|
713
|
+
# aos autopsy
|
|
714
|
+
p_autopsy = subparsers.add_parser("autopsy", help="Synthesize and inscribe an invariant rule from failure autopsy.")
|
|
715
|
+
p_autopsy.add_argument("--id", required=True, help="Unique identifier for the new rule.")
|
|
716
|
+
p_autopsy.add_argument("--incident", default="inc-manual", help="Incident or commit identifier.")
|
|
717
|
+
p_autopsy.add_argument("--statement", required=True, help="Invariant directive statement.")
|
|
718
|
+
p_autopsy.add_argument("--rationale", required=True, help="Rationale explaining the invariant.")
|
|
719
|
+
p_autopsy.add_argument("--paths", nargs="+", required=True, help="Target path globs.")
|
|
720
|
+
p_autopsy.add_argument("--languages", nargs="*", default=[], help="Target language identifiers.")
|
|
721
|
+
p_autopsy.add_argument("--agent", default="forensic-auditor", help="Name of inscribing agent.")
|
|
722
|
+
p_autopsy.add_argument("--promote", action="store_true", help="Immediately promote to active status.")
|
|
723
|
+
p_autopsy.set_defaults(func=cmd_autopsy)
|
|
724
|
+
|
|
725
|
+
# aos curate
|
|
726
|
+
p_curate = subparsers.add_parser("curate", help="Prune and consolidate rules in the substrate.")
|
|
727
|
+
p_curate.add_argument("--dry-run", action="store_true", help="Inspect without modifying files.")
|
|
728
|
+
p_curate.set_defaults(func=cmd_curate)
|
|
729
|
+
|
|
730
|
+
# aos blackboard
|
|
731
|
+
p_bb = subparsers.add_parser("blackboard", help="Inspect and interact with the local blackboard.")
|
|
732
|
+
bb_sub = p_bb.add_subparsers(dest="bb_command", required=True)
|
|
733
|
+
|
|
734
|
+
p_bb_list = bb_sub.add_parser("list", help="List blackboard events.")
|
|
735
|
+
p_bb_list.add_argument("--type", default=None, help="Filter by event type.")
|
|
736
|
+
p_bb_list.add_argument("--sender", default=None, help="Filter by sender.")
|
|
737
|
+
p_bb_list.set_defaults(func=cmd_blackboard_list)
|
|
738
|
+
|
|
739
|
+
# aos ingest
|
|
740
|
+
p_ingest = subparsers.add_parser("ingest", help="Scan repository conventions and synthesize tailored guardrails.")
|
|
741
|
+
p_ingest.add_argument("--promote", action="store_true", help="Immediately promote synthesized rules to active status.")
|
|
742
|
+
p_ingest.add_argument("--install-packs", action="store_true", help="Automatically install stack-recommended rule packs.")
|
|
743
|
+
p_ingest.add_argument("--dry-run", action="store_true", help="Scan and display proposed rules without saving.")
|
|
744
|
+
p_ingest.set_defaults(func=cmd_ingest)
|
|
745
|
+
|
|
746
|
+
# aos identity
|
|
747
|
+
p_ident = subparsers.add_parser("identity", help="Manage autonomous agent identities and supervisory mailboxes.")
|
|
748
|
+
ident_sub = p_ident.add_subparsers(dest="identity_command", required=True)
|
|
749
|
+
|
|
750
|
+
p_ident_list = ident_sub.add_parser("list", help="List autonomous agent identities.")
|
|
751
|
+
p_ident_list.set_defaults(func=cmd_identity_list)
|
|
752
|
+
|
|
753
|
+
p_ident_create = ident_sub.add_parser("create", help="Create a free programmatic mailbox for an agent persona.")
|
|
754
|
+
p_ident_create.add_argument("--persona", default="maintainer", help="Persona name (maintainer, auditor, curator, sentry, ci).")
|
|
755
|
+
p_ident_create.add_argument("--password", default=None, help="Optional custom password.")
|
|
756
|
+
p_ident_create.set_defaults(func=cmd_identity_create)
|
|
757
|
+
|
|
758
|
+
p_ident_inbox = ident_sub.add_parser("inbox", help="View incoming messages for an agent persona.")
|
|
759
|
+
p_ident_inbox.add_argument("persona", help="Persona name or email address.")
|
|
760
|
+
p_ident_inbox.set_defaults(func=cmd_identity_inbox)
|
|
761
|
+
|
|
762
|
+
return parser
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def main(argv: list[str] | None = None) -> int:
|
|
766
|
+
parser = build_parser()
|
|
767
|
+
args = parser.parse_args(argv)
|
|
768
|
+
return args.func(args)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
if __name__ == "__main__":
|
|
772
|
+
sys.exit(main())
|