agentx-kit 0.5.1__tar.gz → 0.7.0__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 (105) hide show
  1. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/PKG-INFO +2 -2
  2. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/pyproject.toml +2 -2
  3. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/__init__.py +1 -1
  4. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/cache.py +6 -3
  5. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/cli.py +3 -1
  6. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/build.py +5 -4
  7. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/generator.py +38 -5
  8. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/spec.py +12 -0
  9. agentx_kit-0.7.0/src/agentx/scaffold/templates/README.md.j2 +69 -0
  10. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/ci.yml.j2 +9 -3
  11. agentx_kit-0.7.0/src/agentx/scaffold/templates/env.example.j2 +25 -0
  12. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/evals/run_evals.py.j2 +3 -9
  13. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/mcp_servers.json.j2 +1 -1
  14. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/agents.py.j2 +21 -0
  15. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/crew.py.j2 +23 -0
  16. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/graph.py.j2 +172 -0
  17. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/main.py.j2 +45 -0
  18. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/memory.py.j2 +25 -0
  19. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/nodes.py.j2 +219 -0
  20. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/rag.py.j2 +15 -4
  21. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/server.py.j2 +101 -0
  22. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/state.py.j2 +17 -0
  23. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/tasks.py.j2 +18 -0
  24. agentx_kit-0.7.0/src/agentx/scaffold/templates/pkg/tools.py.j2 +26 -0
  25. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/wizard.py +26 -0
  26. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_enterprise.py +2 -1
  27. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_scaffold.py +15 -5
  28. agentx_kit-0.5.1/src/agentx/scaffold/templates/README.md.j2 +0 -46
  29. agentx_kit-0.5.1/src/agentx/scaffold/templates/env.example.j2 +0 -12
  30. agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/agents.py.j2 +0 -77
  31. agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/main.py.j2 +0 -87
  32. agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/memory.py.j2 +0 -17
  33. agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/server.py.j2 +0 -85
  34. agentx_kit-0.5.1/src/agentx/scaffold/templates/pkg/tools.py.j2 +0 -16
  35. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.agentx/llm_cache.sqlite +0 -0
  36. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.claude-plugin/marketplace.json +0 -0
  37. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.github/workflows/publish.yml +0 -0
  38. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/.gitignore +0 -0
  39. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/DESIGN.md +0 -0
  40. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/LICENSE +0 -0
  41. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/README.md +0 -0
  42. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/RESEARCH.md +0 -0
  43. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/examples/README.md +0 -0
  44. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/examples/demo_local.sh +0 -0
  45. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/examples/demo_mcp.py +0 -0
  46. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/.claude-plugin/plugin.json +0 -0
  47. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/.mcp.json +0 -0
  48. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/README.md +0 -0
  49. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/claude-plugin/commands/new-agent.md +0 -0
  50. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/.vscodeignore +0 -0
  51. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/README.md +0 -0
  52. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/extension.js +0 -0
  53. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/integrations/vscode/package.json +0 -0
  54. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/config.py +0 -0
  55. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/__init__.py +0 -0
  56. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/recommend.py +0 -0
  57. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/connector/server.py +0 -0
  58. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/dashboard/__init__.py +0 -0
  59. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/dashboard/app.py +0 -0
  60. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/frameworks/__init__.py +0 -0
  61. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/frameworks/crewai_agent.py +0 -0
  62. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/frameworks/langchain_agent.py +0 -0
  63. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/guardrails.py +0 -0
  64. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/__init__.py +0 -0
  65. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/analyze.py +0 -0
  66. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/log.py +0 -0
  67. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/optimize.py +0 -0
  68. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/insights/tokens.py +0 -0
  69. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/memory/__init__.py +0 -0
  70. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/memory/store.py +0 -0
  71. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/observability.py +0 -0
  72. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/prompts/__init__.py +0 -0
  73. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/prompts/templates.py +0 -0
  74. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/__init__.py +0 -0
  75. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/base.py +0 -0
  76. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/factory.py +0 -0
  77. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/providers/registry.py +0 -0
  78. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/rag/__init__.py +0 -0
  79. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/rag/pipeline.py +0 -0
  80. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/reliability.py +0 -0
  81. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/__init__.py +0 -0
  82. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/prompts_store.py +0 -0
  83. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/Dockerfile.j2 +0 -0
  84. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/docker-compose.yml.j2 +0 -0
  85. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/dockerignore.j2 +0 -0
  86. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/evals/dataset.json.j2 +0 -0
  87. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/gitignore.j2 +0 -0
  88. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/__init__.py.j2 +0 -0
  89. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/config.py.j2 +0 -0
  90. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/guardrails.py.j2 +0 -0
  91. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/observability.py.j2 +0 -0
  92. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pkg/prompts.py.j2 +0 -0
  93. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/pyproject.toml.j2 +0 -0
  94. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/scaffold/templates/skills_seed.json.j2 +0 -0
  95. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/skills/__init__.py +0 -0
  96. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/skills/registry.py +0 -0
  97. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/structured.py +0 -0
  98. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/tools/__init__.py +0 -0
  99. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/tools/builtin.py +0 -0
  100. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/src/agentx/tools/mcp.py +0 -0
  101. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_cache.py +0 -0
  102. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_connector.py +0 -0
  103. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_insights.py +0 -0
  104. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_prompts.py +0 -0
  105. {agentx_kit-0.5.1 → agentx_kit-0.7.0}/tests/test_providers.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-kit
