Skip to content

symfonic.memory.layers.procedural.skill_render

skill_render

Rendering a procedural skill into the router prompt.

Split out of :mod:symfonic.memory.layers.procedural.router (371 lines against the 300-line budget). router selects tools; this module is the prompt vocabulary it selects them with -- the v7.5.1 PRE-FLIGHT marker format, the duck-typed input adapter, and the metadata reader that tolerates both the mapping and attribute shapes a skill can arrive in.

The split has a second reason: procedural.layer already imports these two functions from router at call time, purely to reuse the format. Now the format has a module of its own to be imported from. router re-exports every name, so both that call site and the precondition tests are unchanged.

render_skill_for_prompt

render_skill_for_prompt(entry: Any) -> str

Format a single skill for the router prompt's RELEVANT SKILLS section.

Three metadata['precondition'] shapes are honoured:

  • Non-empty str (v7.5.1 shape) -- appended as a single indented PRE-FLIGHT: line. Renders byte-identical to pre-7.6.2 output; prompt-cache parity is preserved.
  • Non-empty list[str] (v7.6.2 shape, adopter multi-precondition ask) -- one indented PRE-FLIGHT: line per entry, in supplied order. Empty / non-string entries are skipped silently. The router prompt's ordering clause at prompts/router.txt treats every PRE-FLIGHT line as a hard ordering constraint over required_tool_ids.
  • Anything else (None, empty string, whitespace-only, non-string non-list metadata) -- the bullet renders single-line and no PRE-FLIGHT marker is emitted.

Skills without preconditions render byte-identical to pre-7.5.1 so prompt-cache parity holds for adopters who never use the authored tier.

v7.7.4 (adopter ask public-render-skill-helper): promoted from the v7.5.1 private _render_skill_for_prompt to a public helper.

v7.7.5 (adopter L2-prose architectural ask, Option D): bare- identifier preconditions render with a strictly stronger imperative phrasing -- PRE-FLIGHT REQUIRED: BEFORE calling \`X\`, YOU MUST first call \`Y\`. -- to lift compliance on the L2 path for adopters who don't opt into the v7.7.5 L1 render knob. Free-text preconditions fall through to the v7.5.1 PRE-FLIGHT: phrasing byte-identical (free text cannot be safely rewritten without inventing tool names). The bare-id discriminator is shared with the v7.7.4 gate via _extract_bare_identifier; the action tool is resolved via _skill_action_tool. Skills with no resolvable bare-id action tool fall back to free-text phrasing even when the precondition itself is a bare id -- refusing to invent.

Source code in src/symfonic/memory/layers/procedural/skill_render.py
def render_skill_for_prompt(entry: Any) -> str:
    r"""Format a single skill for the router prompt's RELEVANT SKILLS section.

    Three ``metadata['precondition']`` shapes are honoured:

    * Non-empty ``str`` (v7.5.1 shape) -- appended as a single
      indented ``PRE-FLIGHT:`` line.  Renders byte-identical to
      pre-7.6.2 output; prompt-cache parity is preserved.
    * Non-empty ``list[str]`` (v7.6.2 shape, adopter multi-precondition
      ask) -- one indented ``PRE-FLIGHT:`` line per entry, in supplied
      order.  Empty / non-string entries are skipped silently.  The
      router prompt's ordering clause at ``prompts/router.txt`` treats
      every PRE-FLIGHT line as a hard ordering constraint over
      ``required_tool_ids``.
    * Anything else (``None``, empty string, whitespace-only,
      non-string non-list metadata) -- the bullet renders single-line
      and no PRE-FLIGHT marker is emitted.

    Skills without preconditions render byte-identical to pre-7.5.1 so
    prompt-cache parity holds for adopters who never use the authored
    tier.

    v7.7.4 (adopter ask `public-render-skill-helper`): promoted from
    the v7.5.1 private ``_render_skill_for_prompt`` to a public
    helper.

    v7.7.5 (adopter L2-prose architectural ask, Option D): bare-
    identifier preconditions render with a strictly stronger imperative
    phrasing -- ``PRE-FLIGHT REQUIRED: BEFORE calling \`X\`, YOU MUST
    first call \`Y\`.`` -- to lift compliance on the L2 path for
    adopters who don't opt into the v7.7.5 L1 render knob.  Free-text
    preconditions fall through to the v7.5.1 ``PRE-FLIGHT:`` phrasing
    byte-identical (free text cannot be safely rewritten without
    inventing tool names).  The bare-id discriminator is shared with
    the v7.7.4 gate via ``_extract_bare_identifier``; the action tool
    is resolved via ``_skill_action_tool``.  Skills with no resolvable
    bare-id action tool fall back to free-text phrasing even when the
    precondition itself is a bare id -- refusing to invent.
    """
    # TA2.3: the skill-metadata readers live in the kernel-contracts module
    # that owns the vocabulary, not in the facade-compiler gate node that
    # enforces it -- ``capability -> facade-compiler`` is ``no``. Same two
    # functions, same definitions.
    from symfonic.core.contracts.skill_metadata import (
        extract_bare_identifier as _extract_bare_identifier,
    )
    from symfonic.core.contracts.skill_metadata import (
        skill_action_tool as _skill_action_tool,
    )

    steps = _skill_metadata_field(entry, "steps") or []
    content = getattr(entry, "content", "")
    line = f"- {content} (steps: {steps})"
    precondition = _skill_metadata_field(entry, "precondition")
    action_tool = _skill_action_tool(entry)
    if isinstance(precondition, str) and precondition.strip():
        pre_tool = _extract_bare_identifier(precondition)
        if pre_tool and action_tool:
            line += (
                f"\n  PRE-FLIGHT REQUIRED: BEFORE calling `{action_tool}`,"
                f" YOU MUST first call `{pre_tool}`."
            )
        else:
            line += f"\n  PRE-FLIGHT: {precondition.strip()}"
    elif isinstance(precondition, list):
        for item in precondition:
            if not isinstance(item, str) or not item.strip():
                continue
            pre_tool = _extract_bare_identifier(item)
            if pre_tool and action_tool:
                line += (
                    f"\n  PRE-FLIGHT REQUIRED: BEFORE calling"
                    f" `{action_tool}`, YOU MUST first call `{pre_tool}`."
                )
            else:
                line += f"\n  PRE-FLIGHT: {item.strip()}"
    return line