Skip to content

naqsha.orchestration

Team topology and Tool-Based Delegation

naqsha.orchestration

Team orchestration and Tool-Based Delegation.

MEMORY_DECORATED_TOOL_NAMES module-attribute

MEMORY_DECORATED_TOOL_NAMES = frozenset(
    {"memory_schema", "list_memory_tables"}
)

AgentRoleConfig dataclass

Source code in src/naqsha/orchestration/topology.py
@dataclass(frozen=True)
class AgentRoleConfig:
    agent_id: str
    role: str
    model_adapter: str
    tools: frozenset[str]
    budgets: BudgetLimits
    max_retries: int = 3
    approval_required_tiers: frozenset[RiskTier] | None = None
    fake_model_messages: tuple[dict[str, Any], ...] | None = None
    instructions: str = ""
    openai_compat: dict[str, Any] | None = None
    anthropic: dict[str, Any] | None = None
    gemini: dict[str, Any] | None = None
    ollama: dict[str, Any] | None = None

agent_id instance-attribute

agent_id: str

role instance-attribute

role: str

model_adapter instance-attribute

model_adapter: str

tools instance-attribute

tools: frozenset[str]

budgets instance-attribute

budgets: BudgetLimits

max_retries class-attribute instance-attribute

max_retries: int = 3

approval_required_tiers class-attribute instance-attribute

approval_required_tiers: frozenset[RiskTier] | None = None

fake_model_messages class-attribute instance-attribute

fake_model_messages: tuple[dict[str, Any], ...] | None = (
    None
)

instructions class-attribute instance-attribute

instructions: str = ''

openai_compat class-attribute instance-attribute

openai_compat: dict[str, Any] | None = None

anthropic class-attribute instance-attribute

anthropic: dict[str, Any] | None = None

gemini class-attribute instance-attribute

gemini: dict[str, Any] | None = None

ollama class-attribute instance-attribute

ollama: dict[str, Any] | None = None

TeamTopology dataclass

Source code in src/naqsha/orchestration/topology.py
@dataclass(frozen=True)
class TeamTopology:
    workspace: WorkspaceSection
    agents: dict[str, AgentRoleConfig]
    memory: TeamMemoryConfig
    reflection: ReflectionSection

    @property
    def orchestrator_id(self) -> str:
        return self.workspace.orchestrator

    def delegate_tool_name_for_worker(self, worker_id: str) -> str:
        return _safe_delegate_tool_name(worker_id)

    def worker_agents(self) -> dict[str, AgentRoleConfig]:
        return {
            aid: cfg
            for aid, cfg in self.agents.items()
            if aid != self.orchestrator_id
        }

workspace instance-attribute

workspace: WorkspaceSection

agents instance-attribute

agents: dict[str, AgentRoleConfig]

memory instance-attribute

memory: TeamMemoryConfig

reflection instance-attribute

reflection: ReflectionSection

orchestrator_id property

orchestrator_id: str

delegate_tool_name_for_worker

delegate_tool_name_for_worker(worker_id: str) -> str
Source code in src/naqsha/orchestration/topology.py
def delegate_tool_name_for_worker(self, worker_id: str) -> str:
    return _safe_delegate_tool_name(worker_id)

worker_agents

worker_agents() -> dict[str, AgentRoleConfig]
Source code in src/naqsha/orchestration/topology.py
def worker_agents(self) -> dict[str, AgentRoleConfig]:
    return {
        aid: cfg
        for aid, cfg in self.agents.items()
        if aid != self.orchestrator_id
    }

WorkspaceSection dataclass

Source code in src/naqsha/orchestration/topology.py
@dataclass(frozen=True)
class WorkspaceSection:
    name: str
    orchestrator: str
    description: str = ""
    trace_dir: Path = Path(".naqsha/traces")
    auto_approve: bool = False
    approval_required_tiers: frozenset[RiskTier] = frozenset(
        {RiskTier.WRITE, RiskTier.HIGH}
    )
    sanitizer_max_chars: int = 4000

    def resolve_trace_dir(self, base_dir: Path) -> Path:
        p = self.trace_dir
        if not p.is_absolute():
            p = (base_dir / p).resolve()
        else:
            p = p.resolve()
        return p

name instance-attribute

name: str

orchestrator instance-attribute

orchestrator: str

description class-attribute instance-attribute

description: str = ''

trace_dir class-attribute instance-attribute

trace_dir: Path = Path('.naqsha/traces')

auto_approve class-attribute instance-attribute

