acquaint
People, and what they are involved in, for AI agents: who someone is, how to reach them, how to read them, how to write to them.
pip install acquaint
acquaint new person "Ada Lovelace"
acquaint remember ada-lovelace "prefers email for anything with attachments" --source "https://example.org/thread/1"
acquaint who ada -f aka # → Ada, Lovelace
acquaint brief ada-lovelace --purpose ask # everything to know before writing to her
Tell an agent "write to Ada about the export" and it can find out, without being told again, who that is, where to reach her for this, what register to use, what to avoid, and where to record what it learns. The "how" is written down once, per person, and every agent, skill and tool reads it through the same verbs.
Records are hand-editable Markdown, one folder per person (or project, org, group), kept outside any code repository. Every preference carries its source, and acquaint lint fails when one does not.
What it records, and what it never does
A profile holds observable behaviour with evidence: "replies in one or two lines", "wants the decision first", quoted and linked. It never holds personality labels, moods, or special-category data (health, religion, politics, ethnicity, sexuality, union membership), stated or inferred, nor credentials, identifiers, or whole message bodies. Every store gets a POLICY.md stating this, and lint warns on tripwires.
The test for every line: would it survive being handed to the person it is about?
Where the data lives
The data root is the first of: the data_dir argument, $ACQUAINT_DATA_DIR, data_dir in ~/.config/acquaint/config.toml, ~/.local/share/acquaint.
POLICY.md what may be recorded
people/ada-lovelace/
PROFILE.md entry file: identity frontmatter + Who · Reach · Write to them · Read them · Don't · Now · More
identities.yaml handles and addresses, with evidence
rules.yaml channel rules: when → do (channel, and the address
when the channel is addressed by conversation), who set it, source
links.yaml affiliations: project, org, group; role; period
style.md the writing card: AI tolerance, register, do, don't, blocklist, exemplars
views.md positions and standing objections, sourced
sources.md where their writing lives, how authorship was verified
log/2026-09.md append-only observations
projects/<id>/PROFILE.md What & status · Where things live · Who · Agents & skills · Norms · Now
orgs/<id>/ groups/<id>/ … any other kind
Only PROFILE.md is required. A malformed file is reported and skipped; it never breaks lookups of anyone else.
The verbs
The same seventeen functions are the Python API (acquaint.tools), the CLI, and the MCP tools. Each returns a JSON-ready dict.
| Verb | Does |
|---|---|
who NAME [-f FIELD] [-b] |
one field (email, aka, label, tier, any frontmatter key), the identity block, or the whole entry file; lists candidates instead of guessing |
resolve HANDLE |
github:octocat, email:… → the person, with the evidence; a handle without its platform, a match by name only, or an inactive identity is reported, never acted on |
check TEXT |
before publishing: one person written as two ("Ada or Lovelace"), shared names, unknown names |
reach PERSON [--purpose --urgency --project --message-type --topic] |
ordered channels: the person's own rules > the operator's rules > project norms > observed habits > defaults |
brief PERSON [--purpose --project --ref CONVERSATION | --audience-json - | FILE] |
card, writing style, views, reach, project norms, recent observations, reminders, and what is not known; with --ref (correspond is asked who can read it; unknown is public) or an audience record, it opens with the ceiling, the records and terms not to identify, how many lines were withheld (never their text), and what they were already told |
remember ENTITY TEXT [--source --kind --disclosed --reactivate] |
append a dated, sourced observation (or identity, preference, view, rule); an interaction may list the records a message identified (--disclosed project:heron); an identity equal to an inactive one is refused, naming that entry, unless --reactivate |
disclosure ID... [--project SLUG] [--audience-json - | FILE] |
who may be told what: the tier and clearance in force for each reader, the least clearance, the seals, the terms a gate must scan for, what each was already told, and the gaps |
review PERSON |
everything that names someone's disclosure standing, with sources and dates: tier entries, links, default tiers and clearances reached through links, seals, rules; exits 1 when an affiliation ended while a permissive tier is still in force. Run it when a collaboration ends, when someone changes role, or before changing a tier |
lint [ENTITY] |
sources on every preference; tiers, labels and seals set by the operator, with known values and review dates; parseable files; entry-file budget; policy tripwires |
style-lint TEXT [--recipient --tolerance] |
machine-writing tells, enforced by the reader's tolerance of AI-sounding text |
new KIND NAME [--qualifier --description] |
scaffold from a template; readable slug ids (ada-lovelace, john-smith--example-org) |
rename ID TO |
new id or name; links elsewhere rewritten (never inside URLs or logs); old forms kept as aliases |
forget ID [--confirm] |
remove the whole folder, leaving a salted tombstone so the person is not silently re-created |
sync init --repo OWNER/NAME · sync push · sync pull · sync status |
private-repository sync, below |
--json prints the result dict; - as the text of check or style-lint reads stdin.
Exit status: 0 on success; 1 when the result is not ok (the reason is on stderr); 2 for a usage error; 3 when reach matched a rule but no usable address is recorded for its channel (the rule is still listed, and the reason says what to add).
Nothing acts on a guess: a name, alias, handle or email counts only when exactly one record has it exactly, and a partial match comes back as a suggestion. rename and forget need the exact id.
Sources
End every preference, view or rule with a source tag:
## Write to them
- Lead with the decision, then the options. [source: self: "give me the recommendation first"]
- Short replies on chat, fuller ones by email. [source: log/2026-09.md#e03]
- No attachments over chat. [source: https://example.org/thread/1]
A source is a permalink, a log or research anchor, the person's own words, operator, or none located. A date alone is not a source, and neither is a placeholder such as unknown or TODO: an unsourced claim with a date attached reads as observed when it was not. A nested bullet is its own line and needs its own source.
Agent skills
Six skills ship inside the package (acquaint/data/skills/) and install with gh skill:
| Skill | For |
|---|---|
acquaint |
the router: lookups at the right cost, check before publishing, remember with sources |
acquaint-profile |
building a profile from someone's own writing, with parallel profile-reader agents |
acquaint-write |
writing for a known reader, and sparring with a simulated one (recipient-reader agent) |
acquaint-read |
interpreting a message from a known person; AI-processing as a likelihood with evidence |
deslop |
prose without machine-writing tells, calibrated to the reader |
acquaint-sync |
private sync, and what it does not protect |
gh skill install thorwhalen/acquaint acquaint --agent claude-code
gh skill needs a recent gh; otherwise symlink the folders from the installed package's acquaint/data/skills/ into ~/.claude/skills/. If you already have a user-level skill named deslop, skip acquaint's or install it under another name: two skills with the same name at the same scope replace each other.
MCP
pip install "acquaint[mcp]"
{"mcpServers": {"acquaint": {"command": "acquaint-mcp"}}}
The server exposes the tools that read locally, append or create (who, resolve, check, reach, brief, remember, lint, new, style_lint, disclosure). Renaming, forgetting and syncing stay at the terminal. data_dir is not exposed: the data root is the server's (set ACQUAINT_DATA_DIR in the client configuration), never the model's. Neither is remember's reactivate: making an inactive identity active again is the operator's call. Nor is disclosure's today, which could revive a lapsed tier.
Private sync
acquaint sync init --repo <owner>/<name> --dry-run
acquaint sync init --repo <owner>/<name>
sync init creates the repository through gh as private, refuses to continue unless gh reports it PRIVATE, and installs a pre-push hook, active in every worktree, that allows a push only through origin, only when origin has exactly the one URL recorded at init (no pushurl or pushInsteadOf redirect), only when that URL names the checked repository on github.com, and only while gh still reports it private. push, pull and status check again. sync init takes over only an empty folder or a clone of that same repository.
What this does not protect: a private repository is access control, not encryption (the host can read everything); git push --no-verify skips the hook; whoever controls the repository's git config or the gh on PATH controls what the guard sees; moving the data root disables the hook until sync init --existing-only runs again; file names and commit messages contain people's names; deleting a folder does not remove it from history or other clones. git-remote-gcrypt would encrypt contents, names and history; it is the planned upgrade, but the guard does not support it yet, so sync init refuses gcrypt:: URLs (#13).
Python
from acquaint import Store, who, brief
who("ada", field="aka")["value"] # ['Ada', 'Lovelace']
print(brief("ada-lovelace", purpose="ask")["text"])
store = Store() # MutableMapping[str, Entity] over a dol files store
entity = store[
"people/ada-lovelace"
] # a mapping of that person's files, plus parsed views
entity.identities, entity.rules, entity.sections["Write to them"]
Store(files={}) # any MutableMapping[str, str] of files: a dict, a remote store
Design
The seams, surfaces and deliberate non-seams are in Discussion #1.
Metadata
Release files for acquaint 0.0.10
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| acquaint-0.0.10.tar.gz | 150.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| acquaint-0.0.10-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 278.9 kB
Release files / acquaint-0.0.10.tar.gz
| Download URL | acquaint-0.0.10.tar.gz |
|---|---|
| Size | 150.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ac6bd4409b9290e1a53832d0172ec454ba355f89aa2e47ca79452a4b2cf0b22
|
|
BLAKE2b-256 checksum How to use checksums |
54f6e4c7d221a2a622b5c9bda3817c011dd3d5eae13d5cf7b9667405515454d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / acquaint-0.0.10-py3-none-any.whl
| Download URL | acquaint-0.0.10-py3-none-any.whl |
|---|---|
| Size | 128.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e343dbf37498ca7d82b6a368a3d4c628ff87b94c6f49a411881878cdd5c64a0e
|
|
BLAKE2b-256 checksum How to use checksums |
934093832d50a66ffb90d87cebec0ac7a52691e906398566740fc2427e6758b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|