Directives

The Directive Engine preprocesses prompts before the LLM call, resolving @context, @memory, @agent, /tool, /mcp, and #meta directives into enriched context. Small and local models that lack native tool-calling can use the full Sagewai stack through directive syntax alone.

from sagewai import DirectiveEngine

engine = DirectiveEngine(
    context=my_context_engine,
    model="codellama:7b",
)
result = await engine.resolve("@context('ML basics') Help me learn")
# result.prompt contains enriched text with context injected

DirectiveEngine

Main orchestrator for prompt preprocessing. Two syntax modes are available:

  • Sigil mode (resolve): @context('q'), /tool.name('a'), #model:x
  • Template mode (resolve_template): {{ context.search('q') }}

Both modes resolve through the same pipeline: parse, resolve, format, compress, and assemble.

from sagewai import DirectiveEngine

engine = DirectiveEngine(
    context=context_engine,
    memory=memory_provider,
    tools={"search": search_tool},
    agents={"researcher": researcher_agent},
    model="gpt-4o",
    resolution_timeout=10.0,
)
result = await engine.resolve(
    "@context('machine learning') @memory('previous findings') Summarize"
)

Constructor

ParameterTypeDefaultDescription
contextAny | NoneNoneContext Engine for @context directives
memoryAny | NoneNoneMemory provider for @memory directives
toolsdict[str, Any] | NoneNoneTools for /tool directives
agentsdict[str, Any] | NoneNoneAgents for @agent:name() directives
mcp_clientsdict[str, Any] | NoneNoneMCP clients for /mcp directives
modelstr | NoneNoneModel name for auto-detection of profile
model_profileModelProfile | NoneNoneExplicit model profile (overrides auto-detection)
max_context_tokensint | NoneNoneToken budget override
registryDirectiveRegistry | NoneNoneCustom directive registry
allowed_toolsset[str] | NoneNoneTool allowlist (security)
allowed_mcpset[str] | NoneNoneMCP allowlist (security)
allow_all_toolsboolFalseBypass tool allowlists
resolution_timeoutfloat10.0Max seconds per directive resolution
max_agent_depthint3Max recursive agent delegation depth

Methods

MethodSignatureReturnsDescription
resolveasync resolve(prompt: str)DirectiveResultResolve sigil-syntax directives
resolve_templateasync resolve_template(prompt: str)DirectiveResultResolve template-syntax directives

DirectiveResult

Result of directive resolution. Contains the enriched prompt and metadata.

result = await engine.resolve("@context('topic') My question")

print(result.prompt)           # Enriched prompt with context injected
print(result.clean_prompt)     # Original text with directives stripped
print(result.context_blocks)   # Resolved context blocks
print(result.metadata)         # Token counts, timings, stats
print(result.tool_descriptions)  # Tool descriptions for prompt-based calling

Fields

FieldTypeDescription
promptstrFinal enriched prompt text with all context injected
clean_promptstrOriginal text with directives stripped
context_blockslist[ContextBlock]Resolved context blocks for system message injection
metadataDirectiveMetadataToken counts, timings, resolution stats
directives_foundlist[ResolvedDirective]All parsed directives with results
overridesExecutionOverrides | NoneExecution overrides from # meta-directives
tool_descriptionsstrFormatted tool descriptions for prompt-based tool calling

ModelProfile

Defines how the Directive Engine formats output for a model class. Controls compression aggressiveness, delimiter style, tool-call mode, and token budget allocation.

from sagewai import ModelProfile

profile = ModelProfile(
    name="custom",
    max_context_tokens=8192,
    compression_ratio=2.0,
    tool_call_mode="prompt_based",
)

Fields

FieldTypeDefaultDescription
namestrrequiredProfile identifier
max_context_tokensint4096Token budget for directive results
compression_ratiofloat1.0Target compression (1.0 = none, 5.0 = aggressive)
max_few_shotint3Max few-shot examples to inject
use_delimitersboolFalseWrap content in [CONTEXT]/[SOURCE] delimiters
use_explicit_instructionsboolFalseAdd explicit framing for small models
tool_call_modestr"native""native" or "prompt_based"
context_budgetdict[str, float]{...}Token budget allocation per category
default_top_kint5Default top_k for context retrieval

Built-in Profiles

ProfileContext TokensCompressionTool ModeUse Case
SMALL20485.0prompt_basedLocal models under 13B params
MEDIUM81922.0nativeMid-range models (13B-70B, GPT-4o-mini)
LARGE327681.0nativeFrontier models (GPT-4o, Claude, Gemini Pro)

detect_profile

Auto-detect the model profile from a model name string. Matches against known patterns and falls back to MEDIUM for unknown models.

from sagewai import detect_profile

profile = detect_profile("codellama:7b-instruct")  # -> SMALL
profile = detect_profile("gpt-4o")                  # -> LARGE
profile = detect_profile("ollama/mistral")           # -> SMALL
profile = detect_profile("unknown-model")            # -> MEDIUM

