← Files Marketing CouncilARCHIVED FILE
scripts/dynamic_router.py
14 KB · Oct 2, 2026 · 00:31 UTC
#!/usr/bin/env python3
"""Build a bounded multi-skill execution DAG without mixing in neural theory routing."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ROUTES_PATH = ROOT / "routing" / "skill-routes.json"
HANDOFFS_PATH = ROOT / "routing" / "skill-handoffs.json"
EXTRA_HINTS = {
"market-diagnosis": ["diagnose the market", "market structure", "demand problem", "market diagnosis"],
"customer-research": ["research customers", "customer switching triggers", "buyer interviews", "customer research", "buying language"],
"competitive-intelligence": ["research competitors", "competitor alternatives", "competitive alternatives", "map competitors", "competitor research"],
"segmentation-strategy": ["target segments", "choose target segments", "segment the market", "segmentation"],
"category-strategy": ["category strategy", "category frame", "category maturity"],
"positioning-strategy": ["position the product", "decide our positioning", "positioning", "competitive frame"],
"product-marketing": ["product story", "launch message", "product marketing", "product narrative"],
"offer-strategy": ["offer architecture", "value proposition", "proof architecture", "offer strategy"],
"pricing-strategy": ["pricing tiers", "set pricing", "pricing architecture", "discount guardrails", "price", "pricing"],
"go-to-market": ["go-to-market", "go to market", "gtm", "launch sequence", "route to market"],
"campaign-strategy": ["campaign strategy", "campaign plan", "campaign direction", "campaign job"],
"media-strategy": ["media strategy", "media plan", "channel roles", "paid distribution", "media"],
"content-strategy": ["content strategy", "editorial plan", "content plan", "content"],
"behavioral-marketing": ["behavioral friction", "choice design", "behavioral marketing", "behavior change"],
"conversion-strategy": ["conversion", "checkout friction", "form abandonment", "cro", "funnel friction"],
"retention-strategy": ["retention", "churn", "repeat purchase", "reactivation", "lifecycle"],
"marketing-signal-strategy": ["signal strategy", "crm signals", "offline signals", "value signals"],
"marketing-measurement": ["measurement architecture", "kpi", "attribution", "measurement"],
"marketing-experimentation": ["experiment design", "test plan", "a/b test", "ab test", "experimentation"],
"incrementality-design": ["incrementality", "incremental roas", "geo holdout", "counterfactual", "causal lift"],
"ai-discovery-strategy": ["ai discovery", "answer surfaces", "ai search", "llm discovery"],
"conversational-advertising": ["conversational advertising", "conversational ad", "chat ad"],
"commerce-feed-intelligence": ["product feed", "merchant feed", "feed quality", "feed truth"],
"agentic-commerce": ["agentic commerce", "shopping agent", "agent-mediated shopping", "agent checkout"],
"autonomous-media-operations": ["autonomous media", "media automation", "automated bidding authority", "rollback authority"],
"creator-commerce": ["creator commerce", "creator affiliate", "creator program", "influencer commerce"],
"commerce-media-strategy": ["commerce media", "retail media", "marketplace media", "closed-loop media"]
}
AMBIGUITY_MARKERS = (
"cannot tell",
"can't tell",
"do not know whether",
"don't know whether",
"not sure whether",
"unclear whether",
"which problem is primary",
"which issue is primary",
)
def _normalise(text: str) -> str:
return " " + re.sub(r"[^a-z0-9]+", " ", text.casefold()).strip() + " "
def _phrase(text: str, phrase: str) -> bool:
candidate = _normalise(phrase).strip()
return bool(candidate) and f" {candidate} " in text
def _load() -> tuple[dict, dict]:
return (
json.loads(ROUTES_PATH.read_text(encoding="utf-8")),
json.loads(HANDOFFS_PATH.read_text(encoding="utf-8")),
)
def _phrase_spans(text: str, phrase: str) -> list[tuple[int, int]]:
candidate = _normalise(phrase).strip()
if not candidate:
return []
needle = f" {candidate} "
spans: list[tuple[int, int]] = []
start = 0
while True:
position = text.find(needle, start)
if position < 0:
break
spans.append((position, position + len(needle)))
start = position + 1
return spans
def _route_matches(raw_text: str, routes: dict) -> tuple[dict[str, int], dict[str, int]]:
text = _normalise(raw_text)
scores: dict[str, int] = {}
positions: dict[str, int] = {}
for route in routes["routes"]:
slug = route["skill"]
score = 0
first_position: int | None = None
negative_spans = [
span
for item in route.get("negative_examples", [])
for span in _phrase_spans(text, item)
]
phrases = list(route.get("intents", [])) + list(route.get("examples", [])) + EXTRA_HINTS.get(slug, [])
for item in phrases:
candidate = _normalise(item).strip()
if not candidate:
continue
valid_positions = [
start
for start, end in _phrase_spans(text, item)
if not any(start < neg_end and end > neg_start for neg_start, neg_end in negative_spans)
]
if not valid_positions:
continue
position = min(valid_positions)
score += max(2, len(candidate.split()) * 2)
first_position = position if first_position is None else min(first_position, position)
if score:
scores[slug] = score
positions[slug] = first_position if first_position is not None else len(text)
return scores, positions
def _result(mode: str, primary: str, nodes: list[str], edges: list[list[str]], parallel: list[list[str]], reason: str, confidence: float, fallback: bool) -> dict:
return {
"mode": mode,
"primary_skill": primary,
"nodes": nodes,
"edges": edges,
"parallel_groups": parallel,
"confidence": round(confidence, 3),
"reason": reason,
"fallback": fallback,
}
def route_dynamic(text: str) -> dict:
routes, handoffs = _load()
fallback = routes["fallback_skill"]
scores, positions = _route_matches(text, routes)
ranked = sorted(scores, key=lambda slug: (-scores[slug], slug))
normalized = _normalise(text)
if not ranked:
return _result("council", fallback, [fallback], [], [], "No focused skill has enough explicit evidence to own the request.", 0.3, True)
strong = [slug for slug in ranked if scores[slug] >= max(4, scores[ranked[0]] * 0.5)]
has_then = " then " in normalized
has_parallel = " in parallel " in normalized or " parallel " in normalized
explicit_sequence = has_then or bool(re.search(r"\bfirst\b.+\bthen\b", normalized))
explicit_uncertainty = any(marker in normalized for marker in AMBIGUITY_MARKERS)
if explicit_uncertainty and len(ranked) >= 2 and not explicit_sequence and not has_parallel:
return _result("council", fallback, [fallback], [], [], "The request explicitly states that ownership is uncertain across multiple plausible problems.", 0.4, True)
if len(strong) == 1 and not explicit_sequence and not has_parallel:
skill = strong[0]
return _result("focused", skill, [skill], [], [], "One dominant marketing function has materially stronger request evidence.", min(0.97, 0.72 + scores[skill] / 100), False)
if not explicit_sequence and not has_parallel:
return _result("council", fallback, [fallback], [], [], "Several focused skills are plausible but the request does not establish a safe dependency order.", 0.42, True)
max_nodes = int(handoffs.get("max_nodes", 6))
requested = sorted(
dict.fromkeys(ranked),
key=lambda slug: (positions.get(slug, 10**9), -scores[slug], slug),
)
declared_handoffs = {
(item["from"], item["to"])
for item in handoffs.get("handoffs", [])
}
parallel_safe = [
tuple(group)
for group in handoffs.get("parallel_safe", [])
if len(group) == 2
]
# Parallel execution is allowed only when the declared configuration marks
# the pair safe and both branches converge through declared handoffs. Any
# sequential tail after the fan-in must also consume every explicitly
# requested Skill through declared handoffs; nothing may be dropped.
if has_parallel:
for pair in parallel_safe:
if not all(slug in requested for slug in pair):
continue
pair_start = min(positions.get(slug, -1) for slug in pair)
cutoff = max(positions.get(slug, -1) for slug in pair)
common_targets = [
slug
for slug in requested
if slug not in pair
and positions.get(slug, -1) > cutoff
and all((upstream, slug) in declared_handoffs for upstream in pair)
]
if not common_targets:
continue
target = common_targets[0]
target_position = positions.get(target, 10**9)
tail = [
slug
for slug in requested
if slug not in pair
and slug != target
and positions.get(slug, -1) > target_position
]
consumed = set(pair) | {target} | set(tail)
unconsumed = [slug for slug in requested if slug not in consumed]
if unconsumed or any(
slug not in pair and positions.get(slug, 10**9) < pair_start
for slug in requested
):
return _result(
"council",
fallback,
[fallback],
[],
[],
"The parallel request contains additional Skills that cannot be placed without reordering or dropping an explicit dependency.",
0.4,
True,
)
nodes = list(pair) + [target] + tail
if len(nodes) > max_nodes:
return _result(
"council",
fallback,
[fallback],
[],
[],
f"The requested dependency graph exceeds the configured maximum of {max_nodes} focused Skills.",
0.4,
True,
)
sequential = [target] + tail
invalid_tail = [
(sequential[index], sequential[index + 1])
for index in range(len(sequential) - 1)
if (sequential[index], sequential[index + 1]) not in declared_handoffs
]
if invalid_tail:
source, destination = invalid_tail[0]
return _result(
"council",
fallback,
[fallback],
[],
[],
f"No declared handoff supports {source} -> {destination} after the parallel fan-in; refusing to drop or reorder the requested tail.",
0.4,
True,
)
edges = [[upstream, target] for upstream in pair]
edges.extend(
[sequential[index], sequential[index + 1]]
for index in range(len(sequential) - 1)
)
primary = sequential[-1]
return _result(
"dag",
primary,
nodes,
edges,
[list(pair)],
"The request explicitly declares parallel work, both branches converge through declared handoffs, and every sequential tail transition is declared.",
0.9,
False,
)
return _result(
"council",
fallback,
[fallback],
[],
[],
"The request asks for parallel work, but no declared parallel-safe handoff graph supports that dependency shape.",
0.4,
True,
)
if len(requested) > max_nodes:
return _result(
"council",
fallback,
[fallback],
[],
[],
f"The requested dependency graph exceeds the configured maximum of {max_nodes} focused Skills; refusing to truncate it silently.",
0.4,
True,
)
if len(requested) < 2:
skill = requested[0] if requested else ranked[0]
return _result(
"focused",
skill,
[skill],
[],
[],
"Sequence language was present, but only one focused decision boundary was supported.",
0.69,
False,
)
invalid_pairs = [
(requested[index], requested[index + 1])
for index in range(len(requested) - 1)
if (requested[index], requested[index + 1]) not in declared_handoffs
]
if invalid_pairs:
source, target = invalid_pairs[0]
return _result(
"council",
fallback,
[fallback],
[],
[],
f"No declared handoff supports {source} -> {target}; refusing to reorder or drop explicitly requested Skills.",
0.4,
True,
)
edges = [[requested[index], requested[index + 1]] for index in range(len(requested) - 1)]
return _result(
"dag",
requested[-1],
requested,
edges,
[],
"The request explicitly asks for dependent marketing decisions and every transition is supported by a declared handoff.",
0.84,
False,
)
def main() -> None:
parser = argparse.ArgumentParser(description="Build a bounded Marketing Council skill DAG.")
parser.add_argument("--text", required=True)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
result = route_dynamic(args.text)
print(json.dumps(result, indent=2, sort_keys=True) if args.json else result)
if __name__ == "__main__":
main()
SHA-256: 47b5c74fcd9530ad27d6be1d155bd6e228742938a5ee436f0ed2a6e95fba3934