3
- Version: 0.5.1
3
+ Version: 0.7.0
4
4
  Summary: An open-source, provider-agnostic agentic framework + interactive project scaffolder for LangChain and CrewAI. Pick your LLM provider, agents, RAG, memory, MCP tools and skills — generate a ready-to-run uv project.
5
5
  Project-URL: Homepage, https://github.com/muhammadyahiya/agentx-kit
6
6
  Project-URL: Repository, https://github.com/muhammadyahiya/agentx-kit
7
7
  Project-URL: Issues, https://github.com/muhammadyahiya/agentx-kit/issues
8
- Author: AgentX
8
+ Author: OptimumAI
9
9
  License: MIT
10
10
  License-File: LICENSE
11
11
  Keywords: agents,azure,bedrock,crewai,gemini,langchain,llm,mcp,openrouter,rag,scaffold,vertex
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  # PyPI distribution name (import name + CLI stay `agentx`; `agentx` was taken).
3
3
  name = "agentx-kit"
4
- version = "0.5.1"
4
+ version = "0.7.0"
5
5
  description = "An open-source, provider-agnostic agentic framework + interactive project scaffolder for LangChain and CrewAI. Pick your LLM provider, agents, RAG, memory, MCP tools and skills — generate a ready-to-run uv project."
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.10,<3.14"
8
8
  license = { text = "MIT" }
9
- authors = [{ name = "AgentX" }]
9
+ authors = [{ name = "OptimumAI" }]
10
10
  keywords = ["agents", "llm", "langchain", "crewai", "rag", "mcp", "scaffold", "openrouter", "bedrock", "vertex", "azure", "gemini"]
