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.
Files changed (31) hide show
  1. create_leafmesh/__init__.py +3 -0
  2. create_leafmesh/cli.py +252 -0
  3. create_leafmesh/create.py +106 -0
  4. create_leafmesh/templates/Dockerfile +21 -0
  5. create_leafmesh/templates/README.md +309 -0
  6. create_leafmesh/templates/agency/__init__.py +0 -0
  7. create_leafmesh/templates/agency/advisor_agent.py +151 -0
  8. create_leafmesh/templates/agency/external_agents.py +278 -0
  9. create_leafmesh/templates/agency/fallback_researcher_agent.py +80 -0
  10. create_leafmesh/templates/agency/greeter_agent.py +79 -0
  11. create_leafmesh/templates/agency/processor_agent.py +90 -0
  12. create_leafmesh/templates/agency/researcher_agent.py +99 -0
  13. create_leafmesh/templates/agency/scheduler_agent.py +67 -0
  14. create_leafmesh/templates/agency/tools.py +123 -0
  15. create_leafmesh/templates/claude_skills/leafmesh/SKILL.md +2049 -0
  16. create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md +1309 -0
  17. create_leafmesh/templates/claude_skills/leafmesh/examples.md +537 -0
  18. create_leafmesh/templates/claude_skills/leafmesh/reference.md +492 -0
  19. create_leafmesh/templates/configs/config.yaml +1028 -0
  20. create_leafmesh/templates/docker-compose.yml +28 -0
  21. create_leafmesh/templates/dockerignore +17 -0
  22. create_leafmesh/templates/env +109 -0
  23. create_leafmesh/templates/gitignore +33 -0
  24. create_leafmesh/templates/hitl_stub_receiver.py +149 -0
  25. create_leafmesh/templates/main.py +105 -0
  26. create_leafmesh/templates/requirements.txt +10 -0
  27. create_leafmesh-2.1.0.dist-info/METADATA +6 -0
  28. create_leafmesh-2.1.0.dist-info/RECORD +31 -0
  29. create_leafmesh-2.1.0.dist-info/WHEEL +5 -0
  30. create_leafmesh-2.1.0.dist-info/entry_points.txt +2 -0
  31. create_leafmesh-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,537 @@
