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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,22 @@ venv.bak/

# Local models and benchmark artifacts (never publish)
/artifacts/
/output/
*.bin
*.safetensors
*.gguf
*.ggml
*.moe

# Runtime databases and secret material (never commit or publish)
*.sqlite
*.sqlite3
*.sqlite3-shm
*.sqlite3-wal
vault.key
vault.aesgcm
*.private.pem

# Logs
*.log
logs/
Expand All @@ -113,7 +123,7 @@ omlx/_engine_commits.json
omlx/_build_info.py

# Tailwind CSS standalone CLI binary (download on demand via build_css.py)
omlx/admin/tailwindcss-*
ai2apps/web/tailwindcss-*

# Git worktrees
.worktrees/
Expand Down
8 changes: 8 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,12 @@
global-exclude *.so
global-exclude *.dylib
global-exclude *.metallib
global-exclude *.private.pem
global-exclude vault.key
global-exclude vault.aesgcm
global-exclude *.sqlite
global-exclude *.sqlite3
global-exclude *.sqlite3-shm
global-exclude *.sqlite3-wal
prune artifacts
prune output
2 changes: 1 addition & 1 deletion ai2apps/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""AI2Apps product version, independent from the embedded oMLX runtime."""

__version__ = "0.1.0.dev1"
__version__ = "0.1.0.dev2"
58 changes: 58 additions & 0 deletions ai2apps/agents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Asynchronous Agent Runtime public contracts."""

from .delegation import install_delegation_service
from .general import GeneralAgentExecutor, install_general_agent
from .models import (
AgentAction,
AgentDefinitionRecord,
AgentDefinitionStatus,
AgentExecutionContext,
AgentRunRecord,
AgentRunStatus,
AgentRuntimeError,
CompleteAction,
ContinueAction,
FailAction,
InteractionAction,
InteractionKind,
InteractionRecord,
InteractionStatus,
ModelCallAction,
RunStepRecord,
RunStepStatus,
StatusAction,
StatusLineRecord,
ToolCallAction,
)
from .repository import AgentRepository
from .runtime import AgentRuntime, diagnostic_executor, install_diagnostic_agent

__all__ = [
"AgentAction",
"AgentDefinitionRecord",
"AgentDefinitionStatus",
"AgentExecutionContext",
"AgentRepository",
"AgentRunRecord",
"AgentRunStatus",
"AgentRuntime",
"AgentRuntimeError",
"CompleteAction",
"ContinueAction",
"FailAction",
"GeneralAgentExecutor",
"InteractionAction",
"InteractionKind",
"InteractionRecord",
"InteractionStatus",
"ModelCallAction",
"RunStepRecord",
"RunStepStatus",
"StatusAction",
"StatusLineRecord",
"ToolCallAction",
"diagnostic_executor",
"install_diagnostic_agent",
"install_general_agent",
"install_delegation_service",
]
164 changes: 164 additions & 0 deletions ai2apps/agents/delegation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""Built-in Agent-to-Agent delegation Service and Tool."""

from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from ai2apps.services import (
ServiceInstanceStatus,
ServiceRegistry,
ServiceRepository,
ServiceRuntimeMode,
ToolCallContext,
ToolProviderError,
)

from .models import AgentRunStatus
from .repository import AgentRepository
from .runtime import AgentRuntime


def install_delegation_service(
agents: AgentRepository,
runtime: AgentRuntime,
services: ServiceRepository,
registry: ServiceRegistry,
) -> None:
"""Expose durable child AgentRuns as the ``agent.delegate`` Tool."""

service = services.ensure_service(
service_key="ai2apps.agent-runtime",
package_id="ai2apps.agent-runtime",
package_version="1.0.0",
display_name="Agent Runtime",
runtime_mode=ServiceRuntimeMode.IN_PROCESS,
capabilities=("agent.delegation",),
)
instance = services.ensure_instance(
service_id=service.id,
provider_key="builtin:agent-runtime",
status=ServiceInstanceStatus.RUNNING,
endpoint="/v1/platform/tools/agent.delegate/invoke",
health={"status": "ok", "max_depth": agents.MAX_DELEGATION_DEPTH},
)
services.ensure_tool(
service_id=service.id,
qualified_name="agent.delegate",
display_name="Delegate to Agent",
description=(
"Run a bounded child Agent in the current Session and return its "
"durable result to the parent Agent."
),
input_schema={
"type": "object",
"properties": {
"agent": {"type": "string", "minLength": 1},
"task": {"type": "string", "minLength": 1, "maxLength": 32768},
"request_key": {
"type": "string",
"minLength": 1,
"maxLength": 128,
},
"parameters": {"type": "object"},
"context": {"type": "string", "maxLength": 16384},
"budget": {
"type": "object",
"properties": {
"max_steps": {"type": "integer", "minimum": 1, "maximum": 24},
"max_model_tokens": {
"type": "integer",
"minimum": 1,
"maximum": 100000,
},
"timeout_seconds": {
"type": "integer",
"minimum": 1,
"maximum": 900,
},
},
"additionalProperties": False,
},
},
"required": ["agent", "task", "request_key"],
"additionalProperties": False,
},
output_schema={"type": "object"},
effects=(),
timeout_ms=910_000,
)

async def delegate(
arguments: dict[str, Any], context: ToolCallContext
) -> dict[str, Any]:
if context.trace_id is None or context.session_id is None:
raise ToolProviderError("Delegation requires an Agent Run and Session")
parent = agents.get_run(context.trace_id)
if parent.session_id != context.session_id:
raise ToolProviderError("Delegation Session does not match parent Run")
request_key = arguments["request_key"]
child = agents.get_delegated_child(parent.id, request_key)
if child is None:
budget = dict(arguments.get("budget") or {})
delegation_context = {
"instructions": arguments.get("context", ""),
"parent_message_id": parent.input.get("message_id"),
}
child_input = {
"model": parent.input.get("model", ""),
"prompt": arguments["task"],
"parameters": dict(arguments.get("parameters") or {}),
"instructions": arguments.get("context", ""),
"invocation": {"source": "delegation"},
}
child, _ = agents.create_run(
session_id=parent.session_id,
agent_key=arguments["agent"],
input=child_input,
idempotency_key=f"delegate:{parent.id}:{request_key}",
priority=parent.priority,
trace_id=parent.id,
parent_run_id=parent.id,
delegation={
"request_key": request_key,
"task": arguments["task"],
"parameters": dict(arguments.get("parameters") or {}),
"context": delegation_context,
"budget": budget,
},
)
runtime.wake()
definition = agents.get_definition(child.agent_definition_id)
await context.report_progress(
f"Waiting for {definition.display_name}",
phase="waiting_subruns",
content={
"child_run_id": child.id,
"agent_key": definition.agent_key,
"depth": child.depth,
},
)
remaining = max(
0.1, (child.deadline_at - datetime.now(UTC)).total_seconds() + 1
)
child = await runtime.wait_for_terminal(child.id, timeout=remaining)
await context.report_progress(
f"{definition.display_name} {child.status.value}",
phase="subrun_completed",
content={"child_run_id": child.id, "status": child.status.value},
)
if child.status is not AgentRunStatus.COMPLETED:
raise ToolProviderError(
f"Delegated AgentRun {child.id} ended as {child.status.value}: "
f"{(child.error or {}).get('message', '')}"
)
return {
"child_run_id": child.id,
"agent_key": definition.agent_key,
"status": child.status.value,
"output": child.output or {},
}

registry.bind_tool(
"agent.delegate", provider_key=instance.provider_key, handler=delegate
)
Loading