{"id":49928107,"url":"https://github.com/mjason/long","last_synced_at":"2026-06-14T09:00:57.552Z","repository":{"id":358187669,"uuid":"1240365600","full_name":"mjason/long","owner":"mjason","description":"Single-binary LLM agent runtime built on Elixir/OTP: chat UI, 4-tier memory, Anthropic-compatible Skills, scheduled tasks, multi-provider LLM routing, and platform bots.","archived":false,"fork":false,"pushed_at":"2026-06-09T04:40:48.000Z","size":1007,"stargazers_count":24,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T05:23:59.155Z","etag":null,"topics":["ai-agent","anthropic-skills","ash-framework","chat","elixir","liveview","llm","phoenix"],"latest_commit_sha":null,"homepage":"https://github.com/mjason/long","language":"Elixir","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/mjason.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-05-16T03:54:05.000Z","updated_at":"2026-06-09T04:40:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mjason/long","commit_stats":null,"previous_names":["mjason/long"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/mjason/long","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjason%2Flong","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjason%2Flong/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjason%2Flong/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjason%2Flong/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mjason","download_url":"https://codeload.github.com/mjason/long/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjason%2Flong/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34315075,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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-agent","anthropic-skills","ash-framework","chat","elixir","liveview","llm","phoenix"],"created_at":"2026-05-17T01:10:34.691Z","updated_at":"2026-06-14T09:00:57.544Z","avatar_url":"https://github.com/mjason.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Long\n\n[![Elixir](https://img.shields.io/badge/elixir-1.15%2B-purple.svg)](https://elixir-lang.org)\n[![Phoenix](https://img.shields.io/badge/phoenix-1.8-orange.svg)](https://phoenixframework.org)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![ghcr.io](https://img.shields.io/badge/ghcr.io-mjason%2Flong-2496ED?logo=docker\u0026logoColor=white)](https://github.com/mjason/long/pkgs/container/long)\n\n**English** · [简体中文](README.zh-CN.md)\n\n\u003e A single-process, multi-user LLM agent runtime on Elixir/OTP — for a household or a small team. Phoenix for the UI, Ash for the data layer, Oban for scheduled tasks, ReqLLM for provider abstraction.\n\nLong *started* as a port of the Python [GenericAgent](https://github.com/lsdefine/GenericAgent) to Elixir, borrowing its core shape — *one session → ReAct loop → tools + memory + skills*. The design has since diverged substantially: on the BEAM it gets real concurrency, fault tolerance, and long-lived push messaging natively (one supervised GenServer per session rather than a bolted-on Python process model), and the agent's capability layer has been rebuilt on **mature, standard technology rather than a bespoke tool protocol** — most notably **GraphQL as the agent's primary skill** (see below).\n\nIt's **web-first**: you don't run a CLI to talk to it. Open the browser, and chat, configuration, memory, channels, and scheduled tasks are all just pages.\n\n## Design philosophy\n\nLong is built to install and run **like a personal CLI tool, not like server infrastructure.** Everything below follows from that one decision.\n\n- **One self-contained binary.** `mix release` bundles the Erlang VM (ERTS) and every BEAM dependency into a single tarball. The target machine needs neither Erlang nor Elixir installed — just untar and run. No Docker image to build, no base image to track.\n- **Installs into one directory, owns nothing else.** `curl | bash` drops everything under `~/.long/` — the binary, the VM, the SQLite database, the config, the agent workspace, the skills. Uninstall is `rm -rf ~/.long`. Upgrades wipe only `bin/ lib/ releases/ erts-*` and **preserve your `env`, `long.db`, and `agent/` data**.\n- **No external services to provision.** Storage is SQLite (a file) plus the filesystem (skills, workspace). No Postgres, no Redis, no message broker. The whole runtime is *one OS process* — the BEAM — internally supervising sessions, bots, the scheduler, and even the headless-browser subprocess.\n- **Userspace, no root.** Install and autostart run entirely as your user. `~/.long/service install` wires up launchd (macOS) or a systemd **user** unit (Linux) so the agent survives reboot — you never write a unit file or `sudo` anything.\n- **No language runtime to provision.** `code_run` executes TypeScript/JavaScript in a sandboxed [Deno](https://deno.land/) binary that the app downloads and manages itself on first use — no Python, Node, or `uv` to install (`bash` is available for shell commands). The optional headless browser, Obscura, is fetched the same way.\n- **LAN-first, not internet-hardened.** Binds `0.0.0.0`, `check_origin` off, no forced SSL — so a freshly installed node is reachable from any device on your home network by IP. Internet exposure is opt-in (`LONG_CHECK_ORIGIN`, a reverse proxy, etc.), never the default that locks you out on first run. In the same spirit, `code_run`'s `bash` mode runs with the server's full host access (only the Deno engine — the default — is sandboxed per-member); that's fine for a trusted household, not for untrusted members.\n- **Open-source-friendly delivery.** The installer pulls release tarballs straight from GitHub Releases over plain `curl` — no `gh` CLI, no GitHub account, no auth. Anyone can install with one line.\n\nThe result: getting Long onto a Mac mini or a Linux box in the corner is `curl | bash` + paste an API key, and it behaves like an appliance from there.\n\n## GraphQL as the agent's primary skill\n\nMost agent frameworks invent a bespoke tool protocol — one narrow tool per capability (`schedule_task`, `remember_fact`, `update_checkpoint`, …), each with a hand-maintained schema the model has to be taught.\n\nLong takes a different route: **the agent's main capability is a single `graphql` tool** over the entire Ash data layer — sessions, messages, both memory tiers, working checkpoints, scheduled tasks, secrets, LLM/search configs — for **read *and* write** through one uniform interface.\n\n- **The model already speaks it.** GraphQL is in every model's pretraining; there's no custom DSL to explain.\n- **It's self-describing.** The schema is introspectable (`{ __schema { queryType { fields { name } } } }`), so the agent discovers its own capabilities at runtime instead of us maintaining a wall of tool descriptions.\n- **One tool replaces ~10.** Adding a new Ash resource automatically grants the agent CRUD over it — no new tool to write, register, or document.\n\nThis is the core bet: lean on mature, introspectable, widely-understood technology (GraphQL) as the agent's capability surface, and let the model's existing fluency do the rest. File-based **Skills** (`SKILL.md` + scripts, below) remain for packaged, code-carrying capabilities; GraphQL is how the agent reads and writes its own world.\n\n## Web UI\n\nEverything is a page — there's no separate CLI you have to learn to operate the agent day to day.\n\n| Page | What it is |\n|---|---|\n| `/chat` | the agent. Streaming replies, live tool-call display, a memory side-rail, AI-named sessions. |\n| `/manage` | everything else. LLMs, memory, skills, **groups \u0026 members**, sessions, search providers, channels, scheduled tasks, **phrases (i18n)** — each a LiveView. |\n\n## Features\n\n- **GraphQL capability layer** — one introspectable `graphql` tool gives the agent read/write over its whole data world (see above).\n- **Groups \u0026 members (multi-tenant)** — a deployment hosts one or more **groups** (a family, a small team). Each **member** links their own WeChat / Telegram by sending `/bind \u003ccode\u003e`, gets an isolated per-member code workspace + personal skills, and can message other members in the group (\"notify …\"). One owner, many members.\n- **Sandboxed code execution** — `code_run` runs TypeScript/JavaScript on a managed [Deno](https://deno.land/) binary, sandboxed (read/write) to the caller's per-member workspace; `bash` is there for shell. No Python — Deno auto-downloads on first use.\n- **Per-chat language (i18n)** — every bot string resolves through a fallback chain (channel → member → group → platform-detected → system default) and is overridable at `/manage/phrases`; set a system-wide default language in one click.\n- **Web-first LiveView UI** — `/chat` (streaming output + tool-call display + live memory side-rail + AI-generated session titles) and `/manage` for everything else.\n- **Four-tier memory:**\n  - L1 `WorkingCheckpoint` (one `key_info` row per session)\n  - L2 `GlobalMemory` / `SessionMemory` (fact / preference / goal / decision, with importance + recency decay)\n  - L3 **Anthropic-compatible Skills** (`SKILL.md` + scripts/references/assets), **scoped per-member or shared group-wide** (promote a personal skill to global, or view a skill's full `SKILL.md`, at `/manage/skills`); the filesystem is the source of truth, a watcher drives an ETS index\n  - L4 `SessionArchive` (session archival + LLM summary)\n- **Multi-provider LLM routing** — ReqLLM speaks 20+ providers natively (openai / anthropic / google / groq / deepseek / openrouter / mistral / ollama / xai / bedrock / …); wire protocol is configurable, one alias is the default.\n- **Unified admin at `/manage`** — LLM configs, memory editing, skill browsing, **groups \u0026 members, channels, phrases (i18n)**, session management, search providers, scheduled tasks, secrets — all LiveView, no ash_admin dependency.\n- **Scheduled tasks** — Oban-driven; the LLM schedules its own via GraphQL `createScheduledTask`, or you create them by hand at `/manage/scheduled`.\n- **Multi-account channels** — host several WeChat accounts and/or Telegram bots at once, each bound to a different member; inbound auto-tags the sender, outbound routes back through the exact account the chat arrived on. Onboarded at `/manage/credentials`.\n- **Web search aggregation** — Tavily / Brave API + SERP scrapers, RRF multi-source merge, providers configured at `/manage/search`.\n- **Real headless browsing** — the Obscura CLI (a Rust Chromium fork) powers the `web_scan` / `web_execute_js` tools.\n- **Error observability** — ErrorTracker dashboard, a `:logger` crash backstop, transparent exponential-backoff retry on LLM calls.\n- **In-conversation control** — `/clear` wipes a session, `/status` asks what the agent is doing, `/btw \u003cnote\u003e` interjects mid-run, `/bind \u003ccode\u003e` links this chat to a member.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│  Phoenix LiveView ─ /chat ─ /manage ─ navigation hub            │\n├─────────────────────────────────────────────────────────────────┤\n│  Long.Agent.Server (GenServer per session)  ──►  ReAct loop     │\n│   │                                │                            │\n│   ├── persist messages → DB       ├── tools (file/web/memory/…) │\n│   └── PubSub stream → LiveView    └── ReqLLM streaming          │\n├─────────────────────────────────────────────────────────────────┤\n│  Memory  L1 WorkingCheckpoint   L2 Global + Session             │\n│          L3 Skill.Store (FS + watcher + ETS)                    │\n│          L4 SessionArchive                                      │\n├─────────────────────────────────────────────────────────────────┤\n│  Long.Agent.Bots ─ WeChat │ Telegram                           │\n│  Oban ─ ScheduledTask runner    ErrorTracker ─ /errors          │\n├─────────────────────────────────────────────────────────────────┤\n│  Storage:  SQLite (Ash) + filesystem (skills, workspace)        │\n└─────────────────────────────────────────────────────────────────┘\n```\n\nCore stack:\n\n| Dependency | Role |\n|---|---|\n| Elixir 1.15+, Phoenix 1.8, Ash 3, AshSqlite | App / data layer |\n| Oban + AshOban + Oban Web | Background job scheduling |\n| ReqLLM | Unified multi-provider LLM access |\n| Jido + Jido.AI | Tool system (Zoi schema + auto JSONSchema) |\n| Mishka Chelekom | 70+ Tailwind LiveView components |\n| Obscura | Rust headless-browser CLI |\n| ErrorTracker | In-app exception aggregation |\n\n## Quick start\n\n### One-line install (macOS / Linux)\n\nPrebuilt releases cover `macos-arm64`, `linux-x64`, and `linux-arm64`:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/mjason/long/main/install.sh | bash\n```\n\nThe script:\n\n- pulls the latest tarball from GitHub Releases and extracts it to `~/.long/`,\n- on first run generates `~/.long/env` (with an auto-generated `SECRET_KEY_BASE`, `DATABASE_PATH`, …),\n- writes the `~/.long/run` launcher and the `~/.long/service` autostart controller.\n\n```bash\n$EDITOR ~/.long/env     # usually nothing to change\n~/.long/run             # start; open http://localhost:4000\n```\n\nMake it start on boot (no root, no unit-file editing):\n\n```bash\n~/.long/service install     # enable autostart (launchd / systemd-user)\n~/.long/service status      # is it registered + running?\n~/.long/service logs        # tail run.log\n~/.long/service uninstall   # disable autostart\n```\n\nInstaller environment variables:\n\n| Variable | Default | Meaning |\n|---|---|---|\n| `LONG_INSTALL_DIR` | `~/.long` | install target |\n| `LONG_VERSION` | latest | pin a version, e.g. `v0.2.9` |\n\n### Docker\n\nA self-contained image bakes in the `mix release` **plus** Deno and Obscura — nothing is downloaded on first run. The repo ships a ready `docker-compose.yml`:\n\n```yaml\nservices:\n  long:\n    image: ghcr.io/mjason/long:latest     # or build locally: build: .\n    ports: [\"4000:4000\"]\n    environment:\n      SECRET_KEY_BASE: ${SECRET_KEY_BASE}  # generate once: openssl rand -base64 48\n      PHX_HOST: ${PHX_HOST:-localhost}\n      # LONG_CHECK_ORIGIN: \"true\"          # set when exposing to the internet\n    volumes: [\"long_data:/data\"]          # SQLite DB + skills + workspace + memory\n    restart: unless-stopped\nvolumes:\n  long_data:\n```\n\nFrom a clone of the repo:\n\n```bash\nexport SECRET_KEY_BASE=$(openssl rand -base64 48)\ndocker compose up -d          # build + run\n# → http://localhost:4000\n```\n\nData (SQLite DB, skills, agent workspace, memory) persists in the `long_data` volume across restarts and upgrades. Prefer a bare image? `docker build -t long .`.\n\n### Run from source\n\nRequirements:\n\n- Elixir 1.15+ / Erlang 26+\n- SQLite 3\n- (Deno and the optional Obscura browser are auto-downloaded at runtime — nothing to pre-install.)\n\n```bash\ngit clone https://github.com/mjason/long.git\ncd long\nmix setup            # deps.get + ecto.create + migrate + seeds + asset build\nmix phx.server\n```\n\nOpen \u003chttp://localhost:4000/\u003e and enter `/chat` or `/manage/llms` from the navigation hub.\n\n### Configure your first LLM\n\nOpen `/manage/llms` → **New LLM**:\n\n| Field | Example |\n|---|---|\n| Alias | `claude_main` |\n| Provider | `anthropic` |\n| Wire protocol | `anthropic_messages` |\n| Model | `claude-sonnet-4` |\n| API base | `https://api.anthropic.com` (or a proxy) |\n| API key | `sk-ant-…`, or leave blank to use `api_key_env_var` |\n| Set as default | ✓ |\n\nSave, go back to `/chat`, and new sessions bind to this alias automatically. The same flow works for OpenAI / Google / Groq / DeepSeek / any ReqLLM-supported provider.\n\n### Install your first Skill (optional)\n\n```bash\nmkdir -p priv/agent/skills/hello-world/scripts\n\ncat \u003e priv/agent/skills/hello-world/SKILL.md \u003c\u003c'MD'\n---\nname: hello-world\ndescription: Demo skill — takes a `name` arg, returns a greeting string.\ntags: [demo]\n---\n\n# hello-world\n\nRun `scripts/hello.py \"\u003cname\u003e\"`.\nMD\n\ncat \u003e priv/agent/skills/hello-world/scripts/hello.py \u003c\u003c'PY'\nimport json, sys\nname = (json.loads(sys.argv[1]) if len(sys.argv) \u003e 1 else {}).get(\"name\", \"world\")\nprint(json.dumps({\"greeting\": f\"hello, {name}\"}, ensure_ascii=False))\nPY\n\nmix long.skill reindex   # or restart the server; the watcher also picks it up\n```\n\nNext conversation, the LLM sees `hello-world` under `# Available skills` and can `skill_read` then `code_run` it. The format is fully compatible with [Anthropic Agent Skills](https://code.claude.com/docs/en/skills), so you can `git clone https://github.com/anthropics/skills priv/agent/skills/` to grab the official repo wholesale.\n\n## Channels \u0026 members\n\nA deployment serves one or more **groups**; each group has **members** (people), and each member links their own chat accounts. The owner sets channels up once at **`/manage/credentials`** — no env vars, no restart:\n\n- **WeChat** — click *扫码登录* (scan to log in) for an inline QR and host an account via Tencent's iLink bot API (no desktop hook). **Multiple accounts** are supported — host one per member/role; the worker hot-reloads on connect.\n- **Telegram** — paste a [@BotFather](https://t.me/BotFather) token; the bot starts long-polling immediately. Replies render as Telegram HTML, with typing indicators and inbound/outbound media. **Multiple bots** are supported, one per member.\n\nMembers then link themselves: each member has a `/bind \u003ccode\u003e` (shown at `/manage/groups`); they send it to the shared bot and that chat account is tied to them. Once bound, members can address each other — *\"notify my spouse / 通知老张 …\"* — and the agent delivers to every channel that member has linked, in **their** language (see i18n above). Outbound always routes back through the exact account a chat arrived on.\n\n## CLI tools\n\n| Command | Purpose |\n|---|---|\n| `mix phx.server` | start the web server (default port 4000) |\n| `mix long.skill list / reindex / remove NAME` | skill index management |\n| `mix long.wechat.login` | WeChat QR login + buf persistence |\n| `iex -S mix` | REPL: `Long.Agent.list_sessions()`, etc. |\n| `mix test` | test suite |\n| `mix precommit` | `compile --warnings-as-errors + format + test` |\n\n## Configuration\n\n**Configure from the web, not from files.** Almost everything — LLMs, search providers, channels, scheduled tasks, memory, secrets — lives in the DB and is edited at `/manage`. There's no config file to redeploy for day-to-day changes.\n\nThe only file-level config is a handful of filesystem roots, under `:long, Long.Agent` in `config/config.exs` (the installed release reads these from `~/.long/env` instead):\n\n```elixir\nconfig :long, Long.Agent,\n  memory_root: \"priv/agent/memory\",      # legacy GenericAgent-compatible path\n  skill_root:  \"priv/agent/skills\",      # L3 skill dir (SKILL.md lives here)\n  workspace_root: \"priv/agent/workspace\" # root for code_run / file_* tools\n```\n\nEverything else is a page in `/manage` (or, if you prefer, an IEx call like `Long.Agent.register_llm/1`).\n\n## Development\n\n```bash\nmix test                                # unit + LiveView tests\nmix test test/long/jido                 # run one group\nmix format\nmix precommit                           # compile --warnings-as-errors + format + test\nmix usage_rules.docs Ash.Resource       # look up dependency docs\n```\n\n`CLAUDE.md` carries project-level AI-agent guidance (usage rules + skill entry points), auto-loaded if you use Claude Code / Cursor / similar IDE agents.\n\n## Status\n\n**Beta — small multi-user.** Runs as a daily AI assistant for a household / small workgroup (a few members, one box on the LAN).\n\n- Multi-tenant by **group + member** — per-member code workspaces, personal/shared skills, per-channel binding — but **no hard auth/RBAC yet**: members are trusted, and `bash` runs unsandboxed (only the Deno engine is per-member sandboxed). Fine for a family/team; not for untrusted members or internet exposure.\n- The schema changes occasionally; no backward-compat promise.\n- Some paths (mixin LLM, full WeChat / Telegram chains) have light test coverage.\n- Deployment docs are single-node only.\n\nIssues and PRs welcome, but there's no committed release cadence.\n\n## Roadmap\n\nNear-term:\n\n- [x] Multi-user via groups + members (per-member workspace, personal/shared skills, channel binding)\n- [ ] Group-level RBAC — member roles enforced at the data layer (for workgroups / semi-trusted members)\n- [ ] Consolidate the dual ReAct loop + tool families (retire the legacy `Long.Agent.Loop` / `Long.Agent.Tools`)\n- [ ] Pin + SHA-verify the Deno / Obscura binary downloads (supply chain)\n\nLonger-term:\n\n- [ ] PubSub reconnect replay — buffer recent turn events so a reconnecting client doesn't miss mid-stream output\n- [ ] Timezone-aware scheduling (currently UTC-only)\n- [ ] Observability page — tool error rates, LLM retries, Deno / Obscura install status\n\nBacklog (nice-to-have, not roadmap):\n\n- Framework-driven `/manage` forms (Mishka `\u003c.text_field\u003e` / AshPhoenix.Form) instead of hand-rolled `\u003cinput\u003e`s\n\n## Acknowledgements\n\n- The Python [GenericAgent](https://github.com/lsdefine/GenericAgent) was the original starting point.\n- [Ash Framework](https://ash-hq.org) lets data + domain logic be described together.\n- [ReqLLM](https://hexdocs.pm/req_llm/) makes onboarding a new LLM provider a one-line alias.\n- [Mishka Chelekom](https://mishka.tools/chelekom/) provides the full LiveView component library.\n- [Anthropic Agent Skills](https://github.com/anthropics/skills) defines the SKILL.md format.\n- [Obscura](https://github.com/h4ckf0r0day/obscura) forked Chromium into a clean CLI.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjason%2Flong","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmjason%2Flong","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjason%2Flong/lists"}