An open API service indexing awesome lists of open source software.

https://github.com/brianium/cog-town

Build agentic workflows with simple core.async primitives
https://github.com/brianium/cog-town

agents async channels clojure llms

Last synced: 3 months ago
JSON representation

Build agentic workflows with simple core.async primitives

Awesome Lists containing this project

README

          

# Cog Town 🏘️

[![Clojars Project](https://img.shields.io/clojars/v/com.github.brianium/cog-town.svg)](https://clojars.org/com.github.brianium/cog-town)

Build **agentic workflows** in Clojure with the ergonomics of `core.async`.

`cog.town` gives you a tiny set of composable primitives—**cogs**—stateful, concurrent agents that pass messages over channels.
Think of them as Lego® bricks for conversational or multimodal AI systems.

Build things like:
- [Conversational personas](dev/workflows/conversation.clj)
- [Debates you can listen to](dev/workflows/debate.clj)
- [Multimodal agents who can speak and show you things](dev/workflows/multimodal.clj)

The dev environment and sample workflows use OpenAI's models. In order to run the samples, your environment
should have an OPENAI_API_KEY variable containing a valid OpenaAI API key.

## 5-min API tour (sans llms)

```clojure
(ns my.ns
(require [clojure.core.async :as a]
[clojure.string :as string]
[cog.town :as cogs]))

;;; 1. Create some cogs
(def echo
(cogs/cog [] (fn [ctx msg]
(let [resp (str "👋 you said: " msg)]
(-> (conj ctx msg)
(conj resp)
(vector resp))))))

(def shout
(cogs/cog [] (fn [ctx msg]
(let [resp (clojure.string/upper-case msg)]
(-> (conj ctx msg)
(conj resp)
(vector resp))))))

(a/put! shout "hello!")
(a/take! shout println) ;;; => HELLO!

;;; cogs can be dereferenced to get a live snapshot of their context
@shout
;; => ["hello!", "HELLO!"]

;;; 2. Wire cogs into a flow

(def shout-flow (cogs/flow [echo shout]))
(a/put! shout-flow "hello!")
(a/take! shout-flow println)
(a/close! shout-flow)

;;; 3. Let two cogs talk

(def shout-convo (cogs/dialogue echo shout))
(a/put! shout-convo "hello!")
(a/go-loop []
(when-some [msg (a/ (:output response) first :message :content first :output-text :text)
:format (when-some [fmt (some-> (:text response) :format)]
(if (some? (:json-schema fmt))
:json-schema
:text))}]
[(conj log-entries output-entry) output-entry]))
```

vs.

```clojure
(defn gpt-4o
"Less pure. Context is backed by some protocol using next.jdbc or similar"
[context input]
(let [log-entries (ctx/insert! context input)
response (openai/create-response :model :gpt-4o :easy-input-messages log-entries)
output-entry {:role :assistant
:content (-> (:output response) first :message :content first :output-text :text)
:format (when-some [fmt (some-> (:text response) :format)]
(if (some? (:json-schema fmt))
:json-schema
:text))}]
(ctx/insert! context output-entry)
[context output-entry]))
```

Clojure is freedom.

---

## Observing & time‑travel

* **Live snapshot** – since a cog implements `IDeref`, you can simply do `(@my-cog)` to get the latest context value.
* **Audit / replay** – Optionally collect the pair `[ctx out]` in transition functions, storing it in an atom or log.
* **Forking** – Pick any historical `ctx` value and feed it back into a new cog for “what‑if” exploration.

---

## Performance & GC notes

* Contexts **≤ 2 MB** updated a few times per second are generally safe.
* Watch `-Xlog:gc*` or `jstat -gcutil` during dev; full GCs should be rare.
* If context grows (large transcripts, embeddings), store bulky data off‑heap or behind an ID reference and keep lightweight keys in `ctx`.
* Cap the length of any time‑travel timeline vector or store *deltas* to avoid memory blow‑up.

---

## API reference (cheatsheet)

```clojure
(cog ctx transition & [buf-or-n xf ex-handler]) => Cog
(fork cog) => Cog
(fork cog ctx-fn) => Cog
(fork cog ctx-fn io) => Cog
(fork cog ctx-fn io transition-fn) => Cog
(extend cog io & [transition-fn]) => Cog

(flow [ch1 ch2 …] & opts) => IoChannel
(fanout chs & opts) => IoChannel
(gate trigger-ch & opts) => IoChannel
(dialogue cogA cogB & opts) => IoChannel

@cog => Context
(:*context cog) => Atom
```

For detailed doc‑strings run:

```clojure
(clojure.repl/doc cog.town/cog)
```

---

## License

MIT © 2025 Brian Scaturro