connectonion 0.6.4__py3-none-any.whl → 0.6.5__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.
Files changed (33) hide show
  1. connectonion/__init__.py +1 -1
  2. connectonion/cli/co_ai/main.py +2 -2
  3. connectonion/cli/co_ai/prompts/connectonion/concepts/trust.md +166 -208
  4. connectonion/cli/commands/copy_commands.py +21 -0
  5. connectonion/cli/commands/trust_commands.py +152 -0
  6. connectonion/cli/main.py +82 -0
  7. connectonion/core/llm.py +2 -2
  8. connectonion/docs/concepts/fast_rules.md +237 -0
  9. connectonion/docs/concepts/onboarding.md +465 -0
  10. connectonion/docs/concepts/trust.md +933 -192
  11. connectonion/docs/design-decisions/023-trust-policy-system-design.md +323 -0
  12. connectonion/docs/network/README.md +23 -1
  13. connectonion/docs/network/connect.md +135 -0
  14. connectonion/docs/network/host.md +73 -4
  15. connectonion/network/__init__.py +7 -6
  16. connectonion/network/asgi/__init__.py +3 -0
  17. connectonion/network/asgi/http.py +125 -19
  18. connectonion/network/asgi/websocket.py +276 -15
  19. connectonion/network/connect.py +145 -29
  20. connectonion/network/host/auth.py +70 -67
  21. connectonion/network/host/routes.py +88 -3
  22. connectonion/network/host/server.py +100 -17
  23. connectonion/network/trust/__init__.py +27 -19
  24. connectonion/network/trust/factory.py +51 -24
  25. connectonion/network/trust/fast_rules.py +100 -0
  26. connectonion/network/trust/tools.py +316 -32
  27. connectonion/network/trust/trust_agent.py +403 -0
  28. connectonion/transcribe.py +1 -1
  29. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/METADATA +1 -1
  30. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/RECORD +32 -27
  31. connectonion/network/trust/prompts.py +0 -71
  32. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/WHEEL +0 -0
  33. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/entry_points.txt +0 -0
@@ -26,6 +26,7 @@ don't interfere between concurrent requests.
26
26
  """
27
27
 
28
28
  import os
29
+ from functools import partial
29
30
  from pathlib import Path
30
31
  from typing import Callable, Union
31
32
 
@@ -33,7 +34,8 @@ from rich.console import Console
33
34
  from rich.panel import Panel
34
35
 
35
36
  from ..asgi import create_app as asgi_create_app
36
- from ..trust import get_default_trust_level
37
+ from ..trust import TrustAgent, get_default_trust_level, parse_policy, TRUST_LEVELS
38
+ from ..trust.factory import PROMPTS_DIR
37
39
  from .session import SessionStorage
38
40
  from .auth import extract_and_authenticate
39
41
  from .routes import (
@@ -44,9 +46,49 @@ from .routes import (
44
46
  info_handler,
45
47
  admin_logs_handler,
46
48
  admin_sessions_handler,
49
+ # Admin trust routes
50
+ admin_trust_promote_handler,
51
+ admin_trust_demote_handler,
52
+ admin_trust_block_handler,
53
+ admin_trust_unblock_handler,
54
+ admin_trust_level_handler,
55
+ # Super admin routes
56
+ admin_admins_add_handler,
57
+ admin_admins_remove_handler,
47
58
  )
48
59
 
49
60
 
61
+ def _parse_trust_config(trust: Union[str, "Agent"]) -> dict | None:
62
+ """Parse trust config from trust parameter.
63
+
64
+ Returns YAML config dict if trust is a level or file path, None otherwise.
65
+ Used to extract onboard info for /info endpoint.
66
+ """
67
+ if not isinstance(trust, str):
68
+ return None
69
+
70
+ # Check if it's a trust level
71
+ if trust.lower() in TRUST_LEVELS:
72
+ policy_path = PROMPTS_DIR / f"{trust.lower()}.md"
73
+ if policy_path.exists():
74
+ config, _ = parse_policy(policy_path.read_text(encoding='utf-8'))
75
+ return config
76
+ return None
77
+
78
+ # Check if it's a file path
79
+ path = Path(trust)
80
+ if path.exists() and path.is_file():
81
+ config, _ = parse_policy(path.read_text(encoding='utf-8'))
82
+ return config
83
+
84
+ # Inline policy text
85
+ if trust.startswith('---'):
86
+ config, _ = parse_policy(trust)
87
+ return config
88
+
89
+ return None
90
+
91
+
50
92
  def get_default_trust() -> str:
51
93
  """Get default trust based on environment.
