Files
2026-07-01 17:56:31 +08:00

1409 lines
58 KiB
Python

"""Organization engine — manages role-based agent orchestration."""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
from loguru import logger
from opc.core.company_tools import COMPANY_APPROVAL_EXEMPT_TOOL_NAMES
from opc.core.config import (
EmployeeConfig,
OPCConfig,
RoleConfig,
)
from opc.core.models import (
AgentInfo,
AgentStatus,
OrgAgent,
Organization,
OrgSnapshot,
ReorgChangeSet,
)
from opc.layer2_organization.collaboration_policy import (
collect_dynamic_contact_roles,
)
from opc.layer2_organization.company_runtime_profiles import (
get_builtin_roles,
get_builtin_runtime_policies,
get_company_profile_descriptions,
)
from opc.layer2_organization.talent_market import resolve_prompt_refs
from opc.layer2_organization.org_work_item_planner import (
CompanyWorkItemRuntimePlan,
build_company_work_item_runtime_plan,
)
from opc.layer5_memory.employee_evolution import EmployeeEvolutionManager
_DEFAULT_EMPLOYEE_TEMPLATE_ID = "general-default-employee"
_DEFAULT_EMPLOYEE_PROMPT_REF = "prompts/talent/general-default-employee.md"
_FALLBACK_EMPLOYEE_TEMPLATE_ID = "fallback-empty-employee"
TASK_MODE_GENERAL_ROLE_ID = "task_generalist"
TASK_MODE_COMPANY_ONLY_TOOLS = frozenset(COMPANY_APPROVAL_EXEMPT_TOOL_NAMES)
_DEFAULT_TASK_MODE_TOOLS = [
"request_user_input",
"shell_exec",
"file_read",
"file_write",
"file_edit",
"apply_patch",
"file_search",
"grep",
"glob",
"list_dir",
"web_search",
"web_fetch",
"browser_navigate",
"browser_navigate_back",
"browser_click",
"browser_snapshot",
"browser_type",
"browser_wait_for",
"browser_scroll",
"browser_select_option",
"browser_take_screenshot",
"browser_close",
"git_status",
"git_commit",
"git_diff",
"python_exec",
"todo_write",
"todo_read",
"agent_spawn",
"agent_wait",
"agent_send",
"agent_list",
]
class OrgEngine:
"""Manages the organizational structure and runtime role policies."""
def __init__(
self,
config: OPCConfig,
opc_home: Path | None = None,
store: Any | None = None,
) -> None:
self.config = config
self.opc_home = opc_home
self.store = store
try:
from opc.core.employee_registry import normalize_employee_records
self.config.org.employees, _ = normalize_employee_records(list(self.config.org.employees))
except Exception:
pass
self.employee_evolution = EmployeeEvolutionManager(opc_home) if opc_home else None
self._agents: dict[str, AgentInfo] = {}
self._task_mode_tool_names: list[str] = list(_DEFAULT_TASK_MODE_TOOLS)
self._task_mode_role: AgentInfo | None = None
self._org_version = 1
self._runtime_topology_version = 1
self._org_history: dict[int, dict[str, Any]] = {}
self._initialize_roles()
def _initialize_roles(self) -> None:
self._agents = {}
roles = self._effective_roles()
for role in roles:
tools = list(role.tools)
self._agents[role.id] = AgentInfo(
role_id=role.id,
name=role.name,
responsibility=role.responsibility,
status=AgentStatus.IDLE,
reports_to=role.reports_to,
icon=role.icon,
can_spawn=list(role.can_spawn),
tools=tools,
preferred_external_agent=role.preferred_external_agent,
prompt_refs=self._resolve_prompt_refs(role.prompt_refs),
skill_refs=list(role.skill_refs),
handoff_template_ref=role.handoff_template_ref,
memory_policy_ref=role.memory_policy_ref,
artifact_contract_ref=role.artifact_contract_ref,
runtime_policy=role.runtime_policy.model_dump(),
)
self._refresh_task_mode_role()
logger.info(f"OrgEngine initialized with {len(self._agents)} roles")
self._org_history[self._org_version] = {
"roles": [role.model_dump() for role in roles],
"company_profile": self.config.org.company_profile,
"runtime_topology_version": self._runtime_topology_version,
}
def _effective_roles(self) -> list[RoleConfig]:
profile = self.config.org.company_profile
# Corporate: use builtin topology, but allow org_config role
# definitions to override explicit fields like tools/runtime_policy.
if profile == "corporate":
return get_builtin_roles(profile, configured_roles=self.config.org.roles)
# Task mode (profile="" or None): no org roles
if not profile or profile not in {"custom"}:
return []
# Custom mode: return config.org.roles as-is
return list(self.config.org.roles)
# ── Task-mode dedicated role (independent of _effective_roles) ────
@staticmethod
def _normalize_task_mode_tools(tool_names: list[str] | None) -> list[str]:
names = tool_names or list(_DEFAULT_TASK_MODE_TOOLS)
normalized: list[str] = []
seen: set[str] = set()
for raw in names:
name = str(raw or "").strip()
if not name or name in TASK_MODE_COMPANY_ONLY_TOOLS or name in seen:
continue
normalized.append(name)
seen.add(name)
if "request_user_input" not in seen:
normalized.insert(0, "request_user_input")
return normalized
def _refresh_task_mode_role(self) -> None:
self._task_mode_tool_names = self._normalize_task_mode_tools(self._task_mode_tool_names)
self._task_mode_role = AgentInfo(
role_id=TASK_MODE_GENERAL_ROLE_ID,
name="Task Generalist",
responsibility=(
"Primary session agent for task mode. Owns the full non-company toolset "
"and solves standalone requests directly."
),
status=AgentStatus.IDLE,
reports_to="owner",
can_spawn=[],
tools=list(self._task_mode_tool_names),
preferred_external_agent=None,
prompt_refs=[],
skill_refs=[],
handoff_template_ref=None,
memory_policy_ref=None,
artifact_contract_ref=None,
runtime_policy={
"execution_strategy": "native",
"allowed_downstream_roles": [],
"review_role": None,
"default_turn_type": "work",
},
)
def configure_task_mode_tools(self, tool_names: list[str] | None) -> None:
self._task_mode_tool_names = self._normalize_task_mode_tools(tool_names)
self._refresh_task_mode_role()
def get_task_mode_role(self) -> AgentInfo:
if self._task_mode_role is None:
self._refresh_task_mode_role()
assert self._task_mode_role is not None
return self._task_mode_role
@staticmethod
def _looks_like_path(ref: str) -> bool:
"""Heuristic: treat ref as a file path only if it looks like one."""
if len(ref) > 260: # too long for any filesystem
return False
if " " in ref and "/" not in ref and "\\" not in ref:
return False # contains spaces but no path separators → literal text
if ref.startswith(("You ", "Act ", "Focus ", "Review ", "Write ")):
return False # common prompt sentence starters
return True
def _resolve_prompt_refs(self, refs: list[str]) -> list[str]:
resolved: list[str] = []
for ref in refs:
if self.opc_home and self._looks_like_path(ref):
try:
path = Path(ref)
if not path.is_absolute():
path = self.opc_home / path
if path.exists() and path.is_file():
resolved.append(path.read_text(encoding="utf-8").strip())
continue
except OSError:
pass # path too long or other OS error — treat as literal
resolved.append(ref)
return resolved
def get_agent(self, role_id: str) -> AgentInfo | None:
if role_id == TASK_MODE_GENERAL_ROLE_ID:
return self.get_task_mode_role()
return self._agents.get(role_id)
def get_executor(self) -> AgentInfo:
return self._agents.get("executor", list(self._agents.values())[0])
def get_coordinator(self) -> AgentInfo:
return self._agents.get("coordinator", list(self._agents.values())[0])
def list_agents(self) -> list[AgentInfo]:
return list(self._agents.values())
def get_company_profile(self) -> str:
return self.config.org.company_profile
def get_execution_model(self) -> str:
return "actor_runtime"
def get_top_level_role_ids(self) -> list[str]:
agent_ids = set(self._agents)
top_level_role_ids = [
role_id
for role_id, agent in self._agents.items()
if agent.reports_to == "owner" or agent.reports_to not in agent_ids
]
return sorted(dict.fromkeys(top_level_role_ids))
def get_final_decider_role_id(self, *, strict: bool = False) -> str | None:
top_level_role_ids = self.get_top_level_role_ids()
if not top_level_role_ids:
return None
configured = str(getattr(self.config.org, "final_decider_role_id", "") or "").strip()
if configured:
if configured in top_level_role_ids:
return configured
if strict:
raise ValueError(
f"Configured final_decider_role_id '{configured}' is not a top-level role."
)
if len(top_level_role_ids) == 1:
return top_level_role_ids[0]
if strict:
raise ValueError(
"Multiple top-level roles exist; final_decider_role_id must be explicitly configured."
)
return configured or None
def validate_company_runtime_setup(self) -> str | None:
if not self._agents:
return "Company mode requires at least one role."
try:
self.get_final_decider_role_id(strict=True)
except ValueError as exc:
return str(exc)
return None
def get_company_profile_descriptions(self) -> dict[str, str]:
descriptions = get_company_profile_descriptions()
enabled = set(self.config.org.company_profiles)
descriptions.setdefault("custom", "A user-defined organization runtime driven by work items, role queues, teams, and seats.")
return {name: descriptions.get(name, "") for name in enabled | {"custom"}}
def get_runtime_policy(self, profile: str) -> dict[str, Any]:
builtin = get_builtin_runtime_policies().get(profile)
if profile == "custom" and builtin is not None:
# Enhance the generic custom defaults with role-aware inference
builtin = self._enhance_custom_policy(builtin)
merged = builtin.model_dump() if builtin else {}
custom = self.config.org.runtime_policies.get(profile)
if custom:
merged = self._deep_merge(merged, custom.model_dump())
return merged
def _enhance_custom_policy(self, base: Any) -> Any:
"""Refine the default custom policy using the current role structure."""
from opc.core.config import (
CommunicationPolicyConfig,
GateHarnessPolicyConfig,
HandoffPolicyConfig,
ReviewPolicyConfig,
RuntimePolicyConfig,
)
has_reviewer = any(
a.runtime_policy.get("review_role")
for a in self._agents.values()
)
depth = self._max_hierarchy_depth()
has_external = any(
a.preferred_external_agent
for a in self._agents.values()
)
# Only override sub-policies that benefit from role-aware inference;
# leave the rest at their Pydantic-model defaults so user overrides
# in org_config.runtime_policies are not masked.
enhanced = base.model_copy() if hasattr(base, "model_copy") else RuntimePolicyConfig()
enhanced.communication = CommunicationPolicyConfig(
default_mode="broadcast" if depth > 3 else "dm",
blocking_default=has_reviewer,
allow_broadcast=True,
)
enhanced.handoff = HandoffPolicyConfig(
require_structured_handoff=depth > 1,
require_ack=has_reviewer,
include_risks=True,
include_open_questions=True,
)
enhanced.review = ReviewPolicyConfig(
strict_gate_inference=has_reviewer,
require_reviewer_role=has_reviewer,
allow_human_override=True,
)
enhanced.gate_harness = GateHarnessPolicyConfig(
decision_mode="agent_first" if has_external else "hybrid",
default_degrade_policy="allow",
auto_infer_turn_kind=True,
auto_infer_gate_profile=True,
allow_pass_with_constraints=True,
)
return enhanced
def _max_hierarchy_depth(self) -> int:
"""Return the longest reports_to chain length in the role hierarchy."""
agent_ids = set(self._agents)
def _depth(role_id: str, visited: set[str]) -> int:
children = [
rid for rid, a in self._agents.items()
if a.reports_to == role_id and rid not in visited
]
if not children:
return 0
return 1 + max(
_depth(c, visited | {c}) for c in children
)
roots = [
rid for rid, a in self._agents.items()
if a.reports_to == "owner" or a.reports_to not in agent_ids
]
if not roots:
return 1
return max(_depth(r, {r}) for r in roots)
def build_company_work_item_runtime_plan(
self,
profile: str | None = None,
*,
runtime_topology: dict[str, Any] | None = None,
original_request: str = "",
) -> CompanyWorkItemRuntimePlan:
"""Build the company execution plan from org topology and work items."""
normalized_profile = str(profile or self.config.org.company_profile or "corporate").strip() or "corporate"
topology = dict(runtime_topology or self.build_runtime_delegation_topology())
policy = self.get_runtime_policy(normalized_profile)
policy_payload = policy.model_dump() if hasattr(policy, "model_dump") else dict(policy or {})
return build_company_work_item_runtime_plan(
self,
profile=normalized_profile,
runtime_topology=topology,
original_request=original_request,
runtime_policy=policy_payload,
)
def _build_runtime_delegation_topology_from_configured_teams(self) -> dict[str, Any]:
final_decider_role_id = self.get_final_decider_role_id(strict=False) or ""
top_level_role_ids = self.get_top_level_role_ids()
agent_ids = set(self._agents)
configured_teams = list(self.config.org.teams or [])
teams: list[dict[str, Any]] = []
seats: list[dict[str, Any]] = []
team_by_lead: dict[str, str] = {}
lead_seat_by_team: dict[str, dict[str, Any]] = {}
seat_ids_by_team_and_role: dict[tuple[str, str], str] = {}
def _seat_meta(raw_seat: Any) -> dict[str, Any]:
metadata = dict(getattr(raw_seat, "metadata", {}) or {})
if getattr(raw_seat, "seat_kind", ""):
metadata.setdefault("seat_kind", str(getattr(raw_seat, "seat_kind", "") or "").strip())
if getattr(raw_seat, "name", ""):
metadata.setdefault("seat_name", str(getattr(raw_seat, "name", "") or "").strip())
return metadata
def _looks_like_lead(raw_seat: Any) -> bool:
metadata = _seat_meta(raw_seat)
if bool(metadata.get("is_team_lead", False)):
return True
seat_kind = str(metadata.get("seat_kind", "") or getattr(raw_seat, "seat_kind", "") or "").strip().lower()
return seat_kind in {"lead", "leader", "manager", "root"}
for team_cfg in configured_teams:
team_id = str(getattr(team_cfg, "team_id", "") or "").strip()
if not team_id:
continue
raw_seats = list(getattr(team_cfg, "seats", []) or [])
if not raw_seats:
continue
lead_cfg = next((seat for seat in raw_seats if _looks_like_lead(seat)), None) or raw_seats[0]
lead_role_id = str(getattr(lead_cfg, "role_id", "") or "").strip()
if lead_role_id:
team_by_lead[lead_role_id] = team_id
member_role_ids: list[str] = []
member_seat_ids: list[str] = []
for raw_seat in raw_seats:
seat_id = str(getattr(raw_seat, "seat_id", "") or "").strip()
role_id = str(getattr(raw_seat, "role_id", "") or "").strip()
if not seat_id or not role_id:
continue
if role_id not in member_role_ids:
member_role_ids.append(role_id)
member_seat_ids.append(seat_id)
seat_ids_by_team_and_role[(team_id, role_id)] = seat_id
team_metadata = dict(getattr(team_cfg, "metadata", {}) or {})
parent_team_id = str(team_metadata.get("parent_team_id", "") or "").strip()
lead_agent = self.get_agent(lead_role_id) if lead_role_id else None
manager_role_id = str(getattr(lead_agent, "reports_to", "") or "").strip()
if manager_role_id and manager_role_id != "owner" and not parent_team_id:
parent_team_id = team_by_lead.get(manager_role_id, "")
team_payload = {
"team_id": team_id,
"lead_role_id": lead_role_id,
"parent_team_id": parent_team_id,
"member_role_ids": member_role_ids,
"member_seat_ids": member_seat_ids,
"metadata": {
"lead_name": str(getattr(lead_agent, "name", "") or getattr(lead_cfg, "name", "") or lead_role_id).strip(),
"configured_team": True,
"final_decider_team": lead_role_id == final_decider_role_id,
**team_metadata,
},
}
teams.append(team_payload)
lead_seat_by_team[team_id] = {
"seat_id": str(getattr(lead_cfg, "seat_id", "") or "").strip(),
"role_id": lead_role_id,
}
for team_cfg in configured_teams:
team_id = str(getattr(team_cfg, "team_id", "") or "").strip()
if not team_id:
continue
lead_entry = lead_seat_by_team.get(team_id, {})
lead_role_id = str(lead_entry.get("role_id", "") or "").strip()
lead_seat_id = str(lead_entry.get("seat_id", "") or "").strip()
parent_team_id = next(
(
str(team.get("parent_team_id", "") or "").strip()
for team in teams
if str(team.get("team_id", "") or "").strip() == team_id
),
"",
)
for raw_seat in list(getattr(team_cfg, "seats", []) or []):
seat_id = str(getattr(raw_seat, "seat_id", "") or "").strip()
role_id = str(getattr(raw_seat, "role_id", "") or "").strip()
if not seat_id or not role_id:
continue
agent = self.get_agent(role_id)
metadata = _seat_meta(raw_seat)
is_team_lead = seat_id == lead_seat_id
manager_role_id = str(getattr(raw_seat, "manager_role_id", "") or "").strip()
manager_seat_id = str(getattr(raw_seat, "manager_seat_id", "") or "").strip()
if not manager_role_id:
if is_team_lead:
manager_role_id = str(getattr(agent, "reports_to", "") or "").strip()
if manager_role_id == "owner":
manager_role_id = ""
else:
manager_role_id = lead_role_id
if not manager_seat_id:
if is_team_lead:
if parent_team_id:
manager_seat_id = (
seat_ids_by_team_and_role.get((parent_team_id, role_id), "")
or seat_ids_by_team_and_role.get((parent_team_id, manager_role_id), "")
or str(lead_seat_by_team.get(parent_team_id, {}).get("seat_id", "") or "").strip()
)
else:
manager_seat_id = lead_seat_id
managed_team_id = team_by_lead.get(role_id, "")
contact_role_ids = [
str(item).strip()
for item in list(metadata.get("contact_role_ids", []) or [])
if str(item).strip()
]
if not contact_role_ids:
contact_role_ids = [
str(getattr(candidate, "role_id", "") or "").strip()
for candidate in list(getattr(team_cfg, "seats", []) or [])
if str(getattr(candidate, "role_id", "") or "").strip()
and str(getattr(candidate, "role_id", "") or "").strip() != role_id
]
if manager_role_id and manager_role_id not in contact_role_ids and manager_role_id in agent_ids:
contact_role_ids.append(manager_role_id)
seats.append(
{
"seat_id": seat_id,
"team_id": team_id,
"role_id": role_id,
"employee_id": "",
"is_team_lead": is_team_lead,
"manager_role_id": manager_role_id,
"manager_seat_id": manager_seat_id,
"managed_team_id": managed_team_id,
"allowed_delegate_role_ids": [
str(item).strip()
for item in list(metadata.get("allowed_delegate_role_ids", []) or self.get_allowed_downstream_roles(role_id))
if str(item).strip()
],
"contact_role_ids": contact_role_ids,
"metadata": {
"team_lead_role_id": lead_role_id,
"parent_team_id": parent_team_id,
"role_name": getattr(agent, "name", role_id) if agent else role_id,
"configured_seat": True,
**metadata,
},
}
)
return {
"final_decider_role_id": final_decider_role_id,
"top_level_role_ids": list(top_level_role_ids),
"teams": teams,
"seats": seats,
}
def get_role_prompt_context(self, role_id: str) -> str:
agent = self.get_agent(role_id)
if not agent or not agent.prompt_refs:
return ""
return "\n\n".join(ref.strip() for ref in agent.prompt_refs if ref.strip())
def get_role_skill_refs(self, role_id: str) -> list[str]:
agent = self.get_agent(role_id)
if not agent:
return []
return list(agent.skill_refs)
def get_role_review_target(self, role_id: str) -> str | None:
agent = self.get_agent(role_id)
if not agent:
return None
review_role = agent.runtime_policy.get("review_role")
return review_role or None
def get_allowed_downstream_roles(self, role_id: str) -> list[str]:
if not role_id:
return []
agent = self.get_agent(role_id)
if not agent:
return []
allowed = list(agent.runtime_policy.get("allowed_downstream_roles", []))
if allowed:
return allowed
return list(agent.can_spawn)
def get_allowed_contact_roles(self, role_id: str, task: Any | None = None) -> list[str]:
task_meta = dict(getattr(task, "metadata", {}) or {}) if task is not None else {}
seat_contacts = [
str(candidate).strip()
for candidate in list(task_meta.get("seat_contact_role_ids", []) or [])
if str(candidate).strip()
]
if seat_contacts:
return [candidate for candidate in seat_contacts if candidate in self._agents and candidate != role_id]
agent = self.get_agent(role_id)
if not agent:
return []
explicit = list(agent.runtime_policy.get("allowed_contact_roles", []))
if explicit:
return [candidate for candidate in explicit if candidate in self._agents and candidate != role_id]
allowed: list[str] = []
def _add(candidate: str | None) -> None:
if not candidate or candidate == role_id or candidate not in self._agents:
return
if candidate not in allowed:
allowed.append(candidate)
# Preserve existing downward delegation behavior.
for candidate in self.get_allowed_downstream_roles(role_id):
_add(candidate)
# Allow escalation to the direct manager only.
_add(agent.reports_to)
# Allow peer collaboration with roles that share the same direct manager.
manager_id = agent.reports_to
if manager_id:
for peer in self._agents.values():
if peer.role_id != role_id and peer.reports_to == manager_id:
_add(peer.role_id)
for candidate in collect_dynamic_contact_roles(task):
_add(candidate)
return allowed
def build_runtime_delegation_topology(self) -> dict[str, Any]:
"""Build runtime team/seat topology from the org tree.
The topology is org-first rather than runtime-plan-first: every manager role
gets a team containing itself and its direct reports, while middle roles
appear both as members of their manager's team and as the lead of their
own team.
"""
if list(self.config.org.teams or []):
return self._build_runtime_delegation_topology_from_configured_teams()
final_decider_role_id = self.get_final_decider_role_id(strict=False) or ""
top_level_role_ids = self.get_top_level_role_ids()
agent_ids = set(self._agents)
team_order: list[str] = []
for role_id in self._agents:
if self.get_subordinates(role_id) or role_id == final_decider_role_id:
team_order.append(role_id)
# Preserve deterministic manager-before-child ordering.
team_order.sort(key=lambda role_id: (len(self.get_chain_of_command(role_id)), role_id))
teams: list[dict[str, Any]] = []
seats: list[dict[str, Any]] = []
team_by_lead: dict[str, str] = {}
for lead_role_id in team_order:
agent = self.get_agent(lead_role_id)
if agent is None:
continue
team_id = f"team::{lead_role_id}"
team_by_lead[lead_role_id] = team_id
direct_reports = [
subordinate.role_id
for subordinate in self.get_subordinates(lead_role_id)
if subordinate.role_id in agent_ids
]
parent_team_id = ""
manager_role_id = str(agent.reports_to or "").strip()
if manager_role_id and manager_role_id != "owner":
parent_team_id = f"team::{manager_role_id}"
member_role_ids = [lead_role_id, *direct_reports]
teams.append(
{
"team_id": team_id,
"lead_role_id": lead_role_id,
"parent_team_id": parent_team_id,
"member_role_ids": member_role_ids,
"member_seat_ids": [
f"seat::{team_id}::{member_role_id}"
for member_role_id in member_role_ids
],
"metadata": {
"lead_name": getattr(agent, "name", lead_role_id),
"final_decider_team": lead_role_id == final_decider_role_id,
},
}
)
for team in teams:
team_id = str(team["team_id"])
lead_role_id = str(team["lead_role_id"])
parent_team_id = str(team.get("parent_team_id", "") or "").strip()
parent_lead_role_id = parent_team_id.replace("team::", "", 1) if parent_team_id else ""
for member_role_id in list(team.get("member_role_ids", []) or []):
agent = self.get_agent(member_role_id)
if agent is None:
continue
seat_id = f"seat::{team_id}::{member_role_id}"
is_team_lead = member_role_id == lead_role_id
managed_team_id = team_by_lead.get(member_role_id, "")
manager_role_id = ""
manager_seat_id = ""
if is_team_lead:
if parent_lead_role_id and parent_lead_role_id in agent_ids:
manager_role_id = parent_lead_role_id
manager_seat_id = f"seat::{parent_team_id}::{member_role_id}"
else:
manager_role_id = lead_role_id
manager_seat_id = f"seat::{team_id}::{lead_role_id}"
contact_role_ids = []
for candidate_role_id in list(team.get("member_role_ids", []) or []):
if candidate_role_id != member_role_id and candidate_role_id not in contact_role_ids:
contact_role_ids.append(candidate_role_id)
if manager_role_id and manager_role_id not in contact_role_ids and manager_role_id in agent_ids:
contact_role_ids.append(manager_role_id)
seats.append(
{
"seat_id": seat_id,
"team_id": team_id,
"role_id": member_role_id,
"employee_id": "",
"is_team_lead": is_team_lead,
"manager_role_id": manager_role_id,
"manager_seat_id": manager_seat_id,
"managed_team_id": managed_team_id if managed_team_id != team_id else managed_team_id,
"allowed_delegate_role_ids": self.get_allowed_downstream_roles(member_role_id),
"contact_role_ids": contact_role_ids,
"metadata": {
"team_lead_role_id": lead_role_id,
"parent_team_id": parent_team_id,
"role_name": getattr(agent, "name", member_role_id),
},
}
)
return {
"final_decider_role_id": final_decider_role_id,
"top_level_role_ids": list(top_level_role_ids),
"teams": teams,
"seats": seats,
}
def resolve_runtime_target_seat(
self,
topology: dict[str, Any],
*,
from_seat_id: str,
target_role_id: str,
) -> dict[str, Any] | None:
"""Resolve the best seat for a delegation target inside runtime teams."""
target_role_id = str(target_role_id or "").strip()
if not from_seat_id or not target_role_id:
return None
seat_map = {
str(seat.get("seat_id", "")).strip(): dict(seat)
for seat in list(topology.get("seats", []) or [])
if str(seat.get("seat_id", "")).strip()
}
from_seat = seat_map.get(from_seat_id)
if from_seat is None:
return None
def _find_in_team(team_id: str) -> dict[str, Any] | None:
for candidate in seat_map.values():
if str(candidate.get("team_id", "") or "").strip() != team_id:
continue
if str(candidate.get("role_id", "") or "").strip() != target_role_id:
continue
if str(candidate.get("seat_id", "") or "").strip() == from_seat_id:
continue
return candidate
return None
same_team = _find_in_team(str(from_seat.get("team_id", "") or "").strip())
if same_team is not None:
return same_team
managed_team = str(from_seat.get("managed_team_id", "") or "").strip()
if managed_team:
managed_candidate = _find_in_team(managed_team)
if managed_candidate is not None:
return managed_candidate
manager_seat_id = str(from_seat.get("manager_seat_id", "") or "").strip()
if manager_seat_id:
manager_seat = seat_map.get(manager_seat_id)
if manager_seat is not None and str(manager_seat.get("role_id", "") or "").strip() == target_role_id:
return manager_seat
return None
def build_contact_directory(self, role_id: str, task: Any | None = None) -> list[dict[str, str]]:
agent = self.get_agent(role_id)
if not agent:
return []
peers = {
peer.role_id
for peer in self._agents.values()
if peer.role_id != role_id and peer.reports_to == agent.reports_to
}
downstream = set(self.get_allowed_downstream_roles(role_id))
manager_id = agent.reports_to if agent.reports_to in self._agents else None
directory: list[dict[str, str]] = []
for contact_role_id in self.get_allowed_contact_roles(role_id, task=task):
contact = self.get_agent(contact_role_id)
if not contact:
continue
relation = "peer"
if contact_role_id == manager_id:
relation = "manager"
elif contact_role_id in downstream:
relation = "downstream"
elif contact_role_id in peers:
relation = "peer"
directory.append({
"role_id": contact.role_id,
"name": contact.name,
"responsibility": contact.responsibility,
"relation": relation,
})
return directory
def get_role_for_domain(self, domains: list[str]) -> AgentInfo:
"""Return the default executor instead of domain-based routing."""
_ = domains
return self.get_executor()
def get_role_for_work_item(self, role_id: str, domains: list[str] | None = None) -> AgentInfo:
agent = self.get_agent(role_id)
if agent:
return agent
_ = domains
return self.get_executor()
def get_employee(self, employee_id: str) -> EmployeeConfig | None:
normalized = str(employee_id or "").strip()
if not normalized:
return None
for employee in self.config.org.employees:
if employee.employee_id == normalized:
return employee
aliases = {
str(item).strip()
for item in list(dict(employee.metadata or {}).get("legacy_employee_ids", []) or [])
if str(item).strip()
}
if normalized in aliases:
return employee
return None
def get_default_employee_for_role(self, role_id: str) -> EmployeeConfig | None:
if role_id == TASK_MODE_GENERAL_ROLE_ID:
return None
default_id = self._slugify_identifier(f"{role_id}-default-employee")
direct = self.get_employee(default_id)
if direct is not None:
return direct
for employee in self.list_employees(role_id=role_id):
metadata = dict(employee.metadata or {})
if metadata.get("is_default_employee") and metadata.get("auto_created_for_role") == role_id:
return employee
return None
def get_fallback_employee_for_role(self, role_id: str) -> EmployeeConfig | None:
fallback_id = self._slugify_identifier(f"{role_id}-fallback-empty-employee")
direct = self.get_employee(fallback_id)
if direct is not None:
return direct
for employee in self.list_employees(role_id=role_id):
metadata = dict(employee.metadata or {})
if metadata.get("is_fallback_employee") and metadata.get("auto_created_for_role") == role_id:
return employee
return None
def _slugify_identifier(self, value: str) -> str:
slug = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip().lower()).strip("-")
return slug or "employee"
def _format_role_label(self, role_id: str) -> str:
agent = self.get_agent(role_id)
if agent and agent.name.strip():
return agent.name.strip()
return re.sub(r"[-_]+", " ", role_id).strip().title() or "General Agent"
def ensure_default_employee_for_role(
self,
role_id: str,
*,
persist: bool = True,
) -> EmployeeConfig | None:
if role_id == TASK_MODE_GENERAL_ROLE_ID:
return None
existing_default = self.get_default_employee_for_role(role_id)
if existing_default is not None:
return existing_default
role_label = self._format_role_label(role_id)
employee_id = self._slugify_identifier(f"{role_id}-default-employee")
already_created = self.get_employee(employee_id)
if already_created is not None:
return already_created
prompt_refs: list[str] = []
if self.opc_home:
prompt_path = self.opc_home / _DEFAULT_EMPLOYEE_PROMPT_REF
if prompt_path.is_file():
prompt_refs.append(_DEFAULT_EMPLOYEE_PROMPT_REF)
agent = self.get_agent(role_id)
employee = EmployeeConfig(
employee_id=employee_id,
template_id=_DEFAULT_EMPLOYEE_TEMPLATE_ID,
name=f"{role_label} Default Employee",
role_id=role_id,
description=(
f"General-purpose default employee for the {role_label} role. "
"Handles routine execution until a specialized employee is assigned."
),
category="general",
prompt_refs=prompt_refs,
preferred_external_agent=agent.preferred_external_agent if agent else None,
metadata={
"is_default_employee": True,
"auto_created_for_role": role_id,
"employee_origin": "system_default",
"default_prompt_ref": _DEFAULT_EMPLOYEE_PROMPT_REF,
},
)
self.config.org.employees = [*self.config.org.employees, employee]
if persist and self.opc_home:
self.config.save(self.opc_home / "config")
return employee
def ensure_fallback_employee_for_role(
self,
role_id: str,
*,
persist: bool = True,
) -> EmployeeConfig | None:
existing_fallback = self.get_fallback_employee_for_role(role_id)
if existing_fallback is not None:
return existing_fallback
role_label = self._format_role_label(role_id)
employee_id = self._slugify_identifier(f"{role_id}-fallback-empty-employee")
already_created = self.get_employee(employee_id)
if already_created is not None:
return already_created
agent = self.get_agent(role_id)
employee = EmployeeConfig(
employee_id=employee_id,
template_id=_FALLBACK_EMPLOYEE_TEMPLATE_ID,
name=f"{role_label} Fallback Empty Employee",
role_id=role_id,
description=(
f"Empty placeholder employee for the {role_label} role when recruitment found no credible fit. "
"Add prompt_refs in YAML to customize fallback execution."
),
category="fallback",
prompt_refs=[],
preferred_external_agent=agent.preferred_external_agent if agent else None,
metadata={
"is_fallback_employee": True,
"auto_created_for_role": role_id,
"employee_origin": "recruitment_fallback",
},
)
self.config.org.employees = [*self.config.org.employees, employee]
if persist and self.opc_home:
self.config.save(self.opc_home / "config")
return employee
def remove_employee(self, employee_id: str) -> bool:
"""Remove an employee from config. Returns True if found and removed."""
before = len(self.config.org.employees)
self.config.org.employees = [
emp for emp in self.config.org.employees
if emp.employee_id != employee_id
]
return len(self.config.org.employees) < before
@staticmethod
def employee_role_ids(employee: EmployeeConfig) -> list[str]:
metadata = dict(employee.metadata or {})
role_ids: list[str] = []
for value in [
employee.role_id,
metadata.get("home_role_id"),
*list(metadata.get("home_role_ids", []) or []),
*list(metadata.get("staffed_role_ids", []) or []),
]:
role_id = str(value or "").strip()
if role_id and role_id not in role_ids:
role_ids.append(role_id)
return role_ids
def list_employees(self, role_id: str | None = None) -> list[EmployeeConfig]:
employees = [employee for employee in self.config.org.employees if employee.status == "active"]
if role_id:
normalized_role_id = str(role_id or "").strip()
employees = [employee for employee in employees if normalized_role_id in self.employee_role_ids(employee)]
return sorted(employees, key=lambda item: (item.role_id, item.name.lower()))
def resolve_employee_for_work_item(
self,
role_id: str,
domains: list[str] | None = None,
*,
project_id: str | None = None,
work_item_metadata: dict[str, Any] | None = None,
preferred_employee_id: str | None = None,
prefer_default_employee: bool = False,
) -> dict[str, Any] | None:
if role_id == TASK_MODE_GENERAL_ROLE_ID:
return None
if preferred_employee_id:
preferred = self.get_employee(preferred_employee_id)
if preferred is not None:
return self._build_employee_assignment(
preferred,
role_id=role_id,
domains=[],
project_id=project_id,
)
candidates = self.list_employees(role_id=role_id)
if not candidates:
default_employee = self.ensure_default_employee_for_role(role_id, persist=False)
if default_employee is None:
return None
candidates = [default_employee]
if prefer_default_employee:
default_employee = self.ensure_default_employee_for_role(role_id, persist=False)
if default_employee is not None:
return self._build_employee_assignment(
default_employee,
role_id=role_id,
domains=[],
project_id=project_id,
)
rankable_candidates = [
employee
for employee in candidates
if not dict(employee.metadata or {}).get("is_fallback_employee")
]
if not rankable_candidates:
default_employee = self.ensure_default_employee_for_role(role_id, persist=False)
if default_employee is None:
return None
return self._build_employee_assignment(
default_employee,
role_id=role_id,
domains=[],
project_id=project_id,
)
work_item_metadata = dict(work_item_metadata or {})
ranked = sorted(
rankable_candidates,
key=lambda employee: self._employee_score(
employee,
role_id=role_id,
domains=[],
project_id=project_id,
work_item_metadata=work_item_metadata,
),
reverse=True,
)
selected = ranked[0]
return self._build_employee_assignment(
selected,
role_id=role_id,
domains=[],
project_id=project_id,
)
def _build_employee_assignment(
self,
selected: EmployeeConfig,
*,
role_id: str,
domains: list[str],
project_id: str | None,
experience_mode: str = "with_experience",
) -> dict[str, Any]:
normalized_experience_mode = (
"template_only"
if str(experience_mode or "").strip() == "template_only"
else "with_experience"
)
experience_score = 0.0
delta_context = ""
organization_id = str(getattr(self.config.org, "organization_id", "") or "").strip()
if self.employee_evolution and normalized_experience_mode != "template_only":
experience_score = self.employee_evolution.get_experience_score(
selected.employee_id,
role_id=role_id,
domains=[],
project_id=project_id,
organization_id=organization_id or None,
)
delta_context = self.employee_evolution.build_employee_delta_context(
selected.employee_id,
project_id=project_id,
organization_id=organization_id or None,
)
prompt_context = ""
metadata = dict(selected.metadata or {})
if metadata.get("is_default_employee") and role_id != TASK_MODE_GENERAL_ROLE_ID:
prompt_context = self._build_company_default_employee_prompt_context(role_id)
elif self.opc_home:
prompt_context = "\n\n".join(resolve_prompt_refs(selected.prompt_refs, self.opc_home))
return {
"employee_id": selected.employee_id,
"template_id": selected.template_id,
"name": selected.name,
"role_id": role_id,
"home_role_id": selected.role_id,
"role_ids": self.employee_role_ids(selected),
"description": selected.description,
"category": selected.category,
"domains": list(selected.domains),
"tags": list(selected.tags),
"prompt_refs": list(selected.prompt_refs),
"prompt_context": prompt_context,
"delta_context": delta_context,
"preferred_external_agent": selected.preferred_external_agent,
"seniority": selected.seniority,
"metadata": dict(selected.metadata),
"experience_score": experience_score,
"experience_mode": normalized_experience_mode,
}
def _build_company_default_employee_prompt_context(self, role_id: str) -> str:
agent = self.get_agent(role_id)
role_name = self._format_role_label(role_id)
responsibility = str(getattr(agent, "responsibility", "") or "").strip()
downstream_roles = self.get_allowed_downstream_roles(role_id)
manager_like = bool(downstream_roles or self.get_subordinates(role_id))
lines = [
f"You are the default employee for the {role_name} role in company mode.",
]
if responsibility:
lines.append(f"Role responsibility: {responsibility}")
if manager_like:
lines.extend(
[
"Operate as a manager by default.",
"Clarify scope, delegate work to the most suitable direct reports, monitor progress, and roll results up.",
"Do not assume you should personally execute the whole request unless no suitable downstream role exists.",
]
)
else:
lines.extend(
[
"Operate as an execution-focused teammate by default.",
"Complete the assigned slice, coordinate when needed, and report a concise digest upward.",
]
)
return "\n".join(lines).strip()
def get_escalation_rules(self) -> list[dict[str, str]]:
return [r.model_dump() for r in self.config.org.escalation_rules]
def current_org_version(self) -> int:
return self._org_version
def current_runtime_topology_version(self) -> int:
return self._runtime_topology_version
def snapshot_org(self, project_id: str = "default", active_tasks: list[dict[str, Any]] | None = None) -> OrgSnapshot:
return OrgSnapshot(
project_id=project_id,
org_version=self._org_version,
runtime_topology_version=self._runtime_topology_version,
company_name=self.config.org.company_name,
topology=self.config.org.topology,
roles=[role.model_dump() for role in self.config.org.roles] or [
{
"id": agent.role_id,
"name": agent.name,
"responsibility": agent.responsibility,
"reports_to": agent.reports_to,
"can_spawn": list(agent.can_spawn),
"tools": list(agent.tools),
"preferred_external_agent": agent.preferred_external_agent,
"prompt_refs": list(agent.prompt_refs),
"skill_refs": list(agent.skill_refs),
"handoff_template_ref": agent.handoff_template_ref,
"memory_policy_ref": agent.memory_policy_ref,
"artifact_contract_ref": agent.artifact_contract_ref,
"runtime_policy": dict(agent.runtime_policy),
}
for agent in self.list_agents()
],
company_profile=self.config.org.company_profile,
active_tasks=list(active_tasks or []),
metadata={
"history_versions": sorted(self._org_history.keys()),
"organization_id": getattr(self.config.org, "organization_id", ""),
"organization_name": getattr(self.config.org, "organization_name", ""),
"organization_config_file": getattr(self.config.org, "organization_config_file", ""),
},
)
def reload_from_config(self) -> None:
self._initialize_roles()
def validate_changeset(self, changeset: ReorgChangeSet) -> list[str]:
errors: list[str] = []
role_ids = {role.id for role in self._effective_roles()} or {agent.role_id for agent in self.list_agents()}
for change in changeset.role_changes:
if change.action in {"remove", "replace", "update"} and change.role_id not in role_ids:
errors.append(f"Unknown role `{change.role_id}` for action `{change.action}`.")
if change.action in {"add", "replace"}:
target = change.replacement_role_id or change.role.get("id", "")
if not target:
errors.append(f"Role change `{change.action}` requires a new role id.")
return errors
def add_role(self, role: RoleConfig) -> None:
self.config.org.roles.append(role)
self._org_version += 1
self._initialize_roles()
def remove_role(self, role_id: str) -> None:
self.config.org.roles = [role for role in self.config.org.roles if role.id != role_id]
self._org_version += 1
self._initialize_roles()
def replace_roles(self, roles: list[RoleConfig]) -> None:
self.config.org.roles = [role.model_copy(deep=True) for role in roles]
self._org_version += 1
self._initialize_roles()
def apply_changeset(self, changeset: ReorgChangeSet, persist: bool = True) -> dict[str, Any]:
errors = self.validate_changeset(changeset)
if errors:
raise ValueError("; ".join(errors))
old_org_version = self._org_version
current_profile = self.config.org.company_profile
builtin_profile = current_profile == "corporate"
base_roles = self._effective_roles() if builtin_profile else self.config.org.roles
roles = [role.model_copy(deep=True) for role in base_roles]
role_index = {role.id: idx for idx, role in enumerate(roles)}
role_mapping: dict[str, str] = {}
for change in changeset.role_changes:
role_payload = dict(change.role)
target_role_id = change.replacement_role_id or role_payload.get("id") or change.role_id
if change.action == "add":
roles.append(RoleConfig.model_validate(role_payload))
elif change.action == "remove":
roles = [role for role in roles if role.id != change.role_id]
role_mapping[change.role_id] = target_role_id if target_role_id != change.role_id else ""
elif change.action == "replace":
roles = [role for role in roles if role.id != change.role_id]
if role_payload:
role_payload.setdefault("id", target_role_id)
roles.append(RoleConfig.model_validate(role_payload))
role_mapping[change.role_id] = target_role_id
elif change.action == "update":
idx = role_index.get(change.role_id)
if idx is None:
continue
merged = roles[idx].model_dump()
merged.update(role_payload)
roles[idx] = RoleConfig.model_validate(merged)
self.config.org.roles = roles
if builtin_profile and changeset.role_changes:
self.config.org.company_profile = "custom"
self._org_version += 1
self._initialize_roles()
if persist and self.opc_home:
self.config.save(self.opc_home / "config")
return {
"old_org_version": old_org_version,
"new_org_version": self._org_version,
"role_mapping": role_mapping,
}
def _deep_merge(self, base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]:
merged = dict(base)
for key, value in overlay.items():
if isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = self._deep_merge(merged[key], value)
else:
merged[key] = value
return merged
def _employee_score(
self,
employee: EmployeeConfig,
*,
role_id: str,
domains: list[str],
project_id: str | None,
work_item_metadata: dict[str, Any],
) -> float:
score = 10.0 if employee.role_id == role_id else 0.0
work_item_tokens = {
token.lower()
for token in (
str(work_item_metadata.get("domain", "")),
str(work_item_metadata.get("focus", "")),
str(work_item_metadata.get("title", "")),
)
if token
}
_ = domains
employee_tokens = {
token.lower()
for token in re.findall(
r"[a-z0-9][a-z0-9+-]{2,}",
f"{employee.name} {employee.description} {employee.category}",
)
}
score += float(len(employee_tokens & work_item_tokens) * 2)
if self.employee_evolution:
score += self.employee_evolution.get_experience_score(
employee.employee_id,
role_id=role_id,
domains=[],
project_id=project_id,
organization_id=str(getattr(self.config.org, "organization_id", "") or "").strip() or None,
)
return score
async def create_organization(
self,
name: str,
description: str = "",
company_profile: str = "corporate",
budget_monthly_cents: int = 0,
metadata: dict | None = None,
) -> Organization:
"""Create and persist a new organization."""
org = Organization(
name=name,
description=description,
company_profile=company_profile,
budget_monthly_cents=budget_monthly_cents,
metadata=dict(metadata or {}),
)
if self.store:
await self.store.save_organization(org)
return org
async def load_organization(self, org_id: str) -> Organization | None:
"""Load a persistent organization from the store."""
if not self.store:
return None
return await self.store.get_organization(org_id)
async def load_org_agents(self, org_id: str) -> list[OrgAgent]:
"""Load all agents belonging to an organization."""
if not self.store:
return []
return await self.store.list_org_agents(org_id)
def get_org_tree(self) -> list[dict]:
"""Build a tree from agents using reports_to relationships."""
agents = list(self._agents.values())
by_manager: dict[str | None, list] = {}
for agent in agents:
key = agent.reports_to if agent.reports_to != "owner" else None
by_manager.setdefault(key, []).append(agent)
def _build(manager_id: str | None) -> list[dict]:
members = by_manager.get(manager_id, [])
return [
{"agent": m, "reports": _build(m.role_id)}
for m in members
]
return _build(None)
def get_chain_of_command(self, agent_id: str) -> list:
"""Walk up the reports_to chain from agent to root."""
chain = []
seen: set[str] = set()
current = self._agents.get(agent_id)
while current and current.role_id not in seen:
seen.add(current.role_id)
chain.append(current)
parent_id = current.reports_to
if not parent_id or parent_id == "owner":
break
current = self._agents.get(parent_id)
return chain
def get_subordinates(self, agent_id: str) -> list:
"""Get all direct reports of an agent."""
return [
agent for agent in self._agents.values()
if agent.reports_to == agent_id
]