Most people who study seriously already keep their thinking somewhere: an Obsidian vault, a visual planner, a notebook. StudyLoop can publish what it knows — your plan, today's next action, what is due for review — into that place, so you are not holding two half-pictures in your head.
This is off by default and stays completely silent until you choose a provider.
With no second_brain: section, StudyLoop imports no code for it, writes no files,
and never mentions it.
StudyLoop publishes projections, not a synchronised copy.
Your plan lives as a Markdown document that StudyLoop owns and you can read
(studyloop plan path). That document is the single source of truth. A
projection is a rendering of it into your second brain — regenerated whenever you
publish, and never read back. Nothing in a backend, the CLI, or an agent protocol
writes to your plan file.
flowchart LR
P["Plan Markdown<br/>(source of truth)"] --> R[Render projection]
R --> V["Your vault<br/>Study/Plans/<plan-id>.md"]
N["Study/Plans/<plan-id>.notes.md<br/>(yours)"] -. "only on studyloop brain pull" .-> A[You and your mentor]
A --> P
The dashed line is the only path back, it is manual, and it stops at you — not at
the plan file. When you ask for your notes, StudyLoop hands them over; you and your
mentor decide what belongs in the plan and change it with studyloop plan ….
The reasoning behind this is recorded in ADR-0010, and the full contract with the
test that proves each clause is in the repository at
docs/architecture/second-brain.md.
| Provider | Cost | Where your study text goes | Publishing |
|---|---|---|---|
none (default) |
— | nowhere | — |
obsidian |
free, local | files in your own vault, on your own disk | studyloop brain publish |
xtiles |
free plan connects; editing an existing project is paid | your assistant's model service, then xTiles' cloud | your assistant, on request |
Obsidian is the free, local option and it is complete: nothing in StudyLoop needs a paid service. xTiles is there because some learners already live in it, and it is served through your assistant rather than by StudyLoop itself.
| Provider | What leaves | To whom |
|---|---|---|
none |
nothing | — |
obsidian |
nothing. StudyLoop writes files into a folder on your disk. | nobody |
xtiles |
the plan text, today's action, due reviews, the session's learning record — only when you run one of the prompts | your assistant's model service, and xTiles' cloud |
Two honest caveats. For Obsidian, "nothing leaves" is a statement about StudyLoop: if you use Obsidian Sync, or a plugin that phones home, that is your vault's arrangement and not something StudyLoop can speak for. For xTiles, your sign-in lives in your assistant's own configuration — StudyLoop stores no credential for this feature, and there is nothing for it to leak.
What you actually do, once. Steps 4 and 5 are the loop you repeat; everything above them happens on the first day only.
flowchart TD
S1["1. studyloop brain enable obsidian --vault ~/MyVault"] --> S2["2. studyloop brain publish --dry-run<br/><i>lists what it would write, writes nothing</i>"]
S2 --> S3["3. studyloop brain publish<br/><i>today's note + every active plan</i>"]
S3 --> S4["4. Read it in Obsidian<br/>Study/Today.md · Study/Plans/<plan-id>.md"]
S4 --> S5["5. Think, and write in your OWN file<br/>Study/Plans/<plan-id>.notes.md"]
S5 --> S6["6. studyloop brain pull <plan-id><br/><i>hands your notes back to you</i>"]
S6 --> S7["7. You decide what belongs in the plan<br/>studyloop plan milestone / status,<br/>or edit the plan document itself"]
S7 --> S3
S3 -.->|"a projection you hand-edited"| W["Replaced on the next publish,<br/>with a warning naming the file"]
S3 -.->|"a file StudyLoop does not own"| R["Refused. Publish stops and says which file."]
classDef you fill:#eceaff,stroke:#4c3fbb,color:#231d63
classDef danger fill:#fff1dc,stroke:#a9741a,color:#673e05
class S4,S5,S7 you
class W,R danger
The purple steps are the ones only you can do. Nothing on this diagram sends anything anywhere: every arrow is a file on your own disk.
Everything lives under one folder you name (Study by default):
Study/
├── Today.md StudyLoop's, replaced each publish
├── Plans/
│ ├── python-decorators.md StudyLoop's, regenerated
│ └── python-decorators.notes.md yours; StudyLoop only ever reads it
└── Learning Records/
└── python-decorators/
└── LR-0001.md StudyLoop's, regenerated
Get started:
studyloop brain enable obsidian --vault ~/Obsidian/Personal
studyloop brain publishEvery note StudyLoop writes carries a marker in its frontmatter:
studyloop:
owned: true
schema: 1
kind: plan-projection
plan_id: python-decorators
content_hash: 9f2c…That marker is the whole safety mechanism, and it works in the direction that matters: StudyLoop never overwrites a note it does not own. It replaces a file only when the marker is there and names that same projection. A note you wrote by hand, a note made from a template, a note whose frontmatter cannot be parsed — all refused, by name:
Refusing to overwrite 'Study/Plans/python-decorators.md': it is not marked as
StudyLoop-owned. Move or rename that note, then retry.
Every write also has to resolve inside the vault you named. An absolute folder, a
.., or a symlinked Study directory pointing elsewhere is refused before
anything is written.
Editing a projection works, but your edit is replaced on the next publish. The
note is a rendering of your plan; there is nowhere in the plan for text you typed
into the vault to live. Put your own thinking in the sibling .notes.md instead —
StudyLoop never writes that file.
Renaming a projection makes it yours. StudyLoop will not touch a file it cannot find, and the next publish recreates the canonical one under the original name.
Republishing an unchanged plan does nothing at all: no write, no changed timestamp, nothing for a sync client to propagate.
studyloop brain pull python-decoratorsThat reads Study/Plans/python-decorators.notes.md and prints it. It never creates
that file, never changes it, and reports plainly when there is nothing there yet —
which is a normal state, not an error.
StudyLoop ships note templates that mirror the plan document's own sections, so a plan you write by hand in your vault and one StudyLoop published look the same:
studyloop brain template # list them
studyloop brain template --print "Study Plan.md"
studyloop brain template --install # copy into your templates folder--install copies into <your templates folder>/StudyLoop/ and refuses to
overwrite anything already there. The templates deliberately carry no ownership
marker, which is what makes a note you create from one permanently yours.
There is also an optional Dataview query page listing your published plans. It needs the community Dataview plugin; without it the page is harmless plain text.
Obsidian ships an official CLI, and an adapter for it was written for this release and then withdrawn before it shipped. Two reasons, both found in review:
- It sent notes to whichever vault the running desktop app answered for, and there was no way to tie that vault to the one you configured. With more than one vault open, a publish could put your plan in the wrong one.
- It passed the whole plan text as a command-line argument, where any other user on the machine could read it out of the process table.
Writing files directly is what this feature always needed — the adapter only added the chance for your own Obsidian template to fire on a note StudyLoop then overwrote anyway. Nothing here runs an external program, and a test asserts that.
If you had use_cli, vault_name, template or daily_note in your config from a
pre-release build, StudyLoop now tells you they are gone rather than ignoring them —
daily_note in particular wrote into your own daily note, and you should know it no
longer does.
If you also use the session-memory export, your vault has two StudyLoop folders and they are for different things. See Obsidian Export for the comparison.
Notes are written with a temporary file and an atomic rename, which is safe on
Windows too, but a file locked by another program cannot be replaced — StudyLoop
reports that and leaves the existing note intact. Under WSL, point vault_path at
the path the vault has from the side StudyLoop runs on.
These images render the exact Markdown StudyLoop wrote into a hermetic throwaway vault. They are deliberately labelled not Obsidian inside the pixels: they show the projection files, not an imitation of Obsidian's desktop interface.
Three images are direct live-interface captures using synthetic probe data. The planner card is a documentation rendering of content read back from the live tile because the browser-extension capture clipped xTiles focus mode; the image labels that distinction inside its pixels. The brain-status image is a sanitized rendering of real CLI output, not xTiles UI. The project and planner tile were deleted after capture.
xTiles is a visual planner — projects, pages, tiles, a daily planner — and it is the one provider StudyLoop does not write to itself. The pattern is two MCP servers and one assistant: you connect StudyLoop's MCP server and xTiles' hosted connector to the same assistant, then ask it to move today's study into your planner. Obsidian remains the free, local option, and nothing in StudyLoop needs xTiles.
The shape to hold on to: StudyLoop never talks to xTiles. It answers your assistant's questions, and your assistant is the only thing holding both connections. That is why the dashed boundary below matters more than any of the steps — it is the line your study text crosses, and it only ever crosses because you ran a prompt.
flowchart TD
subgraph LOCAL["Your machine — configuration only"]
direction TB
SL["StudyLoop<br/><i>plans, next action, due reviews</i>"]
CFG["studyloop brain enable xtiles<br/><i>records the choice; stores no credential</i>"]
ASSIST["Your assistant<br/><i>Claude Code, Kiro, Codex …</i>"]
SKILL["studyloop install agents<br/><i>wind-down skill, silent unless both gates pass</i>"]
CFG --- SL
SKILL --- ASSIST
SL <-->|"StudyLoop MCP server<br/>get_next_action · get_due_cards"| ASSIST
end
SETUP(["claude mcp add … then sign in"]) --> ASSIST
ASSIST -.->|"setup traffic: attaching the connector<br/>and authorising it in a browser"| XT
YOU(["You run one of the three prompts"]) --> ASSIST
ASSIST ==>|"STUDY CONTENT<br/>whatever that prompt includes<br/>or fetches for itself"| MODEL["Your assistant's model service<br/><i>Anthropic, for Claude Code</i>"]
ASSIST ==>|"STUDY CONTENT<br/>through the xTiles connector"| XT["xTiles' cloud<br/><i>project · pages · planner task</i>"]
XT --> READ(["You open xTiles and see it"])
classDef local fill:#eceaff,stroke:#4c3fbb,color:#231d63
classDef remote fill:#fff1dc,stroke:#a9741a,color:#673e05
classDef human fill:#e4f7ee,stroke:#1c7a52,color:#0f4530
class SL,CFG,ASSIST,SKILL local
class MODEL,XT remote
class YOU,SETUP,READ human
Read the arrow weights. Thick arrows carry your study content, and nothing crosses them until you run a prompt. The dashed arrow is setup: attaching the connector and authorising it in a browser is itself traffic to xTiles, so the box is labelled configuration only rather than pretending the network is untouched until later — the honest claim is about study content, not about packets.
Which content crosses depends on the prompt: the planner prompt sends today's
action and your due reviews, which it fetches through StudyLoop's MCP server; the
project prompt sends the plan Markdown you pasted in. StudyLoop is on neither
thick arrow — it cannot see what was sent and cannot verify what arrived. That is
also why removing the xtiles server from your assistant's own MCP settings is
how you end this, not a StudyLoop command.
An xTiles account, an assistant that supports remote MCP servers, and StudyLoop's MCP server. xTiles states that MCP works on every plan, Free included, with some limitations there. What its pricing page gates is editing, not connecting: creating projects, pages and tiles is a Free feature, editing an existing project in a personal space starts at Plus, and editing in a shared space at Pro. That distinction decides which of the prompts below works for you, so it is named against each one.
One URL, one authorisation:
claude mcp add --transport http xtiles https://mcp.xtiles.app/mcpThen run /mcp inside Claude Code and sign in to xTiles. There is no API key to
copy or store, and xTiles also publishes a connector in Claude's Connectors
Directory if you would rather not add the URL by hand. Tool names appear only
after you have signed in.
This is written for Claude Code. Exercised end to end by a person on 2026-09-04
with an xTiles Plus account and Kiro CLI 2.21.0 — not yet run in Claude Code. The
planner and wind-down prompts wrote what they describe; the project prompt created
the project but not the board or the visible page structure it promises (see that
prompt's section). StudyLoop installs the wind-down skill into every harness it
detects: installation paths are verified against each vendor's documentation, but
whether Kiro, Codex, OpenCode, pi or Grok Build can complete xTiles' browser
authorisation is not something this page has verified — the skill stays silent
unless an xtiles server is actually connected, so an untested harness costs you
nothing.
Two opt-in checks exist, and neither runs in a normal test run:
just live-obsidian # a throwaway vault: publish, read back, remove
just xtiles-auth # once, in a real window
just live-xtiles "<url>" "<title prefix>" # is it actually visible in xTiles?The xTiles one answers the question its connector cannot answer about itself: whether what your assistant wrote is visible in the interface. A write can succeed at the API and still render nowhere, and the interface is where you live. Of the three prompts below, only the planner one returns a URL to check — a planner tile is the one shape this live check has actually validated.
It needs a session captured in a real browser window rather than a stored
password, because xtiles.app sign-in is behind reCAPTCHA and fails silently
under automation. Every assertion is scoped to a title prefix you supply, and a
missing or short prefix is refused rather than skipped — an unscoped check would
match your own content.
When you run one of these prompts, the plan text, today's next action, the due
reviews and the session's learning record go to your assistant's model service —
Anthropic, for Claude Code — and through the connector to xTiles' cloud. The next
action and the due reviews are your whole study state, not the named plan's:
get_next_action and get_due_cards are not plan-scoped, so the title and reason
sent to xTiles come from your entire study history even when the prompt names one
plan. Nothing is sent unless you run a prompt. xTiles states that its connector
asks permission per request, that your assistant only gets what your own account
can already see, and that nothing is shared with other users — what you are
actually shown per request depends on your assistant, and in a recorded Kiro CLI
run no per-write prompt surfaced. Your sign-in lives in your assistant's own MCP
settings rather than in StudyLoop, and that is also where you end it: remove the
xtiles server there.
One more boundary worth knowing before you write anything: cleanup is uneven. Planner tiles can be removed through the connector (by patching the planner page); pages and projects have no delete tool and have to be deleted in the xTiles interface by hand.
Creates one planner tile, so it works on any plan including Free. This is the one prompt that returns a URL you can check, and the shape the live UI check has validated.
Using the StudyLoop tools, call get_next_action with energy "medium", time_minutes 25 and modality "recall", and get_due_cards with limit 20. Then, in xTiles, add ONE item to today's planner as a tile built from Markdown, titled "Study: <primary concept>", with the recommendation's reason and estimated minutes, and a checklist of the due reviews, one line per card and at most 20. Do not create a project. Ask me before writing if the planner already has a "Study:" tile today, and tell me the page URL when you are done.
Creating the project works on Free, but know the connector's own limits before
tier limits: it cannot create a board view, and pages that are collections
(tables, boards) cannot be refreshed through it at all — they return an error
regardless of what you pay. Normal text pages refresh in place. Expect the
project URL to open on the home page; the other pages may not be visible from
there in the interface even when the connector reports them created. No MCP tool
returns plan Markdown, so paste it in from
studyloop plan show <plan-id> --markdown.
Here is my StudyLoop study plan as Markdown, pasted from the CLI. In xTiles, create or refresh a project named "<plan title>" with pages Mission, Milestones, Learning Records, Resources, Checkpoints and Today. Put the Mission text on the home page, the milestones in a table with a Status column (done/not done), the learning records as one page each, the resources in a table, and checkpoints as dated tasks. Refresh normal pages in place; do not try to refresh tables or boards, and do not delete anything. Tell me what you changed, and say which pages could not be refreshed.
Name the project exactly, every time. A renamed project is a project this prompt cannot find, so it creates a second one.
Adds a page and a task, so this too works on Free.
We are finishing a study session. Summarise what I covered in three bullets and one insight, then: (1) record that summary in StudyLoop first, with record_plan_learning for plan "<plan-id>", titled "<topic>" — the plan document is the source of truth; (2) in xTiles, add the same summary as a new learning record page under my "<plan title>" project, titled "LR — <date> — <topic>"; (3) add ONE planner task titled "Review: <concept>" on the next review date from get_due_cards. If get_due_cards returns no cards, do not invent a date; skip the Review task and the progress writes and say why. If we reviewed cards, record each one in StudyLoop with record_study_progress, passing the card_hash that get_due_cards returned. Ask before writing to xTiles; do not repeat the offer if I decline.
A limit worth knowing: get_due_cards returns cards that are already due —
it is not a source of the next review date. With nothing due there is no date to
schedule, which is why the prompt tells your assistant to skip rather than
invent one. The prompt records into the plan first (via
record_plan_learning, or studyloop plan record at a shell) so the xTiles
page is a projection of a record the plan already has — never the only copy.
Your mentor offers this last one for you. studyloop install agents installs an
opt-in wind-down skill into every harness it finds, and it stays silent unless
studyloop brain status --json reports provider: xtiles and an xtiles
server is connected in that session.
The Today panel in studyloop web can take you to your second brain with one
click. It is a launcher, not a publisher: the button opens your configured
provider, and nothing on the page ever writes to it.
flowchart LR
CFG["second_brain: config"] --> API["GET /api/second-brain/launch-target<br/><i>read-only, never cached</i>"]
API --> T["Today panel<br/>one action, prefetched state"]
T -->|"one explicit click"| DEST["Obsidian vault<br/>or your retained xTiles page"]
API --> S["Settings<br/>explains state, never launches"]
classDef local fill:#eceaff,stroke:#4c3fbb,color:#231d63
classDef remote fill:#fff1dc,stroke:#a9741a,color:#673e05
class CFG,API,T,S local
class DEST remote
Three rules hold everywhere on that diagram:
- Navigation happens only on your click. Loading the page, refreshing it,
publishing, or ending a session never opens anything. One click is one
navigation: xTiles opens once in a new protected tab, Obsidian hands the
current tab to the desktop app's
obsidian://link without leaving an empty tab behind. - Disabled means disabled, with the reason. When the action cannot work, the button says why — no provider selected, no retained xTiles destination, a vault that does not exist, or a browser on another device — instead of offering a click that goes nowhere.
- The server only reports. The launch-state API is read-only: it cannot change your configuration, it never runs a program on the server, and an invalid configuration comes back as a generic disabled state rather than echoing the bad value.
An obsidian:// link is an instruction to the operating system the browser
runs on. If you open the web app from your phone while StudyLoop runs on your
desktop, that link would ask your phone for an Obsidian vault it does not have —
so the action is enabled only when the browser and StudyLoop are on the same
device. From another device the button stays visible but disabled, saying that
Obsidian opens only on the device running StudyLoop.
Two honest edges of that rule. StudyLoop decides locality from the direct
network peer, so behind a reverse proxy every browser looks remote and the
action stays disabled — a safe false negative, not a bug. And when the vault is
available, the click opens Study/Today.md inside your vault when that note
exists, falling back to the vault itself when it does not.
StudyLoop never talks to xTiles, so it cannot discover a page to open. The handoff is explicit: your assistant creates or finds a page through the xTiles connector and reports its URL, you review it, and you retain it:
studyloop brain destination set --provider xtiles --url '<connector-returned-url>'
studyloop brain destination clear --provider xtilesRetaining a destination does not select xTiles as your provider — consent
stays with provider: — and the URL is validated before anything is written:
HTTPS only, exactly xtiles.app or app.xtiles.app, a real page path, and no
credentials, port, query, or fragment. A rejected URL is described by reason
only; the value itself is never echoed back or logged. The confirmation shows
just the host, and the web app's Settings page never displays the full retained
URL either.
Settings renders one card per provider: the selected provider is active, every other card is muted. An active card's guidance is the same honest reason the launch API reports (empty when the action is ready); a muted card names the CLI command that would select it. There is no save button — changing providers or destinations is a deliberate CLI action, not a web form.
studyloop brain status --json # provider, whether it can publish, where notes land
studyloop brain wind-down --json --connector xtiles # the one offer to make at wind-down, if any
studyloop brain enable obsidian --vault ~/Obsidian/Personal
studyloop brain destination set --provider xtiles --url '<connector-returned-url>'
studyloop brain destination clear --provider xtiles
studyloop brain publish # today's note plus every active plan
studyloop brain publish --all # every plan, whatever its status
studyloop brain publish --today --dry-run # show what would be written, write nothing
studyloop brain publish --plan python-decorators
studyloop brain pull python-decorators
studyloop brain template --install
studyloop brain enable none # turn it off againstudyloop brain wind-down --json is what an agent reads at the end of a session:
it answers with the channel, whether to offer, the exact sentence to say and why.
The publish offer is made only when both configured and supports_publish are
true (both visible in brain status --json), which is why a learner on xTiles is
never offered a command that cannot work.
At the end of a session your mentor offers this once, and only when a provider that can publish is configured:
Want me to publish today's study record and this plan to your Obsidian vault (Study/Today.md and Study/Plans/.md)? Yes or no — I'll only ask once.
The skill instructs your assistant to offer once and to drop the subject on a no; that behaviour has not yet been observed in a recorded session.
second_brain:
provider: obsidian # none (default) | obsidian | xtiles
vault_path: ~/Obsidian/Personal
folder: Study # the folder inside the vault StudyLoop owns
backlinks: true # [[wikilinks]] to your notes, when the matcher is available
xtiles_destination_url: null # optional; set with `brain destination set`Five keys, and that is the whole surface. STUDYLOOP_SECOND_BRAIN_VAULT also exists
and overrides the DEFAULT vault location — it is there so the test suite can never
reach a real vault, it never selects a provider, and an explicit vault_path always
wins over it. If you have it set in a shell profile, unset it.
vault_path is optional: if you already configured a vault for the session-memory
export, or an obsidian_base, StudyLoop uses that. Configuring a vault does not
switch a provider on — only provider: does.
No environment variable selects a provider. Turning this on authorises writes into your own files, so it has to be a deliberate change to your config.
Nothing is written and there is no error. studyloop brain status. If
configured is false, no provider is set.
"Vault path does not exist or is not writable". The drive is not mounted, or the
vault moved. Mount it, or studyloop brain enable obsidian --vault <new path>.
"Refusing to overwrite … not marked as StudyLoop-owned". A note of yours is already at that path. Move or rename it; StudyLoop will not overwrite it for you.
My edits to a published note keep disappearing. They will. Put your own notes in
the sibling .notes.md, which StudyLoop only ever reads.
A note StudyLoop published disappeared from my vault. Nothing here deletes. Check whether you renamed it — a renamed projection becomes yours, and the next publish recreates the canonical one under the original name.
I deleted a plan and its projection is still there. Publishing never deletes, so the note stays until you remove it. That is deliberate: deleting notes is exactly the class of action this feature will not take without being asked.
Backlinks are not appearing. They need the vault topic matcher from
agent-session-tools. Without it, publishing continues and logs one warning.
Every claim about someone else's software, with the date it was checked.
| Claim | Source | Verified on |
|---|---|---|
| Dataview is a community plugin, not built in | https://github.com/blacksmithgu/obsidian-dataview | 2026-09-03 |
| One hosted xTiles MCP server, at https://mcp.xtiles.app/mcp; one browser authorisation, no API key to copy or store | https://help.xtiles.app/en/articles/16126651-how-to-connect-xtiles-to-other-ai-tools | 2026-09-03 |
| MCP works on every xTiles plan, Free included, with some limitations on Free | https://help.xtiles.app/en/articles/16126651-how-to-connect-xtiles-to-other-ai-tools | 2026-09-03 |
| Creating projects, pages and tiles is a Free feature; editing an existing project in a personal space starts at Plus, and editing in a shared space at Pro | https://xtiles.app/en/pricing/ | 2026-09-03 |
| Your assistant only gets what your xTiles account can already see; nothing is shared with other users; you disconnect by removing the server in your AI tool's own MCP settings | https://help.xtiles.app/en/articles/16126651-how-to-connect-xtiles-to-other-ai-tools | 2026-09-03 |
A remote HTTP MCP server is added with claude mcp add --transport http, and /mcp manages and authenticates it |
https://code.claude.com/docs/en/mcp | 2026-09-03 |
| xTiles publishes a connector in Claude's Connectors Directory as a quicker alternative to adding the URL by hand | https://help.xtiles.app/en/articles/15192396-how-to-connect-xtiles-to-claude | 2026-09-03 |







