scitrera-aether-client 0.1.58__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.
Files changed (47) hide show
  1. scitrera_aether_client-0.1.58/PKG-INFO +788 -0
  2. scitrera_aether_client-0.1.58/README.md +753 -0
  3. scitrera_aether_client-0.1.58/pyproject.toml +78 -0
  4. scitrera_aether_client-0.1.58/scitrera_aether_client/__init__.py +367 -0
  5. scitrera_aether_client-0.1.58/scitrera_aether_client/_common.py +406 -0
  6. scitrera_aether_client-0.1.58/scitrera_aether_client/admin.py +373 -0
  7. scitrera_aether_client-0.1.58/scitrera_aether_client/admin_async.py +358 -0
  8. scitrera_aether_client-0.1.58/scitrera_aether_client/authority.py +292 -0
  9. scitrera_aether_client-0.1.58/scitrera_aether_client/authority_cache.py +447 -0
  10. scitrera_aether_client-0.1.58/scitrera_aether_client/client.py +2939 -0
  11. scitrera_aether_client-0.1.58/scitrera_aether_client/client_async.py +3918 -0
  12. scitrera_aether_client-0.1.58/scitrera_aether_client/echo_test/__init__.py +29 -0
  13. scitrera_aether_client-0.1.58/scitrera_aether_client/echo_test/echo_agent.py +294 -0
  14. scitrera_aether_client-0.1.58/scitrera_aether_client/echo_test/echo_agent_async.py +309 -0
  15. scitrera_aether_client-0.1.58/scitrera_aether_client/echo_test/echo_orchestrator.py +170 -0
  16. scitrera_aether_client-0.1.58/scitrera_aether_client/exceptions.py +504 -0
  17. scitrera_aether_client-0.1.58/scitrera_aether_client/httpx_transport.py +257 -0
  18. scitrera_aether_client-0.1.58/scitrera_aether_client/metrics.py +43 -0
  19. scitrera_aether_client-0.1.58/scitrera_aether_client/orchestrator/__init__.py +623 -0
  20. scitrera_aether_client-0.1.58/scitrera_aether_client/orchestrator/multiprocess.py +637 -0
  21. scitrera_aether_client-0.1.58/scitrera_aether_client/proto/__init__.py +0 -0
  22. scitrera_aether_client-0.1.58/scitrera_aether_client/proto/aether_pb2.py +439 -0
  23. scitrera_aether_client-0.1.58/scitrera_aether_client/proto/aether_pb2.pyi +2739 -0
  24. scitrera_aether_client-0.1.58/scitrera_aether_client/proto/aether_pb2_grpc.py +100 -0
  25. scitrera_aether_client-0.1.58/scitrera_aether_client/proxy.py +846 -0
  26. scitrera_aether_client-0.1.58/scitrera_aether_client/proxy_terminator.py +1082 -0
  27. scitrera_aether_client-0.1.58/scitrera_aether_client/py.typed +0 -0
  28. scitrera_aether_client-0.1.58/scitrera_aether_client/requests_adapter.py +178 -0
  29. scitrera_aether_client-0.1.58/scitrera_aether_client/tunnel.py +940 -0
  30. scitrera_aether_client-0.1.58/scitrera_aether_client/types.py +563 -0
  31. scitrera_aether_client-0.1.58/scitrera_aether_client.egg-info/PKG-INFO +788 -0
  32. scitrera_aether_client-0.1.58/scitrera_aether_client.egg-info/SOURCES.txt +45 -0
  33. scitrera_aether_client-0.1.58/scitrera_aether_client.egg-info/dependency_links.txt +1 -0
  34. scitrera_aether_client-0.1.58/scitrera_aether_client.egg-info/requires.txt +11 -0
  35. scitrera_aether_client-0.1.58/scitrera_aether_client.egg-info/top_level.txt +1 -0
  36. scitrera_aether_client-0.1.58/setup.cfg +4 -0
  37. scitrera_aether_client-0.1.58/tests/test_admin.py +241 -0
  38. scitrera_aether_client-0.1.58/tests/test_authority.py +418 -0
  39. scitrera_aether_client-0.1.58/tests/test_authority_cache.py +588 -0
  40. scitrera_aether_client-0.1.58/tests/test_client.py +2558 -0
  41. scitrera_aether_client-0.1.58/tests/test_client_async.py +2023 -0
  42. scitrera_aether_client-0.1.58/tests/test_orchestrator.py +1086 -0
  43. scitrera_aether_client-0.1.58/tests/test_proxy.py +580 -0
  44. scitrera_aether_client-0.1.58/tests/test_proxy_streaming.py +189 -0
  45. scitrera_aether_client-0.1.58/tests/test_proxy_terminator.py +1095 -0
  46. scitrera_aether_client-0.1.58/tests/test_requests_adapter.py +403 -0
  47. scitrera_aether_client-0.1.58/tests/test_tunnel.py +572 -0
