Skip to content

@-mentions

Type @ in the composer to pull a file's contents into your message. kin reads the file at send time and includes it inline, so the model sees exactly what's on disk. See Keybindings & cursor mode for every other composer key, and Slash commands for /tree and /view, the tree-sidebar and read-only-peek commands mentioned below.

Using @ in the composer

Type @ (at the start of the message or after whitespace) and a file picker opens above the composer. Keep typing to filter; the menu shows workspace-relative paths ranked by how well they match.

Key Action
Up / Down Move the highlight
Tab or Enter Insert the highlighted path as @path
Esc Close the picker

The picker only inserts the path — it never previews file contents, so a stale preview can't mislead you. You can also drop a mention in by hand, and you can pin a line range:

@src/kin/harness/loop.py:40-80

@path:lo-hi attaches just those lines; @path:lo attaches one line. A mention inside backticks (`@README`) stays literal, and an address-style name@host never triggers because @ only fires at the start of the message or after whitespace.

Paths with spaces (every macOS screenshot — Screenshot 2026-07-10 at 3.14.15 PM.png) have to be wrapped in double quotes:

@"Screenshot 2026-07-10 at 3.14.15 PM.png"
@"my code/my file.py":1-40

The picker is suppressed inside a quoted path (the token round-trips through mention_token verbatim, so no fuzzy matching helps); the file tree (Ctrl+G) inserts the quoted form for you.

Pasting files (drag-drop + terminal paste)

Dropping files onto the composer (Finder drag-drop, terminal paste of absolute paths) does the same thing as the picker — every path becomes an @-mention inline. The interception lives in Composer._on_paste and recognizes three shapes:

  • Newline-joined (iTerm shape): one path per line, all must exist.
  • Shell-escaped (Ghostty shape): backslash-escapes around spaces, or double-quoted segments. shlex.split(posix=True) recovers the original.
  • Bare spaced path: a single macOS-screenshot name with U+202F narrow no-break spaces — pasted as-is, the whole thing is treated as one candidate token.

The discriminator is "the ENTIRE paste is a drop". Any prose-shaped input — relative tokens, a missing file, mixed token types, a paste longer than 4096 chars — falls through verbatim. So a sentence like please look at /Users/you/notes.md when reviewing is untouched, and a paste of five files plus a typo'd sixth is the same as a plain paste (the typo'd path fails the os.path.exists check; nothing attaches).

After attachment you get one toast: attached N file(s) as @-mentions, with · K already attached appended when some tokens already existed in the composer. Files >5 MB get downscaled via the macOS sips builtin (-Z 1568 — matches the harness's ImageBlock max-edge budget) before attachment, with a downscaled shot.png 8.2 MB → 1.1 MB toast on success; a sips failure or non-macOS host falls back to mentioning the original. >10 files in one drop warns and inserts the paths verbatim — a visible choice beats a silent partial attach. A path that matches is_secret_path (the credential glob) still inserts (Rock 1 makes the mention inert visibly) but pops a warning toast.

Turn the whole thing off via KIN_PASTE_MENTIONS=0 or paste_file_mentions = false in ~/.kin/settings.toml (project files inherit). The knob is PROJECT-SAFE (no egress / secret / containment surface — identical to a manual paste) and human-only (NOT model-writable). See Environment variables and Settings (TOML) for the full knob reference.

Capturing a clipboard image (macOS)

Ctrl+V captures whatever image is on the macOS clipboard and attaches it as an @-mention. Behind the scenes kin.tui.clipboard runs pngpaste if installed (the brew-once path) and falls back to osascript's «class PNGf» otherwise — both are subprocess-argv, no shell, no new dependency. The image is written to <project_dir>/<session_id>.attachments/clip-<hhmmss>-<rand4>.png (directory 0700, file 0600 — a clipboard image can carry anything, so the attachment dir is user-only). Oversize captures (>5 MB) get the same sips downscale as a drag-drop (the -Z 1568 ceiling matches the harness's ImageBlock max-edge budget); the downscale helper reuses paste_paths.run_sips_downscale, so the failure mode (mention-the-original + Rock 2's oversize dropped-note + the sips hint) is identical.

The filename is harness-generated (timestamp + 4 random hex chars), so no user text enters the path — display-sink discipline is preserved (no control / bidi / zero-width vectors on the path itself). The attachments live beside the journal (<project_dir>/<session_id>.attachments/) so resume-durability and the secret-path checks treat them like the journal: same dir family, same containment story. A best-effort >30-day sweep runs at app mount; a vanished attachment degrades to read_file's graceful not-found. If the clipboard has no image, you get an info toast: no image on the clipboard — text paste is unaffected (text paste is unaffected because bracketed-paste and ctrl+v are separate event paths). On non-macOS hosts the action toasts clipboard image capture is macOS-only for now (wl-paste / xclip are a planned follow-up DR).

Tip

Picking a file in the toggleable file-tree sidebar (Ctrl+G or /tree) inserts it as an @-mention — press V on the focused file to peek it in the read-only viewer instead (also /view <path>).

What gets expanded

When you send the message, each @path token is replaced with the file's contents, read with the same logic as the read_file tool — line numbers, PDF and Word/Excel (.docx/.xlsx, macros in .docm/.xlsm ignored) text extraction, and the 50 KB cap all apply. The path text you typed is kept as a label, and the contents follow it inline. See Tools for how read_file renders.

A mention that can't be read never blocks the send: the token is left in place with an inline (not attached: …) note (file not found, is a directory, binary, or a read timeout), and the rest of the message goes through unchanged.

Names in kin's read-deny list (is_secret_path) are refused before the existence probe — @~/.ssh/id_rsa and a project-local @.env both fail with a refused — known credential store note and never inline contents.

Image attachments use a by-reference marker: an @image.png mention inside the 5 MB cap becomes a harness-authored text marker (path + MIME + size + "View it by calling read_file with this exact path") rather than inline bytes. A <system-reminder> on that turn instructs the model to call read_file on each attached path before responding (and to batch the calls in a single round). The bytes only enter the conversation through read_file's audited vision path, so the marker travels cleanly across provider swaps, compaction, and history re-renders — and an oversize drop comes with a downscale hint instead of a silent cap. Notebook mentions remain text-only — call read_notebook for structured cell access.

Ranking & filtering

The picker ranks candidates with fuzzy matching over the full relative path (not just the basename), so helper finds src/utils/helper.py. When the optional rapidfuzz dependency is present it uses a weighted-ratio scorer; otherwise it falls back to a stdlib substring match. An empty query shows the head of the corpus.

The corpus is the set of files under your workspace. It honors .gitignore (via the optional pathspec dependency) and always prunes heavy or noise directories (.git, node_modules, __pycache__, .venv, dist, build, and similar), capped at 10,000 files. It's rebuilt on a 60-second TTL and the walk runs off the UI thread, so a large repo can't freeze the picker on the first keystroke.

Out-of-workspace files

A file inside your workspace is treated as ground-truth context and inlined directly. A file outside the workspace carries the same prompt-injection surface as a fetched web page, so its contents are wrapped in hardened untrusted-content framing before they enter the conversation. Symlinks are resolved before the membership check, so an in-workspace symlink that points outside still gets the untrusted framing. The model is told to treat that content as data, not instructions.