Nathan Aday38 downloadsWork with Claude Code or Codex in your notes. Mention an agent on a line, or chat in a conversation note. You see the agent edit the note live while you type.
Work with Claude Code or Codex inside your Obsidian notes. You and the agent edit the same note at the same time. You see the agent's cursor and each change it makes, and your own typing is never lost.

Coding agents such as Claude Code and Codex are good at more than code. They can read your notes, summarize them, restructure them, and write new ones. But they run in a terminal, and a terminal is a poor place to work with notes.
Duet puts these agents in the editor. Write @claude and a request on any line, and the agent answers under that line. It can also edit the note while you keep typing, like a second person in a shared document. For longer work, open a conversation note: the conversation becomes a note in your vault, with every message and tool call recorded.
Duet uses your own Claude Code or Codex installation and login. It does not need an API key of its own.
claude) or the Codex CLI (codex), installed and logged in. Check this in a terminal: claude --version or codex --version.To install by hand, download main.js, manifest.json and styles.css from the latest release. Put them in <your vault>/.obsidian/plugins/duet/, then turn on Duet under Settings → Community plugins.
@claude Summarize this note in one sentence.The answer streams into a callout under the line. For Codex, write @codex instead.
Write @claude or @codex and a request, then press Enter at the end of the line. The tag can follow other text on the line. When you type the tag of an agent, it changes to a pill in the agent's color, so you know that the tag will work.

@claude make the list shorter. The agent changes this note only. It can read your other notes, but it cannot change them or run commands, so a mention never asks for approval.@claude make it shorter in the same note continues it, also after you restart Obsidian.Duet ignores tags in code blocks, in blockquotes and in email addresses. These tags stay plain text.
Run Duet: New conversation, or select the ribbon button. You get a note with a message box at the bottom. The note records the conversation: your messages, the agent's replies, and its tool calls in collapsed sections. The note gets its name from your first message.

