r/Clojure • • 3d ago

Clojure Agent Framework

https://github.com/TTalkPro/clj-agent

clj-agent is a modular Clojure AI Agent framework built around a ChatClient central orchestrator. It provides a deftool macro for tool definitions, multi-level invoke APIs, and onion-style Filter middleware mirroring Spring AI 2.0 Advisors — including ToolSearch progressive tool disclosure, RAG injection, and self-correcting structured output, all with zero added dependencies. Its dependency-inverted architecture separates a zero-dependency core, an agent runtime (ReAct loop, ChatMemory, HITL approval), and provider adapters for Anthropic, OpenAI, Gemini, Ollama, and other OpenAI-compatible services. All nine modules publish to the Clojars im.ttalk group, so you depend only on what you need.

The README: https://github.com/TTalkPro/clj-agent/blob/main/README_EN.md

The clojars: https://clojars.org/groups/im.ttalk

And I use this code in my own production, https://ttalk.im/products/jinshu/ https://ttalk.im/products/guanlan/

10 Upvotes

2 comments sorted by

1

u/bostonaholic 3d ago

Are tools plain maps under the hood, with deftool just sugar? If the tool spec (name, description, malli schema for args) is data, you get validation, docs, and the provider's JSON schema from one place, and you can build or filter tools at runtime without macros. Same question for the middleware: ring-style (fn [handler] (fn [req] ...)) would feel more at home than Spring AI Advisors.

1

u/TTalkIM 3d ago

clj-agent was designed with reference to Spring AI 2.0. In it, deftool is a macro—a kind of syntactic sugar—and it does not use malli schema for validation. Your suggestion is very good; I will consider enhancing it. The reason we did not choose the Ring style for middleware is that although the Ring style is very good, it is a one-way Request -> middleware -> Response flow, whereas the core of the Agent's filter needs to support recursion, re-entrancy, and skipping. Typical scenarios include:

  • re-entering the loop after final-answer validation fails

  • evaluator-optimizer

  • RE2 re-read

  • iteration-level retry

  • re-running the full loop after RAG injection

Therefore, we did not adopt the Ring design pattern.

And I use this code in my own production, https://ttalk.im/products/jinshu/ https://ttalk.im/products/guanlan/