{"id":48473977,"url":"https://github.com/beardedeagle/beam_agent_sdks","last_synced_at":"2026-04-07T07:49:04.166Z","repository":{"id":342389542,"uuid":"1173549740","full_name":"beardedeagle/beam_agent_sdks","owner":"beardedeagle","description":"Erlang/OTP + Elixir SDKs for agentic coding assistants — Claude Code, OpenAI Codex, Gemini CLI, GitHub Copilot, and OpenCode. Unified wire protocol, streaming via gen_statem, bidirectional content blocks, SDK hooks, MCP integration, telemetry, and fail-closed permissions. Full feature parity across all five adapters.","archived":false,"fork":false,"pushed_at":"2026-03-06T11:58:07.000Z","size":395,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-07T07:49:02.112Z","etag":null,"topics":["ai","elixir","erlang","sdk"],"latest_commit_sha":null,"homepage":"https://beardedeagle.github.io/beam_agent_sdks/","language":"Erlang","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/beardedeagle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T13:46:52.000Z","updated_at":"2026-03-06T11:58:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/beardedeagle/beam_agent_sdks","commit_stats":null,"previous_names":["beardedeagle/beam_agent_sdks"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/beardedeagle/beam_agent_sdks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beardedeagle%2Fbeam_agent_sdks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beardedeagle%2Fbeam_agent_sdks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beardedeagle%2Fbeam_agent_sdks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beardedeagle%2Fbeam_agent_sdks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beardedeagle","download_url":"https://codeload.github.com/beardedeagle/beam_agent_sdks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beardedeagle%2Fbeam_agent_sdks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31504897,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":["ai","elixir","erlang","sdk"],"created_at":"2026-04-07T07:49:03.465Z","updated_at":"2026-04-07T07:49:04.158Z","avatar_url":"https://github.com/beardedeagle.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BEAM Agent SDKs\n\nErlang/OTP and Elixir SDKs for integrating AI coding agents into BEAM applications.\n\nFive adapters, one unified message format. Connect to **Claude Code**, **Codex CLI**,\n**Gemini CLI**, **OpenCode**, or **GitHub Copilot** from any Erlang or Elixir\napplication.\n\n## Architecture\n\n```\n                              +-------------+\n                              | agent_wire  |  Shared types, JSONL, hooks, MCP, telemetry\n                              +------+------+\n                                     |\n                              +------+------+\n                              | AgentWire   |  Elixir wrapper (MCP, hooks, content, telemetry)\n                              +------+------+\n       +--------------+--------------++--------------+--------------+\n       |              |              |               |              |\n+------+------+ +-----+------+ +----+-------+ +-----+------+ +----+-------+\n| claude_     | | codex_app  | | gemini_cli | | opencode   | | copilot    |\n|  agent_sdk  | |  _server   | |  _client   | |  _client   | |  _client   |\n| Port/JSONL  | | Port/JSONRPC| | Port/JSONL | | HTTP/SSE   | | Port/CLRPC |\n+------+------+ +-----+------+ +----+-------+ +-----+------+ +----+-------+\n       |              |              |               |              |\n+------+------+ +-----+------+ +----+-------+ +-----+------+ +----+-------+\n| ClaudeEx    | | CodexEx    | | GeminiEx   | | OpencodeEx | | CopilotEx  |\n| (Elixir)    | | (Elixir)   | | (Elixir)   | | (Elixir)   | | (Elixir)   |\n+-------------+ +------------+ +------------+ +------------+ +------------+\n```\n\nAll five adapters normalize messages into `agent_wire:message()` — a common map\ntype you can pattern-match on regardless of which agent you're talking to.\n\n## Quick Start\n\n### Erlang\n\nAdd the adapter you need to your `rebar.config` deps:\n\n```erlang\n{deps, [\n    {claude_agent_sdk, {path, \"apps/claude_agent_sdk\"}},\n    {agent_wire, {path, \"apps/agent_wire\"}}\n]}.\n```\n\n```erlang\n%% Start a Claude Code session\n{ok, Session} = claude_agent_sdk:start_session(#{\n    cli_path =\u003e \"/usr/local/bin/claude\",\n    permission_mode =\u003e \u003c\u003c\"bypassPermissions\"\u003e\u003e\n}),\n\n%% Blocking query — returns all messages\n{ok, Messages} = claude_agent_sdk:query(Session, \u003c\u003c\"Explain OTP supervisors\"\u003e\u003e),\n\n%% Find the result\n[Result | _] = [M || #{type := result} = M \u003c- Messages],\nio:format(\"~s~n\", [maps:get(content, Result, \u003c\u003c\u003e\u003e)]),\n\nclaude_agent_sdk:stop(Session).\n```\n\n### Elixir\n\n```elixir\n# In mix.exs\ndefp deps do\n  [{:claude_ex, path: \"wrappers/claude_ex\"}]\nend\n```\n\n```elixir\n{:ok, session} = ClaudeEx.start_session(cli_path: \"claude\")\n\n# Streaming query — lazy enumerable\nsession\n|\u003e ClaudeEx.stream!(\"Explain GenServer\")\n|\u003e Enum.each(fn msg -\u003e\n  case msg.type do\n    :text -\u003e IO.write(msg.content)\n    :result -\u003e IO.puts(\"\\n--- Done ---\")\n    _ -\u003e :ok\n  end\nend)\n\nClaudeEx.stop(session)\n```\n\n## Adapters at a Glance\n\n| Adapter | CLI | Transport | Protocol | Bidirectional |\n|---------|-----|-----------|----------|---------------|\n| `claude_agent_sdk` | `claude` | Port | JSONL | Yes (control protocol) |\n| `codex_app_server` | `codex` | Port | JSON-RPC / JSONL | Yes (app-server) or No (exec) |\n| `gemini_cli_client` | `gemini` | Port | JSONL | No (one-shot per query) |\n| `opencode_client` | `opencode serve` | HTTP + SSE | REST + SSE | Yes |\n| `copilot_client` | `copilot` | Port | JSON-RPC / Content-Length | Yes (bidirectional) |\n\n## Common API Surface\n\nEvery adapter exposes this consistent API:\n\n```erlang\nstart_session(Opts)    -\u003e {ok, Pid} | {error, Reason}\nstop(Pid)              -\u003e ok\nquery(Pid, Prompt)     -\u003e {ok, [Message]} | {error, Reason}\nquery(Pid, Prompt, Params) -\u003e {ok, [Message]} | {error, Reason}\nhealth(Pid)            -\u003e ready | connecting | initializing | active_query | error\nsession_info(Pid)      -\u003e {ok, Map} | {error, Reason}\nchild_spec(Opts)       -\u003e supervisor:child_spec()\nsdk_hook(Event, Callback) -\u003e hook_def()\n```\n\nElixir wrappers add `stream!/3` and `stream/3` (lazy `Stream.resource/3`-based\nenumerables) on top of this common surface.\n\n## Unified Message Format\n\nAll adapters normalize messages to `agent_wire:message()`:\n\n```erlang\n#{type := text, content := \u003c\u003c\"Hello!\"\u003e\u003e}\n#{type := tool_use, tool_name := \u003c\u003c\"Bash\"\u003e\u003e, tool_input := #{...}}\n#{type := tool_result, tool_name := \u003c\u003c\"Bash\"\u003e\u003e, content := \u003c\u003c\"output...\"\u003e\u003e}\n#{type := result, content := \u003c\u003c\"Final answer\"\u003e\u003e, duration_ms := 5432}\n#{type := error, content := \u003c\u003c\"Something went wrong\"\u003e\u003e}\n#{type := thinking, content := \u003c\u003c\"Let me consider...\"\u003e\u003e}\n#{type := system, subtype := \u003c\u003c\"init\"\u003e\u003e, system_info := #{...}}\n```\n\nPattern match on `type` for dispatch:\n\n```erlang\nhandle_message(#{type := text, content := Content}) -\u003e\n    io:format(\"~s\", [Content]);\nhandle_message(#{type := tool_use, tool_name := Name}) -\u003e\n    io:format(\"Using tool: ~s~n\", [Name]);\nhandle_message(#{type := result} = Msg) -\u003e\n    io:format(\"Done! Cost: $~.4f~n\", [maps:get(total_cost_usd, Msg, 0.0)]);\nhandle_message(_Other) -\u003e\n    ok.\n```\n\n## SDK Features\n\n### In-Process MCP Servers (Claude Code)\n\nDefine custom tools as Erlang functions that Claude can call:\n\n```erlang\nTool = agent_wire_mcp:tool(\n    \u003c\u003c\"lookup_user\"\u003e\u003e,\n    \u003c\u003c\"Look up a user by ID\"\u003e\u003e,\n    #{\u003c\u003c\"type\"\u003e\u003e =\u003e \u003c\u003c\"object\"\u003e\u003e,\n      \u003c\u003c\"properties\"\u003e\u003e =\u003e #{\u003c\u003c\"id\"\u003e\u003e =\u003e #{\u003c\u003c\"type\"\u003e\u003e =\u003e \u003c\u003c\"string\"\u003e\u003e}}},\n    fun(Input) -\u003e\n        Id = maps:get(\u003c\u003c\"id\"\u003e\u003e, Input, \u003c\u003c\u003e\u003e),\n        {ok, [#{type =\u003e text, text =\u003e \u003c\u003c\"User: \", Id/binary\u003e\u003e}]}\n    end\n),\nServer = agent_wire_mcp:server(\u003c\u003c\"my-tools\"\u003e\u003e, [Tool]),\n{ok, Session} = claude_agent_sdk:start_session(#{sdk_mcp_servers =\u003e [Server]}).\n```\n\n### SDK Lifecycle Hooks\n\nRegister callbacks at key session lifecycle points:\n\n```erlang\n%% Block dangerous tool calls\nHook = agent_wire_hooks:hook(pre_tool_use, fun(Ctx) -\u003e\n    case maps:get(tool_name, Ctx, \u003c\u003c\u003e\u003e) of\n        \u003c\u003c\"Bash\"\u003e\u003e -\u003e {deny, \u003c\u003c\"Shell access denied\"\u003e\u003e};\n        _ -\u003e ok\n    end\nend),\n{ok, Session} = claude_agent_sdk:start_session(#{sdk_hooks =\u003e [Hook]}).\n```\n\nHook events: `pre_tool_use`, `post_tool_use`, `user_prompt_submit`, `stop`,\n`session_start`, `session_end`.\n\n### Telemetry\n\nAll adapters emit `telemetry` events:\n\n```erlang\ntelemetry:attach(my_handler, [agent_wire, query, stop], fun handle/4, #{}).\n```\n\nEvents: `[agent_wire, query, start|stop|exception]`,\n`[agent_wire, message, received]`, `[agent_wire, session, start|stop]`.\n\n### Supervisor Integration\n\nEmbed sessions in your supervision tree:\n\n```erlang\n%% In your supervisor init/1\nChildren = [\n    claude_agent_sdk:child_spec(#{\n        cli_path =\u003e \"/usr/local/bin/claude\",\n        session_id =\u003e \u003c\u003c\"worker-1\"\u003e\u003e\n    })\n],\n{ok, {#{strategy =\u003e one_for_one}, Children}}.\n```\n\n## Project Structure\n\n```\nbeam_agent_sdks/\n  apps/\n    agent_wire/           Shared foundation (types, JSONL, hooks, MCP, telemetry)\n    claude_agent_sdk/     Claude Code adapter (Port/JSONL + control protocol)\n    codex_app_server/     Codex CLI adapter (Port/JSON-RPC + exec fallback)\n    gemini_cli_client/    Gemini CLI adapter (Port/JSONL, one-shot per query)\n    opencode_client/      OpenCode adapter (HTTP REST + SSE via gun)\n    copilot_client/       Copilot adapter (Port/JSON-RPC + Content-Length)\n  wrappers/\n    claude_ex/            Elixir wrapper for Claude Code\n    codex_ex/             Elixir wrapper for Codex CLI\n    gemini_ex/            Elixir wrapper for Gemini CLI\n    opencode_ex/          Elixir wrapper for OpenCode\n    copilot_ex/           Elixir wrapper for GitHub Copilot\n```\n\n## Building\n\n### Erlang\n\n```bash\nrebar3 compile          # Build all apps\nrebar3 eunit            # Run all tests\nrebar3 dialyzer         # Static analysis\nrebar3 check            # compile + dialyzer + eunit + ct\n```\n\n### Elixir Wrappers\n\n```bash\ncd wrappers/claude_ex \u0026\u0026 mix compile \u0026\u0026 mix test\ncd wrappers/codex_ex \u0026\u0026 mix compile \u0026\u0026 mix test\ncd wrappers/gemini_ex \u0026\u0026 mix compile \u0026\u0026 mix test\ncd wrappers/opencode_ex \u0026\u0026 mix compile \u0026\u0026 mix test\ncd wrappers/copilot_ex \u0026\u0026 mix compile \u0026\u0026 mix test\n```\n\n## Requirements\n\n- Erlang/OTP 27+\n- Elixir 1.17+ (for wrappers)\n- `telemetry` ~\u003e 1.3\n- `gun` ~\u003e 2.1 (only for `opencode_client`)\n- Test deps: `proper` ~\u003e 1.4, `meck` ~\u003e 0.9\n\n## Per-App Documentation\n\nEach app and wrapper has its own README with full API reference, configuration\noptions, examples, and intentional omissions with workarounds:\n\n**Erlang Apps:**\n- [agent_wire](apps/agent_wire/README.md) — Shared foundation\n- [claude_agent_sdk](apps/claude_agent_sdk/README.md) — Claude Code adapter\n- [codex_app_server](apps/codex_app_server/README.md) — Codex CLI adapter\n- [gemini_cli_client](apps/gemini_cli_client/README.md) — Gemini CLI adapter\n- [opencode_client](apps/opencode_client/README.md) — OpenCode adapter\n- [copilot_client](apps/copilot_client/README.md) — GitHub Copilot adapter\n\n**Elixir Wrappers:**\n- [AgentWire](wrappers/agent_wire_ex/README.md) — Shared foundation (MCP, hooks, content, telemetry)\n- [ClaudeEx](wrappers/claude_ex/README.md)\n- [CodexEx](wrappers/codex_ex/README.md)\n- [GeminiEx](wrappers/gemini_ex/README.md)\n- [OpencodeEx](wrappers/opencode_ex/README.md)\n- [CopilotEx](wrappers/copilot_ex/README.md)\n\n## License\n\nSee individual app directories for license information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeardedeagle%2Fbeam_agent_sdks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeardedeagle%2Fbeam_agent_sdks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeardedeagle%2Fbeam_agent_sdks/lists"}