Signature

detect_profile(model: str) -> ModelProfile

@transform

@transform applies a named transform — graphify, summarize, or a custom registered operation — to a body of text before the LLM call. Wire it onto a DirectiveEngine with register_transform_directive:

from sagewai.directives import DirectiveEngine
from sagewai.transform import register_transform_directive

engine = DirectiveEngine(model="ollama/llama3.2:3b")
register_transform_directive(engine)
# @transform(...) now resolves on this engine

Unlike @context or @memory, @transform is not a built-in — it must be registered explicitly. register_transform_directive registers it as a multi-argument custom directive (the raw_args form of DirectiveRegistry, which the single-quoted-arg form does not support): the first argument is a bare operation token (not a quoted string), the second is a nested directive reference (@context(...), @memory(...)) or a literal string, and any additional key=value pairs are forwarded as operation params.

@transform(operation, source)
@transform(operation, source, key=value, …)

The source argument is resolved first — nested directives are expanded to text — then the transform runs on that result. The TransformResult.output is injected into the enriched prompt in place of the directive. A failed transform injects nothing and logs a warning; directive resolution always degrades gracefully.

Operations

OperationDescription
graphifyExtracts relation triples and writes them to project-scoped GraphMemory. Output is a compact digest of the relations written.
summarizeSummarises the input text. Accepts a max_words param (default 200).
customAny async callable registered on a TransformRegistry via registry.register("name", fn).

graphify — long context into graph memory

Extracts (subject, predicate, object) triples from text using the LLMRelationExtractor, then writes each triple to the project-scoped GraphMemory. Extracting zero triples is a success — metadata.relations_written is 0 and the output notes "no relations extracted". The LLMRelationExtractor is parse_json-robust, so a fenced SLM response still yields clean triples.

# Distil a long incident transcript into graph memory
@transform(graphify, @context('incident transcript'))

Injected output: "12 relations into graph memory: Alert→triggered-by→DeployX; DeployX→owned-by→TeamA; …"

summarize — compress a large input for a small context window

One LLM summarisation call. A small or local model is an acceptable choice. The max_words param controls output length (default 200).

# Compress a memory namespace before a small model sees it
@transform(summarize, @memory('mission-42'), max_words=200)

Custom operations

A transform operation is any async callable. Register one on a TransformRegistry, wrap that registry in a TransformEngine, and pass the engine to register_transform_directive@transform then invokes the operation by name:

from sagewai.transform import (
    TransformEngine,
    default_registry,
    register_transform_directive,
)

async def extract_clauses(content, *, project_id=None, **params):
    # parse content and return a summary string
    return "Liability clause found; Termination clause: 30 days"

# default_registry() carries graphify + summarize — add the custom op to it
registry = default_registry()
registry.register("extract_clauses", extract_clauses)

register_transform_directive(engine, transform_engine=TransformEngine(registry))
@transform(extract_clauses, @context('vendor contract'))

A bare-string return is auto-wrapped into a successful TransformResult. Return a TransformResult directly to set metadata or signal failure.

TransformRegistry vs DirectiveRegistry: TransformRegistry.register("name", fn) adds an operation @transform can run — it extends what @transform can do, not what directives the engine parses. To add a new directive sigil (e.g. @kb('query') with the single-quoted-arg form), use DirectiveRegistry instead.

Tool form (mid-loop)

The same engine is also available as the transform built-in tool — called mid-loop by any agent. Models without native tool calling reach it via the /tool prose-parser:

/tool.transform(operation="summarize", content="…", params={"max_words": 100})

Returns TransformResult.output on success, or a "transform failed: …" string on failure that the agent can inspect and react to.

Parameters

ParameterTypeRequiredDescription
operationstrYes"graphify", "summarize", or a registered custom name
sourcedirective or strYesText to transform. Nested directives are resolved to text first.
max_wordsintNo (summarize only)Summary word limit (default 200)

Every operation runs under the TransformEngine's configurable timeout (default 30.0 s), applied with asyncio.wait_for — Python 3.10-safe. When invoked as a directive, the directive engine's own resolution_timeout additionally bounds overall directive resolution.


Directive Syntax Reference

DirectiveSyntaxDescription
Context@context('query')Retrieve context by query
Context (scoped)@context('query', scope='org', tags='finance,q4')Scoped retrieval with tag filtering
Memory@memory('query')Search memory store
Agent@agent:name('task')Delegate to another agent (colon syntax)
Workflow@wf:name('input')Invoke a saved workflow
Tool/tool.name('args')Invoke a tool
MCP/mcp.server.tool('args')Invoke an MCP tool
Transform@transform(op, source)Apply a named transform before the LLM call (extended multi-arg syntax; enable with register_transform_directive)
Model#model:nameOverride model for this call
Budget#budget:amountSet cost budget
Dynamic@datetime, @date, @timeCurrent date/time values
Template{{ context.search('q') }}Template syntax alternative