A local companion that keeps thinking when nobody is talking to it.
Most agents are ask and forget: you give an order, they execute, they wait. Curio is the other way around: forget, and watch it ask you. It interviews you once, builds a model of you made of hypotheses (each with a confidence), knows what it doesn't know, and never sits idle: on a computer of its own it goes after things it wants, imagines new ones when it runs out, decides what's worth your attention, and learns from how you react. You hold the brake.
$ curio run
Heart awake. (brake released, Ctrl-C to stop)
[09:15] went after Get to know my own computer · worked on #1 Get to know my own computer → done
[09:22] went after A 60-second demo of Curio · worked on #2 … → done
[09:30] glanced, let it go — only a lockfile changed
[10:45] thought (2748 tokens) → 2 new initiative(s) · told you #2 Launch checklist for Curio
$ curio brake light # a breath between things; firm = only with a reason; full = stop
Curio runs on your machine, against any OpenAI-compatible model (Ollama, llama.cpp, LM Studio, vLLM, OpenRouter, a private router), or optionally against Codex app-server with ChatGPT sign-in.
cargo install --path . # Rust 1.97+
curio status # creates ~/.local/share/curio/curio.toml and checks the model
$EDITOR ~/.local/share/curio/curio.toml # point [model] at your endpoint
curio setup # the interview: 15–30 min, resumable (/pause)
curio run # wake the heartFrom then on:
| Command | What it does |
|---|---|
curio chat |
Talk. It opens with what it thought about while you were away. |
curio topics [--all] |
The conversations it started with you, open (and archived). |
curio topic 3 ["reply"] |
One conversation's history, or your reply in it (--archive, --reopen). |
curio inbox |
What it brought up that's still waiting for a reaction. |
curio react 3 yes|no|later "comment" |
Tell it how an idea landed (sim/nao/depois work too). |
curio mind |
What it believes about you, how sure it is, what it still doesn't know. |
curio forget 12 |
Make it drop a belief. |
curio journal |
Its inner monologue: every time it woke up and thought, and why. |
curio think |
Make it think right now. |
curio setup |
Run it again any time to fill gaps or change permissions. |
curio status |
Config, model reachability, memory, today's budget. |
curio model list |
Model slugs from the configured provider, without running inference. |
curio model login [--device-code] |
Sign in with ChatGPT when the Codex provider is enabled. |
curio model logout |
Sign out of Curio's Codex account. |
To keep the heart beating in the background, see contrib/curio.service
(systemd user unit) or run curio run --once from cron.
curio web: a live view of what Curio is doing.
back to back when released only when it's worth it (tokens)
┌────────┐ ┌─────────────────────────┐ ┌─────────┐ ┌──────────────────────────┐
│ sense │──▶│ notice │──▶│ triage │──▶│ think │
│ repos, │ │ · you talked to it │ │ (cheap │ │ memory + what changed + │
│ folders│ │ · something changed │ │ model) │ │ how past ideas landed │
└────────┘ │ · curiosity is due (6h) │ └─────────┘ │ → suggestion, question, │
│ · else: its own wants │ │ insight, draft — or │
└─────────────────────────┘ │ nothing │
└────────────┬─────────────┘
┌──────────────────────────────┐ ┌───────────────────────────────┐ │
│ learn │◀──│ deliver │◀─┘
│ yes / no / later / silence │ │ one at a time, within your │
│ move the beliefs behind it │ │ quiet hours and daily limit │
└──────────────────────────────┘ └───────────────────────────────┘
- Drive by default, brake by choice. With the brake released (the default) it is
never idle: when nothing calls for it, it takes the next step on something it wants,
on its computer, and starts the next beat right away. Its wants are its own: a gift
(something for you, built from what it knows about you), a growth (makes it more
capable) or a play (because it's interesting). From nothing, it starts by exploring
its own computer; when it runs short, it imagines new wants from what it found, made
and knows, and says what its choices show it becoming (
curio status).curio braketurns it down:light(a breath ofbreath_minutesbetween things),firm(only with a reason, the old heartbeat) orfull(nothing on its own). Its pursuits stop at 80% ofmax_tokens_per_day(0 = no cap), so answering you always has the rest. - Sensed changes are a decision. Worth a think, yes or no, and how sure. A think
takes
worth_thresholdsureness, and the bar rises as the day's tokens run out. - Decisions are choices. Every "should I?" (is this worth a think, is this idea new,
did you ask for work) goes through one narrow call: a situation, a question, a few
numbered options; the model picks one and says how sure it is, and the code holds the
threshold.
decidecan point those at a small fast model; a dedicated decision model could answer them natively. - Restraint is for your attention, not its time. A think may end with "nothing worth raising", and that's recorded in the journal like everything else.
- One initiative per beat, within your rules. Quiet hours, a daily limit and a
minimum gap. While the folders it watches keep changing you're mid-flow, so it waits for
a pause (
pause_minutes). Everything else waits incurio inbox. - It learns what lands. Per kind (suggestion, question, insight…) it keeps a rate:
welcomed raises it, turned down lowers it, ignored lowers it half as much (maybe you
never saw it), and reactions from two weeks ago count half. What waits to be delivered
is ordered by how sure it was times how that kind tends to land, and every think sees
the rates.
curio statusshows them. - Changes settle first. After a think about what changed, new changes pile up for
refractory_minutesand are thought about together: one think about an afternoon of commits, not five. - Conversations, not a feed. Everything it brings up is a post in a topic: a thread
it started on its own and keeps going. You can reply in any of them (
curio topic 3 "…"or the web), and it answers there; what's said in every topic feeds the same memory. It comes back to a topic when it has something new, archives the ones that ran their course, and reopens one when there's more to say. A topic waiting on your answer takes nothing more until you answer. - An editor before you. A new topic is judged against the conversations you already have: new, flattery, or the same subject in other words (then it goes in that one instead). Adding to a topic is judged against the thread itself: does it move it forward, or say again what's there? The cheap model picks and says how sure it is; only a sure answer holds it back or moves it.
- It thinks on from where it was. Each think sees its last few thoughts, so it builds on them instead of having the same one again.
- It sleeps. Once a night, in your quiet hours: questions it stopped pursuing for a week are let go, hunches nothing backed in ten days fade a little, and beliefs that say the same thing are merged (found by code, confirmed by the cheap model, checked to add nothing new). Topics nobody touched in a week are archived, and what was said in the busy ones is folded into their summaries (checked to use the thread's own words), so a long conversation stays in view without all its turns. The journal says what it tidied.
Curio can have a computer of its own: one machine, always on while the heart beats, where it works on its own projects. When it thinks, besides deciding what to bring you, it may decide there's something worth doing there: build a tool that could help you, prototype an idea, analyze something it knows about, chase an open question, or continue a project it started. Nobody hands it tasks; it uses the computer because it has one.
$ curio run --web
Computador ligado.
[10:00] thought (2210 tokens), nothing worth raising · worked on #4 Export script → done
[16:00] thought (1980 tokens) → 1 new initiative(s) · worked on #5 Export script, with tests → done
- Projects, not errands. Each piece of work belongs to a project folder in
/work/projects/, with aNOTES.mdit keeps for itself. The next session starts from those notes, and the next think sees every project's last result and next step. - Its work stays its own unless it's worth your attention. A session ends with a
summary and a
showflag: only results it judges worth seeing reach you, as an initiative with a report, within the usual contact rules. The rest is in the journal and the web. Withautonomous = falseit asks before each piece of work instead, and then always reports back. - It can have a screen. With
screen = truethe machine runs a virtual 1280×800 display (Xvfb + xdotool): it starts graphical programs, takes screenshots, clicks, types and presses keys, and sees the screen after each of those. Screenshots reach the model as images, through Codex or any OpenAI-compatible server; a model that can't read images keeps working through the shell. - You can watch it. The Computer tab of
curio webshows its screen: live while it's using it (refreshing even while it thinks, with a ring where it just clicked), dimmed when it's idle. Every session can be replayed frame by frame, with each step's command and output. Without a screen it shows the terminal instead. - It's a sandbox, not your machine.
bwrap(Linux, rootless, no daemon) gives it this system's programs read-only (Python, git, compilers…) plus one writable folder,machine/in its home, seen as/work. No home folder, no environment variables, no network unless you allow it.podman/dockerwork too, with any image. - Or just the sites you list. With
network = falseandallow = ["pypi.org", "files.pythonhosted.org", "*.github.com"], it still has no network of its own: a bridge inside hands every connection to a proxy in Curio, on a Unix socket, and only listed hosts get through (ports 80 and 443 unless an entry names one).pip,npm,gitandcurljust work for those; anything else gets a 403 that says why. A name never leads into your machine or your network (loopback, private, link-local) unless you list that address itself, so your model server andcurio webstay out of reach. - Off until you turn it on, in
curio setupor[machine]. - Claims are checked. Each step is a small JSON action (
shell,write,done, andscreenshot,click,type,key,scrollwith a screen) validated in code: writes can't leave/work, clicks must land on the screen, keys must be key names, "done" with nothing run counts as unfinished and is never shown, and results list the files that actually exist. - Ask it in conversation. In
curio chator the web it knows it has a computer and what it did there (the record, never more). Ask it to do something there, or say yes when it offers, and the conversation's extractor proposes the work; a second, cheap opinion has to agree you really asked ("you could build that someday" isn't asking) before it's queued as your work. It starts within seconds and reports back. - Free time (brake
firmonly; released, all its time is its own). With nothing else going on, at most everyfree_hours, and only from the first half of the day's tokens, it picks something of its own to do: the next step on a project, a tool it wants, an open question it can chase by trying things. It may also say it'd rather not, and that's recorded too. - It goes where it's learning. Each project gets a state from its sessions' outcomes: learning (results are changing: where it learns most), steady, keeps working (three successes in a row: finished, or time for something harder) or stuck (three without finishing). Free time sees them, and code won't let it walk into a stuck project the same way again; another approach is fine.
- It shapes its own computer. Commands it writes into
/work/binare on its PATH;pip installandnpm install -gland in/work/.local(with network);/work/boot.shruns at every boot, so it can set the machine up the way it likes. - Know-how with a track record. Things it works out it keeps as skills
(
/work/skills/<name>/SKILL.md, Agent Skills style). Every session lists them with how often a command that used them ran and how often it worked: trust is earned by running. - It reads the web as text. With any internet (all of it, or the sites you allow) it
gets a
readaction: a page comes back as its title, readable text with numbered links, form fields and image descriptions, never scripts or styles. Plain fetch by default;renderruns the page's JavaScript first in a headless browser, if the machine has one. Addresses must be http(s) in one piece, and it all happens inside the sandbox, through the same proxy. - It reads the screen as text, too. After every screen action it gets the focused window and the open windows' titles, which helps a model that sees and is all a model that can't has.
- Same budget. Every step is a model call paid from
max_tokens_per_day; a session stops atmax_stepsor when the day's tokens run out, and says how far it got. It does one thing at a time.
curio machine lists its projects and sessions, and curio machine log N replays one
step by step. (curio machine do "…" exists for trying it out.)
curio mind shows it all. Every belief is a hypothesis with a confidence and a trail
of evidence:
- Something you said about yourself starts at 85%. Something it inferred starts at 20–60%: one remark is a weak signal. What you say about the world is your claim, not a fact, so it starts as an inference too: memory shouldn't turn one remark into an authority it'll defer to later.
- Supporting evidence closes 30% of the gap to certainty. Contradicting evidence keeps 60%. Below 15% a belief is retired, but kept for history.
- Grounding. A model may claim you said something, but the claim only stands if it's visibly made of your words. Evidence can only move beliefs it is actually about. This is enforced in code, not left to the prompt, because small models get it wrong.
- Episodic memory. Everything it learned from and everything it thought is indexed (SQLite FTS5). Each think, free time and chat turn uses the moment as a cue and brings back a few older episodes, from before what's already in view, as excerpts with their date: something you said three weeks ago comes back when today calls it. Names are never a cue, since yours is in every conversation.
- The interview asks what would teach it most. Each turn the interviewer offers up to three questions; code drops any it already asked (or its rephrased twin), and a narrow decision, knowing the last answers and what you said you don't want to talk about, picks the one that would change the most about what it believes.
- Open questions are first-class. They drive the interview and its curiosity, and they're capped (a curiosity budget, not a backlog).
- Hard rules you set in setup are absolute. Only you can remove them. Since they never move, a line that reads like something about you ("I learn by doing") rather than a rule ("never…", "don't…", "nunca…") only becomes one if you confirm it.
- Reactions are evidence. Yes reinforces the beliefs behind an idea, no weakens them gently (the idea may have been bad even if the belief was right), and silence for 48h counts as ignored.
Curio's capabilities are deliberately few and explicit. It can:
- think, using the model you configured;
- write drafts (plans, outlines, checklists) into its own
workspace/folder, if you allow it; - notify you: desktop notification and/or a Discord/Slack webhook, if you allow it;
- look at the folders you list (changed files, small text excerpts) and follow the git repos you list (new commit messages);
- use its own computer, a sandbox that can't see your files, for its own projects, if you give it one.
It cannot send messages on your behalf, run commands on your system, or touch anything else.
Permissions come only from curio setup / curio.toml, never inferred from a conversation.
curio.toml lives next to the database (default ~/.local/share/curio, override with
--home or CURIO_HOME) and is commented. The parts you'll touch:
[model]
provider = "openai-compatible" # default; existing configs keep working
base_url = "http://127.0.0.1:11434/v1" # any OpenAI-compatible endpoint
api_key_env = "" # NAME of the env var holding the key, never the key
thinker = "qwen3:8b" # the real thinking, the interview, the conversation
triage = "" # reads evidence, tidies memory; empty = thinker
decide = "" # narrow yes/no and which-one calls; empty = triage
[heart]
brake = "released" # released | light | firm | full — `curio brake` turns it live
breath_minutes = 10 # with a light brake, the pause between one thing and the next
tick_minutes = 15 # how often it looks around when braked
curiosity_hours = 6 # think at least this often even when nothing changed
worth_threshold = 0.6 # how sure a sensed change must be worth a think (rises with spend)
refractory_minutes = 60 # let sensed changes pile up this long after a think about them
[contact]
max_per_day = 3
quiet_hours = "23:00-08:00" # also when it sleeps
pause_minutes = 20 # wait for the watched folders to go still before interrupting
[machine]
runtime = "off" # "bwrap", "podman" or "docker" gives it a computer
network = false
allow = [] # or only these sites, through Curio's proxy: ["pypi.org", ...]
screen = false # a virtual screen it can see and click
autonomous = true # false = it asks before each piece of work
free_hours = 4 # free time on its computer at most this often (0 = never)
max_steps = 15Models. It has run end to end with Qwen3-4B-Instruct (Q4_K_M on llama.cpp, about
2.5 GB): interview, beliefs, heartbeat, drafts, reactions. That is enough to watch the
loop work, but a 4B model's ideas are generic, so give thinker the biggest model you
can afford and keep the small one for triage. <think> blocks are stripped and JSON
is dug out of whatever comes back, so json_mode can stay off for servers that
mishandle response_format.
Install the Codex CLI, then opt in by
setting provider = "codex-app-server" in the existing [model] section of
curio.toml. This permits sending Curio's model prompts to Codex using your
ChatGPT account. The local provider remains available by switching back to
"openai-compatible".
curio model login # prints a browser sign-in URL
# For a headless machine: curio model login --device-code
curio model list # list model slugs; no inferenceChoose thinking and triage models independently from that list:
[model]
provider = "codex-app-server"
codex_command = "codex" # executable name or absolute path
thinker = "YOUR_THINKING_MODEL" # also used for interview and chat
triage = "YOUR_TRIAGE_MODEL" # heartbeat filter and learning; empty = thinker
timeout_secs = 300Codex manages the supported ChatGPT login and token refresh flow.
Its private CODEX_HOME is <curio-home>/codex; sign in separately for each Curio
home. Curio never reads your regular Codex credential file or copies tokens into
curio.toml. Browser/device login is explicit; background calls report an error
if sign-in is needed. Model listing checks the catalog, not account entitlement;
an inference request can still report model access or account usage limits.
Each request uses an ephemeral thread with Curio's supplied conversation. Shell,
image reading, web search, apps, plugins, hooks, and delegation are disabled;
the provider refuses requests for tool approval. Curio continues to own its
memory, evidence validation, heartbeat schedule, and daily budget. Token usage
comes from app-server, with the same text estimate fallback as the local provider.
temperature, max_tokens, and json_mode apply to OpenAI-compatible endpoints;
app-server uses its model's output limit and Curio's existing lenient JSON parser.
Everything Curio knows sits in one SQLite file in its home folder, which is created owner-only. Nothing leaves your machine except calls to the model endpoint you configured, including Codex when explicitly selected (and the webhook, if you set one).
cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo testTests drive the whole life cycle (interview, heartbeat, delivery, reactions, learning,
work) against a scripted model (llm::Scripted). No network, no model needed. Where
unprivileged bwrap works, a test also checks the real sandbox keeps the host out. The code is
also tested against a scripted stdio app-server on Unix (Python 3 required).
The implementation stays
small on purpose: one crate, about ten modules. src/lib.rs lists them in the order a
thought flows through them. Read VISION.md before adding anything, and
AGENTS.md if you're a coding agent.
Curio v0.1 (July–August 2026) tried to earn autonomy by building every guarantee first:
10 crates, ~107k lines of Rust, five specs, a 13-stage cognitive-cycle accountability
model. It was safe and recoverable, but it never proposed anything: its discovery
step asked a model for a ~30-field schema in 256 tokens and treated any parse failure
as "no opportunity". v0.2 starts from the behavior instead. The old code is preserved
in the git history, before the v0.2 rebuild (tags v0.1.0 and v0.1.0-mvp mark early v0.1 milestones).
MIT, see LICENSE.
