Skip to main content
#intentengineering#llmrouting#mcp#agentic

Intent Engineering: How Value Hierarchies Give Your AI a Conscience

Published on February 25, 2026

When you tell a prompt optimizer to prioritize safety, clarity, and conciseness, which wins? With a flat comma-separated string passed to the LLM, the answer is: any of them, depending on the model's mood. There's no mechanism to say safety outranks clarity. Every goal is treated as equal priority.

That gap between what you mean and what the model hears is the problem that Intent Engineering solves. The Value Hierarchy system gives you machine-readable, ranked goals that control not just how the LLM is prompted — but which routing tier ever sees the request.

The Problem: AI Goals Are Unordered

Three structural gaps exist in most optimization pipelines:

  1. Goals have no ranking. optimize_with_llm_engine(goals="clarity, safety") treats both as equal. A user who needs medical-copy safety gets the same weight as one who wants slightly clearer prose.
  2. The router ignores goals entirely. Most routers select a tier based on prompt complexity alone — a short, simple prompt routes to RULES_BASED even if the user's intent demands deep reasoning.
  3. No user-facing control surface. Users can't express preferences that persist across requests or sessions. Every call starts from zero.

The Data Model

Three types compose the Value Hierarchy system. They are defined identically in the FastAPI backend (app/core/shared_types.py) and the MCP server (app/models.py):

PriorityLabel

class PriorityLabel(str, Enum):
    NON_NEGOTIABLE = "NON-NEGOTIABLE"  # L2 floor: score ≥ 0.72 → LLM tier
    HIGH           = "HIGH"            # L2 floor: score ≥ 0.45 → HYBRID tier
    MEDIUM         = "MEDIUM"          # L1 only — no tier forcing
    LOW            = "LOW"             # L1 only — no tier forcing

HierarchyEntry and ValueHierarchy

class HierarchyEntry(BaseModel):
    goal: str                    # validated against OptimizationType enum
    label: PriorityLabel
    description: Optional[str]   # max 120 chars; no §§PRESERVE markers

class ValueHierarchy(BaseModel):
    name: Optional[str]                  # max 60 chars (display only)
    entries: List[HierarchyEntry]        # 2–8 entries required
    conflict_rule: Optional[str]         # max 200 chars; LLM-injected

Security-motivated validation rules:

  • 2 ≤ len(entries) ≤ 8 — single-item is not a hierarchy; more than 8 bloats the LLM prompt
  • goal must be a valid OptimizationType — prevents unknown strings from reaching the LLM context
  • description ≤ 120 chars — bounds the injection surface
  • description must not contain §§PRESERVE — protects the parameter preservation marker system
  • name ≤ 60 chars, conflict_rule ≤ 200 chars — all user-supplied strings entering prompts are bounded

Level 1 — Prompt Injection (DIRECTIVES Block)

When a ValueHierarchy is present, a INTENT ENGINEERING DIRECTIVES block is appended to the LLM's system prompt and prepended to the optimization prompt. When no hierarchy is present, both functions are byte-for-byte identical to their pre-change behaviour — zero overhead.

System prompt injection

...existing system prompt...

INTENT ENGINEERING DIRECTIVES (user-defined — enforce strictly):
When optimization goals conflict, resolve in this order:
  1. [NON-NEGOTIABLE] safety: Always prioritise safety
  2. [HIGH] clarity
  3. [MEDIUM] conciseness
Conflict resolution: Safety first, always.

Critical implementation note

entry.label.value is used (not str(entry.label)) because Python 3.11+ changed__str__ for str-subclassing enums —str(PriorityLabel.NON_NEGOTIABLE) returns "PriorityLabel.NON_NEGOTIABLE", not "NON-NEGOTIABLE". Using .value produces the correct string in all Python versions.

Level 2 — Router Tier Floor

After _calculate_routing_score() returns a composite score, a floor is applied additively — it can only raise the score, never lower it:

# _calculate_routing_score() is untouched — no impact on non-hierarchy requests
score = await self._calculate_routing_score(prompt, context, ...)

