Define project-specific instructions and constraints with LOOM.md
Project rules let you give Loom persistent instructions that are injected into every conversation. This is how you encode project conventions, architectural patterns, and tool constraints without repeating yourself.
A LOOM.md file is parsed as Markdown with special sections:
LOOM.md
# Project InstructionsThis is a Phoenix LiveView app using Ecto with PostgreSQL.We follow the context module pattern with modules organized under `lib/myapp/`.## Rules- Always run `mix format` after editing .ex files- Run `mix test` before committing- Use `binary_id` for all primary keys- Follow the context module pattern## Allowed Operations- Shell: `mix test`, `mix format`, `git status`, `git diff`- File Write: `lib/**`, `test/**`, `priv/repo/migrations/**`## Denied Operations- File Write: `config/runtime.exs`, `.env*`, `*.pem`- Shell: `rm -rf`, `mix deps.clean`
Any Markdown outside the three recognized sections becomes instructions injected into the system prompt.
LOOM.md
# Phoenix Best PracticesThis project uses Phoenix 1.7 with LiveView.### AuthenticationWe use Pow for authentication. User sessions are stored in Redis.Never commit secrets to the repo.### Testing StrategyWrite feature tests for LiveView interactions.Use Mox for external service mocks.## Rules- Run `mix format` before committing...
Everything under “Phoenix Best Practices” and “Authentication” and “Testing Strategy” becomes part of the instructions.
Here’s a real-world LOOM.md for an Elixir project:
LOOM.md
# Loom Project GuidelinesLoom is an Elixir-native AI coding assistant built with Phoenix LiveView and OTP.## Architecture- **Session layer**: One GenServer per conversation- **Tool layer**: Jido.Action modules for all capabilities- **Decision graph**: SQLite-backed DAG for reasoning memory- **Web UI**: Phoenix LiveView with PubSub broadcasting## Coding Standards- Follow [Credo](https://github.com/rrrene/credo) guidelines- Use `with` for happy-path chaining- Pattern match in function heads when possible- Keep GenServers focused on coordination, not business logic## Testing- Async tests by default unless testing ETS/global state- Use `setup` callbacks for common fixtures- Test both success and error paths## Rules- Run `mix format` after editing Elixir files- Run `mix test` before creating commits- Use `Loom.Tool.param!/2` for required params, `param/3` for optional- Keep system prompts in the module that uses them, not in config- Broadcast session events via PubSub for LiveView updates- Never expose raw file paths to the LLM - use relative paths## Allowed Operations- Shell: `mix test`, `mix format`, `mix credo`, `git status`, `git diff`, `git log`- File Write: `lib/**/*.ex`, `test/**/*.exs`, `priv/repo/migrations/*.exs`- File Edit: `lib/**`, `test/**`, `config/*.exs`- File Read: `**/*`## Denied Operations- File Write: `config/runtime.exs`, `.env*`, `*.pem`, `priv/**/*.db`- Shell: `rm -rf`, `mix deps.clean --all`, `mix ecto.drop`, `git push --force`
You can load and inspect project rules programmatically:
# Load rules from a project directory{:ok, rules} = Loom.ProjectRules.load("/path/to/project")rules.raw # Original file contentrules.instructions # Custom instructions textrules.rules # List of rule stringsrules.allowed_ops # Map of %{"shell" => ["git*"], ...}rules.denied_ops # List of denied operation strings# Format for system prompt injectionprompt_text = Loom.ProjectRules.format_for_prompt(rules)
Here’s a LOOM.md that enforces a test-driven workflow:
## Rules- Write tests BEFORE implementation- Run tests after every code change- Keep test coverage above 80%- Use descriptive test names that explain the behavior being tested## Allowed Operations- Shell: `mix test`, `mix test.coverage`- File Write: `test/**/*_test.exs`- File Edit: `lib/**/*.ex`, `test/**/*.exs`## Denied Operations- Shell: `mix test --only skip`