-
Notifications
You must be signed in to change notification settings - Fork 0
Compiler and Validation Rules
theirish81 edited this page Sep 3, 2026
·
1 revision
The FML compiler enforces strict static and runtime validation rules to ensure deterministic execution, prevent infinite loops, and eliminate runtime LLM formatting errors.
-
Rule: Never declare
require mcp searchorrequire searchat the file level. - Explanation: Web search is a native, built-in capability of the FML runtime.
-
Correction: Use
use searchinside the individualsessionblocks where search is needed.
# ❌ INCORRECT:
require mcp search
# ✅ CORRECT:
session("research") {
use search
}
-
Rule: Any session that references
context.<session_name>inside itsexpect,iterate, or template strings MUST declareafter="<session_name>". - Explanation: FML builds an execution DAG. If a dependency is undeclared, the runtime cannot guarantee that the upstream session has completed before the downstream session begins.
# ❌ INCORRECT:
session("format_data") {
context "Previous: {{ .context.fetch_data | json }}" # ERROR: fetch_data not declared in after!
}
# ✅ CORRECT:
session("format_data", after="fetch_data") {
context "Previous: {{ .context.fetch_data | json }}"
}
-
Rule: If a session declares the
iterateattribute, itsschemamust be an array type (e.g.,schema type[]orschema { ... }[]). - Explanation: When iterating over a collection, the runtime executes the session once for each element and sequentially appends each output to an array in the exact order of the input items.
# ❌ INCORRECT:
session("process_batch", iterate=context.items) {
schema { id: string } # ERROR: Must be an array schema!
}
# ✅ CORRECT:
session("process_batch", iterate=context.items) {
schema { id: string }[] # OR schema string[]
}
-
Rule: Each session must have exactly one prompt prefixed with
-. -
Explanation: Pre-prompts (
+) allow tool use and free-form reasoning. The single prompt (-) triggers the final schema-constrained generation where tool schemas are stripped. Having multiple prompts would create ambiguity regarding which prompt governs the schema output.
# ❌ INCORRECT:
session("example") {
- First prompt.
- Second prompt. # ERROR: Only one prompt allowed!
schema { id: string }
}
# ✅ CORRECT:
session("example") {
+ Free-form pre-prompt (Tools enabled)
+ Another pre-prompt (Tools enabled)
- Final prompt (Tools disabled)
schema { id: string }
}
- Rule: Do not wrap schema properties inside a redundant parent object named after the session.
-
Explanation: Session outputs are already automatically namespaced under
context.<session_name>. Wrapping fields in an extra nested object creates unnecessary property depth (e.g.,context.user.user.name).
# ❌ INCORRECT:
session("user_details") {
schema {
user_details: {
username: string
}
}
}
# ✅ CORRECT:
session("user_details") {
schema {
username: string
}
}
-
Rule: A
transformerblock can define eitherjmesPathorcode, but never both. - Explanation: A tool output can only undergo one primary transformation pipeline.
# ❌ INCORRECT:
transformer("bad_transformer") {
onFunctionOutput = "query"
jmesPath = "[*].id"
code((args.map(x => x.id))) # ERROR: Mutually exclusive!
}
-
Rule: If a session's
expectcondition evaluates tofalse, that session is skipped completely. Any downstream sessions depending on it viaafterare automatically skipped in cascade. - Explanation: Downstream sessions cannot satisfy their data requirements if the upstream provider was skipped.
session("optional_stage", expect="params.enable_deep_scan") {
# If enable_deep_scan is false, this is skipped...
}
session("report_stage", after="optional_stage") {
# ...which automatically skips this stage as well!
}
-
Rule: Enum or string union fields must use the vertical bar (
|) syntax without quotes around individual choices. - Explanation: Standardized syntax for categorical fields.
schema {
severity: low|medium|high|critical # Valid enum
tags: ai|cloud|security[] # Valid array of enum items
}
-
Rule: All global file-level
callstatements must define an explicit output target routing (-> vars:nameor-> name). - Explanation: Session-level calls can omit target routing because the engine injects their output into the session's ambient context. At the root level, there is no ambient session context, so target routing is mandatory.
# ❌ INCORRECT:
call("pre_warm") { # ERROR: Missing target routing!
env = "prod"
}
# ✅ CORRECT:
call("pre_warm") -> vars:prewarm_result {
env = "prod"
}
-
Rule: The
contextblock (e.g.,context "...") is evaluated during session initialization. It must only reference completed upstream session outputs ({{ .context.completed_session }}). -
Antipattern: Referencing local variables populated by PreCalls in the current session inside a
contextstatement is an antipattern. PreCalls execute after session initialization, so local variables are not yet defined whencontextis evaluated. -
Correction: Reference local PreCall variables directly inside pre-prompts (
+) or prompts (-), or omit the PreCall's target routing so it is injected automatically.
# ❌ INCORRECT:
session("audit") {
call("fetch_logs") -> vars:local_logs { limit = 100 }
context "Logs: {{ .vars.local_logs | json }}" # FAIL: Evaluated before fetch_logs runs!
}
# ✅ CORRECT:
session("audit") {
call("fetch_logs") -> vars:local_logs { limit = 100 }
+ Review the fetched logs: {{ .vars.local_logs | json }}
- Summarize anomalies.
schema { anomalies: string[] }
}
-
Rule: When generating plans, avoid routing PreCalls directly to the context namespace (
-> context:key). -
Explanation:
-> context:keytightly couples tool outputs to the public session bus, bypassing session boundary validation. Use-> vars:name, implicit routing-> name, or omit routing instead.
| Routing Expression | Validity | Consequence |
|---|---|---|
-> vars:result |
Valid | Stored in vars.result
|
-> result |
Valid | Implicitly stored in vars.result
|
-> context:result |
Valid | Stored in context.result
|
-> vars.result |
INVALID | Syntax Parse Error (dots not permitted in target arrows) |
-> context.result |
INVALID | Syntax Parse Error (dots not permitted in target arrows) |
Go Templates ({{ ... }}):
✔ {{ .params.x }} (Leading dot REQUIRED)
✖ {{ params.x }} (Syntax error / evaluates to empty)
Antonmedv Expr ($( ... ), expect, iterate):
✔ $(vars.x) (No leading dot)
✔ expect="params.x > 0" (No leading dot)
✖ $(.vars.x) (Syntax error)
✖ expect=".params.x > 0"(Syntax error)
| Error / Symptom | Probable Cause | Fix |
|---|---|---|
Unknown tool 'search' |
Used require mcp search
|
Remove require, use use search inside session. |
Unsatisfied dependency: foo |
Missing after="foo" on session |
Add after="foo" attribute. |
Iterate session must produce array |
Schema is an object (schema { ... }) |
Change schema to array (schema { ... }[] or schema string[]). |
Multiple prompts defined |
Defined more than one - prompt |
Change upstream prompts to pre-prompts (+). |
Global call missing target |
call("...") { ... } at file root |
Add -> vars:name output routing. |
Empty context variable |
Referenced local PreCall variable in context statement |
Reference variable in + or - prompt instead. |
Tool call failed during prompt |
Instructed LLM to use tools in - prompt |
Move tool instructions to + pre-prompt. |
FML (Frags Modeling Language) | Getting Started | Cheat Sheet | Examples
Documentation for FML & Gemini Agent Workflows — Maintained by Frags HQ