Reference

DML language reference for the current runtime.

DML is a Prolog-style language for agent workflows. It combines declarative logic, backtracking, recursion, direct tool execution, and LLM-driven subtasks. This page reflects the current runtime behavior, including the modern task harness and task-loop semantics.

At a Glance

What DML is for

Use DML when the workflow itself matters: retries, fallback clauses, recursion, explicit tool routing, helper predicates, and a clear separation between deterministic logic and open-ended model calls.

Logic

Normal Prolog-style clauses, unification, backtracking, cuts, and recursion.

LLM calls

task() and prompt() support typed outputs and tool use.

Tooling

exec() calls runtime and MCP tools directly, while tool wrappers expose capabilities to the model loop.

Control

Use helper files, scoped tools, params, memory operations, and standard SWI libraries.

Entry Points

agent_main is required

Every DML program needs at least one agent_main clause. The current runtime dispatches agent_main/0 through agent_main/3. If the provided argument count does not match exactly, the runtime falls back to the best available arity and leaves missing positions unbound.

% No arguments
agent_main :-
    answer("Ready.").

% One positional argument
agent_main(Topic) :-
    answer(Topic).

% Up to three positional arguments
agent_main(Topic, Mode, Limit) :-
    answer("Running").
  • CLI positional arguments arrive as strings.
  • SDK runDML() can pass structured values such as numbers, booleans, lists, and dict-like objects, which are serialized into Prolog terms.
  • Use named params for optional settings instead of packing too many meaning changes into positional args.
Parsing text arguments If a CLI skill needs numeric input, parse it explicitly with predicates such as number_string/2 or atom_number/2.
LLM Predicates

task() uses memory, prompt() starts fresh

task() and prompt() are the core model-facing predicates. They share the same task harness and tool mechanism, but differ in memory behavior.

Predicate Behavior
task(Description) Run a subtask using accumulated memory from system(), user(), and earlier task history.
task(Description, Var1[, Var2, Var3]) Run a subtask and return up to three outputs through typed or untyped output variables.
prompt(Description) Run a model call with fresh memory and no prior conversation context.
prompt(Description, Var1[, Var2, Var3]) Fresh-memory variant with typed or untyped outputs.
system(Text) Add a system instruction or persona to the current memory.
user(Text) Add a user turn to the current memory.

Typed outputs

Typed wrappers let the runtime validate the model output before returning it to the clause.

task("Return the issue count.", integer(IssueCount)).
task("Return true or false only.", boolean(IsValid)).
task("List the file paths.", list(string(Paths))).
task("Return a JSON object with summary and severity.", object(Report)).
task("Return a floating-point score.", number(Score)).
  • Supported wrappers include string(), integer(), number(), float(), boolean(), list(...), and object().
  • Describe expected outputs plainly in the prompt text. The harness is more reliable when the prompt names the outputs and required format directly.
  • Prefer prompt() for independent critiques, classification passes, or fresh reasoning that should not inherit earlier context.

Memory isolation example

agent_main(Topic) :-
    system("You are a researcher."),
    task("Research {Topic} and produce a draft.", Draft),
    prompt("Critique this draft from a skeptical perspective: {Draft}", Critique),
    task("Revise the draft using this critique: {Critique}", Final),
    answer(Final).
Task Loop Semantics

Tool wrappers are for the model loop, not ordinary predicate calls

DML tool wrappers expose capabilities that the model can decide to call while executing task() or prompt(). They are not regular predicates that you invoke directly from ordinary DML control flow.

tool(search(Query, Results), "Search the web") :-
    exec(web_search(query: Query), Results).

tool(explain_sum(A, B, Explanation), "Calculate and explain a sum") :-
    Total is A + B,
    format(string(Desc), "Explain why ~w + ~w = ~w", [A, B, Total]),
    task(Desc, Explanation).
Core built-ins inside the task loop The current runtime injects finish(success) and set_result(variable, value) into the task loop. If the task declares output variables, the model must set them before finish(true) succeeds.
  • Nested task() or prompt() calls inside a tool run with fresh memory, not the parent task memory.
  • The currently executing tool is hidden from the nested task loop to prevent immediate self-recursion.
  • Use with_tools(ToolList, Goal) and without_tools(ToolList, Goal) to narrow the available tool set for a nested model call.
tool(safe_research(Topic, Result), "Research using only the search tool") :-
    with_tools([search], (
        format(string(Desc), "Research ~w and summarize the results", [Topic]),
        task(Desc, Result)
    )).

tool(cheap_pass(Input, Output), "Run a cheaper pass without expensive tools") :-
    without_tools([expensive_api], (
        format(string(Desc), "Process this input conservatively: ~w", [Input]),
        prompt(Desc, Output)
    )).
Exec and Tools

exec() is the direct path to runtime and MCP tools

Use exec(ToolCall, Result) when the step is deterministic or external by nature: shell commands, HTTP fetches, search calls, browser steps, or runtime-specific orchestration helpers.