11
11
  classifiers = [
12
12
  "Development Status :: 4 - Beta",
@@ -16,7 +16,7 @@ is enough to get started.
16
16
  """
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.5.1"
19
+ __version__ = "0.7.0"
20
20
 
21
21
  from .providers import ( # noqa: E402
22
22
  ProviderSpec,
@@ -23,6 +23,8 @@ import time
23
23
  from pathlib import Path
24
24
  from typing import Any
25
25
 
26
+ from langchain_core.caches import BaseCache
27
+
26
28
  _DEFAULT_PATH = ".agentx/llm_cache.sqlite"
27
29
  _lock = threading.Lock()
28
30
 
@@ -39,11 +41,12 @@ def _model_from_llm_string(llm_string: str) -> str:
39
41
  return "gpt-4o-mini"
40
42
 
41
43
 
42
- class AgentXCache:
44
+ class AgentXCache(BaseCache):
43
45
  """A LangChain ``BaseCache`` backed by SQLite, with TTL + savings stats.
44
46
 
45
- Implements ``lookup``/``update`` (and ``aclear``) so it can be passed to
46
- ``langchain_core.globals.set_llm_cache``.
47
+ Implements ``lookup``/``update``/``clear``; the async ``alookup``/``aupdate``
48
+ paths are inherited from ``BaseCache`` (which runs the sync methods in an
49
+ executor), so it works under both ``invoke`` and ``ainvoke``.
47
50
  """
48
51
 
49
52
  def __init__(self, path: str | Path = _DEFAULT_PATH, ttl: int | None = None):
@@ -153,6 +153,7 @@ def new(
153
153
  provider: str = typer.Option("openai", help="Provider id (with --yes)."),
154
154
  model: str = typer.Option("", help="Model id (with --yes; blank = provider default)."),
155
155
  agents: int = typer.Option(1, help="Number of agents (with --yes)."),
156
+ orchestration: str = typer.Option("supervisor", help="supervisor|sequential|parallel — how agents connect (with --yes, only for LangGraph with >1 agents)."),
156
157
  prompt: str = typer.Option("", "--prompt", "-p", help="System prompt for the first agent (with --yes)."),
157
158
  role: str = typer.Option("Helpful Assistant", help="Role for the first agent (with --yes)."),
158
159
  goal: str = typer.Option("Help the user accomplish their task accurately.", help="Goal for the first agent (with --yes)."),
@@ -187,7 +188,8 @@ def new(
187
188
  agent_specs.append(AgentSpec(name=a_name))
188
189
  spec = ProjectSpec(
189
190
  name=name or "my-agent", framework=framework, provider=provider, model=model,
190
- agents=agent_specs, use_rag=rag, memory=memory, use_mcp=mcp, use_skills=skills,
191
+ agents=agent_specs, orchestration=orchestration,
192
+ use_rag=rag, memory=memory, use_mcp=mcp, use_skills=skills,
191
193
  prompt_style="custom" if prompt else "default",
192
194
  observability=observability, guardrails=guardrails, serve=serve,
193
195
  docker=docker, ci=ci, evals=evals,
@@ -74,10 +74,11 @@ def build_project_from_statement(
74
74
  # Collect a compact view for the calling LLM.
75
75
  tree = sorted(str(p.relative_to(root)) for p in root.glob("**/*") if p.is_file())
76
76
  pkg = spec.package
77
- key_paths = [
78
- "pyproject.toml", "prompts.json", "agentx.json",
79
- f"src/{pkg}/main.py", f"src/{pkg}/agents.py", f"src/{pkg}/prompts.py",
80
- ]
77
+ key_paths = ["pyproject.toml", "prompts.json", "agentx.json", f"src/{pkg}/main.py"]
78
+ if spec.framework == "langgraph":
79
+ key_paths += [f"src/{pkg}/graph.py", f"src/{pkg}/state.py", f"src/{pkg}/nodes.py"]
80
+ else:
81
+ key_paths += [f"src/{pkg}/crew.py", f"src/{pkg}/agents.py", f"src/{pkg}/tasks.py"]
81
82
  if spec.serve:
82
83
  key_paths.append(f"src/{pkg}/server.py")
83
84
  key_files = {}
@@ -18,8 +18,8 @@ from .spec import ProjectSpec
18
18
 
19
19
  TEMPLATES_DIR = Path(__file__).parent / "templates"
20
20
 
21
- # (template, output-relative-path, include?) — output paths use {pkg} placeholder.
22
- _FILE_PLAN: list[tuple[str, str]] = [
21
+ # (template, output-relative-path) — output paths use {pkg} placeholder.
22
+ _COMMON_FILES: list[tuple[str, str]] = [
23
23
  ("pyproject.toml.j2", "pyproject.toml"),
24
24
  ("README.md.j2", "README.md"),
25
25
  ("env.example.j2", ".env.example"),
@@ -27,10 +27,24 @@ _FILE_PLAN: list[tuple[str, str]] = [
27
27
  ("pkg/__init__.py.j2", "src/{pkg}/__init__.py"),
28
28
  ("pkg/config.py.j2", "src/{pkg}/config.py"),
29
29
  ("pkg/prompts.py.j2", "src/{pkg}/prompts.py"),
30
- ("pkg/agents.py.j2", "src/{pkg}/agents.py"),
31
30
  ("pkg/main.py.j2", "src/{pkg}/main.py"),
32
31
  ]
33
32
 
33
+ # A real LangGraph project: explicit state + nodes + graph + tool assembly.
34
+ _LANGGRAPH_FILES: list[tuple[str, str]] = [
35
+ ("pkg/state.py.j2", "src/{pkg}/state.py"),
36
+ ("pkg/tools.py.j2", "src/{pkg}/tools.py"),
37
+ ("pkg/nodes.py.j2", "src/{pkg}/nodes.py"),
38
+ ("pkg/graph.py.j2", "src/{pkg}/graph.py"),
39
+ ]
40
+
41
+ # A real CrewAI project: agents + tasks + crew.
42
+ _CREWAI_FILES: list[tuple[str, str]] = [
43
+ ("pkg/agents.py.j2", "src/{pkg}/agents.py"),
44
+ ("pkg/tasks.py.j2", "src/{pkg}/tasks.py"),
45
+ ("pkg/crew.py.j2", "src/{pkg}/crew.py"),
46
+ ]
47
+
34
48
 
35
49
  @dataclass
36
50
  class GenerationResult:
@@ -70,6 +84,8 @@ def _context(spec: ProjectSpec) -> dict:
70
84
  "provider_env": list(provider_spec.env_vars),
71
85
  "extras": _extras(spec),
72
86
  "extras_str": ",".join(_extras(spec)),
87
+ "multi_agent": len(spec.agents) > 1,
88
+ "orchestration": spec.orchestration,
73
89
  }
74
90
 
75
91
 
@@ -85,12 +101,13 @@ def _env() -> Environment:
85
101
 
86
102
  def _conditional_files(spec: ProjectSpec) -> list[tuple[str, str]]:
87
103
  plan: list[tuple[str, str]] = []
104
+ # Framework-specific core (real project structure).
105
+ plan += _LANGGRAPH_FILES if spec.framework == "langgraph" else _CREWAI_FILES
88
106
  if spec.use_rag:
89
107
  plan.append(("pkg/rag.py.j2", "src/{pkg}/rag.py"))
90
108
  if spec.needs_memory:
91
109
  plan.append(("pkg/memory.py.j2", "src/{pkg}/memory.py"))
92
110
  if spec.use_mcp:
93
- plan.append(("pkg/tools.py.j2", "src/{pkg}/tools.py"))
94
111
  plan.append(("mcp_servers.json.j2", "mcp_servers.json"))
95
112
  if spec.use_skills:
96
113
  plan.append(("skills_seed.json.j2", "data/skills/star-method.json"))
@@ -123,6 +140,7 @@ def _write_manifest(target: Path, spec: ProjectSpec) -> Path:
123
140
  "model": spec.model or get_spec(spec.provider).default_model,
124
141
  "python_version": ">=3.10,<3.14",
125
142
  "agents": [a.name for a in spec.agents],
143
+ "orchestration": spec.orchestration,
126
144
  "features": {
127
145
  "rag": spec.use_rag,
128
146
  "memory": spec.memory,
@@ -156,13 +174,28 @@ def generate_project(spec: ProjectSpec, target_dir: str | Path, overwrite: bool
156
174
  written: list[Path] = []
157
175
  messages: list[str] = []
158
176
 
159
- for template_name, out_rel in _FILE_PLAN + _conditional_files(spec):
177
+ for template_name, out_rel in _COMMON_FILES + _conditional_files(spec):
160
178
  out_path = target / out_rel.format(pkg=spec.package)
161
179
  out_path.parent.mkdir(parents=True, exist_ok=True)
162
180
  rendered = env.get_template(template_name).render(**ctx)
163
181
  out_path.write_text(rendered, encoding="utf-8")
164
182
  written.append(out_path)
165
183
 
184
+ # Seed a knowledge/ directory when RAG or the MCP filesystem server needs one
185
+ # (the restricted MCP server points at ./knowledge and RAG indexes it).
186
+ if spec.use_rag or spec.use_mcp:
187
+ knowledge_dir = target / "knowledge"
188
+ knowledge_dir.mkdir(parents=True, exist_ok=True)
189
+ seed = knowledge_dir / "README.md"
190
+ if not seed.exists():
191
+ seed.write_text(
192
+ f"# {spec.slug} knowledge base\n\n"
193
+ "Drop `.txt` / `.md` files here. They are indexed for RAG"
194
+ " and exposed (read-only) to the MCP filesystem tool.\n",
195
+ encoding="utf-8",
196
+ )
197
+ written.append(seed)
198
+
166
199
  # The prompt source of truth — edited by hand or via `agentx prompt`.
167
200
  written.append(prompts_store.write_prompts(target, spec))
168
201
  # A single declarative manifest of the project (à la langgraph.json).
@@ -15,6 +15,12 @@ Framework = Literal["langgraph", "crewai"]
15
15
  MemoryMode = Literal["none", "short", "long", "both"]
16
16
  PromptStyle = Literal["default", "custom"]
17
17
 
18
+ # How multiple agents are wired together (LangGraph only; CrewAI always uses sequential crew).
19
+ # supervisor — an LLM router decides which worker acts next (dynamic, context-aware)
20
+ # sequential — agents run in order: agent_1 → agent_2 → … (pipeline / chain-of-thought)
21
+ # parallel — all agents handle the same input simultaneously; results are merged
22
+ OrchestrationMode = Literal["supervisor", "sequential", "parallel"]
23
+
18
24
 
19
25
  def to_snake(name: str) -> str:
20
26
  s = re.sub(r"[^0-9a-zA-Z]+", "_", name.strip().lower()).strip("_")
@@ -40,6 +46,8 @@ class ProjectSpec(BaseModel):
40
46
  provider: str = "openai"
41
47
  model: str = "" # blank → provider default
42
48
  agents: list[AgentSpec] = Field(default_factory=lambda: [AgentSpec()])
49
+ # How agents are connected (only meaningful when len(agents) > 1 and framework == langgraph).
50
+ orchestration: OrchestrationMode = "supervisor"
43
51
  use_rag: bool = False
44
52
  memory: MemoryMode = "none"
45
53
  use_mcp: bool = False
@@ -84,3 +92,7 @@ class ProjectSpec(BaseModel):
84
92
  @property
85
93
  def use_long_memory(self) -> bool:
86
94
  return self.memory in ("long", "both")
95
+
96
+ @property
97
+ def multi_agent(self) -> bool:
98
+ return len(self.agents) > 1
@@ -0,0 +1,69 @@
1
+ # {{ spec.slug }}
2
+
3
+ Agentic app generated by **AgentX**.
4
+
5
+ - **Framework:** {{ spec.framework }}
6
+ - **Provider:** {{ provider_label }} (`{{ model }}`)
7
+ - **Agents:** {% for a in spec.agents %}`{{ a.name }}`{% if not loop.last %}, {% endif %}{% endfor %}
8
+
9
+ - **RAG:** {{ "yes" if spec.use_rag else "no" }}
10
+ - **Memory:** {{ spec.memory }}
11
+ - **MCP tools:** {{ "yes" if spec.use_mcp else "no" }}
12
+ - **Skills:** {{ "yes" if spec.use_skills else "no" }}
13
+
14
+ ## Setup
15
+ ```bash
16
+ uv sync # install dependencies into .venv
17
+ cp .env.example .env # then fill in your credentials
18
+ ```
19
+
20
+ ## Run
21
+ ```bash
22
+ uv run {{ spec.slug }}
23
+ # or:
24
+ uv run python -m {{ pkg }}.main
25
+ {% if spec.serve %}# API server:
26
+ uv run uvicorn {{ pkg }}.server:app --reload
27
+ {% endif %}
28
+ ```
29
+ {% if spec.framework == 'langgraph' %}
30
+ Uses LangGraph's dev server too: `uvx langgraph dev` (the compiled graph is
31
+ `{{ pkg }}.graph:graph`).
32
+
33
+ ## Layout (LangGraph)
34
+ ```
35
+ src/{{ pkg }}/
36
+ ├── config.py # provider/model selection (pydantic-settings)
37
+ ├── prompts.py # loads prompts.json (edit prompts there or via `agentx prompt`)
38
+ ├── state.py # AgentState — the typed graph state (messages + reducers)
39
+ ├── tools.py # tool assembly (web search{% if spec.use_rag %} + knowledge base{% endif %}{% if spec.use_mcp %} + MCP{% endif %})
40
+ ├── nodes.py # graph nodes ({% if multi_agent %}supervisor + workers{% else %}the agent model call{% endif %})
41
+ ├── graph.py # StateGraph workflow + compiled `graph` + run_text()
42
+ {% if spec.use_rag %}├── rag.py # knowledge base / retriever tool
43
+ {% endif %}
44
+ {% if spec.needs_memory %}├── memory.py # conversation / long-term memory helpers
45
+ {% endif %}
46
+ {% if spec.serve %}├── server.py # FastAPI (REST + SSE)
47
+ {% endif %}
48
+ └── main.py # interactive REPL
49
+ ```
50
+ {% else %}
51
+ ## Layout (CrewAI)
52
+ ```
53
+ src/{{ pkg }}/
54
+ ├── config.py # provider/model selection (pydantic-settings)
55
+ ├── prompts.py # loads prompts.json
56
+ ├── agents.py # CrewAI Agent definitions
57
+ ├── tasks.py # Task definitions
58
+ ├── crew.py # Crew assembly + run_text()
59
+ {% if spec.serve %}├── server.py # FastAPI (REST + SSE)
60
+ {% endif %}
61
+ └── main.py # interactive REPL
62
+ ```
63
+ {% endif %}
64
+ {% if spec.use_rag %}
65
+ Add `.txt`/`.md` files under `knowledge/` to populate the RAG index.
66
+ {% endif %}
67
+ {% if spec.use_mcp %}
68
+ Edit `mcp_servers.json` to configure your MCP servers.
69
+ {% endif %}
@@ -33,9 +33,15 @@ jobs:
33
33
  - name: Install
34
34
  run: uv pip install --system -e .
35
35
  - name: Run eval gate
36
+ # Non-blocking: the eval harness needs a reachable model (an API key or a
37
+ # local model server). Add your provider secret(s) below and drop
38
+ # `continue-on-error` to make this a hard CI gate.
39
+ continue-on-error: true
36
40
  env:
37
- # Add your provider key as a repo secret to enable the gate.
38
- OPENAI_API_KEY: {% raw %}${{ secrets.OPENAI_API_KEY }}{% endraw %}
39
-
41
+ AGENTX_PROVIDER: {{ spec.provider }}
42
+ AGENTX_MODEL: {{ model }}
43
+ {% for v in provider_env %}
44
+ {{ v }}: {% raw %}${{ secrets.{% endraw %}{{ v }}{% raw %} }}{% endraw %}
45
+ {% endfor %}
40
46
  run: python evals/run_evals.py
41
47
  {% endif %}
@@ -0,0 +1,25 @@
1
+ # --- Provider selection (read by agentx) ---
2
+ AGENTX_PROVIDER={{ spec.provider }}
3
+ AGENTX_MODEL={{ model }}
4
+ AGENTX_TEMPERATURE=0.3
5
+
6
+ # --- Credentials for {{ provider_label }} ---
7
+ {% for v in provider_env %}
8
+ {{ v }}=
9
+ {% endfor %}
10
+ {% if not provider_env %}
11
+ # {{ provider_label }} is local — no API key required.
12
+ {% if spec.provider == 'ollama' %}
13
+ # Start it first: ollama serve && ollama pull {{ model }}
14
+ # OLLAMA_BASE_URL=http://localhost:11434
15
+ {% endif %}
16
+ {% endif %}
17
+ {% if spec.observability %}
18
+
19
+ # --- Observability (optional) ---
20
+ AGENTX_TELEMETRY=true
21
+ # OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
22
+ # LANGFUSE_PUBLIC_KEY=
23
+ # LANGFUSE_SECRET_KEY=
24
+ # LANGFUSE_HOST=https://cloud.langfuse.com
25
+ {% endif %}
@@ -17,10 +17,9 @@ from dotenv import load_dotenv
17
17
 
18
18
  from agentx import get_chat_model
19
19
  {% if spec.framework == 'langgraph' %}
20
- from agentx.frameworks import run_agent
21
- from {{ pkg }}.agents import build_agents
20
+ from {{ pkg }}.graph import run_text
22
21
  {% else %}
23
- from {{ pkg }}.agents import build_project_crew
22
+ from {{ pkg }}.crew import run_text
24
23
  {% endif %}
25
24
 
26
25
  THRESHOLD = 0.6
@@ -35,12 +34,7 @@ JUDGE_PROMPT = (
35
34
 
36
35
 
37
36
  def _agent_reply(message: str) -> str:
38
- {% if spec.framework == 'langgraph' %}
39
- agent = next(iter(build_agents().values()))
40
- return run_agent(agent, message)
41
- {% else %}
42
- return str(build_project_crew(message).kickoff())
43
- {% endif %}
37
+ return run_text(message)
44
38
 
45
39
 
46
40
  def _score(judge, case: dict, output: str) -> float:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "filesystem": {
3
3
  "command": "npx",
4
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
4
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "./knowledge"],
5
5
  "transport": "stdio"
6
6
  }
7
7
  }
@@ -0,0 +1,21 @@
1
+ """Agent definitions (CrewAI) for {{ spec.slug }} — built from prompts.json."""
2
+ from __future__ import annotations
3
+
4
+ from agentx.frameworks import build_crewai_agent
5
+ from .config import MODEL, PROVIDER
6
+ from .prompts import agents_meta, resolved_system_prompts
7
+
8
+
9
+ def build_agents() -> dict:
10
+ """Return {name: CrewAI Agent} — one per entry in prompts.json."""
11
+ prompts = resolved_system_prompts()
12
+ agents: dict = {}
13
+ for name, meta in agents_meta().items():
14
+ agents[name] = build_crewai_agent(
15
+ role=meta.get("role", name),
16
+ goal=meta.get("goal", "Help the user accomplish their task accurately."),
17
+ backstory=prompts.get(name, ""),
18
+ provider=PROVIDER,
19
+ model=MODEL,
20
+ )
21
+ return agents
@@ -0,0 +1,23 @@
1
+ """Crew assembly (CrewAI) for {{ spec.slug }}."""
2
+ from __future__ import annotations
3
+
4
+ from agentx.frameworks import build_crew
5
+ from .agents import build_agents
6
+ from .tasks import build_tasks
7
+
8
+
9
+ def build_project_crew(user_input: str):
10
+ """Assemble a sequential crew of all agents for the user's request."""
11
+ agents = build_agents()
12
+ tasks = build_tasks(user_input, agents)
13
+ return build_crew(list(agents.values()), tasks)
14
+
15
+
16
+ def run_text(message: str, thread_id: str = "default") -> str:
17
+ """Run the crew on a message and return the final text (used by main/server).
18
+
19
+ ``thread_id`` is accepted for API-compatibility with the server; a CrewAI
20
+ kickoff is stateless per call, so it is not used to partition memory here.
21
+ """
22
+ del thread_id
23
+ return str(build_project_crew(message).kickoff())
@@ -0,0 +1,172 @@
1
+ """LangGraph workflow for {{ spec.slug }}.
2
+
3
+ Exposes a module-level compiled ``graph`` (the LangGraph convention, also picked
4
+ up by ``langgraph dev``) and ``run_text`` / ``stream_text`` helpers.
5
+
6
+ {% if multi_agent %}
7
+ Orchestration: {{ orchestration }}
8
+ {% if orchestration == "supervisor" %} An LLM router decides which specialist to call next.{% endif %}
9
+ {% if orchestration == "sequential" %} Agents run in order: {% for a in spec.agents %}{{ a.name }}{% if not loop.last %} → {% endif %}{% endfor %}.{% endif %}
10
+ {% if orchestration == "parallel" %} All agents answer simultaneously; results are merged.{% endif %}
11
+ {% endif %}
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import logging
17
+
18
+ from langgraph.checkpoint.memory import MemorySaver
19
+ from langgraph.graph import END, START, StateGraph
20
+
21
+ from .state import AgentState
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ RECURSION_LIMIT = 25
26
+ {% if not multi_agent %}
27
+ from langgraph.prebuilt import ToolNode, tools_condition
28
+
29
+ from .nodes import call_model
30
+ from .tools import get_tools
31
+
32
+
33
+ def build_graph():
34
+ """Build a ReAct agent graph: agent ⇄ tools loop, with checkpointed memory."""
35
+ builder = StateGraph(AgentState)
36
+ builder.add_node("agent", call_model)
37
+ builder.add_node("tools", ToolNode(get_tools()))
38
+
39
+ builder.add_edge(START, "agent")
40
+ builder.add_conditional_edges("agent", tools_condition)
41
+ builder.add_edge("tools", "agent")
42
+
43
+ return builder.compile(checkpointer=MemorySaver())
44
+ {% elif orchestration == "supervisor" %}
45
+ from .nodes import WORKERS, make_worker, supervisor
46
+ from .prompts import resolved_system_prompts
47
+
48
+
49
+ def build_graph():
50
+ """Supervisor graph: an LLM router picks the next specialist each turn."""
51
+ builder = StateGraph(AgentState)
52
+ builder.add_node("supervisor", supervisor)
53
+ for name, system_prompt in resolved_system_prompts().items():
54
+ builder.add_node(name, make_worker(name, system_prompt))
55
+
56
+ builder.add_edge(START, "supervisor")
57
+ builder.add_conditional_edges(
58
+ "supervisor",
59
+ lambda state: state.get("next", "FINISH"),
60
+ {**{name: name for name in WORKERS}, "FINISH": END},
61
+ )
62
+ for name in WORKERS:
63
+ builder.add_edge(name, "supervisor")
64
+
65
+ return builder.compile(checkpointer=MemorySaver())
66
+ {% elif orchestration == "sequential" %}
67
+ from .nodes import WORKERS, make_worker
68
+ from .prompts import resolved_system_prompts
69
+
70
+
71
+ def build_graph():
72
+ """Sequential pipeline: agents run in order, each seeing the previous output."""
73
+ builder = StateGraph(AgentState)
74
+ prompts = resolved_system_prompts()
75
+ names = list(prompts)
76
+
77
+ for name in names:
78
+ builder.add_node(name, make_worker(name, prompts[name]))
79
+
80
+ builder.add_edge(START, names[0])
81
+ for i in range(len(names) - 1):
82
+ builder.add_edge(names[i], names[i + 1])
83
+ builder.add_edge(names[-1], END)
84
+
85
+ return builder.compile(checkpointer=MemorySaver())
86
+ {% elif orchestration == "parallel" %}
87
+ from langgraph.types import Send
88
+
89
+ from .nodes import WORKERS, make_worker, merge_results
90
+ from .prompts import resolved_system_prompts
91
+
92
+
93
+ def _fan_out(state: AgentState) -> list[Send]:
94
+ """Send the same user message to every worker simultaneously."""
95
+ return [Send(name, state) for name in WORKERS]
96
+
97
+
98
+ def build_graph():
99
+ """Parallel graph: all agents answer simultaneously, then results are merged."""
100
+ builder = StateGraph(AgentState)
101
+ for name, system_prompt in resolved_system_prompts().items():
102
+ builder.add_node(name, make_worker(name, system_prompt))
103
+ builder.add_node("merge", merge_results)
104
+
105
+ builder.add_conditional_edges(START, _fan_out, WORKERS)
106
+ for name in WORKERS:
107
+ builder.add_edge(name, "merge")
108
+ builder.add_edge("merge", END)
109
+
110
+ return builder.compile(checkpointer=MemorySaver())
111
+ {% endif %}
112
+
113
+
114
+ graph = build_graph()
115
+
116
+
117
+ def _config(thread_id: str) -> dict:
118
+ return {"configurable": {"thread_id": thread_id}, "recursion_limit": RECURSION_LIMIT}
119
+
120
+
121
+ async def arun_text(message: str, thread_id: str = "default") -> str:
122
+ """Async invoke; runs async-only tools (MCP) correctly.
123
+
124
+ Pass a unique ``thread_id`` per user/session — callers that omit it share
125
+ one conversation history (fine for a single CLI session, wrong for a server).
126
+ """
127
+ try:
128
+ result = await graph.ainvoke(
129
+ {"messages": [{"role": "user", "content": message}]}, config=_config(thread_id)
130
+ )
131
+ except Exception as exc: # noqa: BLE001
132
+ logger.exception("graph invocation failed")
133
+ return f"Sorry, something went wrong: {exc}"
134
+ messages = result.get("messages", [])
135
+ return getattr(messages[-1], "content", "") if messages else ""
136
+
137
+
138
+ _loop: asyncio.AbstractEventLoop | None = None
139
+
140
+
141
+ def _sync_loop() -> asyncio.AbstractEventLoop:
142
+ """Reuse one loop across calls so MCP stdio sessions don't break on turn 2."""
143
+ global _loop
144
+ if _loop is None or _loop.is_closed():
145
+ _loop = asyncio.new_event_loop()
146
+ return _loop
147
+
148
+
149
+ def run_text(message: str, thread_id: str = "default") -> str:
150
+ """Sync wrapper for CLI / REPL use. Do NOT call from an async context."""
151
+ return _sync_loop().run_until_complete(arun_text(message, thread_id))
152
+
153
+
154
+ async def stream_text(message: str, thread_id: str = "default"):
155
+ """Yield reply tokens as the model produces them."""
156
+ try:
157
+ async for _ns, (chunk, meta) in graph.astream(
158
+ {"messages": [{"role": "user", "content": message}]},
159
+ config=_config(thread_id),
160
+ stream_mode="messages",
161
+ subgraphs=True,
162
+ ):
163
+ {% if multi_agent and orchestration == "supervisor" %}
164
+ if meta.get("langgraph_node") == "supervisor":
165
+ continue
166
+ {% endif %}
167
+ text = getattr(chunk, "content", "")
168
+ if text:
169
+ yield text
170
+ except Exception as exc: # noqa: BLE001
171
+ logger.exception("graph streaming failed")
172
+ yield f"Sorry, something went wrong: {exc}"
@@ -0,0 +1,45 @@
1
+ """Entry point for {{ spec.slug }} — an interactive REPL over the agent graph."""
2
+ from __future__ import annotations
3
+
4
+ from dotenv import load_dotenv
5
+ {% if spec.observability %}from .observability import init_observability
6
+ {% endif %}
7
+ {% if spec.guardrails %}from .guardrails import guard_input, guard_output
8
+ {% endif %}
9
+ {% if spec.use_cache %}from agentx.cache import enable_caching
10
+ {% endif %}
11
+ {% if spec.framework == 'langgraph' %}from .graph import run_text
12
+ {% else %}from .crew import run_text
13
+ {% endif %}
14
+
15
+
16
+ def main() -> None:
17
+ load_dotenv()
18
+ {% if spec.observability %}
19
+ init_observability()
20
+ {% endif %}
21
+ {% if spec.use_cache %}
22
+ enable_caching() # cache LLM responses → lower cost & latency
23
+ {% endif %}
24
+ print("🧬 {{ spec.slug }} ready. Type 'quit' to exit.\n")
25
+ while True:
26
+ try:
27
+ user = input("you > ").strip()
28
+ except (EOFError, KeyboardInterrupt):
29
+ break
30
+ if user.lower() in {"quit", "exit"}:
31
+ break
32
+ if not user:
33
+ continue
34
+ {% if spec.guardrails %}
35
+ user = guard_input(user).text
36
+ {% endif %}
37
+ reply = run_text(user)
38
+ {% if spec.guardrails %}
39
+ reply = guard_output(reply).text
40
+ {% endif %}
41
+ print(f"\nbot > {reply}\n")
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()