Skip to content
Closed
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
6 changes: 3 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ timeline
- [ ] **Node-Based Architecture (Agent as a node)**
- [x] Object schema definition
- [x] Node wrapper SDK
- [ ] Shared memory access
- [ ] Default monitoring hooks
- [ ] Tool access layer
- [x] Shared memory access
- [x] Default monitoring hooks
- [x] Tool access layer
- [x] LLM integration layer (Natively supports all mainstream LLMs through LiteLLM)
- [x] Anthropic
- [x] OpenAI
Expand Down
Binary file added core/.coverage
Binary file not shown.
63 changes: 63 additions & 0 deletions core/benchmark_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import asyncio
import shutil
import tempfile
import time

from framework.schemas.run import Run
from framework.storage.concurrent import ConcurrentStorage


async def measure_ops(storage: ConcurrentStorage, num_ops: int, concurrency: int):
"""Run concurrent save/load operations."""

async def worker(worker_id):
timings = []
for i in range(num_ops // concurrency):
run_id = f"bench_{worker_id}_{i}"
run = Run(id=run_id, goal_id="bench", input_data={"test": i})

start = time.perf_counter()
await storage.save_run(run)
save_dur = time.perf_counter() - start

start = time.perf_counter()
await storage.load_run(run_id)
load_dur = time.perf_counter() - start

timings.append(save_dur + load_dur)
return timings

start_total = time.perf_counter()
tasks = [worker(i) for i in range(concurrency)]
results = await asyncio.gather(*tasks)
total_dur = time.perf_counter() - start_total

flat_timings = [t for r in results for t in r]
avg_latency = sum(flat_timings) / len(flat_timings)
ops_sec = num_ops / total_dur

return ops_sec, avg_latency


async def main():
tmp_dir = tempfile.mkdtemp()
try:
print(f"Benchmarking ConcurrentStorage in {tmp_dir}")
storage = ConcurrentStorage(tmp_dir, batch_interval=0.05)
await storage.start()

# Warmup
await measure_ops(storage, 100, 10)

print("\nRunning Benchmark (1000 ops, 50 concurrency)...")
ops, lat = await measure_ops(storage, 1000, 50)
print(f"Throughput: {ops:.2f} OPS")
print(f"Avg Latency: {lat * 1000:.2f} ms")

await storage.stop()
finally:
shutil.rmtree(tmp_dir)


if __name__ == "__main__":
asyncio.run(main())
Binary file added core/core_collection_error.txt
Binary file not shown.
100 changes: 100 additions & 0 deletions core/core_collection_error_ascii.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<Class TestRuntimeMetrics>
<Function test_success_rate>
<Function test_success_rate_zero_decisions>
<Class TestRun>
<Function test_duration_ms>
<Function test_add_decision>
<Function test_record_outcome>
<Function test_add_problem>
<Function test_complete>
<Class TestRunSummary>
<Function test_from_run_basic>
<Function test_from_run_with_decisions>
<Function test_from_run_with_problems>
<Module test_runtime.py>
<Class TestRuntimeBasics>
<Function test_start_and_end_run>
<Function test_end_without_start_is_graceful>
<Function test_run_saved_on_end>
<Class TestDecisionRecording>
<Function test_basic_decision>
<Function test_decision_without_run_is_graceful>
<Function test_decision_with_node_context>
<Function test_decision_type>
<Class TestOutcomeRecording>
<Function test_record_successful_outcome>
<Function test_record_failed_outcome>
<Function test_metrics_updated_on_outcome>
<Class TestProblemReporting>
<Function test_report_problem>
<Function test_problem_linked_to_decision>
<Class TestConvenienceMethods>
<Function test_quick_decision>
<Function test_decide_and_execute_success>
<Function test_decide_and_execute_failure>
<Class TestNarrativeGeneration>
<Function test_default_narrative_success>
<Function test_default_narrative_failure>
<Module test_testing_framework.py>
<Class TestTestCaseSchema>
<Function test_create_test>
<Function test_approve_test>
<Function test_modify_test>
<Function test_reject_test>
<Function test_record_result>
<Class TestTestResultSchema>
<Function test_create_passed_result>
<Function test_create_failed_result>
<Function test_summary_dict>
<Class TestTestSuiteResult>
<Function test_suite_result_properties>
<Function test_get_results_by_category>
<Class TestTestStorage>
<Function test_save_and_load_test>
<Function test_delete_test>
<Function test_get_tests_by_goal>
<Function test_get_approved_tests>
<Function test_save_and_load_result>
<Function test_result_history>
<Function test_get_stats>
<Class TestErrorCategorizer>
<Function test_categorize_passed>
<Function test_categorize_logic_error>
<Function test_categorize_implementation_error>
<Function test_categorize_edge_case>
<Function test_categorize_from_stack_trace>
<Function test_get_fix_suggestion>
<Function test_get_iteration_guidance>
<Class TestDebugTool>
<Function test_analyze_missing_test>
<Function test_analyze_with_result>

=================================== ERRORS ====================================
______________________ ERROR collecting test_output.txt _______________________
C:\Python313\Lib\pathlib\_local.py:546: in read_text
return PathBase.read_text(self, encoding, errors, newline)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Python313\Lib\pathlib\_abc.py:633: in read_text
return f.read()
^^^^^^^^
<frozen codecs>:325: in decode
???
E UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
============================== warnings summary ===============================
framework\graph\safe_eval.py:79
D:\my-dev-knowledge-base\aden-hive\core\framework\graph\safe_eval.py:79: DeprecationWarning: ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead
def visit_Num(self, node: ast.Num) -> Any:

framework\graph\safe_eval.py:82
D:\my-dev-knowledge-base\aden-hive\core\framework\graph\safe_eval.py:82: DeprecationWarning: ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead
def visit_Str(self, node: ast.Str) -> Any:

framework\graph\safe_eval.py:85
D:\my-dev-knowledge-base\aden-hive\core\framework\graph\safe_eval.py:85: DeprecationWarning: ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead
def visit_NameConstant(self, node: ast.NameConstant) -> Any:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ===========================
ERROR test_output.txt - UnicodeDecodeError: 'utf-8' codec can't decode byte 0...
!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
==================== 282 tests collected, 1 error in 7.14s ====================
72 changes: 69 additions & 3 deletions core/framework/graph/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ def __init__(
cleansing_config: CleansingConfig | None = None,
enable_parallel_execution: bool = True,
parallel_config: ParallelExecutionConfig | None = None,
state_manager: Any = None,
execution_id: str | None = None,
stream_id: str | None = None,
isolation_level: Any = None,
event_bus: Any = None,
tool_registry: Any = None,
):
"""
Initialize the executor.
Expand All @@ -125,12 +131,32 @@ def __init__(
self.runtime = runtime
self.llm = llm
self.tools = tools or []
self.tool_executor = tool_executor
self._tool_executor = tool_executor
self._tool_registry = tool_registry
self.node_registry = node_registry or {}
self.approval_callback = approval_callback
self.validator = OutputValidator()
self.logger = logging.getLogger(__name__)

@property
def tool_executor(self) -> Any:
"""Get the current tool executor."""
return self._tool_executor

# If registry is provided, use it to manage tools and executor
if self._tool_registry:
# Sync tools from registry
reg_tools = self._tool_registry.get_tools()
for t_name, t_spec in reg_tools.items():
if t_name not in [t.name for t in self.tools]:
self.tools.append(t_spec)

# Create ToolExecutor if not provided
if not self._tool_executor:
from framework.tools.executor import ToolExecutor
self._tool_executor_service = ToolExecutor(self._tool_registry, self.runtime)
self._tool_executor = self._tool_executor_service.execute

# Initialize output cleaner
self.cleansing_config = cleansing_config or CleansingConfig()
self.output_cleaner = OutputCleaner(
Expand All @@ -142,6 +168,13 @@ def __init__(
self.enable_parallel_execution = enable_parallel_execution
self._parallel_config = parallel_config or ParallelExecutionConfig()

# Shared State Management
self._state_manager = state_manager
self._execution_id = execution_id
self._stream_id = stream_id
self._isolation_level = isolation_level
self._event_bus = event_bus

def _validate_tools(self, graph: GraphSpec) -> list[str]:
"""
Validate that all tools declared by nodes are available.
Expand Down Expand Up @@ -207,7 +240,13 @@ async def execute(
)

# Initialize execution state
memory = SharedMemory()
from framework.runtime.shared_state import IsolationLevel
memory = SharedMemory(
_manager=self._state_manager,
_execution_id=self._execution_id,
_stream_id=self._stream_id,
_isolation=self._isolation_level or IsolationLevel.ISOLATED
)

# Restore session state if provided
if session_state and "memory" in session_state:
Expand Down Expand Up @@ -308,8 +347,35 @@ async def execute(

# Execute node
self.logger.info(" Executing...")

# Emit node started event
if self._event_bus and self._stream_id and self._execution_id:
asyncio.create_task(
self._event_bus.emit_node_started(
stream_id=self._stream_id,
execution_id=self._execution_id,
node_id=node_spec.id,
node_name=node_spec.name,
node_type=node_spec.node_type,
)
)

result = await node_impl.execute(ctx)

# Emit node completed event
if self._event_bus and self._stream_id and self._execution_id:
asyncio.create_task(
self._event_bus.emit_node_completed(
stream_id=self._stream_id,
execution_id=self._execution_id,
node_id=node_spec.id,
success=result.success,
tokens_used=result.tokens_used,
latency_ms=result.latency_ms,
error=result.error,
)
)

if result.success:
# Validate output before accepting it
if result.output and node_spec.output_keys:
Expand Down Expand Up @@ -618,7 +684,7 @@ def _get_node_implementation(
"Either add tools to the node or change type to 'llm_generate'."
)
return LLMNode(
tool_executor=self.tool_executor,
tool_executor=self._tool_executor,
require_tools=True,
cleanup_llm_model=cleanup_llm_model,
)
Expand Down
2 changes: 1 addition & 1 deletion core/framework/graph/hitl.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def parse_response(

client = anthropic.Anthropic(api_key=api_key)
message = client.messages.create(
model="claude-3-5-haiku-20241022",
model="claude-3-5-haiku-latest",
max_tokens=500,
messages=[{"role": "user", "content": prompt}],
)
Expand Down
Loading
Loading