No description
  • Go 99.5%
  • Just 0.5%
Find a file
2026-11-05 23:47:10 +08:00
cmd/cli fix: retry process lock acquisition on contention 2026-11-05 23:47:10 +08:00
docs fix: retry process lock acquisition on contention 2026-11-05 23:47:10 +08:00
internal fix: retry process lock acquisition on contention 2026-11-05 23:47:10 +08:00
.env.example chore: load development environment from .env 2026-11-05 20:54:44 +08:00
.gitignore feat: add .test-config/ directory to gitignore 2026-11-05 21:06:01 +08:00
AGENTS.md feat: install and index agent skills with ranked CLI search 2026-11-05 20:48:34 +08:00
doc.go chore: initialize Go module and repository guidance 2026-11-05 16:54:48 +08:00
go.mod refactor: Use kong for CLI library instead of manual parsing 2026-11-05 21:36:14 +08:00
go.sum refactor: Use kong for CLI library instead of manual parsing 2026-11-05 21:36:14 +08:00
Justfile chore: add recipe to print loaded dotenv variables 2026-11-05 21:04:38 +08:00
README.md fix: retry process lock acquisition on contention 2026-11-05 23:47:10 +08:00
TODOS.md feat: serialize CLI access with a process lock 2026-11-05 23:29:54 +08:00

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, using modernc.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.

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.