1
+ # LeafMesh Agent Patterns — Copy-Paste Templates
2
+
3
+ ## Pattern 1: Customer Support Hub (Router + Specialists)
4
+
5
+ ### config.yaml
6
+ ```yaml
7
+ entry_points:
8
+ - name: "support"
9
+ target: "router_agent"
10
+ condition: "always"
11
+
12
+ agents:
13
+ router_agent:
14
+ agent_type: "llm"
15
+ model: "gpt-4o-mini"
16
+ prompt: |
17
+ You are a customer support router. Classify the customer's intent
18
+ and respond with: intent (billing/technical/general), urgency (low/medium/high),
19
+ and a brief summary.
20
+ yields: {intent: string, urgency: string, summary: string}
21
+ can_call:
22
+ - agent: "billing_agent"
23
+ condition: "intent == 'billing'"
24
+ - agent: "technical_agent"
25
+ condition: "intent == 'technical'"
26
+ - agent: "general_agent"
27
+ condition: "intent == 'general'"
28
+
29
+ billing_agent:
30
+ agent_type: "llm"
31
+ model: "gpt-4o"
32
+ prompt: "You are a billing specialist. Help resolve billing inquiries."
33
+ tools: ["lookup_account", "check_invoice"]
34
+ tool_categories: ["data"]
35
+
36
+ technical_agent:
37
+ agent_type: "llm"
38
+ model: "gpt-4o"
39
+ prompt: "You are a technical support specialist."
40
+ tools: ["check_status", "search_kb"]
41
+ memory: true
42
+
43
+ general_agent:
44
+ agent_type: "llm"
45
+ model: "gpt-4o-mini"
46
+ prompt: "You are a friendly general support agent."
47
+ ```
48
+
49
+ ### agency/router_agent.py
50
+ ```python
51
+ from leafmesh import pre_compose
52
+
53
+ def detect_language(input_data, context):
54
+ msg = input_data.get("message", "")
55
+ return {"language": "en", "char_count": len(msg)}
56
+
57
+ @pre_compose(context_processor=detect_language)
58
+ async def router_agent(llm_response, input_data, context):
59
+ return {
60
+ "intent": llm_response.get("intent", "general"),
61
+ "urgency": llm_response.get("urgency", "low"),
62
+ "summary": llm_response.get("summary", input_data.get("message", "")),
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Pattern 2: Research Pipeline (Fan-Out + Fan-In)
69
+
70
+ ### config.yaml
71
+ ```yaml
72
+ entry_points:
73
+ - name: "research"
74
+ target: "coordinator_agent"
75
+ condition: "always"
76
+
77
+ agents:
78
+ coordinator_agent:
79
+ agent_type: "llm"
80
+ model: "gpt-4o-mini"
81
+ prompt: "Break down the research query into sub-questions."
82
+ can_call:
83
+ - {agent: "web_researcher"}
84
+ - {agent: "data_analyst"}
85
+ - {agent: "domain_expert"}
86
+
87
+ web_researcher:
88
+ agent_type: "llm"
89
+ model: "gpt-4o"
90
+ prompt: "Research the web for relevant information on the topic."
91
+ tools: ["web_search"]
92
+ parallel: true
93
+
94
+ data_analyst:
95
+ agent_type: "programmatic"
96
+ parallel: true
97
+
98
+ domain_expert:
99
+ agent_type: "llm"
100
+ model: "claude-sonnet-4-5-20250929"
101
+ prompt: "Provide domain expertise and analysis."
102
+
103
+ synthesizer_agent:
104
+ agent_type: "llm"
105
+ model: "gpt-4o"
106
+ prompt: "Synthesize all research findings into a coherent report."
107
+ wait_for: "web_researcher AND data_analyst AND domain_expert?"
108
+ wait_for_timeout: 120
109
+ ```
110
+
111
+ ### agency/synthesizer_agent.py
112
+ ```python
113
+ from leafmesh import chain
114
+
115
+ def add_citations(result, context):
116
+ sources = result.get("sources", [])
117
+ result["citation_count"] = len(sources)
118
+ return result
119
+
120
+ def format_report(result, context):
121
+ result["format"] = "markdown"
122
+ result["report"] = f"# Research Report\n\n{result.get('synthesis', '')}"
123
+ return result
124
+
125
+ @chain(add_citations, format_report)
126
+ async def synthesizer_agent(llm_response, input_data, context):
127
+ upstream = input_data.get("upstream_yields", {})
128
+ web = upstream.get("web_researcher", {})
129
+ data = upstream.get("data_analyst", {})
130
+ domain = upstream.get("domain_expert", {}) # May be empty (optional)
131
+
132
+ return {
133
+ "synthesis": llm_response,
134
+ "sources": web.get("sources", []) + data.get("sources", []),
135
+ "confidence": 0.9 if domain else 0.7,
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Pattern 3: HITL Dual-Mode (Human Reviews Agent Output)
142
+
143
+ System triggers a workflow, agent processes, human reviews before continuing.
144
+
145
+ ### config.yaml
146
+ ```yaml
147
+ entry_points:
148
+ - name: "greet_user"
149
+ target: "greeter_agent"
150
+ - name: "human_contact"
151
+ target: "client"
152
+
153
+ agents:
154
+ client:
155
+ name: "client"
156
+ agent_type: "human"
157
+ human_interface: "webhook" # required to actually use webhook_config below
158
+ communication_type: "dual"
159
+ human_timeout_seconds: 300
160
+ webhook_config:
161
+ outbound_url: "http://127.0.0.1:9999/human-notify"
162
+ outbound_headers:
163
+ Content-Type: "application/json"
164
+ outbound_timeout: 30
165
+ # inbound_endpoint is auto-derived from entry_points — no need to set it
166
+ max_retries: 1
167
+ retry_delay: 2
168
+ can_call:
169
+ - agent: "greeter_agent"
170
+ condition: "not calling_agent_response.from_agent"
171
+ - agent: "processor_agent"
172
+ condition: "calling_agent_response.from_agent == 'greeter_agent'"
173
+
174
+ greeter_agent:
175
+ agent_type: "llm"
176
+ model: "gpt-4o-mini"
177
+ communication_type: "dual"
178
+ can_call:
179
+ - agent: "client" # Routes to human for review
180
+
181
+ processor_agent:
182
+ agent_type: "programmatic"
183
+ can_call:
184
+ - agent: "researcher_agent"
185
+ condition: "calling_agent_response.item_count > 0"
186
+ ```
187
+
188
+ ### How it works
189
+ ```
190
+ Scenario 1 (system-initiated):
191
+ POST /api/mesh/request {"entry_point": "greet_user", ...}
192
+ -> greeter -> client (HITL, webhook sent)
193
+ -> human responds via POST /webhook/greet_user
194
+ -> from_agent=="greeter_agent" -> processor -> ...
195
+
196
+ Scenario 2 (human-initiated):
197
+ POST /webhook/human_contact {"message": "I need help"}
198
+ -> client (no from_agent -> greeter)
199
+ -> greeter (dual callback -> client HITL, webhook sent)
200
+ -> human responds -> from_agent=="greeter_agent" -> processor -> ...
201
+
202
+ Scenario 3 (same session, new message):
203
+ POST /webhook/human_contact {"session_id": "existing", "message": "Now check refund"}
204
+ -> session not paused -> new request, conversation history preserved
205
+ ```
206
+
207
+ ### Testing HITL locally
208
+ ```bash
209
+ # Terminal 1: stub receiver (captures outbound webhooks)
210
+ python hitl_stub_receiver.py
211
+
212
+ # Terminal 2: mesh server
213
+ python main.py
214
+
215
+ # Terminal 3: trigger + respond
216
+ SECRET=$(curl -s http://127.0.0.1:18820/api/webhook/secret | jq -r .secret)
217
+ curl -X POST http://127.0.0.1:18820/api/mesh/request \
218
+ -H "Content-Type: application/json" \
219
+ -d '{"entry_point": "greet_user", "data": {"message": "Help me"}}'
220
+
221
+ # ... stub prints session_id, then respond:
222
+ BODY='{"session_id": "SESSION_ID", "decision": "approved", "message": "Proceed"}'
223
+ SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
224
+ curl -X POST http://127.0.0.1:18820/webhook/greet_user \
225
+ -H "Content-Type: application/json" \
226
+ -H "X-LeafMesh-Signature: sha256=$SIG" \
227
+ -d "$BODY"
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Pattern 4: Scheduled Background Jobs
233
+
234
+ ### config.yaml
235
+ ```yaml
236
+ agents:
237
+ daily_report_agent:
238
+ agent_type: "programmatic"
239
+ wake_up: "0 9 * * *" # Every day at 9 AM
240
+ communication_type: "execute" # Fire-and-forget
241
+
242
+ hourly_monitor_agent:
243
+ agent_type: "llm"
244
+ model: "gpt-4o-mini"
245
+ prompt: "Analyze system metrics and flag anomalies."
246
+ wake_up: "0 * * * *" # Every hour
247
+ tools: ["check_metrics", "send_alert"]
248
+ ```
249
+
250
+ ### agency/daily_report_agent.py
251
+ ```python
252
+ async def daily_report_agent(llm_response, input_data, context):
253
+ from datetime import datetime, timezone
254
+ return {
255
+ "report": "Daily system health: all green",
256
+ "generated_at": datetime.now(timezone.utc).isoformat(),
257
+ "checks": ["redis_ok", "agents_healthy", "sessions_active"],
258
+ "status": "completed",
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Pattern 5: Multi-Model Strategy (Cost Optimization)
265
+
266
+ ### config.yaml
267
+ ```yaml
268
+ agents:
269
+ triage_agent:
270
+ agent_type: "llm"
271
+ model: "gpt-4o-mini" # Cheap, fast triage
272
+ optimization_strategy: "speed"
273
+ prompt: "Classify query complexity: simple, moderate, or complex."
274
+ can_call:
275
+ - agent: "simple_handler"
276
+ condition: "complexity == 'simple'"
277
+ - agent: "complex_handler"
278
+ condition: "complexity == 'complex'"
279
+ - agent: "moderate_handler"
280
+ condition: "complexity == 'moderate'"
281
+
282
+ simple_handler:
283
+ agent_type: "llm"
284
+ model: "gpt-4o-mini" # Cheap for simple queries
285
+ optimization_strategy: "cost"
286
+
287
+ moderate_handler:
288
+ agent_type: "llm"
289
+ model: "gpt-4o" # Balanced
290
+ optimization_strategy: "cost"
291
+
292
+ complex_handler:
293
+ agent_type: "llm"
294
+ model: "claude-sonnet-4-5-20250929" # Best quality for hard tasks
295
+ optimization_strategy: "performance"
296
+ reasoning: true # Enable chain-of-thought
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Pattern 6: External Framework Integration
302
+
303
+ ### CrewAI (connector-only, no Python needed)
304
+ ```yaml
305
+ agents:
306
+ crewai_research:
307
+ agent_type: "external"
308
+ framework: "crewai"
309
+ connector_config:
310
+ endpoint: "http://localhost:9000"
311
+ api_key: "${CREWAI_API_KEY}" # Bearer Token
312
+ # user_api_key: "${CREWAI_USER_API_KEY}" # User Bearer Token (preferred over api_key)
313
+ poll_interval: 2.0
314
+ max_poll_seconds: 300
315
+ can_call:
316
+ - agent: "internal_processor"
317
+ yields: {result: object}
318
+ inputs: {task: string}
319
+ ```
320
+
321
+ ### Programmatic + Connector (connector-only, no Python needed)
322
+ ```yaml
323
+ agents:
324
+ zapier_sheets:
325
+ agent_type: "programmatic"
326
+ integration: "zapier"
327
+ connector_config:
328
+ connection: "google_sheets"
329
+ action: "create_spreadsheet_row"
330
+ api_key: "${ZAPIER_API_KEY}"
331
+ yields: {status: string}
332
+ inputs: {row_data: object}
333
+
334
+ n8n_workflow:
335
+ agent_type: "programmatic"
336
+ integration: "n8n"
337
+ connector_config:
338
+ webhook_url: "http://localhost:5678/webhook/my-workflow"
339
+ mode: "callback"
340
+ callback_timeout: 120
341
+ yields: {result: object}
342
+ inputs: {data: object}
343
+ ```
344
+
345
+ The connector response is returned as-is. To post-process, add `@sdk.intelligence()`:
346
+
347
+ ### Programmatic + Connector + Python (post-process connector result)
348
+ ```python
349
+ async def zapier_sheets(connector_response, input_data, context):
350
+ # connector_response = Zapier's raw response
351
+ return {
352
+ "status": "logged" if connector_response.get("success") else "failed",
353
+ "row_id": connector_response.get("content", {}).get("id"),
354
+ }
355
+ ```
356
+
357
+ ### Using Zapier as @pre_compose helper (enrichment before LLM)
358
+ ```python
359
+ from leafmesh import pre_compose, zapier
360
+
361
+ @pre_compose(
362
+ context_processor=zapier(
363
+ action="slack_send_message",
364
+ api_key="${ZAPIER_NLA_API_KEY}",
365
+ )
366
+ )
367
+ async def notification_agent(llm_response, input_data, context):
368
+ slack_result = context["prepared_data"]["business_context"]
369
+ return {"notified": True, "channel": slack_result.get("channel")}
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Pattern 7: Memory-Aware Agent (Learning from History)
375
+
376
+ ### config.yaml
377
+ ```yaml
378
+ agents:
379
+ advisor_agent:
380
+ agent_type: "llm"
381
+ model: "gpt-4o"
382
+ prompt: |
383
+ You are a financial advisor. Use the memory of past interactions
384
+ to provide personalized, context-aware advice. Reference previous
385
+ conversations when relevant.
386
+ memory: true
387
+ memory_limit: 20 # Load last 20 feed posts
388
+ tools: ["market_data", "portfolio_lookup"]
389
+ ```
390
+
391
+ ### agency/advisor_agent.py
392
+ ```python
393
+ from leafmesh import chain_with_results
394
+
395
+ def check_portfolio(result, context):
396
+ memory = context.get("memory_posts", [])
397
+ prior_topics = [p.get("content", "") for p in memory[-5:]]
398
+ result["prior_context"] = prior_topics
399
+ return result
400
+
401
+ def personalize(result, context):
402
+ if result.get("prior_context"):
403
+ result["personalized"] = True
404
+ result["greeting"] = "Welcome back! Continuing from where we left off..."
405
+ return result
406
+
407
+ @chain_with_results(check_portfolio, personalize)
408
+ async def advisor_agent(llm_response, input_data, context):
409
+ memory_posts = context.get("memory_posts", [])
410
+ return {
411
+ "advice": llm_response,
412
+ "session_count": len(memory_posts),
413
+ "confidence": 0.85,
414
+ }
415
+ ```
416
+
417
+ ---
418
+
419
+ ## Pattern 8: Conditional Processing with @conditional_chain
420
+
421
+ ```python
422
+ from leafmesh import conditional_chain
423
+
424
+ def needs_translation(result, context):
425
+ return result.get("language") != "en"
426
+
427
+ def translate_to_english(result, context):
428
+ result["original_language"] = result["language"]
429
+ result["translated"] = True
430
+ # In real code, call translation API here
431
+ return result
432
+
433
+ def add_disclaimer(result, context):
434
+ result["disclaimer"] = "This response was auto-translated"
435
+ return result
436
+
437
+ @conditional_chain(needs_translation, translate_to_english, add_disclaimer)
438
+ async def intake_agent(llm_response, input_data, context):
439
+ return {
440
+ "response": llm_response,
441
+ "language": input_data.get("language", "en"),
442
+ }
443
+ ```
444
+
445
+ ---
446
+
447
+ ## Pattern 9: Custom Global Tools
448
+
449
+ ```python
450
+ from leafmesh import global_tool
451
+ import httpx
452
+
453
+ @global_tool(
454
+ name="fetch_weather",
455
+ description="Get current weather for a city",
456
+ category="web",
457
+ timeout_seconds=10,
458
+ )
459
+ async def fetch_weather(city: str) -> dict:
460
+ async with httpx.AsyncClient() as client:
461
+ resp = await client.get(f"https://wttr.in/{city}?format=j1")
462
+ data = resp.json()
463
+ current = data.get("current_condition", [{}])[0]
464
+ return {
465
+ "city": city,
466
+ "temp_c": current.get("temp_C"),
467
+ "description": current.get("weatherDesc", [{}])[0].get("value"),
468
+ }
469
+
470
+ @global_tool(
471
+ name="db_query",
472
+ description="Run a read-only database query",
473
+ category="data",
474
+ allowed_agents=["analyst_agent", "report_agent"],
475
+ requires_confirmation=True,
476
+ timeout_seconds=30,
477
+ )
478
+ async def db_query(sql: str) -> dict:
479
+ # Validate read-only
480
+ if any(kw in sql.upper() for kw in ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"]):
481
+ return {"error": "Only SELECT queries allowed"}
482
+ # Execute query...
483
+ return {"rows": [], "count": 0}
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Quick Reference: Decorator Stacking
489
+
490
+ ```python
491
+ # All decorators can be combined. Execution order (bottom to top):
492
+
493
+ @chain(step1, step2) # 4. Post-process pipeline
494
+ @compose( # 3. Shape per-target payloads
495
+ agent_a=lambda r, c: {"key": r["x"]},
496
+ agent_b=lambda r, c: {"key": r["y"]},
497
+ )
498
+ @pre_compose( # 1. Prepare inputs (before LLM)
499
+ context_processor=enrich,
500
+ input_processor=clean,
501
+ )
502
+ async def my_agent(llm_response, input_data, context):
503
+ return {"x": 1, "y": 2} # 2. Agent logic + LLM
504
+ ```
505
+
506
+ ## Docker Deployment
507
+
508
+ ```yaml
509
+ # docker-compose.yml
510
+ services:
511
+ redis:
512
+ image: redis:7-alpine
513
+ ports: ["6379:6379"]
514
+ healthcheck:
515
+ test: ["CMD", "redis-cli", "ping"]
516
+
517
+ app:
518
+ build: .
519
+ ports: ["18820:18820"]
520
+ env_file: .env
521
+ depends_on:
522
+ redis:
523
+ condition: service_healthy
524
+ ```
525
+
526
+ ```dockerfile
527
+ # Dockerfile
528
+ FROM python:3.13-slim
529
+ WORKDIR /app
530
+ COPY requirements.txt .
531
+ RUN pip install --no-cache-dir -r requirements.txt
532
+ COPY configs/ configs/
533
+ COPY agency/ agency/
534
+ COPY main.py .
535
+ EXPOSE 18820
536
+ CMD ["python", "main.py"]
537
+ ```