exec(web_search(query: "latest AI news"), SearchResult).
exec(url_fetch(url: "https://example.com", save_to: "notes/example.html"), FetchResult).
exec(vm_exec(command: "git status --short"), ShellResult),
get_dict(stdout, ShellResult, StatusText).

Common runtime tools

Tool Notes
web_search Search the web through the configured runtime provider.
news_search Search recent news results.
url_fetch Fetch a page and optionally save it into the workspace.
bash Run a shell command in the active workspace.
vm_exec Alias of bash; with --sandbox, execution happens in AgentVM.
  • Other tools can be runtime-role specific. The conductor and skill creator each add their own higher-level exec tools.
  • MCP servers can add more tools without changing the DML language itself.
  • Shell tools run in the configured workspace. In sandbox mode the workspace is mounted at /workspace.

Handling structured results

exec(vm_exec(command: "echo '{\"ok\": true}'"), ShellResult),
get_dict(stdout, ShellResult, Stdout),
atom_json_dict(Stdout, Data, []),
get_dict(ok, Data, IsOk).

Most runtime tools return dict-like structures. Use get_dict/3 for field access and atom_json_dict/3 when a tool emits JSON text that you want to parse back into a dict.

Memory and Control

Backtracking and explicit memory control are first-class

DML keeps the normal Prolog model: clauses can fail, the runtime backtracks to alternatives, and memory is restored with the choice point. That makes fallback workflows natural instead of bolted on.

Predicate Purpose
output(Text) / yield(Text) Emit progress output without ending the run.
log(Text) Emit a debug-style log message.
answer(Text) Emit the final answer and commit execution.
push_context, push_context(clear), pop_context Save and restore memory manually when you want local isolation inside a clause.
clear_memory Clear accumulated model memory.

Fallback via multiple clauses

agent_main(Question) :-
    system("Answer concisely."),
    task("Answer this question: {Question}", Draft),
    validate_answer(Draft),
    answer(Draft).

agent_main(Question) :-
    system("Use a more thorough approach."),
    task("Research this question carefully: {Question}", Final),
    answer(Final).

Recursion and list processing

process_files([]).
process_files([File|Rest]) :-
    task("Review this file: {File}"),
    process_files(Rest).

agent_main(Files) :-
    process_files(Files),
    answer("All files processed.").

Normal Prolog constructs such as fail, true, disjunction, if-then-else, list decomposition, arithmetic with is/2, and cuts remain available.

Params and Directives

Named parameters, interpolation, and helper file loading

DML supports named runtime parameters, compile-time string interpolation, and workspace-relative helper loading. These features are what make larger skills maintainable.

Named params

agent_main :-
    param(system_prompt, SystemPrompt),
    param(mode, "Requested execution mode", Mode),
    system(SystemPrompt),
    answer(Mode).
  • param(Key, Value) looks up a named parameter passed at runtime.
  • param(Key, Description, Value) is the self-documented form used by system skills and tooling.
  • Named params are especially useful for configuration-like inputs that should not occupy positional agent_main arguments.

Parameter declaration directives

:- param(system_prompt, "Optional system prompt override").
:- param(limit, "Maximum result count", 10).

These directives let a skill declare expected named parameters for documentation and tooling purposes. Runtime lookups still happen through param/2 or param/3 inside executable clauses.

String interpolation

task("Summarize the findings for {Topic}", Summary).
output("Processed {Count} files successfully.").

Quoted strings support {Var} interpolation against bound variables. Use format(string(...), ...) when you need precise formatting or want to build strings incrementally from non-string terms.

Libraries and helper files

:- use_module(library(lists)).
:- use_module(library(clpfd)).
:- consult('.deepclause/tools/my-skill/helpers/search_helpers.dml').
  • Use :- use_module(library(...)). for standard SWI-Prolog libraries such as lists, clpfd, clpq, and clpr.
  • Use :- consult('relative/path.dml'). for workspace-local helper predicates.
  • Local :- use_module('path/to/helper.dml'). also works today, but it behaves more like consult-style loading than strict SWI export filtering.
  • Keep non-DML helper assets, scripts, and per-skill virtual environments under .deepclause/tools/lib/<name>/. Keep consulted .dml helpers wherever the skill structure stays clearest.
Best Practices

Write smaller, more explicit skills

  • Use ordinary Prolog and exec() for deterministic work such as parsing, arithmetic, file I/O, or branching on exact values.
  • Use task() and prompt() only where open-ended reasoning or generation is genuinely needed.
  • Move reusable logic into helper predicates or helper files rather than growing one giant agent_main clause.
  • Prefer scoped tool exposure for nested model calls so the model only sees the tools relevant to that local step.
  • Be explicit about output variables in prompt text. Smaller models behave better when the prompt states exactly what result names and formats are expected.

Common mistakes

  • Treating a tool wrapper like a normal predicate call. Use exec() directly or let the model call the wrapper during task() or prompt().
  • Assuming nested task() calls inherit the parent memory. They do not.
  • Forgetting that output variables in a task loop must be set through set_result before finish(true).
  • Overloading positional arguments when a named param would be easier to read and safer to evolve.
  • Using the model for logic that could be encoded directly as a deterministic predicate.