agentics-you 1.2.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.
@@ -0,0 +1,82 @@
1
+ """agentics-you — the Agentics Python SDK: shared memory for AI agents, receipts and the gateway.
2
+
3
+ Memory quickstart:
4
+
5
+ from agentics_you import AgenticsClient
6
+
7
+ client = AgenticsClient() # reads AGENTICS_API_KEY (ak_...), optional AGENTICS_AGENT
8
+ client.memories.add("Dana prefers aisle seats.", wait=True)
9
+ hits = client.memories.search("Which seat does Dana want?", k=5)["hits"]
10
+
11
+ Receipts (decorator style):
12
+
13
+ from agentics_you import Network
14
+
15
+ net = Network(api_key="ak_...")
16
+
17
+ @net.task(scope=["read:tickets", "write:reply"])
18
+ def handle_ticket(ticket_id: str) -> str:
19
+ return reply
20
+
21
+ Extras: ``agentics-you[openai]`` (agentics_you.openai_agents trace processor) and ``agentics-you[litellm]``
22
+ (agentics_you.litellm_callback).
23
+ """
24
+ from .client import Agentics, Task, TaskContext
25
+ from .network import Network
26
+ from .version import __version__
27
+
28
+ # Memory client (namespaces, memories, ingest) with typed errors and retries.
29
+ from .memory import (
30
+ AgenticsClient,
31
+ AsyncAgenticsClient,
32
+ AgenticsError,
33
+ ValidationError,
34
+ AuthenticationError,
35
+ PermissionDeniedError,
36
+ NotFoundError,
37
+ ConflictError,
38
+ RateLimitError,
39
+ ServerError,
40
+ )
41
+
42
+ # Task receipts posted to /v1/receipts (formerly packages/agentics-python).
43
+ from .receipts import client, Client
44
+
45
+ # Gateway client (drop-in OpenAI-compatible base_url swap). Stdlib-only, so this
46
+ # import is always safe even without the `openai` package installed.
47
+ from .gateway import (
48
+ AgenticsGateway,
49
+ GatewayError,
50
+ createHeaders,
51
+ list_models,
52
+ DEFAULT_GATEWAY_BASE_URL,
53
+ )
54
+
55
+ __all__ = [
56
+ # Memory
57
+ "AgenticsClient", "AsyncAgenticsClient", "AgenticsError", "ValidationError", "AuthenticationError",
58
+ "PermissionDeniedError", "NotFoundError", "ConflictError", "RateLimitError", "ServerError",
59
+ # Receipts
60
+ "client", "Client",
61
+ "Agentics", "Task", "TaskContext",
62
+ "Network",
63
+ "AgenticsTasks", "AgenticsTasksError",
64
+ # Gateway (drop-in OpenAI-compatible client + trace/metadata propagation)
65
+ "AgenticsGateway", "GatewayError", "createHeaders", "list_models",
66
+ "DEFAULT_GATEWAY_BASE_URL",
67
+ "__version__",
68
+ ]
69
+
70
+
71
+ def __getattr__(name):
72
+ """Lazily expose the framework callback handlers so importing `agentics`
73
+ never requires LangChain/LlamaIndex/CrewAI to be installed."""
74
+ if name in ("AgenticsTasks", "AgenticsTasksError"): # needs httpx, which is not a dependency
75
+ from . import tasks as _tasks
76
+
77
+ return getattr(_tasks, name)
78
+ if name in ("instrument", "LangchainCallbackHandler", "LlamaIndexCallbackHandler"):
79
+ from . import instrument as _instr
80
+
81
+ return getattr(_instr, name)
82
+ raise AttributeError(f"module 'agentics_you' has no attribute {name!r}")
agentics_you/cli.py ADDED
@@ -0,0 +1,305 @@
1
+ """Agentics CLI.
2
+
3
+ Usage:
4
+ agentics login --api-key ak_...
5
+ agentics deploy [--manifest agentics.json]
6
+ agentics receipt send --agent support-bot --model anthropic/claude-opus-4-6 --outcome success --input "..." --output "..."
7
+ agentics agents list
8
+ agentics flags
9
+ agentics whoami
10
+
11
+ # Gateway devex verbs (sdk spec Appendix A)
12
+ agentics gateway test [--url https://agentics.you/api/v1] [--user a@b.com]
13
+ agentics config publish [config.json] [--name default]
14
+ agentics config validate [config.json]
15
+ agentics prompt push <prompt.json>
16
+ agentics log tail [--limit 50]
17
+ agentics proof verify <hash>
18
+ agentics proof bundle <hash>
19
+ agentics models [--provider anthropic]
20
+ """
21
+ from __future__ import annotations
22
+ import argparse, json, os, sys
23
+ from urllib import request as _request
24
+ from urllib.error import HTTPError
25
+
26
+ from .client import Agentics, Task
27
+
28
+
29
+ def _api(method: str, path: str, body=None, key: str | None = None, query: dict | None = None):
30
+ base = os.environ.get("AGENTICS_BASE_URL", "https://agentics.you")
31
+ if query:
32
+ from urllib.parse import urlencode
33
+ clean = {k: v for k, v in query.items() if v is not None}
34
+ if clean:
35
+ path = f"{path}?{urlencode(clean)}"
36
+ req = _request.Request(
37
+ f"{base}{path}",
38
+ data=json.dumps(body).encode("utf-8") if body is not None else None,
39
+ headers={
40
+ "Content-Type": "application/json",
41
+ "Authorization": f"Bearer {key or os.environ.get('AGENTICS_API_KEY','')}",
42
+ },
43
+ method=method,
44
+ )
45
+ try:
46
+ with _request.urlopen(req, timeout=15) as r:
47
+ return json.loads(r.read().decode("utf-8") or "{}")
48
+ except HTTPError as e:
49
+ try:
50
+ payload = json.loads(e.read().decode("utf-8") or "{}")
51
+ except Exception:
52
+ payload = {}
53
+ # 404 from a flag-gated surface == "feature not enabled for this tenant".
54
+ if e.code == 404:
55
+ raise SystemExit(
56
+ "HTTP 404: feature not enabled for this tenant "
57
+ f"(path may be flag-gated). {payload.get('error') or ''}".strip()
58
+ )
59
+ raise SystemExit(f"HTTP {e.code}: {payload.get('error') or payload}")
60
+
61
+
62
+ def _read_json_file(path: str) -> dict:
63
+ if not path or not os.path.exists(path):
64
+ raise SystemExit(f"file not found: {path}")
65
+ with open(path) as f:
66
+ return json.loads(f.read() or "{}")
67
+
68
+
69
+ def _load_manifest(path: str | None) -> dict:
70
+ """Load agent manifest from JSON (or YAML if PyYAML installed)."""
71
+ candidates = [path] if path else ["agentics.json", "agentics.yaml", "agentics.yml"]
72
+ for cand in candidates:
73
+ if not cand or not os.path.exists(cand):
74
+ continue
75
+ with open(cand) as f:
76
+ raw = f.read()
77
+ if cand.endswith((".yaml", ".yml")):
78
+ try:
79
+ import yaml # type: ignore
80
+ except ImportError:
81
+ raise SystemExit(
82
+ f"Manifest {cand} is YAML but PyYAML is not installed. "
83
+ "Install with: pip install agentics[yaml]"
84
+ )
85
+ return yaml.safe_load(raw) or {}
86
+ return json.loads(raw or "{}")
87
+ raise SystemExit(
88
+ "No agent manifest found. Create agentics.json (or pass --manifest) with at least: "
89
+ "foundation_model, scope_array, display_name."
90
+ )
91
+
92
+
93
+ def _do_deploy(args) -> None:
94
+ manifest = _load_manifest(args.manifest)
95
+ # CLI flags override manifest values
96
+ for flag, key in (
97
+ ("handle", "handle"),
98
+ ("model", "foundation_model"),
99
+ ("name", "display_name"),
100
+ ("description", "description"),
101
+ ("avatar_url", "avatar_url"),
102
+ ("owner_email", "owner_email"),
103
+ ):
104
+ v = getattr(args, flag, None)
105
+ if v:
106
+ manifest[key] = v
107
+ if args.scope:
108
+ manifest["scope_array"] = [s.strip() for s in args.scope.split(",") if s.strip()]
109
+
110
+ if not manifest.get("foundation_model"):
111
+ raise SystemExit("Manifest missing required field: foundation_model")
112
+ if not manifest.get("scope_array"):
113
+ raise SystemExit("Manifest missing required field: scope_array (e.g. ['read:tickets'])")
114
+
115
+ print(f"Deploying agent to {os.environ.get('AGENTICS_BASE_URL', 'https://agentics.you')} ...")
116
+ resp = _api("POST", "/api/v1/agents/register", body=manifest)
117
+ print(json.dumps(resp, indent=2))
118
+ if resp.get("api_key"):
119
+ print(
120
+ "\n✓ Deployed. Save the api_key above as AGENTICS_API_KEY; the agent will start"
121
+ " appearing on /network/registry once its first receipt is anchored.",
122
+ file=sys.stderr,
123
+ )
124
+
125
+
126
+ def main(argv=None):
127
+ p = argparse.ArgumentParser(prog="agentics", description="Agentics CLI")
128
+ sub = p.add_subparsers(dest="cmd", required=True)
129
+
130
+ sp = sub.add_parser("login"); sp.add_argument("--api-key", required=True)
131
+ sub.add_parser("whoami")
132
+ sub.add_parser("flags")
133
+ sub.add_parser("agents")
134
+
135
+ dp = sub.add_parser("deploy", help="Register or update an agent from a manifest")
136
+ dp.add_argument("--manifest", help="Path to agentics.json / agentics.yaml (default: ./agentics.json)")
137
+ dp.add_argument("--handle", help="Override handle from manifest")
138
+ dp.add_argument("--model", help="Override foundation_model")
139
+ dp.add_argument("--name", help="Override display_name")
140
+ dp.add_argument("--description", help="Override description")
141
+ dp.add_argument("--avatar-url", dest="avatar_url", help="Override avatar URL")
142
+ dp.add_argument("--owner-email", dest="owner_email", help="Owner contact email")
143
+ dp.add_argument("--scope", help="Comma-separated scope override, e.g. read:tickets,write:reply")
144
+
145
+ rs = sub.add_parser("receipt"); rss = rs.add_subparsers(dest="rcmd", required=True)
146
+ rsn = rss.add_parser("send")
147
+ rsn.add_argument("--agent", required=True)
148
+ rsn.add_argument("--model", required=True)
149
+ rsn.add_argument("--version", default="")
150
+ rsn.add_argument("--outcome", default="success", choices=["success", "partial", "failed"])
151
+ rsn.add_argument("--input", default="")
152
+ rsn.add_argument("--output", default="")
153
+ rsn.add_argument("--scope", default="", help="comma-separated declared scope")
154
+ rsn.add_argument("--scope-used", default="", help="comma-separated used scope")
155
+
156
+ # ── gateway test ──────────────────────────────────────────────────────────
157
+ gt = sub.add_parser("gateway", help="Gateway client helpers")
158
+ gts = gt.add_subparsers(dest="gcmd", required=True)
159
+ gtt = gts.add_parser("test", help="Send a 1-token ping through the gateway")
160
+ gtt.add_argument("--url", help="Gateway base_url (…/v1). Defaults to env AGENTICS_GATEWAY_URL")
161
+ gtt.add_argument("--user", default=None, help="x-agentics-user attribution")
162
+ gtt.add_argument("--model", default="@anthropic/claude-opus-4-8")
163
+
164
+ # ── config publish / validate ─────────────────────────────────────────────
165
+ cp = sub.add_parser("config", help="Routing config (Routing Fabric)")
166
+ cps = cp.add_subparsers(dest="ccmd", required=True)
167
+ cpp = cps.add_parser("publish", help="Create + publish a GatewayConfig")
168
+ cpp.add_argument("file", nargs="?", default="gateway-config.json")
169
+ cpp.add_argument("--name", default=None, help="Config name (default: from file or 'default')")
170
+ cpv = cps.add_parser("validate", help="Dry-run validate a GatewayConfig (no publish)")
171
+ cpv.add_argument("file", nargs="?", default="gateway-config.json")
172
+
173
+ # ── prompt push ───────────────────────────────────────────────────────────
174
+ pp = sub.add_parser("prompt", help="Prompt Registry")
175
+ pps = pp.add_subparsers(dest="pcmd", required=True)
176
+ ppp = pps.add_parser("push", help="Publish a prompt template")
177
+ ppp.add_argument("file")
178
+
179
+ # ── log tail ──────────────────────────────────────────────────────────────
180
+ lt = sub.add_parser("log", help="Gateway receipt logs")
181
+ lts = lt.add_subparsers(dest="lcmd", required=True)
182
+ ltt = lts.add_parser("tail", help="Tail recent gateway receipts (with trace_id)")
183
+ ltt.add_argument("--limit", type=int, default=50)
184
+ ltt.add_argument("--since", default=None)
185
+
186
+ # ── proof verify / bundle ─────────────────────────────────────────────────
187
+ pr = sub.add_parser("proof", help="Verify a receipt end-to-end (Proof Mesh)")
188
+ prs = pr.add_subparsers(dest="prcmd", required=True)
189
+ prv = prs.add_parser("verify", help="4-step verify: leaf, chain, merkle, anchor")
190
+ prv.add_argument("hash")
191
+ prb = prs.add_parser("bundle", help="Offline-verifiable evidence bundle")
192
+ prb.add_argument("hash")
193
+
194
+ # ── models (Model Garden catalog, D8) ─────────────────────────────────────
195
+ md = sub.add_parser("models", help="List the Model Garden catalog")
196
+ md.add_argument("--provider", default=None)
197
+ md.add_argument("--modality", default=None)
198
+ md.add_argument("-q", "--query", dest="q", default=None)
199
+ md.add_argument("--include-deprecated", action="store_true")
200
+
201
+ args = p.parse_args(argv)
202
+
203
+ if args.cmd == "login":
204
+ cfg = os.path.expanduser("~/.agentics")
205
+ os.makedirs(cfg, exist_ok=True)
206
+ with open(os.path.join(cfg, "credentials"), "w") as f:
207
+ f.write(args.api_key)
208
+ print("Saved credentials to ~/.agentics/credentials")
209
+ return
210
+
211
+ # Load creds from file if env is empty
212
+ if not os.environ.get("AGENTICS_API_KEY"):
213
+ cfg = os.path.expanduser("~/.agentics/credentials")
214
+ if os.path.exists(cfg):
215
+ with open(cfg) as f:
216
+ os.environ["AGENTICS_API_KEY"] = f.read().strip()
217
+
218
+ if args.cmd == "whoami":
219
+ print(json.dumps(_api("GET", "/api/me"), indent=2))
220
+ elif args.cmd == "flags":
221
+ print(json.dumps(_api("GET", "/api/admin/flags"), indent=2))
222
+ elif args.cmd == "agents":
223
+ print(json.dumps(_api("GET", "/api/agents-e"), indent=2))
224
+ elif args.cmd == "deploy":
225
+ _do_deploy(args)
226
+ elif args.cmd == "receipt" and args.rcmd == "send":
227
+ client = Agentics(async_buffer=False)
228
+ t = Task(
229
+ agent_id=args.agent,
230
+ foundation_model=args.model,
231
+ model_version=args.version,
232
+ scope=[s for s in args.scope.split(",") if s],
233
+ )
234
+ t.set_outputs(args.output)
235
+ t.set_outcome(args.outcome)
236
+ for s in [s for s in args.scope_used.split(",") if s]:
237
+ t.use_scope(s)
238
+ client.send(t)
239
+ client.flush()
240
+ print(json.dumps({"sent": True, "task_id": t.task_id}, indent=2))
241
+
242
+ # ── gateway test ──────────────────────────────────────────────────────────
243
+ elif args.cmd == "gateway" and args.gcmd == "test":
244
+ from .gateway import AgenticsGateway, GatewayError
245
+ gw = AgenticsGateway(base_url=args.url).with_options(user=args.user)
246
+ try:
247
+ resp = gw.chat.completions.create(
248
+ model=args.model,
249
+ messages=[{"role": "user", "content": "ping"}],
250
+ max_tokens=1,
251
+ )
252
+ print(json.dumps({"ok": True, "gateway": gw.base_url, "response": resp}, indent=2))
253
+ except GatewayError as e:
254
+ raise SystemExit(f"gateway test failed: HTTP {e.status}: {e.body[:300]}")
255
+
256
+ # ── config publish / validate (Routing Fabric /api/routing/*) ─────────────
257
+ elif args.cmd == "config" and args.ccmd == "publish":
258
+ body = _read_json_file(args.file)
259
+ name = args.name or body.get("name") or "default"
260
+ created = _api("POST", "/api/routing/configs", body={"name": name, "body": body.get("body", body)})
261
+ cfg_id = created.get("id")
262
+ if not cfg_id:
263
+ raise SystemExit(f"create failed: {json.dumps(created)}")
264
+ published = _api("POST", f"/api/routing/configs/{cfg_id}/publish")
265
+ print(json.dumps({"published": True, "id": cfg_id, "name": name, "result": published}, indent=2))
266
+ elif args.cmd == "config" and args.ccmd == "validate":
267
+ body = _read_json_file(args.file)
268
+ # Routing Fabric's simulate endpoint dry-runs a config without persisting.
269
+ out = _api("POST", "/api/routing/simulate", body={"config": body.get("body", body)})
270
+ print(json.dumps(out, indent=2))
271
+
272
+ # ── prompt push (Prompt Registry /api/prompts/*) ──────────────────────────
273
+ elif args.cmd == "prompt" and args.pcmd == "push":
274
+ body = _read_json_file(args.file)
275
+ print(json.dumps(_api("POST", "/api/prompts", body=body), indent=2))
276
+
277
+ # ── log tail (gateway receipt tail w/ trace_id — sdk devex /api/sdk/logs) ──
278
+ elif args.cmd == "log" and args.lcmd == "tail":
279
+ out = _api("GET", "/api/sdk/logs", query={"limit": args.limit, "since": args.since})
280
+ for row in out.get("logs", []):
281
+ print(json.dumps(row))
282
+
283
+ # ── proof verify / bundle (Proof Mesh /api/proof/*) ───────────────────────
284
+ elif args.cmd == "proof" and args.prcmd == "verify":
285
+ out = _api("GET", "/api/proof/verify", query={"hash": args.hash})
286
+ print(json.dumps(out, indent=2))
287
+ if not out.get("verified"):
288
+ raise SystemExit(2)
289
+ elif args.cmd == "proof" and args.prcmd == "bundle":
290
+ print(json.dumps(_api("GET", "/api/proof/bundle", query={"hash": args.hash}), indent=2))
291
+
292
+ # ── models (Model Garden catalog, D8) ─────────────────────────────────────
293
+ elif args.cmd == "models":
294
+ out = _api("GET", "/api/model-garden/catalog", query={
295
+ "provider": args.provider, "modality": args.modality, "q": args.q,
296
+ "includeDeprecated": "true" if args.include_deprecated else None,
297
+ })
298
+ rows = out if isinstance(out, list) else (
299
+ out.get("models") or out.get("catalog") or out.get("data") or out.get("rows") or []
300
+ )
301
+ print(json.dumps(rows, indent=2))
302
+
303
+
304
+ if __name__ == "__main__":
305
+ main()
agentics_you/client.py ADDED
@@ -0,0 +1,201 @@
1
+ """Agentics SDK client + Task context manager."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import time
7
+ import uuid
8
+ import hashlib
9
+ import threading
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timezone
12
+ from typing import Any, Dict, Iterable, List, Optional
13
+ from urllib import request as _request
14
+ from urllib.error import URLError, HTTPError
15
+
16
+ from .version import __version__
17
+
18
+ DEFAULT_BASE_URL = "https://agentics.you"
19
+
20
+
21
+ def _now_iso() -> str:
22
+ return datetime.now(timezone.utc).isoformat()
23
+
24
+
25
+ def _sha256(s: str) -> str:
26
+ return hashlib.sha256(s.encode("utf-8") if isinstance(s, str) else s).hexdigest()
27
+
28
+
29
+ @dataclass
30
+ class Task:
31
+ agent_id: str
32
+ foundation_model: str
33
+ model_version: str
34
+ system_prompt: str = ""
35
+ tool_manifest: Any = None
36
+ scope: List[str] = field(default_factory=list)
37
+ task_id: str = field(default_factory=lambda: str(uuid.uuid4()))
38
+ started_at: str = field(default_factory=_now_iso)
39
+ finished_at: Optional[str] = None
40
+ outcome: str = "success"
41
+ cost_usd: float = 0.0
42
+ tokens_in: int = 0
43
+ tokens_out: int = 0
44
+ scope_used: List[str] = field(default_factory=list)
45
+ inputs: str = ""
46
+ outputs: str = ""
47
+ metadata: Dict[str, Any] = field(default_factory=dict)
48
+
49
+ def set_inputs(self, text: str) -> None:
50
+ self.inputs = text
51
+
52
+ def set_outputs(self, text: str) -> None:
53
+ self.outputs = text
54
+
55
+ def set_outcome(self, outcome: str) -> None:
56
+ if outcome not in ("success", "partial", "failed"):
57
+ raise ValueError("outcome must be one of: success | partial | failed")
58
+ self.outcome = outcome
59
+
60
+ def add_usage(self, *, tokens_in: int = 0, tokens_out: int = 0, cost_usd: float = 0.0) -> None:
61
+ self.tokens_in += tokens_in
62
+ self.tokens_out += tokens_out
63
+ self.cost_usd += cost_usd
64
+
65
+ def use_scope(self, scope: str) -> None:
66
+ if scope not in self.scope_used:
67
+ self.scope_used.append(scope)
68
+
69
+ def to_payload(self) -> Dict[str, Any]:
70
+ return {
71
+ "event_id": self.task_id,
72
+ "agent": {
73
+ "external_id": self.agent_id,
74
+ "foundation_model": self.foundation_model,
75
+ "model_version": self.model_version,
76
+ "system_prompt": self.system_prompt,
77
+ "tool_manifest": self.tool_manifest,
78
+ "scope": self.scope,
79
+ },
80
+ "task": {
81
+ "task_id": self.task_id,
82
+ "started_at": self.started_at,
83
+ "finished_at": self.finished_at or _now_iso(),
84
+ "outcome": self.outcome,
85
+ "cost_usd": self.cost_usd,
86
+ "tokens_in": self.tokens_in,
87
+ "tokens_out": self.tokens_out,
88
+ "scope_used": self.scope_used,
89
+ "inputs": self.inputs,
90
+ "outputs": self.outputs,
91
+ "metadata": self.metadata,
92
+ },
93
+ }
94
+
95
+
96
+ class TaskContext:
97
+ """Context manager that auto-emits a receipt on exit."""
98
+
99
+ def __init__(self, client: "Agentics", task: Task) -> None:
100
+ self.client = client
101
+ self.task = task
102
+
103
+ def __enter__(self) -> Task:
104
+ return self.task
105
+
106
+ def __exit__(self, exc_type, exc, tb) -> None:
107
+ if exc:
108
+ self.task.set_outcome("failed")
109
+ self.task.metadata["error"] = repr(exc)
110
+ self.task.finished_at = _now_iso()
111
+ try:
112
+ self.client.send(self.task)
113
+ except Exception: # never break the caller because telemetry failed
114
+ pass
115
+
116
+
117
+ class Agentics:
118
+ def __init__(
119
+ self,
120
+ api_key: Optional[str] = None,
121
+ base_url: Optional[str] = None,
122
+ timeout: float = 5.0,
123
+ async_buffer: bool = True,
124
+ ) -> None:
125
+ self.api_key = api_key or os.environ.get("AGENTICS_API_KEY", "")
126
+ if not self.api_key:
127
+ raise RuntimeError("Agentics SDK: AGENTICS_API_KEY required")
128
+ self.base_url = base_url or os.environ.get("AGENTICS_BASE_URL", DEFAULT_BASE_URL)
129
+ self.timeout = timeout
130
+ self._buffer: List[Dict[str, Any]] = []
131
+ self._buffer_lock = threading.Lock()
132
+ self._async = async_buffer
133
+ if async_buffer:
134
+ self._flusher = threading.Thread(target=self._flush_loop, daemon=True)
135
+ self._flusher.start()
136
+
137
+ def track(
138
+ self,
139
+ *,
140
+ agent_id: str,
141
+ foundation_model: str,
142
+ model_version: str = "",
143
+ system_prompt: str = "",
144
+ tool_manifest: Any = None,
145
+ scope: Optional[List[str]] = None,
146
+ metadata: Optional[Dict[str, Any]] = None,
147
+ ) -> TaskContext:
148
+ return TaskContext(self, Task(
149
+ agent_id=agent_id, foundation_model=foundation_model,
150
+ model_version=model_version, system_prompt=system_prompt,
151
+ tool_manifest=tool_manifest, scope=list(scope or []),
152
+ metadata=dict(metadata or {}),
153
+ ))
154
+
155
+ def send(self, task: Task) -> None:
156
+ payload = task.to_payload()
157
+ if self._async:
158
+ with self._buffer_lock:
159
+ self._buffer.append(payload)
160
+ else:
161
+ self._post(payload)
162
+
163
+ def send_batch(self, tasks: Iterable[Task]) -> Dict[str, Any]:
164
+ return self._post({"batch": [t.to_payload() for t in tasks]})
165
+
166
+ def flush(self) -> None:
167
+ with self._buffer_lock:
168
+ items = self._buffer[:]
169
+ self._buffer.clear()
170
+ if not items:
171
+ return
172
+ try:
173
+ self._post({"batch": items})
174
+ except Exception:
175
+ with self._buffer_lock:
176
+ self._buffer.extend(items[:1000])
177
+
178
+ def _flush_loop(self) -> None:
179
+ while True:
180
+ time.sleep(2.0)
181
+ self.flush()
182
+
183
+ def _post(self, body: Dict[str, Any]) -> Dict[str, Any]:
184
+ data = json.dumps(body).encode("utf-8")
185
+ req = _request.Request(
186
+ f"{self.base_url}/api/sdk/ingest",
187
+ data=data,
188
+ headers={
189
+ "Content-Type": "application/json",
190
+ "Authorization": f"Bearer {self.api_key}",
191
+ "User-Agent": f"agentics-python/{__version__}",
192
+ },
193
+ method="POST",
194
+ )
195
+ try:
196
+ with _request.urlopen(req, timeout=self.timeout) as resp:
197
+ return json.loads(resp.read().decode("utf-8"))
198
+ except HTTPError as e:
199
+ return {"error": "http", "status": e.code, "body": e.read().decode("utf-8", "ignore")}
200
+ except URLError as e:
201
+ return {"error": "network", "reason": str(e)}