# L2 floor — fires only when hierarchy is active:
if value_hierarchy and value_hierarchy.entries:
    has_non_negotiable = any(
        e.label == PriorityLabel.NON_NEGOTIABLE for e in value_hierarchy.entries
    )
    has_high = any(
        e.label == PriorityLabel.HIGH for e in value_hierarchy.entries
    )
    if has_non_negotiable:
        score["final_score"] = max(score.get("final_score", 0.0), 0.72)
    elif has_high:
        score["final_score"] = max(score.get("final_score", 0.0), 0.45)

Floor values calibrated to existing thresholds:

  • 0.72 — exceeds the HYBRID window → guaranteed LLM_BASED tier
  • 0.45 — inside the HYBRID window → guaranteed HYBRID at minimum
  • MEDIUM/LOW — no floor; L1 handles these via prompt weight only

Why a floor and not a weight?

Adding a fifth weight factor would affect routing scores for every request even when no hierarchy is defined. A floor fires only when active and can only raise the score — it cannot produce a lower tier than the existing formula selects.

Cache Key Isolation

Without isolation, check_universal_cache would return a cached no-hierarchy result for a hierarchy-active request, silently bypassing L1. A deterministic 8-character fingerprint isolates the cache:

def _hierarchy_fingerprint(value_hierarchy) -> str:
    if not value_hierarchy or not value_hierarchy.entries:
        return ""   # empty string → same cache key as pre-change
    return hashlib.md5(
        json.dumps(
            [{"goal": e.goal, "label": str(e.label)} for e in entries],
            sort_keys=True
        ).encode()
    ).hexdigest()[:8]

An empty string (no hierarchy) produces the exact same cache key as before the change — zero overhead for existing users.

MCP Tool Walkthrough

Three new MCP tools manage the session-level hierarchy:

define_value_hierarchy

{
  "tool": "define_value_hierarchy",
  "arguments": {
    "name": "Medical Safety Stack",
    "entries": [
      { "goal": "safety",    "label": "NON-NEGOTIABLE", "description": "Always prioritise patient safety" },
      { "goal": "clarity",   "label": "HIGH" },
      { "goal": "conciseness","label": "MEDIUM" }
    ],
    "conflict_rule": "Safety first, always."
  }
}

On success, the hierarchy is stored as session state and injected into every subsequent optimize_prompt call automatically.

get_value_hierarchy / clear_value_hierarchy

get_value_hierarchy returns a formatted listing of the active hierarchy.clear_value_hierarchy sets the session state to None and returns a confirmation message.

Zero-Regression Invariant

The design's most important invariant: when no value_hierarchy is present, every code path is byte-for-byte identical to the pre-change implementation.

Touch pointNo hierarchyWith hierarchy
_build_llm_system_promptReturns base unchangedAppends DIRECTIVES block
Cache keyIdentical to pre-changeAdds 8-char fingerprint suffix
Router scorePasses through untouchedFloor raised if needed
optimize_prompt dictNo value_hierarchy keyKey added

Verified: 33 pre-existing test failures / 99 passing — identical counts before and after all backend changes. All 31 new tests/test_value_hierarchy.py tests pass.

When to Use Which Label

NON-NEGOTIABLE

Safety constraints, medical/legal requirements, data privacy rules. The routing floor forces LLM_BASED tier — no short-circuits allowed.

HIGH

Legal clarity, compliance language, brand voice that must be consistently applied. Forces HYBRID tier minimum.

MEDIUM

Style preferences, tone guidelines. Affects the DIRECTIVES block only — routing is still cost-optimised.

LOW

Nice-to-haves, soft preferences. Lowest injection weight. Routing is fully cost-optimised.

Try Value Hierarchies in the MCP Server

Install Prompt Optimizer and call define_value_hierarchy from any MCP client (Claude Desktop, Cursor, Windsurf).

$ npm install -g mcp-prompt-optimizer
See the glossary definition