Conjure (Conjure v0.1.1-alpha)

View Source

Elixir library for leveraging Anthropic Agent Skills with Claude models.

Conjure provides a complete implementation of the Agent Skills specification, allowing Elixir applications to:

  • Load and parse skills from the filesystem
  • Generate system prompt fragments for skill discovery
  • Provide tool definitions compatible with Claude's tool use API
  • Execute skill-related tool calls (file reads, script execution)
  • Manage the conversation loop between Claude and tools

Quick Start

# Load skills from a directory
{:ok, skills} = Conjure.load("/path/to/skills")

# Generate system prompt fragment
system_prompt = """
You are a helpful assistant.

#{Conjure.system_prompt(skills)}
"""

# Get tool definitions for API
tools = Conjure.tool_definitions()

# Run conversation loop
{:ok, messages} = Conjure.run_loop(
  [%{role: "user", content: "Use the PDF skill to extract text"}],
  skills,
  &my_claude_api_call/1
)

Architecture

Conjure is designed to be:

  • Composable: Use individual components independently
  • Pluggable: Swap execution backends (Local, Docker, custom)
  • API-agnostic: Works with any Claude API client
  • OTP-compliant: GenServer registry, supervision trees

Execution Backends

Components

Summary

Functions

Build a complete system prompt with skills.

Create an execution context for skills.

Execute a tool call and return the result.

Load skills from a directory path.

Load skills from multiple directories.

Load the full body of a skill.

Load a single .skill file (ZIP format).

Parse a Claude API response.

Process a single Claude response.

Read a resource file from a skill.

Run a complete conversation loop until completion.

Generate the system prompt fragment for skill discovery.

Get tool definitions for the Claude API.

Validate a skill's structure.

Get the library version.

Types

context()

@type context() :: Conjure.ExecutionContext.t()

skill()

@type skill() :: Conjure.Skill.t()

tool_call()

@type tool_call() :: Conjure.ToolCall.t()

tool_result()

@type tool_result() :: Conjure.ToolResult.t()

Functions

build_system_prompt(base_prompt, skills, opts \\ [])

@spec build_system_prompt(String.t(), [Conjure.Skill.t()], keyword()) :: String.t()

Build a complete system prompt with skills.

Example

prompt = Conjure.build_system_prompt("You are helpful.", skills)

create_context(skills, opts \\ [])

@spec create_context(
  [Conjure.Skill.t()],
  keyword()
) :: Conjure.ExecutionContext.t()

Create an execution context for skills.

Options

  • :skills_root - Root directory containing skills
  • :working_directory - Working directory for operations
  • :timeout - Execution timeout in milliseconds
  • :allowed_paths - Paths that can be accessed
  • :network_access - :none, :limited, or :full
  • :executor_config - Executor-specific configuration

execute(tool_call, skills, opts \\ [])

@spec execute(Conjure.ToolCall.t(), [Conjure.Skill.t()], keyword()) ::
  {:ok, Conjure.ToolResult.t()} | {:error, Conjure.Error.t()}

Execute a tool call and return the result.

Options

  • :executor - Executor module (default: Conjure.Executor.Local)
  • :context - ExecutionContext (created if not provided)

Example

tool_call = %Conjure.ToolCall{
  id: "toolu_123",
  name: "view",
  input: %{"path" => "/path/to/file"}
}

{:ok, result} = Conjure.execute(tool_call, skills)

load(path)

@spec load(Path.t()) :: {:ok, [Conjure.Skill.t()]} | {:error, Conjure.Error.t()}

Load skills from a directory path.

Returns a list of parsed Skill structs with metadata only (body not loaded). This implements progressive disclosure - full skill content is loaded on demand.

Example

{:ok, skills} = Conjure.load("/path/to/skills")
# Returns skills with name, description, path loaded
# Body content loaded when Claude reads SKILL.md via view tool

load_all(paths)

@spec load_all([Path.t()]) :: {:ok, [Conjure.Skill.t()]} | {:error, Conjure.Error.t()}