auto_approve: bool = False

approval_required_tiers class-attribute instance-attribute

approval_required_tiers: frozenset[RiskTier] = frozenset(
    {WRITE, HIGH}
)

sanitizer_max_chars class-attribute instance-attribute

sanitizer_max_chars: int = 4000

resolve_trace_dir

resolve_trace_dir(base_dir: Path) -> Path
Source code in src/naqsha/orchestration/topology.py
def resolve_trace_dir(self, base_dir: Path) -> Path:
    p = self.trace_dir
    if not p.is_absolute():
        p = (base_dir / p).resolve()
    else:
        p = p.resolve()
    return p

build_delegate_tool

build_delegate_tool(
    worker: AgentRoleConfig,
    topology: TeamTopology,
    workspace_path: Path,
    memory_engine: DynamicMemoryEngine,
    parent_runtime_slot: list[CoreRuntime | None],
    event_bus: RuntimeEventBus | None,
) -> FunctionTool
Source code in src/naqsha/orchestration/delegation.py
def build_delegate_tool(
    worker: AgentRoleConfig,
    topology: TeamTopology,
    workspace_path: Path,
    memory_engine: DynamicMemoryEngine,
    parent_runtime_slot: list[CoreRuntime | None],
    event_bus: RuntimeEventBus | None,
) -> FunctionTool:
    tool_name = topology.delegate_tool_name_for_worker(worker.agent_id)

    def execute(arguments: dict[str, Any]) -> ToolObservation:
        return _run_delegation(
            arguments,
            worker=worker,
            topology=topology,
            workspace_path=workspace_path,
            memory_engine=memory_engine,
            parent_runtime_slot=parent_runtime_slot,
            event_bus=event_bus,
            tool_name=tool_name,
        )

    spec = ToolSpec(
        name=tool_name,
        description=(
            f"Delegate a task to worker agent {worker.agent_id!r}. "
            "Runs that agent's loop to completion and returns its final answer."
        ),
        parameters={
            "type": "object",
            "properties": {"task": {"type": "string", "description": "Task instructions."}},
            "required": ["task"],
            "additionalProperties": False,
        },
        risk_tier=RiskTier.WRITE,
        read_only=False,
    )
    return FunctionTool(spec, execute)

build_team_orchestrator_runtime

build_team_orchestrator_runtime(
    topology: TeamTopology,
    workspace_path: Path,
    *,
    event_bus: RuntimeEventBus | None = None,
    approve_prompt: bool = False,
    implicit_tool_approval: bool = False,
) -> CoreRuntime

Create the orchestrator CoreRuntime for a parsed TeamTopology.

Source code in src/naqsha/orchestration/team_runtime.py
def build_team_orchestrator_runtime(
    topology: TeamTopology,
    workspace_path: Path,
    *,
    event_bus: RuntimeEventBus | None = None,
    approve_prompt: bool = False,
    implicit_tool_approval: bool = False,
) -> CoreRuntime:
    """Create the orchestrator ``CoreRuntime`` for a parsed ``TeamTopology``."""

    workspace_path = workspace_path.resolve()
    engine = open_team_memory_engine(workspace_path, topology.memory)
    parent_slot: list[CoreRuntime | None] = [None]
    tools = build_orchestrator_tools(
        topology,
        workspace_path,
        engine,
        parent_slot,
        event_bus,
    )
    orch = topology.agents[topology.orchestrator_id]
    trace_dir = topology.workspace.resolve_trace_dir(workspace_path)
    profile = run_profile_for_topology_agent(
        orch,
        workspace=topology.workspace,
        trace_dir=trace_dir,
        tool_root=workspace_path,
        base_dir=workspace_path,
    )
    policy = tool_policy_for_topology_agent(
        orch,
        tools,
        workspace=topology.workspace,
    )
    gate: ApprovalGate
    if topology.workspace.auto_approve:
        gate = StaticApprovalGate(approved=True)
    elif implicit_tool_approval:
        gate = StaticApprovalGate(approved=True)
    elif approve_prompt:
        gate = InteractiveApprovalGate()
    else:
        gate = StaticApprovalGate(approved=False)
    config = RuntimeConfig(
        model=model_client_from_profile(profile),
        tools=tools,
        trace_store=JsonlTraceStore(trace_dir),
        policy=policy,
        budgets=orch.budgets,
        approval_gate=gate,
        sanitizer=ObservationSanitizer(max_chars=topology.workspace.sanitizer_max_chars),
        memory=None,
        memory_token_budget=512,
        shared_memory_scope=engine.get_shared_scope(),
        private_memory_scope=engine.get_private_scope(orch.agent_id),
        workspace_path=workspace_path,
        agent_id=orch.agent_id,
        trace_id=None,
        span_context=None,
        event_bus=event_bus,
        max_retries=orch.max_retries,
        agent_instructions=profile.instructions,
    )
    rt = CoreRuntime(config)
    parent_slot[0] = rt
    return rt

