Opyt

How Opyt works.

Opyt is an MCP server. It exposes tools to the AI client you already use. A local install keeps its data under ~/.opyt: opyt.db holds the knowledge-base records, each item's Markdown snapshot and the search index, and rail_jobs.db holds the worker's job queue. Its background helper, opyt-worker, which onboard installs on your Mac, runs the ingest jobs, including while the client is closed. The worker opens no port and holds no credential of its own.

Local clients start Opyt over stdio: Claude Code, Claude Desktop, ChatGPT Desktop, Codex, Cursor, and Windsurf, on a Mac. claude.ai and chatgpt.com read the library that install builds: say “connect Opyt to Google” in the local client, then add the Opyt connector at https://api.useopyt.com/mcp and sign in with the same Google account — step by step here, and the connector's own documentation is here. They search a published copy by keyword, and a change they ask for (a saved link, a topic, a person, a removal) is made by this install. There is no Opyt chat UI.

The tools.

The tools, in roughly the order you meet them. Every argument, its type and its default, and what comes back.

ToolEffectWhat it does
onboardwrites · re-entrant Asks for a source where you already read, then runs setup.
oraclewrites Screens candidates from your activity and tracked sources, confirms picks, and follows imports.
add_oracletwo-phase Adds a person or research venue by handle or URL, previews the source, then pulls what is available.
searchread-only Ranked retrieval over the store, filterable by tag, kind, source, author and date.
openreads · may read a paper's full text The full stored text of one atom, plus its live source URL.
aggregateread-only · free Counts the whole scope by author, kind and source; can sample descriptions to help identify subjects.
latestread-only · free What is new since you last looked, by the topics you follow: finds to check, and your people's posts.
hoppertwo-phase · ingests Takes a supported paper, repo, post or article link and saves it as an atom.
watchwrites · free Follows a topic: names to watch on arXiv, GitHub, OpenAlex, X and Substack, and words and people for your own people's posts.
lookread-only · free Searches X, Substack, arXiv, OpenAlex and GitHub now for a few named things, and saves nothing.
sharetwo-phase · publishes Hands someone a link to search your store, makes one for an agent you run, or connects your own claude.ai or ChatGPT.
acceptsingle-phase · writes Registers a knowledge base somebody shared with you, from the link they sent.
unsharetwo-phase · deletes Cuts off one reader, or deletes everything Opyt's server holds for the knowledge base.
reader_viewpublishes · expires Puts an answer and the sources behind it on one private page, in full, that you can read or pass on.
forgettwo-phase · removes Removes atoms, ends one Oracle subscription, or drops one of an Oracle's other sources. Existing atoms stay unless you ask for them to go.

The server owns only the last segment of each name, and a client that namespaces adds its own prefix: search reaches Claude Code as mcp__Opyt__search. Argument names are the ones below in every client. Required arguments have no default and the call fails without them; optional ones name their default. Every tool returns a JSON object.

onboard

writes config idempotent human in the loop

Sets up this Opyt home. It recomputes where you are from disk on every call, so calling it twice never repeats a finished step. It asks one thing: a source where you already read. Connecting X or Substack imports what you saved there and keeps it current, with nothing else to answer. Opyt is free and calls no model: search matches your words rather than their meaning, and images are not read. Any step that opens a sign-in or approval page is explained before you agree to begin it. Setup runs the applicable curation collectors and hands off to oracle.

onboard(source=None, start=None) -> dict

Parameters

sourcestringoptional
One of "x" · "substack" · "research" · "blog" · "skip". The roots are not exclusive — take one, then call again with another. On a local install, x and substack open OPYT's own browser profile at that site's sign-in page, so OPYT never reads a session from your normal browser profiles. On a computer that is not a Mac, such as an assistant's computer with no browser of its own, x returns a link (login_url) to a private sign-in browser on Opyt's service instead, and the install collects the session once X shows its home page. research and blog have no account to connect: they hand back the question to put to you and the exact call to make with your answer. skip records that the question was asked and moves on; every root still works later.
startstringoptional
"github" · "openalex". Each opens a browser page, so say what it is for and wait for the user's agreement before passing it. github and openalex are optional keys that lift those services' anonymous rate limits on what Opyt pulls. On a local install the tool names them once, beside the answer to the first Oracle confirmation. github shows a code to enter on github.com; pass it again once approved, to finish. openalex opens a one-time page on the user's own machine and checks the key before keeping it. Every word works at any time.

Returns

