Conjure.Session (Conjure v0.1.1-alpha)

View Source

Manage multi-turn conversation sessions.

This module provides a unified API for managing conversation sessions that works with all execution backends: local, Docker, Anthropic, and native.

Unified API

The same interface works regardless of execution backend:

# Local execution
session = Conjure.Session.new_local(skills)
{:ok, response, session} = Conjure.Session.chat(session, "Hello", api_callback)

# Docker execution
session = Conjure.Session.new_local(skills, executor: Conjure.Executor.Docker)
{:ok, response, session} = Conjure.Session.chat(session, "Hello", api_callback)

# Anthropic execution
session = Conjure.Session.new_anthropic([{:anthropic, "xlsx", "latest"}])
{:ok, response, session} = Conjure.Session.chat(session, "Hello", api_callback)

# Native execution (Elixir modules)
session = Conjure.Session.new_native([MyApp.Skills.Database])
{:ok, response, session} = Conjure.Session.chat(session, "Hello", api_callback)

Session State

Sessions track:

  • Execution mode (:local, :docker, :anthropic, :native)
  • Message history
  • Created files (with source tracking)
  • Container ID (for Anthropic)
  • Execution context (for local/Docker/native)

File Handling

Files created during conversations are tracked with their source:

files = Conjure.Session.get_created_files(session)

for file <- files do
  case file.source do
    :local -> File.read!(file.id)
    :native -> File.read!(file.id)
    :anthropic -> download_from_anthropic(file.id)
  end
end

See Also

Summary

Functions

Add a message to the session history.

Send a message and get a response.

Cleanup session resources including storage and uploaded skills.

Get the container ID (Anthropic sessions only).

Get the execution context (local/Docker sessions only).

Get the execution mode of the session.

Get all files created during the session.

Get all messages in the session.

Create a session for Anthropic execution.

Create a session for Anthropic execution, raising on error.

Create a session for Docker execution with storage.

Create a session for Docker execution, raising on error.

Create a session for local/Docker execution.

Create a session for native Elixir module execution.

Reset messages to empty (keeps session configuration).

Get the session ID from storage.

Get the skills associated with the session.

Get the storage state for this session.

Get the storage strategy module for this session.

Types

execution_mode()

@type execution_mode() :: :local | :docker | :anthropic | :native

file_info()

@type file_info() :: %{
  id: String.t(),
  filename: String.t() | nil,
  size: non_neg_integer() | nil,
  source: :local | :anthropic | :native
}

skill_spec()

@type skill_spec() :: {atom(), String.t(), String.t()}

t()

@type t() :: %Conjure.Session{
  api_callback: function() | nil,
  container_id: String.t() | nil,
  context: Conjure.ExecutionContext.t() | nil,
  created_files: [file_info()],
  execution_mode: execution_mode(),
  file_callbacks:
    %{
      required(Conjure.Storage.callback_event()) =>
        Conjure.Storage.file_callback()
    }
    | nil,
  messages: [map()],
  opts: keyword(),
  skills: [Conjure.Skill.t()] | [skill_spec()] | [module()],
  storage: term() | nil,
  storage_strategy: module() | nil,
  uploaded_skills: [uploaded_skill()]
}

uploaded_skill()

@type uploaded_skill() :: %{
  skill_id: String.t(),
  version: String.t(),
  display_title: String.t()
}

Functions

add_message(session, message)

@spec add_message(t(), map()) :: t()

Add a message to the session history.

chat(session, user_message, api_callback)

@spec chat(t(), String.t(), (list() -> {:ok, map()} | {:error, term()})) ::
  {:ok, map(), t()} | {:error, Conjure.Error.t()}

Send a message and get a response.

Works identically for both local/Docker and Anthropic execution modes. The api_callback is passed per-call, following API-agnostic design.

API Callback

For local/Docker execution:

api_callback = fn messages ->
  # Your HTTP client call
  MyApp.Claude.post("/v1/messages", %{messages: messages})
end

For Anthropic execution, the callback should handle the container config:

api_callback = fn messages ->
  # Container config is built by the session
  MyApp.Claude.post("/v1/messages", %{messages: messages})
end

Returns

Returns {:ok, response, updated_session} on success.

Example

{:ok, response, session} = Conjure.Session.chat(
  session,
  "Create a spreadsheet",
  &my_api_callback/1
)

# Continue conversation
{:ok, response2, session} = Conjure.Session.chat(
  session,
  "Add headers to it",
  &my_api_callback/1
)

cleanup(session)

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

Cleanup session resources including storage and uploaded skills.

Should be called when done with a session to release resources.

  • For Docker sessions: removes the working directory and any remote storage
  • For Anthropic sessions: deletes uploaded skills from Anthropic

Example

{:ok, session} = Conjure.Session.new_docker(skills)
# ... use session ...
{:ok, _} = Conjure.Session.cleanup(session)

{:ok, session} = Conjure.Session.new_anthropic([skill], api_callback: cb)
# ... use session ...
:ok = Conjure.Session.cleanup(session)

container_id(session)

@spec container_id(t()) :: String.t() | nil

Get the container ID (Anthropic sessions only).

context(session)

@spec context(t()) :: Conjure.ExecutionContext.t() | nil

