Skip to Content
Developer DocsServicesOrchestration Engine

Orchestration Engine

The Orchestration Engine is the core agent execution runtime for AEGIS. It uses LangGraph to build a stateful graph pipeline that processes user messages through system prompts, memory retrieval, LLM calls, tool execution, skill injection, HITL approval checkpoints, and output formatting.

Overview

This service is the domain-neutral agent runtime at the center of AEGIS. Compliance domains are installable packs discovered at startup (packs/<name>/manifest.yaml, or out-of-tree via PACKS_DIR), and agent behavior — skills, routers, rules, prompts, settings — is tenant-configurable data served by the agent-config-service, not code in this repo. The five bundled packs (rule_37, rule_32, form_pr, flaring_monitor, epa_oooob) are all part of the first installed vertical (Texas RRC oil & gas / EPA); adding a domain is a new pack directory, not a core edit.

When a user sends a message to an AI agent, the orchestration engine:

  1. Assembles the agent’s system prompt (persona + identity; for general mode, a router manifest sealed at conversation birth) and conversation history
  2. Retrieves working memory and episodic memories
  3. Calls the LLM (via LiteLLM) with budget enforcement
  4. Executes any tool calls the LLM requests — including the R36 selection tools pull_routers / select_skill, which load skills mid-turn
  5. Checks if HITL approval is required
  6. Formats the final output

(The legacy marker-scanning skill selection hop was retired in R36, and the vestigial synthesis LLM phase was removed entirely in R41 B0 — the graph runs a single LLM node and approval_node flows straight to output_format.)

Beyond the core agent pipeline, the orchestration engine also hosts the compliance dashboard API, checklist management, rule lifecycle, and workspace SSE endpoints.

Port & Language

PropertyValue
Port8001
LanguagePython 3.12
FrameworkFastAPI
Entry pointsrc/orchestration/main.py

Key Endpoints

Authorization (R42d)

All user-facing routers require perimeter-stamped identity via aegis_shared.auth (get_current_user — including /execute and the SSE stream), mode-gated by AEGIS_AUTH_MODE. Fine-grained gates on top: filings.submit on checklist submit, filings.review on checklist reject, eval.read / eval.run on the eval routes, power_user/admin on rule-version writes and /compliance/assess, and skills.author on the sandbox code-block dry-run. See Auth Flow — Service-Side Enforcement.

Agent Execution

MethodPathDescription
POST/executeSynchronous full pipeline execution. Runs the entire LangGraph graph and returns the final state.
GET/conversations/{conversation_id}/streamSSE streaming endpoint. Streams node_end events as each LangGraph node completes, then a done event.
GET/healthHealth check. Returns service version.

Conversations

MethodPathDescription
GET/conversationsList the requesting user’s conversations (scoped to the gateway-injected X-User-Id; 401 without identity). Excludes soft-deleted rows and harness runs (conversation_type replay/test). Rows include a preview snippet.
POST/conversationsCreate a conversation. Accepts an optional caller-supplied id (harness pre-registration, idempotent) and derives ownership from X-User-Id.
GET/conversations/{id}/messagesGet conversation messages.
PATCH/conversations/{id}Update conversation title or status (owner-guarded; 404 on mismatch).
DELETE/conversations/{id}Soft-delete a conversation (owner-guarded).

Compliance Dashboard (R1.2)

