Imported from trymirai/lalamo (
AGENTS.md). Install upstream withnpx skills add trymirai/lalamo. Copyright stays with the author.
Workflow rules:
- When given a coding task, AI Assistant carefully and deeply inspects the codebase to understand the context.
- If the Assistant has access to search tools, it uses them to search for documentation and use case examples for external libraries.
- AI Assistant doesn't ask for permission to read the contents of files and directories.
- When given a task, AI assistant first thinks about multiple high-level strategies, and then asks the user for feedback on every option.
- AI Assistant splits tasks into smallest possible subtasks, and solves them one by one while asking the user for detailed feedback during every step.
- AI Assistant never edits code before proposing a short draft to the user and receiving a feedback.
- AI Assistant is not allowed to make even the tiniest architectural decisions, it can only propose multiple options to the user.
- AI Assistant behaves like an interviewer and asks the user questions all the time.
- AI Assistant never relies on its own assumptions about implementation details of libraries and tools. It always makes sure to check the reference.
- Instead of asking multiple questions in one message, AI assistant asks them one by one.
- AI Assistant always asks for permission before launching CLI tools which may take lots of time or produce side effects.
- Before using any function from an external library, AI Assistant thoroughly inspects the code or documentation for the function to ensure that it uses it correctly.
- When encountering an error, AI Assistant never rushes to quickly fix it. Instead, it uses the scientific method to understand the source of the error: comes up with hypotheses and tests them by performing a series of experiments. It only attempts to fix the error once the hypothesis has been sufficiently validated.
- AI Assistant avoids creating .md files with explanations unless explicitly requested by the user.
General coding guidelines:
- AI Assistant never resorts to quick hacks or stubs to make something work. Instead it always investigates the problem thoroughly and comes up with a well-thought-out solution.
- By default, AI Assistant does not write documentation or comments unless the code contains complex logic which is difficult to understand without them.
- AI Assistant strives for simplicity and maintainability. AI Assistant avoids writing boilerplate code, instead it tries to come up with the right abstractions.
- AI Assistant prefers functional programming style, namely:
- Avoids mutable state.
- Avoids modifying objects in-place.
- Avoids implicit side effects.
- Prefers immutable data structures.
- Strives to make invalid state unrepresentable.
- AI Assistant tries to write code in the way that makes bugs hard to make and easy to spot.
- AI Assistant writes the code so that assumptions about invariants are as explicit as possible.
- AI Assistant leverages strong typing as much as possible, for example, it always uses enums instead of literals.
- AI Assistant uses good descriptive variable names that make it easy to understand the purpose of the variable without looking at the code that uses it.
- AI Assistant does not manually parse things such as JSON configs. Instead it first models a schema using types, and then uses a serialisation library such as Cattrs, Pydantic, or Serde to handle serialization and deserialization and validation.
Testing guidelines
- AI Assistant strives for simplicity and minimalism when writing tests.
- AI Assistant avoids extensive mocking and stubbing.
- AI Assistant focuses on testing core functionality, and does not test minor details such as property accessors.
- AI Assistant avoids boilerplate in the testing code, and tries to come up with the right generic abstractions.
- AI Assistant should run the tests via
uv run pytest -m fast -n=4after finishing any non-trivial changes. - AI Assistant runs pyrefly and ruff checks after every edit to make sure there are no type errors and linter warnings.
Python coding rules:
- Never use pip to manage dependencies. Instead use uv commands, such as
uv add. - Assume Python 3.12+. Use
T | Noneinstead ofOptional[T]anddict[K, V]instead oftyping.Dict[K, V]. - Every function should have full type annotations.
- Prefer comprehensions to map and filter expressions.
- Prefer dataclasses over vanilla python classes.
- All path-like arguments should have type
Path | str. - Prefer frozen dataclasses or named tuples as primary data structures.
- Never use dicts in place of dataclasses.
- init.py files should only contain reexports.
- Avoid single-line if expressions. Prefer multi-line if statements.
Rust coding rules:
- Prefer
Box<[T]>toVec<T>if the contents of the container are not going to change. - Avoid mutable variables as much as possible. Use iterator expressions to collect data into containers.
Code best practices in examples
# Bad: This expression does not communicate implicit assumptions about the list's shape, and variable names are not descriptive enough.
x, y = my_list[0], my_list[-1]
# Good: Destructuring clearly communicates the assumptions about the length of the list, and variable names are clear.
first, *_, last = my_list
# Bad: it is not clear what values the dish can hold.
def cook(dish: str) -> ...:
...
# Good: Enum makes it clear.
from enum import Enum
class Dish(Enum):
STEAK = "steak"
PIZZA = "pizza"
SALAD = "salad"
def cook(dish: Dish) -> ...:
...
# Bad: This expression makes the shape of the `x` and the semantics of the operation unclear.
permuted_x = np.permute(x, (0, 2, 1))
# Good: This expression makes the shape of the `x` and the semantics of the operation clear.
permuted_x = einops.rearrange(x, "batch in_channels out_channels -> batch out_channels in_channels")
# Bad: Dict is used instead of a struct.
def model_registry():
"""Registry of all available models"""
return {
"llama": {
"hf_name": "meta-llama/Llama-3.2-1B-Instruct",
"test_class": LlamaModelTest,
"max_layers": 16,
},
"qwen": {
"hf_name": "Qwen/Qwen2.5-1.5B-Instruct",
"test_class": QwenModelTest,
"max_layers": 24,
},
}
# Good: Frozen dataclasses.
@dataclass(frozen=True)
class ModelTestSpec:
hf_name: str
test_class: type[BaseModelTest]
max_layers: int
def model_registry() -> list[ModelTestSpec]:
return [
ModelTestSpec(
hf_name="meta-llama/Llama-3.2-1B-Instruct",
test_class=LlamaModelTest,
max_layers=16,
),
ModelTestSpec(
hf_name="Qwen/Qwen2.5-1.5B-Instruct",
test_class=QwenModelTest,
max_layers=24,
),
]
# Bad: Using an unnecessary global constant
NUMBER_OF_SOLVER_STEPS = 5
def solve(self, problem: Problem, num_steps: int = NUMBER_OF_SOLVER_STEPS):
...
# Good: In-place constant
def solve(self, problem: Problem, num_steps: int = 5):
...
# Bad: custom __init__ method on a dataclass
@dataclass
class Banana:
color: Color
length: float
width: float
def __init__(self, is_green: bool = False):
if is_green:
self.color = Color.Green
self.length = 5.0
self.width = 3.0
else:
self.color = Color.Yellow
self.length = 7.0
self.width = 1.0
# Good: A special method to construct the dataclass, boolean argument keyword-only
@dataclass
class Banana:
color: Color
length: float
width: float
@staticmethod
def build(cls, *, is_green: bool):
...
# Bad: Mixing semantic and operational arguments together in one structure
@dataclass
class Config:
vram_limit: str # semantic
use_flash_attention: bool # operational
...
# Good: Split them up
@dataclass
class ParallelizationConfig:
vram_limit: str
...
@dataclass
class ForwardPassConfig:
use_flash_attention: bool
...
# Bad: An unnecessary function and confusing logic
def _apply_sharding_config(array: Float[Array, "*"], sharding: Sharding | None):
if sharding is None:
return array
return sharding.shard(array)
def shard_module(module: ShardableModule):
for field in module.fields():
if eqx.is_array(field):
field = _apply_sharding_config(field, module.get_sharding())
...
# Good: No unnecessary functions, easy to read and understand
def shard_module(module: ShardableModule):
sharding = module.get_sharding()
for field in module.fields():
if eqx.is_array(field) and sharding:
field = sharding.shard(field)
...
# Bad: message is non informative and contains trivial details
def test_decode_one_token():
skip_on_gpu("Skip on GPU due to token mismatch in test_decode_one_token.")
...
# Good: message describes the reasoning
def test_decode_one_token():
skip_on_gpu("Flaky on GPU due to torch/jax precision inconsistencies.")
...