[[ to pick a note. The agent reads linked notes when it needs them./ to see commands. /model, /effort, /mode, /new and /end belong to Duet. The other commands come from the agent, for example your Claude Code skills./end, or run Duet: End this conversation. The note stays as a record. Select Continue it to reopen the conversation.In a conversation, the agent can change any file in the vault. Before it changes a file or runs a command, the request shows above the message box with the exact change. Press Y to allow it once, A to allow it for the rest of the conversation, or N to deny it.

When Claude has questions for you, they show above the message box, one at a time. Pick an option with a click or its number key, or write your own answer and press Enter. A pick on a single-choice question moves to the next question. Select Back to change an answer, or Skip to answer nothing. The note records the questions and your answers.
When the agent changes a note that is open, you see its cursor in that note. Its changes merge with the text that you type at the same time.

Duet records which text of a note its agents wrote. To see it, select the lens button in the ribbon, or run Duet: Toggle contribution lens. Text that a Duet agent wrote gets a cyan mark. Hold the pointer over a mark to see which agent wrote it and when. All other text stays plain.

Duet keeps the record in the hidden folder .duet/contributions/ in your vault, with one file for each note that an agent wrote in.
Each agent has a tag and its own options. You can add more agents, for example a second Claude account.
| Setting | What it does |
|---|---|
| Tag | The name after @. |
| Color | The color of the agent's cursor and highlights. |
| Agent | Claude Code or Codex. |
| Approvals | When the agent asks before it acts, in conversation notes. |
| Model | The model to use. Leave empty for the default. |
| Effort | How much the model reasons. Leave empty for the default. |
| Program path | Leave empty to find the program on your shell's PATH. |
| Environment variables | One KEY=VALUE per line. |
| Load my MCP servers and plugins | Claude Code only. Off by default. |
General settings:
| Setting | What it does |
|---|---|
| Conversation folder | Where new conversation notes go. The default is Conversations. |
| Show agents typing | Agents type their edits into open notes. Off: edits appear at once. Duet also turns this off when your system asks for reduced motion. |
| Close idle agents after | Minutes without activity before an agent process stops. The next message continues the same conversation. |
To use a second Claude account, add an agent with the tag work, the agent Claude Code, and the environment variable CLAUDE_CONFIG_DIR=~/.claude-work.
Another Obsidian plugin can start a Duet conversation and follow it. For the types, copy plugin/src/api.ts into your plugin. It imports nothing.
import type { DuetApi } from './duet-api';
const duet = (this.app as any).plugins.getPlugin('duet')?.api as DuetApi | undefined;
if (duet && duet.version >= 1) {
const { path } = await duet.newConversation({ message: '/my-plugin:my-skill', loadUserSetup: true });
const stop = duet.onTurnEnd(path, (turn) => console.log(turn.status));
}
| Member | What it does |
|---|---|
version |
1. A later version only adds members. |
newConversation(options) |
Creates a conversation note, opens it in a new tab, and sends options.message as the first message. Resolves with { path } when the message is sent. |
conversationStatus(path) |
working, active (waits for a message), ended, or none (no conversation note has this path). |
onTurnEnd(path, callback) |
Calls callback with { path, status, error? } after each turn. status is completed, interrupted or failed. Returns a function that stops the calls. |
Options of newConversation:
| Option | Default | What it does |
|---|---|---|
message |
(required) | The first message. A / command runs an agent command, such as a Claude Code skill. |
profile |
the first agent | The tag of a Duet agent, without @. |
title |
the date and the first words of the message | The name of the note. |
folder |
the conversation folder setting | The folder of the note. |
open |
true |
false creates the note without opening it. |
loadUserSetup |
false |
Claude Code only. Starts the agent with the user's MCP servers and plugins, also when the agent's setting is off. The note keeps this choice. |
Limits:
api is undefined when Duet is not installed or is turned off. Get it each time you need it. After Duet turns off, newConversation on an old api rejects.newConversation rejects when the message is empty or when no agent has the tag in profile.failed.conversationStatus takes the current path.Duet does not connect to the internet. It starts Claude Code or Codex on your computer, and those programs connect to their providers:
.duet/contributions/. Each file holds the positions of the agent's text and a copy of the note's text. Duet uses the copy to follow changes that it did not see.claude or codex, and your login shell, once after Obsidian starts, to read its PATH. Claude Code and Codex save their conversation history in their own folders, such as ~/.claude and ~/.codex. The agents work in the vault folder. Their own permission rules control access to other files, and Duet shows you each approval request that they send.npm install
npm run plugin:dev # creates dev-vault/ and rebuilds the plugin into it on every change
Open dev-vault/ as a vault in Obsidian. Other commands:
| Command | What it does |
|---|---|
npm run plugin:build |
Builds the plugin into plugin/dist. |
npm run typecheck |
Type-checks the library and the plugin. |
npm run lint |
Runs Obsidian's review rules on the plugin code. |
npm test |
Runs the unit tests. |
npm run test:obsidian |
Starts a separate Obsidian with a temporary vault and tests mentions and conversations with Claude Code and Codex. Your own Obsidian and vaults are not touched. |
npm run screenshots |
Captures the images in this README with real agent sessions. |
Work happens on the preview branch. main takes changes only through a pull request from preview whose checks pass. To release a version:
preview, run npm run set-version <x.y.z>. It sets the version in manifest.json, package.json and package-lock.json, and adds it to versions.json with the current minAppVersion. A unit test holds the four files to one version.main. CI runs the type check, the lint, the unit tests and the build. Run npm run test:obsidian before you merge, because CI has no Obsidian to run it in.v), and publishes main.js, manifest.json and styles.css, which Obsidian's community plugins download. A merge whose version is released already publishes nothing.Obsidian reads manifest.json from main. Until the release job ends, about a minute, main names a version with no release, and an install in that minute fails once.
The plugin is built on a small TypeScript library in src/ that starts and drives Claude Code and Codex sessions. Other apps can use it as a package; duet-demo is a browser app built on it. The design notes are in docs/design.md.