- Go 99.5%
- Just 0.5%
| cmd/cli | ||
| docs | ||
| internal | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| doc.go | ||
| go.mod | ||
| go.sum | ||
| Justfile | ||
| README.md | ||
| TODOS.md | ||
agent-memory
Go module for agent memory functionality.
Repository: https://git.yongbeom.com/dernbu/agent-memory
Development
Requires Go 1.26.4 or later.
Use just as the development entry point:
just # List available recipes
just check # Test, vet, and compile all packages
just fmt # Format Go source
just cli --help # Run the CLI
just build # Build bin/agent-memory
The Go commands can also be run directly:
go test ./...
go vet ./...
go build ./...
Module path: git.yongbeom.com/dernbu/agent-memory.
Internal packages
internal/db: SQLite facade, usingmodernc.org/sqlite, with Goose (github.com/pressly/goose/v3) for migrations.internal/skills: agent skill management.internal/processlock: PID files and Unix OS locks for CLI process exclusion.
db.Open(ctx, path) opens a database and applies embedded Goose migrations.
Use a file path for persistent storage or :memory: for an in-memory database.
The parent directory must exist, and the caller must close the database.
The first migration enables WAL mode. Each connection enforces foreign keys, waits up to five seconds for locks, and uses FULL synchronous mode for durability. Connections also use in-memory temporary storage, a roughly 20 MiB page-cache budget, disabled trusted schemas, automatic WAL checkpoints at 1,000 pages, and a 64 MiB retained journal size limit. This limit does not cap an active WAL. Access is serialized through a single connection per database handle.
skills.Manager provides programmatic installation, search, listing, and retrieval
through an injected skills.Store interface. skills.SQLiteStore implements that
interface using SQLite. Git and filesystem operations are performed by the manager.
Full-text search
The SQLite driver includes FTS5 without additional dependencies or extension loading. The second migration adds repositories, skills, attachment paths, and the full-text index. Search uses BM25 weights of 10, 5, and 1 for skill name, description, and full SKILL.md content, respectively.
CLI
The CLI entry point is cmd/cli/main.go, invoked through the Justfile. Copy the
environment template and edit AGENT_MEMORY_CONFIG_DIR in .env if needed:
cp .env.example .env
just cli skills install https://example.com/team/skills.git
just cli skills install git@example.com:team/other-skills.git
just cli skills add https://example.com/team/skills.git
just cli skills search "remember useful goals"
just cli skills search --limit 5 "remember useful goals" --offset 10
just cli skills list
just cli skills list --limit 10 --offset 20
just cli skills list --name-only
just cli skills get my-skill
just cli skills get team/skills:my-skill
just cli skills retrieve my-skill
just cli skills root
Commands, arguments, flags, and help are defined with
Kong. Use --help at any command level,
for example just cli skills search --help, to see its arguments and defaults.
Just automatically loads .env for every recipe. The template uses
${HOME}/.agent-memory-config; existing shell environment variables take
precedence. The local .env file is ignored by version control. When running
the CLI directly, export AGENT_MEMORY_CONFIG_DIR in your shell instead.
Every invocation requires a nonblank AGENT_MEMORY_CONFIG_DIR, including help
and invalid commands. Before parsing arguments, startup creates that directory
if necessary and acquires <CONFIG>/global.lock. One exclusive,
nonblocking Unix OS file lock is held for the entire command, with the owner's
PID recorded in the file. On contention, acquisition makes one initial attempt
and up to three retries, with 500 ms between attempts (up to 1.5 seconds of
retry delay). If it still cannot acquire the lock, it reports an error on stderr
with exit status 1 and no command output. Cancellation interrupts retry delays;
configuration and other permanent errors fail immediately. Separate configuration
directories run independently.
Startup removes dead or invalid PID files only after acquiring their OS lock, then attempts to acquire a replacement. A live recorded PID blocks startup even if its OS lock is absent. Normal completion and command errors remove the owned file while locked. Abrupt termination may leave a file that the next invocation recovers. Acquiring the process lock alone does not initialize the database or skills directory. Programmatic database/manager callers must coordinate their own shared-state access.
skills add <git-url> is an alias for skills install <git-url>.
The CLI creates <CONFIG>/db/agent-memory.db and clones entire repositories into
<CONFIG>/skills/<host>/<repository-path>. SSH and HTTPS sources for the same
host and repository share a location. Installation requires the installed git
binary and fails early if the location already exists.
skills root prints the plain absolute path to <CONFIG>/skills followed by a
newline. It requires AGENT_MEMORY_CONFIG_DIR and an existing skills directory;
missing, inaccessible, or non-directory paths produce an error on stderr and a
nonzero exit status. Startup may create the configuration directory for its lock;
the root command creates no skills directory and does not open the database.
Relative configuration paths are resolved against the current working directory.
Symlinks to directories are accepted, and the configured absolute path is
preserved without resolving symlinks; the broader symlink policy is deferred.
Use quoted command substitution to inspect installed files:
skills_root=$(just cli skills root) && ls "$skills_root"
skills_root=$(just cli skills root) && grep -r 'search text' "$skills_root"
Every regular file named SKILL.md, case-insensitively, identifies a skill.
Discovery skips .git metadata and symlinks. Other regular files beneath each
skill directory are indexed as attachment paths; files elsewhere are retained in
the clone. Missing or malformed YAML metadata is accepted, with the containing
directory name as the name fallback and an empty description fallback.
Each skill has a simple name from its metadata (or directory-name fallback) and
a canonical name <repository-path>:<simple-name>. For example, a skill named
grill-me in github.com/mattpocock/skills has canonical name
mattpocock/skills:grill-me, regardless of its directory inside the repository.
The repository path excludes the host and trailing .git and retains the
normalized repository key's case and percent-encoding. Canonical names are
stored during indexing; opening an existing database automatically backfills
them without reinstalling skills.
Installation prints a JSON summary containing the clone path and indexed skill
count. A repository with no skills succeeds with a count of zero. Search accepts
plain text, matches any query word, and prints a ranked JSON array containing
name, description, and snippet for each match. name always contains the
canonical name; no separate simple-name field is returned. Search still matches
simple names, descriptions, and content; repository prefixes are not indexed.
SQLite FTS5 selects one raw snippet from the indexed simple name, description,
or full SKILL.md content, with up to
64 FTS5 tokens and … marking omitted text. No match-highlighting markup is
added; source Markdown and frontmatter are preserved when selected. Existing
installations support snippets without reindexing.
--limit defaults to 10 and must be a positive integer;
--offset defaults to 0 and must be a nonnegative integer. Offset skips that
many ranked matches before applying the limit. Both flags accept --flag N
and --flag=N, before or after the single query argument. Each flag may appear
only once. Use -- before queries beginning with a dash. Invalid values,
including integer overflow, missing values, and repeated flags, fail before
initializing the database or skills directory. No matches or an offset beyond
the matches prints []; empty queries fail. Attachments are not included in
text search.
skills list returns all indexed installed skills, ordered alphabetically by
canonical name, grouping skills from the same repository, as a JSON array of
objects containing canonical name and description. --name-only returns a
JSON array of canonical-name strings.
--limit N caps the number of results and must be positive; omitting it returns
all remaining skills.
--offset N skips that many ordered skills and defaults to zero. It must be
nonnegative and works with or without a limit. All three flags can be combined;
numeric flags accept both --flag N and --flag=N. Repeated flags and invalid
arguments fail before storage is opened. Empty installations and offsets past
the end print [] in either output mode. Listing reads the installation index
and requires no Git executable. Sorting folds ASCII case, with exact name and
installation path breaking ties; entries with duplicate names are preserved.
Programmatically, use Manager.List(ctx, skills.ListOptions{Limit: limit, Offset: offset}). Limit is an optional *int: nil means unlimited, while
a supplied zero or negative limit fails validation.
See TODOS.md for deferred work. Interrupted or failed installs can leave a clone without a complete index; automatic reconciliation is deferred.
skills get <skill-name> and its alias skills retrieve <skill-name> return one
JSON object with name, files, base_dir, and content. Matching first tries
the exact, case-sensitive canonical name, then the exact, case-sensitive simple
name if no canonical match exists. Successful results always use the canonical
name in name, including for simple-name lookups. base_dir is absolute;
each entry in files is a relative path beneath it, including the skill
Markdown file. Traversal
recursively includes regular files and skips symlinks and .git metadata,
following the same rules as discovery. content is the raw indexed Markdown,
including frontmatter. The command requires no Git executable.
A missing skill prints an error to stderr and exits nonzero, with no JSON on
stdout. Blank names and invalid arguments fail before storage is opened. File
listing errors also fail the command. Programmatically, use
Manager.FindByName(ctx, name) to obtain skills.Details; a missing name wraps
skills.ErrNotFound. An ambiguous simple name prints an error to stderr listing
all matching canonical names, asks the caller to specify a canonical name, and
exits nonzero without JSON on stdout. Programmatic callers can use errors.As
to inspect *skills.AmbiguousNameError, whose Name is the requested name and
CanonicalNames contains all candidates in canonical-name order. There is no
ambiguity sentinel.
At the persistence boundary, Store.FindByName(ctx, name) returns all exact
canonical or simple-name matches as []skills.Skill, ordered by canonical name
then base directory. No matches returns an empty slice. Manager.FindByName applies
canonical precedence and turns the match count into a result, not-found error,
or ambiguity error.
Cross-host collisions and duplicate skill names within a repository remain deferred. When canonical names themselves collide, canonical lookup temporarily selects the first match ordered by base directory. Retrieval combines indexed content with the current file list; reconciliation after external file changes also remains deferred.