antz-studio 0.1.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,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: antz-studio
3
+ Version: 0.1.0
4
+ Summary: Antz Studio -- Sovereign Agentic OS
5
+ Author-email: Antz Studio <gabriel@antz.studio>
6
+ License: AGPL-3.0
7
+ Project-URL: Homepage, https://antz.studio
8
+ Project-URL: Repository, https://github.com/Gabbsx7/antz-studio-aos
9
+ Keywords: agents,sovereign-ai,agentic-os,llm,observability,nest,colony,hive
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: typer>=0.12
19
+ Requires-Dist: pyyaml>=6.0
20
+ Requires-Dist: opentelemetry-api>=1.24
21
+ Requires-Dist: opentelemetry-sdk>=1.24
22
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.24
23
+ Requires-Dist: rich>=13.0
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: sqlalchemy>=2.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
29
+ Requires-Dist: pytest-mock>=3.12; extra == "dev"
30
+ Provides-Extra: cloud
31
+ Requires-Dist: azure-mgmt-compute>=30.0.0; extra == "cloud"
32
+ Requires-Dist: azure-mgmt-network>=25.0.0; extra == "cloud"
33
+ Requires-Dist: azure-mgmt-resource>=23.0.0; extra == "cloud"
34
+ Requires-Dist: azure-identity>=1.15.0; extra == "cloud"
@@ -0,0 +1,29 @@
1
+ # Antz Studio -- Colony SDK
2
+ # Sovereign Agentic OS - antz.studio
3
+
4
+ from antz.decorators import agent, tool, memory
5
+ from antz.client import AntzClient
6
+ from antz.config import AntzConfig
7
+ from antz.hive_mode import HiveMode
8
+ from antz.db_connector import db_connector
9
+ from antz.privacy import add_gaussian_noise
10
+ from antz.exceptions import (
11
+ AntzConnectionError,
12
+ ConstitutionViolationError,
13
+ AntzAuthError,
14
+ )
15
+
16
+ __version__ = "0.1.0"
17
+ __all__ = [
18
+ "agent",
19
+ "tool",
20
+ "memory",
21
+ "HiveMode",
22
+ "db_connector",
23
+ "add_gaussian_noise",
24
+ "AntzClient",
25
+ "AntzConfig",
26
+ "AntzConnectionError",
27
+ "ConstitutionViolationError",
28
+ "AntzAuthError",
29
+ ]
@@ -0,0 +1,545 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+ import yaml
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.table import Table
13
+
14
+ app = typer.Typer(help="Ant'z Studio — Sovereign Agentic OS CLI", add_completion=False)
15
+ console = Console()
16
+
17
+
18
+ # ── antz login ────────────────────────────────────────────────────────────────
19
+
20
+ @app.command()
21
+ def login(
22
+ nest: str = typer.Option(..., "--nest", help="Nest URL (e.g. http://nest.local:8001)"),
23
+ api_key: str = typer.Option(..., "--api-key", help="Nest API key"),
24
+ mode: str = typer.Option("isolated", "--mode", help="isolated | shared | sovereign"),
25
+ ):
26
+ """Authenticate with a Nest and save credentials locally."""
27
+ from antz.config import AntzConfig
28
+ from antz.client import AntzClient
29
+
30
+ console.print(f"[dim]Connecting to Nest at {nest}...[/dim]")
31
+
32
+ try:
33
+ cfg = AntzConfig(nest_url=nest, api_key=api_key, mode=mode)
34
+ client = AntzClient(cfg)
35
+ health = client.health()
36
+
37
+ AntzConfig.save_global(nest_url=nest, api_key=api_key, mode=mode)
38
+
39
+ console.print(Panel.fit(
40
+ f"[green]✓ Connected[/green]\n\n"
41
+ f" Nest: [cyan]{nest}[/cyan]\n"
42
+ f" Mode: [yellow]{mode}[/yellow]\n"
43
+ f" Version: {health.get('version', 'unknown')}\n\n"
44
+ f"Credentials saved to [dim]~/.antz/config.yaml[/dim]",
45
+ title="Ant'z Studio",
46
+ border_style="green",
47
+ ))
48
+
49
+ except Exception as e:
50
+ console.print(f"[red]✗ Connection failed:[/red] {e}")
51
+ console.print("[dim]Hint: make sure the Nest is running and the API key is correct.[/dim]")
52
+ raise typer.Exit(1)
53
+
54
+
55
+ # ── antz status ───────────────────────────────────────────────────────────────
56
+
57
+ @app.command()
58
+ def status():
59
+ """Check connection to the configured Nest."""
60
+ from antz.config import AntzConfig
61
+ from antz.client import AntzClient
62
+
63
+ try:
64
+ cfg = AntzConfig.load()
65
+ client = AntzClient(cfg)
66
+ health = client.health()
67
+
68
+ table = Table(show_header=False, box=None, padding=(0, 2))
69
+ table.add_row("[dim]Nest URL[/dim]", f"[cyan]{cfg.nest_url}[/cyan]")
70
+ table.add_row("[dim]Mode[/dim]", f"[yellow]{cfg.mode}[/yellow]")
71
+ table.add_row("[dim]Status[/dim]", "[green]● online[/green]")
72
+ table.add_row("[dim]Version[/dim]", health.get("version", "unknown"))
73
+
74
+ console.print(Panel(table, title="Ant'z Nest Status", border_style="green"))
75
+
76
+ except Exception as e:
77
+ console.print(f"[red]✗ Cannot reach Nest:[/red] {e}")
78
+ console.print("[dim]Run: antz login --nest <url> --api-key <key>[/dim]")
79
+ raise typer.Exit(1)
80
+
81
+
82
+ # ── antz init colony ──────────────────────────────────────────────────────────
83
+
84
+ @app.command()
85
+ def init(
86
+ resource: str = typer.Argument(..., help="Resource type: colony"),
87
+ name: str = typer.Argument(..., help="Colony name (e.g. financial-credit-analysis)"),
88
+ framework: str = typer.Option("langgraph", "--framework", "-f",
89
+ help="langgraph | crewai | mixed"),
90
+ ):
91
+ """Scaffold a new Colony with the chosen framework template."""
92
+ if resource != "colony":
93
+ console.print(f"[red]Unknown resource '{resource}'. Currently supported: colony[/red]")
94
+ raise typer.Exit(1)
95
+
96
+ target = Path.cwd() / f"colony-{name}"
97
+
98
+ if target.exists():
99
+ console.print(f"[red]✗ Directory '{target.name}' already exists.[/red]")
100
+ raise typer.Exit(1)
101
+
102
+ _scaffold_colony(name=name, framework=framework, target=target)
103
+
104
+ console.print(Panel.fit(
105
+ f"[green]✓ Colony scaffolded[/green]\n\n"
106
+ f" Name: [cyan]{name}[/cyan]\n"
107
+ f" Framework: [yellow]{framework}[/yellow]\n"
108
+ f" Directory: [dim]{target}[/dim]\n\n"
109
+ f"Next steps:\n"
110
+ f" [dim]cd colony-{name}[/dim]\n"
111
+ f" [dim]code .[/dim] # open in VSCode\n"
112
+ f" [dim]antz run --demo[/dim] # run the example agent",
113
+ title="Colony created",
114
+ border_style="cyan",
115
+ ))
116
+
117
+
118
+ # ── antz run ──────────────────────────────────────────────────────────────────
119
+
120
+ @app.command()
121
+ def run(
122
+ input_file: Optional[Path] = typer.Option(None, "--input", "-i",
123
+ help="JSON input file for the agent"),
124
+ demo: bool = typer.Option(False, "--demo", help="Run the built-in demo agent"),
125
+ colony_dir: Optional[Path] = typer.Option(None, "--colony", "-c",
126
+ help="Colony directory (default: cwd)"),
127
+ ):
128
+ """Execute the Colony agent."""
129
+ cwd = colony_dir or Path.cwd()
130
+ cfg_file = cwd / "antz.config.yaml"
131
+
132
+ if not cfg_file.exists():
133
+ console.print(
134
+ "[red]✗ No antz.config.yaml found.[/red]\n"
135
+ "[dim]Run from inside a Colony directory, or use: antz init colony <name>[/dim]"
136
+ )
137
+ raise typer.Exit(1)
138
+
139
+ with open(cfg_file) as f:
140
+ colony_cfg = yaml.safe_load(f) or {}
141
+
142
+ colony_id = colony_cfg.get("colony_id", "unknown")
143
+ entrypoint = colony_cfg.get("entrypoint", "agents/main.py")
144
+
145
+ console.print(f"[dim]Running Colony [cyan]{colony_id}[/cyan]...[/dim]")
146
+
147
+ if demo:
148
+ _run_demo(cwd, colony_cfg)
149
+ return
150
+
151
+ entry_path = cwd / entrypoint
152
+ if not entry_path.exists():
153
+ console.print(f"[red]✗ Entrypoint not found: {entry_path}[/red]")
154
+ raise typer.Exit(1)
155
+
156
+ cmd = [sys.executable, str(entry_path)]
157
+ if input_file:
158
+ cmd += ["--input", str(input_file)]
159
+
160
+ result = subprocess.run(cmd, cwd=str(cwd))
161
+ raise typer.Exit(result.returncode)
162
+
163
+
164
+ # ── antz version ─────────────────────────────────────────────────────────────
165
+
166
+ @app.command()
167
+ def version():
168
+ """Show SDK version."""
169
+ from antz import __version__
170
+ console.print(f"antz-sdk [cyan]{__version__}[/cyan]")
171
+
172
+
173
+ # ── Internal helpers ──────────────────────────────────────────────────────────
174
+
175
+ def _scaffold_colony(name: str, framework: str, target: Path) -> None:
176
+ """Create Colony directory structure from template."""
177
+ target.mkdir(parents=True)
178
+
179
+ (target / "agents").mkdir()
180
+ (target / "tools").mkdir()
181
+ (target / "workflows").mkdir()
182
+ (target / "tests").mkdir()
183
+
184
+ # antz.config.yaml
185
+ config = {
186
+ "colony_id": name,
187
+ "framework": framework,
188
+ "entrypoint": "agents/main.py",
189
+ "nest_url": "http://localhost:8001",
190
+ "mode": "isolated",
191
+ }
192
+ with open(target / "antz.config.yaml", "w", encoding="utf-8") as f:
193
+ yaml.dump(config, f, default_flow_style=False)
194
+
195
+ # constitution.yaml
196
+ constitution = {
197
+ "agent_id": f"{name}-v1",
198
+ "version": "1.0",
199
+ "allowed_tools": [],
200
+ "prohibited_actions": ["delete_record", "export_data", "modify_audit_log"],
201
+ "spend_limits": {"max_per_action_usd": 5.0, "max_per_day_usd": 500.0},
202
+ "data_policy": {
203
+ "can_send_outside_nest": False,
204
+ "can_log_pii": False,
205
+ "audit_all_tool_calls": True,
206
+ },
207
+ }
208
+ with open(target / "constitution.yaml", "w", encoding="utf-8") as f:
209
+ yaml.dump(constitution, f, default_flow_style=False)
210
+
211
+ # agents/main.py
212
+ agent_code = _agent_template(name=name, framework=framework)
213
+ with open(target / "agents" / "main.py", "w", encoding="utf-8") as f:
214
+ f.write(agent_code)
215
+
216
+ # README
217
+ with open(target / "README.md", "w", encoding="utf-8") as f:
218
+ f.write(_readme_template(name=name, framework=framework))
219
+
220
+
221
+ def _agent_template(name: str, framework: str) -> str:
222
+ safe_name = name.replace("-", "_")
223
+
224
+ if framework == "langgraph":
225
+ return f'''# Colony: {name}
226
+ # Framework: LangGraph
227
+ # Ant'z SDK injects: observability, audit, constitution validation, memory
228
+
229
+ from antz import agent, tool, memory
230
+ from typing import TypedDict
231
+
232
+
233
+ # ── State ─────────────────────────────────────────────────────────────────────
234
+
235
+ class AgentState(TypedDict):
236
+ input: dict
237
+ result: dict
238
+ status: str
239
+
240
+
241
+ # ── Tools ─────────────────────────────────────────────────────────────────────
242
+
243
+ @tool("{safe_name}-lookup")
244
+ def lookup_data(query: str) -> dict:
245
+ """
246
+ Replace with your actual tool logic.
247
+ Ant'z automatically logs this call and validates against constitution.
248
+ """
249
+ return {{"data": f"mock result for: {{query}}", "confidence": 0.9}}
250
+
251
+
252
+ # ── Main agent ────────────────────────────────────────────────────────────────
253
+
254
+ @agent("{safe_name}-v1")
255
+ @memory(namespace="{safe_name}")
256
+ def run_agent(input_data: dict, memory_context: list = None) -> dict:
257
+ """
258
+ Main agent entrypoint.
259
+ memory_context is auto-populated with relevant past runs from Hive.
260
+ Replace the logic below with your LangGraph workflow.
261
+ """
262
+ past = memory_context or []
263
+ context = past[0]["content"] if past else "No prior context."
264
+
265
+ result = lookup_data(str(input_data))
266
+
267
+ return {{
268
+ "status": "success",
269
+ "colony": "{name}",
270
+ "result": result,
271
+ "memory_hits": len(past),
272
+ "context_used": context,
273
+ }}
274
+
275
+
276
+ # ── Demo entrypoint ───────────────────────────────────────────────────────────
277
+
278
+ if __name__ == "__main__":
279
+ import json, sys
280
+
281
+ sample_input = {{"query": "demo run", "source": "antz-cli"}}
282
+
283
+ if "--input" in sys.argv:
284
+ idx = sys.argv.index("--input")
285
+ with open(sys.argv[idx + 1]) as f:
286
+ sample_input = json.load(f)
287
+
288
+ output = run_agent(sample_input)
289
+ print(json.dumps(output, indent=2))
290
+ '''
291
+
292
+ elif framework == "crewai":
293
+ return f'''# Colony: {name}
294
+ # Framework: CrewAI
295
+ # Ant'z SDK injects: observability, audit, constitution validation, memory
296
+
297
+ from antz import agent, tool, memory
298
+ from crewai import Agent, Task, Crew
299
+
300
+
301
+ # ── Tools ─────────────────────────────────────────────────────────────────────
302
+
303
+ @tool("{safe_name}-lookup")
304
+ def lookup_data(query: str) -> str:
305
+ """Replace with your actual tool logic."""
306
+ return f"mock result for: {{query}}"
307
+
308
+
309
+ # ── Crew definition ───────────────────────────────────────────────────────────
310
+
311
+ def build_crew():
312
+ analyst = Agent(
313
+ role="Data Analyst",
314
+ goal="Analyze input data and produce a structured report",
315
+ backstory="Expert analyst with deep domain knowledge",
316
+ verbose=False,
317
+ )
318
+ task = Task(
319
+ description="Analyze the provided data and return a structured summary.",
320
+ expected_output="A JSON-formatted analysis report",
321
+ agent=analyst,
322
+ )
323
+ return Crew(agents=[analyst], tasks=[task], verbose=False)
324
+
325
+
326
+ # ── Main agent ────────────────────────────────────────────────────────────────
327
+
328
+ @agent("{safe_name}-v1")
329
+ @memory(namespace="{safe_name}")
330
+ def run_agent(input_data: dict, memory_context: list = None) -> dict:
331
+ """
332
+ Main agent entrypoint.
333
+ memory_context is auto-populated with relevant past runs from Hive.
334
+ """
335
+ past = memory_context or []
336
+ crew = build_crew()
337
+ result = crew.kickoff(inputs=input_data)
338
+ return {{
339
+ "status": "success",
340
+ "colony": "{name}",
341
+ "result": str(result),
342
+ "memory_hits": len(past),
343
+ }}
344
+
345
+
346
+ # ── Demo entrypoint ───────────────────────────────────────────────────────────
347
+
348
+ if __name__ == "__main__":
349
+ import json, sys
350
+
351
+ sample_input = {{"query": "demo run", "source": "antz-cli"}}
352
+
353
+ if "--input" in sys.argv:
354
+ idx = sys.argv.index("--input")
355
+ with open(sys.argv[idx + 1]) as f:
356
+ sample_input = json.load(f)
357
+
358
+ output = run_agent(sample_input)
359
+ print(json.dumps(output, indent=2))
360
+ '''
361
+
362
+ else: # mixed
363
+ return f'''# Colony: {name}
364
+ # Framework: Mixed (LangGraph + raw LiteLLM)
365
+ # Ant'z SDK injects: observability, audit, constitution validation, memory
366
+
367
+ from antz import agent, tool, memory
368
+ import httpx
369
+ import os
370
+
371
+ LITELLM_URL = os.getenv("ANTZ_LITELLM_URL", "http://localhost:4000")
372
+
373
+
374
+ # ── Tools ─────────────────────────────────────────────────────────────────────
375
+
376
+ @tool("{safe_name}-llm-call")
377
+ def call_llm(prompt: str) -> str:
378
+ """Direct LiteLLM call through the Nest — stays sovereign."""
379
+ r = httpx.post(
380
+ f"{{LITELLM_URL}}/chat/completions",
381
+ json={{
382
+ "model": "antz-default",
383
+ "messages": [{{"role": "user", "content": prompt}}],
384
+ "max_tokens": 512,
385
+ }},
386
+ timeout=30,
387
+ )
388
+ r.raise_for_status()
389
+ return r.json()["choices"][0]["message"]["content"]
390
+
391
+
392
+ # ── Main agent ────────────────────────────────────────────────────────────────
393
+
394
+ @agent("{safe_name}-v1")
395
+ @memory(namespace="{safe_name}")
396
+ def run_agent(input_data: dict, memory_context: list = None) -> dict:
397
+ """
398
+ Main agent with memory context from Hive.
399
+ memory_context is auto-populated with relevant past runs.
400
+ """
401
+ past = memory_context or []
402
+ context_str = "\\n".join(str(m) for m in past[:3]) if past else "No prior context."
403
+
404
+ prompt = f"""
405
+ Context from previous runs:
406
+ {{context_str}}
407
+
408
+ Current input: {{input_data}}
409
+
410
+ Analyze and respond with a structured result.
411
+ """
412
+
413
+ response = call_llm(prompt)
414
+
415
+ return {{
416
+ "status": "success",
417
+ "colony": "{name}",
418
+ "result": response,
419
+ "memory_hits": len(past),
420
+ }}
421
+
422
+
423
+ # ── Demo entrypoint ───────────────────────────────────────────────────────────
424
+
425
+ if __name__ == "__main__":
426
+ import json, sys
427
+
428
+ sample_input = {{"query": "demo run", "source": "antz-cli"}}
429
+
430
+ if "--input" in sys.argv:
431
+ idx = sys.argv.index("--input")
432
+ with open(sys.argv[idx + 1]) as f:
433
+ sample_input = json.load(f)
434
+
435
+ output = run_agent(sample_input)
436
+ print(json.dumps(output, indent=2))
437
+ '''
438
+
439
+
440
+ def _readme_template(name: str, framework: str) -> str:
441
+ return f"""# Colony: {name}
442
+
443
+ Framework: `{framework}`
444
+
445
+ ## Run
446
+
447
+ ```bash
448
+ # Demo (no input required)
449
+ antz run --demo
450
+
451
+ # With input file
452
+ antz run --input data/sample.json
453
+ ```
454
+
455
+ ## Structure
456
+
457
+ ```
458
+ agents/main.py # Main agent logic — edit this
459
+ tools/ # Add your tools here
460
+ workflows/ # Workflow definitions
461
+ constitution.yaml # Agent behavioral constraints
462
+ antz.config.yaml # Colony configuration
463
+ ```
464
+
465
+ ## Constitution
466
+
467
+ Edit `constitution.yaml` to define what this agent can and cannot do.
468
+ The Ant'z Hive validates every tool call against these rules at runtime.
469
+
470
+ ## Observability
471
+
472
+ All executions are automatically traced. View them in the Nest Grafana dashboard.
473
+ """
474
+
475
+
476
+ def _run_demo(cwd: Path, colony_cfg: dict) -> None:
477
+ """Run the demo mode — executes agents/main.py with sample data."""
478
+ entry = cwd / "agents" / "main.py"
479
+ if not entry.exists():
480
+ console.print("[red]✗ agents/main.py not found.[/red]")
481
+ raise typer.Exit(1)
482
+
483
+ console.print("[dim]Running demo agent...[/dim]\n")
484
+ result = subprocess.run([sys.executable, str(entry)], cwd=str(cwd))
485
+ raise typer.Exit(result.returncode)
486
+
487
+
488
+
489
+
490
+ # -- antz nest ----------------------------------------------------------------
491
+
492
+ @app.command("nest")
493
+ def nest_cmd(
494
+ action: str = typer.Argument(..., help="status"),
495
+ tenant: str = typer.Option(..., "--tenant", "-t", help="Tenant ID"),
496
+ watch: bool = typer.Option(False, "--watch", "-w"),
497
+ api_key: str = typer.Option(None, "--api-key"),
498
+ ):
499
+ """Manage Nest provisioning and status."""
500
+ import httpx, os, time
501
+ cp_url = os.getenv("ANTZ_CONTROL_PLANE_URL", "https://control.antz.studio")
502
+ if not api_key:
503
+ api_key = typer.prompt("Control Plane API Key")
504
+ STATUS = {
505
+ "pending": "Aguardando...", "provisioning": "Iniciando...",
506
+ "creating_vm": "Criando VM...", "vm_created": "VM criada...",
507
+ "wireguard_ready": "WireGuard pronto...", "provisioning_nest": "Instalando Nest...",
508
+ "sending_email": "Enviando email...", "active": "Nest ativo!", "failed": "Falhou",
509
+ }
510
+ def get():
511
+ try:
512
+ r = httpx.get(f"{cp_url}/tenants/{tenant}", headers={"x-api-key": api_key}, timeout=10)
513
+ return r.json() if r.status_code == 200 else None
514
+ except Exception:
515
+ return None
516
+ data = get()
517
+ if not data:
518
+ console.print(f"[red]Tenant {tenant!r} nao encontrado.[/red]")
519
+ raise typer.Exit(1)
520
+ if not watch:
521
+ console.print(f"Tenant: [cyan]{tenant}[/cyan]")
522
+ console.print(f"Status: {STATUS.get(data.get('status','?'), data.get('status','?'))}")
523
+ if data.get("nest_url"):
524
+ console.print(f"URL: [green]{data['nest_url']}[/green]")
525
+ return
526
+ last = None
527
+ while True:
528
+ data = get()
529
+ if not data:
530
+ time.sleep(10); continue
531
+ s = data.get("status", "")
532
+ if s != last:
533
+ console.print(STATUS.get(s, s)); last = s
534
+ if s == "active":
535
+ url = data.get("nest_url", "")
536
+ console.print(f"[green]Nest ativo em {url}[/green]")
537
+ console.print(f"[dim]antz login --nest {url} --api-key SUA_CHAVE[/dim]")
538
+ raise typer.Exit(0)
539
+ if s == "failed":
540
+ console.print("[red]Falhou.[/red]"); raise typer.Exit(1)
541
+ time.sleep(15)
542
+
543
+
544
+ if __name__ == "__main__":
545
+ app()