build_worker_runtime

build_worker_runtime(
    worker: AgentRoleConfig,
    *,
    topology: TeamTopology,
    workspace_path: Path,
    memory_engine: DynamicMemoryEngine,
    span_context: SpanContext,
    event_bus: RuntimeEventBus | None,
    approval_gate: ApprovalGate,
) -> CoreRuntime
Source code in src/naqsha/orchestration/team_runtime.py
def build_worker_runtime(
    worker: AgentRoleConfig,
    *,
    topology: TeamTopology,
    workspace_path: Path,
    memory_engine: DynamicMemoryEngine,
    span_context: SpanContext,
    event_bus: RuntimeEventBus | None,
    approval_gate: ApprovalGate,
) -> CoreRuntime:
    child_slot: list[CoreRuntime | None] = [None]
    tools = build_tools_for_agent(
        worker,
        workspace_path,
        memory_engine,
        lambda: child_slot[0],
    )
    trace_dir = topology.workspace.resolve_trace_dir(workspace_path)
    profile = run_profile_for_topology_agent(
        worker,
        workspace=topology.workspace,
        trace_dir=trace_dir,
        tool_root=workspace_path,
        base_dir=workspace_path,
    )
    policy = tool_policy_for_topology_agent(
        worker,
        tools,
        workspace=topology.workspace,
    )
    config = RuntimeConfig(
        model=model_client_from_profile(profile),
        tools=tools,
        trace_store=JsonlTraceStore(trace_dir),
        policy=policy,
        budgets=worker.budgets,
        approval_gate=approval_gate,
        sanitizer=ObservationSanitizer(max_chars=topology.workspace.sanitizer_max_chars),
        memory=None,
        memory_token_budget=512,
        shared_memory_scope=memory_engine.get_shared_scope(),
        private_memory_scope=memory_engine.get_private_scope(worker.agent_id),
        workspace_path=workspace_path.resolve(),
        agent_id=worker.agent_id,
        trace_id=span_context.trace_id,
        span_context=span_context,
        event_bus=event_bus,
        max_retries=worker.max_retries,
        agent_instructions=profile.instructions,
    )
    rt = CoreRuntime(config)
    child_slot[0] = rt
    return rt

run_profile_for_topology_agent

run_profile_for_topology_agent(
    agent: AgentRoleConfig,
    *,
    workspace: WorkspaceSection,
    trace_dir: Path,
    tool_root: Path,
    base_dir: Path,
) -> RunProfile
Source code in src/naqsha/orchestration/team_runtime.py
def run_profile_for_topology_agent(
    agent: AgentRoleConfig,
    *,
    workspace: WorkspaceSection,
    trace_dir: Path,
    tool_root: Path,
    base_dir: Path,
) -> RunProfile:
    tiers = agent.approval_required_tiers or workspace.approval_required_tiers
    data: dict[str, Any] = {
        "name": agent.agent_id,
        "model": agent.model_adapter,
        "trace_dir": str(trace_dir),
        "tool_root": str(tool_root),
        "memory_adapter": "none",
        "memory_token_budget": 512,
        "auto_approve": workspace.auto_approve,
        "sanitizer_max_chars": workspace.sanitizer_max_chars,
        "approval_required_tiers": sorted(t.value for t in tiers),
        "budgets": {
            "max_steps": agent.budgets.max_steps,
            "max_tool_calls": agent.budgets.max_tool_calls,
            "wall_clock_seconds": agent.budgets.wall_clock_seconds,
            "per_tool_seconds": agent.budgets.per_tool_seconds,
            "max_model_tokens": agent.budgets.max_model_tokens,
        },
    }
    if agent.fake_model_messages is not None:
        data["fake_model"] = {"messages": list(agent.fake_model_messages)}
    i = agent.instructions.strip()
    if i:
        data["instructions"] = i
    if agent.openai_compat is not None:
        data["openai_compat"] = agent.openai_compat
    if agent.anthropic is not None:
        data["anthropic"] = agent.anthropic
    if agent.gemini is not None:
        data["gemini"] = agent.gemini
    if agent.ollama is not None:
        data["ollama"] = agent.ollama

    names = sorted(agent.tools)
    delegate_prefix = "delegate_to_"

    parse_names = sorted(
        n
        for n in names
        if not n.startswith(delegate_prefix) and n not in MEMORY_DECORATED_TOOL_NAMES
    )
    if parse_names:
        data["allowed_tools"] = parse_names
    profile = parse_run_profile(data, base_dir=base_dir)
    if profile.allowed_tool_names != frozenset(agent.tools):
        profile = replace(profile, allowed_tool_names=frozenset(agent.tools))
    return profile

