Loops? Graphs? Prolog

schmuhblaster1 pts0 comments

Loops? Graphs? Prolog! - Substack von Andreas

Substack von Andreas

AbonnierenAnmelden

Loops? Graphs? Prolog!<br>Step by step guide to building an agent with DeepClause.

Andreas<br>Juli 25, 2026

Teilen

So, apparently people are discussing how to orchestrate agents not just with loops now, but also graphs:

This framing is somewhat confusing to me. Or at least the timing of it. Didn’t we have LangGraph already since like…forever? What have all these other agent frameworks been doing? Or is this about letting your agent write the prompts for other agents? Who knows…<br>What I do know is that whatever loop or graph-based orchestration you desire, it should be easily implementable using an actual Turing-complete language. So why not try Prolog!<br>Prolog itself (or rather modern implementations such as SWI or Scryer Prolog) might not be exactly the right choice to do this of course. This is where the DeepClause project comes in. It introduces a language called DML: a Prolog dialect purpose-built for programming AI agents. You get LLM calls as native predicates, automatic backtracking across model invocations and agent memory, typed outputs, and tool orchestration — all in a few lines of declarative code that get executed by TypeScript and WASM based runtime (it actually uses SWI Prolog through WASM).<br>So let’s try it out and build something real: a vulnerability scanner agent that analyzes a codebase, finds security issues, and reports them. We’ll start dead simple and layer on capabilities.<br>The Simplest Possible Agent

First, install and configure DeepClause:<br>npm install -g deepclause-sdk<br>deepclause init #this creates .deepclause/<br>deepclause --set-model [your favorite model, e.g. openai:gpt-5.4-mini]Having completed our setup, let’s start by defining our agent. Open a file in the current directory (where your .deepclause folder is), name it “vuln-scanner.dml” and add the following code:<br>agent_main :-<br>system(”You are a senior security researcher.<br>You specialize in finding vulnerabilities<br>in source code.“),<br>task(”Analyze common web application patterns and<br>list the top 5 vulnerability categories<br>you would look for during a security audit.“),<br>answer(“Analysis complete.”).What does this mean?<br>Every DML program starts with agent_main. This is the agent entry point.

system/1 sets the LLM’s persona. It goes into the conversation as a system message — persistent context that shapes every subsequent call.

task/1 sends a request to the model. The LLM thinks, responds, and execution continues. This will trigger a full blown agent loop. If we had defined any tools for our agent, they might get used here as well (see next example for more details).

answer/1 emits the final result and commits — no backtracking happens after this. The program is done.

Run it:<br>deepclause run vuln-scanner.dmlDepending on what model you chose, this will produce a nice list of OWASP categories or similar (use —stream and —verbose options to better understand what’s going on). Useful as a starting point, but not exactly a scanner. It’s just talking to itself. Let’s make it interactive.<br>Adding User Input

Now, let’s add a tool that the LLM can use to talk to users. In DML we defined LLM-tools with the tool/2 predicate — a name, a description (shown to the LLM), and a body that does the actual work:<br>% Tool: Ask the user a question<br>tool(ask(Prompt, Response),<br>“Ask the user a question and get their response”) :-<br>exec(ask_user(prompt: Prompt), Result),<br>Response = Result.user_response.

agent_main :-<br>system(”You are a senior security researcher.<br>You specialize in finding vulnerabilities<br>in source code.“),<br>task(”Ask the user what codebase or file they want<br>to analyze for security vulnerabilities.<br>Store their response in Target.“, Target),<br>task(”Based on the target ‘{Target}’, outline a<br>security audit plan. List what you would check<br>and why. Store the plan in Plan.”, Plan),<br>output(Plan),<br>answer(“Audit plan generated.”).tool/2 makes a predicate visible to the LLM as a tool. When we tell the model to “ask the user” in a task(), it can call our ask tool — DML routes that to the exec/2 call under the hood, which invokes the registered ask_user external tool (things callable through the exec predicate are actually defined in DeepClause’s typescript layer, which maybe extended through the DeepClause SDK). The response flows back into the Prolog variable “Response”. The key insight: tools defined with tool/2 are available to the LLM during any task() call. The model decides when to use them.<br>task/2 (with two arguments) is where DML gets interesting. The second argument is an output variable. We instruct the model to “ask the user” and “store their response in Target” — the LLM calls the ask tool, gets the user’s answer, and the runtime binds it to Target. You reference the variable name in the prompt itself, and the runtime extracts it. This is how data flows between steps: not through callbacks or shared mutable state, but through Prolog unification.<br>output/1...

deepclause tool prolog agent model task

Related Articles