#!/usr/bin/env python3
"""Deterministic Codex guidance compiled from one resolved MMO profile."""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any

PROFILE_SKILL_NAME = "mmo-profile-orchestration"
PROFILE_SKILL_RELATIVE_PATH = f"guidance/skills/{PROFILE_SKILL_NAME}/SKILL.md"


def agent_guidance_relative_path(agent_id: str) -> str:
    return f"guidance/agents/{agent_id}.md"


def coordination_capable_agents(resolved: Mapping[str, Any]) -> list[str]:
    return sorted(
        agent_id
        for agent_id, agent in resolved["agents"].items()
        if agent.get("can_spawn") or agent.get("controls")
    )


def _mechanical_limit_lines(resolved: Mapping[str, Any]) -> list[str]:
    coordination = resolved["coordination"]
    orchestration = coordination["orchestration"]
    lines = [f"- Orchestration: `{orchestration}`."]
    if orchestration in {"mcp", "hybrid"}:
        lines.extend(
            [
                "- Maximum simultaneously admitted Agent MCP jobs plus the root: "
                f"{coordination['max_active_agents']}.",
                "- Terminal, suspended, and cold-paused MCP jobs release admission capacity; "
                "sequential delegation is not limited by a lifetime spawn counter.",
                f"- Maximum MCP delegation depth: {coordination['max_depth']}.",
                f"- Maximum active MCP writers: {coordination['max_active_writers']}.",
                "- MCP write-scope conflicts are rejected mechanically.",
                f"- MCP result visibility: {coordination['result_visibility']}.",
            ]
        )
    if orchestration in {"native", "hybrid"}:
        lines.append(
            "- Maximum concurrent native spawned threads, excluding the root: "
            f"{coordination['native_max_concurrent_threads']}."
        )
    lines.append(f"- Contradictions are resolved by `{coordination['contradiction_policy']}`.")
    return lines


def _profile_coordination_text(resolved: Mapping[str, Any]) -> str:
    coordination = resolved["coordination"]
    root = resolved["profile"]["root"]
    orchestration = coordination["orchestration"]
    if orchestration == "mcp":
        backend_text = """Delegation is handled by the `mmo_mesh` Agent MCP supervisor. Use its
`agent_spawn` or `agents_spawn` tools. Each child is an isolated asynchronous
Codex app-server worker with a durable thread, event trace, partial evidence,
bounded lineage, resource admission, write-scope leasing, lifecycle control,
and output-contract validation."""
    elif orchestration == "native":
        backend_text = """Delegation is handled by Codex native subagents. Use the native agent tools
and the generated custom roles. Native threads integrate with `/agent` and have
lower launch overhead, but MMO cannot mechanically enforce per-child write
scopes, output contracts, or every spawn-graph edge. Keep writes disjoint and
validate all material results."""
    else:
        backend_text = """Both delegation paths are available.

- Use Codex native subagents for low-latency, read-heavy, tightly coupled work
  where the `/agent` UI and shared live context are valuable.
- Use the `mmo_mesh` Agent MCP supervisor for durable asynchronous jobs, strict
  role/model pinning, nested bounded delegation, output contracts, explicit
  write scopes, live steering, detach-safe continuation, auditability, or
  low-trust participants.

Do not launch the same assignment through both paths unless independent
redundancy is intentional. Native and MCP results are both evidence that the
caller must reconcile."""
    limits = "\n".join(_mechanical_limit_lines(resolved))
    return f"""## Codex MMO coordination contract

This session is pinned to immutable profile `{resolved["profile"]["id"]}` and logical profile
`{resolved["logical_hash"][:16]}`. The root role is `{root}`.
Orchestration backend: `{orchestration}`.

{backend_text}

Runtime policy:
{limits}

Keep the immediate critical path. Launch independent side work early, then
continue non-overlapping work. Do not wait merely because a child exists. Wait
only when the next required action depends on unfinished output. Treat every
child result as evidence, not authority. Resolve disagreement from source,
commands, tests, specifications, or other reproducible primary evidence; never
decide by model vote.
"""