statusstring
"ok" · "in_progress" · "needs_source" · "needs_name" · "needs_login" · "awaiting_login" · "connected" · "unsupported_platform" · "error". A key step can also return "waiting".
phasestring
Where setup stands: sources → curation → done. On a computer that is not a Mac, unsupported: the local install runs on macOS only, and nothing is set up.
messagestring
Written to be read out. On a needs_* status it is the question.
stepstring
On a key step's reply: the start word it answers.
open_this_urlstring
When present, the sign-in or approval link for the user to open (login_url for the X sign-in that runs on Opyt's service). After they finish, call onboard() again to read the current state.
worker_problemobject
Only when Opyt's background helper is not running: its cause and the step that fixes it. Work waits meanwhile; nothing is lost.
candidatesinteger
How many people the free collectors surfaced for oracle to screen.
next_toolstring
"oracle" once setup is done.

Notes

  • Seven signal collectors read your connected accounts: X bookmarks, likes, follows and Lists; Substack follows, subscriptions and saved posts. Reading these signals is free, and so is importing saved content.
  • Local X setup opens an OPYT-owned profile and never scans or reads a normal browser profile. On a computer that is not a Mac, X setup returns a link to a private sign-in browser on Opyt's service instead; both return so you can finish the login before calling onboard again.

oracle

writes action-dispatched

Chooses the Oracles the knowledge base deep-ingests. Candidates come from your follows, Lists, subscriptions, bookmarks, likes and saved papers, plus tracked researchers' coauthors and tracked Substack publications' recommendations. Ranking puts person-level acts (follow, subscribe, add to a List) ahead of content-level acts. The screen lists candidates by source: your top people on X, on Substack and from papers, then people your Oracles name, then one group for organisations, media and projects. A person found on several sources is listed once, under the stronger one. Nothing is selected by default; you choose which to confirm. Six actions run the flow in chat.

oracle(action="screen", canonical_ids=None, add_handles=None,
       top_n=30, limit=10, source=None, force=False, x_lookback=None,
       web_lookback=None, scholar_lookback=None, scholar_topics=None,
       query="", min_signals=1, oracle=None) -> dict

Actions

actionReadsWhat it does
"screen"limit, source Default. The candidates in groups, in print order: one per source (X, Substack, papers), then organisations, media and projects. Each group holds its top limit people in rank order and more, the count of the rest. Each candidate carries a one-line reason ("you follow, bookmarked 12") and its canonical_id. Once you have Oracles, a last group, Named by your Oracles, lists people their posts quote, mention or link who are in none of your lists; they have no canonical_id, so a pick goes in add_handles by handle. Nobody is pre-selected; the reply carries the pick question in ask. Also carries oracle_freshness unasked: per-Oracle, per-source last-pulled times, worst first.
"candidates"query, top_n,
min_signals, oracle
The same people ranked by what they write rather than how hard you vouched. Evidence is one of probed (a ~25-post timeline sample, unvetted), saved (a post you kept, so it is a real atom) or mentioned (someone an Oracle's own posts quote, mention or link). Scores compare only within a basis.
"confirm"canonical_ids,
add_handles,
x_lookback, web_lookback,
scholar_lookback, scholar_topics
Commits picks into the oracles table and records how far back each is owed. Idempotent, and it returns at once: nothing is pulled inside the call. Opyt's background process then finds each person's other profiles and pulls every trusted personal profile that has an available collector; X is included only when X is connected, and an org link becomes an affiliation edge instead. Raw handles and URLs are resolved at confirm time. Company, team and publication accounts are confirmed as given.
"ingest"canonical_ids, force,
x_lookback, web_lookback,
scholar_lookback, scholar_topics
Asks again for confirmed Oracles: a wider window, subjects, a top-up of their newest posts (x_lookback="since_last") or a fresh discovery of their other profiles (force). Windows only ever widen. Returns at once, like confirm.
"progress"canonical_ids Where each person stands: their newest posts, their archives beyond X, and how far back their X goes against how far it is owed. Waits up to about 40 seconds for something to change, then returns. There is no complete, and nothing waits on this call.
"map"(none) The people you read, grouped by what they write: each group's words, its central people, and which of your Oracles sit in it. A group with no Oracle is where a new one would add the most.

Parameters

actionstringoptionaldefault "screen"
See above.
canonical_idsstring[]optional
On confirm, the candidates you kept, verbatim from screen. On ingest, which confirmed Oracles to ask again for; omitting it means all of them. On progress, whose status to report; omitting it means all of them.
add_handlesstring[]optional
confirm only. Raw X @handles or supported URLs, including Substack, personal sites and OpenAlex records, to add beyond the ranked list.
top_nintegeroptionaldefault 30
candidates only. Maximum people returned by content search. Screening uses limit to bound its returned cards.
limitintegeroptionaldefault 10
screen only. How many people each group lists, in rank order; the rest are counted in the group's more. A screen returns at most 80 people across its groups, so a larger value is clamped and reported in limit_clamped. To go further into one group, pass its source.
sourcestringoptional
screen only. List only the people who reach you through one source, such as "x", "substack" or "papers", or "named" for the people your Oracles name. A person on two sources is listed under the one you filter by. Ranking stays unchanged. An unknown platform returns an error and known_platforms.
forcebooleanoptionaldefault False
ingest only. Re-runs discovery from scratch, ignoring the trust cache.
x_lookbackstringoptionaldefault "6mo"
confirm and ingest, only when X is connected: "6mo" · "1yr" · "2yr" · "since_last". Hard-capped at two years whatever you pass. since_last checks now for what has appeared since this Oracle's last pull, and nothing else; someone never pulled gets their first pull in full.
web_lookbackstringoptionaldefault "all"
"1yr" · "2yr" · "5yr" · "all". Narrows a discovered Substack or blog source.
scholar_lookbackstringoptionaldefault "all"
"2yr" · "5yr" · "10yr" · "all". Narrows the paper record of a researcher or research venue.
scholar_topicsstring[]optional
confirm and ingest, for one named Oracle. OpenAlex topic ids from an Oracle preview's scholar_topics.topics. This filter persists for that source; pass [] to return to a researcher's whole corpus. A research venue has no whole corpus to return to: without subjects, none of its papers are pulled.
querystringoptionaldefault ""
candidates only. A topic question to rank people by ("agent memory"). Omit it for "who has been sampled".
min_signalsintegeroptionaldefault 1
candidates only. Floor on distinct curation signals; 1 is everyone.
oraclestringoptional
candidates only. One Oracle's handle or name: lists only the people that Oracle's own posts quote, mention or link (add query for one subject), plus their coauthors.

Returns

screenobject
total_candidates, groups[], ask, note, lookback_options, oracle_freshness, and model_routing only when a model is dead or fragile. Each group carries title, candidates[], more and source (x, substack, papers or named). Each candidate card carries canonical_id, name, handle and reason. lookback_options.x, present only when X is connected, is the separate X lookback question: its ask, the presets ("6mo" · "1yr" · "2yr") and the default. When applicable, the response also includes source and limit_clamped (asked and applied limits).
confirmobject
confirmed, unresolved (handles a lookup could not find; nothing was written for those), unknown, total_oracles, picks (where each stands, as in progress) and message, what to tell the user about what is still to come. A research venue confirmed without subjects carries needs_topics and a note.
ingestobject
picks and message, as on confirm, and checking: the Oracles whose newest posts are being checked now (since_last).
progressobject
picks[], one per Oracle: name, canonical_id, newest_posts (null without an X timeline), archives (null when nothing was asked of them), reach (how far back their X goes against how far it is owed), asked (the windows on record), and stuck, with its cause and the step that clears it, only when the user can do something. other_sources, only when Opyt found more of someone's writing: added, also and a message to tell the user once. waiting_on says when X will next move. A false means not yet, never that something is missing.

Notes

  • The X pull is free (it goes through your connected OPYT-managed session), but the default window is about six months, so a user who wanted an Oracle's whole visible history gets a fraction of it and is never told. That is why the screen asks it as a separate question, offering 6 months, 1 year or 2 years.
  • Plain organisations, media and projects are moved to their own last group by your AI client when it prints the list, as the screen's note asks. Nothing changes a candidate's rank.
  • Nothing is pulled inside a call. confirm and ingest record what each person is owed and return at once, and Opyt's background process does the pulling. Call progress when the user asks how it is going; what lands while they are away is announced once, on their next call.

add_oracle

preview is free confirm writes

Adds a person or a supported research venue as an Oracle. For a person, Opyt discovers where they actually publish (X, GitHub, a Substack, a blog found by feed or sitemap), and verifies each profile belongs to them through links they attest to themselves, so it never merges two people with the same name. X is pulled only when you have connected X; other discovered sources are collected only when their collector is available. OpenAlex author records and research venues are pulled as papers with their authors and DOIs. Two phases: a free preview showing the resolved person or venue and what a confirm would do, then, once you agree, the add, with the pull in the background. To stop following someone, or drop one of their sources, use forget.

add_oracle(reference, confirm=False, x_lookback=None,
           web_lookback=None, scholar_lookback=None, scholar_topics=None,
           force=False) -> dict

Parameters

referencestringrequired
Polymorphic: an X handle ("@karpathy"), a Substack, blog or site URL ("https://simonwillison.net"), an OpenAlex author or source URL, a supported research venue URL, or a canonical_id from oracle(action="screen"), to promote a below-the-cut candidate. There is no name-search endpoint: a name has to be resolved to a handle or URL first.
confirmbooleanoptionaldefault False
False previews and writes nothing. True adds them, records how far back to pull, and returns at once; the pull runs in the background.
x_lookbackstringoptionaldefault "6mo"
Only applies when X is connected: "6mo" · "1yr" · "2yr" · "since_last", hard-capped at two years. since_last is the top-up for someone already on the roster.
web_lookbackstringoptionaldefault "all"
"1yr" · "2yr" · "5yr" · "all". Narrows only.
scholar_lookbackstringoptionaldefault "all"
"2yr" · "5yr" · "10yr" · "all". Narrows the paper record of a researcher or research venue.
scholar_topicsstring[]optional
OpenAlex topic ids under the preview's scholar_topics.topics. The choice persists for that source; pass [] to clear it. A research venue without subjects gets none of its papers pulled.
forcebooleanoptionaldefault False
Re-runs discovery from scratch, ignoring the cache. The cache key is the person's display name plus their declared links, so a source they created after the last run leaves that key identical and only this gets past it. Costs a full probe walk.

Returns

previewobject
confirm_required, reference, lookback, on_confirm, mode ("new" or "existing") and resolved: name, bio, followers, site for an X handle; the roster entry for someone already known. A reference that does not resolve comes back as unresolved with nothing to confirm. For a new OpenAlex root, resolved includes root_entity, platform ("openalex_author" or "openalex_venue") and works, the publication count. Paper previews can also include lookback.scholar_counts for an existing record and scholar_topics with topic counts. For a link to a single page it also carries ambiguous_scope, since confirming follows the whole site; ask which was meant before confirming.
confirmobject
added (canonical_id, name, source, was_already_oracle), picks and message as on oracle(action="confirm").

Notes

  • Three windows, asked separately, because they answer to different constraints. A single shared window would be wrong for at least one corpus: it either truncates a durable archive or over-pulls the ephemeral stream.
  • For a research venue, review its publication count and choose subjects before confirming. A venue publishes far more than one pull should hold, so none of its papers are pulled until subjects are chosen. scholar_topics narrows the pull by subject and persists into later refreshes.
  • A company, team or publication account is a fine Oracle when you want one. Only a platform profile page (such as a GitHub, LinkedIn or Medium author page, or an X profile link) comes back as an error naming what to pass instead, such as the bare @handle. Supported research venues use the OpenAlex paper route.
  • Opyt finds a person's other places in the background, from the links their own profiles declare: X, Substack, GitHub, a blog, ORCID. It never takes sources found by a web search, and checks each one belongs to them before pulling it.

open

reads idempotent may read a paper's full text

Follows an atom's pointer and returns its real stored snapshot text plus the live source URL, so a claim can always be traced back to where it was published. search routes you to an atom; this is the ground truth to reason from.

open(atom_id, kb=None) -> dict

Parameters

atom_idstringrequired
From a hit card, an aggregate row, or a prior open. An unknown id returns {"error": "not found"} rather than raising.
kbstringoptional
Must be whatever the hit card carried. An atom id is scoped to one knowledge base: the same tweet in two people's stores is one id in each, so opening a foreign id without its kb either finds nothing or hands back your own copy of the same source.

Returns

rawstring | null
The stored snapshot. This is what a claim is asserted from. raw_available says whether it is there at all.
source_urlstring
The live pointer. Re-fetch it for anything fresher than the snapshot.
body_statestring
"complete" · "partial" · "absent" · "pending", with body_basis saying how it was determined.
also—
atom_id, kb, source_type, what_kind, who_id, when_ts, cite, when_precision, description, payload, and papers when the item is a post that links papers: each with its full_text while the answer's size allows, and an open_id to pass to open for the rest.

Notes

  • raw is only as complete as body_state says. On partial you hold a knowing fragment, so quoting it as the full article invents a citation; attribute what is there and send the reader to source_url.
  • Opening an abstract-only paper in your own store reads its full text into that answer from its open PDF. Nothing is added to the library. A paper with no open PDF stays an abstract, and a shared knowledge base returns the owner's stored copy as it is.
  • When describing what an atom says, include its cite link in the answer. cite is null if no source URL is stored.

aggregate

read-only idempotent free · pure SQL

Counts across the entire store rather than a sample. This is what answers distributional questions: what you saved most of, when your interests moved, how much of the store comes from people you confirmed. Scope is optional: omit everything for the whole store.

aggregate(tags=None, what_kind=None, source_type=None,
          who_id=None, date_from=None, date_to=None,
          kb=None, sample=0) -> dict

Parameters

tagsstring[]optional
Authors' source tags, matched as in search. These cover only part of the corpus and are not a topic taxonomy.
what_kindstringoptional
"opinion" or "artifact".
source_typestringoptional
"x" · "github" · "substack" · "blog" · "paper".
who_idstring | string[]optional
Ids, not handles. To scope to a person, call search(who="@handle") first and pass its insights.resolved_who[].who_ids here: resolution has one home rather than two that can drift.
date_fromstringoptional
Earliest date, inclusive. Same formats and same widening as search; any other shape raises.
date_tostringoptional
Latest date, inclusive.
kbstringoptional
Summarize a shared knowledge base instead of your own. trusted_atoms then counts atoms whose author that owner confirmed, not you. The optional sample is currently not forwarded to remote shared knowledge bases.
sampleintegeroptionaldefault 0
Include up to this many descriptions from your own store as corpus_sample, capped at 500. The sample cycles through authors to show breadth. Use 200 for a corpus-topic overview; leave it off for ordinary counts. Counts still cover the entire selected scope.

Returns

totalinteger
Atoms in scope.
by_what_kindobject
Counts keyed by kind.
by_source_typeobject
Counts keyed by source.
trusted_atomsinteger
Atoms whose author is a confirmed Oracle.
corpus_sampleobject
When requested and available: descriptions (plain description strings, spread across authors; find an item with search), sampled, of, authors, of_authors and host_note. There is no top_topics field.
top_entitiesobject[]
Up to 15, as {who_id, count}.
recent_descriptionsobject[]
The 12 newest, as {atom_id, description, who_id, when_ts}. These are mechanical descriptions, so they can be read without opening, but a claim still needs open.
also—
scope as applied, kb, and notices in the same shape search uses, so one channel serves both.

Notes

  • Counts here are a plain filter result: an undated atom is excluded by either bound, and a year-only atom counts if its year overlaps the window. Unlike search, this tool does not report how many undated atoms that dropped.
  • To answer what subjects your corpus covers, call aggregate(sample=200), read the descriptions, and propose subjects. Verify each with search(query=...); report only supported subjects with cited examples and the sample size out of the total. Do not present raw tags or the raw sample as a topic overview, or save the inferred subject names as a taxonomy.

latest

read-only · free

What is new for you since you last looked. For each topic you follow (watch): new papers, repos and posts its watches found, which wait outside your library until your agent checks them, and your own people's new posts on it. Then what your Oracles posted, what you saved and pages they linked, outside every topic, each saying why it is here. New means recently published: when an Oracle's archive arrives, its older posts are counted in one line rather than listed. A line about what is new can appear in search responses for your own knowledge base, mentioned to you once per conversation.

latest(topic_id=None, limit=50) -> dict

Parameters

topic_idstringoptional
Shows one topic only, and marks nothing as seen.
limitintegeroptionaldefault 50
How many posts to list, shared among the groups in turn.

Returns

topicsobject[]
One per topic with something new (topic_id, name): finds, each with its title, cite, summary and the watch that found it, and posts, your people's new posts on it. A post two topics cover appears under both.
unfiledobject
New posts outside every topic: new, its count, atoms, and more when some were left out. Each atom carries who, when, cite, description, and why: you saved it, or an Oracle posted or linked it.
olderstring[]
Older items that arrived since you last looked, counted one line per Oracle or kind, e.g. "Ben Thompson: 2,991 older posts, 2009–2026".

Notes

  • Your agent checks before it shows. It keeps only the finds and posts that are about the topic and worth your time, records its check with watch(action="checked"), and tells you nothing about the rest. Kept finds join your library; the rest never come back.
  • A full look marks the posts seen. The next call starts from that moment; a first call reaches back a week. Finds stay until they are checked.

hopper

preview is free confirm writes idempotent

Takes a supported link (a paper, repo, Substack post, article or single X post), detects what it is, routes it to the right ingester, and shows you what it found before writing anything. This is the only manual save path; there is no separate save-a-paper or save-a-repo tool. Every atom it stores is stamped entry_mode="user-saved", the same mark an X bookmark gets, and that is load-bearing downstream: search ranks hand-saved items as your own, and latest says you saved them.

hopper(reference, confirm=False, kind_hint=None) -> dict

Parameters

referencestringrequired
The URL to save: an article, a paper (arXiv, DOI or .pdf), a GitHub repo, a Substack post, or an x.com status link. Anything that is not an http(s) URL comes back unroutable with nothing written, rather than being filed somewhere plausible. Unsupported video, audio and login-walled platform URLs are also refused as unroutable; a supported written transcript or article can be saved instead.
confirmbooleanoptionaldefault False
False previews without writing to the knowledge base. A preview may read an X post or inspect an unrecognized article URL to determine its route. True saves the item or records your save of an existing Frontier item.
kind_hintstringoptional
Consulted only when the host matches nothing known: a recognized host always wins, because the host is a fact. "substack" is the one value worth passing: a Substack post on a custom domain cannot be detected without fetching, and routed as a plain article it gets a different atom id and never dedupes against the same post saved from a bookmark. "paper", "github" and "x" cannot override anything: those adapters check the host themselves.

Returns

statusstring
"preview" · "saved" · "already_present" · "rejected" (the page was nav or promo boilerplate, not an error) · "blocked" (a bot-check; retryable) · "failed" · "unroutable". already_present can still update an existing Frontier item's save attribution; it does not mean the call made no writes.
previewobject
routable, kind, why (what the route was decided from), atom_id, already_present, entry_mode, and a note for the two kinds whose id cannot be predicted offline.
detailstring
On rejected, blocked and failed: what actually happened, and whether it is worth retrying.
warningstring
On a saved result, marks a degraded success: most often a paper whose full text landed but whose metadata lookup was throttled, so it is stored with no title, date or author. The atom is searchable by its body, but re-saving will not repair it.

Notes

  • Previews usually route recognized paper, repo and Substack URLs without fetching them. An unrecognized article URL can trigger a bounded page inspection for scholarly metadata; a detected DOI can change the route to a paper before you confirm. An x.com status can also be read to return a description, so you can check which post the link identifies.
  • It stores public content only. A paywalled Substack post is skipped by the adapter and comes back failed: the same cookie-less public endpoint a reader hits, so it cannot see past a wall either.
  • Repeat saves avoid duplicate atoms. Confirming an existing Frontier item records that you saved it: its entry_mode becomes user-saved and its promoted_at timestamp is set, without rewriting its body. The result is still already_present. Subsequent saves leave that attribution unchanged; the preview's route inspection may still make a fetch.
  • It never adds a person to the tracked roster. Saving someone's article does not start following them; add_oracle is the only way in.

watch

preview is free writes action-dispatched

Follows a topic for you, so you are told when something new on it appears. Your agent writes three lists: watches, each naming one specific thing (a model, a drug, a benchmark), searched every day on arXiv, GitHub, OpenAlex, X and Substack; words that pick out your own people's posts on the subject; and the people who mostly write about it. Opyt runs no model for this: the searches run in the background, and your agent checks what they find in latest before it shows you anything.

watch(action, name=None, watches=None, words=None, people=None, topic_id=None,
      drop_watches=None, keep=None, passed=None) -> dict

Parameters

actionstringrequired
"preview" shows what a topic would have found last month and writes nothing. "create" starts following it, "edit" changes it, "drop" stops it, "list" shows them, and "checked" records your agent's check of the finds latest handed it.
watchesobject[]optional
Each a name for one specific thing, its other spellings, the sources to search, and, for X, context: a word or two that narrow a name that also means something else ("Chai-2" with "antibody"). A find is kept only when the name appears in its title or text.
words
people
listoptional
Phrases that pick out your people's posts on the subject, and handles of the people who mostly write about it.
keep
passed
listoptional
With "checked": the finds your agent kept and the ones it passed over.

Notes

  • Names, not categories. Measured on 403 labelled results: searching a descriptive phrase brought 44% off-topic finds; searching the thing's name, kept only when it appears, brought 13%.
  • Finds stay out of your library until checked. They wait, saved, where search does not see them; the ones your agent keeps are added, the rest never come back.
  • A daily update is your client's. After a topic is created your agent asks once whether you want one, and creates a daily task if your client can schedule.
  • Right now, without following it. To see what the sources have on a name this week, your agent uses look.

look

read-only · free

Searches outside your library, right now, for one to three specific named things: what X, Substack, arXiv, OpenAlex and GitHub have on each over the last days ("what are people saying about Boltz-2 this week?"). It is the search a topic's watches run every day, with the same rule: a result is kept only if it names the thing. Nothing is saved. Your agent shows you what is worth your time, with links, and can save any of it or follow the name with watch.

look(names, sources=None, days=None) -> dict

Parameters

nameslistrequired
One to three things, each a name or an object with the name, its other spellings, and context: a word or two that narrow a name on X when it also means something else ("Chai-2" with "antibody").
sourcesstring[]optional
Any of x, substack, arxiv, openalex, github; all of them when left out.
daysintegeroptionaldefault 7
How far back to look, in days: any whole number from 1 up, so a literature survey can reach back years. X and Substack reach back at most a month. Past about three months, OpenAlex answers with the most-cited work rather than the newest.

Returns

namesobject[]
One per name: by_source, how many each source found (or that it could not be searched), and results by source, newest first, each with its title, date, cite link, a snippet, and the save arguments for hopper. A paid Substack post is marked paid and cannot be saved.
searched_lessobject
Only when a source could not reach as far back as you asked: how many days it searched instead.

Notes

  • Not your people. What comes back is from outside your library; your agent never presents it as what your Oracles said.
  • X needs X connected. X is searched through your own signed-in X session, on a local install; without it, the answer says X was not searched.

share

preview is free confirm publishes returns a link

Hands you a link to send someone. They open it, and from then on their AI client can search everything you have read, attributed to you. The preview is the consent step and it carries the whole picture (how many atoms, whose, from which sources, over what date span), because a summary you have to ask for separately is one that gets skipped. Sharing is the whole knowledge base, standing: there is no way to share a slice, and access lasts until you revoke it. With agent the link is for an agent you run yourself, one that calls web addresses: it reads the library and also acts as you.

share(confirm=False, as_name=None, for_whom=None, refresh=False, to="person") -> dict

Parameters

confirmbooleanoptionaldefault False
False previews: nothing is uploaded, nothing is minted, nothing is sent. True registers this install if it is not registered yet, starts the upload, mints a one-time invite and returns the link.
as_namestringoptional
What to call this knowledge base's owner: your name or handle. Required on the first share and ignored after, because everyone you share with sees it as the name to search under. It is a suggestion rather than a claim: two people may pick the same one, and a recipient who already knows another alex gets alex-2.
for_whomstringoptional
A label for who this invite is for. It is the handle that makes them nameable afterwards: unshare(reader="Leo", confirm=True) cuts off exactly them after you review the preview. Skip the label and they can only be named by a token id.
refreshbooleanoptionaldefault False
True updates the shared copy now instead of at the next pass, so the people you share with (and the Opyt connector on claude.ai or chatgpt.com) see what just changed. No invite and no preview; every other argument is ignored. An unchanged library uploads nothing and says so.
tostringoptionaldefault "person"
Who it is for, which decides what happens. "person": a link for someone else, who reads and changes nothing. "agent": a link for an agent you run yourself. The agent redeems it once and gets a key that acts as you: it reads the library, and can save links, remove items, add people to follow, and invite or cut off readers, until you cut it off with unshare. "connector": your own claude.ai or ChatGPT (what “connect Opyt to Google” does). No link: the confirm shows a code to enter on Google's page, and the same call again after you approve finishes it. The preview's consent says what each gives. How an agent uses its key is in the connector's documentation.

Returns

statusstring
"preview" · "shared" · "empty" (nothing in the knowledge base to share yet) · "needs_name" (a first share with no as_name) · "register_failed" · "grant_failed" · on a refresh, "refreshing", "no_change", "not_published" (sharing was stopped) or "not_shared". All but "grant_failed" left this install exactly as they found it. "grant_failed" on a first share means the registration already succeeded: the owner token is saved and the routing key is claimed, so retrying mints the invite without registering again.
invitestring
On a confirm: the link, like https://useopyt.com/invite#…. Send it however you like. The code is in the URL fragment, which is excluded from the browser's initial page request. When the recipient calls accept, Opyt sends the code to the sharing service to redeem it for a reader token.
consentlist
On a preview: the sentences describing what sharing means, meant to be read back before confirming.
also—
On a preview, the shape of the store: atoms, by_source_type, by_what_kind, top_entities, trusted_atoms, date_span, plus already_shared and already_published. On a confirm, owner, the routing key this knowledge base is served under, and queued, whether the upload job was durably recorded for the worker.

Notes

  • Publishing is self-service. No invitation, no waiting list, no account. The first confirm registers you and assigns an opaque routing key that lives in a URL and a filename and nowhere a person reads.
  • The first share uploads the whole corpus, which the worker does in the background and usually finishes in a minute or two. The link works immediately: a reader who accepts inside that window is registered normally, and it is their first search that comes back saying the copy is still arriving. The next one works. Later shares are instant. If opyt-worker is not running the upload waits for it — the grant is durable, so nothing is lost, but nothing is served either.
  • The served copy keeps itself current. Every hour while your Mac is awake, and every five minutes for the hour after you share or connect Google, it uploads what changed since the last upload, so a reader sees it soon after. share(refresh=True) updates it at once. While your Mac sleeps, the copy waits, and it catches up when the Mac wakes.
  • It publishes nothing to the open web. The service serves only people holding a link, one reader token each, revocable.
  • An invite is not a standing credential: it buys exactly one reader token and then it is spent, so what sits in that chat window afterwards opens nothing.

accept

single-phase read-only access

Takes an invite somebody sent you and registers their knowledge base locally. After one call, search, open and aggregate accept kb="their-name". There is deliberately no preview: a grant code buys one reader token and then dies, so checking it would spend it, and pasting the invite is already the yes.

accept(invite, name=None) -> dict

Parameters

invitestringrequired
The invite link, the fragment, or the bare code. All three work: the code is found inside whatever was pasted.
namestringoptional
What kb= should call this knowledge base on this machine. Omitted, the owner's own name is used.

Returns

statusstring
"accepted" · "code_unavailable" (already used, or not one this service issued) · "not_an_invite" · "reserved_name" (that name is what this install calls its own library; pass another or leave it out). None of the failures registered anything.
kbstring
The name to pass as kb=. Use this string: a name already taken on this install is suffixed rather than overwritten, because the registry holds the only copy of each reader token in existence and replacing one destroys access nothing can restore.

Notes

  • It goes one way. Accepting gives you read access and gives them nothing. After your first search of theirs that finds something, Opyt offers once to share yours back, and never raises it again for that person. A second person's knowledge base gets its own single offer.
  • Read access is all it is. The service exposes only read endpoints (search, open, aggregate and a feed of new arrivals) to your token, which carries the reader role, so there is no call this install can make that writes to their knowledge base.
  • What the service records is that you read, never what you searched for. There is no query column in its schema.
  • You configure nothing. Your install already knows the service address, and an invite to somebody's self-hosted one carries its own.

unshare

preview is free confirm deletes

Stops sharing, with one person or with everybody. reader picks which, and the two are not the same act. An agent you gave a link to is listed with your readers, marked as an agent, and cut off the same way. Name a reader and they lose access while the copy stays up and everyone else keeps reading. Leave it out and everything Opyt's server holds for the knowledge base is deleted: the served copy, every reader and share link, the count of its reads, and its link to a Google account, so claude.ai and chatgpt.com can no longer read it. That is one act rather than several because a person who says "stop sharing my knowledge base" means all of it and will not say it twice. It cannot be undone; the knowledge base on your Mac stays, and nothing is uploaded again until you share it or connect Opyt to Google again.

unshare(confirm=False, reader=None) -> dict

Parameters

confirmbooleanoptionaldefault False
False previews what this scope would do, and changes nothing. True does it.
readerstringoptional
Who to cut off, matched against the label you gave at share time as for_whom, case-insensitively. A token id from an earlier return also works, which is how you name somebody you invited without a label. Omitted means everybody, so it is required whenever the user named a person. "google" disconnects your own claude.ai and ChatGPT (your Google account) and nobody else.

Returns

statusstring
"preview" · "unshared" · "reader_revoked" · "already_gone" · "no_such_reader" · "ambiguous_reader" · "not_shared" · "unreachable" · "failed". The three reader statuses revoked nobody.
scopestring
On a preview: "one_reader" or "everyone". Read it before confirming; it is the field that says which of the two acts you are looking at.
readers_revokedinteger
On a whole-knowledge-base confirm: how many lost access. Effective on their very next request: there is no refresh cycle and no window.
also—
On a whole-knowledge-base preview, readers, how many hold access now, and reader_labels, whatever for_whom called them; on its confirm, export_deleted. On anything scoped to a reader, reader_label and readers_remaining. When a name does not resolve, readers is the roster to pick from, each with a label and an id.

Notes

  • One reader is cheap to undo; everybody is not. Letting one person back in is another share link. Taking the whole knowledge base down kills every link ever sent, so everyone has to be re-invited by hand.
  • A name that matches two readers refuses rather than picking one, and hands back both with their ids. The wrong guess is somebody's access, and from here the two are indistinguishable.
  • Readers are cut off before the file is removed, so a failure in the middle leaves people already revoked and a stray file a retry clears, never live access to a knowledge base that is half gone.
  • Your own ability to publish survives, so re-sharing is one call and not a re-setup.

reader_view

publishes · expires

Opens an answer and the sources behind it in reader view, on one private page: your assistant's answer on top, then every post and thread with its photos and video, every article in full, a repository's README, a paper as its own PDF. One link, readable on a phone, that you can keep or send on. Opyt cannot see your conversation, so your assistant names the items it cited.

reader_view(atom_ids=None, title=None, note=None, answer=None, delete=None) -> dict

Parameters

atom_idslist[string]optional
The items, in the order the answer cited them: the atom_id of search hits or open results from your own library. At most 50.
titlestringoptional
The page's title, a short plain phrase.
notestringoptional
A sentence or two shown under the title.
answerstringoptional
Your assistant's answer, in Markdown, shown above the sources. A link in it to one of them jumps to that source's card. Leave it out for a page of sources alone.
deletestringoptional
A reader view's link, to take it down now instead of when it expires.

Returns

statusstring
"made" · "deleted" · "not_found" · "failed".
urlstring
On made: the page's link. missing lists any id that is not in your library and so is not on the page.

Notes

  • Private to whoever has the link. The address is a long random id, kept out of search engines, and the page is deleted 90 days after it was last opened.
  • Anyone with the link can comment. A comment is on a passage or on a whole source, under the name its writer types, and everyone with the link sees it. Comments go when the page does.
  • Only the items on the page leave your Mac, and nothing else of your library. A reader view does not share your knowledge base.
  • A paper shows its open PDF, which the service fetches. A paper with no open PDF shows its abstract and a link to where it was published.
  • The Opyt connector in claude.ai and chatgpt.com has the same tool.

forget

preview is free confirm removes

Remove local atoms, stop tracking one Oracle, or drop one of an Oracle's other sources, and optionally delete what they brought in. Preview the consequences first, then confirm the same target. There is no foreign knowledge-base target.

forget(atom_id=None, atom_ids=None, oracle=None, source=None, delete_atoms=False, confirm=False) -> dict

Arguments

  • atom_id — optional string, default None. Deletes one atom, its text and search entry.
  • atom_ids — optional list of strings, default None. Deletes several atoms in one preview and one confirm. An unknown id refuses the whole list and nothing is removed.
  • oracle — optional string, default None. A canonical id, locally known handle, URL or exact roster name. Ends that person's subscription and refresh registrations. Existing atoms, identity records and trust roots remain.
  • source — optional string, default None. One of an Oracle's other places, by URL: a Substack, blog or GitHub Opyt found and added for them. Opyt stops pulling it and never adds it back, even if their profile still links it; their other sources keep going. When two Oracles share it, also pass oracle to choose. The profile they were added by is not a source: to drop that, forget the Oracle.
  • delete_atoms — boolean, default False, with oracle or source. Also deletes every atom Opyt pulled from that person's own sources, or from that one source, each as completely as atom_id would. Atoms you saved yourself, such as bookmarked posts and saved links, remain.
  • Specify exactly one of atom_id, atom_ids, oracle and source. An ambiguous name returns candidates; choose a canonical id and preview again.
  • confirm — boolean, default False. The preview includes human identity and finished consent sentences to read before setting it to True. Removal makes no model calls.

Returns and consequences

status is preview, forgotten, not_found, ambiguous, refused (a source that is the profile the Oracle was added by) or error. Atom previews include description, who_id, when_ts, source_url and reference counts. Oracle previews include canonical_id, name, atoms_kept and sources_removed, plus atoms_deleted with delete_atoms.

An X post still bookmarked on x.com can return on an hourly bookmark pass. A tracked author's work can return on a due refresh; ending their subscription prevents future scheduled pulls. A materialized Frontier candidate remains recorded and will not be admitted again automatically.

A shared copy loses a removed atom at its next upload, within about an hour while your Mac is awake. Readers can receive the previous copy before then. There is no local undo.

Where content comes from.

Seven curation signal feeds use an OPYT-managed signed-in X or Substack session, with no API key and no password. Locally, OPYT opens that profile; on an assistant's computer with no browser, X signs in through a private browser on useopyt.com. Everything else is public or handed over by you.

Xbookmarks · likes · follows · Listsyour connected OPYT-managed session
Substackfollows · subscriptions · saved posts, full textyour connected OPYT-managed session
GitHuba tracked person's repos and READMEspublic
Blogsa tracked person's whole archive, found by feed or sitemappublic
arXiv · GitHub · OpenAlexnew papers and repos that name what a topic you follow watches, added once your AI keeps them; tracked OpenAlex authors and research venuespublic
X searchnew posts that name what a topic you follow watches, added once your AI keeps them, and what look findsyour connected OPYT-managed session, on a local install
Substack searchnew newsletter posts that name what a topic you follow watches, added once your AI keeps them, and what look findspublic
A link you hand ita supported paper, repo, post or article for hopperyou

Opyt only reads. It never posts, follows, likes, or changes anything on any account. A resident process on your machine, opyt-worker, runs each feed as a bounded one-pass job — so pulls continue while your client is closed, and each feed decides for itself whether there is anything new before it asks. A feed starts running when you give it something to do: you connect a platform during setup, you track a person, you share your knowledge base. Nothing is scheduled on a fresh install.

The growth loop.

This is the part that runs without you. A topic you follow is what creates the watchers for its subject.

  1. A topic keeps watches. Following one with watch adds standing searches outside your store, one per spelling of each name, and those become durable queries. A watch's identity is a hash of its normalized text, so asking the same thing twice does not create a second watcher.
  2. They run every day against arXiv, GitHub, OpenAlex, X and Substack, and the worker runs whatever is due whether or not your client is open. A watch runs while a topic you follow owns it; dropping it, or the topic, stops it.
  3. Finds wait for your agent's check. They stay out of your library, and a line saying a topic has new finds can reach your client in a search response for your own knowledge base.
  4. You see them in latest, checked first: only what is worth your time, each with the watch that found it.

Sharing.

You say "share my knowledge base" and get a link. They paste it. Their next question can draw on everything you have read. Nobody opens a terminal, and the copy keeps itself current.

  1. You share. share shows you what is in there first (how many atoms, whose, over what span), then registers you, starts the upload, and hands back a link. Publishing is self-service: no invitation, no account, no waiting. The first upload runs in the background and takes a minute or two; the link works immediately.
  2. They accept. The link opens a page with one line to paste into their client. Someone who uses claude.ai or ChatGPT in a browser adds the Opyt connector and pastes it there, with nothing to install; someone who wants a library of their own installs Opyt on a Mac first. accept registers your knowledge base on their machine, and from then on their agent passes kb="your-name" to search, open and aggregate. Those three over HTTPS. No upload, no grant, no write of any kind.
  3. It stays current on its own. Every hour while your Mac is awake, and every five minutes for the hour after you share or connect Google, the served copy is updated if your library changed, so a reader is at most an hour behind you. Ask for share(refresh=True) to update it at once. The copy is made on your Mac, so while it sleeps, readers see the last upload.
  4. You stop whenever, with one person or with everyone. After reviewing the preview, unshare(reader="Leo", confirm=True) cuts off Leo and leaves the copy serving for everybody else. unshare(confirm=True) deletes everything the service holds for it (the copy, every reader, the read counts and the Google link) in one act. Either way it takes effect on that reader's very next request: there is no refresh cycle and no window. Without confirm=True, either call only previews the removal.

To pass on the sources of one answer rather than the whole knowledge base, ask for a reader view: reader_view puts every item the answer cited on one private link, in full, where whoever you send it to can comment.

Every result from a shared knowledge base is labeled with whose it is, so an answer built from a friend's research says so. It goes one way: accepting gives you read access and gives them nothing, and Opyt offers once, after your first search that finds something, to share yours back.

The invite code travels in the URL fragment, which is excluded from the browser's initial page request. accept sends the code to the sharing service's redemption endpoint to obtain a reader token. The name a reader searches under is theirs to pick and never has to be unique; the address in the URL is an assigned key nobody types. What the service records is that a read happened, never what was searched for. TELEMETRY.md is the full schema, checked against the code by a test.

How calls run.

Opyt is free and MIT-licensed, and it calls no model at all: search matches your words rather than their meaning, and images are not read.

Your AI clientevery question, every answer, all the reasoningyour configured client
Xposts, timelines, profiles, threadsyour OPYT-managed browser session
Substacksubscriptions, saved posts, full textyour OPYT-managed browser session
Everything elsepapers, blogs, GitHub, supported article pagespublic source access

Failure handling depends on the operation. An import can keep items already saved when a later source read fails; it reports the error or incomplete pull so the remaining work can be retried. A first share can register the installation before invite creation fails; retrying then uses that registration. Read the tool's result to see what completed and what remains.

useopyt.comwhat Opyt is for
use casesnine questions it answers, with the tools that answer them
Opyt vs the alternativeshow it compares to read-it-later apps, notebooks, agent memory and Grok
github.com/maimond123/Opytsource, issues, MIT license
hello@useopyt.comquestions and feedback
© 2026 Opyt Built with ❤️ in New Jersey MIT Use cases Compare Docs GitHub