Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.d/0.78.0-adaptive-orchestration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 0.78.0 — Adaptive contextual-orchestrator defaults

## Changed

- Structured extraction, summarization, commitment, relationship-classification, and LLM-as-a-Judge consumers now request contextual-orchestrator `auto` mode, allowing the orchestration plane to meet the quality requirement and then minimize known execution cost.
- Explicit checked `verify` paths remain unchanged.
28 changes: 28 additions & 0 deletions docs/adr/0013-adaptive-contextual-orchestrator-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ADR-0013: Adaptive contextual-orchestrator mode is the default

- Status: Accepted
- Date: 2026-08-16

## Context

LineageWeave used fixed single-worker `route` mode for structured extraction, summarization, commitment derivation, relationship classification, and post evaluation. That made each consumer choose the execution topology and prevented `contextual-orchestrator` from allocating deeper verification when task difficulty, uncertainty, or risk justified it.

## Decision

Ordinary LineageWeave LLM consumers request `mode="auto"`.

The orchestration plane owns provider/model selection, test-time compute, workflow depth, verification, fallback, and known-price optimization. Quality sufficiency is the first constraint; cost is minimized among execution paths that satisfy it. Unpriced models are not treated as free.

Explicit `verify` remains for the citation-bearing post-chat and lineage adjudication paths because those are deliberate checked-judgment contracts, not product defaults. Explicit route or conduct modes may be used only for documented ablation, incident response, or a bounded domain requirement.

LineageWeave continues to own strict output parsing, evidence identifiers, IRT projection, and fail-closed domain validation.

## Consequences

A structured task may still be served by one model when the adaptive policy determines that it is sufficient. Harder requests may receive a deeper workflow without changing the LineageWeave API. Consumers must retain returned orchestration and usage evidence when the gateway exposes it.

## References

Omidvar, H., & Akhlaghi, V. (2026). *A communication-theoretic framework for LLM agents: Cost-aware adaptive reliability* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2605.09121

Tang, Y., Cetin, E., Xu, J., Sun, Q., Nielsen, S., Richard, V., Goda, H., Tymchenko, I., Nguyen, N., Lee, H., Ashiga, M., Kotyan, S., Kuroki, S., & Clanuwat, T. (2026). *Sakana Fugu technical report* [Technical report]. arXiv. https://doi.org/10.48550/arXiv.2606.21228
4 changes: 2 additions & 2 deletions lineageweave/commitment_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def parse_commitment_response(content: str) -> CustomerCommitment | None:


class ContextualOrchestratorCommitmentExtractionClient:
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="route"``."""
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="auto"``."""

available = True

