We're open source. If actrone-memory has been useful to you, a star on GitHub means a lot to us.

Coding assistants that learn your repository’s conventions

Tell the assistant once that tests run with vitest or that dates use date-fns. It recalls the conventions that matter for each task, inside the token budget you set. With the defaults, the memory runs in your process and sends nothing anywhere.

npm install actrone-memory  ·  pip install actrone-memory

The problem

The same rules, pasted into every prompt

Conventions live in people’s heads and in long rules files, and neither fits neatly into a context window.
  • Rules are repeated on every request

    Developers paste the same instructions again, or keep a rules file that the assistant reads in full every time, relevant or not.

  • Long sessions crowd out what matters

    After an hour of back and forth, the convention that matters for this change is buried under turns that do not.

  • One repository’s rules leak into another

    A single shared memory suggests the Python service’s conventions while you work in the TypeScript app.

How it works

Four steps, one scope per repository

Each repository gets its own scope, such as repo:web, so conventions are recalled for the codebase they belong to and nowhere else.
  1. Record a convention once

    injectMemory, in Python inject_memory

    When a developer or a code review states a rule, store it under the repository’s scope.

  2. Recall for each task

    retrieveContext, in Python retrieve_context

    Ranks the repository’s conventions against the task description and returns the most relevant ones.

  3. Stay inside the budget

    tokenBudget, in Python token_budget

    By default a quarter of the budget goes to long-term memory and about a third to recent turns. Both are pruned to fit, so the context never exceeds what you allowed.

  4. Keep the session

    storeTurn, in Python store_turn

    Each request and answer is recorded, so a follow-up request in the same session sees the previous exchange.

The code

The whole integration, in both languages

The test suites of both packages run this code and check that a task recalls its own repository’s convention, never another’s, and that the returned context stays within the 800-token budget.
coding.ts
import { MemoryManager } from "actrone-memory";

// One scope per repository, so one codebase's conventions never leak into another's.
const scopeFor = (repo: string) => `repo:${repo}`;

// Record a convention once, when the developer or a code review states it.
export async function learnConvention(memory: MemoryManager, repo: string, convention: string) {
  return memory.injectMemory(scopeFor(repo), convention, 0.9, "conventions", ["convention"], "user");
}

// Before each request, recall this task's conventions within a token budget.
export async function conventionsFor(
  memory: MemoryManager,
  repo: string,
  sessionId: string,
  task: string,
) {
  const context = await memory.retrieveContext(scopeFor(repo), sessionId, task, 800);
  return {
    conventions: context.episodicMemories.map((m) => m.content),
    tokensUsed: context.totalTokensUsed,
  };
}

// Keep the exchange, so the next request in this session sees it.
export async function recordExchange(
  memory: MemoryManager,
  repo: string,
  sessionId: string,
  request: string,
  answer: string,
) {
  await memory.storeTurn(scopeFor(repo), sessionId, request, answer);
}
coding.py
from dataclasses import dataclass

from actrone_memory import MemoryManager


def scope_for(repo: str) -> str:
    """One scope per repository, so one codebase's conventions never leak into another's."""
    return f"repo:{repo}"


async def learn_convention(memory: MemoryManager, repo: str, convention: str) -> str:
    """Record a convention once, when the developer or a code review states it."""
    return await memory.inject_memory(
        agent_id=scope_for(repo),
        content=convention,
        importance=0.9,
        session_id="conventions",
        topic_tags=["convention"],
        source="user",
    )


@dataclass
class TaskContext:
    conventions: list[str]
    tokens_used: int


async def conventions_for(
    memory: MemoryManager, repo: str, session_id: str, task: str
) -> TaskContext:
    """Before each request, recall this task's conventions within a token budget."""
    context = await memory.retrieve_context(
        agent_id=scope_for(repo), session_id=session_id, query=task, token_budget=800
    )
    return TaskContext(
        conventions=[m.content for m in context.episodic_memories],
        tokens_used=context.total_tokens_used,
    )


async def record_exchange(
    memory: MemoryManager, repo: str, session_id: str, request: str, answer: str
) -> None:
    """Keep the exchange, so the next request in this session sees it."""
    await memory.store_turn(
        agent_id=scope_for(repo),
        session_id=session_id,
        user_message=request,
        assistant_message=answer,
    )

Create the manager once and pass it in: await MemoryManager.create() in TypeScript, or async with create_memory_manager() as memory: in Python.

Limits

What this does not do for a coding assistant

It is memory for rules you state, not a code index.
  • It does not read your code

    It remembers what you tell it. It does not index the repository, parse files or build a code graph.

  • It recalls conventions. It does not enforce them

    Memory puts the right rules in front of the model. Following them is still the model’s job, and your review’s.

  • Recall matches shared words until you add a model

    With no extra packages the embedder compares words, so “Add a route handler” finds “Route handlers live in src/routes” through the words they share. Install fastembed in TypeScript or the onnx extra in Python for recall by meaning, still on your machine.

  • In-process until you add a store

    The default store lives in your process, so a restart forgets everything. Pass the Redis, Postgres or Qdrant adapters when memory has to outlive a deploy. The calls in the code do not change.

  • Token counts are estimates

    Both libraries count about four characters per token by default. Python counts exactly with the tiktoken extra, and TypeScript accepts your own counter.