def agent_instructions_text(resolved: Mapping[str, Any], agent_id: str, *, is_root: bool) -> str:
    agent = resolved["agents"][agent_id]
    model = resolved["models"][agent["model"]]
    route = resolved["routes"][model["route"]]
    role = "root integration authority" if is_root else "delegated participant"
    native_children = [
        child for child in agent["can_spawn"] if "native" in resolved["agents"][child]["backends"]
    ]
    mcp_children = [
        child for child in agent["can_spawn"] if "mcp" in resolved["agents"][child]["backends"]
    ]
    child_lines: list[str] = []
    for child in agent["can_spawn"]:
        child_agent = resolved["agents"][child]
        backends = "/".join(child_agent["backends"])
        child_lines.append(
            f"- `{child}` via {backends}: {child_agent.get('description') or 'profile participant'}"
        )
    children_text = "\n".join(child_lines) or "- none"
    if agent["execution_mode"] == "goal":
        execution_text = f"""Execution mode: durable Codex `goal`. The initial token budget is
{agent["goal_token_budget"]} and the profile ceiling is {agent["max_goal_token_budget"]}.
Silence for {agent["stall_warning_seconds"]} seconds produces an operator warning only; it does
not interrupt the model. A terminal schema turn may use up to
{agent["finalization_grace_seconds"]} seconds after investigation is complete. The host owns
token accounting and lifecycle state. Never estimate elapsed time or emit checkpoint prose merely
to prove liveness. A final assistant message does not finish an active goal. When the objective is
actually achieved and no required work remains, call `update_goal` with `status="complete"` in the
terminal turn, then provide the final result. Use `blocked` only under the tool-defined repeated-
impasse rule; do not use it for ordinary uncertainty, slow work, or a nearly exhausted budget."""
    else:
        execution_text = f"""Execution mode: one durable Codex `turn`, with no profile wall-clock
task deadline. Silence for {agent["stall_warning_seconds"]} seconds produces an operator warning
only. A strict terminal repair may use up to {agent["finalization_grace_seconds"]} seconds. Never
estimate elapsed time or emit checkpoint prose merely to prove liveness."""
    text = f"""# Codex MMO agent: {agent_id}

You are the `{agent_id}` {role} in profile `{resolved["profile"]["id"]}`.

Model binding: `{model["upstream_id"]}` through route `{model["route"]}` ({route["name"]}).
Trust policy: `{agent["trust"]}`.
Verification policy: `{agent["verification"]}`.
Maximum permissions: `{agent["permissions"]}`.
{execution_text}

Permitted child roles and execution paths:
{children_text}

{_profile_coordination_text(resolved)}
"""
    if not is_root:
        text += """
The parent and root retain ownership of integration and the final user-facing
answer. Stay inside the delegated objective. Do not broaden scope or make an
unstated architectural or product decision. Preserve unrelated changes and
return precise evidence, validation, risks, and blockers.
"""
    if agent["can_spawn"]:
        text += """
## Required delegation checkpoint

Use `$mmo-profile-orchestration` before prolonged work. Within at most three
substantive task calls, or before starting a second independent workstream,
identify work retained here and eligible independent work for a direct child.
A substantive call performs repository discovery, shell execution, external
lookup, analysis, or implementation; loading guidance and managing an existing
child do not count.

For a nontrivial task, launch at least one eligible independent branch early.
When two independent branches are eligible and capacity permits, batch-launch
them. If no branch is launched, state the concrete reason before continuing:
the task is atomic, no direct child is independently useful, a required route
is unavailable, a graph or resource limit is exhausted, the user prohibited
delegation, or an authority/tool boundary prevents a safe handoff. Do not use a
generic claim that solo work is easier.
"""
    if native_children:
        text += """
## Native delegation

Use the generated Codex native roles for fast read-heavy parallelism and
closely coupled work. Native agents share the Codex process and workspace; do
not assume MMO write-scope or output-contract enforcement applies to them.
"""
    if mcp_children or agent.get("controls"):
        text += """
## Agent MCP delegation

Use `mmo_mesh` for durable asynchronous jobs, strict model pinning, disjoint
write scopes, contract validation, nested bounded delegation, cancellation, or
low-trust roles. Continue separate work after spawning when this role has spawn
authority. Start with `agent_list`: controls use opaque `agent_run_ref` values,
never guessed job IDs or model-supplied identities. Inspect or trace an
authorized long-running agent when its state matters; silence produces a
warning, not an automatic failure. Use only the actions granted for the target:
steer, answer pending input, change an allowed effort, interrupt the current
turn, pause continued work, continue the same thread (or extend a recoverable
goal within its compiled token ceiling), detach without stopping work, compact,
fork, request terminal serialization, or fully stop while retaining evidence.
Use the revision from the latest inspection for every mutating control.

Use the `progress_revision` values from `agent_status` or a prior
`agents_wait` call as the exact `after_revision` map for every requested job;
this returns compact state on the first
durable change instead of repeatedly injecting unchanged job metadata. Result
previews are opt-in and bounded. Read each material terminal result through
`agent_result`, beginning at cursor 0 and following every returned
`next_cursor` until it is null. Never read MMO supervisor state, job result
files, event logs, stderr files, or sockets directly from the state directory;
use `agent_result`, `agent_inspect`, `agent_trace`, and `agent_trace_record`.
Page a truncated trace summary through its `record_cursor`. When this role
has lineage disposition authority, explicitly accept or reject a successfully
completed result before relying on it and integrate a writable result only
after acceptance and review.
A lost client or app-server transport does not imply lost work. After recovery,
use `agent_list`, `agent_status`, and `agent_inspect` to find retained runs before
spawning any replacement. Continue the same suspended run when its original
objective remains useful. A replacement turn-mode host first settles an
orphaned active turn and starts at most one continuation; a terminal result
that completed during that race remains authoritative.
A provider limit, transport failure, malformed tool call, or failed terminal
turn remains typed on the same run with raw error and partial evidence. Treat
provider reset text without a timezone as provider-local/unspecified; do not
invent a timezone or replace the affected role with an undeclared route.
A detached job retains its live host, thread, and evidence. A cold-paused MCP
job retains its persisted thread and evidence while retiring its host and
releasing execution capacity; continuation starts one replacement host for
that same thread after fresh admission. Native pause remains logical because
native threads share the root host. A suspended job also retains its persisted
thread and evidence. Failed, stopped, or cancelled jobs retain inspectable
evidence but cannot be dispositioned as successful results.
"""
    if agent["can_spawn"]:
        text += """
You remain responsible for consuming and reconciling every material descendant
result before reporting upward.
"""
    elif agent.get("controls"):
        text += """
You have control authority but no spawn authority. Do not create agents. Use
the exact control graph only to unblock, correct, preserve, or conclude work
that another authorized role already admitted.
"""
    else:
        text += "\nYou are a true leaf participant with no spawn or control authority.\n"
    if agent["trust"] == "low":
        text += """
## Low-trust evidence boundary

Perform only bounded, literal, directly verifiable work. Do not infer intent,
architecture, correctness, causality, or recommended action unless the task and
contract explicitly permit it. Report conflicts without choosing a winner. The
parent must independently verify every material claim.
"""
    if agent["verification"] == "root_adjudication":
        text += (
            "\nYour conclusions are adversarial input for root adjudication, not final decisions.\n"
        )
    profile_text = agent.get("instructions_text", "").strip()
    if profile_text:
        text += "\n## Profile-specific role instructions\n\n" + profile_text + "\n"
    return text


