This directory contains example applications demonstrating various features of the Copilot SDK for Clojure.
-
Copilot CLI: Ensure the GitHub Copilot CLI is installed and accessible in your PATH.
which copilot # Or set COPILOT_CLI_PATH to your CLI location -
Dependencies: The examples use the
:examplesalias fromdeps.edn.
All examples use Clojure's -X invocation, which allows passing parameters directly.
From the project root:
# Basic Q&A conversation
clojure -A:examples -X basic-chat/run
# With custom questions
clojure -A:examples -X basic-chat/run :q1 '"What is Clojure?"' :q2 '"Who created it?"'
# Simple stateless query (helpers API)
clojure -A:examples -X helpers-query/run
clojure -A:examples -X helpers-query/run :prompt '"Explain recursion briefly."'
# Multiple independent queries
clojure -A:examples -X helpers-query/run-multi
clojure -A:examples -X helpers-query/run-multi :questions '["What is Rust?" "What is Go?"]'
# Streaming output
clojure -A:examples -X helpers-query/run-streaming
# Custom tool integration
clojure -A:examples -X tool-integration/run
clojure -A:examples -X tool-integration/run :languages '["clojure" "haskell"]'
# Multi-agent orchestration
clojure -A:examples -X multi-agent/run
clojure -A:examples -X multi-agent/run :topics '["AI safety" "machine learning"]'
# Streaming responses
clojure -A:examples -X streaming-chat/run
clojure -A:examples -X streaming-chat/run :prompt '"Explain the Fibonacci sequence."'
# Config directory, skills, and large output
clojure -A:examples -X config-skill-output/run
# Permission handling
clojure -A:examples -X permission-bash/runOr run all examples:
./run-all-examples.shWith a custom CLI path:
COPILOT_CLI_PATH=/path/to/copilot clojure -A:examples -X basic-chat/runDifficulty: Beginner
Concepts: Client lifecycle, sessions, message sending
The simplest use case—create a client, start a conversation, and get responses.
- Creating and starting a
CopilotClient - Creating a session with a specific model
- Sending messages with
send-and-wait! - Multi-turn conversation (context is preserved)
- Proper cleanup with
with-client-session
clojure -A:examples -X basic-chat/run
clojure -A:examples -X basic-chat/run :q1 '"What is Clojure?"' :q2 '"Who created it?"';; 1. Create a client and session
(copilot/with-client-session [session {:model "gpt-5.2"}]
;; 2. Send a message and wait for the complete response
(def response (copilot/send-and-wait! session
{:prompt "What is the capital of France?"}))
;; 3. Access the response content
(println (get-in response [:data :content]))
;; => "The capital of France is Paris."
;; 4. Follow-up question (conversation context preserved)
(def response2 (copilot/send-and-wait! session
{:prompt "What is its population?"}))
;; The model knows "its" refers to Paris
)Difficulty: Beginner
Concepts: Stateless queries, simple API
Shows the simplified helpers API for one-shot queries without managing client/session lifecycle.
query- Simple synchronous query, returns just the answer stringquery-seq- Returns lazy sequence of all eventsquery-chan- Returns core.async channel of events- Automatic client management (created on first use, reused across queries)
- Automatic cleanup via JVM shutdown hook (no manual cleanup needed)
# Simple query
clojure -A:examples -X helpers-query/run
# With custom prompt
clojure -A:examples -X helpers-query/run :prompt '"What is functional programming?"'
# Streaming output (lazy seq)
clojure -A:examples -X helpers-query/run-streaming
# Streaming output (core.async)
clojure -A:examples -X helpers-query/run-async
# Multiple independent queries
clojure -A:examples -X helpers-query/run-multi
clojure -A:examples -X helpers-query/run-multi :questions '["What is Rust?" "What is Go?"]'(require '[krukow.copilot-sdk.helpers :as h])
;; Simplest possible query - just get the answer
(h/query "What is 2+2?")
;; => "4"
;; With options
(h/query "What is Clojure?" :session {:model "gpt-5.2"})
;; Streaming with multimethod event handling
(defmulti handle-event :type)
(defmethod handle-event :default [_] nil)
(defmethod handle-event :assistant.message_delta [{{:keys [delta-content]} :data}]
(print delta-content)
(flush))
(defmethod handle-event :assistant.message [_] (println))
(run! handle-event (h/query-seq "Tell me a joke" :session {:streaming? true}))Difficulty: Intermediate
Concepts: Custom tools, tool handlers, result types
Shows how to let the LLM call back into your application when it needs capabilities you provide.
- Defining tools with
define-tool - JSON Schema parameters for type-safe tool inputs
- Handler functions that execute when tools are invoked
- Different result types:
result-success,result-failure
clojure -A:examples -X tool-integration/run
clojure -A:examples -X tool-integration/run :languages '["clojure" "haskell"]';; Define a tool with handler
(def lookup-tool
(copilot/define-tool "lookup_language"
{:description "Look up information about a programming language"
:parameters {:type "object"
:properties {:language {:type "string"
:description "Language name"}}
:required ["language"]}
:handler (fn [args invocation]
;; args = {:language "clojure"}
;; invocation = full invocation context
(let [lang (-> args :language str/lower-case)
info (get knowledge-base lang)]
(if info
(copilot/result-success info)
(copilot/result-failure
(str "No info for: " lang)
"not found"))))}))
;; Create session with tools
(copilot/with-client-session [session {:model "gpt-5.2"
:tools [lookup-tool]}]
(copilot/send-and-wait! session
{:prompt "Tell me about Clojure using the lookup tool"}));; Success - return data to the LLM
(copilot/result-success "The answer is 42")
;; Failure - tell LLM the operation failed
(copilot/result-failure "Could not connect to database" "connection timeout")
;; Denied - permission was denied
(copilot/result-denied "User declined permission")
;; Rejected - tool invocation was invalid
(copilot/result-rejected "Invalid parameters")Difficulty: Advanced
Concepts: Multiple sessions, core.async, concurrent operations, agent coordination
Demonstrates a sophisticated pattern where multiple specialized agents collaborate using core.async channels for coordination.
- Creating multiple sessions with different system prompts (personas)
- Using
core.asyncchannels for concurrent operations - Parallel research queries with
goblocks - Sequential pipeline: Research → Analysis → Synthesis
- Coordinating results from multiple async operations
clojure -A:examples -X multi-agent/run
clojure -A:examples -X multi-agent/run :topics '["AI safety" "machine learning" "neural networks"]'┌─────────────────────────────────────────────────────────────┐
│ Multi-Agent Workflow │
├─────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: Parallel Research │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Topic 1 │ │ Topic 2 │ │ Topic 3 │ │
│ │ (go block) │ │ (go block) │ │ (go block) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────────┬┴─────────────────┘ │
│ │ result-ch │
│ ▼ │
│ Phase 2: Analysis ┌──────────────┐ │
│ │ Analyst │ │
│ │ Session │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ Phase 3: Synthesis ┌──────────────┐ │
│ │ Writer │ │
│ │ Session │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ Final Summary │
└─────────────────────────────────────────────────────────────┘
Difficulty: Intermediate
Concepts: Streaming deltas, event handling, incremental output
Demonstrates how to enable streaming and render assistant output incrementally as
:assistant.message_delta events arrive, then print the final message on idle.
clojure -A:examples -X streaming-chat/run
clojure -A:examples -X streaming-chat/run :prompt '"Explain the Fibonacci sequence."'Difficulty: Intermediate
Concepts: config-dir overrides, skill directories, disabling skills, large tool output settings
Shows how to:
- set a custom config directory
- provide additional skill directories
- disable specific skills by name
- configure large tool output handling with a custom tool
clojure -A:examples -X config-skill-output/runDifficulty: Intermediate
Concepts: permission requests, bash tool, approval callback
Shows how to:
- handle
permission.requestvia:on-permission-request - invoke the built-in shell tool with allow/deny decisions
- log the full permission request payload for inspection
clojure -A:examples -X permission-bash/runDifficulty: Intermediate
Concepts: Java interop, AOT compilation, static API
Shows how to use the SDK from Java code.
The SDK can be used from Java via AOT-compiled classes. See examples/java/ for a complete Maven project.
Once published to Maven Central, add to your pom.xml:
<dependency>
<groupId>io.github.krukow</groupId>
<artifactId>copilot-sdk</artifactId>
<version>0.1.0</version>
</dependency>
<!-- Required Clojure runtime dependencies -->
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.12.4</version>
</dependency>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>core.async</artifactId>
<version>1.8.741</version>
</dependency>
<dependency>
<groupId>cheshire</groupId>
<artifactId>cheshire</artifactId>
<version>6.1.0</version>
</dependency>You'll also need the Clojars repository:
<repositories>
<repository>
<id>clojars</id>
<url>https://repo.clojars.org</url>
</repository>
</repositories># Build and install to local Maven repo
clj -T:build aot-jar
clj -T:build install
# Then in your Maven project, use the dependency above# Build uberjar with all dependencies
clj -T:build uber
# Compile your Java code
javac -cp target/io.github.krukow/copilot-sdk-0.1.0-SNAPSHOT-standalone.jar \
MyApp.java
# Run
java -cp "target/io.github.krukow/copilot-sdk-0.1.0-SNAPSHOT-standalone.jar:." \
MyAppTo publish a release:
# Create signed bundle for Maven Central
clj -T:build bundle :version '"0.1.0"'
# Upload target/copilot-sdk-0.1.0-bundle.zip at:
# https://central.sonatype.com/publishingSee PUBLISHING.md for detailed instructions.
import krukow.copilot_sdk.Copilot;
import krukow.copilot_sdk.SessionOptions;
import krukow.copilot_sdk.SessionOptionsBuilder;
// Simple one-liner query
String answer = Copilot.query("What is 2+2?");
// Query with options
SessionOptionsBuilder builder = new SessionOptionsBuilder();
builder.model("gpt-5.2");
SessionOptions opts = (SessionOptions) builder.build();
String answer = Copilot.query("Explain monads", opts);
// Query with timeout
String answer = Copilot.query("Complex question", opts, 60000);
// Streaming
Copilot.queryStreaming("Tell me a story", opts, event -> {
if (event.isMessageDelta()) {
System.out.print(event.getDeltaContent());
}
});
// Full client/session control for multi-turn conversations
Object client = Copilot.createClient(null);
Copilot.startClient(client);
Object session = Copilot.createSession(client, opts);
String a1 = Copilot.sendAndWait(session, "What is the capital of France?", 60000);
String a2 = Copilot.sendAndWait(session, "What is its population?", 60000); // context preserved
Copilot.destroySession(session);
Copilot.stopClient(client);Here's how common patterns compare between the Clojure and JavaScript SDKs:
JavaScript:
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient({ logLevel: "info" });
await client.start();Clojure:
(require '[krukow.copilot-sdk :as copilot])
(copilot/with-client [client]
;; use client
)JavaScript:
// No direct equivalent - must create client/sessionClojure:
(require '[krukow.copilot-sdk.helpers :as h])
(h/query "What is 2+2?")
;; => "4"JavaScript:
session.on((event) => {
if (event.type === "assistant.message") {
console.log(event.data.content);
}
});Clojure:
;; Using helpers with multimethod dispatch
(defmulti handle-event :type)
(defmethod handle-event :assistant.message [{{:keys [content]} :data}]
(println content))
(run! handle-event (h/query-seq "Hello" :session {:streaming? true}))JavaScript:
import { z } from "zod";
import { defineTool } from "@github/copilot-sdk";
defineTool("lookup", {
description: "Look up data",
parameters: z.object({ id: z.string() }),
handler: async ({ id }) => fetchData(id)
});Clojure:
(copilot/define-tool "lookup"
{:description "Look up data"
:parameters {:type "object"
:properties {:id {:type "string"}}
:required ["id"]}
:handler (fn [{:keys [id]} _]
(fetch-data id))})JavaScript (Promises):
const response = await session.sendAndWait({ prompt: "Hello" });Clojure (Blocking):
(def response (copilot/send-and-wait! session {:prompt "Hello"}))Clojure (core.async):
(go
(let [ch (copilot/send-async session {:prompt "Hello"})]
(loop []
(when-let [event (<! ch)]
(println event)
(recur)))))Ensure the Copilot CLI is installed and accessible:
copilot --version
# Or check your custom path
$COPILOT_CLI_PATH --versionIncrease the timeout for complex queries:
(copilot/send-and-wait! session {:prompt "Complex question"} 300000) ; 5 minutesEnsure your prompt explicitly mentions the tool or its capability:
;; Less likely to trigger tool:
{:prompt "Tell me about Clojure"}
;; More likely to trigger tool:
{:prompt "Use the lookup_language tool to tell me about Clojure"}Clean up sessions when done:
(copilot/destroy! session)And periodically list/delete orphaned sessions:
(doseq [s (copilot/list-sessions client)]
(copilot/delete-session! client (:session-id s)))