08e48c2f9c
Loguru has no exc_info kwarg: extra kwargs are str.format() arguments, so
logger.error(f"...{e}", exc_info=True) forces .format() on the rendered
message — any error text containing braces (e.g. a JSON error body) raises
KeyError FROM the log call itself, escaping the surrounding except block and
killing the caller (observed: whole agent turns dying in benchmark runs).
The intended traceback was also never logged, since exc_info is not a loguru
feature.
Batch fix of all 143 sites across 11 files:
logger.X(msg, exc_info=True) -> logger.opt(exception=True).X(msg)
(one exc_info=exc site -> opt(exception=exc))
Messages are byte-identical; with the kwarg gone loguru never calls
.format(), so brace-containing f-string messages are inert.
Verified: AST post-conditions per file, py_compile, import smoke of all
modules, behavioral equivalence of the 3 patterns, full unit suite (1549
passed) with a failure set identical to the pristine tree (22 pre-existing,
zero regressions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also add pixel-agents to the README acknowledgements.
1479 lines
65 KiB
Python
1479 lines
65 KiB
Python
"""LLM-assisted recruiter for pre-execution staffing decisions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from typing import Any
|
|
|
|
from loguru import logger
|
|
|
|
from opc.core.config import TalentTemplateConfig
|
|
from opc.core.models import (
|
|
RecruitmentCandidateRecommendation,
|
|
RecruitmentEmployeeRecommendation,
|
|
RecruitmentNeed,
|
|
RecruitmentPlan,
|
|
RecruitmentProposal,
|
|
)
|
|
|
|
RECRUITER_PROMPT = """\
|
|
You are the staffing recruiter for a company before it starts execution.
|
|
|
|
Your job is to choose the best staffing option for a single company role. You must compare:
|
|
- existing employees already hired for the role
|
|
- new imported talent templates that could be hired now
|
|
|
|
Return strict JSON:
|
|
{
|
|
"status": "existing_staff" | "proposed_hire" | "fallback_role_only",
|
|
"employee_id": "existing employee id or empty string",
|
|
"template_id": "candidate template id or empty string",
|
|
"proposed_employee_name": "short employee display name or empty string",
|
|
"rationale": "brief reason",
|
|
}
|
|
|
|
Rules:
|
|
- If a strong existing employee already fits, prefer `existing_staff`.
|
|
- If a new candidate is clearly better than existing staff or no existing staff exists, choose `proposed_hire`.
|
|
- If none of the provided options are credible, return status `fallback_role_only`.
|
|
- Respect user recruiter feedback if present.
|
|
- Pick a single durable hire per role.
|
|
- Judge new candidates mainly from their category context, name, and description.
|
|
- Return JSON only.
|
|
"""
|
|
|
|
GLOBAL_RECRUITER_PROMPT = """\
|
|
You are the staffing recruiter for a company before it starts execution.
|
|
|
|
Your job is to produce one global staffing plan for all roles at once. Compare:
|
|
- each role's responsibility and the user's request
|
|
- the shared top-level employee_pool of existing company employees with experience
|
|
- the shared top-level candidate_pool of imported talent templates
|
|
- org_graph, the reporting/delegation structure between roles
|
|
- recruiter feedback from earlier revisions
|
|
|
|
Return strict JSON:
|
|
{
|
|
"proposals": [
|
|
{
|
|
"role_id": "role id from the payload",
|
|
"status": "existing_staff" | "proposed_hire" | "fallback_role_only" | "direct_role_execution",
|
|
"employee_id": "existing employee id or empty string",
|
|
"template_id": "candidate template id or empty string",
|
|
"proposed_employee_name": "short employee display name or empty string",
|
|
"rationale": "brief reason"
|
|
}
|
|
]
|
|
}
|
|
|
|
Rules:
|
|
- Return exactly one proposal for every role in the payload and no extra roles.
|
|
- Consider both the user's request and every role's role_responsibility.
|
|
- Use org_graph as the reporting/delegation structure. Managers may coordinate or cover adjacent work; leaf roles usually represent dedicated execution/review specialties. Reuse the same employee/template across roles only when this structure and role responsibilities make shared coverage sensible; explain why.
|
|
- If you repeat the same employee or template across roles, explain why it still fits each role in that role's rationale.
|
|
- selected_categories are role-level hints from triage; employee_pool and candidate_pool are shared across all roles.
|
|
- Only choose employee_id values from the top-level employee_pool and template_id values from the top-level candidate_pool.
|
|
- Choosing employee_id means use the existing experienced employee. Choosing template_id means use the template-only version for this run.
|
|
- Use `direct_role_execution` when ordinary role execution is enough and no staffing decision is useful.
|
|
- Use `fallback_role_only` when the visible candidates and staff are not credible for the role.
|
|
- Respect recruiter feedback if present.
|
|
- Return JSON only.
|
|
"""
|
|
|
|
STAFFING_TRIAGE_PROMPT = """\
|
|
You are the staffing recruiter for a company before it starts execution.
|
|
|
|
First decide which roles need deliberate staffing and which talent categories should be considered.
|
|
|
|
Return strict JSON:
|
|
{
|
|
"roles": [
|
|
{
|
|
"role_id": "role id from the payload",
|
|
"action": "direct_role_execution" | "category_screening",
|
|
"categories": ["category-1", "category-2"],
|
|
"rationale": "brief reason"
|
|
}
|
|
]
|
|
}
|
|
|
|
Rules:
|
|
- Return exactly one entry for every role in the payload and no extra roles.
|
|
- Use the user's request as the primary signal, then map useful categories to roles with role responsibilities and org_graph.
|
|
- Generic or short role responsibilities do not by themselves mean staffing is unnecessary.
|
|
- Choose `direct_role_execution` only when no staffing comparison is useful for that role.
|
|
- Choose `category_screening` when the role could benefit from a specialized employee/template for this request.
|
|
- If action is `direct_role_execution`, return an empty categories list.
|
|
- If action is `category_screening`, select 1 to 3 categories only.
|
|
- Only choose categories from the provided category catalog.
|
|
- Use org_graph as the reporting/delegation structure.
|
|
- Respect recruiter feedback if present.
|
|
- Return JSON only.
|
|
"""
|
|
|
|
RECRUITMENT_AGENT_CHOICES = frozenset({
|
|
"native",
|
|
"codex",
|
|
"claude_code",
|
|
"cursor",
|
|
"opencode",
|
|
})
|
|
DEFAULT_RECRUITMENT_EXECUTION_AGENT = "codex"
|
|
VALID_RECRUITMENT_STATUSES = frozenset({
|
|
"existing_staff",
|
|
"proposed_hire",
|
|
"fallback_role_only",
|
|
"direct_role_execution",
|
|
})
|
|
|
|
|
|
def normalize_recruitment_agent_choice(value: Any, default: str | None = None) -> str | None:
|
|
normalized = str(value or "").strip().lower().replace("-", "_")
|
|
if normalized in RECRUITMENT_AGENT_CHOICES:
|
|
return normalized
|
|
fallback = str(default or "").strip().lower().replace("-", "_")
|
|
if fallback in RECRUITMENT_AGENT_CHOICES:
|
|
return fallback
|
|
return None
|
|
|
|
|
|
def resolve_effective_execution_agent(
|
|
selected_agent: Any,
|
|
preferred_external_agent: Any = None,
|
|
*,
|
|
force_native_execution: bool = False,
|
|
) -> tuple[str, str | None, bool]:
|
|
"""Resolve UI/recruitment selection into display, assigned external, native flag."""
|
|
preferred = normalize_recruitment_agent_choice(preferred_external_agent)
|
|
if preferred == "native":
|
|
preferred = None
|
|
selected = normalize_recruitment_agent_choice(
|
|
selected_agent,
|
|
default=("native" if not preferred else preferred),
|
|
)
|
|
resolved_force_native = bool(force_native_execution or selected == "native")
|
|
if resolved_force_native:
|
|
return "native", None, True
|
|
assigned_external = selected if selected and selected != "native" else preferred
|
|
return selected or assigned_external or "native", assigned_external, False
|
|
|
|
|
|
def ensure_recruitment_plan_default_agents(
|
|
plan: RecruitmentPlan,
|
|
*,
|
|
default_agent: str = DEFAULT_RECRUITMENT_EXECUTION_AGENT,
|
|
) -> RecruitmentPlan:
|
|
normalized_default = (
|
|
normalize_recruitment_agent_choice(
|
|
default_agent,
|
|
default=DEFAULT_RECRUITMENT_EXECUTION_AGENT,
|
|
)
|
|
or DEFAULT_RECRUITMENT_EXECUTION_AGENT
|
|
)
|
|
for proposal in plan.proposals:
|
|
metadata = dict(proposal.metadata or {})
|
|
metadata["selected_execution_agent"] = (
|
|
normalize_recruitment_agent_choice(metadata.get("selected_execution_agent"))
|
|
or normalized_default
|
|
)
|
|
proposal.metadata = metadata
|
|
return plan
|
|
|
|
|
|
def apply_recruitment_role_agent_overrides(
|
|
plan: RecruitmentPlan,
|
|
role_agent_overrides: dict[str, Any] | None,
|
|
) -> None:
|
|
if not role_agent_overrides:
|
|
return
|
|
normalized_overrides: dict[str, str] = {}
|
|
for raw_role_id, raw_agent in dict(role_agent_overrides).items():
|
|
role_id = str(raw_role_id or "").strip()
|
|
agent = normalize_recruitment_agent_choice(raw_agent)
|
|
if role_id and agent:
|
|
normalized_overrides[role_id] = agent
|
|
if not normalized_overrides:
|
|
return
|
|
for proposal in plan.proposals:
|
|
role_id = str(proposal.role_id or "").strip()
|
|
selected_agent = normalized_overrides.get(role_id)
|
|
if not selected_agent:
|
|
continue
|
|
proposal.metadata = dict(proposal.metadata)
|
|
proposal.metadata["selected_execution_agent"] = selected_agent
|
|
|
|
|
|
def extract_recruitment_role_agent_overrides(plan: RecruitmentPlan) -> dict[str, str]:
|
|
overrides: dict[str, str] = {}
|
|
for proposal in plan.proposals:
|
|
role_id = str(proposal.role_id or "").strip()
|
|
if not role_id:
|
|
continue
|
|
selected_agent = normalize_recruitment_agent_choice(
|
|
dict(proposal.metadata or {}).get("selected_execution_agent")
|
|
)
|
|
if selected_agent:
|
|
overrides[role_id] = selected_agent
|
|
return overrides
|
|
|
|
|
|
def build_staffing_overrides(plan: RecruitmentPlan) -> dict[str, str]:
|
|
overrides: dict[str, str] = {}
|
|
for proposal in plan.proposals:
|
|
if proposal.status == "existing_staff" and proposal.existing_employee:
|
|
overrides[proposal.role_id] = proposal.existing_employee.employee_id
|
|
elif proposal.status == "proposed_hire" and proposal.candidate and proposal.candidate.proposed_employee_id:
|
|
overrides[proposal.role_id] = proposal.candidate.proposed_employee_id
|
|
return overrides
|
|
|
|
|
|
def build_staffing_experience_modes(plan: RecruitmentPlan) -> dict[str, str]:
|
|
modes: dict[str, str] = {}
|
|
for proposal in plan.proposals:
|
|
role_id = str(proposal.role_id or "").strip()
|
|
if not role_id:
|
|
continue
|
|
if proposal.status == "existing_staff" and proposal.existing_employee:
|
|
modes[role_id] = "with_experience"
|
|
elif proposal.status == "proposed_hire" and proposal.candidate:
|
|
modes[role_id] = "template_only"
|
|
return modes
|
|
|
|
|
|
def build_fallback_role_ids(plan: RecruitmentPlan) -> set[str]:
|
|
return {
|
|
str(proposal.role_id).strip()
|
|
for proposal in plan.proposals
|
|
if proposal.status == "fallback_role_only" and str(proposal.role_id).strip()
|
|
}
|
|
|
|
|
|
def recruitment_plan_requires_confirmation(plan: RecruitmentPlan) -> bool:
|
|
return any(proposal.status in {"existing_staff", "proposed_hire"} for proposal in plan.proposals)
|
|
|
|
|
|
def serialize_recruitment_plan(plan: RecruitmentPlan) -> dict[str, Any]:
|
|
return {
|
|
"company_profile": plan.company_profile,
|
|
"proposals": [
|
|
{
|
|
"role_id": proposal.role_id,
|
|
"status": proposal.status,
|
|
"rationale": proposal.rationale,
|
|
"role_labels": list(proposal.role_labels),
|
|
"candidate": (
|
|
{
|
|
"template_id": proposal.candidate.template_id,
|
|
"template_name": proposal.candidate.template_name,
|
|
"category": proposal.candidate.category,
|
|
"domains": list(proposal.candidate.domains),
|
|
"prompt_ref": proposal.candidate.prompt_ref,
|
|
"preferred_external_agent": proposal.candidate.preferred_external_agent,
|
|
"source_path": proposal.candidate.source_path,
|
|
"rationale": proposal.candidate.rationale,
|
|
"proposed_employee_name": proposal.candidate.proposed_employee_name,
|
|
"proposed_employee_id": proposal.candidate.proposed_employee_id,
|
|
"metadata": dict(proposal.candidate.metadata),
|
|
}
|
|
if proposal.candidate
|
|
else None
|
|
),
|
|
"existing_employee": (
|
|
{
|
|
"employee_id": proposal.existing_employee.employee_id,
|
|
"employee_name": proposal.existing_employee.employee_name,
|
|
"role_id": proposal.existing_employee.role_id,
|
|
"category": proposal.existing_employee.category,
|
|
"domains": list(proposal.existing_employee.domains),
|
|
"learned_skill_refs": list(proposal.existing_employee.learned_skill_refs),
|
|
"experience_score": proposal.existing_employee.experience_score,
|
|
"rationale": proposal.existing_employee.rationale,
|
|
"metadata": dict(proposal.existing_employee.metadata),
|
|
}
|
|
if proposal.existing_employee
|
|
else None
|
|
),
|
|
"existing_employee_ids": list(proposal.existing_employee_ids),
|
|
"metadata": dict(proposal.metadata),
|
|
}
|
|
for proposal in plan.proposals
|
|
],
|
|
"recruiter_feedback": list(plan.recruiter_feedback),
|
|
"summary": plan.summary,
|
|
"metadata": dict(plan.metadata),
|
|
}
|
|
|
|
|
|
def deserialize_recruitment_plan(data: dict[str, Any]) -> RecruitmentPlan:
|
|
proposals: list[RecruitmentProposal] = []
|
|
for item in data.get("proposals", []):
|
|
candidate_data = item.get("candidate")
|
|
existing_employee_data = item.get("existing_employee")
|
|
candidate = None
|
|
existing_employee = None
|
|
if candidate_data:
|
|
candidate = RecruitmentCandidateRecommendation(
|
|
template_id=str(candidate_data.get("template_id", "")),
|
|
template_name=str(candidate_data.get("template_name", "")),
|
|
category=str(candidate_data.get("category", "")),
|
|
domains=list(candidate_data.get("domains", [])),
|
|
prompt_ref=str(candidate_data.get("prompt_ref", "")),
|
|
preferred_external_agent=candidate_data.get("preferred_external_agent"),
|
|
source_path=str(candidate_data.get("source_path", "")),
|
|
rationale=str(candidate_data.get("rationale", "")),
|
|
proposed_employee_name=str(candidate_data.get("proposed_employee_name", "")),
|
|
proposed_employee_id=str(candidate_data.get("proposed_employee_id", "")),
|
|
metadata=dict(candidate_data.get("metadata", {})),
|
|
)
|
|
if existing_employee_data:
|
|
existing_employee = RecruitmentEmployeeRecommendation(
|
|
employee_id=str(existing_employee_data.get("employee_id", "")),
|
|
employee_name=str(existing_employee_data.get("employee_name", "")),
|
|
role_id=str(existing_employee_data.get("role_id", "")),
|
|
category=str(existing_employee_data.get("category", "")),
|
|
domains=list(existing_employee_data.get("domains", [])),
|
|
learned_skill_refs=list(existing_employee_data.get("learned_skill_refs", [])),
|
|
experience_score=float(existing_employee_data.get("experience_score", 0.0) or 0.0),
|
|
rationale=str(existing_employee_data.get("rationale", "")),
|
|
metadata=dict(existing_employee_data.get("metadata", {})),
|
|
)
|
|
proposals.append(
|
|
RecruitmentProposal(
|
|
role_id=str(item.get("role_id", "")),
|
|
status=str(item.get("status", "fallback_role_only")),
|
|
rationale=str(item.get("rationale", "")),
|
|
role_labels=list(item.get("role_labels", [])),
|
|
candidate=candidate,
|
|
existing_employee=existing_employee,
|
|
existing_employee_ids=list(item.get("existing_employee_ids", [])),
|
|
metadata=dict(item.get("metadata", {})),
|
|
)
|
|
)
|
|
return ensure_recruitment_plan_default_agents(RecruitmentPlan(
|
|
company_profile=str(data.get("company_profile", "corporate")),
|
|
proposals=proposals,
|
|
recruiter_feedback=list(data.get("recruiter_feedback", [])),
|
|
summary=str(data.get("summary", "")),
|
|
metadata=dict(data.get("metadata", {})),
|
|
))
|
|
|
|
|
|
class CompanyRecruiter:
|
|
"""Generate runtime recruitment proposals before company execution."""
|
|
|
|
def __init__(self, llm: Any, org_engine: Any, talent_market: Any) -> None:
|
|
self.llm = llm
|
|
self.org_engine = org_engine
|
|
self.talent_market = talent_market
|
|
|
|
def _build_organization_payload(self) -> dict[str, Any]:
|
|
agents = list(self.org_engine.list_agents()) if self.org_engine else []
|
|
role_ids = {
|
|
str(getattr(agent, "role_id", "") or "").strip()
|
|
for agent in agents
|
|
if str(getattr(agent, "role_id", "") or "").strip()
|
|
}
|
|
direct_reports: dict[str, list[str]] = {}
|
|
for agent in agents:
|
|
role_id = str(getattr(agent, "role_id", "") or "").strip()
|
|
manager_id = str(getattr(agent, "reports_to", "") or "").strip()
|
|
if not role_id or manager_id == "owner" or manager_id not in role_ids:
|
|
continue
|
|
direct_reports.setdefault(manager_id, [])
|
|
if role_id not in direct_reports[manager_id]:
|
|
direct_reports[manager_id].append(role_id)
|
|
|
|
org_graph: dict[str, list[str]] = {}
|
|
for manager in agents:
|
|
manager_id = str(getattr(manager, "role_id", "") or "").strip()
|
|
children = list(direct_reports.get(manager_id) or [])
|
|
if not manager_id or not children:
|
|
continue
|
|
spawn_order = [
|
|
str(role_id or "").strip()
|
|
for role_id in list(getattr(manager, "can_spawn", []) or [])
|
|
if str(role_id or "").strip() in children
|
|
]
|
|
standing_reports = [role_id for role_id in children if role_id not in spawn_order]
|
|
org_graph[manager_id] = [*standing_reports, *spawn_order]
|
|
|
|
final_decider_role_id = ""
|
|
getter = getattr(self.org_engine, "get_final_decider_role_id", None)
|
|
if callable(getter):
|
|
try:
|
|
final_decider_role_id = str(getter() or "").strip()
|
|
except Exception:
|
|
logger.opt(exception=True).debug("failed to resolve final decider role for recruiter payload")
|
|
|
|
return {
|
|
"final_decider_role_id": final_decider_role_id,
|
|
"org_graph": org_graph,
|
|
}
|
|
|
|
async def build_recruitment_plan(
|
|
self,
|
|
runtime_spec: Any,
|
|
*,
|
|
domains: list[str],
|
|
project_id: str,
|
|
recruiter_feedback: list[str] | None = None,
|
|
recruitment_llm: Any | None = None,
|
|
recruitment_agent: str | None = None,
|
|
) -> RecruitmentPlan:
|
|
domains = list(domains or [])
|
|
feedback = list(recruiter_feedback or [])
|
|
active_llm = recruitment_llm if recruitment_llm is not None else self.llm
|
|
selected_recruitment_agent = normalize_recruitment_agent_choice(
|
|
recruitment_agent,
|
|
default="native",
|
|
) or "native"
|
|
needs = self._collect_needs(runtime_spec)
|
|
triage_by_role = await self._triage_staffing_for_needs(
|
|
needs,
|
|
recruiter_feedback=feedback,
|
|
llm=active_llm,
|
|
)
|
|
prepared_needs: list[dict[str, Any]] = []
|
|
selected_category_union: list[str] = []
|
|
for need in needs:
|
|
existing = self.org_engine.list_employees(role_id=need.role_id)
|
|
triage_action, selected_categories, category_rationale = triage_by_role.get(
|
|
need.role_id,
|
|
("direct_role_execution", [], "No staffing triage was produced for this role."),
|
|
)
|
|
for category in selected_categories:
|
|
if category not in selected_category_union:
|
|
selected_category_union.append(category)
|
|
prepared_needs.append(
|
|
{
|
|
"need": need,
|
|
"existing_employees": list(existing),
|
|
"candidates": [],
|
|
"triage_action": triage_action,
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
}
|
|
)
|
|
candidate_pool = self._recall_candidates_for_need(
|
|
selected_categories=selected_category_union,
|
|
)
|
|
employee_pool = self._recall_existing_employee_pool(
|
|
prepared_needs,
|
|
candidate_pool=candidate_pool,
|
|
selected_categories=selected_category_union,
|
|
project_id=project_id,
|
|
)
|
|
for item in prepared_needs:
|
|
item["candidates"] = candidate_pool
|
|
item["employee_pool"] = employee_pool
|
|
if active_llm:
|
|
proposals = await self._recruit_globally(
|
|
prepared_needs,
|
|
recruiter_feedback=feedback,
|
|
project_id=project_id,
|
|
llm=active_llm,
|
|
candidate_pool=candidate_pool,
|
|
employee_pool=employee_pool,
|
|
)
|
|
else:
|
|
proposals = [
|
|
self._heuristic_proposal_for_prepared_need(item, project_id=project_id)
|
|
for item in prepared_needs
|
|
]
|
|
plan_metadata = {
|
|
"project_id": project_id,
|
|
"execution_mode": str(getattr(runtime_spec, "metadata", {}).get("execution_mode", "company_mode") or "company_mode"),
|
|
"request_label": str(getattr(runtime_spec, "metadata", {}).get("request_label", "runtime") or "runtime"),
|
|
"recruitment_agent": selected_recruitment_agent,
|
|
}
|
|
plan = ensure_recruitment_plan_default_agents(
|
|
RecruitmentPlan(
|
|
company_profile=str(getattr(runtime_spec, "profile", "corporate") or "corporate"),
|
|
proposals=proposals,
|
|
recruiter_feedback=feedback,
|
|
metadata=plan_metadata,
|
|
)
|
|
)
|
|
summary = self.render_recruitment_summary(plan)
|
|
return ensure_recruitment_plan_default_agents(RecruitmentPlan(
|
|
company_profile=str(getattr(runtime_spec, "profile", "corporate") or "corporate"),
|
|
proposals=proposals,
|
|
recruiter_feedback=feedback,
|
|
summary=summary,
|
|
metadata=plan_metadata,
|
|
))
|
|
|
|
def render_recruitment_summary(self, plan: RecruitmentPlan) -> str:
|
|
execution_mode = str(plan.metadata.get("execution_mode", "company_mode") or "company_mode")
|
|
if execution_mode == "task_mode":
|
|
intro = "Task mode has a pending staffing decision before execution."
|
|
cancel_line = "Reply `deny` / `stop` to cancel task-mode execution."
|
|
else:
|
|
intro = "Company mode has a pending staffing decision before execution."
|
|
cancel_line = "Reply `deny` / `stop` to cancel company-mode execution."
|
|
lines = [
|
|
intro,
|
|
"",
|
|
f"Company profile: `{plan.company_profile}`",
|
|
]
|
|
if plan.recruiter_feedback:
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"Recruiter feedback so far:",
|
|
*[f"- {item}" for item in plan.recruiter_feedback[-5:]],
|
|
]
|
|
)
|
|
if not plan.proposals:
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"No staffing action is needed.",
|
|
"Reply `approve` to continue execution.",
|
|
]
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
lines.append("")
|
|
lines.append("Recruitment decisions:")
|
|
for proposal in plan.proposals:
|
|
role_label = proposal.role_labels[0] if proposal.role_labels else proposal.role_id
|
|
if proposal.status == "direct_role_execution":
|
|
lines.append(f"- Role `{proposal.role_id}` ({role_label}): use ordinary role execution without staffing.")
|
|
lines.append(f" Reason: {proposal.rationale}")
|
|
elif proposal.status == "existing_staff":
|
|
existing = ", ".join(proposal.existing_employee_ids) or "(existing staff)"
|
|
selected = proposal.existing_employee
|
|
if selected:
|
|
lines.append(
|
|
f"- Role `{proposal.role_id}` ({role_label}): keep existing employee "
|
|
f"`{selected.employee_name}` ({selected.employee_id})."
|
|
)
|
|
lines.append(
|
|
f" Experience score: {selected.experience_score}; learned skills: {len(selected.learned_skill_refs)}; "
|
|
f"existing pool: {existing}"
|
|
)
|
|
else:
|
|
lines.append(f"- Role `{proposal.role_id}` ({role_label}): reuse existing staff {existing}.")
|
|
lines.append(f" Reason: {proposal.rationale}")
|
|
elif proposal.status == "proposed_hire" and proposal.candidate:
|
|
lines.append(
|
|
f"- Role `{proposal.role_id}` ({role_label}): hire `{proposal.candidate.template_name}` "
|
|
f"as `{proposal.candidate.proposed_employee_name or proposal.candidate.proposed_employee_id}`."
|
|
)
|
|
if proposal.existing_employee_ids:
|
|
lines.append(
|
|
f" Compared against existing staff: {', '.join(proposal.existing_employee_ids)}"
|
|
)
|
|
lines.append(f" Reason: {proposal.rationale or proposal.candidate.rationale}")
|
|
else:
|
|
lines.append(f"- Role `{proposal.role_id}` ({role_label}): fallback to role-only execution.")
|
|
lines.append(f" Reason: {proposal.rationale}")
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"Reply `1` or `approve` / `continue` to accept these hires and start execution.",
|
|
"Reply `2` or `deny` / `stop` / `cancel` to reject this staffing proposal and stop execution.",
|
|
"Any other input will be treated as feedback or a suggestion for revising the staffing proposal.",
|
|
cancel_line,
|
|
]
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
def _collect_needs(self, runtime_spec: Any) -> list[RecruitmentNeed]:
|
|
"""One staffing need per role in the live org topology.
|
|
|
|
Recruitment runs once before execution enters the org, so it does not
|
|
consume runtime work-item projections. ``runtime_spec.metadata['original_request']``
|
|
is forwarded to the triage LLM as request-specific context.
|
|
"""
|
|
metadata = dict(getattr(runtime_spec, "metadata", {}) or {})
|
|
request_text = str(
|
|
getattr(runtime_spec, "original_request", "")
|
|
or metadata.get("original_request", "")
|
|
or ""
|
|
).strip()
|
|
grouped: dict[str, RecruitmentNeed] = {}
|
|
# Roles in the active topology.
|
|
for agent in self.org_engine.list_agents():
|
|
role_id = str(getattr(agent, "role_id", "") or "").strip()
|
|
if not role_id or role_id == "task_generalist":
|
|
continue
|
|
grouped[role_id] = RecruitmentNeed(
|
|
role_id=role_id,
|
|
role_name=str(getattr(agent, "name", "") or role_id),
|
|
role_responsibility=str(getattr(agent, "responsibility", "") or "").strip(),
|
|
request_text=request_text,
|
|
)
|
|
# Roles outside the topology that already have configured employees
|
|
# (e.g. custom roles registered via OrgConfig but not yet promoted to
|
|
# an active agent). Recruitment should still consider them.
|
|
try:
|
|
extra_employees = self.org_engine.list_employees()
|
|
except TypeError:
|
|
extra_employees = []
|
|
for employee in extra_employees:
|
|
role_id = str(getattr(employee, "role_id", "") or "").strip()
|
|
if not role_id or role_id == "task_generalist" or role_id in grouped:
|
|
continue
|
|
grouped[role_id] = RecruitmentNeed(
|
|
role_id=role_id,
|
|
role_name=role_id,
|
|
role_responsibility="",
|
|
request_text=request_text,
|
|
)
|
|
return list(grouped.values())
|
|
|
|
def _recall_candidates_for_need(
|
|
self,
|
|
*,
|
|
selected_categories: list[str],
|
|
) -> list[TalentTemplateConfig]:
|
|
merged: dict[str, TalentTemplateConfig] = {}
|
|
for candidate in self.talent_market.list_templates_by_categories(categories=selected_categories):
|
|
candidate_id = str(getattr(candidate, "id", "") or "").strip()
|
|
if candidate_id and candidate_id not in merged:
|
|
merged[candidate_id] = candidate
|
|
return list(merged.values())
|
|
|
|
def _recall_existing_employee_pool(
|
|
self,
|
|
prepared_needs: list[dict[str, Any]],
|
|
*,
|
|
candidate_pool: list[TalentTemplateConfig],
|
|
selected_categories: list[str],
|
|
project_id: str,
|
|
) -> list[Any]:
|
|
_ = project_id
|
|
categories = {
|
|
str(category or "").strip().lower()
|
|
for category in list(selected_categories or [])
|
|
if str(category or "").strip()
|
|
}
|
|
candidate_template_ids = {
|
|
str(getattr(candidate, "id", "") or "").strip()
|
|
for candidate in list(candidate_pool or [])
|
|
if str(getattr(candidate, "id", "") or "").strip()
|
|
}
|
|
same_role_ids = {
|
|
str(getattr(employee, "employee_id", "") or "").strip()
|
|
for item in prepared_needs
|
|
for employee in list(item.get("existing_employees") or [])
|
|
if str(getattr(employee, "employee_id", "") or "").strip()
|
|
}
|
|
try:
|
|
employees = self.org_engine.list_employees()
|
|
except Exception:
|
|
employees = []
|
|
selected: dict[str, Any] = {}
|
|
for employee in employees:
|
|
employee_id = str(getattr(employee, "employee_id", "") or "").strip()
|
|
if not employee_id:
|
|
continue
|
|
metadata = dict(getattr(employee, "metadata", {}) or {})
|
|
if metadata.get("is_default_employee") or metadata.get("is_fallback_employee"):
|
|
continue
|
|
category = str(getattr(employee, "category", "") or "").strip().lower()
|
|
template_id = str(getattr(employee, "template_id", "") or "").strip()
|
|
employee_domains = {
|
|
str(domain or "").strip().lower()
|
|
for domain in list(getattr(employee, "domains", []) or [])
|
|
if str(domain or "").strip()
|
|
}
|
|
if (
|
|
employee_id in same_role_ids
|
|
or category in categories
|
|
or template_id in candidate_template_ids
|
|
or bool(employee_domains & categories)
|
|
):
|
|
selected[employee_id] = employee
|
|
return list(selected.values())
|
|
|
|
def _heuristic_proposal_for_prepared_need(
|
|
self,
|
|
item: dict[str, Any],
|
|
*,
|
|
project_id: str,
|
|
) -> RecruitmentProposal:
|
|
need: RecruitmentNeed = item["need"]
|
|
existing_employees = list(item.get("existing_employees") or [])
|
|
employee_pool = list(item.get("employee_pool") or existing_employees)
|
|
candidates = list(item.get("candidates") or [])
|
|
triage_action = str(item.get("triage_action", "") or "")
|
|
selected_categories = list(item.get("selected_categories") or [])
|
|
category_rationale = str(item.get("category_rationale", "") or "")
|
|
metadata = {
|
|
"triage_action": triage_action,
|
|
"selected_categories": selected_categories,
|
|
"category_rationale": category_rationale,
|
|
}
|
|
role_labels = [need.role_name] if need.role_name else []
|
|
if triage_action == "direct_role_execution":
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="direct_role_execution",
|
|
rationale=category_rationale or "Ordinary role execution is sufficient; no staffing comparison is needed.",
|
|
role_labels=role_labels,
|
|
existing_employee_ids=[item.employee_id for item in existing_employees],
|
|
metadata={**metadata, "selection_source": "heuristic_direct"},
|
|
)
|
|
if employee_pool:
|
|
existing_payload = [
|
|
self._build_existing_employee_summary(
|
|
employee,
|
|
role_id=need.role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
)
|
|
for employee in employee_pool
|
|
]
|
|
selected = max(existing_payload, key=lambda item: float(item.get("experience_score", 0.0)))
|
|
employee = next(item for item in employee_pool if item.employee_id == selected["employee_id"])
|
|
recommendation = self._make_existing_employee_recommendation(
|
|
employee,
|
|
role_id=need.role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
rationale="Selected heuristically because an existing experienced employee was available.",
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="existing_staff",
|
|
rationale=recommendation.rationale,
|
|
role_labels=role_labels,
|
|
existing_employee=recommendation,
|
|
existing_employee_ids=[item.employee_id for item in existing_employees],
|
|
metadata={**metadata, "selection_source": "heuristic_existing"},
|
|
)
|
|
if candidates:
|
|
candidate = candidates[0]
|
|
recommendation = self._make_candidate_recommendation(
|
|
candidate,
|
|
rationale="Selected heuristically because no recruiter LLM was available.",
|
|
role_id=need.role_id,
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="proposed_hire",
|
|
rationale=recommendation.rationale,
|
|
role_labels=role_labels,
|
|
candidate=recommendation,
|
|
metadata={**metadata, "selection_source": "heuristic_hire"},
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="fallback_role_only",
|
|
rationale="No credible imported talent template matched this role need. Execution should fall back to the role-only path.",
|
|
role_labels=role_labels,
|
|
metadata={**metadata, "selection_source": "heuristic_fallback", "fallback_reason": "no_candidate_templates"},
|
|
)
|
|
|
|
async def _recruit_globally(
|
|
self,
|
|
prepared_needs: list[dict[str, Any]],
|
|
*,
|
|
recruiter_feedback: list[str],
|
|
project_id: str,
|
|
llm: Any,
|
|
candidate_pool: list[TalentTemplateConfig],
|
|
employee_pool: list[Any],
|
|
) -> list[RecruitmentProposal]:
|
|
if not prepared_needs:
|
|
return []
|
|
role_payloads: list[dict[str, Any]] = []
|
|
prepared_by_role: dict[str, dict[str, Any]] = {}
|
|
role_order: list[str] = []
|
|
candidate_payload = [
|
|
self.talent_market.build_candidate_summary(candidate)
|
|
for candidate in candidate_pool
|
|
]
|
|
employee_pool_payload = [
|
|
self._build_existing_employee_summary(
|
|
employee,
|
|
role_id=str(getattr(employee, "role_id", "") or ""),
|
|
domains=[],
|
|
project_id=project_id,
|
|
)
|
|
for employee in employee_pool
|
|
]
|
|
for item in prepared_needs:
|
|
need: RecruitmentNeed = item["need"]
|
|
existing_employees = list(item.get("existing_employees") or [])
|
|
existing_payload = [
|
|
self._build_existing_employee_summary(
|
|
employee,
|
|
role_id=need.role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
)
|
|
for employee in existing_employees
|
|
]
|
|
prepared_item = {
|
|
**item,
|
|
"existing_payload": existing_payload,
|
|
}
|
|
prepared_by_role[need.role_id] = prepared_item
|
|
role_order.append(need.role_id)
|
|
role_payloads.append(
|
|
{
|
|
"role_id": need.role_id,
|
|
"role_name": need.role_name,
|
|
"role_responsibility": need.role_responsibility,
|
|
"user_request": need.request_text,
|
|
"triage_action": str(item.get("triage_action", "") or ""),
|
|
"selected_categories": list(item.get("selected_categories") or []),
|
|
"category_rationale": str(item.get("category_rationale", "") or ""),
|
|
"existing_employees": existing_payload,
|
|
"current_role_employee_ids": [
|
|
str(employee.get("employee_id", "") or "").strip()
|
|
for employee in existing_payload
|
|
if str(employee.get("employee_id", "") or "").strip()
|
|
],
|
|
}
|
|
)
|
|
prompt_payload = {
|
|
"project_id": project_id,
|
|
"recruiter_feedback": list(recruiter_feedback),
|
|
**self._build_organization_payload(),
|
|
"employee_pool": employee_pool_payload,
|
|
"candidate_pool": candidate_payload,
|
|
"roles": role_payloads,
|
|
}
|
|
retry_feedback: list[str] = []
|
|
for attempt in range(1, 4):
|
|
payload = dict(prompt_payload)
|
|
if retry_feedback:
|
|
payload["retry_feedback"] = list(retry_feedback)
|
|
try:
|
|
raw = await llm.simple_chat(
|
|
prompt=json.dumps(payload, ensure_ascii=False),
|
|
system=GLOBAL_RECRUITER_PROMPT,
|
|
task_type="quick_tasks",
|
|
)
|
|
except Exception as exc:
|
|
retry_feedback.append(f"Recruiter LLM call failed: {type(exc).__name__}: {exc}")
|
|
continue
|
|
try:
|
|
data = self._parse_llm_json(raw)
|
|
except json.JSONDecodeError as exc:
|
|
retry_feedback.append(f"Response was not valid JSON: {exc.msg} at char {exc.pos}.")
|
|
continue
|
|
proposals_data = data.get("proposals")
|
|
if not isinstance(proposals_data, list):
|
|
retry_feedback.append("Response must contain a `proposals` array.")
|
|
continue
|
|
try:
|
|
return self._build_global_proposals_from_response(
|
|
proposals_data,
|
|
prepared_by_role=prepared_by_role,
|
|
role_order=role_order,
|
|
project_id=project_id,
|
|
attempt=attempt,
|
|
candidate_pool=candidate_pool,
|
|
employee_pool=employee_pool,
|
|
)
|
|
except ValueError as exc:
|
|
retry_feedback.append(str(exc))
|
|
logger.warning("Global recruiter exhausted retries; falling back to heuristic staffing decisions")
|
|
return [
|
|
self._heuristic_proposal_for_prepared_need(item, project_id=project_id)
|
|
for item in prepared_needs
|
|
]
|
|
|
|
@staticmethod
|
|
def _parse_llm_json(raw: str) -> dict[str, Any]:
|
|
text = str(raw or "").strip()
|
|
if text.startswith("```"):
|
|
text = text.split("\n", 1)[1] if "\n" in text else text[3:]
|
|
if text.endswith("```"):
|
|
text = text[:-3]
|
|
text = text.strip()
|
|
data = json.loads(text)
|
|
if not isinstance(data, dict):
|
|
raise json.JSONDecodeError("response must be a JSON object", text, 0)
|
|
return data
|
|
|
|
def _build_global_proposals_from_response(
|
|
self,
|
|
proposals_data: list[Any],
|
|
*,
|
|
prepared_by_role: dict[str, dict[str, Any]],
|
|
role_order: list[str],
|
|
project_id: str,
|
|
attempt: int,
|
|
candidate_pool: list[TalentTemplateConfig],
|
|
employee_pool: list[Any],
|
|
) -> list[RecruitmentProposal]:
|
|
expected_roles = set(role_order)
|
|
proposal_by_role: dict[str, RecruitmentProposal] = {}
|
|
candidate_by_id = {
|
|
str(getattr(candidate, "id", "") or "").strip(): candidate
|
|
for candidate in candidate_pool
|
|
if str(getattr(candidate, "id", "") or "").strip()
|
|
}
|
|
global_employee_by_id = {
|
|
str(getattr(employee, "employee_id", "") or "").strip(): employee
|
|
for employee in employee_pool
|
|
if str(getattr(employee, "employee_id", "") or "").strip()
|
|
}
|
|
for index, raw_item in enumerate(proposals_data):
|
|
if not isinstance(raw_item, dict):
|
|
raise ValueError(f"Proposal at index {index} must be a JSON object.")
|
|
role_id = str(raw_item.get("role_id", "") or "").strip()
|
|
if role_id not in expected_roles:
|
|
raise ValueError(f"Invalid role_id `{role_id}`. Choose one of: {', '.join(sorted(expected_roles))}.")
|
|
if role_id in proposal_by_role:
|
|
raise ValueError(f"Duplicate proposal for role_id `{role_id}`.")
|
|
status = str(raw_item.get("status", "fallback_role_only") or "").strip().lower()
|
|
if status not in VALID_RECRUITMENT_STATUSES:
|
|
raise ValueError(
|
|
f"Invalid status `{status}` for role `{role_id}`. "
|
|
f"Choose one of: {', '.join(sorted(VALID_RECRUITMENT_STATUSES))}."
|
|
)
|
|
item = prepared_by_role[role_id]
|
|
need: RecruitmentNeed = item["need"]
|
|
existing_employees = list(item.get("existing_employees") or [])
|
|
employee_by_id = {
|
|
str(getattr(employee, "employee_id", "") or "").strip(): employee
|
|
for employee in existing_employees
|
|
if str(getattr(employee, "employee_id", "") or "").strip()
|
|
}
|
|
metadata = {
|
|
"selection_source": "llm_global",
|
|
"attempts": attempt,
|
|
"triage_action": str(item.get("triage_action", "") or ""),
|
|
"selected_categories": list(item.get("selected_categories") or []),
|
|
"category_rationale": str(item.get("category_rationale", "") or ""),
|
|
}
|
|
rationale = str(raw_item.get("rationale", "") or "").strip()
|
|
role_labels = [need.role_name] if need.role_name else []
|
|
existing_employee_ids = list(employee_by_id)
|
|
if status == "direct_role_execution":
|
|
proposal_by_role[role_id] = RecruitmentProposal(
|
|
role_id=role_id,
|
|
status="direct_role_execution",
|
|
rationale=rationale or "Recruiter determined that ordinary role execution is sufficient.",
|
|
role_labels=role_labels,
|
|
existing_employee_ids=existing_employee_ids,
|
|
metadata=metadata,
|
|
)
|
|
continue
|
|
if status == "fallback_role_only":
|
|
proposal_by_role[role_id] = RecruitmentProposal(
|
|
role_id=role_id,
|
|
status="fallback_role_only",
|
|
rationale=rationale or "Recruiter determined that no provided option was a credible fit.",
|
|
role_labels=role_labels,
|
|
existing_employee_ids=existing_employee_ids,
|
|
metadata=metadata,
|
|
)
|
|
continue
|
|
if status == "existing_staff":
|
|
employee_id = str(raw_item.get("employee_id", "") or "").strip()
|
|
if employee_id not in global_employee_by_id:
|
|
raise ValueError(
|
|
f"Invalid employee_id `{employee_id}` for role `{role_id}`. "
|
|
f"Choose one of: {', '.join(sorted(global_employee_by_id))}."
|
|
)
|
|
recommendation = self._make_existing_employee_recommendation(
|
|
global_employee_by_id[employee_id],
|
|
role_id=role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
rationale=rationale or "Recruiter selected the existing employee based on role fit and prior experience.",
|
|
)
|
|
proposal_by_role[role_id] = RecruitmentProposal(
|
|
role_id=role_id,
|
|
status="existing_staff",
|
|
rationale=recommendation.rationale,
|
|
role_labels=role_labels,
|
|
existing_employee=recommendation,
|
|
existing_employee_ids=existing_employee_ids,
|
|
metadata=metadata,
|
|
)
|
|
continue
|
|
template_id = str(raw_item.get("template_id", "") or "").strip()
|
|
if template_id not in candidate_by_id:
|
|
raise ValueError(
|
|
f"Invalid template_id `{template_id}` for role `{role_id}`. "
|
|
f"Choose one of: {', '.join(sorted(candidate_by_id))}."
|
|
)
|
|
recommendation = self._make_candidate_recommendation(
|
|
candidate_by_id[template_id],
|
|
rationale=rationale or "Recruiter selected this candidate based on role and domain fit.",
|
|
role_id=role_id,
|
|
proposed_employee_name=str(raw_item.get("proposed_employee_name", "") or "").strip(),
|
|
)
|
|
proposal_by_role[role_id] = RecruitmentProposal(
|
|
role_id=role_id,
|
|
status="proposed_hire",
|
|
rationale=recommendation.rationale,
|
|
role_labels=role_labels,
|
|
candidate=recommendation,
|
|
existing_employee_ids=existing_employee_ids,
|
|
metadata=metadata,
|
|
)
|
|
missing = [role_id for role_id in role_order if role_id not in proposal_by_role]
|
|
if missing:
|
|
raise ValueError(f"Missing proposals for role_id(s): {', '.join(missing)}.")
|
|
return [proposal_by_role[role_id] for role_id in role_order]
|
|
|
|
async def _recruit_for_need(
|
|
self,
|
|
need: RecruitmentNeed,
|
|
*,
|
|
existing_employees: list[Any],
|
|
candidates: list[TalentTemplateConfig],
|
|
recruiter_feedback: list[str],
|
|
project_id: str,
|
|
selected_categories: list[str],
|
|
category_rationale: str,
|
|
) -> RecruitmentProposal:
|
|
existing_payload = [
|
|
self._build_existing_employee_summary(
|
|
employee,
|
|
role_id=need.role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
)
|
|
for employee in existing_employees
|
|
]
|
|
if not self.llm:
|
|
if existing_payload:
|
|
selected = max(existing_payload, key=lambda item: float(item.get("experience_score", 0.0)))
|
|
employee = next(item for item in existing_employees if item.employee_id == selected["employee_id"])
|
|
recommendation = self._make_existing_employee_recommendation(
|
|
employee,
|
|
role_id=need.role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
rationale="Selected heuristically because an existing experienced employee was available.",
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="existing_staff",
|
|
rationale=recommendation.rationale,
|
|
role_labels=[need.role_name] if need.role_name else [],
|
|
existing_employee=recommendation,
|
|
existing_employee_ids=[item.employee_id for item in existing_employees],
|
|
metadata={
|
|
"selection_source": "heuristic_existing",
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
},
|
|
)
|
|
candidate = candidates[0]
|
|
recommendation = self._make_candidate_recommendation(
|
|
candidate,
|
|
rationale="Selected heuristically because no recruiter LLM was available.",
|
|
role_id=need.role_id,
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="proposed_hire",
|
|
rationale=recommendation.rationale,
|
|
role_labels=[need.role_name] if need.role_name else [],
|
|
candidate=recommendation,
|
|
metadata={
|
|
"selection_source": "heuristic_hire",
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
},
|
|
)
|
|
|
|
candidate_payload = [
|
|
self.talent_market.build_candidate_summary(candidate)
|
|
for candidate in candidates
|
|
]
|
|
prompt_payload = {
|
|
"project_id": project_id,
|
|
"need": {
|
|
"role_id": need.role_id,
|
|
"role_name": need.role_name,
|
|
"role_responsibility": need.role_responsibility,
|
|
"user_request": need.request_text,
|
|
},
|
|
"recruiter_feedback": recruiter_feedback,
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
"existing_employees": existing_payload,
|
|
"new_candidates": candidate_payload,
|
|
}
|
|
retry_feedback: list[str] = []
|
|
max_attempts = 3
|
|
valid_template_ids = {candidate.id for candidate in candidates}
|
|
valid_employee_ids = {employee.employee_id for employee in existing_employees}
|
|
for attempt in range(1, max_attempts + 1):
|
|
payload = dict(prompt_payload)
|
|
if retry_feedback:
|
|
payload["retry_feedback"] = list(retry_feedback)
|
|
raw = await self.llm.simple_chat(
|
|
prompt=json.dumps(payload, ensure_ascii=False),
|
|
system=RECRUITER_PROMPT,
|
|
task_type="quick_tasks",
|
|
)
|
|
text = raw.strip()
|
|
if text.startswith("```"):
|
|
text = text.split("\n", 1)[1] if "\n" in text else text[3:]
|
|
if text.endswith("```"):
|
|
text = text[:-3]
|
|
text = text.strip()
|
|
try:
|
|
data = json.loads(text)
|
|
except json.JSONDecodeError as e:
|
|
retry_feedback.append(f"Response was not valid JSON: {e.msg} at char {e.pos}.")
|
|
continue
|
|
status = str(data.get("status", "fallback_role_only")).strip()
|
|
employee_id = str(data.get("employee_id", "")).strip()
|
|
template_id = str(data.get("template_id", "")).strip()
|
|
rationale = str(data.get("rationale", "")).strip()
|
|
proposed_name = str(data.get("proposed_employee_name", "")).strip()
|
|
if status == "fallback_role_only":
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="fallback_role_only",
|
|
rationale=rationale or "Recruiter determined that no provided template was a credible fit.",
|
|
role_labels=[need.role_name] if need.role_name else [],
|
|
metadata={
|
|
"selection_source": "llm",
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
},
|
|
)
|
|
if status == "existing_staff":
|
|
if employee_id not in valid_employee_ids:
|
|
retry_feedback.append(
|
|
f"Invalid employee_id `{employee_id}`. Choose one of: {', '.join(sorted(valid_employee_ids))}."
|
|
)
|
|
continue
|
|
chosen_employee = next(employee for employee in existing_employees if employee.employee_id == employee_id)
|
|
recommendation = self._make_existing_employee_recommendation(
|
|
chosen_employee,
|
|
role_id=need.role_id,
|
|
domains=[],
|
|
project_id=project_id,
|
|
rationale=rationale or "Recruiter selected the existing employee based on role fit and prior experience.",
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="existing_staff",
|
|
rationale=recommendation.rationale,
|
|
role_labels=[need.role_name] if need.role_name else [],
|
|
existing_employee=recommendation,
|
|
existing_employee_ids=[employee.employee_id for employee in existing_employees],
|
|
metadata={
|
|
"selection_source": "llm",
|
|
"attempts": attempt,
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
},
|
|
)
|
|
if template_id not in valid_template_ids:
|
|
retry_feedback.append(
|
|
f"Invalid template_id `{template_id}`. Choose one of: {', '.join(sorted(valid_template_ids))}."
|
|
)
|
|
continue
|
|
chosen = next(candidate for candidate in candidates if candidate.id == template_id)
|
|
recommendation = self._make_candidate_recommendation(
|
|
chosen,
|
|
rationale=rationale or "Recruiter selected this candidate based on role and domain fit.",
|
|
role_id=need.role_id,
|
|
proposed_employee_name=proposed_name,
|
|
)
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="proposed_hire",
|
|
rationale=recommendation.rationale,
|
|
role_labels=[need.role_name] if need.role_name else [],
|
|
candidate=recommendation,
|
|
existing_employee_ids=[employee.employee_id for employee in existing_employees],
|
|
metadata={
|
|
"selection_source": "llm",
|
|
"attempts": attempt,
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
},
|
|
)
|
|
logger.warning(f"Recruiter exhausted retries for role `{need.role_id}`; falling back to role-only execution")
|
|
return RecruitmentProposal(
|
|
role_id=need.role_id,
|
|
status="fallback_role_only",
|
|
rationale="Recruiter could not produce a valid staffing decision after retries.",
|
|
role_labels=[need.role_name] if need.role_name else [],
|
|
metadata={
|
|
"selection_source": "llm_fallback",
|
|
"selected_categories": list(selected_categories),
|
|
"category_rationale": category_rationale,
|
|
},
|
|
)
|
|
|
|
async def _triage_staffing_for_needs(
|
|
self,
|
|
needs: list[RecruitmentNeed],
|
|
*,
|
|
recruiter_feedback: list[str],
|
|
llm: Any | None = None,
|
|
) -> dict[str, tuple[str, list[str], str]]:
|
|
if not needs:
|
|
return {}
|
|
category_catalog = self.talent_market.list_category_catalog()
|
|
valid_categories = {item["category"] for item in category_catalog}
|
|
active_llm = llm if llm is not None else self.llm
|
|
if not category_catalog:
|
|
return {
|
|
need.role_id: self._heuristic_staffing_triage(need, category_catalog)
|
|
for need in needs
|
|
}
|
|
if not active_llm:
|
|
return {
|
|
need.role_id: self._heuristic_staffing_triage(need, category_catalog)
|
|
for need in needs
|
|
}
|
|
|
|
payload = {
|
|
"user_request": next((need.request_text for need in needs if need.request_text), ""),
|
|
"recruiter_feedback": recruiter_feedback,
|
|
**self._build_organization_payload(),
|
|
"roles": [
|
|
{
|
|
"role_id": need.role_id,
|
|
"role_name": need.role_name,
|
|
"role_responsibility": need.role_responsibility,
|
|
}
|
|
for need in needs
|
|
],
|
|
"category_catalog": category_catalog,
|
|
}
|
|
retry_feedback: list[str] = []
|
|
expected_roles = {need.role_id for need in needs}
|
|
for _ in range(3):
|
|
attempt_payload = dict(payload)
|
|
if retry_feedback:
|
|
attempt_payload["retry_feedback"] = list(retry_feedback)
|
|
try:
|
|
raw = await active_llm.simple_chat(
|
|
prompt=json.dumps(attempt_payload, ensure_ascii=False),
|
|
system=STAFFING_TRIAGE_PROMPT,
|
|
task_type="quick_tasks",
|
|
)
|
|
except Exception as exc:
|
|
retry_feedback.append(f"Staffing triage LLM call failed: {type(exc).__name__}: {exc}")
|
|
continue
|
|
text = raw.strip()
|
|
if text.startswith("```"):
|
|
text = text.split("\n", 1)[1] if "\n" in text else text[3:]
|
|
if text.endswith("```"):
|
|
text = text[:-3]
|
|
text = text.strip()
|
|
try:
|
|
data = json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
retry_feedback.append(f"Response was not valid JSON: {exc.msg} at char {exc.pos}.")
|
|
continue
|
|
roles_data = data.get("roles")
|
|
if not isinstance(roles_data, list):
|
|
retry_feedback.append("Response must contain a `roles` array.")
|
|
continue
|
|
try:
|
|
return self._build_triage_plan_from_response(
|
|
roles_data,
|
|
expected_roles=expected_roles,
|
|
valid_categories=valid_categories,
|
|
)
|
|
except ValueError as exc:
|
|
retry_feedback.append(str(exc))
|
|
return {
|
|
need.role_id: self._heuristic_staffing_triage(need, category_catalog)
|
|
for need in needs
|
|
}
|
|
|
|
@staticmethod
|
|
def _build_triage_plan_from_response(
|
|
roles_data: list[Any],
|
|
*,
|
|
expected_roles: set[str],
|
|
valid_categories: set[str],
|
|
) -> dict[str, tuple[str, list[str], str]]:
|
|
triage_by_role: dict[str, tuple[str, list[str], str]] = {}
|
|
for index, raw_item in enumerate(roles_data):
|
|
if not isinstance(raw_item, dict):
|
|
raise ValueError(f"Triage entry at index {index} must be a JSON object.")
|
|
role_id = str(raw_item.get("role_id", "") or "").strip()
|
|
if role_id not in expected_roles:
|
|
raise ValueError(f"Invalid role_id `{role_id}`. Choose one of: {', '.join(sorted(expected_roles))}.")
|
|
if role_id in triage_by_role:
|
|
raise ValueError(f"Duplicate triage entry for role_id `{role_id}`.")
|
|
action = str(raw_item.get("action", "category_screening") or "").strip().lower()
|
|
rationale = str(raw_item.get("rationale", "") or "").strip()
|
|
if action == "direct_role_execution":
|
|
triage_by_role[role_id] = (action, [], rationale)
|
|
continue
|
|
if action != "category_screening":
|
|
raise ValueError(
|
|
f"Invalid action `{action}` for role `{role_id}`. "
|
|
"Choose `direct_role_execution` or `category_screening`."
|
|
)
|
|
chosen = [
|
|
str(category).strip().lower()
|
|
for category in raw_item.get("categories", [])
|
|
if str(category).strip()
|
|
]
|
|
chosen = [category for category in chosen if category in valid_categories]
|
|
chosen = list(dict.fromkeys(chosen))[:3]
|
|
if not chosen:
|
|
raise ValueError(
|
|
f"Role `{role_id}` chose category_screening without valid categories. "
|
|
f"Choose 1 to 3 valid categories from: {', '.join(sorted(valid_categories))}."
|
|
)
|
|
triage_by_role[role_id] = ("category_screening", chosen, rationale)
|
|
missing = [role_id for role_id in sorted(expected_roles) if role_id not in triage_by_role]
|
|
if missing:
|
|
raise ValueError(f"Missing triage entries for role_id(s): {', '.join(missing)}.")
|
|
return triage_by_role
|
|
|
|
@staticmethod
|
|
def _need_text(need: RecruitmentNeed) -> str:
|
|
return " ".join(
|
|
text for text in (need.role_name, need.role_responsibility, need.request_text) if text
|
|
).strip()
|
|
|
|
def _heuristic_staffing_triage(
|
|
self,
|
|
need: RecruitmentNeed,
|
|
category_catalog: list[dict[str, Any]],
|
|
) -> tuple[str, list[str], str]:
|
|
combined_text = self._need_text(need).lower()
|
|
if len(combined_text) <= 24 or combined_text in {"hi", "hello", "你好", "您好", "thanks", "谢谢"}:
|
|
return "direct_role_execution", [], "The request is simple enough to handle without staffing."
|
|
if not category_catalog:
|
|
return "direct_role_execution", [], "No staffing catalog is available; use ordinary role execution."
|
|
selected = self._heuristic_category_selection(need, category_catalog)
|
|
if not selected:
|
|
return "direct_role_execution", [], "No strong staffing categories stood out."
|
|
return "category_screening", selected, "Selected heuristically from category descriptions."
|
|
|
|
def _heuristic_category_selection(
|
|
self,
|
|
need: RecruitmentNeed,
|
|
category_catalog: list[dict[str, Any]],
|
|
) -> list[str]:
|
|
need_tokens = set(
|
|
re.findall(
|
|
r"[a-z0-9][a-z0-9+-]{2,}",
|
|
self._need_text(need).lower(),
|
|
)
|
|
)
|
|
scored: list[tuple[float, str]] = []
|
|
for item in category_catalog:
|
|
category = str(item.get("category", "")).strip().lower()
|
|
description = str(item.get("description", "")).strip().lower()
|
|
template_count = int(item.get("template_count", 0) or 0)
|
|
category_tokens = set(re.findall(r"[a-z0-9][a-z0-9+-]{2,}", f"{category} {description}"))
|
|
score = float(len(need_tokens & category_tokens)) + min(template_count, 3) * 0.1
|
|
scored.append((score, category))
|
|
scored.sort(key=lambda item: (-item[0], item[1]))
|
|
selected = [category for score, category in scored if score > 0][:3]
|
|
if selected:
|
|
return selected
|
|
return [item["category"] for item in category_catalog[:2]]
|
|
|
|
def _build_existing_employee_summary(
|
|
self,
|
|
employee: Any,
|
|
*,
|
|
role_id: str,
|
|
domains: list[str],
|
|
project_id: str,
|
|
) -> dict[str, Any]:
|
|
experience_score = 0.0
|
|
learned_skill_refs: list[str] = []
|
|
if self.org_engine.employee_evolution:
|
|
organization_id = str(getattr(self.org_engine.config.org, "organization_id", "") or "").strip()
|
|
experience_score = self.org_engine.employee_evolution.get_experience_score(
|
|
employee.employee_id,
|
|
role_id=role_id,
|
|
domains=domains,
|
|
project_id=project_id,
|
|
organization_id=organization_id or None,
|
|
)
|
|
learned_skill_refs = self.org_engine.employee_evolution.get_learned_skill_refs(
|
|
employee.employee_id,
|
|
project_id=project_id,
|
|
)
|
|
return {
|
|
"employee_id": employee.employee_id,
|
|
"employee_name": employee.name,
|
|
"template_id": employee.template_id,
|
|
"home_role_id": employee.role_id,
|
|
"role_ids": (
|
|
list(self.org_engine.employee_role_ids(employee))
|
|
if hasattr(self.org_engine, "employee_role_ids")
|
|
else [employee.role_id]
|
|
),
|
|
"category": employee.category,
|
|
"experience_score": experience_score,
|
|
"learned_skill_refs": list(learned_skill_refs),
|
|
"description": employee.description,
|
|
}
|
|
|
|
def _make_existing_employee_recommendation(
|
|
self,
|
|
employee: Any,
|
|
*,
|
|
role_id: str,
|
|
domains: list[str],
|
|
project_id: str,
|
|
rationale: str,
|
|
) -> RecruitmentEmployeeRecommendation:
|
|
summary = self._build_existing_employee_summary(
|
|
employee,
|
|
role_id=role_id,
|
|
domains=domains,
|
|
project_id=project_id,
|
|
)
|
|
return RecruitmentEmployeeRecommendation(
|
|
employee_id=employee.employee_id,
|
|
employee_name=employee.name,
|
|
role_id=employee.role_id,
|
|
category=employee.category,
|
|
domains=list(employee.domains),
|
|
learned_skill_refs=list(summary.get("learned_skill_refs", [])),
|
|
experience_score=float(summary.get("experience_score", 0.0) or 0.0),
|
|
rationale=rationale,
|
|
metadata={"template_id": employee.template_id, "experience_mode": "with_experience"},
|
|
)
|
|
|
|
def _make_candidate_recommendation(
|
|
self,
|
|
candidate: TalentTemplateConfig,
|
|
*,
|
|
rationale: str,
|
|
role_id: str,
|
|
proposed_employee_name: str = "",
|
|
) -> RecruitmentCandidateRecommendation:
|
|
employee_name = proposed_employee_name.strip() or candidate.name.strip() or candidate.id
|
|
employee_id = self.talent_market.build_employee_id(role_id=role_id, template_id=candidate.id)
|
|
return RecruitmentCandidateRecommendation(
|
|
template_id=candidate.id,
|
|
template_name=candidate.name,
|
|
category=candidate.category,
|
|
domains=list(candidate.domains),
|
|
prompt_ref=candidate.prompt_ref,
|
|
preferred_external_agent=candidate.preferred_external_agent,
|
|
source_path=candidate.source_path,
|
|
rationale=rationale,
|
|
proposed_employee_name=employee_name,
|
|
proposed_employee_id=employee_id,
|
|
metadata={"source_repo": candidate.source_repo, "experience_mode": "template_only"},
|
|
)
|
|
|
|
|
|
def build_recruitment_plan_from_payload(data: dict[str, Any]) -> RecruitmentPlan:
|
|
return deserialize_recruitment_plan(data)
|
|
|
|
|
|
def build_recruitment_feedback(reply: str) -> str:
|
|
cleaned = re.sub(r"\s+", " ", reply.strip())
|
|
return cleaned
|