parse_team_topology

parse_team_topology(
    data: Mapping[str, Any], *, base_dir: Path
) -> TeamTopology

Validate a mapping (typically loaded from naqsha.toml) into a TeamTopology.

Source code in src/naqsha/orchestration/topology.py
def parse_team_topology(data: Mapping[str, Any], *, base_dir: Path) -> TeamTopology:
    """Validate a mapping (typically loaded from ``naqsha.toml``) into a ``TeamTopology``."""

    _ = base_dir  # reserved for relative-path resolution alongside caller workspace root

    allowed_top = {"workspace", "memory", "reflection", "agents"}
    extra = set(data) - allowed_top
    if extra:
        raise ProfileValidationError(f"Unknown top-level keys: {sorted(extra)}.")

    ws = data.get("workspace")
    if not isinstance(ws, Mapping):
        raise ProfileValidationError("'workspace' section is required.")

    name = _as_str(ws.get("name", "team"), "workspace.name")
    orchestrator = _as_str(ws.get("orchestrator"), "workspace.orchestrator")
    dval = ws.get("description", "")
    if dval is None or dval == "":
        description = ""
    elif isinstance(dval, str):
        description = dval.strip()
    else:
        raise ProfileValidationError("'workspace.description' must be a string.")
    trace_dir_raw = ws.get("trace_dir", ".naqsha/traces")
    trace_dir = Path(_as_str(trace_dir_raw, "workspace.trace_dir"))
    auto_approve = _as_bool(ws.get("auto_approve", False), "workspace.auto_approve")
    approval_required_tiers = _parse_risk_tiers(ws.get("approval_required_tiers"))
    sanitizer_max_chars = _as_int(
        ws.get("sanitizer_max_chars", 4000), "workspace.sanitizer_max_chars", minimum=1
    )

    ws_extra = set(ws) - {
        "name",
        "orchestrator",
        "description",
        "trace_dir",
        "auto_approve",
        "approval_required_tiers",
        "sanitizer_max_chars",
    }
    if ws_extra:
        raise ProfileValidationError(f"Unknown workspace keys: {sorted(ws_extra)}.")

    mem_blob = data.get("memory") or {}
    if not isinstance(mem_blob, Mapping):
        raise ProfileValidationError("'memory' must be a table.")
    mem_type = _as_str(mem_blob.get("type", "sqlite"), "memory.type").lower()
    db_path = Path(_as_str(mem_blob.get("db_path", ".naqsha/memory.db"), "memory.db_path"))
    embeddings = _as_bool(mem_blob.get("embeddings", False), "memory.embeddings")
    mem_extra = set(mem_blob) - {"type", "db_path", "embeddings"}
    if mem_extra:
        raise ProfileValidationError(f"Unknown memory keys: {sorted(mem_extra)}.")
    memory = TeamMemoryConfig(type=mem_type, db_path=db_path, embeddings=embeddings)

    refl_blob = data.get("reflection") or {}
    if not isinstance(refl_blob, Mapping):
        raise ProfileValidationError("'reflection' must be a table.")
    reflection = ReflectionSection(
        enabled=_as_bool(refl_blob.get("enabled", False), "reflection.enabled"),
        auto_merge=_as_bool(refl_blob.get("auto_merge", False), "reflection.auto_merge"),
        reliability_gate=_as_bool(
            refl_blob.get("reliability_gate", True), "reflection.reliability_gate"
        ),
    )
    refl_extra = set(refl_blob) - {"enabled", "auto_merge", "reliability_gate"}
    if refl_extra:
        raise ProfileValidationError(f"Unknown reflection keys: {sorted(refl_extra)}.")

    agents_blob = data.get("agents")
    if not isinstance(agents_blob, Mapping) or not agents_blob:
        raise ProfileValidationError("'agents' must be a non-empty table.")

    if orchestrator not in agents_blob:
        raise ProfileValidationError(
            f"workspace.orchestrator {orchestrator!r} is missing from [agents]."
        )

    starter = starter_tool_names()
    agent_keys = [_as_str(str(k), "agents") for k in agents_blob]
    delegates_for_team = frozenset(
        _safe_delegate_tool_name(wid) for wid in agent_keys if wid != orchestrator
    )

    agents: dict[str, AgentRoleConfig] = {}

    for aid_raw, acfg in agents_blob.items():
        agent_id = _as_str(str(aid_raw), "agents.<name>")
        if not isinstance(acfg, Mapping):
            raise ProfileValidationError(f"agents.{agent_id} must be a table.")
        role = _as_str(acfg.get("role", "agent"), f"agents.{agent_id}.role")
        model_adapter = (
            _as_str(acfg.get("model_adapter", "fake"), f"agents.{agent_id}.model_adapter")
            .strip()
            .lower()
            .replace("-", "_")
        )
        if model_adapter not in {"fake", "openai_compat", "anthropic", "gemini", "ollama"}:
            raise ProfileValidationError(
                f"agents.{agent_id}.model_adapter must be one of "
                f"fake, openai_compat, anthropic, gemini, ollama; got {model_adapter!r}."
            )

        openai_blob = acfg.get("openai_compat")
        anthropic_blob = acfg.get("anthropic")
        gemini_blob = acfg.get("gemini")
        ollama_blob = acfg.get("ollama")
        if openai_blob is not None and model_adapter != "openai_compat":
            raise ProfileValidationError(
                f"agents.{agent_id}.openai_compat is only valid when "
                f"model_adapter is openai_compat."
            )
        if anthropic_blob is not None and model_adapter != "anthropic":
            raise ProfileValidationError(
                f"agents.{agent_id}.anthropic is only valid when model_adapter is anthropic."
            )
        if gemini_blob is not None and model_adapter != "gemini":
            raise ProfileValidationError(
                f"agents.{agent_id}.gemini is only valid when model_adapter is gemini."
            )
        if ollama_blob is not None and model_adapter != "ollama":
            raise ProfileValidationError(
                f"agents.{agent_id}.ollama is only valid when model_adapter is ollama."
            )
        if openai_blob is not None and not isinstance(openai_blob, Mapping):
            raise ProfileValidationError(f"agents.{agent_id}.openai_compat must be a table.")
        if anthropic_blob is not None and not isinstance(anthropic_blob, Mapping):
            raise ProfileValidationError(f"agents.{agent_id}.anthropic must be a table.")
        if gemini_blob is not None and not isinstance(gemini_blob, Mapping):
            raise ProfileValidationError(f"agents.{agent_id}.gemini must be a table.")
        if ollama_blob is not None and not isinstance(ollama_blob, Mapping):
            raise ProfileValidationError(f"agents.{agent_id}.ollama must be a table.")

        tools_raw = acfg.get("tools")
        if not isinstance(tools_raw, list) or not tools_raw:
            raise ProfileValidationError(f"agents.{agent_id}.tools must be a non-empty array.")
        tool_set: set[str] = set()
        for t in tools_raw:
            tname = _as_str(t, f"agents.{agent_id}.tools[]")
            tool_set.add(tname)
        if agent_id != orchestrator:
            illegal_del = {t for t in tool_set if t.startswith("delegate_to_")}
            if illegal_del:
                raise ProfileValidationError(
                    f"agents.{agent_id} cannot declare delegation tools: {sorted(illegal_del)}."
                )
        elif delegates_for_team:
            tool_set |= set(delegates_for_team)

        diff = frozenset(tool_set) - starter - MEMORY_DECORATED_TOOL_NAMES
        unknown_tools = diff - delegates_for_team
        if unknown_tools:
            raise ProfileValidationError(
                f"agents.{agent_id}.tools contains unknown names: {sorted(unknown_tools)}."
            )

        default_b = BudgetLimits()
        max_steps = _as_int(
            acfg.get("max_steps", default_b.max_steps), f"agents.{agent_id}.max_steps"
        )
        max_tokens = acfg.get("max_model_tokens")
        if max_tokens is None:
            mt: int | None = default_b.max_model_tokens
        elif isinstance(max_tokens, bool) or not isinstance(max_tokens, int):
            raise ProfileValidationError(f"agents.{agent_id}.max_model_tokens must be int or null.")
        elif max_tokens < 1:
            raise ProfileValidationError(f"agents.{agent_id}.max_model_tokens must be >= 1.")
        else:
            mt = max_tokens

        budgets = BudgetLimits(
            max_steps=max_steps,
            max_tool_calls=default_b.max_tool_calls,
            wall_clock_seconds=default_b.wall_clock_seconds,
            per_tool_seconds=default_b.per_tool_seconds,
            max_model_tokens=mt,
        )
        budgets = _parse_budgets_for_agent(acfg.get("budgets"), defaults=budgets)

        max_retries = _as_int(
            acfg.get("max_retries", 3), f"agents.{agent_id}.max_retries", minimum=0
        )

        ins_raw = acfg.get("instructions", "")
        if ins_raw is None or ins_raw == "":
            agent_instructions_txt = ""
        elif isinstance(ins_raw, str):
            agent_instructions_txt = ins_raw
        else:
            raise ProfileValidationError(f"agents.{agent_id}.instructions must be a string.")

        art = acfg.get("approval_required_tiers")
        tiers: frozenset[RiskTier] | None
        if art is None:
            tiers = None
        else:
            tiers = _parse_risk_tiers(art)

        fake_messages: tuple[dict[str, Any], ...] | None = None
        fm = acfg.get("fake_model")
        json_messages = acfg.get("fake_model_json")
        if fm is not None and json_messages is not None:
            raise ProfileValidationError(
                f"agents.{agent_id}: use only one of fake_model or fake_model_json."
            )
        if fm is not None:
            if not isinstance(fm, Mapping):
                raise ProfileValidationError(f"agents.{agent_id}.fake_model must be a table.")
            if model_adapter != "fake":
                raise ProfileValidationError(
                    f"agents.{agent_id}.fake_model is only valid when model_adapter is fake."
                )
            fm_extra = set(fm) - {"messages"}
            if fm_extra:
                raise ProfileValidationError(
                    f"Unknown agents.{agent_id}.fake_model keys: {sorted(fm_extra)}."
                )
            fake_messages = _validate_fake_messages(fm.get("messages"))
        elif json_messages is not None:
            if model_adapter != "fake":
                raise ProfileValidationError(
                    f"agents.{agent_id}.fake_model_json is only valid when model_adapter is fake."
                )
            if not isinstance(json_messages, str):
                raise ProfileValidationError(
                    f"agents.{agent_id}.fake_model_json must be a string of JSON."
                )
            parsed = json.loads(json_messages)
            if not isinstance(parsed, list):
                raise ProfileValidationError("fake_model_json must decode to a JSON array.")
            fake_messages = _validate_fake_messages(parsed)

        acfg_extra = set(acfg) - {
            "role",
            "model_adapter",
            "tools",
            "max_steps",
            "max_model_tokens",
            "max_retries",
            "budgets",
            "approval_required_tiers",
            "fake_model",
            "fake_model_json",
            "instructions",
            "openai_compat",
            "anthropic",
            "gemini",
            "ollama",
        }
        if acfg_extra:
            raise ProfileValidationError(
                f"Unknown keys for agents.{agent_id}: {sorted(acfg_extra)}."
            )

        agents[agent_id] = AgentRoleConfig(
            agent_id=agent_id,
            role=role,
            model_adapter=model_adapter,
            tools=frozenset(tool_set),
            budgets=budgets,
            max_retries=max_retries,
            approval_required_tiers=tiers,
            fake_model_messages=fake_messages,
            instructions=agent_instructions_txt,
            openai_compat=dict(openai_blob) if openai_blob is not None else None,
            anthropic=dict(anthropic_blob) if anthropic_blob is not None else None,
            gemini=dict(gemini_blob) if gemini_blob is not None else None,
            ollama=dict(ollama_blob) if ollama_blob is not None else None,
        )

    return TeamTopology(
        workspace=WorkspaceSection(
            name=name,
            orchestrator=orchestrator,
            description=description,
            trace_dir=trace_dir,
            auto_approve=auto_approve,
            approval_required_tiers=approval_required_tiers,
            sanitizer_max_chars=sanitizer_max_chars,
        ),
        agents=agents,
        memory=memory,
        reflection=reflection,
    )

parse_team_topology_file

parse_team_topology_file(path: Path) -> TeamTopology
Source code in src/naqsha/orchestration/topology.py
def parse_team_topology_file(path: Path) -> TeamTopology:
    raw = path.read_bytes()
    data = tomllib.loads(raw.decode("utf-8"))
    return parse_team_topology(data, base_dir=path.parent.resolve())