Conjure (Conjure v0.1.1-alpha)
View SourceElixir 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
Conjure.Executor.Local- Direct execution (development only)Conjure.Executor.Docker- Sandboxed container execution (production)
Components
Conjure.Loader- Skill loading and parsingConjure.Prompt- System prompt generationConjure.Tools- Tool schema definitionsConjure.Conversation- Conversation loop managementConjure.Registry- Skill registry (GenServer)
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
@type context() :: Conjure.ExecutionContext.t()
@type skill() :: Conjure.Skill.t()
@type tool_call() :: Conjure.ToolCall.t()
@type tool_result() :: Conjure.ToolResult.t()
Functions
@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)
@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
@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)
@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
@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"
])
@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)
@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")
@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)
@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
@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")
@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
)
@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)}
"""
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
@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
@spec version() :: String.t()
Get the library version.