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,99 @@
1
+ """LLM Agent — Auto-discovered with @chain_with_results (accumulating pipeline).
2
+
3
+ auto_discover matches function name 'researcher_agent' to YAML agent config.
4
+
5
+ Features showcased:
6
+ - @chain_with_results: each step's return is accumulated into a list
7
+ - Smart memory (BRD-009): strategy: "hybrid" with cross-session persistence
8
+ context["memory_posts"] has relevance+recency weighted past interactions
9
+ - tool_categories: ["data"] — all tools in the "data" category are available
10
+ - tools: ["math_eval", "timestamp"] — specific tools also included
11
+ - reasoning: true — enables chain-of-thought for deeper analysis
12
+ - allow_parallel_tool_calls: true — LLM can invoke multiple tools at once
13
+
14
+ Execution flow:
15
+ 1. LLM generates response (with memory context + tools available)
16
+ 2. This function post-processes the LLM response
17
+ 3. @chain_with_results runs: analyze_data -> format_findings
18
+ Each step's result is accumulated in context["chain_results"]
19
+ """
20
+ from leafmesh import chain_with_results, LeafMeshLogger
21
+
22
+ logger = LeafMeshLogger(__name__)
23
+
24
+
25
+ def analyze_data(result, context):
26
+ """Chain step 1: Enrich the result with analysis and a confidence score.
27
+
28
+ With @chain_with_results, this return value is appended to
29
+ context["chain_results"] AND passed to the next step.
30
+ """
31
+ items = result.get("findings", [])
32
+ result["analysis"] = {
33
+ "total_findings": len(items),
34
+ "confidence_score": 0.85 if items else 0.1,
35
+ "data_quality": "high" if len(items) >= 3 else "limited",
36
+ }
37
+ return result
38
+
39
+
40
+ def format_findings(result, context):
41
+ """Chain step 2: Structure into a formal report format.
42
+
43
+ context["chain_results"] already contains the output from analyze_data.
44
+ """
45
+ analysis = result.get("analysis", {})
46
+ result["report"] = {
47
+ "title": "Research Findings",
48
+ "confidence": analysis.get("confidence_score", 0),
49
+ "quality": analysis.get("data_quality", "unknown"),
50
+ "finding_count": analysis.get("total_findings", 0),
51
+ "formatted": True,
52
+ }
53
+ return result
54
+
55
+
56
+ @chain_with_results(analyze_data, format_findings)
57
+ async def researcher_agent(llm_response, input_data, context):
58
+ """Post-process the LLM's research response.
59
+
60
+ At this point:
61
+ - LLM already generated research using the YAML prompt + tools
62
+ - context["memory_posts"] has past conversation history (memory: true)
63
+ - After this function, @chain_with_results runs analyze_data -> format_findings
64
+ - Each step result is accumulated in context["chain_results"]
65
+ """
66
+ # ─── Smart memory access (BRD-009: strategy "hybrid" in YAML) ───
67
+ # Past interactions are injected as context["memory_posts"], scored by
68
+ # relevance (0.6 weight) + recency (0.4 weight). Cross-session enabled
69
+ # means memory persists across sessions for returning users.
70
+ # Each post has "role" (user/assistant) and "content" (text).
71
+ memory_posts = context.get("memory_posts", [])
72
+ prior_findings_count = sum(1 for p in memory_posts if p.get("role") == "assistant")
73
+
74
+ # ─── Upstream yields access ───
75
+ # When processor_agent stores yields, the SDK injects them here.
76
+ upstream_yields = input_data.get("upstream_yields", {})
77
+ processor_yields = upstream_yields.get("processor_agent", {})
78
+ # processor_yields has: processed_items, item_count, status, alert
79
+
80
+ findings = []
81
+ if llm_response:
82
+ findings.append({"source": "llm", "content": llm_response})
83
+
84
+ calling_data = input_data.get("calling_agent_response", {})
85
+ if calling_data.get("processed_items"):
86
+ for item in calling_data["processed_items"]:
87
+ findings.append({"source": "upstream", "content": item.get("item", "")})
88
+
89
+ query = input_data.get("message", input_data.get("research_topic", ""))
90
+ logger.info(f"Research complete — {len(findings)} findings for: {query[:60]}")
91
+
92
+ return {
93
+ "findings": findings,
94
+ "query": query,
95
+ "prior_research_sessions": prior_findings_count,
96
+ "upstream_item_count": processor_yields.get("item_count", 0),
97
+ "source_agent": "researcher_agent",
98
+ "status": "researched",
99
+ }
@@ -0,0 +1,67 @@
1
+ """Programmatic Agent — Cron-scheduled, chains to advisor for analysis.
2
+
3
+ auto_discover matches function name 'scheduler_agent' to YAML agent config.
4
+
5
+ Features showcased:
6
+ - wake_up: "0 9 * * *" — cron expression triggers this agent automatically
7
+ - communication_type: "chain" — output chains to advisor_agent via can_call
8
+ - Entry point: mesh_call("scheduled_report", ...) for on-demand reports
9
+ - No LLM, no external deps — pure Python on a schedule
10
+
11
+ Use cases for scheduled agents:
12
+ - Daily/weekly report generation → chain to LLM agent for analysis
13
+ - Health checks and alerting → chain to human agent for review
14
+ - Periodic data aggregation → chain to downstream processors
15
+
16
+ All agent functions use the 3-param signature: (llm_response, input_data, context)
17
+ For programmatic agents, llm_response is always None.
18
+ input_data will be empty on scheduled runs (no upstream caller).
19
+ On-demand runs via mesh_call("scheduled_report", data) will have input_data.
20
+ """
21
+ from datetime import datetime, timezone
22
+ from leafmesh import LeafMeshLogger
23
+
24
+ logger = LeafMeshLogger(__name__)
25
+
26
+
27
+ async def scheduler_agent(llm_response, input_data, context):
28
+ """Generate a status report with mesh metrics.
29
+
30
+ This runs on the cron schedule defined in config.yaml AND can be
31
+ triggered on-demand via mesh_call("scheduled_report", data).
32
+
33
+ communication_type: "chain" means the result is forwarded to
34
+ advisor_agent via can_call for LLM-powered analysis.
35
+ """
36
+ now = datetime.now(timezone.utc)
37
+ trigger = input_data.get("trigger", "scheduled")
38
+
39
+ # ─── Gather mesh metrics ───
40
+ # In production, you'd pull real metrics from your data sources.
41
+ # The advisor_agent receives this as input_data and analyzes it.
42
+ checks = {
43
+ "system_status": "healthy",
44
+ "scheduled_run": trigger == "scheduled",
45
+ "on_demand": trigger != "scheduled",
46
+ }
47
+
48
+ metrics = {
49
+ "report_time": now.isoformat(),
50
+ "trigger_type": trigger,
51
+ "uptime_hours": 24,
52
+ "total_requests_today": 0,
53
+ "error_rate": 0.0,
54
+ }
55
+
56
+ logger.info(f"Report generated ({trigger}) — status={checks['system_status']}")
57
+
58
+ return {
59
+ "report": f"Status report ({trigger}) at {now.isoformat()}",
60
+ "generated_at": now.isoformat(),
61
+ "checks": checks,
62
+ "metrics": metrics,
63
+ "processed_items": [
64
+ {"item": f"Daily {trigger} report", "category": "normal"},
65
+ ],
66
+ "status": "report_generated",
67
+ }
@@ -0,0 +1,123 @@
1
+ """Custom tools that LLM agents can call during generation.
2
+
3
+ Two decorator types:
4
+ @global_tool — Auto-registers in GlobalToolRegistry on import
5
+ @tool — Creates a local FunctionTool (register manually)
6
+
7
+ Tools are referenced in YAML agent config:
8
+ tools: ["word_count", "timestamp"] # specific tools by name
9
+ tool_categories: ["text", "utility"] # all tools in a category
10
+ tool_choice: "auto"
11
+
12
+ Import these in main.py so @global_tool registration runs:
13
+ from agency.tools import word_count, timestamp, format_as_markdown
14
+ """
15
+ from leafmesh import global_tool, tool
16
+
17
+
18
+ @global_tool(
19
+ name="word_count",
20
+ description="Count the number of words in a text string",
21
+ category="text",
22
+ )
23
+ def word_count(text: str) -> dict:
24
+ """Count words in the given text."""
25
+ words = text.split()
26
+ return {"word_count": len(words), "character_count": len(text)}
27
+
28
+
29
+ @global_tool(
30
+ name="timestamp",
31
+ description="Get the current UTC timestamp",
32
+ category="utility",
33
+ )
34
+ def timestamp() -> dict:
35
+ """Return the current UTC time."""
36
+ from datetime import datetime, timezone
37
+ now = datetime.now(timezone.utc)
38
+ return {"utc": now.isoformat(), "unix": int(now.timestamp())}
39
+
40
+
41
+ @global_tool(
42
+ name="math_eval",
43
+ description="Evaluate a simple math expression (addition, subtraction, multiplication, division)",
44
+ category="data",
45
+ )
46
+ def math_eval(expression: str) -> dict:
47
+ """Safely evaluate a math expression."""
48
+ allowed = set("0123456789+-*/.(). ")
49
+ if not all(c in allowed for c in expression):
50
+ return {"error": "Invalid characters in expression"}
51
+ try:
52
+ result = eval(expression, {"__builtins__": {}}) # noqa: S307
53
+ return {"expression": expression, "result": result}
54
+ except Exception as e:
55
+ return {"error": str(e)}
56
+
57
+
58
+ @global_tool(
59
+ name="sensitive_data_lookup",
60
+ description="Look up sensitive data records (restricted access)",
61
+ category="data",
62
+ allowed_agents=["researcher_agent", "advisor_agent"],
63
+ requires_confirmation=True,
64
+ timeout_seconds=15,
65
+ )
66
+ def sensitive_data_lookup(record_id: str) -> dict:
67
+ """Look up a sensitive data record by ID.
68
+
69
+ - allowed_agents: only researcher_agent and advisor_agent can call this
70
+ - requires_confirmation: manager must approve before execution
71
+ - timeout_seconds: auto-cancel if it takes longer than 15s
72
+ """
73
+ return {
74
+ "record_id": record_id,
75
+ "data": f"Record {record_id} contents",
76
+ "classification": "internal",
77
+ }
78
+
79
+
80
+ # ─── @tool example (local, not auto-registered) ──────────────
81
+ # Use @tool for agent-specific tools that don't need global access.
82
+
83
+ @tool(
84
+ name="format_as_markdown",
85
+ description="Format a list of items as a markdown checklist",
86
+ timeout_seconds=10,
87
+ requires_confirmation=False,
88
+ )
89
+ def format_as_markdown(items: list) -> str:
90
+ """Convert a list to markdown checklist."""
91
+ return "\n".join(f"- [ ] {item}" for item in items)
92
+
93
+
94
+ # ═══════════════════════════════════════════════════════════════
95
+ # BUILT-IN TOOLS REFERENCE
96
+ # These are provided by LeafMesh — no code needed, just reference
97
+ # them by name in your YAML config under `tools:` or by category
98
+ # under `tool_categories:`.
99
+ #
100
+ # Category: "web"
101
+ # - web_search — Search the web (requires API key config)
102
+ # - web_scrape — Scrape a URL and return content
103
+ #
104
+ # Category: "data"
105
+ # - json_parse — Parse and validate JSON strings
106
+ # - csv_parse — Parse CSV data into structured format
107
+ #
108
+ # Category: "text"
109
+ # - text_summarize — Summarize long text passages
110
+ # - text_translate — Translate text between languages
111
+ #
112
+ # Category: "utility"
113
+ # - file_read — Read file contents
114
+ # - file_write — Write content to a file
115
+ # - http_request — Make HTTP requests
116
+ #
117
+ # YAML usage:
118
+ # tools: ["calculator", "web_search"] # pick specific tools
119
+ # tool_categories: ["data", "utility"] # pick entire categories
120
+ # tool_choice: "auto" # auto | none | required
121
+ # allow_parallel_tool_calls: true # LLM can call multiple tools at once
122
+ # max_tool_calls_per_message: 10 # safety limit per LLM turn
123
+ # ═══════════════════════════════════════════════════════════════