Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

260 lines
9.9 KiB
Python

#
# Copyright 2026 The Dapr Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import functools
import logging
from typing import Any, Optional
from pydantic import BaseModel, Field
from dapr_agents.tool.workflow.tool_context import WorkflowContextInjectedTool
from dapr_agents.workflow.utils.names import sanitize_agent_name
logger = logging.getLogger(__name__)
AGENT_WORKFLOW_SUFFIX = "_agent_workflow" # kept for backward compat
def agent_workflow_id(
agent_name: str,
framework: Optional[str] = None,
workflow_name: Optional[str] = None,
) -> str:
"""
Return the Dapr-registered workflow name for an agent.
Priority order:
1. If workflow_name is provided, use it directly
2. If agent_name is already a full workflow name (starts with 'dapr.' and ends
with '.workflow'), return it as-is
3. If framework is provided and not "Dapr Agents", use 'dapr.{framework}.{agent_name}.workflow'
4. Otherwise, use the standard format: 'dapr.agents.{agent_name}.workflow'
Agent names are sanitized to comply with OpenAI's name requirements (no spaces,
<, |, \\, /, >) before constructing workflow names.
This supports:
- Other agentic frameworks (e.g., "dapr.openai.catering-coordinator.workflow")
- Dapr Agents framework (e.g., "dapr.agents.catering-coordinator.workflow")
- Explicit workflow names passed directly
Args:
agent_name: The agent name (e.g., "catering-coordinator", "Samwise Gamgee")
framework: Optional framework name (e.g., "openai", "pydantic_ai", "langgraph", "crewai").
If None or "Dapr Agents", uses the standard dapr.agents.* format.
workflow_name: Optional explicit workflow name. If provided, this takes precedence
over all other parameters.
Returns:
The Dapr-registered workflow name with sanitized agent name.
"""
from dapr_agents.tool.utils.function_calling import sanitize_openai_tool_name
# Priority 1: Explicit workflow name
if workflow_name:
return workflow_name
# Priority 2: Already a full workflow name
if agent_name.startswith("dapr.") and agent_name.endswith(".workflow"):
return agent_name
# Sanitize agent name to comply with OpenAI requirements
# This ensures workflow names don't contain invalid characters
sanitized_agent_name = sanitize_openai_tool_name(agent_name)
# Priority 3: Use framework if provided and not "Dapr Agents"
if framework and framework != "Dapr Agents":
# Normalize framework name (replace spaces/underscores with dots if needed)
normalized_framework = framework.replace(" ", "-").replace("_", "-").lower()
return f"dapr.{normalized_framework}.{sanitized_agent_name}.workflow"
# Priority 4: Default to dapr.agents.* format
return f"dapr.agents.{sanitized_agent_name}.workflow"
class AgentTaskArgs(BaseModel):
"""Arguments accepted by AgentWorkflowTool."""
task: str = Field(
...,
description="The instruction or task to send to the agent.",
)
class AgentWorkflowTool(WorkflowContextInjectedTool):
"""
A WorkflowContextInjectedTool that invokes another DurableAgent as a
synchronous child workflow.
The parent agent's LLM calls this tool with a ``task`` string; the tool
schedules a child workflow named ``{target_agent_name}_agent_workflow``
(optionally on a different Dapr app via ``target_app_id``) and waits for
the child agent's final response.
The workflow context (``ctx``) is injected by the dispatch loop and is
never exposed in the LLM provider's function-call schema.
"""
target_agent_name: str
target_app_id: Optional[str] = None
"""
Dapr app-id of the app hosting the target agent.
``None`` means the target agent is in the same Dapr app (in-process);
cross-app invocation requires appropriate Dapr access-control policies.
"""
def _schedule_agent_workflow(
ctx: Any,
task: str,
agent_name: str,
agent_type: str = "agents",
target_app_id: Optional[str] = None,
_source_agent: Optional[str] = None,
workflow_name: Optional[str] = None,
framework: Optional[str] = None,
_child_instance_id: Optional[str] = None,
) -> Any:
"""
Schedule a child workflow for a named agent.
This is intentionally a *sync* function that returns a Dapr workflow Task.
The parent workflow yields on it::
result = yield tool_obj(ctx=ctx, task="...")
Args:
ctx: Dapr workflow context supplied by the dispatch loop.
task: The instruction to forward to the child agent.
agent_name: Registered name of the target agent.
agent_type: Framework/type prefix for the workflow name (e.g. "strands", "agents").
target_app_id: Dapr app-id for cross-app routing; ``None`` for in-process.
_source_agent: Name of the calling agent; forwarded in ``_message_metadata``
so the child agent labels the user message as "on behalf of".
workflow_name: Optional explicit workflow name. Takes precedence over framework.
framework: Optional framework name for constructing workflow name.
_child_instance_id: Explicit instance ID to assign to the child workflow.
When provided, this exact ID is used so callers can record it in
``tool_history`` before the yield completes.
"""
input_payload: dict = {"task": task}
if _source_agent:
input_payload["_message_metadata"] = {"source": _source_agent}
workflow_id = agent_workflow_id(
agent_name, framework=framework, workflow_name=workflow_name
)
call_kwargs: dict = {
"workflow": workflow_id,
"input": input_payload,
}
if target_app_id:
call_kwargs["app_id"] = target_app_id
if _child_instance_id:
call_kwargs["instance_id"] = _child_instance_id
logger.debug(
"Scheduling child workflow '%s' app_id=%r task=%r",
workflow_id,
target_app_id,
task,
)
return ctx.call_child_workflow(**call_kwargs)
def agent_to_tool(
agent_name: str,
description: str,
*,
agent_type: str = "agents",
target_app_id: Optional[str] = None,
workflow_name: Optional[str] = None,
framework: Optional[str] = None,
) -> AgentWorkflowTool:
"""
Create an AgentWorkflowTool for a named agent.
This is the explicit factory for cases where you know the agent name and
(optionally) its Dapr app ID — no registry lookup is performed. Use it
for cross-app agents where you have a known ``target_app_id``, or for
advanced scenarios where you want full control.
For registry-based auto-discovery, simply register both agents in the
same registry; the parent agent's ``load_tools`` activity will pick up
all registry peers automatically at workflow start.
Args:
agent_name: The name of the target agent (e.g., ``"catering-coordinator"``).
This value is used to derive the tool name exposed to the LLM; the
underlying :class:`AgentTool` normalizes it (title-casing, removing
spaces and underscores), so e.g. ``"my agent"`` becomes ``"MyAgent"``.
It should correspond to the agent's registered name under this
normalization, rather than needing to match character-for-character.
description: Human-readable description shown to the LLM in the tool
schema (e.g. ``"Ring-bearer. Goal: carry the One Ring to Mordor."``).
agent_type: Framework/type prefix for the workflow name (e.g. "strands",
"agents"). Defaults to "agents" for backward compatibility.
target_app_id: Dapr app-id of the app hosting the target agent.
Pass ``None`` (default) for in-process invocation where both
agents are registered in the same Dapr application.
workflow_name: Optional explicit workflow name (e.g.,
``"dapr.openai.catering-coordinator.workflow"``). If provided, this
takes precedence over framework-based construction.
framework: Optional framework name (e.g., ``"openai"``, ``"pydantic_ai"``).
Used to construct workflow name as ``dapr.{framework}.{agent_name}.workflow``.
If None or "Dapr Agents", uses ``dapr.agents.{agent_name}.workflow``.
Returns:
AgentWorkflowTool ready to be registered in a DurableAgent's toolset.
Example — explicit cross-app::
from dapr_agents.tool.workflow import agent_to_tool
sam_tool = agent_to_tool(
"sam",
"Logistics & Support. Goal: Manage provisions and supplies.",
target_app_id="sam-app",
)
frodo = DurableAgent(name="frodo", tools=[sam_tool], ...)
Example — with framework::
catering_tool = agent_to_tool(
"catering-coordinator",
"Coordinates catering services.",
framework="openai",
)
# This will call workflow: dapr.openai.CateringCoordinator.workflow
"""
executor = functools.partial(
_schedule_agent_workflow,
agent_name=agent_name,
agent_type=agent_type,
target_app_id=target_app_id,
workflow_name=workflow_name,
framework=framework,
)
setattr(
executor, "__name__", agent_name
) # partial has no __name__; AgentTool validator reads it
return AgentWorkflowTool(
name=agent_name,
description=description,
func=executor,
args_model=AgentTaskArgs,
target_agent_name=agent_name,
target_app_id=target_app_id,
)