52
94
 
@@ -70,7 +112,7 @@ def _extract_agent_metadata(create_agent: Callable) -> tuple[dict, object]:
70
112
  return metadata, sample
71
113
 
72
114
 
73
- def _create_route_handlers(create_agent: Callable, agent_metadata: dict, result_ttl: int):
115
+ def _create_route_handlers(create_agent: Callable, agent_metadata: dict, result_ttl: int, trust_agent):
74
116
  """Create route handler dict for ASGI app.
75
117
 
76
118
  Args:
@@ -79,6 +121,7 @@ def _create_route_handlers(create_agent: Callable, agent_metadata: dict, result_
79
121
  agent_metadata: Pre-extracted metadata (name, tools, address) - avoids
80
122
  creating agents for health/info endpoints.
81
123
  result_ttl: How long to keep results on server in seconds
124
+ trust_agent: TrustAgent instance for trust operations
82
125
  """
83
126
  agent_name = agent_metadata["name"]
84
127
 
@@ -91,8 +134,8 @@ def _create_route_handlers(create_agent: Callable, agent_metadata: dict, result_
91
134
  def handle_health(start_time):
92
135
  return health_handler(agent_name, start_time)
93
136
 
94
- def handle_info(trust):
95
- return info_handler(agent_metadata, trust)
137
+ def handle_info(trust, trust_config=None):
138
+ return info_handler(agent_metadata, trust, trust_config)
96
139
 
97
140
  def handle_admin_logs():
98
141
  return admin_logs_handler(agent_name)
@@ -107,6 +150,17 @@ def _create_route_handlers(create_agent: Callable, agent_metadata: dict, result_
107
150
  "ws_input": handle_ws_input,
108
151
  "admin_logs": handle_admin_logs,
109
152
  "admin_sessions": admin_sessions_handler,
153
+ # TrustAgent instance for direct access in http.py/websocket.py
154
+ "trust_agent": trust_agent,
155
+ # Admin trust routes (partial injects trust_agent as first arg)
156
+ "admin_trust_promote": partial(admin_trust_promote_handler, trust_agent),
157
+ "admin_trust_demote": partial(admin_trust_demote_handler, trust_agent),
158
+ "admin_trust_block": partial(admin_trust_block_handler, trust_agent),
159
+ "admin_trust_unblock": partial(admin_trust_unblock_handler, trust_agent),
160
+ "admin_trust_level": partial(admin_trust_level_handler, trust_agent),
161
+ # Super admin routes
162
+ "admin_admins_add": partial(admin_admins_add_handler, trust_agent),
163
+ "admin_admins_remove": partial(admin_admins_remove_handler, trust_agent),
110
164
  }
111
165
 
112
166
 
@@ -197,14 +251,14 @@ def host(
197
251
  whitelist: Allowed identities
198
252
 
199
253
  Endpoints:
200
- POST /input - Submit prompt, get result
201
- GET /sessions/{id} - Get session by ID
202
- GET /sessions - List all sessions
203
- GET /health - Health check
204
- GET /info - Agent info
205
- WS /ws - WebSocket
206
- GET /logs - Activity log (requires OPENONION_API_KEY)
207
- GET /logs/sessions - Activity sessions (requires OPENONION_API_KEY)
254
+ POST /input - Submit prompt, get result
255
+ GET /sessions/{id} - Get session by ID
256
+ GET /sessions - List all sessions
257
+ GET /health - Health check
258
+ GET /info - Agent info
259
+ WS /ws - WebSocket
260
+ GET /admin/logs - Activity log (requires OPENONION_API_KEY)
261
+ GET /admin/sessions - Activity sessions (requires OPENONION_API_KEY)
208
262
  """
209
263
  import uvicorn
210
264
  from ... import address
@@ -228,11 +282,24 @@ def host(
228
282
  agent_metadata["address"] = addr_data['address']
229
283
 
230
284
  storage = SessionStorage()
231
- route_handlers = _create_route_handlers(create_agent, agent_metadata, result_ttl)
285
+
286
+ # Create TrustAgent instance - the single interface for all trust operations
287
+ # Users can subclass TrustAgent to customize (e.g., database-backed admin storage)
288
+ if isinstance(trust, TrustAgent):
289
+ trust_agent = trust
290
+ else:
291
+ trust_agent = TrustAgent(trust if isinstance(trust, str) else "careful")
292
+
293
+ route_handlers = _create_route_handlers(create_agent, agent_metadata, result_ttl, trust_agent)
294
+
295
+ # Parse trust config for /info onboard info
296
+ trust_config = _parse_trust_config(trust)
297
+
232
298
  app = asgi_create_app(
233
299
  route_handlers=route_handlers,
234
300
  storage=storage,
235
- trust=trust,
301
+ trust=trust_agent, # Pass resolved TrustAgent, not raw trust
302
+ trust_config=trust_config,
236
303
  blacklist=blacklist,
237
304
  whitelist=whitelist,
238
305
  )
@@ -243,13 +310,23 @@ def host(
243
310
 
244
311
  # Display startup info
245
312
  relay_status = f"[green]✓[/] {relay_url}" if relay_url else "[dim]disabled[/]"
313
+
314
+ # Build trust info for display
315
+ trust_info = ""
316
+ if trust_config and isinstance(trust, str) and trust.lower() in TRUST_LEVELS:
317
+ onboard = trust_config.get("onboard", {})
318
+ invite_codes = onboard.get("invite_code", [])
319
+ if invite_codes:
320
+ codes_str = ", ".join(invite_codes) if isinstance(invite_codes, list) else invite_codes
321
+ trust_info = f"\n[bold]Invite:[/] {codes_str}\n[dim] Run 'co copy trust/{trust}' to customize[/]"
322
+
246
323
  Console().print(Panel(
247
324
  f"[bold]POST[/] http://localhost:{port}/input\n"
248
325
  f"[dim]GET /sessions/{{id}} · /sessions · /health · /info[/]\n"
249
326
  f"[dim]WS ws://localhost:{port}/ws\n"
250
327
  f"[dim]UI http://localhost:{port}/docs[/]\n\n"
251
328
  f"[bold]Address:[/] {agent_metadata['address']}\n"
252
- f"[bold]Relay:[/] {relay_status}",
329
+ f"[bold]Relay:[/] {relay_status}{trust_info}",
253
330
  title=f"[green]Agent '{agent_metadata['name']}'[/]"
254
331
  ))
255
332
 
@@ -279,11 +356,17 @@ def create_app(create_agent: Callable, storage=None, trust="careful", result_ttl
279
356
  agent_metadata, sample = _extract_agent_metadata(create_agent)
280
357
  agent_metadata["address"] = get_agent_address(sample)
281
358
 
282
- route_handlers = _create_route_handlers(create_agent, agent_metadata, result_ttl)
359
+ # Create TrustAgent instance
360
+ if isinstance(trust, TrustAgent):
361
+ trust_agent = trust
362
+ else:
363
+ trust_agent = TrustAgent(trust if isinstance(trust, str) else "careful")
364
+
365
+ route_handlers = _create_route_handlers(create_agent, agent_metadata, result_ttl, trust_agent)
283
366
  return asgi_create_app(
284
367
  route_handlers=route_handlers,
285
368
  storage=storage,
286
- trust=trust,
369
+ trust=trust_agent, # Pass resolved TrustAgent, not raw trust
287
370
  blacklist=blacklist,
288
371
  whitelist=whitelist,
289
372
  )
@@ -1,30 +1,38 @@
1
1
  """
2
- Purpose: Trust verification system module re-exporting factory, prompts, and verification tools
3
- LLM-Note:
4
- Dependencies: imports from [factory.py, prompts.py, tools.py] | imported by [network/__init__.py, network/host/auth.py, network/host/server.py] | tested by [tests/network/test_trust.py]
5
- Data flow: re-exports trust agent creation utilities and trust level prompts
6
- State/Effects: no state
7
- Integration: exposes create_trust_agent(trust_spec) → Agent, get_default_trust_level() → str, validate_trust_level(level), TRUST_LEVELS dict, TRUST_PROMPTS dict, get_trust_prompt(level) → str, get_trust_verification_tools() → list | used by host() for access control policies
8
- Performance: trivial
9
- Errors: none
10
2
  Trust verification system for agent networking.
11
3
 
12
- This module contains:
13
- - factory: Trust agent creation and configuration
14
- - prompts: Pre-configured trust prompts for different levels
15
- - tools: Verification tools for trust agents
4
+ Usage:
5
+ from connectonion.network.trust import TrustAgent
6
+
7
+ trust = TrustAgent("careful") # or "open", "strict", or path to policy
8
+
9
+ # Access control
10
+ decision = trust.should_allow("client-123", {"prompt": "hello"})
11
+
12
+ # Trust level operations
13
+ trust.promote_to_contact("client-123")
14
+ trust.block("bad-actor", reason="spam")
15
+ level = trust.get_level("client-123") # "stranger", "contact", "whitelist", "blocked"
16
+
17
+ # Admin operations
18
+ trust.is_admin("client-123")
19
+ trust.add_admin("new-admin")
20
+
21
+ TrustAgent is the single interface for all trust operations.
22
+ Subclass to customize behavior (e.g., database-backed admin storage).
16
23
  """
17
24
 
18
- from .factory import create_trust_agent, get_default_trust_level, validate_trust_level, TRUST_LEVELS
19
- from .prompts import TRUST_PROMPTS, get_trust_prompt
20
- from .tools import get_trust_verification_tools
25
+ from .trust_agent import TrustAgent, Decision
26
+ from .factory import get_default_trust_level, validate_trust_level, TRUST_LEVELS
27
+ from .fast_rules import parse_policy
21
28
 
22
29
  __all__ = [
23
- "create_trust_agent",
30
+ # TrustAgent - the single interface
31
+ "TrustAgent",
32
+ "Decision",
33
+ # Helpers
24
34
  "get_default_trust_level",
25
35
  "validate_trust_level",
26
36
  "TRUST_LEVELS",
27
- "TRUST_PROMPTS",
28
- "get_trust_prompt",
29
- "get_trust_verification_tools",
37
+ "parse_policy",
30
38
  ]
@@ -1,23 +1,36 @@
1
1
  """
2
- Purpose: Factory for creating trust verification agents with policies.
2
+ Factory for creating trust verification agents with policies.
3
+
4
+ Policy files use YAML frontmatter for fast rules + markdown body for LLM prompts:
5
+
6
+ ---
7
+ allow: [whitelisted, contact]
8
+ deny: [blocked]
9
+ onboard:
10
+ invite_code: [BETA2024]
11
+ payment: 10
12
+ default: ask
13
+ ---
14
+ # LLM Prompt for trust evaluation...
15
+
16
+ Fast rules execute without LLM (zero tokens, instant). Only 'default: ask' triggers LLM.
3
17
 
4
18
  String Resolution Priority:
5
- 1. Trust level ("open", "careful", "strict")
19
+ 1. Trust level ("open", "careful", "strict") → loads from prompts/trust/{level}.md
6
20
  2. File path (if exists)
7
21
  3. Inline policy text
8
-
9
- LLM-Note:
10
- Dependencies: [os, pathlib, typing, .prompts, .tools] | imported by [host/server.py] | tested by [tests/unit/test_trust.py]
11
- Data flow: trust param → check env default → resolve by priority → return Agent or None
12
- Errors: TypeError (invalid type) | FileNotFoundError (Path doesn't exist)
13
22
  """
14
23
 
15
24
  import os
16
25
  from pathlib import Path
17
26
  from typing import Union, Optional
18
27
 
19
- from .prompts import get_trust_prompt
20
28
  from .tools import get_trust_verification_tools
29
+ from .fast_rules import parse_policy
30
+
31
+
32
+ # Path to trust policy files (at repo root: prompts/trust/)
33
+ PROMPTS_DIR = Path(__file__).parent.parent.parent.parent / "prompts" / "trust"
21
34
 
22
35
 
23
36
  # Trust level constants
@@ -45,21 +58,28 @@ def get_default_trust_level() -> Optional[str]:
45
58
 
46
59
  def create_trust_agent(trust: Union[str, Path, 'Agent', None], api_key: Optional[str] = None, model: str = "gpt-5-mini") -> Optional['Agent']:
47
60
  """
48
- Create a trust agent based on the trust parameter.
61
+ DEPRECATED: Use TrustAgent instead.
62
+
63
+ >>> from connectonion.network.trust import TrustAgent
64
+ >>> trust = TrustAgent("careful")
65
+ >>> trust.should_allow("client-123")
66
+
67
+ This function returns a regular Agent, which lacks TrustAgent methods
68
+ like should_allow(), promote_to_contact(), etc.
49
69
 
50
70
  Args:
51
- trust: Trust configuration:
52
- - None: Check CONNECTONION_TRUST env, else return None
53
- - Agent: Return as-is (must have tools)
54
- - Path: Read file as policy
55
- - str: Resolved by priority:
56
- 1. Trust level ("open", "careful", "strict")
57
- 2. File path (if file exists)
58
- 3. Inline policy text
71
+ trust: Trust configuration (see TrustAgent for new API)
59
72
 
60
73
  Returns:
61
74
  Agent configured for trust verification, or None
62
75
  """
76
+ import warnings
77
+ warnings.warn(
78
+ "create_trust_agent() is deprecated. Use TrustAgent instead: "
79
+ "from connectonion.network.trust import TrustAgent; trust = TrustAgent('careful')",
80
+ DeprecationWarning,
81
+ stacklevel=2
82
+ )
63
83
  from ...core.agent import Agent # Import here to avoid circular dependency
64
84
 
65
85
  # If None, check for environment default
@@ -95,13 +115,20 @@ def create_trust_agent(trust: Union[str, Path, 'Agent', None], api_key: Optional
95
115
  # Handle string: trust level > file path > inline policy
96
116
  if isinstance(trust, str):
97
117
  if trust.lower() in TRUST_LEVELS:
98
- return Agent(
99
- name=f"trust_agent_{trust.lower()}",
100
- tools=trust_tools,
101
- system_prompt=get_trust_prompt(trust.lower()),
102
- api_key=api_key,
103
- model=model
104
- )
118
+ # Load from prompts/trust/{level}.md
119
+ policy_path = PROMPTS_DIR / f"{trust.lower()}.md"
120
+ if policy_path.exists():
121
+ policy_text = policy_path.read_text(encoding='utf-8')
122
+ config, markdown_body = parse_policy(policy_text)
123
+ return Agent(
124
+ name=f"trust_agent_{trust.lower()}",
125
+ tools=trust_tools,
126
+ system_prompt=markdown_body,
127
+ api_key=api_key,
128
+ model=model
129
+ )
130
+ # Fallback if file doesn't exist
131
+ raise FileNotFoundError(f"Trust policy file not found: {policy_path}")
105
132
 
106
133
  path = Path(trust)
107
134
  if path.exists() and path.is_file():
@@ -0,0 +1,100 @@
1
+ """
2
+ Parse YAML config from trust policy files and execute fast rules.
3
+
4
+ Config format:
5
+ allow: [whitelisted, contact] # Who has access
6
+ deny: [blocked] # Who is blocked
7
+ onboard: # How strangers become contacts
8
+ invite_code: [CODE1, CODE2]
9
+ payment: 10
10
+ default: deny # allow | deny | ask
11
+ """
12
+
13
+ import yaml
14
+ from typing import Optional
15
+ from .tools import is_whitelisted, is_blocked, is_contact, promote_to_contact
16
+
17
+
18
+ def parse_policy(policy_text: str) -> tuple[dict, str]:
19
+ """
20
+ Parse YAML frontmatter from markdown policy file.
21
+
22
+ Returns:
23
+ (config_dict, markdown_body)
24
+ """
25
+ if not policy_text.startswith('---'):
26
+ return {}, policy_text
27
+
28
+ end = policy_text.find('---', 3)
29
+ if end == -1:
30
+ return {}, policy_text
31
+
32
+ yaml_content = policy_text[3:end].strip()
33
+ markdown_body = policy_text[end + 3:].strip()
34
+
35
+ config = yaml.safe_load(yaml_content) or {}
36
+ return config, markdown_body
37
+
38
+
39
+ def evaluate_request(config: dict, client_id: str, request: dict) -> Optional[str]:
40
+ """
41
+ Evaluate request using fast rules (no LLM).
42
+
43
+ Config format:
44
+ allow: [whitelisted, contact] # Who has access
45
+ deny: [blocked] # Who is blocked
46
+ onboard: # How strangers become contacts
47
+ invite_code: [CODE1]
48
+ payment: 10
49
+ default: deny # allow | deny | ask
50
+
51
+ Args:
52
+ config: Parsed YAML config
53
+ client_id: The client making request
54
+ request: Request data (may contain invite_code, payment, etc.)
55
+
56
+ Returns:
57
+ 'allow', 'deny', or None (needs LLM)
58
+ """
59
+ # 1. Check deny list first (blocked users)
60
+ deny_list = config.get('deny', ['blocked'])
61
+ for condition in deny_list:
62
+ if condition == 'blocked' and is_blocked(client_id):
63
+ return 'deny'
64
+
65
+ # 2. Check allow list (whitelisted, contacts)
66
+ allow_list = config.get('allow', [])
67
+ for condition in allow_list:
68
+ if condition == 'whitelisted' and is_whitelisted(client_id):
69
+ return 'allow'
70
+ if condition == 'contact' and is_contact(client_id):
71
+ return 'allow'
72
+
73
+ # 3. Try onboarding (stranger → contact)
74
+ onboard = config.get('onboard', {})
75
+
76
+ # Check invite code
77
+ valid_codes = onboard.get('invite_code', [])
78
+ request_code = request.get('invite_code')
79
+ if request_code and request_code in valid_codes:
80
+ promote_to_contact(client_id)
81
+ return 'allow'
82
+
83
+ # Check payment
84
+ required_payment = onboard.get('payment')
85
+ request_payment = request.get('payment', 0)
86
+ if required_payment and request_payment >= required_payment:
87
+ promote_to_contact(client_id)
88
+ return 'allow'
89
+
90
+ # 4. Default action for strangers without onboarding
91
+ default = config.get('default', 'deny')
92
+
93
+ if default == 'allow':
94
+ return 'allow'
95
+ elif default == 'deny':
96
+ return 'deny'
97
+ elif default == 'ask':
98
+ return None # Needs LLM evaluation
99
+
100
+ return 'deny' # Safe fallback