create-leafmesh 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- create_leafmesh/__init__.py +3 -0
- create_leafmesh/cli.py +252 -0
- create_leafmesh/create.py +106 -0
- create_leafmesh/templates/Dockerfile +21 -0
- create_leafmesh/templates/README.md +309 -0
- create_leafmesh/templates/agency/__init__.py +0 -0
- create_leafmesh/templates/agency/advisor_agent.py +151 -0
- create_leafmesh/templates/agency/external_agents.py +278 -0
- create_leafmesh/templates/agency/fallback_researcher_agent.py +80 -0
- create_leafmesh/templates/agency/greeter_agent.py +79 -0
- create_leafmesh/templates/agency/processor_agent.py +90 -0
- create_leafmesh/templates/agency/researcher_agent.py +99 -0
- create_leafmesh/templates/agency/scheduler_agent.py +67 -0
- create_leafmesh/templates/agency/tools.py +123 -0
- create_leafmesh/templates/claude_skills/leafmesh/SKILL.md +2049 -0
- create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md +1309 -0
- create_leafmesh/templates/claude_skills/leafmesh/examples.md +537 -0
- create_leafmesh/templates/claude_skills/leafmesh/reference.md +492 -0
- create_leafmesh/templates/configs/config.yaml +1028 -0
- create_leafmesh/templates/docker-compose.yml +28 -0
- create_leafmesh/templates/dockerignore +17 -0
- create_leafmesh/templates/env +109 -0
- create_leafmesh/templates/gitignore +33 -0
- create_leafmesh/templates/hitl_stub_receiver.py +149 -0
- create_leafmesh/templates/main.py +105 -0
- create_leafmesh/templates/requirements.txt +10 -0
- create_leafmesh-2.1.0.dist-info/METADATA +6 -0
- create_leafmesh-2.1.0.dist-info/RECORD +31 -0
- create_leafmesh-2.1.0.dist-info/WHEEL +5 -0
- create_leafmesh-2.1.0.dist-info/entry_points.txt +2 -0
- create_leafmesh-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
services:
|
|
2
|
+
redis:
|
|
3
|
+
image: redis:7-alpine
|
|
4
|
+
ports:
|
|
5
|
+
- "6379:6379"
|
|
6
|
+
volumes:
|
|
7
|
+
- redis_data:/data
|
|
8
|
+
healthcheck:
|
|
9
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
10
|
+
interval: 10s
|
|
11
|
+
timeout: 5s
|
|
12
|
+
retries: 3
|
|
13
|
+
|
|
14
|
+
app:
|
|
15
|
+
build: .
|
|
16
|
+
ports:
|
|
17
|
+
- "18820:18820"
|
|
18
|
+
env_file:
|
|
19
|
+
- .env
|
|
20
|
+
environment:
|
|
21
|
+
REDIS_HOST: redis
|
|
22
|
+
REDIS_PORT: 6379
|
|
23
|
+
depends_on:
|
|
24
|
+
redis:
|
|
25
|
+
condition: service_healthy
|
|
26
|
+
|
|
27
|
+
volumes:
|
|
28
|
+
redis_data:
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# {{project_name}} environment variables
|
|
2
|
+
|
|
3
|
+
# Required
|
|
4
|
+
LEAFMESH_LICENSE_KEY=your-license-key-here # Get yours at https://leafcraft.ai
|
|
5
|
+
OPENAI_API_KEY=your-key-here
|
|
6
|
+
REDIS_HOST=localhost
|
|
7
|
+
REDIS_PORT=6379
|
|
8
|
+
REDIS_PASSWORD=
|
|
9
|
+
|
|
10
|
+
# Alternative LLM Providers (uncomment to use)
|
|
11
|
+
# ANTHROPIC_API_KEY=your-anthropic-key
|
|
12
|
+
# GOOGLE_API_KEY=your-google-key
|
|
13
|
+
# DEEPSEEK_API_KEY=your-deepseek-key
|
|
14
|
+
|
|
15
|
+
# Microsoft Foundry / Azure AI (uncomment to use)
|
|
16
|
+
# AZURE_FOUNDRY_API_KEY=your-foundry-api-key
|
|
17
|
+
# AZURE_FOUNDRY_TOKEN=your-entra-id-token # Alternative: Entra ID bearer token
|
|
18
|
+
|
|
19
|
+
# External Integrations (uncomment when needed)
|
|
20
|
+
# COMPOSIO_API_KEY=your-composio-key
|
|
21
|
+
# ZAPIER_NLA_API_KEY=your-zapier-key
|
|
22
|
+
# N8N_BASE_URL=http://localhost:5678
|
|
23
|
+
# N8N_API_KEY=your-n8n-key
|
|
24
|
+
# CREWAI_API_KEY=your-crewai-key
|
|
25
|
+
# LANGGRAPH_API_URL=http://localhost:8123
|
|
26
|
+
# LANGGRAPH_API_KEY=your-langgraph-key
|
|
27
|
+
|
|
28
|
+
# Webhooks (for human agent interface)
|
|
29
|
+
# WEBHOOK_OUTBOUND_URL=https://your-webhook-endpoint.com
|
|
30
|
+
# WEBHOOK_TOKEN=your-webhook-token
|
|
31
|
+
# WEBHOOK_AUTH_TOKEN=your-inbound-auth-token
|
|
32
|
+
|
|
33
|
+
# API Server
|
|
34
|
+
# Default port is 18820. Change if running multiple projects locally.
|
|
35
|
+
# LEAFMESH_API_PORT=18820
|
|
36
|
+
|
|
37
|
+
# Observability
|
|
38
|
+
# Observability auto-enables with a valid LEAFMESH_LICENSE_KEY.
|
|
39
|
+
# LEAFMESH_ENV_TOKEN is your unique environment key (from https://leafcraft.ai).
|
|
40
|
+
LEAFMESH_ENV_TOKEN=your-env-token-here
|
|
41
|
+
|
|
42
|
+
# ────────────────────────────────────────────────────────────────────────
|
|
43
|
+
# Optional security / hardening knobs
|
|
44
|
+
# All have safe defaults — uncomment only when you need to tune them.
|
|
45
|
+
# ────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
# X-API-Key auth middleware. Default in the SDK is ON — every request to
|
|
48
|
+
# the API server must carry a valid X-API-Key header (validated against
|
|
49
|
+
# the LeafCraft auth backend). For LOCAL DEVELOPMENT WITHOUT INTERNET or
|
|
50
|
+
# without a real LeafCraft account yet, set to 0 to disable the middleware
|
|
51
|
+
# entirely (no header required, no backend call, requests pass through).
|
|
52
|
+
# Never set =0 in production.
|
|
53
|
+
# LEAFMESH_AUTH_STRICT=1
|
|
54
|
+
|
|
55
|
+
# Cookie SameSite policy for SSE auth (HITL / evolution / trace streams).
|
|
56
|
+
# Default Lax = cookie sent only on same-site requests. Set to None when
|
|
57
|
+
# your frontend lives on a different host than the SDK (ADK Studio at
|
|
58
|
+
# app.example.com → SDK at api.example.com, or local dev with frontend
|
|
59
|
+
# on localhost:5173 → SDK on 127.0.0.1:18820). The SDK auto-flips
|
|
60
|
+
# Secure=true with None; loopback HTTP works because browsers treat
|
|
61
|
+
# 127.0.0.1 / localhost as secure contexts.
|
|
62
|
+
# LEAFMESH_SSE_COOKIE_SAMESITE=None
|
|
63
|
+
|
|
64
|
+
# OTel — span attribute redaction. Default ON (user input/output replaced
|
|
65
|
+
# with `<redacted bytes:N>` before export). Set to 0 to ship raw content
|
|
66
|
+
# (only for self-hosted OTel collectors with strict access control).
|
|
67
|
+
# LEAFMESH_OTEL_REDACT_PII=1
|
|
68
|
+
|
|
69
|
+
# OTel content redaction toggle for the prompt-builder guardrail. Off-switch
|
|
70
|
+
# for the BEGIN/END_USER_MESSAGE / BEGIN/END_TOOL_RESULT delimiters.
|
|
71
|
+
# LEAFMESH_DISABLE_PROMPT_GUARDRAIL=0
|
|
72
|
+
|
|
73
|
+
# Webhook hardening (HMAC + replay protection).
|
|
74
|
+
# Set to 1 to keep accepting body-only HMAC during a migration window.
|
|
75
|
+
# LEAFMESH_WEBHOOK_ALLOW_LEGACY_HMAC=0
|
|
76
|
+
# LEAFMESH_WEBHOOK_REPLAY_SKEW_S=300
|
|
77
|
+
# LEAFMESH_WEBHOOK_RATE_LIMIT_MAX=60
|
|
78
|
+
# LEAFMESH_WEBHOOK_RATE_LIMIT_WINDOW_S=60
|
|
79
|
+
# LEAFMESH_WEBHOOK_MAX_PAYLOAD_BYTES=1048576 # 1 MiB
|
|
80
|
+
|
|
81
|
+
# Knowledge ingest / query caps.
|
|
82
|
+
# LEAFMESH_KNOWLEDGE_MAX_DOCS_PER_INGEST=1000
|
|
83
|
+
# LEAFMESH_KNOWLEDGE_MAX_DOC_BYTES=2097152 # 2 MiB
|
|
84
|
+
# LEAFMESH_KNOWLEDGE_MAX_INGEST_TOTAL_BYTES=52428800 # 50 MiB
|
|
85
|
+
# LEAFMESH_KNOWLEDGE_QUERY_RATE_LIMIT_MAX=120
|
|
86
|
+
# LEAFMESH_KNOWLEDGE_QUERY_RATE_LIMIT_WINDOW_S=60
|
|
87
|
+
|
|
88
|
+
# LLM hard timeout — prevents a stuck provider from pinning an agent slot.
|
|
89
|
+
# LEAFMESH_LLM_HARD_TIMEOUT_S=300
|
|
90
|
+
|
|
91
|
+
# Cron min-interval (seconds). Default 60s — schedules below this are rejected.
|
|
92
|
+
# LEAFMESH_CRON_MIN_INTERVAL_SECONDS=60
|
|
93
|
+
|
|
94
|
+
# MCP subprocess allowlist (comma-separated absolute paths or basenames).
|
|
95
|
+
# Empty = warn-only at startup. Set to "*" for "any command" with explicit
|
|
96
|
+
# operator opt-in. Anything else acts as a strict allowlist.
|
|
97
|
+
# LEAFMESH_MCP_COMMAND_ALLOWLIST=
|
|
98
|
+
|
|
99
|
+
# Teams adapter — fail-closed by default. Set to 1 only for local development
|
|
100
|
+
# behind a tunnel (production must wire a Bot Framework JWT validator).
|
|
101
|
+
# LEAFMESH_TEAMS_ALLOW_UNVERIFIED=0
|
|
102
|
+
|
|
103
|
+
# Conversation history hard byte cap (per session).
|
|
104
|
+
# LEAFMESH_MAX_SESSION_HISTORY_BYTES=5242880 # 5 MiB
|
|
105
|
+
|
|
106
|
+
# Security headers.
|
|
107
|
+
# LEAFMESH_HSTS_ENABLED=1
|
|
108
|
+
# LEAFMESH_REFERRER_POLICY=no-referrer
|
|
109
|
+
# LEAFMESH_CSP="default-src 'none'; frame-ancestors 'none'; base-uri 'none'"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environment
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# Environment variables
|
|
16
|
+
.env
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# Logs
|
|
25
|
+
logs/
|
|
26
|
+
*.log
|
|
27
|
+
|
|
28
|
+
# Docker
|
|
29
|
+
docker-compose.override.yml
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HITL Stub Receiver — captures outbound webhook notifications from LeafMesh.
|
|
3
|
+
|
|
4
|
+
When the SDK's human agent (client) receives a task mid-flow, it sends an
|
|
5
|
+
outbound webhook to this stub. This simulates the external system that would
|
|
6
|
+
notify a real human (Slack, email, dashboard, etc).
|
|
7
|
+
|
|
8
|
+
The stub prints the webhook payload and a ready-to-use curl command so you
|
|
9
|
+
can respond as the human and continue the agent chain.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
# Terminal 1: start the stub receiver
|
|
13
|
+
python hitl_stub_receiver.py
|
|
14
|
+
|
|
15
|
+
# Terminal 2: start the mesh
|
|
16
|
+
python main.py
|
|
17
|
+
|
|
18
|
+
# Terminal 3: trigger the mesh (Scenario 1 — system-initiated)
|
|
19
|
+
curl -X POST http://127.0.0.1:18820/api/mesh/request \\
|
|
20
|
+
-H "Content-Type: application/json" \\
|
|
21
|
+
-d '{"entry_point": "greet_user", "data": {"message": "I need help with my order"}}'
|
|
22
|
+
|
|
23
|
+
# ... the stub will print the outbound webhook + curl command to respond
|
|
24
|
+
|
|
25
|
+
See README.md for full HITL walkthrough (Scenario 1 and Scenario 2).
|
|
26
|
+
"""
|
|
27
|
+
import json
|
|
28
|
+
import sys
|
|
29
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class StubHandler(BaseHTTPRequestHandler):
|
|
34
|
+
def do_POST(self):
|
|
35
|
+
length = int(self.headers.get("Content-Length", 0))
|
|
36
|
+
body = self.rfile.read(length)
|
|
37
|
+
|
|
38
|
+
print(f"\n{'='*60}")
|
|
39
|
+
print(f"[{datetime.now().strftime('%H:%M:%S')}] OUTBOUND WEBHOOK RECEIVED")
|
|
40
|
+
print(f"Path: {self.path}")
|
|
41
|
+
print(f"{'='*60}")
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
payload = json.loads(body)
|
|
45
|
+
print(json.dumps(payload, indent=2, default=str))
|
|
46
|
+
|
|
47
|
+
# Extract useful info for the human response
|
|
48
|
+
session_id = payload.get("session_id", "unknown")
|
|
49
|
+
agent = payload.get("agent_name", payload.get("from_agent", "unknown"))
|
|
50
|
+
print(f"\n>>> To respond as the human, POST to the LeafMesh server.")
|
|
51
|
+
print(f">>> The SDK binds HMAC to a fresh timestamp + nonce —")
|
|
52
|
+
print(f">>> see the helper at the bottom of this file for a working example.")
|
|
53
|
+
print(f"")
|
|
54
|
+
print(f" SECRET=$(curl -s http://127.0.0.1:18820/api/webhook/secret | jq -r .webhook_secret)")
|
|
55
|
+
print(f" TS=$(date +%s)")
|
|
56
|
+
print(f" NONCE=$(python3 -c 'import secrets; print(secrets.token_urlsafe(16))')")
|
|
57
|
+
print(f" BODY='{{\"session_id\": \"{session_id}\", \"decision\": \"approved\", \"message\": \"Looks good, proceed\"}}'")
|
|
58
|
+
print(f" SIG=$(printf '%s' \"$TS.$NONCE.$BODY\" | openssl dgst -sha256 -hmac \"$SECRET\" -hex | awk '{{print $NF}}')")
|
|
59
|
+
print(f" curl -X POST http://127.0.0.1:18820/webhook/greet_user \\\\")
|
|
60
|
+
print(f' -H "Content-Type: application/json" \\\\')
|
|
61
|
+
print(f' -H "X-LeafMesh-Signature: sha256=$SIG" \\\\')
|
|
62
|
+
print(f' -H "X-LeafMesh-Timestamp: $TS" \\\\')
|
|
63
|
+
print(f' -H "X-LeafMesh-Nonce: $NONCE" \\\\')
|
|
64
|
+
print(f" -d \"$BODY\"")
|
|
65
|
+
except Exception:
|
|
66
|
+
print(body.decode(errors="replace"))
|
|
67
|
+
|
|
68
|
+
print(f"{'='*60}\n")
|
|
69
|
+
sys.stdout.flush()
|
|
70
|
+
|
|
71
|
+
self.send_response(200)
|
|
72
|
+
self.send_header("Content-Type", "application/json")
|
|
73
|
+
self.end_headers()
|
|
74
|
+
self.wfile.write(json.dumps({"status": "received"}).encode())
|
|
75
|
+
|
|
76
|
+
def log_message(self, format, *args):
|
|
77
|
+
pass # Suppress default logging
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def sign_and_post_response(
|
|
81
|
+
session_id: str,
|
|
82
|
+
decision: str = "approved",
|
|
83
|
+
message: str = "Looks good, proceed",
|
|
84
|
+
leafmesh_url: str = "http://127.0.0.1:18820",
|
|
85
|
+
entry_point: str = "greet_user",
|
|
86
|
+
) -> int:
|
|
87
|
+
"""Reference implementation for signing a HITL response on the SDK's
|
|
88
|
+
HMAC-with-timestamp-and-nonce scheme.
|
|
89
|
+
|
|
90
|
+
Pulls the webhook secret from /api/webhook/secret, builds the canonical
|
|
91
|
+
signed material (`f"{ts}.{nonce}.".encode() + body`), and POSTs to
|
|
92
|
+
/webhook/{entry_point} with all three headers.
|
|
93
|
+
|
|
94
|
+
Returns the HTTP status code.
|
|
95
|
+
"""
|
|
96
|
+
import hmac as _hmac
|
|
97
|
+
import hashlib as _hashlib
|
|
98
|
+
import secrets as _secrets
|
|
99
|
+
import time as _time
|
|
100
|
+
import urllib.request as _ur
|
|
101
|
+
|
|
102
|
+
# Fetch the signing secret from the SDK.
|
|
103
|
+
try:
|
|
104
|
+
with _ur.urlopen(f"{leafmesh_url}/api/webhook/secret", timeout=5) as resp:
|
|
105
|
+
secret_data = json.loads(resp.read())
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"Could not fetch webhook secret from {leafmesh_url}: {e}")
|
|
108
|
+
return 0
|
|
109
|
+
secret = (secret_data or {}).get("webhook_secret")
|
|
110
|
+
if not secret:
|
|
111
|
+
print("Server is not configured for webhook auth — set LEAFMESH_LICENSE_KEY or LEAFMESH_WEBHOOK_SECRET.")
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
body = json.dumps(
|
|
115
|
+
{"session_id": session_id, "decision": decision, "message": message}
|
|
116
|
+
).encode("utf-8")
|
|
117
|
+
timestamp = str(int(_time.time()))
|
|
118
|
+
nonce = _secrets.token_urlsafe(16)
|
|
119
|
+
signed_material = f"{timestamp}.{nonce}.".encode("utf-8") + body
|
|
120
|
+
sig = _hmac.new(secret.encode("utf-8"), signed_material, _hashlib.sha256).hexdigest()
|
|
121
|
+
|
|
122
|
+
req = _ur.Request(
|
|
123
|
+
f"{leafmesh_url}/webhook/{entry_point}",
|
|
124
|
+
data=body,
|
|
125
|
+
method="POST",
|
|
126
|
+
headers={
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
"X-LeafMesh-Signature": f"sha256={sig}",
|
|
129
|
+
"X-LeafMesh-Timestamp": timestamp,
|
|
130
|
+
"X-LeafMesh-Nonce": nonce,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
try:
|
|
134
|
+
with _ur.urlopen(req, timeout=10) as resp:
|
|
135
|
+
return resp.status
|
|
136
|
+
except Exception as e:
|
|
137
|
+
print(f"POST failed: {e}")
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
server = HTTPServer(("127.0.0.1", 9999), StubHandler)
|
|
143
|
+
print(f"HITL Stub Receiver listening on http://127.0.0.1:9999")
|
|
144
|
+
print(f"Waiting for outbound webhook notifications from LeafMesh...\n")
|
|
145
|
+
sys.stdout.flush()
|
|
146
|
+
try:
|
|
147
|
+
server.serve_forever()
|
|
148
|
+
except KeyboardInterrupt:
|
|
149
|
+
print("\nStopped.")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""{{project_name}} — LeafMesh Multi-Agent Project
|
|
2
|
+
|
|
3
|
+
Agent registration:
|
|
4
|
+
- agency/*_agent.py → auto_discover matches function names to YAML agent names
|
|
5
|
+
- agency/tools.py → import here so @global_tool decorators register
|
|
6
|
+
|
|
7
|
+
Standalone decorators (no leafmesh instance needed):
|
|
8
|
+
from leafmesh import pre_compose, chain, compose, conditional_chain, chain_with_results
|
|
9
|
+
"""
|
|
10
|
+
import asyncio
|
|
11
|
+
import signal
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
from leafmesh import LeafMesh, LeafMeshLogger
|
|
14
|
+
|
|
15
|
+
# Import tools so @global_tool decorators register before leafmesh.start()
|
|
16
|
+
import agency.tools # noqa: F401
|
|
17
|
+
|
|
18
|
+
load_dotenv()
|
|
19
|
+
logger = LeafMeshLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# auto_discover in config.yaml wires agency/*_agent.py files automatically
|
|
22
|
+
leafmesh = LeafMesh.from_yaml("configs/config.yaml")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
await leafmesh.start()
|
|
27
|
+
|
|
28
|
+
# leafmesh.start() launches a built-in API server at http://127.0.0.1:18820
|
|
29
|
+
# See config.yaml for the full endpoint reference, or visit /docs for interactive API docs.
|
|
30
|
+
# Key endpoints:
|
|
31
|
+
# POST /api/mesh/request — trigger agent workflows via entry points
|
|
32
|
+
# POST /api/mesh/stream — SSE stream of LLM response
|
|
33
|
+
# POST /webhook/{entry_point} — unified webhook (new task or human response)
|
|
34
|
+
# GET /docs — interactive API docs (ReDoc)
|
|
35
|
+
|
|
36
|
+
# Log registered agents
|
|
37
|
+
agents = leafmesh.registry.list_agents() if hasattr(leafmesh, 'registry') and leafmesh.registry else []
|
|
38
|
+
if agents:
|
|
39
|
+
agent_summary = ", ".join(f"{a.name} ({a.agent_type})" for a in agents)
|
|
40
|
+
logger.info(f"Registered agents: {agent_summary}")
|
|
41
|
+
logger.info("{{project_name}} is running — press Ctrl+C to stop")
|
|
42
|
+
logger.info("API docs: http://127.0.0.1:18820/docs")
|
|
43
|
+
logger.info("Docker: docker compose up --build")
|
|
44
|
+
|
|
45
|
+
# ─── mesh_call: the external entry point into the agent mesh ───
|
|
46
|
+
# This is how you trigger agent workflows from code, APIs, or webhooks.
|
|
47
|
+
# The mesh automatically chains agents via can_call rules in config.yaml.
|
|
48
|
+
#
|
|
49
|
+
# result = await leafmesh.mesh_call(
|
|
50
|
+
# "greet_user",
|
|
51
|
+
# {"message": "Hello, I need help organizing my tasks"},
|
|
52
|
+
# session_id="optional-session-id"
|
|
53
|
+
# )
|
|
54
|
+
# logger.info(f"Result: {result}")
|
|
55
|
+
#
|
|
56
|
+
# # Direct research (bypasses greeting flow):
|
|
57
|
+
# result = await leafmesh.mesh_call("research", {"research_topic": "market trends"})
|
|
58
|
+
#
|
|
59
|
+
# # Direct advice:
|
|
60
|
+
# result = await leafmesh.mesh_call("advise", {"processed_data": {...}})
|
|
61
|
+
#
|
|
62
|
+
# # On-demand scheduled report (also runs daily at 9 AM UTC via cron):
|
|
63
|
+
# result = await leafmesh.mesh_call("scheduled_report", {"trigger": "manual"})
|
|
64
|
+
|
|
65
|
+
# ─── Usage analytics & LLM cache stats ───
|
|
66
|
+
# These are available after agents have run:
|
|
67
|
+
#
|
|
68
|
+
# analytics = await leafmesh.get_usage_analytics()
|
|
69
|
+
# logger.info(f"Usage: {analytics}")
|
|
70
|
+
# → Returns: agent call counts, error rates, avg latency per agent
|
|
71
|
+
#
|
|
72
|
+
# cache_stats = await leafmesh.get_llm_cache_stats()
|
|
73
|
+
# logger.info(f"LLM cache: {cache_stats}")
|
|
74
|
+
# → Returns: hit_count, miss_count, hit_rate, cached_keys
|
|
75
|
+
#
|
|
76
|
+
# ─── Upstream yields ───
|
|
77
|
+
# Agents define yields in YAML (typed key-value pairs).
|
|
78
|
+
# The SDK auto-stores them to Redis and auto-injects them into
|
|
79
|
+
# downstream agents as input_data["upstream_yields"][agent_name].
|
|
80
|
+
# Fan-in agents receive yields from ALL contributing upstream agents.
|
|
81
|
+
|
|
82
|
+
# Keep the process alive until interrupted
|
|
83
|
+
stop_event = asyncio.Event()
|
|
84
|
+
loop = asyncio.get_running_loop()
|
|
85
|
+
import sys
|
|
86
|
+
if sys.platform != "win32":
|
|
87
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
88
|
+
loop.add_signal_handler(sig, stop_event.set)
|
|
89
|
+
await stop_event.wait()
|
|
90
|
+
else:
|
|
91
|
+
# Windows: add_signal_handler not supported, use KeyboardInterrupt
|
|
92
|
+
try:
|
|
93
|
+
await stop_event.wait()
|
|
94
|
+
except KeyboardInterrupt:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
logger.info("Shutting down...")
|
|
98
|
+
await leafmesh.stop()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
try:
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
pass
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
create_leafmesh/__init__.py,sha256=m_g1ZFIPUBpVL_oB6LSkan5DfRl-y_1fP67ZdWrX1h0,88
|
|
2
|
+
create_leafmesh/cli.py,sha256=Y3aIIX3WUw0vTIEvz905CyDYFADrZVgPSNvumDAS0qE,8880
|
|
3
|
+
create_leafmesh/create.py,sha256=XCoArGNU0sXcGU2ScFG_bFceYGOEsDGXySIs8a8mk2c,3207
|
|
4
|
+
create_leafmesh/templates/Dockerfile,sha256=Dmog9_BfFEVKCG3rGWJ9j-CPld64KKi392e0-ETNUXs,455
|
|
5
|
+
create_leafmesh/templates/README.md,sha256=1jjcgnSboYNlSf2yYkyjSoIs-UMngI9cc-Tu6bdvLUs,10871
|
|
6
|
+
create_leafmesh/templates/docker-compose.yml,sha256=yjUeMBlC2AAgmkX51BXesW1I9bE650SpPs5ThGGrXKM,460
|
|
7
|
+
create_leafmesh/templates/dockerignore,sha256=7M9sHH1JmUPSlJ06zaC2D_GAYxkY9X95VjSzyh8DUXA,136
|
|
8
|
+
create_leafmesh/templates/env,sha256=63stn8M9cYhbClGbpfksEWVB2MqMXcgdZNhnwfiQyO0,4827
|
|
9
|
+
create_leafmesh/templates/gitignore,sha256=cPk8HY32uzIbuM2byii939_ElGwunDeGvAuuLj7QUdo,264
|
|
10
|
+
create_leafmesh/templates/hitl_stub_receiver.py,sha256=DAQVwtaIhgG22SrJF6Pf6dBvUOV-yJP1tQ45yd7IrQA,5796
|
|
11
|
+
create_leafmesh/templates/main.py,sha256=d0YFoCO03zzhg0j0e3Bk3NQIzVv4XKFWN9pAzMUnt4E,4145
|
|
12
|
+
create_leafmesh/templates/requirements.txt,sha256=2uXz3SRqbqAkCij9RstSEPSTMQVs6ZhvL6vbbHcnPxA,208
|
|
13
|
+
create_leafmesh/templates/agency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
create_leafmesh/templates/agency/advisor_agent.py,sha256=gfXLXe4_h9HCrj6FAuF0kGzegfwgzrqE2cSO5rwrH64,6467
|
|
15
|
+
create_leafmesh/templates/agency/external_agents.py,sha256=GF6DVuamZo1PboQnVB_r3GA5d6N39UGE7eZrQqufE9c,9827
|
|
16
|
+
create_leafmesh/templates/agency/fallback_researcher_agent.py,sha256=mqKfOKDycqPhkf8QFb5m7SiFF3iIlm1wSi1C5IgT65A,3050
|
|
17
|
+
create_leafmesh/templates/agency/greeter_agent.py,sha256=9iaiakf6doT-kg1CwPAeGJM8i-3gbDJ6o_tYg1dst5E,3039
|
|
18
|
+
create_leafmesh/templates/agency/processor_agent.py,sha256=j3bCesnUvZKIS7DPBJ_SeqiL4L7TQm5j7u9WZMddRtA,3618
|
|
19
|
+
create_leafmesh/templates/agency/researcher_agent.py,sha256=JJjCo4SEqcahiv2xsvOuMU71wiygV2eycPCszUlWwE0,4184
|
|
20
|
+
create_leafmesh/templates/agency/scheduler_agent.py,sha256=-ZEKwZ_ChaLn7IFvpWHmb2koWdIOOBG6l3FsC2nBwMU,2508
|
|
21
|
+
create_leafmesh/templates/agency/tools.py,sha256=M7kgHDZfd_S3AKW5FIOW0JDBUqRZnzVnk9bszvzHNSY,4548
|
|
22
|
+
create_leafmesh/templates/claude_skills/leafmesh/SKILL.md,sha256=Dn2KBsl4n9wX2JV_jm-o8LOGCsN0nzlg3-C6fvZOWO8,91196
|
|
23
|
+
create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md,sha256=CaRQepcfsemFUeW9diSOnRGEPYPDaoJlt7uv5-fGFU0,60479
|
|
24
|
+
create_leafmesh/templates/claude_skills/leafmesh/examples.md,sha256=oRviamSANLOZF7hdIz3UxFpbItCOK7Kjt-DzZK0Cl1E,14857
|
|
25
|
+
create_leafmesh/templates/claude_skills/leafmesh/reference.md,sha256=JY6vrB1ETPnhUApWc9qK1ZbjsHrYKVznRIetnjKameo,15940
|
|
26
|
+
create_leafmesh/templates/configs/config.yaml,sha256=KHFePtyQH02eFDIhUvrx5t4BgCc9GfqV-4PPXywU5LA,47232
|
|
27
|
+
create_leafmesh-2.1.0.dist-info/METADATA,sha256=IyuDHwZi6F7BtK05tIO3btqxHT5qJaPm6nt5bf2meWs,154
|
|
28
|
+
create_leafmesh-2.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
29
|
+
create_leafmesh-2.1.0.dist-info/entry_points.txt,sha256=HLh-Hxx7Nf-Wq6l6NaSJbaiowCHjzm6raIA2ZW0zGuc,61
|
|
30
|
+
create_leafmesh-2.1.0.dist-info/top_level.txt,sha256=WNrfYclQe8LCuUacHcXbauq6cYBlC1i5CIiyQHs1nnY,16
|
|
31
|
+
create_leafmesh-2.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
create_leafmesh
|