def _cell(value: Any) -> str:
    return str(value).replace("|", "\\|").replace("\n", " ").strip()


def profile_skill_text(resolved: Mapping[str, Any]) -> str:
    profile_id = str(resolved["profile"]["id"])
    coordination = resolved["coordination"]
    role_rows: list[str] = []
    for agent_id in sorted(resolved["agents"]):
        agent = resolved["agents"][agent_id]
        backends = "/".join(agent.get("backends", [])) or "root"
        task_kinds = ", ".join(agent.get("allowed_task_kinds", [])) or "root-owned"
        children = ", ".join(agent.get("can_spawn", [])) or "none"
        role_rows.append(
            "| "
            + " | ".join(
                _cell(value)
                for value in (
                    f"`{agent_id}`",
                    backends,
                    task_kinds,
                    agent["permissions"],
                    f"{agent['trust']}/{agent['verification']}",
                    children,
                )
            )
            + " |"
        )
    rows = "\n".join(role_rows)
    control_rows = "\n".join(
        f"- `{agent_id}` -> `{target}`: " + ", ".join(f"`{action}`" for action in grant["actions"])
        for agent_id, agent in sorted(resolved["agents"].items())
        for target, grant in sorted(agent.get("controls", {}).items())
    )
    orchestration = coordination["orchestration"]
    if orchestration == "native":
        path_description = "Codex native agents"
        execution_paths = """- Use the generated roles through Codex native agent tools. They are
  appropriate for fast, read-heavy, tightly coupled work. Keep writes disjoint
  because MMO does not enforce native write scopes or result contracts
  mechanically.
- `mmo_mesh` Agent MCP delegation is not available in this profile."""
    elif orchestration == "mcp":
        path_description = "mmo_mesh"
        execution_paths = """- Use MCP roles through `mmo_mesh`. Tool MCP servers such as IDA or
  Firecrawl provide capabilities; they do not launch agents. `mmo_mesh` owns
  agent lineage, admission, job state, cancellation, contracts, and result
  decisions.
- Codex native agent delegation is not available in this profile."""
    else:
        path_description = "native agents or mmo_mesh"
        execution_paths = """- Use native roles through Codex native agent tools. They are appropriate for
  fast, read-heavy, tightly coupled work. Keep writes disjoint because MMO does
  not enforce native write scopes or result contracts mechanically.
- Use MCP roles through `mmo_mesh`. Tool MCP servers such as IDA or Firecrawl
  provide capabilities; they do not launch agents. `mmo_mesh` owns agent
  lineage, admission, job state, cancellation, contracts, and result decisions.
- Do not send the same assignment through both paths unless independent
  reproduction or adversarial diversity is the explicit objective."""
    has_mcp_lifecycle = orchestration in {"mcp", "hybrid"} and any(
        agent.get("controls")
        or any(
            "mcp" in resolved["agents"][child].get("backends", [])
            for child in agent.get("can_spawn", [])
        )
        for agent in resolved["agents"].values()
    )
    mcp_lifecycle = ""
    if has_mcp_lifecycle:
        mcp_lifecycle = """
## Use the MCP lifecycle

For each MCP task, provide an objective, necessary context and paths, non-goals,
the required deliverable or result contract, and validation evidence. Use
`agents_spawn` for independent batches and `agent_spawn` for one branch.

Remain productive while jobs run. Check status only when useful. Call
`agents_wait` only at a genuine dependency barrier. Pass the latest exact
per-job `progress_revision` map for every requested job as `after_revision` so unchanged work does not bloat
the caller context; the call returns when durable state changes or its bounded
wait expires. Result previews are opt-in and never the complete result. Read
every terminal result with
`agent_result`: begin at cursor 0 and keep calling it with each `next_cursor`
until `next_cursor` is null. Concatenate text pages in cursor order without
overlap; a complete strict structured result may instead arrive once as JSON.
Never bypass this lifecycle by opening MMO job result files, event logs, stderr
files, sockets, or other supervisor state directly. Use `agent_result`,
`agent_inspect`, and `agent_trace`; when a trace summary is truncated, use its
`record_cursor` with `agent_trace_record` and page through every `next_cursor`.
Same-user filesystem access is not an authorization boundary.
Use `agent_list` to discover root, native, and MCP runs plus their opaque refs;
use `agent_status` for a known supervised job, then use `agent_inspect` and
`agent_trace` instead of polling blindly. After any client or transport
recovery, discover and inspect retained work before spawning replacements.
Continue the same suspended run when its objective remains useful. Mutating
controls are compare-and-swap operations: inspect first and pass the returned
revision. Use `agent_steer` to add direction to an active turn without replacing
its existing task. `agent_interrupt` stops only the current turn and an active
goal may continue; `agent_pause` first pauses the goal, interrupts it, retains
partial evidence, and cold-retires a supervised MCP host.
`agent_detach` removes the client while work continues, `agent_continue`
reactivates recoverable work (and may raise its token budget only within the
compiled ceiling), and `agent_stop` pauses, interrupts, retains evidence, and
retires a supervised MCP host or terminates a native run without retiring its
shared root host. Use `agent_respond` for pending requests with the exact
method-specific response shape, `agent_finalize` for strict terminal
serialization, `agent_compact` for thread compaction, `agent_set_effort` within
the role grant. `agent_fork` normally admits an independent MCP job; a native
fork instead inherits its role, cwd, and sandbox in the shared root host, obeys
the native-thread limit, and cannot accept MCP write-scope or attachment overrides.

For a successfully completed job, use `agent_result_accept` or
`agent_result_reject` with a concrete reason when those lineage-authority tools
are exposed to the current role. A detached job keeps its live host. A
cold-paused supervised MCP job keeps its thread, trace, partial evidence, and
artifacts while releasing host capacity; a paused native thread remains in its
shared root host. A suspended job keeps the same durable evidence;
continuation starts or reattaches exactly one host for that thread after fresh
admission. Failed, stopped, and
cancelled jobs cannot be dispositioned; preserve their status and uncertainty.
Integrate an accepted writable patch only after reviewing its scope and tests.
Use `agent_cancel` only for work that is stale, superseded, unsafe, or no longer
worth its cost; cancellation is immediate and distinct from evidence-preserving
finalization.

Every root and supervised MCP worker owns one Unix app-server host and durable
thread; native agents are durable child threads inside their parent root host.
Interactive clients attach to the current root generation of the immutable MMO
session and run. An intentional stock-TUI fresh-context action may create a new
top-level Codex root generation only while the verified attached root client is
idle; it does not create another MMO session or run. Resume by the MMO session
ID or any predecessor root-thread ID attaches to the current generation. This
is host-owned lifecycle: do not simulate it by spawning a replacement root or
starting another MMO session. A replacement turn-mode worker host first settles
an orphaned active turn before starting at most one same-thread continuation;
a terminal result completed during that race is preserved. Goal roles are
bounded by Codex token accounting; turn roles have no task wall clock. Stall
intervals are warning-only and provider/model slowness does not erase work.
Do not tell a model to watch a clock or emit periodic checkpoint prose. Strict
contracts apply to the
explicit terminal serialization turn, followed by at most one same-thread
repair.

Resolve conflicts with primary evidence, not voting or model reputation. A
worker failure, budget suspension, unavailable route, or malformed contract is explicit
uncertainty; it is not permission to silently substitute another route.
"""
        if control_rows:
            mcp_lifecycle += f"""
## Control graph

{control_rows}
"""
    limits = "\n".join(_mechanical_limit_lines(resolved))
    return f"""---
name: {PROFILE_SKILL_NAME}
description: "Coordinate the resolved Codex MMO profile {profile_id}. Use for nontrivial work when the current MMO role can delegate or control durable work through {path_description}, especially for parallel investigation, specialist work, independent verification, or bounded nested delegation."
---

# Orchestrate the profile

Identify the current role from `AGENTS.md`. Spawn only its direct children and
obey the exact profile graph and limits below. Keep ownership of the critical
path and final integration.

## Delegation checkpoint

This checkpoint applies only when the current role's `Direct children` cell is
not `none`. A control-only role must not spawn; it should use the MCP lifecycle
and exact control graph below only at a real dependency, correction, or risk
boundary.

Within at most three substantive task calls, or before entering a second
independent workstream:

1. State the critical-path work retained in the current role.
2. Identify independent work matched to a permitted child.
3. Launch at least one useful branch for a nontrivial decomposable task.
4. Batch-launch two or more independent branches when capacity permits.
5. Continue non-overlapping work immediately after launch.

If delegation is skipped, state one concrete reason before continuing: atomic
task, no independently useful direct child, unavailable route, exhausted graph
or resource limit, explicit user prohibition, or an authority/tool boundary.
Do not treat unfamiliarity with the orchestration tools as a skip reason.

## Choose the execution path

{execution_paths}
{mcp_lifecycle}

## Profile graph

| Role | Backend | Task kinds | Permissions | Trust/verification | Direct children |
|---|---|---|---|---|---|
{rows}

## Mechanical limits

{limits}
"""


def compiled_guidance(resolved: Mapping[str, Any]) -> dict[str, str]:
    root = resolved["profile"]["root"]
    payload = {
        agent_guidance_relative_path(agent_id): agent_instructions_text(
            resolved, agent_id, is_root=agent_id == root
        )
        for agent_id in sorted(resolved["agents"])
    }
    if coordination_capable_agents(resolved):
        payload[PROFILE_SKILL_RELATIVE_PATH] = profile_skill_text(resolved)
    return dict(sorted(payload.items()))
