oc_orchestrator
Loading...
Searching...
No Matches
oc_orchestrator.dispatch Namespace Reference

Classes

class  DispatchError
class  DispatchHooks
class  _NoHooks
class  DispatchRequest
class  DispatchResult
class  _PlanDispatchData
class  PlanAnchors

Functions

subprocess.CompletedProcess[str] _run_git (Path repo_root, *str args, bool check=True)
Path _resolve_plan (Path plans_dir, str plan_number)
dict[str, Any] _frontmatter (Path path)
str _nonblank (dict[str, Any] data, str field, *, str context)
_PlanDispatchData _load_plan (Path path, str plan_number, str|None thinking_override)
PlanAnchors resolve_plan_anchors (Path plans_dir, str plan_number)
str _require_committed_on_master (Path repo_root, Path plan_path)
bool _branch_exists (Path repo_root, str branch)
str _base_sha (Path repo_root, str branch, str master_sha)
set[str] _template_field_names (str template)
str render_launch_template (Path template_path, dict[str, str] fields)
PrepareResult _prepare_worktree_env (DispatchRequest request, _PlanDispatchData plan, *, bool use_worktree_venv)
None _write_launch (Path path, str text)
dict[str, str] _launch_fields (DispatchRequest request, _PlanDispatchData plan, *, str base_sha, Path launch_path, str agent_id, bool use_worktree_venv)
DispatchResult dispatch (DispatchRequest request)
None _announce_dispatched (DispatchHooks hooks, DispatchResult result)
list[str] format_dispatch_suggestion (DispatchResult result, *, str model_attested, Path registry_path)
str _open_instance_summary (Path registry_path, str exclude_agent_id)

Variables

str _TEMPLATES_DIR = Path(__file__).resolve().parent / "templates"
str DEFAULT_LAUNCH_TEMPLATE = _TEMPLATES_DIR / "launch.md"
str SHARED_VENV_LAUNCH_TEMPLATE = _TEMPLATES_DIR / "launch_shared_venv.md"
tuple _REQUIRED_REQUIRES = ("tier", "repo", "thinking", "session")
 _VALID_THINKING = frozenset({"off", "low", "medium", "high", "max"})
 _VALID_SESSION = frozenset({"fresh", "continue", "either"})
 _LEFTOVER_TOKEN_RE = re.compile(r"\{[^{}\n]+\}|<[^<>\n]+>")
 _PLAN_NUMBER_RE = re.compile(r"^[0-9]{3}$")
dict _SESSION_EXPLANATIONS

Detailed Description

Fail-closed, one-shot plan dispatch orchestration.

Purpose
-------
Plan 221 P2/P3 (design §4.1). Every step of dispatching a plan either
goes through this module — which fills it correctly — or refuses loudly
with the reason named. It resolves the plan by number, validates its
``requires:`` block, creates branch and worktree idempotently, allocates
the ``agent-NNN`` identity, renders the launch file from the ONE
committed template, and refuses the WHOLE dispatch if any template field
would render unfilled. Hand-authoring a launch file is a workflow
violation from the moment this landed.

Responsibilities
----------------
- :class:`DispatchRequest` / :class:`DispatchResult` — the operator's
  inputs and the committed outputs of one attempt.
- :class:`DispatchHooks` / :data:`NO_HOOKS` — the plan-259 seam: one
  announcement after a dispatch commits, and an empty default.
- :class:`DispatchError` — the named refusal at the dispatch boundary.
- The refusal matrix (W3): missing/incomplete ``requires:``, plan absent
  from master, an open instance without ``--again``.
- All-or-nothing rendering (W4): no instance allocated and no launch
  file written when anything refuses.

Diagnostics
-----------
Domain: OC_CHAT.MAIN
Levels:
    L2 — lifecycle events (dispatch attempted, refused, completed).
    L3 — semantic details (plan resolution, template rendering).
    L4 — deep tracing.

Contracts
---------
- BASE_SHA is the point the branch actually diverges from master,
  recorded in the launch file and never re-derived: master's SHA for a
  branch created by this dispatch, and the merge-base of master and the
  branch tip for a pre-existing one (plan 223 P4). The two values
  coincide at creation, so a fresh dispatch is unchanged.
- Refusals raise :class:`DispatchError` BEFORE any state mutates; the
  registry allocation and the launch-file write share one transaction
  (via :meth:`AgentInstanceRegistry.dispatch_allocation`).
