{"id":28371538,"url":"https://github.com/brianium/cog-town","last_synced_at":"2026-03-16T19:01:35.141Z","repository":{"id":293712093,"uuid":"984938880","full_name":"brianium/cog-town","owner":"brianium","description":"Build agentic workflows with simple core.async primitives","archived":false,"fork":false,"pushed_at":"2025-07-04T11:25:40.000Z","size":242,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-02T07:50:14.960Z","etag":null,"topics":["agents","async","channels","clojure","llms"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-16T19:13:17.000Z","updated_at":"2025-08-22T15:31:03.000Z","dependencies_parsed_at":"2025-06-04T19:50:33.084Z","dependency_job_id":"2d046a74-9dce-4ca7-90c9-033679d86f39","html_url":"https://github.com/brianium/cog-town","commit_stats":null,"previous_names":["brianium/cog-town"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/brianium/cog-town","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fcog-town","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fcog-town/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fcog-town/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fcog-town/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianium","download_url":"https://codeload.github.com/brianium/cog-town/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fcog-town/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30566701,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-16T04:42:47.996Z","status":"ssl_error","status_checked_at":"2026-03-16T04:42:44.668Z","response_time":96,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","async","channels","clojure","llms"],"created_at":"2025-05-29T10:12:02.664Z","updated_at":"2026-03-16T19:01:35.135Z","avatar_url":"https://github.com/brianium.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cog Town 🏘️\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.github.brianium/cog-town.svg)](https://clojars.org/com.github.brianium/cog-town)\n\nBuild **agentic workflows** in Clojure with the ergonomics of `core.async`.\n\n`cog.town` gives you a tiny set of composable primitives—**cogs**—stateful, concurrent agents that pass messages over channels.  \nThink of them as Lego® bricks for conversational or multimodal AI systems.\n\nBuild things like:\n- [Conversational personas](dev/workflows/conversation.clj)\n- [Debates you can listen to](dev/workflows/debate.clj)\n- [Multimodal agents who can speak and show you things](dev/workflows/multimodal.clj)\n\nThe dev environment and sample workflows use OpenAI's models. In order to run the samples, your environment\nshould have an OPENAI_API_KEY variable containing a valid OpenaAI API key.\n\n## 5-min API tour (sans llms)\n\n```clojure\n(ns my.ns\n  (require [clojure.core.async :as a]\n           [clojure.string :as string]\n           [cog.town :as cogs]))\n\n;;; 1. Create some cogs\n(def echo\n  (cogs/cog [] (fn [ctx msg]\n                 (let [resp (str \"👋 you said: \"  msg)]\n                   (-\u003e (conj ctx msg)\n                       (conj resp)\n                       (vector resp))))))\n\n(def shout\n    (cogs/cog [] (fn [ctx msg]\n                   (let [resp (clojure.string/upper-case msg)]\n                     (-\u003e (conj ctx msg)\n                         (conj resp)\n                         (vector resp))))))\n\n(a/put! shout \"hello!\")\n(a/take! shout println) ;;; =\u003e HELLO!\n\n;;; cogs can be dereferenced to get a live snapshot of their context\n@shout\n;; =\u003e [\"hello!\", \"HELLO!\"]\n\n;;; 2. Wire cogs into a flow\n\n(def shout-flow (cogs/flow [echo shout]))\n(a/put! shout-flow \"hello!\")\n(a/take! shout-flow println)\n(a/close! shout-flow)\n\n;;; 3. Let two cogs talk\n\n(def shout-convo (cogs/dialogue echo shout))\n(a/put! shout-convo \"hello!\")\n(a/go-loop []\n  (when-some [msg (a/\u003c! shout-convo)]\n    (println msg)\n    (recur)))\n```\n\nYou write **pure**[*](#a-note-on-purity) business logic; Cog Town handles state threading, back‑pressure, and blocking work on dedicated threads.\n\n---\n\n## Core concepts\n\n| Primitive      | What it is                                                   | When to reach for it                                           |\n| -------------- | ------------------------------------------------------------ | -------------------------------------------------------------- |\n| **`cog`**      | Bidirectional channel **plus** private immutable **context** and a **pure transition**. Implements `ReadPort`, `WritePort`, `Channel`, `Mult`, **`IDeref`**. | Anytime you need an agent that remembers and evolves. |\n| **`extend`**   | Light‑weight wrapper around a cog that **adds** or **re‑routes** its I/O without touching core logic. | Enrich output (TTS, embeddings) or translate input. |\n| **`fork`**     | Clones a cog, optionally transforming its context, IO pair, or transition. | Re‑use behaviours with tweaks; create read‑only taps; testing. |\n| **`flow`**     | Sequentially connects N channels so each output becomes the next input. | Pipelines (ETL, request → AI → TTS, etc.). |\n| **`fanout`**   | Broadcasts an input value to many channels, gathers ordered results. | Scatter‑gather, multi‑tool calls. |\n| **`gate`**     | Releases the value of a latched channel when triggered by input. | Throttling, deferred fetches. |\n| **`dialogue`** | Ping‑pong messages between two cogs forever. | Multi‑turn agent duets or self‑conversation. |\n\n---\n\n## Transition functions\n\nA cog’s transition function has the following signature:\n\n```clojure\ntransition :: (context, input) → [new‑context, output]\n```\n\n* Runs on its own **real** thread (via `async/thread`) so it is safe to block on HTTP or disk I/O.  \n* Throwing emits `{:type ::error :throwable th :input input}` as output.\n\n### Context\n\nA cog doesn't know anything about its context or transition function. If the transition function follows the signature mentioned above, a cog\nwill happily ensure the function is applied in a separate thread. End users can build context (vector?, record? type?) and messages (maps?, strings?, references?) to suit their needs.\n\n### A note on purity\n\nTransition functions are **pure** in the sense that they do not force you to mutate the context directly. Instead, they return a new context value. This can be useful for time travel, debugging, testing, etc. \n\nHowever, you can still use side effects (we are talking to LLMs after all) like logging, HTTP requests, or database writes within the transition function. In fact this may be necessary when moving beyond stateful atoms (storing context in a database, for example).\n\n```clojure\n(defn gpt-4o\n  \"More pure. Just a Clojure data structure as context. Still need to talk to OpenAI.\"\n  [context input]\n  (let [log-entries  (conj context input)\n        response     (openai/create-response :model :gpt-4o :easy-input-messages log-entries)\n        output-entry {:role    :assistant\n                      :content (-\u003e (:output response) first :message :content first :output-text :text)\n                      :format  (when-some [fmt (some-\u003e (:text response) :format)]\n                                 (if (some? (:json-schema fmt))\n                                   :json-schema\n                                   :text))}]\n    [(conj log-entries output-entry) output-entry]))\n```\n\nvs.\n\n```clojure\n(defn gpt-4o\n  \"Less pure. Context is backed by some protocol using next.jdbc or similar\"\n  [context input]\n  (let [log-entries  (ctx/insert! context input)\n        response     (openai/create-response :model :gpt-4o :easy-input-messages log-entries)\n        output-entry {:role    :assistant\n                      :content (-\u003e (:output response) first :message :content first :output-text :text)\n                      :format  (when-some [fmt (some-\u003e (:text response) :format)]\n                                 (if (some? (:json-schema fmt))\n                                   :json-schema\n                                   :text))}]\n    (ctx/insert! context output-entry)\n    [context output-entry]))\n```\n\nClojure is freedom.\n\n---\n\n## Observing \u0026 time‑travel\n\n* **Live snapshot** – since a cog implements `IDeref`, you can simply do `(@my-cog)` to get the latest context value.  \n* **Audit / replay** – Optionally collect the pair `[ctx out]` in transition functions, storing it in an atom or log.  \n* **Forking** – Pick any historical `ctx` value and feed it back into a new cog for “what‑if” exploration.\n\n---\n\n## Performance \u0026 GC notes\n\n* Contexts **≤ 2 MB** updated a few times per second are generally safe.  \n* Watch `-Xlog:gc*` or `jstat -gcutil` during dev; full GCs should be rare.  \n* If context grows (large transcripts, embeddings), store bulky data off‑heap or behind an ID reference and keep lightweight keys in `ctx`.  \n* Cap the length of any time‑travel timeline vector or store *deltas* to avoid memory blow‑up.\n\n---\n\n## API reference (cheatsheet)\n\n```clojure\n(cog     ctx transition \u0026 [buf-or-n xf ex-handler])            =\u003e Cog\n(fork    cog)                                                  =\u003e Cog\n(fork    cog ctx-fn)                                           =\u003e Cog\n(fork    cog ctx-fn io)                                        =\u003e Cog\n(fork    cog ctx-fn io transition-fn)                          =\u003e Cog\n(extend  cog io \u0026 [transition-fn])                             =\u003e Cog\n\n(flow    [ch1 ch2 …] \u0026 opts)                                   =\u003e IoChannel\n(fanout  chs \u0026 opts)                                           =\u003e IoChannel\n(gate    trigger-ch \u0026 opts)                                    =\u003e IoChannel\n(dialogue cogA cogB \u0026 opts)                                    =\u003e IoChannel\n\n@cog                                                           =\u003e Context\n(:*context cog)                                                =\u003e Atom\u003cContext\u003e\n```\n\nFor detailed doc‑strings run:\n\n```clojure\n(clojure.repl/doc cog.town/cog)\n```\n\n---\n\n## License\n\nMIT © 2025 Brian Scaturro\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Fcog-town","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianium%2Fcog-town","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Fcog-town/lists"}