Expand All @@ -156,7 +156,7 @@ def extract(self, post_title: str, post_body: str, reference_date: str) -> Custo
f"{self._base_url}/v1/chat/completions",
{
"messages": [{"role": "user", "content": prompt}],
"mode": "route",
"mode": "auto",
"reasoning_effort": self._reasoning_effort,
},
headers={"authorization": f"Bearer {self._api_key}"},
Expand Down
4 changes: 2 additions & 2 deletions lineageweave/entity_relationship_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def parse_classification_response(


class ContextualOrchestratorEntityRelationshipClient:
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="route"``."""
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="auto"``."""

available = True

Expand All @@ -183,7 +183,7 @@ def classify(
f"{self._base_url}/v1/chat/completions",
{
"messages": [{"role": "user", "content": prompt}],
"mode": "route",
"mode": "auto",
"reasoning_effort": self._reasoning_effort,
},
headers={"authorization": f"Bearer {self._api_key}"},
Expand Down
12 changes: 6 additions & 6 deletions lineageweave/keyman_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
contextual-orchestrator instance -- never a raw LLM API directly, per
AGENTS.md -- because Keyman identification is a structured-extraction task
that benefits from the orchestrator's reasoning-effort allocation, not a
single confidence number, so it uses ``mode="route"`` (one worker call) at
a ``"medium"`` reasoning effort by default rather than ``verify``'s
worker-plus-checker pattern, which is reserved for adjudication's binary
judgment calls.
single confidence number, so it uses ``mode="auto"`` and lets the orchestration plane allocate
quality-sufficient test-time compute before minimizing known cost. Explicit
``verify`` remains reserved for adjudication's checked binary-judgment
contract.
"""

from __future__ import annotations
Expand Down Expand Up @@ -133,7 +133,7 @@ def parse_keyman_response(content: str) -> list[PersonMention]:


class ContextualOrchestratorKeymanExtractionClient:
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="route"``."""
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="auto"``."""

available = True

Expand All @@ -151,7 +151,7 @@ def extract(self, post_title: str, post_body: str) -> list[PersonMention]:
f"{self._base_url}/v1/chat/completions",
{
"messages": [{"role": "user", "content": prompt}],
"mode": "route",
"mode": "auto",
"reasoning_effort": self._reasoning_effort,
},
headers={"authorization": f"Bearer {self._api_key}"},
Expand Down
4 changes: 2 additions & 2 deletions lineageweave/post_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def __init__(self, base_url: str, api_key: str, *, timeout: float = 60.0) -> Non
self._api_key = api_key
self._timeout = timeout

def complete(self, messages: list[dict[str, Any]], mode: str = "route") -> dict[str, Any]:
def complete(self, messages: list[dict[str, Any]], mode: str = "auto") -> dict[str, Any]:
body = post_json(
f"{self._base_url}/v1/chat/completions",
{"messages": messages, "mode": mode, "reasoning_effort": "medium"},
Expand All @@ -107,7 +107,7 @@ class ContextualOrchestratorPostEvaluationClient:
def __init__(self, base_url: str, api_key: str, *, timeout: float = 60.0) -> None:
self._judge = ContextualOrchestratorJudge(
_OrchestratorCompleteAdapter(base_url, api_key, timeout=timeout),
mode="route",
mode="auto",
)

def evaluate(self, post_title: str, post_body: str) -> LLMJudgeResult:
Expand Down
4 changes: 2 additions & 2 deletions lineageweave/post_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def parse_summary_response(content: str) -> PostSummary | None:


class ContextualOrchestratorPostSummaryClient:
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="route"``."""
"""Calls ``POST {base_url}/v1/chat/completions`` with ``mode="auto"``."""

available = True

Expand All @@ -170,7 +170,7 @@ def summarize(self, post_title: str, post_body: str) -> PostSummary:
f"{self._base_url}/v1/chat/completions",
{
"messages": [{"role": "user", "content": prompt}],
"mode": "route",
"mode": "auto",
"reasoning_effort": self._reasoning_effort,
},
headers={"authorization": f"Bearer {self._api_key}"},
Expand Down
131 changes: 131 additions & 0 deletions tests/test_adaptive_orchestration_defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Adaptive contextual-orchestrator defaults remain explicit at every consumer boundary."""

from __future__ import annotations

import json

import pytest

from lineageweave.commitment_extraction import (
ContextualOrchestratorCommitmentExtractionClient,
)
from lineageweave.entity_relationship_classification import (
ContextualOrchestratorEntityRelationshipClient,
)
from lineageweave.keyman_extraction import (
ContextualOrchestratorKeymanExtractionClient,
)
from lineageweave.post_evaluation import ContextualOrchestratorPostEvaluationClient
from lineageweave.post_summary import ContextualOrchestratorPostSummaryClient


@pytest.mark.parametrize(
("module_name", "client_factory", "invoke", "content"),
[
(
"lineageweave.post_summary",
lambda: ContextualOrchestratorPostSummaryClient("https://orchestrator.test", "token"),
lambda client: client.summarize("Title", "Body"),
json.dumps(
{
"korean_summary": "요약",
"key_events": [],
"roles_and_responsibilities": [],
}
),
),
(
"lineageweave.keyman_extraction",
lambda: ContextualOrchestratorKeymanExtractionClient(
"https://orchestrator.test", "token"
),
lambda client: client.extract("Title", "Body"),
"[]",
),
(
"lineageweave.commitment_extraction",
lambda: ContextualOrchestratorCommitmentExtractionClient(
"https://orchestrator.test", "token"
),
lambda client: client.extract("Title", "Body", "2026-08-16"),
json.dumps(
{
"has_commitment": False,
"commitment_summary": None,
"due_date": None,
}
),
),
(
"lineageweave.entity_relationship_classification",
lambda: ContextualOrchestratorEntityRelationshipClient(
"https://orchestrator.test", "token"
),
lambda client: client.classify("Title", "Body", ["Example Corp"]),
json.dumps(
[
{
"organization_name": "Example Corp",
"relationship_type_code": "rel_voc",
}
]
),
),
],
)
def test_structured_consumers_request_auto_mode(
monkeypatch, module_name, client_factory, invoke, content
) -> None:
observed: dict[str, object] = {}

def fake_post_json(url, payload, *, headers, timeout):
observed["url"] = url
observed["payload"] = payload
observed["headers"] = headers
observed["timeout"] = timeout
return {"choices": [{"message": {"content": content}}]}

module = __import__(module_name, fromlist=["post_json"])
monkeypatch.setattr(module, "post_json", fake_post_json)

invoke(client_factory())

assert observed["payload"]["mode"] == "auto"


def test_post_evaluation_judge_defaults_to_auto(monkeypatch) -> None:
observed: dict[str, object] = {}

def fake_post_json(url, payload, *, headers, timeout):
observed["payload"] = payload
return {
"choices": [
{
"message": {
"content": json.dumps(
{
"score": 1.0,
"accepted": True,
"rationale": "Evidence supports each criterion.",
"criterion_categories": {
"general_sentiment_negative": 4,
"general_sentiment_positive": 4,
"sales_lead_specificity": 4,
},
}
)
}
}
]
}

import lineageweave.post_evaluation as module

monkeypatch.setattr(module, "post_json", fake_post_json)
client = ContextualOrchestratorPostEvaluationClient(
"https://orchestrator.test", "token"
)

client.evaluate("Title", "Body")

assert observed["payload"]["mode"] == "auto"
Loading