- Git operations run against the caller's ``repo_root`` only.
- The plan-259 hook is told AFTER everything has committed and its
  failure is reported on stderr, never raised: by then the worktree
  exists, and unmaking a real worktree because a log was unreachable
  would be the worse failure.

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_orchestrator.agent_instances (the runtime instance registry)
- yaml (plan frontmatter parsing; already a project dependency)
- oc_orchestrator.worktree_env (the git anchors, the per-worktree venv prepare and the dispatch timing ledger)

Function Documentation

◆ _announce_dispatched()

None oc_orchestrator.dispatch._announce_dispatched ( DispatchHooks hooks,
DispatchResult result )
protected
Tell the hooks once, after the dispatch has committed. Never raise.

Plan 259 P6. By the time this runs the branch, the worktree, its venv,
the instance row and the launch file all exist; a listener that cannot
record the fact is a reason to say so, not a reason to unmake any of
that. Undoing a real worktree because a log was unreachable would be
the worse failure by a wide margin.

Definition at line 666 of file dispatch.py.

Here is the caller graph for this function:

◆ _base_sha()

str oc_orchestrator.dispatch._base_sha ( Path repo_root,
str branch,
str master_sha )
protected
Return the SHA the worker's BASE diff should be measured against.

Plan 223 P4 (from the 225 gate finding). Recording master's CURRENT SHA
for a branch that was cut earlier is a lie the moment master moves: the
2026-09-01 dispatch rendered ``344fe8ce`` for a branch cut at
``b7962f5f``, so the worker's "diff against BASE" silently spanned every
commit master had gained in between. The truthful anchor for an existing
branch is where it diverged — the merge-base.

A branch this dispatch is about to CREATE has no divergence yet, so
master's SHA is both values at once and the fresh path is unchanged.

Definition at line 381 of file dispatch.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _branch_exists()

bool oc_orchestrator.dispatch._branch_exists ( Path repo_root,
str branch )
protected

Definition at line 370 of file dispatch.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _frontmatter()

dict[str, Any] oc_orchestrator.dispatch._frontmatter ( Path path)
protected

Definition at line 229 of file dispatch.py.

Here is the caller graph for this function:

◆ _launch_fields()

dict[str, str] oc_orchestrator.dispatch._launch_fields ( DispatchRequest request,
_PlanDispatchData plan,
* ,
str base_sha,
Path launch_path,
str agent_id,
bool use_worktree_venv )
protected

Definition at line 537 of file dispatch.py.

Here is the caller graph for this function:

◆ _load_plan()

_PlanDispatchData oc_orchestrator.dispatch._load_plan ( Path path,
str plan_number,
str | None thinking_override )
protected

Definition at line 256 of file dispatch.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _nonblank()

str oc_orchestrator.dispatch._nonblank ( dict[str, Any] data,
str field,
* ,
str context )
protected

Definition at line 249 of file dispatch.py.

Here is the caller graph for this function:

◆ _open_instance_summary()

str oc_orchestrator.dispatch._open_instance_summary ( Path registry_path,
str exclude_agent_id )
protected
Render open instances as ``agent-NNN (plan-id, state)``, or ``none``.

The instance this dispatch just allocated is excluded: it is the thing
being launched, not a candidate to reuse for it. A registry that cannot
be read reports that fact rather than claiming an empty pool, because
"none" and "unknown" are different answers to the operator's question.

Definition at line 738 of file dispatch.py.

◆ _prepare_worktree_env()

PrepareResult oc_orchestrator.dispatch._prepare_worktree_env ( DispatchRequest request,
_PlanDispatchData plan,
* ,
bool use_worktree_venv )
protected
Create the git anchors and, by default, the worktree's own venv.

Plan 241 P1/P4. ``--shared-venv`` (``use_worktree_venv=False``) keeps
the legacy behaviour — anchors only, the shared main venv in the brief.
Otherwise the anchors and the per-worktree environment are made in one
step and every refusal inside it aborts the whole dispatch: a silent
fallback to the shared venv would re-open the F17 shadow.

Definition at line 459 of file dispatch.py.

Here is the caller graph for this function:

◆ _require_committed_on_master()

str oc_orchestrator.dispatch._require_committed_on_master ( Path repo_root,
Path plan_path )
protected

Definition at line 326 of file dispatch.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _resolve_plan()

Path oc_orchestrator.dispatch._resolve_plan ( Path plans_dir,
str plan_number )
protected

Definition at line 211 of file dispatch.py.

Here is the caller graph for this function:

◆ _run_git()