@@ -0,0 +1,788 @@
1
+ Metadata-Version: 2.4
2
+ Name: scitrera-aether-client
3
+ Version: 0.1.58
4
+ Summary: Python client SDK for Scitrera Aether distributed control plane
5
+ Author-email: "scitrera.ai" <open-source-team@scitrera.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://aetherlayer.ai
8
+ Project-URL: Documentation, https://github.com/scitrera/aether#readme
9
+ Project-URL: Repository, https://github.com/scitrera/aether
10
+ Project-URL: Issues, https://github.com/scitrera/aether/issues
11
+ Keywords: aether,scitrera,grpc,distributed,agents,control-plane,async,asyncio
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: System :: Distributed Computing
21
+ Classifier: Typing :: Typed
22
+ Classifier: Framework :: AsyncIO
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: grpcio>=1.76.0
26
+ Requires-Dist: protobuf<8.0,>=5.29.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: grpcio-tools>=1.76.0; extra == "dev"
29
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
31
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
32
+ Requires-Dist: types-protobuf>=4.24.0; extra == "dev"
33
+ Requires-Dist: grpc-stubs>=1.53.0; extra == "dev"
34
+ Requires-Dist: build>=1.0.0; extra == "dev"
35
+
36
+ # Aether Python SDK
37
+
38
+ Aether Python SDK — client library for the Aether distributed control plane. Aether is a system for routing structured messages, tracking tasks, and managing connection lifecycles for agents, tasks, users, and other principals.
39
+
40
+ ## Features
41
+
42
+ - **Sync and Async Support**: Both synchronous (threading-based) and asynchronous (asyncio-based) client implementations
43
+ - **Multiple Client Types**: Agent, Task, User, Orchestrator, WorkflowEngine, and MetricsBridge clients
44
+ - **Key-Value Store**: Hierarchical configuration store with multiple scopes (global, workspace, user, user-workspace)
45
+ - **Task Management**: Create and manage tasks with different assignment modes
46
+ - **Checkpointing**: Persist and restore agent/task state across restarts
47
+ - **Auto-Reconnection**: Configurable exponential backoff with automatic reconnection
48
+ - **TLS/mTLS Support**: Secure connections with optional mutual TLS authentication
49
+ - **Typed + Catch-all Handlers**: Register `on_chat_message`, `on_control_message`, etc. alongside `on_message` — both fire for matching messages
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install scitrera-aether-client
55
+ ```
56
+
57
+ For development:
58
+
59
+ ```bash
60
+ pip install scitrera-aether-client[dev]
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ### Synchronous Client
66
+
67
+ ```python
68
+ from scitrera_aether_client import AgentClient, CHAT
69
+
70
+ # Create an agent client
71
+ client = AgentClient(
72
+ workspace="default",
73
+ implementation="my-agent",
74
+ specifier="agent-01"
75
+ )
76
+
77
+ # Set up message callback
78
+ def on_message(msg):
79
+ print(f"Received from {msg.source_topic}: {msg.payload.decode()}")
80
+
81
+ client.on_message = on_message
82
+
83
+ # Connect to the gateway
84
+ client.connect("localhost:50051")
85
+
86
+ # Send a message to another agent
87
+ client.send_message_to_agent(
88
+ workspace="default",
89
+ implementation="other-agent",
90
+ specifier="01",
91
+ payload=b"Hello!"
92
+ )
93
+
94
+ # Keep running until interrupted
95
+ try:
96
+ while True:
97
+ import time
98
+ time.sleep(1)
99
+ except KeyboardInterrupt:
100
+ pass
101
+ finally:
102
+ client.close()
103
+ ```
104
+
105
+ ### Asynchronous Client
106
+
107
+ ```python
108
+ import asyncio
109
+ from scitrera_aether_client import AsyncAgentClient
110
+
111
+ async def main():
112
+ client = AsyncAgentClient(
113
+ workspace="default",
114
+ implementation="my-async-agent",
115
+ specifier="agent-01"
116
+ )
117
+
118
+ async def on_message(msg):
119
+ print(f"Received: {msg.payload.decode()}")
120
+
121
+ client.on_message = on_message
122
+
123
+ await client.connect("localhost:50051")
124
+
125
+ await client.send_message_to_agent(
126
+ workspace="default",
127
+ implementation="my-async-agent",
128
+ specifier="agent-01",
129
+ payload=b"Hello from async!"
130
+ )
131
+
132
+ # Wait until disconnected
133
+ await client.wait_until_disconnected()
134
+
135
+ asyncio.run(main())
136
+ ```
137
+
138
+ ### Using Async Context Manager
139
+
140
+ ```python
141
+ async with AsyncAgentClient("default", "my-agent", "01") as client:
142
+ await client.connect("localhost:50051")
143
+ await client.send_message_to_agent("default", "other", "01", b"Hello!")
144
+ await asyncio.sleep(1)
145
+ # Connection automatically closed
146
+ ```
147
+
148
+ ## Client Types
149
+
150
+ ### AgentClient / AsyncAgentClient
151
+
152
+ For long-running agent processes that need unique identities.
153
+
154
+ ```python
155
+ from scitrera_aether_client import AgentClient
156
+
157
+ client = AgentClient(
158
+ workspace="default",
159
+ implementation="python-worker",
160
+ specifier="worker-01"
161
+ )
162
+ ```
163
+
164
+ ### TaskClient / AsyncTaskClient
165
+
166
+ For task execution. Supports both unique (named) and non-unique (pooled) tasks.
167
+
168
+ ```python
169
+ from scitrera_aether_client import TaskClient
170
+
171
+ # Unique task (named)
172
+ unique_task = TaskClient(
173
+ workspace="default",
174
+ implementation="data-processor",
175
+ unique_specifier="job-123"
176
+ )
177
+
178
+ # Non-unique task (pooled, server assigns ID)
179
+ pooled_task = TaskClient(
180
+ workspace="default",
181
+ implementation="worker"
182
+ )
183
+ ```
184
+
185
+ ### UserClient / AsyncUserClient
186
+
187
+ For user session connections (e.g., from browser windows).
188
+
189
+ ```python
190
+ from scitrera_aether_client import UserClient
191
+
192
+ client = UserClient(
193
+ user_id="user-123",
194
+ window_id="window-abc"
195
+ )
196
+ ```
197
+
198
+ ### OrchestratorClient / AsyncOrchestratorClient
199
+
200
+ For managing agent/task lifecycle and compute resources.
201
+
202
+ ```python
203
+ from scitrera_aether_client import OrchestratorClient
204
+
205
+ client = OrchestratorClient(
206
+ implementation="kubernetes-orchestrator",
207
+ supported_profiles=["docker", "kubernetes"]
208
+ )
209
+ ```
210
+
211
+ ### WorkflowEngineClient / AsyncWorkflowEngineClient
212
+
213
+ For processing events and triggering downstream actions.
214
+
215
+ ```python
216
+ from scitrera_aether_client import WorkflowEngineClient
217
+
218
+ client = WorkflowEngineClient()
219
+ ```
220
+
221
+ ### MetricsBridgeClient / AsyncMetricsBridgeClient
222
+
223
+ For collecting telemetry data from agents and tasks.
224
+
225
+ ```python
226
+ from scitrera_aether_client import MetricsBridgeClient
227
+
228
+ client = MetricsBridgeClient()
229
+ ```
230
+
231
+ ### ServiceClient / AsyncServiceClient
232
+
233
+ For trusted backend intermediaries (e.g., app servers or WebSocket backends) that act on behalf of users.
234
+
235
+ ```python
236
+ from scitrera_aether_client import ServiceClient, Credentials
237
+
238
+ client = ServiceClient(
239
+ implementation="platform-server",
240
+ specifier="pod-abc",
241
+ credentials=Credentials.api_key(api_key)
242
+ )
243
+ ```
244
+
245
+ `ServiceClient` differs from `AgentClient` in that it is workspace-less — it authenticates as itself and uses `AuthorizationContext` (on-behalf-of mode) to perform operations scoped to a user. Use `AgentClient` for long-running autonomous agents; use `ServiceClient` for backend services that proxy user actions.
246
+
247
+ ---
248
+
249
+ ## Principal Types
250
+
251
+ Aether defines the following principal types. Each maps to a dedicated client class in the Python SDK.
252
+
253
+ | Principal Type | Python SDK Class | Identity Format | Description |
254
+ |---------------|-----------------|-----------------|-------------|
255
+ | **Agent** | `AgentClient` / `AsyncAgentClient` | `ag.{workspace}.{impl}.{spec}` | Long-running autonomous process with a globally unique identity. Receives messages, can create tasks, and persists state via KV and checkpoints. |
256
+ | **Task** | `TaskClient` / `AsyncTaskClient` | `tu.{workspace}.{impl}.{spec}` (unique) or `ta.{workspace}.{impl}.{id}` (pooled) | Short-lived compute unit. Unique tasks have named identities; non-unique (pooled) tasks receive a server-assigned ID and participate in load-balanced dispatch via `tb.*` topics. |
257
+ | **User** | `UserClient` / `AsyncUserClient` | `us.{user_id}.{window_id}` | Represents a human user session (e.g., a browser window). Multiple tabs may connect simultaneously using different `window_id` values. Can also receive workspace-scoped messages via `uw.{user_id}.{workspace}`. |
258
+ | **Orchestrator** | `OrchestratorClient` / `AsyncOrchestratorClient` | `oc.{workspace}.{impl}.{spec}` | Manages compute lifecycle — receives `TaskAssignment` messages and launches agents or tasks on demand. Registers supported profiles to receive matching assignments. |
259
+ | **WorkflowEngine** | `WorkflowEngineClient` / `AsyncWorkflowEngineClient` | `we.{workspace}.{impl}.{spec}` | Processes `EVENT`-type messages and triggers downstream automation. Has the broadest send permissions — can target any principal type. |
260
+ | **MetricsBridge** | `MetricsBridgeClient` / `AsyncMetricsBridgeClient` | `mb.{workspace}.{impl}.{spec}` | Receive-only telemetry sink. Collects `METRIC`-type messages from agents and tasks; cannot send messages. |
261
+ | **Service** | `ServiceClient` / `AsyncServiceClient` | `sv.{impl}.{spec}` | Trusted backend intermediary. Workspace-less; authenticates as itself and performs privileged operations on behalf of users via `AuthorizationContext`. Use for app/WebSocket backends proxying user actions. |
262
+ | **Bridge** | *(not yet in Python SDK)* | `br.{impl}.{spec}` | Cross-workspace messaging integration (e.g., Discord, Teams, Email). Has no workspace component and can send to any workspace. Implemented as a standalone server (`cmd/msgbridge`). |
263
+
264
+ ---
265
+
266
+ ## Callbacks
267
+
268
+ All clients support the following callbacks:
269
+
270
+ | Callback | Description | Signature |
271
+ |----------|-------------|-----------|
272
+ | `on_message` | Every incoming message (catch-all) | `(msg: IncomingMessage) -> None` |
273
+ | `on_chat_message` | CHAT-typed messages | `(msg: IncomingMessage) -> None` |
274
+ | `on_control_message` | CONTROL-typed messages | `(msg: IncomingMessage) -> None` |
275
+ | `on_tool_call` | TOOL_CALL-typed messages | `(msg: IncomingMessage) -> None` |
276
+ | `on_event` | EVENT-typed messages | `(msg: IncomingMessage) -> None` |
277
+ | `on_metric` | METRIC-typed messages | `(msg: IncomingMessage) -> None` |
278
+ | `on_config` | Configuration snapshot received | `(config: ConfigSnapshot) -> None` |
279
+ | `on_signal` | Signal received | `(signal: Signal) -> None` |
280
+ | `on_error` | Error occurred | `(error: ErrorResponse) -> None` |
281
+ | `on_kv_response` | Async KV operation response | `(kv: KVResponse) -> None` |
282
+ | `on_task_assignment` | Task assigned (Orchestrators) | `(assignment: TaskAssignment) -> None` |
283
+ | `on_checkpoint_response` | Async checkpoint response | `(response: CheckpointResponse) -> None` |
284
+ | `on_connect` | Connection established | `() -> None` |
285
+ | `on_disconnect` | Connection lost | `(reason: str) -> None` |
286
+
287
+ Typed handlers (`on_chat_message`, etc.) and the catch-all `on_message` are independent. If both are registered, the typed handler fires first, then `on_message` fires as well. This matches the behavior of the Go and TypeScript SDKs.
288
+
289
+ For async clients, callbacks can be either sync or async functions:
290
+
291
+ ```python
292
+ # Sync callback
293
+ def on_message(msg):
294
+ print(msg.payload.decode())
295
+
296
+ # Async callback
297
+ async def on_message(msg):
298
+ await process_message(msg)
299
+ ```
300
+
301
+ ## Messaging
302
+
303
+ ### Message Types
304
+
305
+ ```python
306
+ from scitrera_aether_client import CHAT, CONTROL, TOOL_CALL, EVENT, METRIC
307
+
308
+ # CHAT - Regular chat messages
309
+ client.send_message_to_agent(..., message_type=CHAT)
310
+
311
+ # CONTROL - Control/command messages
312
+ client.send_message_to_agent(..., message_type=CONTROL)
313
+
314
+ # EVENT - Events for workflow engine
315
+ client.send_event(payload)
316
+
317
+ # METRIC - Telemetry data for metrics bridge
318
+ client.send_metric(payload)
319
+ ```
320
+
321
+ ### Sending Messages
322
+
323
+ ```python
324
+ # To a specific agent
325
+ client.send_message_to_agent(
326
+ workspace="default",
327
+ implementation="worker",
328
+ specifier="01",
329
+ payload=b"Hello!"
330
+ )
331
+
332
+ # To a task
333
+ client.send_message_to_task(
334
+ workspace="default",
335
+ implementation="processor",
336
+ payload=b"Process this",
337
+ unique_specifier="task-123" # Optional for unique tasks
338
+ )
339
+
340
+ # To a user session
341
+ client.send_message_to_user_session(
342
+ user_id="user-123",
343
+ window_id="window-abc",
344
+ payload=b"Notification"
345
+ )
346
+
347
+ # Broadcast to all agents in workspace
348
+ client.send_broadcast_to_agents(
349
+ workspace="default",
350
+ payload=b"Broadcast message"
351
+ )
352
+
353
+ # Send event (agents/tasks only)
354
+ client.send_event(b'{"event": "completed"}')
355
+
356
+ # Send metric (agents/tasks only)
357
+ client.send_metric(b'{"metric": "latency", "value": 42}')
358
+ ```
359
+
360
+ ## KV Operations
361
+
362
+ The KV store supports multiple scopes:
363
+
364
+ | Scope | Description | Required Parameters |
365
+ |-------|-------------|---------------------|
366
+ | `global` | Global configuration | None |
367
+ | `workspace` | Workspace-specific | `workspace` |
368
+ | `user` | User-specific | `user_id` |
369
+ | `user-workspace` | User + workspace scoped | `user_id`, `workspace` |
370
+
371
+ ### Synchronous Client KV
372
+
373
+ ```python
374
+ # Store a value (fire-and-forget)
375
+ client.kv_put(
376
+ key="config/setting",
377
+ value=b"value",
378
+ scope="global"
379
+ )
380
+
381
+ # Store with workspace scope
382
+ client.kv_put(
383
+ key="team/setting",
384
+ value=b"team-value",
385
+ scope="workspace",
386
+ workspace="default"
387
+ )
388
+
389
+ # Get a value (response via callback)
390
+ client.kv_get(key="config/setting", scope="global")
391
+
392
+ # List keys
393
+ client.kv_list(key_prefix="config/", scope="global")
394
+
395
+ # Delete a key
396
+ client.kv_delete(key="config/old", scope="global")
397
+ ```
398
+
399
+ ### Async Client KV
400
+
401
+ Async clients support both fire-and-forget (`_nowait`) and awaitable operations:
402
+
403
+ ```python
404
+ # Fire-and-forget
405
+ await client.kv_put_nowait(
406
+ key="setting",
407
+ value=b"value",
408
+ scope="global"
409
+ )
410
+
411
+ # Await response
412
+ response = await client.kv_get(
413
+ key="setting",
414
+ scope="global",
415
+ timeout=5.0
416
+ )
417
+ if response:
418
+ print(f"Value: {response.value}")
419
+
420
+ # Put and await confirmation
421
+ response = await client.kv_put(
422
+ key="setting",
423
+ value=b"new-value",
424
+ scope="global",
425
+ timeout=5.0
426
+ )
427
+ ```
428
+
429
+ ## Task Creation
430
+
431
+ Create tasks with different assignment modes:
432
+
433
+ ```python
434
+ from scitrera_aether_client import SELF_ASSIGN, TARGETED, POOL
435
+
436
+ # Self-assigned task (creator handles it)
437
+ client.create_task(
438
+ task_type="data-processing",
439
+ workspace="default",
440
+ assignment_mode=SELF_ASSIGN,
441
+ metadata={"priority": "high"}
442
+ )
443
+
444
+ # Targeted task (assign to specific agent)
445
+ client.create_task(
446
+ task_type="specialized-work",
447
+ workspace="default",
448
+ assignment_mode=TARGETED,
449
+ target_agent_id="ag.default.worker.specialist-01",
450
+ launch_param_overrides={"memory": "4G"}
451
+ )
452
+
453
+ # Pool task (load-balanced to available workers)
454
+ client.create_task(
455
+ task_type="batch-job",
456
+ workspace="default",
457
+ assignment_mode=POOL
458
+ )
459
+ ```
460
+
461
+ ## Checkpointing
462
+
463
+ Save and restore agent/task state:
464
+
465
+ ### Synchronous
466
+
467
+ ```python
468
+ # Save checkpoint
469
+ client.checkpoint_save(data=b"state data", key="my-checkpoint")
470
+
471
+ # Save and wait for confirmation
472
+ response = client.checkpoint_save_sync(
473
+ data=b"state data",
474
+ key="my-checkpoint",
475
+ timeout=5.0
476
+ )
477
+
478
+ # Load checkpoint
479
+ response = client.checkpoint_load_sync(key="my-checkpoint", timeout=5.0)
480
+ if response and response.data:
481
+ print(f"Restored state: {response.data}")
482
+
483
+ # List checkpoints
484
+ response = client.checkpoint_list_sync(timeout=5.0)
485
+ if response:
486
+ print(f"Checkpoints: {response.keys}")
487
+
488
+ # Delete checkpoint
489
+ client.checkpoint_delete_sync(key="my-checkpoint", timeout=5.0)
490
+ ```
491
+
492
+ ### Asynchronous
493
+
494
+ ```python
495
+ # Save and wait
496
+ response = await client.checkpoint_save(
497
+ data=b"state data",
498
+ key="my-checkpoint",
499
+ timeout=5.0
500
+ )
501
+
502
+ # Load
503
+ response = await client.checkpoint_load(key="my-checkpoint", timeout=5.0)
504
+
505
+ # Fire-and-forget operations
506
+ await client.checkpoint_save_nowait(data=b"state", key="quick-save")
507
+ await client.checkpoint_delete_nowait(key="old-checkpoint")
508
+ ```
509
+
510
+ ## Admin Operations
511
+
512
+ The SDK exposes named admin helpers via `AdminClient` (sync) and
513
+ `AsyncAdminClient` (async). Both wrap an already-connected client whose
514
+ authenticated identity has admin permissions on the gateway. The wrappers
515
+ own no connection of their own — pass in any connected
516
+ `AgentClient` / `UserClient` / `ServiceClient` (or their async
517
+ equivalents).
518
+
519
+ Covered surface: workspace CRUD, agent registry inspection, ACL rule
520
+ management and fallback policies, API token lifecycle, and read-side
521
+ workflow rule queries. The async wrapper additionally forwards
522
+ `list_connections` / `disconnect_session` to the underlying session-op
523
+ primitive.
524
+
525
+ ### Synchronous
526
+
527
+ ```python
528
+ from scitrera_aether_client import AgentClient, AdminClient, Credentials
529
+
530
+ agent = AgentClient(
531
+ workspace="default",
532
+ implementation="admin-agent",
533
+ specifier="ops-1",
534
+ credentials=Credentials.api_key("my-admin-key"),
535
+ )
536
+ agent.connect("localhost:50051")
537
+
538
+ admin = AdminClient(agent)
539
+
540
+ # Workspaces
541
+ admin.create_workspace(workspace_id="ws-1", display_name="Workspace One")
542
+ workspaces = admin.list_workspaces(limit=50)
543
+
544
+ # Tokens
545
+ token = admin.create_token(name="ci-token", principal_type="agent")
546
+ print(token.plaintext_token)
547
+ admin.revoke_token(token_id=token.token.id)
548
+
549
+ # ACL
550
+ admin.create_acl_rule(
551
+ principal_type="user",
552
+ principal_id="alice",
553
+ resource_type="workspace",
554
+ resource_id="ws-1",
555
+ access_level=20, # READWRITE
556
+ granted_by="ops",
557
+ )
558
+ rules = admin.list_acl_rules(principal_type="user")
559
+
560
+ # Agents (read-only inspection)
561
+ agents = admin.list_agents()
562
+ info = admin.get_agent(implementation="scitrera/echo-bot")
563
+
564
+ # Workflow rules (read-only)
565
+ admin.list_workflow_rules(workspace="ws-1")
566
+ ```
567
+
568
+ ### Asynchronous
569
+
570
+ ```python
571
+ import asyncio
572
+ from scitrera_aether_client import (
573
+ AsyncAgentClient,
574
+ AsyncAdminClient,
575
+ Credentials,
576
+ )
577
+
578
+ async def main():
579
+ agent = AsyncAgentClient(
580
+ workspace="default",
581
+ implementation="admin-agent",
582
+ specifier="ops-1",
583
+ credentials=Credentials.api_key("my-admin-key"),
584
+ )
585
+ await agent.connect("localhost:50051")
586
+
587
+ admin = AsyncAdminClient(agent)
588
+
589
+ await admin.create_workspace(workspace_id="ws-1", display_name="Workspace One")
590
+ workspaces = await admin.list_workspaces(limit=50)
591
+
592
+ token = await admin.create_token(name="ci-token", principal_type="agent")
593
+ print(token.plaintext_token)
594
+
595
+ # Session ops are forwarded to the underlying async client.
596
+ conns = await admin.list_connections(workspace="ws-1")
597
+ await admin.disconnect_session(session_id="abc-123", reason="ops cleanup")
598
+
599
+ asyncio.run(main())
600
+ ```
601
+
602
+ > **Note**: A few methods on the TypeScript `AdminClient` (notably
603
+ > `getHealth`) rely on streaming pathways that are not yet exposed as
604
+ > primitives on the Python client. Use the REST admin endpoints for
605
+ > gateway health checks until the corresponding `admin_query` primitive
606
+ > is added.
607
+
608
+ ## Connection Configuration
609
+
610
+ All clients support configurable connection behavior:
611
+
612
+ ```python
613
+ client = AgentClient(
614
+ workspace="default",
615
+ implementation="worker",
616
+ specifier="01",
617
+ # Retry configuration
618
+ max_retries=5, # Max connection attempts (0 = infinite)
619
+ initial_backoff=1.0, # Initial retry delay in seconds
620
+ max_backoff=30.0, # Maximum retry delay
621
+ auto_reconnect=True # Auto-reconnect on connection loss
622
+ )
623
+ ```
624
+
625
+ ### Reconnection Behavior
626
+
627
+ - On connection loss, clients automatically attempt to reconnect (if `auto_reconnect=True`)
628
+ - Exponential backoff with jitter prevents thundering herd
629
+ - Non-recoverable errors (authentication failures, etc.) stop reconnection attempts
630
+ - Session IDs are preserved for session resumption when possible
631
+
632
+ ## Workspace Switching
633
+
634
+ Agents and tasks can switch workspaces:
635
+
636
+ ```python
637
+ client.switch_workspace("new-workspace")
638
+ ```
639
+
640
+ ## Constants
641
+
642
+ ```python
643
+ from scitrera_aether_client import (
644
+ # Message types
645
+ CHAT, # Regular messages
646
+ CONTROL, # Control/command messages
647
+ TOOL_CALL, # Tool invocations
648
+ EVENT, # Events for workflow engine
649
+ METRIC, # Telemetry data
650
+
651
+ # Task assignment modes
652
+ SELF_ASSIGN, # Creator handles the task
653
+ TARGETED, # Assign to specific agent
654
+ POOL, # Load-balanced assignment
655
+
656
+ # KV operations
657
+ KV_GET,
658
+ KV_PUT,
659
+ KV_LIST,
660
+ KV_DELETE,
661
+
662
+ # KV scopes
663
+ KV_SCOPE_GLOBAL,
664
+ KV_SCOPE_WORKSPACE,
665
+ KV_SCOPE_USER,
666
+ KV_SCOPE_USER_WORKSPACE,
667
+ )
668
+ ```
669
+
670
+ ## Examples
671
+
672
+ See the `example.py` and `example_async.py` files for comprehensive examples including:
673
+
674
+ - Agent client with messaging, KV operations, and task creation
675
+ - Orchestrator for managing agent lifecycle
676
+ - Workflow engine for event processing
677
+ - Metrics bridge for telemetry collection
678
+ - Concurrent async clients
679
+ - Context manager usage
680
+
681
+ Run examples:
682
+
683
+ ```bash
684
+ # Sync examples
685
+ python example.py agent
686
+ python example.py orchestrator
687
+ python example.py workflow
688
+ python example.py metrics
689
+
690
+ # Async examples
691
+ python example_async.py agent
692
+ python example_async.py concurrent
693
+ python example_async.py context
694
+ ```
695
+
696
+ ## TLS Configuration
697
+
698
+ ```python
699
+ # Simple TLS (server authentication using system CA)
700
+ client = AgentClient(
701
+ workspace="default", implementation="worker", specifier="w1",
702
+ tls_enabled=True,
703
+ )
704
+
705
+ # Custom CA certificate
706
+ client = AgentClient(
707
+ workspace="default", implementation="worker", specifier="w1",
708
+ tls_enabled=True,
709
+ tls_root_cert_path="/path/to/ca.pem",
710
+ )
711
+
712
+ # mTLS (mutual authentication)
713
+ client = AgentClient(
714
+ workspace="default", implementation="worker", specifier="w1",
715
+ tls_enabled=True,
716
+ tls_root_cert_path="/path/to/ca.pem",
717
+ tls_client_cert_path="/path/to/client.pem",
718
+ tls_client_key_path="/path/to/client-key.pem",
719
+ )
720
+
721
+ # Pass certificate bytes directly instead of paths
722
+ client = AgentClient(
723
+ workspace="default", implementation="worker", specifier="w1",
724
+ tls_enabled=True,
725
+ tls_root_cert=ca_cert_bytes,
726
+ tls_client_cert=client_cert_bytes,
727
+ tls_client_key=client_key_bytes,
728
+ )
729
+ ```
730
+
731
+ ## Proxy
732
+
733
+ Route HTTP requests through the Aether connection to a service principal using
734
+ the httpx transport adapters:
735
+
736
+ ```python
737
+ import httpx
738
+ from scitrera_aether_client.httpx_transport import AetherHTTPXTransport
739
+
740
+ transport = AetherHTTPXTransport(aether_client, "sv::memorylayer::default")
741
+ with httpx.Client(transport=transport) as http:
742
+ resp = http.get("http://ignored/v1/memories/abc")
743
+ ```
744
+
745
+ Async variant:
746
+
747
+ ```python
748
+ from scitrera_aether_client.httpx_transport import AetherAsyncHTTPXTransport
749
+
750
+ transport = AetherAsyncHTTPXTransport(aether_client, "sv::memorylayer::default")
751
+ async with httpx.AsyncClient(transport=transport) as http:
752
+ resp = await http.get("http://ignored/v1/memories/abc")
753
+ ```
754
+
755
+ For `requests` users, mount `AetherRequestsAdapter` on a session:
756
+
757
+ ```python
758
+ import requests
759
+ from scitrera_aether_client.requests_adapter import AetherRequestsAdapter
760
+
761
+ session = requests.Session()
762
+ session.mount("aether+sv://", AetherRequestsAdapter(aether_client))
763
+ session.get("aether+sv://memorylayer--default/v1/memories/abc")
764
+ ```
765
+
766
+ For full details on sidecar deployment, service addressing, ACL/OBO model,
767
+ limits, audit events, and failure modes, see
768
+ [server/docs/proxy.md](../../server/docs/proxy.md).
769
+
770
+ ## Key Architectural Principle
771
+
772
+ **Connection = Lock = Heartbeat**: The active gRPC stream IS the distributed lock AND the liveness proof for the connected identity. No separate heartbeat API exists. When the stream closes, the lock is released automatically.
773
+
774
+ ## Requirements
775
+
776
+ - Python 3.11+
777
+ - grpcio >= 1.76.0
778
+ - protobuf >= 5.29.0
779
+
780
+ ## License
781
+
782
+ Copyright 2024-2025 Scitrera. Licensed under the Apache License, Version 2.0.
783
+
784
+ ## Links
785
+
786
+ - [GitHub Repository](https://github.com/scitrera/aether)
787
+ - [Documentation](https://github.com/scitrera/aether#readme)
788
+ - [Issue Tracker](https://github.com/scitrera/aether/issues)