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
| Parameter | Type | Default | Description |
|---|---|---|---|
context | Any | None | None | Context Engine for @context directives |
memory | Any | None | None | Memory provider for @memory directives |
tools | dict[str, Any] | None | None | Tools for /tool directives |
agents | dict[str, Any] | None | None | Agents for @agent:name() directives |
mcp_clients | dict[str, Any] | None | None | MCP clients for /mcp directives |
model | str | None | None | Model name for auto-detection of profile |
model_profile | ModelProfile | None | None | Explicit model profile (overrides auto-detection) |
max_context_tokens | int | None | None | Token budget override |
registry | DirectiveRegistry | None | None | Custom directive registry |
allowed_tools | set[str] | None | None | Tool allowlist (security) |
allowed_mcp | set[str] | None | None | MCP allowlist (security) |
allow_all_tools | bool | False | Bypass tool allowlists |
resolution_timeout | float | 10.0 | Max seconds per directive resolution |
max_agent_depth | int | 3 | Max recursive agent delegation depth |
Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
resolve | async resolve(prompt: str) | DirectiveResult | Resolve sigil-syntax directives |
resolve_template | async resolve_template(prompt: str) | DirectiveResult | Resolve 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
| Field | Type | Description |
|---|---|---|
prompt | str | Final enriched prompt text with all context injected |
clean_prompt | str | Original text with directives stripped |
context_blocks | list[ContextBlock] | Resolved context blocks for system message injection |
metadata | DirectiveMetadata | Token counts, timings, resolution stats |
directives_found | list[ResolvedDirective] | All parsed directives with results |
overrides | ExecutionOverrides | None | Execution overrides from # meta-directives |
tool_descriptions | str | Formatted 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
| Field | Type | Default | Description |
|---|---|---|---|
name | str | required | Profile identifier |
max_context_tokens | int | 4096 | Token budget for directive results |
compression_ratio | float | 1.0 | Target compression (1.0 = none, 5.0 = aggressive) |
max_few_shot | int | 3 | Max few-shot examples to inject |
use_delimiters | bool | False | Wrap content in [CONTEXT]/[SOURCE] delimiters |
use_explicit_instructions | bool | False | Add explicit framing for small models |
tool_call_mode | str | "native" | "native" or "prompt_based" |
context_budget | dict[str, float] | {...} | Token budget allocation per category |
default_top_k | int | 5 | Default top_k for context retrieval |
Built-in Profiles
| Profile | Context Tokens | Compression | Tool Mode | Use Case |
|---|---|---|---|---|
SMALL | 2048 | 5.0 | prompt_based | Local models under 13B params |
MEDIUM | 8192 | 2.0 | native | Mid-range models (13B-70B, GPT-4o-mini) |
LARGE | 32768 | 1.0 | native | Frontier 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
| Operation | Description |
|---|---|
graphify | Extracts relation triples and writes them to project-scoped GraphMemory. Output is a compact digest of the relations written. |
summarize | Summarises the input text. Accepts a max_words param (default 200). |
| custom | Any 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
| Parameter | Type | Required | Description |
|---|---|---|---|
operation | str | Yes | "graphify", "summarize", or a registered custom name |
source | directive or str | Yes | Text to transform. Nested directives are resolved to text first. |
max_words | int | No (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
| Directive | Syntax | Description |
|---|---|---|
| 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:name | Override model for this call |
| Budget | #budget:amount | Set cost budget |
| Dynamic | @datetime, @date, @time | Current date/time values |
| Template | {{ context.search('q') }} | Template syntax alternative |