Get the execution context (local/Docker sessions only).

execution_mode(session)

@spec execution_mode(t()) :: execution_mode()

Get the execution mode of the session.

get_created_files(session)

@spec get_created_files(t()) :: [file_info()]

Get all files created during the session.

Returns a list of file info maps with source tracking.

get_messages(session)

@spec get_messages(t()) :: [map()]

Get all messages in the session.

new_anthropic(skills, opts \\ [])

@spec new_anthropic(
  [skill_spec()] | [Conjure.Skill.t()],
  keyword()
) :: {:ok, t()} | {:error, Conjure.Error.t()}

Create a session for Anthropic execution.

Accepts either:

  • Skill specs for pre-uploaded or Anthropic-provided skills
  • Skill.t() structs (or .skill file paths) which will be uploaded automatically

Options

  • :api_callback - Required when passing Skill.t() structs for upload
  • :max_iterations - Maximum pause_turn iterations (default: 10)
  • :on_pause - Callback when pause_turn received

Example

# Using pre-uploaded or Anthropic skills (no upload needed)
{:ok, session} = Conjure.Session.new_anthropic([
  {:anthropic, "xlsx", "latest"},
  {:anthropic, "pdf", "latest"}
])

# Using local skills (will be uploaded automatically)
{:ok, skill} = Conjure.Loader.load_skill_file("my-skill.skill")
{:ok, session} = Conjure.Session.new_anthropic([skill],
  api_callback: my_api_callback
)

# Cleanup deletes uploaded skills
:ok = Conjure.Session.cleanup(session)

new_anthropic!(skills, opts \\ [])

@spec new_anthropic!(
  [skill_spec()] | [Conjure.Skill.t()],
  keyword()
) :: t()

Create a session for Anthropic execution, raising on error.

Same as new_anthropic/2 but raises on failure.

Example

session = Conjure.Session.new_anthropic!([skill], api_callback: my_callback)

new_docker(skills, opts \\ [])

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

Create a session for Docker execution with storage.

This is the recommended way to create Docker sessions as it properly initializes storage and fixes the working directory issue.

Options

  • :storage - Storage strategy: module or {module, opts} (default: Conjure.Storage.Local)
  • :on_file_created - Callback when file is created: fn file_ref, session_id -> :ok end
  • :on_file_deleted - Callback when file is deleted
  • :on_file_synced - Callback when file is synced from remote
  • :timeout - Execution timeout in ms (default: 30000)
  • :max_iterations - Maximum tool-use iterations (default: 25)

Example

# Default local storage
{:ok, session} = Conjure.Session.new_docker(skills)

# S3 storage
{:ok, session} = Conjure.Session.new_docker(skills,
  storage: {Conjure.Storage.S3, bucket: "my-bucket"}
)

# With file callbacks
{:ok, session} = Conjure.Session.new_docker(skills,
  on_file_created: fn file_ref, session_id ->
    IO.puts("File created: #{file_ref.path}")
  end
)

# Cleanup when done
{:ok, _} = Conjure.Session.cleanup(session)

new_docker!(skills, opts \\ [])

@spec new_docker!(
  [Conjure.Skill.t()],
  keyword()
) :: t()

Create a session for Docker execution, raising on error.

Same as new_docker/2 but raises on failure.

Example

session = Conjure.Session.new_docker!(skills)

new_local(skills, opts \\ [])

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

Create a session for local/Docker execution.

Options

  • :executor - Executor module (default: Conjure.Executor.Local)
  • :working_directory - Working directory for execution
  • :timeout - Execution timeout in ms (default: 30000)
  • :max_iterations - Maximum tool-use iterations (default: 25)

Example

{:ok, skills} = Conjure.load("priv/skills")

session = Conjure.Session.new_local(skills,
  executor: Conjure.Executor.Docker,
  timeout: 60_000
)

new_native(skill_modules, opts \\ [])

@spec new_native(
  [module()],
  keyword()
) :: t()

Create a session for native Elixir module execution.

Native skills are Elixir modules that implement the Conjure.NativeSkill behaviour. They execute directly in the BEAM with full access to the application's runtime context.

Options

  • :working_directory - Working directory for file operations
  • :timeout - Execution timeout in ms (default: 30000)
  • :max_iterations - Maximum tool-use iterations (default: 25)

Example

defmodule MyApp.Skills.Cache do
  @behaviour Conjure.NativeSkill

  def __skill_info__ do
    %{name: "cache", description: "Cache manager", allowed_tools: [:execute]}
  end

  def execute("clear", _ctx), do: {:ok, "Cache cleared"}
end

session = Conjure.Session.new_native([MyApp.Skills.Cache])

reset_messages(session)

@spec reset_messages(t()) :: t()

Reset messages to empty (keeps session configuration).

session_id(arg1)

@spec session_id(t()) :: String.t() | nil

Get the session ID from storage.

Returns nil if no storage is configured.

skills(session)

@spec skills(t()) :: [Conjure.Skill.t()] | [skill_spec()]

Get the skills associated with the session.

storage(session)

@spec storage(t()) :: term() | nil

Get the storage state for this session.

storage_strategy(session)

@spec storage_strategy(t()) :: module() | nil

Get the storage strategy module for this session.