Load skills from multiple directories.

Example

{:ok, skills} = Conjure.load_all([
  "/path/to/skills",
  "~/.conjure/skills"
])

load_body(skill)

@spec load_body(Conjure.Skill.t()) ::
  {:ok, Conjure.Skill.t()} | {:error, Conjure.Error.t()}

Load the full body of a skill.

For progressive disclosure, skills are initially loaded with metadata only. Use this to explicitly load the body content.

Example

{:ok, skill} = Conjure.load_skill("/path/to/skill")
{:ok, skill_with_body} = Conjure.load_body(skill)
IO.puts(skill_with_body.body)

load_skill_file(path)

@spec load_skill_file(Path.t()) ::
  {:ok, Conjure.Skill.t()} | {:error, Conjure.Error.t()}

Load a single .skill file (ZIP format).

Example

{:ok, skill} = Conjure.load_skill_file("/path/to/my-skill.skill")

parse_response(response)

@spec parse_response(map()) :: {:ok, Conjure.API.parsed_response()} | {:error, term()}

Parse a Claude API response.

Example

{:ok, parsed} = Conjure.parse_response(response)
IO.inspect(parsed.tool_uses)

process_response(response, skills, opts \\ [])

@spec process_response(map(), [Conjure.Skill.t()], keyword()) ::
  {:done, String.t()} | {:continue, [Conjure.ToolResult.t()]} | {:error, term()}

Process a single Claude response.

Use this for manual conversation management.

Example

case Conjure.process_response(response, skills) do
  {:done, text} ->
    IO.puts("Complete: " <> text)

  {:continue, results} ->
    # Send results back to Claude
    next_response = call_claude(results)
end

read_resource(skill, relative_path)

@spec read_resource(Conjure.Skill.t(), Path.t()) ::
  {:ok, String.t()} | {:error, Conjure.Error.t()}

Read a resource file from a skill.

Example

{:ok, content} = Conjure.read_resource(skill, "scripts/helper.py")

run_loop(messages, skills, api_callback, opts \\ [])

@spec run_loop([map()], [Conjure.Skill.t()], fun(), keyword()) ::
  {:ok, [map()]} | {:error, Conjure.Error.t()}

Run a complete conversation loop until completion.

This is the main entry point for managing the tool-use conversation with Claude. Provide a callback function that makes API calls.

Options

  • :max_iterations - Maximum tool loops (default: 25)
  • :executor - Executor module to use
  • :timeout - Tool execution timeout
  • :on_tool_call - Callback for each tool call
  • :on_tool_result - Callback for each result

Example

messages = [%{role: "user", content: "Extract text from the PDF"}]

{:ok, final_messages} = Conjure.run_loop(
  messages,
  skills,
  fn msgs -> MyApp.Claude.call(msgs) end,
  max_iterations: 10
)

system_prompt(skills, opts \\ [])

@spec system_prompt(
  [Conjure.Skill.t()],
  keyword()
) :: String.t()

Generate the system prompt fragment for skill discovery.

This should be appended to your system prompt to enable Claude to discover and use available skills.

Options

  • :include_instructions - Include usage instructions (default: true)

Example

skills = Conjure.load("/skills")

system_prompt = """
You are a helpful assistant.

#{Conjure.system_prompt(skills)}
"""

tool_definitions(opts \\ [])

@spec tool_definitions(keyword()) :: [map()]

Get tool definitions for the Claude API.

Returns an array of tool schemas to pass in API requests.

Options

  • :only - Only include these tools (e.g., ["view", "bash_tool"])
  • :except - Exclude these tools

Example

tools = Conjure.tool_definitions()
# Pass to Claude API request

validate(skill)

@spec validate(Conjure.Skill.t()) :: :ok | {:error, [String.t()]}

Validate a skill's structure.

Example

case Conjure.validate(skill) do
  :ok -> :valid
  {:error, errors} -> IO.inspect(errors)
end

version()

@spec version() :: String.t()

Get the library version.