Date: 2026-04-04 Status: Proposal Core Principle: Don't sell a framework — sell a safety layer. Users keep LangChain/CrewAI/AutoGen, we add static analysis on top.
lambdagentpaas should not compete with LangChain/CrewAI/AutoGen as an agent framework. Instead, it should serve as a static analysis layer — like TypeScript for JavaScript, mypy for Python, or ESLint for code quality.
| Analogy | Original Language | Checker Tool | Must users switch? |
|---|---|---|---|
| TypeScript | JavaScript | tsc | No |
| mypy | Python | mypy | No |
| ESLint | JavaScript | eslint | No |
| lambdagent-guard | LangChain/CrewAI/AutoGen | lint + type + cost | No |
Users continue writing agents in their preferred framework. lambdagentpaas extracts the configuration, compiles it to a λA term, and runs static analysis — type checking, cost prediction, loop termination verification, parallel safety — before a single LLM call is made.
Expose static analysis as MCP tools. All AI IDEs that support MCP (Claude Code, Cursor, Windsurf, VS Code Copilot) gain lambdagent capabilities with one config line.
User setup (one line in settings):
// .claude/settings.json (Claude Code)
// .cursor/mcp.json (Cursor)
{
"mcpServers": {
"lambdagent-analyzer": {
"command": "uvx",
"args": ["lambdagent-mcp-server"]
}
}
}
Tools exposed via MCP:
lint_agent_configLint an agent YAML/JSON config for structural defects. Works with LangChain, CrewAI, AutoGen, Dify, and generic configs. Detects: missing terminate conditions, type mismatches, dead routes, empty loops, and 20+ other defect patterns.
Input: { "config_path": "agents/security_scanner.yml" }
Output: {
"framework": "crewai",
"errors": [
{
"rule": "L004a",
"level": "ERROR",
"message": "No terminate tool in ReAct loop (maxSteps=200)",
"lambda_meaning": "fix₂₀₀ has no base case λx.x",
"line": 15,
"fix": "Add 'terminate' to localTools list"
}
],
"warnings": [...],
"summary": "2 errors, 3 warnings"
}
estimate_agent_costEstimate worst-case cost of an agent pipeline BEFORE execution. Returns token upper bound, latency estimate, dollar cost, and end-to-end success probability.
Input: { "config_path": "agents/research_pipeline.yml" }
Output: {
"tokens_upper_bound": 164000,
"latency_upper_bound_sec": 173,
"cost_upper_bound_usd": 1.74,
"success_probability": 0.0016,
"breakdown": [
{ "stage": "parallel_scanners", "cost": 0.18, "probability": 0.021 },
{ "stage": "deep_analysis", "cost": 0.60, "probability": 0.12 },
{ "stage": "fix_and_review", "cost": 0.96, "probability": 0.62 }
],
"recommendation": "Success probability 0.16% is critically low. Primary bottleneck: 5 parallel scanners must ALL succeed (0.46^5=2.1%). Reduce to 3 scanners or lower maxSteps."
}
check_agent_typesType-check an agent pipeline. Verifies that each stage's output type is compatible with the next stage's input type (Paper III T-Compose rule).
Input: { "config_path": "agents/data_pipeline.yml" }
Output: {
"type_safe": false,
"errors": [
{
"stage": 2,
"composition": "scanner >> analyzer",
"output_type": "Json(object({results: array(string)}))",
"input_type": "Str",
"error": "Json(object) is not subtype of Str",
"fix": "Add a Json-to-Str adapter between scanner and analyzer, or change analyzer to accept Json input"
}
]
}
check_parallel_safetyCheck if parallel agents have store-independence (no shared mutable state). Prevents data corruption from race conditions (Paper II Proposition 30).
Input: { "config_path": "agents/multi_agent.yml" }
Output: {
"safe": false,
"conflicts": [
{
"agent_a": "researcher",
"agent_b": "code_analyzer",
"shared_keys": ["shared_doc"],
"risk": "Both agents write to 'shared_doc'. Last-write-wins race condition.",
"fix": "Use separate keys: 'research_notes' and 'code_analysis'"
}
]
}
Implementation: ~300 lines. All four tools call existing functions: lint_config(), compute_grade(), type_check(), check_store_independence().
Agent config lint runs on every pull request, blocking merges with structural defects.
User setup (add one workflow file):
# .github/workflows/agent-lint.yml
name: Agent Config Lint
on:
pull_request:
paths:
- '**/*.yml'
- '**/*.yaml'
- '**/*.json'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: lambdagent/agent-lint-action@v1
with:
# Which directories to scan
paths: |
agents/
configs/
# Fail the check on errors (options: error | warn | info)
fail-on: error
# Auto-detect framework (options: auto | crewai | langchain | autogen | dify)
frameworks: auto
# Fail if worst-case cost per run exceeds threshold
cost-threshold: 5.00
# Enable type checking across pipeline stages
type-check: true
# Enable parallel safety verification
parallel-check: true
PR comment output:
## 🔍 Agent Config Lint Results
### ❌ agents/security_scanner.yml (2 errors, 1 warning)
| Level | Rule | Message | λA Meaning |
|-------|------|---------|------------|
| ❌ ERROR | L004a | No terminate tool in ReAct loop (maxSteps=200) | fix₂₀₀ has no base case λx.x — forced truncation |
| ❌ ERROR | T-COMPOSE | Stage 2 output `Json(object)` ≠ Stage 3 input `Str` | Composition type mismatch: B ≮: B' |
| ⚠️ WARN | COST | Worst-case cost $12.40/run (threshold: $5.00) | Graded type: (0.016, 164000, 173s, $12.40) |
### ✅ agents/data_fetcher.yml (clean)
### Summary
- 1/2 configs have errors
- Estimated total cost: $12.40 + $0.85 = $13.25 per full run
- Recommendation: Fix security_scanner.yml before merge
Implementation: ~200 lines (Docker action wrapping lambdagent lint --format json).
Non-invasive wrapper around existing framework objects. Users add 2 lines of code; the rest of their codebase stays unchanged.
# pip install lambdagent-guard
from langchain.agents import AgentExecutor, create_react_agent
from lambdagent_guard import guard_langchain
# === Original LangChain code (unchanged) ===
llm = ChatAnthropic(model="claude-sonnet-4-20250514")
tools = [search_tool, read_tool, write_tool]
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, max_iterations=20)
# === Add guard (2 lines) ===
guarded = guard_langchain(executor,
cost_budget=5.00, # Pause if cost exceeds $5
type_check=True, # Verify tool I/O type compatibility
loop_detection=True, # Detect repeated identical states
cost_alert=lambda c: print(f"⚠️ Cost so far: ${c:.2f}")
)
# === Use exactly as before ===
result = guarded.invoke({"input": "Analyze this repository"})
from crewai import Agent, Task, Crew
from lambdagent_guard import guard_crewai
# === Original CrewAI code (unchanged) ===
researcher = Agent(role="Researcher", goal="...", tools=[search])
writer = Agent(role="Writer", goal="...", tools=[write])
crew = Crew(agents=[researcher, writer], tasks=[...])
# === Add guard (2 lines) ===
guarded_crew = guard_crewai(crew,
cost_budget=10.00, # Budget ceiling
parallel_safety=True, # Check agent store independence
terminate_check=True, # Verify termination conditions exist
)
result = guarded_crew.kickoff()
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
from lambdagent_guard import guard_autogen
# === Original AutoGen code (unchanged) ===
assistant = AssistantAgent("assistant", llm_config=llm_config)
proxy = UserProxyAgent("user_proxy", code_execution_config={...})
chat = GroupChat(agents=[assistant, proxy], max_round=20)
manager = GroupChatManager(groupchat=chat)
# === Add guard (2 lines) ===
guarded_manager = guard_autogen(manager,
cost_budget=5.00,
empty_message_detection=True, # Catch AutoGen #108 blank-message loops
terminate_robustness=True, # Don't rely on exact string matching
)
proxy.initiate_chat(guarded_manager, message="Write a sorting algorithm")
# lambdagent_guard/core.py
class GuardedExecutor:
"""Wraps any agent executor with lambdagent static/dynamic guards."""
def __init__(self, executor, config, **opts):
self.executor = executor
self.opts = opts
# Phase 1: Compile-time checks (before any execution)
term = from_config(config)
if opts.get("type_check"):
errors = type_check(term)
if errors:
raise AgentTypeError(
f"Pipeline type mismatch: {errors[0].description}\n"
f"This would crash at runtime after spending "
f"${errors[0].wasted_cost:.2f} on preceding stages."
)
grade = compute_grade(term)
if grade.cost > opts.get("cost_budget", float("inf")):
raise CostBudgetExceeded(
f"Worst-case cost ${grade.cost:.2f} exceeds "
f"budget ${opts['cost_budget']:.2f}\n"
f"Breakdown: {grade.per_stage_summary()}"
)
if opts.get("parallel_safety"):
conflicts = check_store_independence(term)
if conflicts:
raise StoreConflictError(conflicts)
# Phase 2: Runtime hooks (during execution)
self.cost_accumulator = CostVector(0, 0, 0)
self.state_history = []
def on_step(self, step_info):
"""Called before each LLM/tool invocation (CEK Yield equivalent)."""
# Cost monitoring
self.cost_accumulator += estimate_step_cost(step_info)
if self.opts.get("cost_alert"):
self.opts["cost_alert"](self.cost_accumulator.cost)
if self.cost_accumulator.cost > self.opts.get("cost_budget", float("inf")):
raise CostBudgetExceeded(self.cost_accumulator)
# Loop detection
if self.opts.get("loop_detection"):
state_hash = hash_state(step_info)
recent = self.state_history[-5:]
if recent.count(state_hash) >= 3:
raise InfiniteLoopDetected(
f"Identical state detected {recent.count(state_hash)} "
f"times in last 5 steps. Agent is not making progress."
)
self.state_history.append(state_hash)
Implementation: ~800 lines per framework (extractor + guard wrapper).
Real-time lint on YAML file save. Shows inline diagnostics in the editor.
User experience:
# User edits agents/security_scanner.yml in VS Code
agents/security_scanner.yml
│
├─ Line 5: ❌ L004a: No terminate tool — ReAct loop may never exit
│ λA: fix₂₀₀ has no base case λx.x
│ Quick Fix: Add 'terminate' to localTools
│
├─ Line 12: ❌ T-COMPOSE: output(scanner)=Json ≠ input(analyzer)=Str
│ Quick Fix: Add json_to_str adapter between stages
│
├─ Line 18: ⚠️ COST: maxSteps=200 × claude-opus = $6.00/run upper bound
│ Quick Fix: Change maxSteps to 20
│
└─ Line 23: ⚠️ L013: Route missing default branch — non-exhaustive dispatch
Quick Fix: Add 'default' route
Status bar: λA: 2 errors, 2 warnings | Est. cost: $6.00/run | Success: 0.16%
Implementation: ~500 lines (VS Code extension calling lambdagent lint --format json via Language Server Protocol).
The key technical component that enables all integration modes: extracting λA-compatible configurations from each framework's runtime objects.
lambdagent/extractors/
├── base.py # Abstract extractor interface
├── langchain_extractor.py # LangChain Agent → normalized config
├── crewai_extractor.py # CrewAI Crew → normalized config
├── autogen_extractor.py # AutoGen GroupChat → normalized config
└── dify_extractor.py # Dify workflow → normalized config
# lambdagent/extractors/base.py
from abc import ABC, abstractmethod
from typing import Dict, Any
class FrameworkExtractor(ABC):
"""Extract lambdagent-compatible config from a framework object."""
@abstractmethod
def extract(self, framework_object: Any) -> Dict[str, Any]:
"""Convert framework-specific agent to normalized YAML config dict.
The returned dict must conform to lambdagent YAML schema:
- type: simple | react | chain | router | parallel
- model: { name, temperature, maxTokens }
- systemPrompt: str
- react: { maxSteps }
- mcp: { localTools: [...] }
- memory: { enabled, strategy, size }
"""
...
@abstractmethod
def detect(self, obj: Any) -> bool:
"""Return True if obj is an instance of this framework's agent."""
...
# lambdagent/extractors/langchain_extractor.py
class LangChainExtractor(FrameworkExtractor):
def detect(self, obj):
return hasattr(obj, 'agent') and hasattr(obj, 'tools') and \
hasattr(obj, 'max_iterations')
def extract(self, executor) -> dict:
# Extract model info
llm = executor.agent.llm_chain.llm if hasattr(executor.agent, 'llm_chain') \
else executor.agent.llm
model_name = getattr(llm, 'model_name', getattr(llm, 'model', 'unknown'))
temperature = getattr(llm, 'temperature', 0.0)
# Extract tools
tool_names = [t.name for t in executor.tools]
has_terminate = any(
name in ('terminate', 'final_answer', 'human')
for name in tool_names
)
# Extract prompt
prompt_template = ""
if hasattr(executor.agent, 'llm_chain') and \
hasattr(executor.agent.llm_chain, 'prompt'):
prompt_template = executor.agent.llm_chain.prompt.template
return {
"type": "react",
"model": {
"name": model_name,
"temperature": temperature,
},
"systemPrompt": prompt_template,
"react": {
"maxSteps": executor.max_iterations or 15,
},
"mcp": {
"localTools": tool_names + (["terminate"] if has_terminate else []),
},
}
# lambdagent/extractors/crewai_extractor.py
class CrewAIExtractor(FrameworkExtractor):
def detect(self, obj):
return hasattr(obj, 'agents') and hasattr(obj, 'tasks') and \
hasattr(obj, 'kickoff')
def extract(self, crew) -> dict:
agents = []
for agent in crew.agents:
agent_config = {
"type": "react",
"model": {
"name": getattr(agent, 'llm', {}).get('model', 'unknown') \
if isinstance(getattr(agent, 'llm', None), dict) \
else str(getattr(agent, 'llm', 'unknown')),
},
"systemPrompt": f"Role: {agent.role}\nGoal: {agent.goal}\n"
f"Backstory: {agent.backstory}",
"react": {
"maxSteps": getattr(agent, 'max_iter', 25),
},
"mcp": {
"localTools": [t.name for t in (agent.tools or [])],
},
}
agents.append(agent_config)
# Determine crew execution pattern
process = getattr(crew, 'process', 'sequential')
if process == 'sequential':
return {
"type": "chain",
"steps": agents,
}
elif process == 'hierarchical':
return {
"type": "router",
"classifier": agents[0], # manager agent
"routes": {a["systemPrompt"][:20]: a for a in agents[1:]},
}
else:
return {
"type": "parallel",
"agents": agents,
}
# lambdagent/extractors/autogen_extractor.py
class AutoGenExtractor(FrameworkExtractor):
def detect(self, obj):
return hasattr(obj, 'groupchat') or \
(hasattr(obj, 'llm_config') and hasattr(obj, 'system_message'))
def extract(self, manager) -> dict:
chat = manager.groupchat
agents = []
for agent in chat.agents:
agent_config = {
"type": "react",
"model": {
"name": self._extract_model(agent),
},
"systemPrompt": getattr(agent, 'system_message', ''),
"react": {
"maxSteps": getattr(agent, 'max_consecutive_auto_reply', 10),
},
"mcp": {
"localTools": self._extract_tools(agent),
},
}
agents.append(agent_config)
# GroupChat is a multi-agent loop
termination_msg = getattr(chat, 'is_termination_msg', None)
return {
"type": "parallel", # GroupChat agents interact
"agents": agents,
"multiagent": {
"maxRounds": getattr(chat, 'max_round', 10),
"terminationCondition": "is_termination_msg" if termination_msg else None,
},
}
def _extract_model(self, agent):
llm_config = getattr(agent, 'llm_config', {})
if isinstance(llm_config, dict):
config_list = llm_config.get('config_list', [{}])
if config_list:
return config_list[0].get('model', 'unknown')
return 'unknown'
def _extract_tools(self, agent):
funcs = getattr(agent, '_function_map', {})
return list(funcs.keys()) if funcs else []
# lambdagent/extractors/__init__.py
from .langchain_extractor import LangChainExtractor
from .crewai_extractor import CrewAIExtractor
from .autogen_extractor import AutoGenExtractor
_EXTRACTORS = [
LangChainExtractor(),
CrewAIExtractor(),
AutoGenExtractor(),
]
def extract_config(framework_object) -> dict:
"""Auto-detect framework and extract normalized config."""
for extractor in _EXTRACTORS:
if extractor.detect(framework_object):
return extractor.extract(framework_object)
raise UnsupportedFrameworkError(
f"Cannot extract config from {type(framework_object).__name__}. "
f"Supported: LangChain AgentExecutor, CrewAI Crew, AutoGen GroupChatManager."
)
Add dedicated lint/analysis endpoints to agentpaas API:
POST /api/v1/analyze/lint
Body: { "config": <YAML string or dict>, "framework": "auto" }
Returns: { "framework": "crewai", "errors": [...], "warnings": [...] }
POST /api/v1/analyze/type-check
Body: { "config": <YAML string or dict> }
Returns: { "type_safe": true/false, "errors": [...] }
POST /api/v1/analyze/cost
Body: { "config": <YAML string or dict> }
Returns: { "grade": { "p": 0.016, "t": 164000, "l": 173, "m": 1.74 }, "breakdown": [...] }
POST /api/v1/analyze/parallel-safety
Body: { "config": <YAML string or dict> }
Returns: { "safe": true/false, "conflicts": [...] }
POST /api/v1/analyze/full
Body: { "config": <YAML string or dict>, "framework": "auto" }
Returns: { "lint": {...}, "types": {...}, "cost": {...}, "parallel": {...} }
These endpoints power the GitHub Action (calls /api/v1/analyze/full) and can be used by any HTTP client.
| Mode | Effort | User Reach | Dependencies |
|---|---|---|---|
| MCP Server | ~300 LOC, 1-2 days | Claude Code, Cursor, Windsurf, all MCP clients | lint_config(), compute_grade(), type_check() (all exist) |
| GitHub Action | ~200 LOC, 1 day | All GitHub projects | CLI lambdagent lint (exists) |
| REST API endpoints | ~200 LOC, 1 day | Any HTTP client | Existing FastAPI app |
| Python middleware | ~800 LOC/framework, 1 week | LangChain/CrewAI/AutoGen users | Framework extractors (new) |
| VS Code extension | ~500 LOC, 2-3 days | VS Code users | CLI (exists) |
Recommended order: MCP Server → GitHub Action → REST endpoints → Python middleware → VS Code extension.
lambdagent # Core DSL + compiler + lint + type checker
lambdagent-mcp-server # MCP Server (standalone, no framework deps)
lambdagent-guard # Python middleware for LangChain/CrewAI/AutoGen
GitHub Marketplace # lambdagent/agent-lint-action (GitHub Action)
VS Code Marketplace # lambdagent.agent-lint (VS Code extension)
Docker Hub # lambdagent/analyzer (for CI/CD)
# MCP Server (for AI IDEs)
uvx lambdagent-mcp-server
# CLI tool (for terminal users)
pip install lambdagent
lambdagent lint agents/
# Python middleware (for framework users)
pip install lambdagent-guard
# Then: from lambdagent_guard import guard_langchain
# GitHub Action (for CI/CD)
# Add .github/workflows/agent-lint.yml (see Section 2.2)
# Docker (for any environment)
docker run lambdagent/analyzer lint /configs/
Evidence from documented GitHub issues (see REAL_WORLD_DEFECTS.md):
| Integration Mode | Example Bug Caught | Real Issue | Savings |
|---|---|---|---|
| MCP Server | Developer asks "check my agent config" → type mismatch found | LangChain #10997 | Prevents runtime crash |
| GitHub Action | PR adds agent with maxSteps=200 → cost warning blocks merge | Claude Code #38029 | Prevents $342 session |
| Python middleware | guard detects 3 identical states → kills loop | AutoGen #108 | Prevents token burn |
| VS Code extension | Inline warning on save: "no terminate tool" | CrewAI #737 | Prevents infinite loop |
| REST API | CI pipeline calls /analyze/full on every deploy | Claude Code #34629 | Catches 10-20x cost anomaly |
The theoretical work (three papers, λA calculus, type system, CEK machine) is the engine. The integration modes are the steering wheel. Without integration, the engine sits in a lab. With integration, every LangChain/CrewAI/AutoGen developer gets a safety net — without changing a single line of their agent code.
The MCP Server alone, at ~300 lines of code, puts the full power of the λA type system, 26 lint rules, graded cost prediction, and parallel safety verification into every AI IDE on the market. That is the highest-leverage implementation task in the entire project.