MethodPathDescription
GET/compliance/summarySummary cards (total entities, domain counts, status distribution).
GET/compliance/matrixEntity x compliance domain matrix with color-coded status cells.
GET/compliance/charts/*Chart data (deadline bar, compliance trend, filing status, flaring volume).
POST/compliance/assessTrigger compliance assessment for an entity.

Checklists (R1.3)

MethodPathDescription
GET/checklistsList filing checklists.
POST/checklistsCreate a new checklist from a template.
GET/checklists/{id}Get checklist with items.
PUT/checklists/{id}/items/{idx}Update a checklist item status.
POST/checklists/{id}/submitSubmit a completed checklist for review.

Artifacts

MethodPathDescription
GET/checklists/{id}/items/{idx}/artifactsList artifacts for a checklist item.
POST/checklists/{id}/items/{idx}/artifactsUpload or create an artifact.

Rules (R1.5)

MethodPathDescription
GET/rulesList rule versions.
POST/rulesCreate a new rule version (immutable).
GET/rules/{identifier}/versionsGet all versions of a specific rule.
GET/rules/changesDetect rules that changed since a reference date.
POST/rules/monitor/scanTrigger a rule change monitoring scan.

Workspaces (R1.4)

MethodPathDescription
POST/workspaces/{id}/assessStart an automated compliance assessment.
GET/workspaces/{id}/streamSSE stream for workspace events.
POST/workspaces/{id}/items/{idx}/agentSend a message to the scoped agent for a checklist item.

Eval Capture (R40a)

MethodPathDescription
POST/eval/capturesRecord an expert verdict (thumbs up/down + optional enrichment) on a completed turn. Upserts on (execution_id, captured_by); identity comes from X-User-Id/X-Tenant-Id headers, never the body.
GET/eval/captures?conversation_id=The requesting tester’s captures for one conversation (renders captured state on reload).
GET/eval/metricsInternal missing-rate counts (snapshot_status='missing' is a writer-bug detector, not an operating mode).

At turn end (both the SSE turn runner and the sync /execute path) the full assembled context publishes fire-and-forget to the snapshots:pending Redis stream; a background consumer-group writer drains it into turn_snapshots (ack-after-persist, so entries survive restarts). See the database schema page for the tables.

Architecture

LangGraph Pipeline

The core execution follows a StateGraph (R36 — the skill_select_node / skill_inject_node hop is deleted; skill selection happens inside the tool loop):

START -> system_prompt_node -> memory_node -> resume_guard -> initial_llm_call -> [tool_node -> initial_llm_call]* (tool loop; pull_routers / select_skill execute here as ordinary tool calls) -> approval_node -> output_format -> END

Routing logic between nodes:

  • After initial_llm_call: If there are pending tool calls, route to tool_node. If there is an error, route to output_format. Otherwise, route to approval_node.
  • After tool_node: If a HITL gate fired (awaiting_hitl), route to output_format. Check iteration limit. Otherwise route back to initial_llm_call.
  • approval_node flows straight to output_format (R41 B0 removed the vestigial synthesis phase). It either pauses awaiting_hitl or passes through — either way the next stop is output_format.

Module Breakdown

src/orchestration/ ├── main.py # FastAPI app, /execute and /stream endpoints ├── engine.py # LangGraph StateGraph construction and compilation ├── nodes.py # Node implementations (1 function per node) ├── tools.py # Tool definitions and execution for LLM function calling │ # (hosts SKILL_SELECT_MARKER_TOMBSTONE — the retired legacy selection marker) ├── selection_tools.py # R36: pull_routers / select_skill tool handlers ├── conversation_runtime.py # R36: sealed-prefix runtime state (conversation_runtime_state) ├── context_maintenance.py # R36: per-turn router deltas, lazy upgrades, tombstones ├── state.py # GraphState TypedDict (flows through the graph) ├── schemas.py # Request/response Pydantic models ├── services.py # HTTP clients for memory, KG, approval services ├── sandbox/ # Unix-socket client + pipeline for the sandbox-runner │ ├── python_client.py # (R33): ships skill code blocks to the jailed runner │ ├── pipeline.py # AST pre-validation → socket → structured result │ └── harness.py # fail-closed governance gate around execution ├── budget.py # Token/cost budget enforcement ├── config.py # Settings from environment variables ├── conversation_routes.py # Conversation CRUD (list, create, get, update, delete) ├── packs/ # Pack discovery (R41 B2): manifest per compliance domain │ ├── __init__.py # discover_packs() / load_packs() │ ├── schema.py # PackManifest schema (v1) │ ├── rule_37/manifest.yaml # Skills + checklist template for Rule 37 │ ├── rule_32/manifest.yaml # … Rule 32 │ ├── form_pr/manifest.yaml # … Form PR │ ├── flaring_monitor/manifest.yaml │ └── epa_oooob/manifest.yaml # … EPA OOOOb (fifth domain, platform-expansion) ├── skills/ # Checklist-driven skill implementations │ ├── __init__.py # Slug-keyed skill registry (register_skill, list_registered_skills) │ ├── base.py # Base skill class (SKILL_KEY identity) │ ├── declarative.py # Descriptor-driven skills (R41 B4): manifest descriptors, no Python │ ├── rule37/ # Rule 37 spacing exception skills (11 skills) │ ├── rule32/ # Rule 32 flaring exception skills (10 skills) │ ├── form_pr/ # Form PR production report skills (8 skills) │ ├── flaring_monitor/ # Flaring monitor skills (6 skills) │ └── field_event/ # Field event conversation skills ├── compliance/ # Compliance dashboard and workspace │ ├── routes.py # /compliance/summary, /matrix, /charts │ ├── engine.py # Compliance status computation engine │ ├── queries.py # Cypher query templates for compliance │ ├── checklist_routes.py # Checklist CRUD routes │ ├── artifact_routes.py # Artifact management routes │ ├── rule_routes.py # Rule version CRUD routes │ ├── rule_lifecycle.py # Immutable rule versioning logic │ ├── rule_monitor/ # Rule change monitoring │ └── workspace/ # Entity workspace (assessment, SSE, agent) │ ├── routes.py # Workspace API routes │ ├── assessment.py # Automated assessment logic │ └── events.py # WorkspaceEventType enum, SSE event formatting ├── seed_checklists.py # Checklist template seeder (content from pack manifests) ├── seed_rules.py # Compliance rule data seeding script ├── seed_conversations.py # Demo conversation seeding └── seed_demo_data.py # Combined demo data seeder

GraphState

The GraphState TypedDict carries all execution state through the graph:

class GraphState(TypedDict, total=False): execution_id: str conversation_id: str agent_id: str agent_type: str # "rule_37", "rule_32", etc. tenant_id: str status: str # "pending", "running", "completed", "awaiting_hitl", "failed" messages: list[dict] # Conversation messages (role/content/tool_calls) system_prompt: str model: str # LLM model to use (default: gpt-4o) loaded_skills: list # skill_keys currently loaded (R36: grows mid-turn via select_skill) injected_skill_ids: list # Skills already injected active_skills: dict # skill_id -> tier level tokens_used: int cost_usd: float max_tokens: int max_cost_usd: float pending_tool_calls: list # Tool calls awaiting execution iteration: int # Guards against infinite loops memory_context: dict # Working + episodic memory data hitl_required: bool hitl_checkpoint_type: str hitl_approval_id: str | None

Budget Enforcement

The budget.py module enforces two independent tiers (R41 A8):

  • Per-conversation (cumulative across every turn): BudgetExceededError when tokens_used >= max_tokens or cost_usd >= max_cost_usd.
  • Per-turn (spend since the turn began): BudgetExceededError when tokens_used - turn_tokens_start >= max_tokens_per_turn (or the cost equivalent) — bounds a single runaway turn while the conversation still has headroom.

tokens_remaining returns the tighter of the two tiers, so the per-call output ceiling never blows past either. Budget is checked before every LLM call. When exceeded, /execute returns HTTP 429 and the SSE stream emits an error event. Per-turn env defaults (DEFAULT_MAX_TOKENS_PER_TURN / DEFAULT_MAX_COST_PER_TURN) equal the per-conversation ceiling, so the per-turn tier is dormant until an operator lowers it.

Platform Settings Resolution (Tenant LLM Config)

At every turn start — both the synchronous /execute path and the SSE stream — the engine fetches GET /settings from the agent-config-service (with X-Tenant-Id/X-User-Id headers) and resolves the model and per-execution budgets as:

request override → tenant platform setting → env default

The env defaults (DEFAULT_LLM_MODEL, DEFAULT_MAX_TOKENS_PER_EXECUTION, DEFAULT_MAX_COST_PER_EXECUTION, DEFAULT_MAX_OUTPUT_TOKENS_PER_CALL, DEFAULT_THINKING_MODE) remain the fallback floor; a settings fetch failure degrades to them.

The last two are per-LLM-call knobs (no request override): max_output_tokens_per_call is the max_tokens sent on each call — thinking + visible output share it, so on Claude Sonnet 5 (adaptive thinking by default) a low ceiling can be consumed entirely by a thinking block and yield an empty assistant message; llm_call now fails the turn loudly when that happens (finish_reason=length with no content and no tool calls) instead of appending an empty message. thinking_mode (adaptive | disabled) is sent explicitly to Anthropic models (omitted for Haiku-tier/non-Anthropic, and re-derived when a call retries on the fallback_model). Admins manage the tenant values from the Platform Settings page. If the tenant sets a fallback_model, every LLM call site retries once on that model when the primary model errors. Seeded agent_definitions no longer pin model_config.model_preference (now null), so the tenant default_model governs — an explicitly set per-agent model_preference still overrides. Workspace assessment skills and the scoped agent use the same settings.

Daily caps (tenant-aggregate plus optional per-user overrides) are enforced with Redis counters — usage:daily:{tenant}:{yyyymmdd}:tokens|cost and usage:daily:{tenant}:{user}:{yyyymmdd}:* (48h TTL). The counters are checked before a turn starts (429 on /execute; a terminal SSE error event on the stream) and incremented with actual usage after each turn — so an in-flight turn can overshoot a cap, and it is the next turn that gets blocked.

Skill Selection (R36 — message-driven)

The legacy marker-scanning selection flow is gone. General-mode runs (an agent_definitions row with root_skill_key = NULL) receive a router manifest (router_key + recruitment_blurb per active router) in the index-0 system prompt block, plus two selection tools (orchestration/selection_tools.py):

  1. pull_routers(router_keys[]) — returns the pulled routers’ selectable skills with full descriptions (derived read over approved mappings × active skills × latest-approved description version)
  2. select_skill(skill_key) — fires the existing load_skill() machinery: context templates land as system messages, the skill’s llm_visible code-block tools join the next tool assembly

Validation failures return typed errors and fail toward deny — domain tools do not exist in the run until select_skill succeeds. Agent-mode runs (rule_37, rule_32) load their root skill at conversation start and do not pull routers.

The general-mode index-0 block is sealed at conversation birth (conversation_runtime_state, migration 016) and reused byte-for-byte on reopen; router changes, lazy skill version upgrades, and skill-deletion tombstones reach live conversations as tail appends only (conversation_context_appends), applied per-turn by context_maintenance.py.

HITL Approval Checkpoints

HITL fires from the seeded require_hitl SkillRules on the RRC skills (R35 P5): a rule matches the executed tool name on after_tool_call (e.g. rule37_filing_assemblypre_filing, good_cause_narrativegood_cause_review, rule32_filing_assemblypre_filing). A before_tool_call platform rule gates mutating capabilities, and request metadata can still override hitl_checkpoint_type.

When HITL is required, the engine creates an approval request via the approval service and pauses execution with status: "awaiting_hitl".

Tool Definitions

Since R35 P4 the visible tool list derives from provenance, not agent type: core capabilities (entity_resolve, context_assemble, and since R43 render_chart) are always present; R36 adds the selection tools (pull_routers, select_skill) for general mode; everything domain-specific arrives from the loaded skills’ llm_visible code blocks. Those code blocks are not run in-process — they are shipped over a unix socket to the sandbox-runner (nsjail-jailed Python), which is what keeps tenant-authored skill code from touching the host:

ToolSourceDescription
entity_resolvecoreResolve an entity by name/API/alias
context_assemblecoreAssemble an entity’s dual-view context
render_chartcore (R43)Validate an AEGIS chart spec and emit it as a chart artifact (presentational, read-only — never HITL-gated)
pull_routersselection (R36, general mode)Pull routers to see full skill descriptions
select_skillselection (R36, general mode)Load a skill from a pulled router
spacing_assessment, offset_well_analysis, rule37_filing_assembly, good_cause_narrativeskill rrc_rule37Rule 37 domain tools (sandboxed code blocks)
flaring_volume_calc, gas_analysis, rule32_filing_assembly, emissions_estimateskill rrc_rule32Rule 32 domain tools (sandboxed code blocks)
create_skill_draft, add_code_block, add_rule, run_dry_run, submit_for_review, create_skill_revisionskill skill_creator (R38, native handlers)Skill Creator write tools (authoring)
list_action_capabilities, list_my_drafts, list_categories, get_effective_rulesskill skill_creator (R38 + R39 P0, native handlers)Skill Creator read tools

R38 — the Skill Creator’s authoring tools. The platform creator agent (root skill skill_creator, seeded by agent-config-service) gets ten tools (R39 P0 added the list_categories read) from orchestration/authoring_tools.pynative handlers, not sandboxed code blocks — but visibility stays data-driven: their schemas are unioned into the LLM payload only when the skill_creator skill is loaded (no agent_type branch). All six write tools are in MUTATING_TOOLS (fail-closed when governance is unevaluable), and they act as the conversing user via fail-closed X-User-Id headers, so owner_id lands as the real user. The Creator authors only — it has no approve/map/retire/invoke tools; admin approval (publish-on-approve, R38) is what makes an authored skill loadable and reachable.

The deferred flaring_monitor / compliance_monitor agents keep a hardcoded fence set (R35-FENCE) until their skills land. See LangGraph Pipeline for the full breakdown.

Sandbox Runner (Jailed Code Execution)

Skill code blocks are tenant-authored, so they never execute in the orchestration process. The sandbox-runner (services/sandbox-runner/) is the platform’s only privileged component: a stdlib-only root server, reachable solely over a local unix socket (root:aegis, 0660 — never a TCP port), that spawns nsjail to run a single Python code block under namespaces + seccomp + cgroups and returns a structured JSON result. It has no service page of its own because it has no HTTP API.

The defense is two-layer: an unprivileged AST validator (aegis_shared.sandbox.validation) is the cheap first pass, and the deny-by-default seccomp policy (infrastructure/deploy/sandbox/python.seccomp.kafel) is the load-bearing containment. The orchestration side lives in src/orchestration/sandbox/ (python_client.py → socket, pipeline.py, harness.py); the fail-closed governance gate blocks classification-bearing code when governance is unevaluable (R41 Phase A). Socket path and limits are configured via AEGIS_SANDBOX_SOCKET, AEGIS_SANDBOX_WALL_S, and AEGIS_SANDBOX_MAX_OUTPUT_BYTES.

Dependencies

Python Packages

PackageVersionPurpose
fastapi^0.115Web framework
uvicorn^0.34ASGI server
langgraph^0.4StateGraph agent execution engine
litellm^1.60LLM routing (OpenAI, Anthropic, etc.)
sse-starlette^2.0Server-Sent Events for streaming
aegis-sharedlocalShared models and DB helpers
langfuse^4.0LLM observability / tracing (OTEL-based, optional)
opentelemetry-{api,sdk,exporter-otlp}^1.27OTEL plumbing for the litellm langfuse_otel callback

Service Dependencies

ServiceProtocolPurpose
Memory Service (8002)HTTPWorking memory, episodic memory, injection ledger
Knowledge Graph Service (8003)HTTPEntity context assembly (Tier 3.5)
Approval Service (8004)HTTPHITL approval request creation
Sandbox RunnerUnix socketJailed Python execution for skill code blocks (R33; AEGIS_SANDBOX_SOCKET, default /run/aegis-sandbox/sandbox.sock) — never a TCP port
Agent Config Service (8010)HTTPSkill definitions/personas/rules (R35); router manifest + disambiguation reads for skill selection (R36); tenant platform settings (GET /settings at every turn start)
PostgreSQLTCPSkill registry, conversation persistence, conversation runtime state (R36 sealed prefix), compliance data

Configuration

Environment VariableDefaultDescription
ORCHESTRATION_HOST0.0.0.0Bind address
ORCHESTRATION_PORT8001Bind port
DATABASE_URLpostgresql://aegis:aegis_local@localhost:5432/aegisPostgreSQL connection
REDIS_URLredis://localhost:6379Redis connection
KAFKA_BOOTSTRAP_SERVERSlocalhost:9092Kafka brokers
DEFAULT_LLM_MODELgpt-4o (code fallback)Default LLM model for agent calls. Fallback floor under tenant Platform Settings — used when the tenant has no default_model set or the settings fetch fails. The dev box sets this to anthropic/claude-sonnet-5 (routes via LiteLLM to Anthropic; the box OpenAI key is dead)
DEFAULT_MAX_TOKENS_PER_EXECUTION100000Default token budget per execution (fallback floor under Platform Settings)
DEFAULT_MAX_COST_PER_EXECUTION5.0Default cost budget (USD) per execution (fallback floor under Platform Settings)
DEFAULT_MAX_OUTPUT_TOKENS_PER_CALL16384Per-LLM-call max_tokens ceiling — thinking + visible output (fallback floor under Platform Settings)
DEFAULT_THINKING_MODEadaptiveThinking mode sent explicitly to Anthropic models: adaptive | disabled (fallback floor under Platform Settings)
MAX_GRAPH_ITERATIONS20Max tool call loop iterations
MEMORY_SERVICE_URLhttp://localhost:8002Memory service URL
KNOWLEDGE_GRAPH_SERVICE_URLhttp://localhost:8003Knowledge graph service URL
APPROVAL_SERVICE_URLhttp://localhost:8004Approval service URL
AGENT_CONFIG_SERVICE_URLhttp://localhost:8010Agent config service URL (skills, personas, rules, R36 skill routers)
EPISODIC_TOP_K3Number of episodic memories to retrieve
LANGFUSE_PUBLIC_KEY(empty)Langfuse public key — tracing is enabled only when set
LANGFUSE_SECRET_KEY(empty)Langfuse secret key — tracing is enabled only when set
LANGFUSE_HOSThttps://us.cloud.langfuse.comLangfuse host/region (US default; EU/JP/HIPAA/self-hosted supported)
ORCHESTRATION_REPLAY_PORT8101R40b loopback replay bind port (replay_app). Host is pinned to 127.0.0.1 in code — never 0.0.0.0
EVAL_JUDGE_MODELanthropic/claude-sonnet-4-6R40b expert-grader judge model, passed explicitly to litellm so it never self-grades with the model under test
EVAL_JUDGE_THRESHOLD0.7R40b judge score ≥ threshold → pass (recorded per run)
AEGIS_GIT_SHA(unset)R40b: optional override for the build SHA on /health; falls back to git rev-parse HEAD at the repo root

Replay bind (R40b eval)

The eval runner drives cases through a separate FastAPI app (orchestration.replay_app:replay_app) bound loopback-only on 127.0.0.1:8101, run as its own systemd unit (aegis-replay.service). It is not a second bind of the main app — that would expose /replay publicly via Caddy’s forward_auth. It mounts only POST /replay (plus /health) and shares the engine internals by import; the replay_origin flag exists only on its request model, so there is no public surface to forge it on. Nothing in the Caddyfile references the port → it is structurally unreachable from the edge.

Observability (Langfuse Tracing)

The engine ships optional Langfuse  tracing, wired in orchestration/observability.py. It is off by default and activates only when both LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are set — otherwise every tracing helper is a no-op and the service behaves identically.

How it works:

  • At startup (init_tracing() in the FastAPI lifespan) the engine enables the LiteLLM langfuse_otel callback for all litellm.acompletion calls and initialises the Langfuse SDK (v4, OpenTelemetry-based).
  • Each agent run is wrapped in a parent span via trace_agent_run(...). LiteLLM generation spans nest under it automatically: both layers ride the global OTEL TracerProvider and share OTEL context, and Langfuse’s span processor exports gen_ai.* spans by default — yielding one trace per agent run with each LLM call as a nested generation (model, tokens and cost captured automatically).

Trace attributes set:

AttributeSourceWhy
session_idconversation_id / checklist_idGroups multi-turn runs in the Sessions view
user_idmetadata.user_idUser filtering and cost attribution
tagsagent type + phase (execute / stream / assessment / workspace-agent)Per-agent / per-feature analytics
input / outputuser message + final assistant messageReadable traces without leaking full state

Instrumented entry points: POST /execute, GET /conversations/{id}/stream, workspace assessment (GET /workspaces/{id}/stream), and the scoped agent (POST /workspaces/{id}/items/{idx}/agent).

R34 entity-layer child spans (orchestration/entity_spans.py): the entity resolution / context-assembly / HITL pipelines emit child_span observations that nest under the agent-run span, so an auto-select decision and the full HITL flow are reconstructable from the trace alone. They carry the internal, precise values (the audit audience, not the LLM) and are no-ops when tracing is off. All fire orchestration-side — the knowledge-graph service is untraced and OTEL context doesn’t cross the HTTP boundary, so KG returns the facts (the resolution_trace block on the resolve response) and the orchestration tool wrappers + gate/guard emit the spans.

SpanWhenKey attributes
entity_resolve.exact_matchevery resolvematch_found, query, entity_types_scope
entity_resolve.trigram_matchevery resolvetop_name_similarity (the calibration signal), candidates_returned
entity_resolve.proximity_scoreresolve w/ candidatestop_proximity_score, context_entity_ids_count
entity_resolve.composite_scoreresolve w/ candidatesname_component / proximity_component / type_hint_component (weighted)
entity_resolve.outcomeevery resolveoutcome (auto_selected / asked_user / no_match), confidence
context_assemble.walkcontext_assembleentity_id, depth, entities_traversed
context_assemble.rendercontext_assembletoken_estimate, truncation_occurred, relationships_truncated_count
hitl.gate_firedmutating-capability gate firescapability_key, tool_call_id, assistant_message_stripped
hitl.resume_reconcileresume_guard reconciles a verdictverdict (approved / rejected / pending / unreadable), reviewer_note_present
hitl.resume_replayapproved mutation replayedreplay_succeeded, duplicate_emission_suppressed

The entity_resolve.outcome span is a separate layer from the platform:log_entity_resolution rules-audit entry — the spans only wrap the resolve callsite and never re-fire the on_entity_resolve trigger (one audit entry + one outcome span per auto-select). An approved-but-unexecuted mutation is the absence of a hitl.resume_replay span next to an approved approval record. See docs/specs/phases/R34-phase-6-calibration-and-demo-runbook.md.

Running Locally

cd services/orchestration-engine poetry install poetry run uvicorn orchestration.main:app --reload --port 8001

Seeding Data

The service includes several seeding scripts:

# Seed checklist templates poetry run python -m orchestration.seed_checklists # Seed compliance rules poetry run python -m orchestration.seed_rules # Seed demo conversations poetry run python -m orchestration.seed_conversations

The orchestration engine requires PostgreSQL to be running for conversation persistence and skill registry access. The memory service and knowledge graph service should be running for full pipeline functionality, but the engine degrades gracefully if they are unavailable.

Last updated on