Contributing to Lemon

August 10, 2026 · View on GitHub

Thank you for your interest in contributing. Lemon is a BEAM-native platform for building agents, and the parts that are easiest to extend — channels, engines, storage, memory — are the parts we most want contributions to. This guide gets you from a clone to a merged extension.

If you only read one section, read Your first contribution.

Before You Start

  1. Run mix lemon.doctor — ensures your environment is set up correctly.
  2. Read docs/contributor/public_repo_basics.md — branching, commit style, feature flags.
  3. Read docs/contributor/ownership.md — code ownership lanes and CODEOWNERS rules.

Prerequisites

  • Elixir 1.19 / OTP 28. The umbrella dev build requires Elixir ~> 1.19 (pinned to 1.19.5 / OTP 28.5 in .tool-versions, same as CI). The published packages declare a lower ~> 1.15 floor for consumers, but building this repo needs 1.19 — 15 of the umbrella apps require it, so an older Elixir will fail at compile.
  • A version manager (asdf or mise) is the easy way to get that toolchain; the Quick Start assumes one is installed. Or install Elixir 1.19.5 / OTP 28 directly.
  • A C toolchain (cc/gcc + make). The SQLite-backed store compiles exqlite's native NIF from C source, so mix compile needs a working compiler — build-essential on Debian/Ubuntu, base-devel on Arch, Xcode command-line tools on macOS.

A clean checkout to a green mix lemon.doctor is about 2-3 minutes on a warm Hex cache (a first-ever mix deps.get downloads ~73 packages, so budget a little more cold).

Quick Start

git clone https://github.com/z80dev/lemon.git
cd lemon
asdf install   # or `mise install` — toolchain is pinned in .tool-versions (Elixir 1.19.5 / OTP 28.5, same as CI)
mix deps.get && mix compile
mix lemon.doctor

Full setup: docs/user-guide/setup.md

Development

scripts/test fast                 # compile with warnings as errors + ExUnit excluding integration
scripts/test path apps/lemon_core/test
scripts/test quality              # lint + architecture boundaries + doc freshness

scripts/test quality runs the same mix lemon.quality lane CI runs, and it is the gate most PRs need to pass. See docs/testing.md for the canonical local test lanes and how they map to CI.

Your first contribution

The best first contribution to Lemon is a new channel adapter — a Slack adapter, an SMS provider, an internal chat bridge. It touches one well-defined behaviour, it ships with a ready-made compliance suite, and it can live in this repo or in your own. Here is the whole on-ramp:

  1. Scaffold a project with the generator, so you have a running agent to attach a channel to:

    cd installer && MIX_ENV=prod mix archive.build
    mix archive.install lemon_new-0.1.0.ez
    mix lemon.new my_agent
    
  2. Follow the guide. Add a channel walks from the console loop the generator gives you to a registered LemonChannels.Plugin, callback by callback. Its companions — Build your first agent, Add a tool, and Persist memory — cover the neighbouring extension points.

  3. Prove it with the contract kit (below). A channel adapter that passes LemonPlatformTest.PluginCase is a channel adapter we can review quickly and merge with confidence.

You do not have to contribute the adapter back — the X integration lives in its own package and registers itself at boot, and yours can too. But if it is general-purpose, open a PR; it is exactly the contribution this project is shaped to receive.

Extension points

Every extension point is a behaviour with a published compliance suite in the lemon_platform_test kit. Implement the behaviour, then run the matching case against your module — the suite is the specification in executable form, so a green run is most of what a reviewer needs.

You want to addImplementCompliance suiteGuide
A channel (Slack, SMS, chat bridge)LemonChannels.PluginLemonPlatformTest.PluginCaseAdd a channel
A coding/agent engineLemonGateway.EngineLemonPlatformTest.EngineCase
A storage backendLemonCore.Store.BackendLemonPlatformTest.BackendCase
A memory providerLemonMemory.ProviderLemonPlatformTest.ProviderCasePersist memory

Each behaviour's moduledoc is the contract in prose; each case's moduledoc explains what it exercises and why. The built-in implementations are the worked examples — Telegram/Discord/email for channels, EtsBackend/SqliteBackend for storage, LemonMemory.Providers.Local for memory.

Running the contract kit

Add lemon_platform_test as a test-only dependency and write a one-file test that hands your module to the matching case. The suites are parameterized use macros; the options tell the suite what to run and which probes are safe.

# test/my_agent/slack_channel_test.exs
defmodule MyAgent.SlackChannelComplianceTest do
  use LemonPlatformTest.PluginCase,
    async: false,
    adapter: MyAgent.SlackChannel,
    deliver_probe: {__MODULE__, :unsupported_payload},
    inbound_fixtures: {__MODULE__, :updates}

  # `:deliver_probe` has no default on purpose: only you know which payload
  # cannot reach a real user. A kind your adapter does not support is the right
  # choice — a compliance suite that posts to a live workspace is worse than none.
  def unsupported_payload(_context), do: # ... an OutboundPayload your adapter rejects
  def updates(_context), do: # ... raw inbound fixtures your normalize_inbound/1 accepts
end
mix test test/my_agent/slack_channel_test.exs

The other suites follow the same shape:

use LemonPlatformTest.EngineCase,   async: true,  engine: MyApp.MyEngine, registry: false
use LemonPlatformTest.BackendCase,  async: true,  backend: MyApp.MyBackend
use LemonPlatformTest.ProviderCase, async: false, provider: MyApp.MyProvider

Look at apps/lemon_platform_test/test/compliance/ for a runnable example of each — those are the platform's own implementations held to the same suite you will run.

Commit Style (Conventional Commits)

<type>(<scope>): <short description>

Types: feat, fix, docs, refactor, test, chore Scope: app name or domain (lemon_core, lemon_skills, config, etc.)

Feature Flags

All new non-trivial features must be gated behind a flag in [features]. Use LemonCore.Config.Features.enabled?/2 — not System.get_env.

Skills and Generated Artifacts

  • Skills live in ~/.lemon/agent/skills/ or <project>/.lemon/skills/
  • Auto-generated skill drafts must go through human review before promotion
  • Do not commit skill draft files or personal ~/.lemon/ content

Pull Requests

  • Branch from main; branch name: <type>/<short-description>
  • PRs require approval from the CODEOWNERS of affected files
  • Cross-cutting changes (mix.exs, shared schemas) require @z80 sign-off
  • mix lemon.quality must be green (lint + architecture boundaries + doc freshness)
  • Register any new docs files under docs/ in docs/catalog.exs
  • Changed a published package's lib/? Add an entry to that package's CHANGELOG.md in the same PR — the change is visible to third parties who installed it from Hex. A CI job annotates PRs that miss this (advisory, per docs/platform-split.md §4.4); it does not block the merge, but reviewers do.

The pull request template has the full checklist.

Reporting Security Issues

See SECURITY.md.

License

By contributing, you agree your contributions will be licensed under the MIT License.