Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "ruff check --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null; ruff format \"$CLAUDE_FILE_PATH\" 2>/dev/null; true"
}
]
}
]
}
}
18 changes: 18 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
This project uses ruff for Python linting and formatting.

Rules:
- Line length: 100 characters
- Python target: 3.11+
- Use double quotes for strings
- Sort imports with isort (ruff I rules): stdlib, third-party, first-party (framework), local
- Combine as-imports
- Use type hints on all function signatures
- Use `from __future__ import annotations` for modern type syntax
- Raise exceptions with `from` in except blocks (B904)
- No unused imports (F401), no unused variables (F841)
- Prefer list/dict/set comprehensions over map/filter (C4)

Run `make lint` to auto-fix, `make check` to verify without modifying files.
Run `make format` to apply ruff formatting.

The ruff config lives in core/pyproject.toml under [tool.ruff].
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.py]
indent_size = 4

[*.md]
trim_trailing_whitespace = false

Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ jobs:
pip install -e .
pip install -r requirements-dev.txt

- name: Run ruff
- name: Ruff lint
run: |
cd core
ruff check .
cd core && ruff check .
cd tools && ruff check .

- name: Ruff format
run: |
cd core && ruff format --check .
cd tools && ruff format --check .

test:
name: Test Python Framework
Expand Down
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
hooks:
- id: ruff
name: ruff lint (core)
args: [--fix]
files: ^core/
- id: ruff
name: ruff lint (tools)
args: [--fix]
files: ^tools/
- id: ruff-format
name: ruff format (core)
files: ^core/
- id: ruff-format
name: ruff format (tools)
files: ^tools/
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"charliermarsh.ruff",
"editorconfig.editorconfig",
"ms-python.python"
]
}
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.PHONY: lint format check test install-hooks help

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'

lint: ## Run ruff linter (with auto-fix)
cd core && ruff check --fix .
cd tools && ruff check --fix .

format: ## Run ruff formatter
cd core && ruff format .
cd tools && ruff format .

check: ## Run all checks without modifying files (CI-safe)
cd core && ruff check .
cd tools && ruff check .
cd core && ruff format --check .
cd tools && ruff format --check .

test: ## Run all tests
cd core && python -m pytest tests/ -v

install-hooks: ## Install pre-commit hooks
pip install pre-commit
pre-commit install
20 changes: 10 additions & 10 deletions core/examples/manual_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ def greet(name: str) -> str:
"""Generate a simple greeting."""
return f"Hello, {name}!"


def uppercase(greeting: str) -> str:
"""Convert text to uppercase."""
return greeting.upper()


async def main():
print("🚀 Setting up Manual Agent...")

Expand All @@ -42,9 +44,9 @@ async def main():
"id": "greeting_generated",
"description": "Greeting produced",
"metric": "custom",
"target": "any"
"target": "any",
}
]
],
)

# 3. Define Nodes
Expand All @@ -56,7 +58,7 @@ async def main():
node_type="function",
function="greet", # Matches the registered function name
input_keys=["name"],
output_keys=["greeting"]
output_keys=["greeting"],
)

node2 = NodeSpec(
Expand All @@ -66,7 +68,7 @@ async def main():
node_type="function",
function="uppercase",
input_keys=["greeting"],
output_keys=["final_greeting"]
output_keys=["final_greeting"],
)

# 4. Define Edges
Expand All @@ -75,7 +77,7 @@ async def main():
id="greet-to-upper",
source="greeter",
target="uppercaser",
condition=EdgeCondition.ON_SUCCESS
condition=EdgeCondition.ON_SUCCESS,
)

# 5. Create Graph
Expand All @@ -92,6 +94,7 @@ async def main():
# 6. Initialize Runtime & Executor
# Runtime handles state/memory; Executor runs the graph
from pathlib import Path

runtime = Runtime(storage_path=Path("./agent_logs"))
executor = GraphExecutor(runtime=runtime)

Expand All @@ -103,11 +106,7 @@ async def main():
# 8. Execute Agent
print("▶ Executing agent with input: name='Alice'...")

result = await executor.execute(
graph=graph,
goal=goal,
input_data={"name": "Alice"}
)
result = await executor.execute(graph=graph, goal=goal, input_data={"name": "Alice"})

# 9. Verify Results
if result.success:
Expand All @@ -117,6 +116,7 @@ async def main():
else:
print(f"\n❌ Failed: {result.error}")


if __name__ == "__main__":
# Optional: Enable logging to see internal decision flow
# logging.basicConfig(level=logging.INFO)
Expand Down
7 changes: 2 additions & 5 deletions core/framework/builder/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,7 @@ def suggest_improvements(self, goal_id: str) -> list[dict[str, Any]]:
"target": goal_id,
"reason": f"Goal success rate is only {patterns.success_rate:.1%}",
"recommendation": (
"Consider restructuring the agent graph "
"or improving goal definition"
"Consider restructuring the agent graph or improving goal definition"
),
"priority": "high",
}
Expand Down Expand Up @@ -428,9 +427,7 @@ def _generate_suggestions(
# Check for constraint issues
if decision.active_constraints:
constraints = ", ".join(decision.active_constraints)
suggestions.append(
f"Review constraints: {constraints} - may be too restrictive"
)
suggestions.append(f"Review constraints: {constraints} - may be too restrictive")

# Check for reported problems with suggestions
for problem in run.problems:
Expand Down
4 changes: 1 addition & 3 deletions core/framework/graph/code_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ def validate(self, code: str) -> list[str]:
# Check for blocked node types
if type(node) in self.blocked_nodes:
lineno = getattr(node, "lineno", "?")
issues.append(
f"Blocked operation: {type(node).__name__} at line {lineno}"
)
issues.append(f"Blocked operation: {type(node).__name__} at line {lineno}")

# Check for dangerous attribute access
if isinstance(node, ast.Attribute):
Expand Down
7 changes: 2 additions & 5 deletions core/framework/graph/edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,7 @@ class GraphSpec(BaseModel):
async_entry_points: list[AsyncEntryPointSpec] = Field(
default_factory=list,
description=(
"Asynchronous entry points for concurrent execution streams "
"(used with AgentRuntime)"
"Asynchronous entry points for concurrent execution streams (used with AgentRuntime)"
),
)
terminal_nodes: list[str] = Field(
Expand Down Expand Up @@ -468,9 +467,7 @@ def detect_fan_out_nodes(self) -> dict[str, list[str]]:
for node in self.nodes:
outgoing = self.get_outgoing_edges(node.id)
# Fan-out: multiple edges with ON_SUCCESS condition
success_edges = [
e for e in outgoing if e.condition == EdgeCondition.ON_SUCCESS
]
success_edges = [e for e in outgoing if e.condition == EdgeCondition.ON_SUCCESS]
if len(success_edges) > 1:
fan_outs[node.id] = [e.target for e in success_edges]
return fan_outs
Expand Down
Loading
Loading