subprocess.CompletedProcess[str] oc_orchestrator.dispatch._run_git ( Path repo_root,
*str args,
bool check = True )
protected

Definition at line 197 of file dispatch.py.

Here is the caller graph for this function:

◆ _template_field_names()

set[str] oc_orchestrator.dispatch._template_field_names ( str template)
protected

Definition at line 416 of file dispatch.py.

Here is the caller graph for this function:

◆ _write_launch()

None oc_orchestrator.dispatch._write_launch ( Path path,
str text )
protected

Definition at line 522 of file dispatch.py.

Here is the caller graph for this function:

◆ dispatch()

DispatchResult oc_orchestrator.dispatch.dispatch ( DispatchRequest request)
Validate, prepare git state, allocate an instance, and render launch.

Definition at line 573 of file dispatch.py.

Here is the call graph for this function:

◆ format_dispatch_suggestion()

list[str] oc_orchestrator.dispatch.format_dispatch_suggestion ( DispatchResult result,
* ,
str model_attested,
Path registry_path )
Build the operator-facing suggestion lines for a completed dispatch.

Plan 223 P4b. The suggestion line spells out what the session field
constrains, and a following line names the instances that are actually
open right now — the concrete reuse candidates, rather than leaving the
operator to remember which windows are still alive.

NOTE (scope): these are candidate INSTANCES, not sessions. Naming the
actual session to continue requires ``agents.sqlite.session_uuid`` to be
populated, which is plan 224's session-linkage territory and deliberately
out of scope here; until then the operator maps instance to window.

Definition at line 702 of file dispatch.py.

◆ render_launch_template()

str oc_orchestrator.dispatch.render_launch_template ( Path template_path,
dict[str, str] fields )
Render the single launch template or refuse any unresolved field.

Definition at line 437 of file dispatch.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolve_plan_anchors()

PlanAnchors oc_orchestrator.dispatch.resolve_plan_anchors ( Path plans_dir,
str plan_number )
Resolve one plan number to the branch and worktree it declares.

Plan 241 P1/P5. ``prepare-worktree`` and ``teardown-worktree`` act on
exactly the anchors ``dispatch`` would use, from the same parser, so the
three commands can never disagree about which tree they mean.

Definition at line 311 of file dispatch.py.

Here is the call graph for this function:

Variable Documentation

◆ _LEFTOVER_TOKEN_RE

oc_orchestrator.dispatch._LEFTOVER_TOKEN_RE = re.compile(r"\{[^{}\n]+\}|<[^<>\n]+>")
protected

Definition at line 110 of file dispatch.py.

◆ _PLAN_NUMBER_RE

oc_orchestrator.dispatch._PLAN_NUMBER_RE = re.compile(r"^[0-9]{3}$")
protected

Definition at line 111 of file dispatch.py.

◆ _REQUIRED_REQUIRES

tuple oc_orchestrator.dispatch._REQUIRED_REQUIRES = ("tier", "repo", "thinking", "session")
protected

Definition at line 107 of file dispatch.py.

◆ _SESSION_EXPLANATIONS

dict oc_orchestrator.dispatch._SESSION_EXPLANATIONS
protected
Initial value:
1= {
2 "continue": "an existing worker session may be reused; fresh also works",
3 "fresh": "a NEW session is required; an existing one may not be reused",
4 "either": "any session satisfies this plan",
5}

Definition at line 695 of file dispatch.py.

◆ _TEMPLATES_DIR

str oc_orchestrator.dispatch._TEMPLATES_DIR = Path(__file__).resolve().parent / "templates"
protected

Definition at line 102 of file dispatch.py.

◆ _VALID_SESSION

oc_orchestrator.dispatch._VALID_SESSION = frozenset({"fresh", "continue", "either"})
protected

Definition at line 109 of file dispatch.py.

◆ _VALID_THINKING

oc_orchestrator.dispatch._VALID_THINKING = frozenset({"off", "low", "medium", "high", "max"})
protected

Definition at line 108 of file dispatch.py.

◆ DEFAULT_LAUNCH_TEMPLATE

str oc_orchestrator.dispatch.DEFAULT_LAUNCH_TEMPLATE = _TEMPLATES_DIR / "launch.md"

Definition at line 103 of file dispatch.py.

◆ SHARED_VENV_LAUNCH_TEMPLATE

str oc_orchestrator.dispatch.SHARED_VENV_LAUNCH_TEMPLATE = _TEMPLATES_DIR / "launch_shared_venv.md"

Definition at line 106 of file dispatch.py.