A Home Manager module for a ready-to-use OpenCode 2 workspace.
Limitless pins OpenCode 2.1.4, supplies coding agents and local code-intelligence
tools, and configures optional first-party MCP connections. OpenCode owns its
service discovery, OAuth credentials, Code Mode, and native
notification delivery. Home Manager owns packages and non-secret configuration, with
optional Linux service supervision.
{
inputs.limitless.url = "github:your-org/limitless";
outputs = { home-manager, limitless, ... }: {
homeConfigurations.me = home-manager.lib.homeManagerConfiguration {
modules = [
limitless.homeModules.default
{ programs.limitless.enable = true; }
];
};
};
}Run opencode, or its oc shell alias, from a project. It discovers or starts
its native background service. Connect model providers through /connect.
Limitless disables OpenCode's built-in browser plugin (opencode.browser) to
avoid conflicts with T3 Code's and Paseo's browser tools. Use either for browser access.
- Agents:
limitless, a research subagent, and technical and design Oracle advisers, plusleadandunitfor native asynchronous implementation in isolated worktrees. - Clients: optional supervised T3 Code and Paseo environments, both served by the one managed OpenCode service, plus opt-in desktop apps for OpenCode, T3 Code and Paseo.
- Skills:
repository-setup, which makes a repository ready for parallel worktrees. - Code intelligence: ast-grep search and replacement, TypeScript/Biome diagnostics, and seven LSP tools for definitions, hover, implementations, call hierarchy, references, symbols, and rename previews.
- Language servers: TypeScript, Biome, JSON, YAML, Markdown, TOML, and Nix.
- Artifacts: project-scoped folders under
.limitless/artifacts/. - Source research: optional guarded GitHub checkouts under
.limitless/repos/. - Service integrations: optional Atlassian, Notion, Sentry, Linear, and GitHub MCP presets with account-specific connection names and audited reads for Research.
- Anthropic subscription authentication: a pinned Claude Pro/Max plugin.
- Git hygiene:
.limitless/is globally ignored by default.
The plugin exposes 13 core tools directly with codemode = false. MCP tools use
OpenCode's native Code Mode. Every Limitless tool resolves the invoking session's
location.directory as its project root.
For a shared self-hosted OAuth gateway, see Executor. Direct connections remain the default and can stay in place during a gateway pilot.
Each entry names one connection. Enable only the services you use:
programs.limitless.mcp.servers = {
atlassian.preset = "atlassian";
notion-work.preset = "notion";
notion-personal.preset = "notion";
sentry.preset = "sentry";
linear.preset = "linear";
};After applying Home Manager, run /mcps, select each connection, and sign in.
Connections are ready only when OpenCode reports them as connected. Notion work
and personal entries have separate OAuth identities even though their URLs match;
authorize the intended workspace for each. Renaming a connection creates a new
credential identity. Token refresh and OAuth state belong to OpenCode.
settings overlays the preset using native OpenCode V2 server fields. For example:
programs.limitless.mcp.servers.sentry = {
preset = "sentry";
settings.timeout.execution = 120000;
};Set settings.disabled = true to retain a configured connection without connecting
it. Additional servers can use programs.limitless.opencode.settings.mcp.servers.
Use one configuration path per server name. Native-only servers receive the same
role-based permissions, with no automatic read exceptions for Research.
The hosted GitHub server requires an explicitly configured registered OAuth client or a PAT. Generic OAuth discovery alone is insufficient. For a registered client:
programs.limitless.mcp.servers.gh = {
preset = "github";
settings.oauth.client_id = "{env:GITHUB_MCP_CLIENT_ID}";
};Include the native oauth.client_secret and callback settings when required by
your registered application. Alternatively, supply a PAT through the OpenCode
process environment:
programs.limitless.mcp.servers.gh = {
preset = "github";
settings = {
oauth = false;
headers.Authorization = "Bearer {env:GITHUB_MCP_TOKEN}";
};
};Keep token values out of Nix expressions and generated files. Ensure the credential
is available to the background service; exporting a variable in a new terminal
does not change an already-running service's environment. The preset enables
context, repository, issue, pull-request, and Actions toolsets. GitHub MCP
authentication is separate from Git clone/fetch/push credentials and the guarded
research clone configuration below. Limitless does not install gh.
Use gh or github-mcp as the connection name. The name github overlaps the
local github_clone tool. The module rejects overlapping tool namespaces and
account prefixes such as notion plus notion_work; use notion-work instead.
Tool access follows the agent's role:
| Agents | Access |
|---|---|
limitless, lead, unit |
Automatic approval for reads, writes, shell commands, and MCP tools except T3 delegation and the Research-only executor-read connection. unit can delegate only to Oracle Solve and Research; it has no T3 or Paseo tools |
oracle-solve, oracle-design |
Read/write MCP access; external mutations require explicit delegation of user-authorized actions. Investigative shell/edit/AST; no T3 or Paseo tools; deny artifact creation and session changes |
research |
Executor's read-only connection, or audited reads in direct-MCP mode; investigative shell/edit/AST; no T3 or Paseo tools; deny external mutations, artifact creation and session changes |
With Executor enabled, Research can use only executor-read; Executor's
connection policy owns read-only enforcement. Direct MCPs are denied to Research,
even while retained for main and Oracles. Without Executor, direct-MCP mode uses
deny rules plus exact audited read exceptions for Research. Read tools such as
web fetch and artifact listing inherit the global allow rule.
Sensitive-file reads and potentially destructive shell commands no longer have default approval prompts. Shared instructions still require explicit user direction for destructive work. Managed-checkout edits remain denied.
In direct-MCP mode, readTools replaces a preset's exact read-tool list for Research.
Add names only after checking their behavior and the authenticated catalog.
Wildcards are rejected. The optional https://mcp.linear.app/mcp/readonly endpoint
can further restrict a Linear connection at the server. Linear's read-tool list
is empty until verified names from an authenticated catalog are configured.
These rules control named-tool access. Shell commands can write files or call
services, and Code Mode's raw HTTP fetch can send write requests. Research
instructions forbid external mutations; Oracles require explicit delegation of
user-authorized MCP writes. The primary agent owns implementation and integration;
the tool permissions do not provide a sandbox. Later project configuration or agent
definitions can override global permissions. Project-local MCP additions need their
own permission rules. A configured server must be trusted to implement its advertised
read operations correctly.
For T3's role-specific tool access and shared-service setup, see T3 Code.
| Preset | Daily workflows | Coverage limits |
|---|---|---|
| Atlassian | Jira and Confluence search, reads, edits, comments, transitions | Organization policies and enabled tool groups determine access. Uses the v2 flat catalog. |
| Notion | Pages, databases, comments, queries, uploads | Workers deployment and generic API administration are outside this preset. Tools can depend on plan. |
| Sentry | Issues, events, traces, debugging | Release/symbol uploads and broad administration require dedicated tooling. Mixed-purpose catalog execution is unavailable to Research. |
| Linear | Issues, projects, comments | Exact read exceptions require authenticated discovery. |
| GitHub | Repositories, issues, PRs, reviews, Actions | Local Git and release uploads require separate tooling and authentication. |
Before relying on a new connection, verify account identity, a representative read, an explicitly requested write, mutation denial in Research, and token refresh. The repository checks generated configuration and policy behavior; account entitlements and live upstream tool catalogs require this authenticated verification.
Limitless can host Executor on NixOS and configure its scoped connections on any OpenCode client. Executor owns upstream account grants; each client signs in to Executor separately. AI-provider authentication is independent and unchanged.
OpenCode clients ── private HTTPS ── Executor ── Notion / Atlassian / Sentry
client grants upstream OAuth and persistent database
Import limitless.nixosModules.executor and enable the service:
services.limitless.executor = {
enable = true;
origin = "https://server-athena.example.ts.net:9443";
};The service uses a pinned official multi-platform container with rootful Podman, bridge networking, and a loopback-only port (4400 by default). It does not add a user to a privileged container group or open the host firewall. The consuming configuration owns private HTTPS routing and its access rules; use a dedicated Tailscale Serve port rather than changing existing routes.
The service enables Executor's official app catalog. Catalog apps declare their own authentication requirements and may use REST APIs rather than hosted MCP. For the existing Notion, Atlassian, and Sentry MCP accounts, choose Add app → Connect a service and enter the official MCP URL; this works independently of the catalog feed.
For first-owner setup, use origin = "http://localhost:4400" and leave the HTTPS
route disabled. Forward the loopback port over SSH and create the owner in your
browser. Verify /api/auth/self-host/config reports setup: false, then set the
final HTTPS origin and enable its route. An empty instance's owner setup is
unauthenticated; do not expose it to the whole tailnet before creating the owner.
Executor creates its database, OAuth encryption keys, auth key, and apps in
/var/lib/limitless-executor/product, behind a root-owned 0700 parent directory.
Podman uses a dedicated subordinate-ID range: its internal UID 1000 is not
the agent's host UID 1000. The product and collector mounts get mapped ownership;
never share these mounts with another service or manually reuse its ID range.
Do not protect backups of its master keys with an encryption identity already
readable by agent processes. The container is
the credential boundary, not a sandbox for the machine's administrator. Never
copy its database or upstream refresh tokens to clients or the Nix store.
Before upgrades, stop the service and back up the whole state directory to a root-only location, then restart it. Database and keys must be restored together. There is no automatic off-host backup or database downgrade guarantee. The collector cache is disposable; treat logs and traces as potentially sensitive.
Create the owner yourself, then connect the official remote MCP services from
the dashboard. One app per service can hold several accounts, such as Notion's
work and personal workspaces; label each account and verify its workspace. For Atlassian, use
https://mcp.atlassian.com/v2/mcp?tools=all and select the intended site. Do not
replace these with REST integrations and assume their OAuth requirements match.
Dynamic client registration can use a private HTTPS callback when the browser is on the tailnet and the provider permits that redirect. Test each actual provider before migration. Do not enable CIMD with a private metadata URL that the provider cannot fetch. Use your own browser for account sign-in, not an agent-controlled browser.
For least privilege, create two scoped connections, excluding Executor's administrative apps:
- Read/write: main and both Oracles use the intended accounts and tools, with model-handled elicitation.
- Read: Research uses a connection configured for read-only access inside Executor. Browser elicitation is a fail-safe, not the read-only boundary.
No upstream read-tool list is duplicated in Nix for Executor mode. This trusts Executor's classification and enforcement, including its treatment of generic execution tools and future catalog changes. Verify that the configured policy rejects writes rather than relying on the connection's label. Connection IDs are non-secret; a connection URL is not authorization. Sign in through OAuth, not a personal access token, which bypasses scoped-connection restrictions.
After creating and verifying the scopes:
programs.limitless.mcp.executor = {
enable = true;
url = "https://server-athena.example.ts.net:9443/mcp";
connection = "primary-connection-id";
readConnection = "read-connection-id";
};Alternatively, explicitly opt main and the Oracles into the broad endpoint:
programs.limitless.mcp.executor = {
enable = true;
url = "https://server-athena.example.ts.net:9443/mcp";
fullAccess = true;
};This grants main and the Oracles the full consented interface, including Executor
administration and all authorized apps. Do not also set connection. Research
remains denied the broad connection; an optional readConnection still
requires a separate read-only scope. One endpoint or scoped connection can be
shared across the fleet, but every OpenCode server authorizes its own grant.
Do not synchronize OAuth token files between machines.
For a remote OpenCode host, set callbackPort = 19876 (and optionally
readCallbackPort = 19877). The browser must reach that OpenCode host's
loopback listener; OpenCode 2.1.4 does not relay callbacks through its frontend.
Open an SSH forward from the browser computer before running /mcps against
the remote backend:
ssh -N -o ExitOnForwardFailure=yes \
-L 127.0.0.1:19876:127.0.0.1:19876 chase@server-athenaForward 19877 too when onboarding executor-read. Leave the waiting sign-in
dialog open until it reports Connected. No tunnel is needed when OpenCode runs
on the browser computer, or for ordinary refresh after authorization. These
ports are unrelated to Executor's upstream account callback. Do not set a
custom redirect URI or forward the OpenCode service port as a substitute.
Run /mcps and sign in to executor and, when configured, executor-read on each device. Their
tokens stay in OpenCode's mutable credential store. Each grant is bound to the exact
MCP URL, which Limitless writes in Executor's canonical form: the connection
parameter, and elicitation_mode only when it is not the default model. Any change
to that URL, including the switch to canonical URLs, needs a new sign-in. With readConnection = null
(the default), Research has no MCP tools in Executor mode. When supplied, Executor
must enforce the read-only scope on every call and execute; Nix cannot verify
the dashboard's mutable policy. Main and Oracles see only executor, and
Research sees only tools from executor-read, including future wrappers whose
authorization remains Executor's responsibility.
These are tool-selection rules, not a process sandbox: agents with shell access
run as the same user and can reach that user's stored credentials. Preserve the
instruction-level restrictions on external mutations. Test a known mutation's
denial through both call and execute before granting Research access.
Executor exposes its own skills/search/describe/call/execute/resume interface,
not the upstream catalogs. codemode = false avoids wrapping it in a second Code
Mode layer. Upstream resources, prompts, session state, URL elicitation, and
OpenCode session metadata are not transparently forwarded. Verify the workflows
you need rather than treating this as a transparent MCP proxy.
Revoke a device under Connected agents; revoke a scoped connection to revoke all its device grants. Keep an onboarding record if clients display identical names. This is not per-project isolation. An unavailable Athena means unavailable gateway tools; native provider accounts and local tools continue independently. An Executor restart can lose paused executions. Inspect effects before retrying a mutation; never automatically replay an uncertain program.
Keep direct MCP connections until all of these are verified:
- ARM64 startup, private routing, owner creation, and closed signup afterward.
- Both Notion identities, the intended Atlassian site, and Sentry scope through the exact callback origin.
- Main/Oracle reads and separately authorized writes; Research mutation denial
for both direct calls and programs. Scoped connections exclude administration;
an explicitly chosen
fullAccessread/write connection for main and Oracles intentionally includes it. - Device revocation, reconnect, upstream refresh after expiry, and restart.
- Persistence/restore, inaccessible state for the agent user, and resource use.
Only then cut clients over one host at a time. Account authorization, mutable scope validation, credential expiry, and actual provider access need live checks; the Nix checks establish generated configuration and permission behavior only.
nix/modules/
├── home.nix # composition and Git hygiene
├── opencode.nix # runtime, agents, plugins, and configuration assembly
├── service.nix # optional Linux supervision and native service binding
├── t3.nix # optional headless T3 service and OpenCode provider binding
├── paseo.nix # optional Paseo daemon and OpenCode provider binding
├── desktop.nix # independent Linux desktop installation
├── darwin.nix # macOS desktop installation through Homebrew
├── lsp.nix # language-server packages and plugin configuration
└── mcp.nix # named connections and permission generation
nix/mcp-presets.nix # first-party endpoints and audited read-tool names
opencode/ # native defaults, agents, and shared instructions
packages/limitless/ # code-intelligence tools, artifacts, source research, policies
Native server settings go in programs.limitless.opencode.settings. Limitless
enforces the default agent, managed plugins, ordered permissions, generated MCP
policies, and the managed-checkout edit denial. Managed plugin directives disable
opencode.browser independently of agent and session permissions.
opencode.extraAgentsFile appends additional instructions.
opencode.disableClaudeCode = true wraps the executable with
OPENCODE_DISABLE_CLAUDE_CODE=1.
Native terminal settings go in programs.limitless.opencode.cliSettings. The
launcher supplies them through OPENCODE_CLI_CONFIG_CONTENT; declared values
override matching preferences in ~/.config/opencode/cli.json. An explicitly
supplied OPENCODE_CLI_CONFIG_CONTENT replaces the generated override. Apply
Home Manager and relaunch the TUI after changing these Nix settings.
Configure language servers through programs.limitless.lsp: enable,
servers.<name>.{enable,package,command,args,extensions,env}, extraServers, and
extraPackages. Limitless owns these definitions in plugin options.lsp.
Project-local OpenCode lsp settings do not change Limitless's tools.
skills.package can supply your own skill directories; skills.enable = false
disables installation. The default package copies a top-level skills/ directory
when present and is otherwise empty. OpenCode's built-in skills remain available.
limitless is the primary user-facing agent. Its default model is configured in
opencode/agents/limitless.md and remains independently selectable in OpenCode.
lead is the design-and-delegation primary agent; unit is its native implementation
subagent. See Lead and Units.
Research uses openai/gpt-6.1-sol#xhigh; Oracle Solve uses
openai/gpt-6-astra#xhigh; Oracle Design uses anthropic/claude-opus-5-5#xhigh.
Subagents keep the processing tier of their configured model.
Limitless owns implementation, integration, and final validation. Research owns focused investigations: explaining behavior, diagnosing complex bugs, testing hypotheses, and proposing complete solutions. Oracle Solve provides independent technical judgment when evidence conflicts, an investigation stalls, correctness is uncertain, or the consequences warrant deeper scrutiny. Oracle Design advises on consequential architecture, interfaces, ownership, and maintainability. Research can resolve a question without an Oracle consultation; either Oracle can be consulted directly when its judgment is needed.
All support agents can run tests and make focused diagnostic edits directly in the shared workspace, including reproduction tests, fixtures, and temporary instrumentation. They report changed files, their purpose, commands, results, and material interference. Limitless reviews the diagnostic changes and decides what to retain or remove; automatic cleanup is not required. Limitless waits when work could conflict or an investigation gates the next change, and otherwise works on independent tasks. All agents account for shared files, services, and test resources and preserve concurrent work. Diagnostic scope and implementation ownership are instruction-level policies; edit permissions cannot distinguish those purposes.
Astra, Sol 6.1, and their Fast modes use conservative client budgets of 872,000 context tokens and 828,400 input tokens, overriding OpenCode's 400K subscription default. These derive from the Codex catalog's override ceiling with 5% input headroom. These budgets have not been verified against the subscription backend. With OpenCode's default 10% compaction buffer, automatic compaction starts at approximately 745,560 input tokens. Reasoning variants, Fast mode, and output limits come from the upstream catalog.
OpenAI models use native provider compaction for automatic and manual requests
by default. Configure providers.openai.settings.compaction.type through
opencode.settings; individual model settings can override the provider policy.
providers.disabled defaults to
[ "google-vertex" "google-vertex-anthropic" ] to avoid ambient Vertex selection.
Lead is a continuing engineering and design conversation. It dispatches each coherent approved design to a Unit, keeps designing with you, and reviews returned work against the design and the Unit's technical review evidence. Unit uses Research and Oracle Solve; design judgment and Oracle Design belong to Lead. This runs on one OpenCode host, including from the native TUI; T3 and Paseo are optional UI adapters.
Lead ── native subagent ──► Unit in a native worktree
▲ │
└── native final response ──┘
Lead uses the existing subagent tool:
subagent({
agent: "unit",
description: "Implement customer pagination",
prompt: "The approved design, context, scope, deliverable, and validation…",
background: true,
})Limitless decorates native Unit creation. After OpenCode's permission, depth,
and model checks, it creates an OpenCode-managed worktree, moves the new child
there, and initializes its model from the destination's effective Unit configuration.
Explicitly requested model overrides are preserved. Other subagents keep their
normal placement. Follow-ups pass the same sessionID to subagent and reuse the
worktree. The depth limit is three for Lead → Unit → Oracle → Research.
The brief and Unit's final response are ordinary prose. No Lead ID, report status, reporting tool, callback subscription, or ledger artifact is required. OpenCode's background job owns the return to the parent, including its durable completion record and managed-server restart recovery. The returned execution outcome is not an assertion that the assignment is complete; Lead reads the answer and decides what follows.
Native worktrees use the project's configured strategy, inventory, and startup
command (project.commands.start). The default Git strategy starts detached at
the Lead checkout's HEAD and preserves Lead's checkout-relative directory.
Uncommitted files are not copied; a directory still absent after checkout and
startup fails placement with its path in the error. Units create named
branches when their requested delivery calls for one. This is checkout isolation,
not an OS sandbox. Worktrees remain after completion and are removed separately
through OpenCode once the work is integrated or abandoned. Provisioning failure
can leave a child or checkout behind; inspect the returned identity and native
worktree inventory before retrying.
OpenCode's separate provider-backed Workspace IDs are an Effect SDK embedding facility in 2.1.4, not a public plugin lifecycle. This implementation uses the native local worktree API without embedding another OpenCode host.
The placement adapter targets OpenCode 2.1.4. It uses the native subagent
executor's awaited progress callback between child creation and prompt admission;
native schemas, execution hooks, authorization, and background jobs remain in
place. Other versions refuse Unit dispatch until this ordering is reviewed.
The units-runtime check exercises real OpenCode with a scripted model and
isolated storage, without T3 or Paseo.
Deferred upstream bug: a follow-up admitted while the prior execution settles can complete in the child without its automatic result reaching Lead. This was reproduced under controlled scheduling in 2.0.32 and in the latest stable V2 release checked on 2026-10-26, 2.1.4. The single-machine proof intentionally keeps the standard runtime and this known limitation rather than introducing a second reporting system. Native recovery does not guarantee exactly-once execution or external side effects; interrupted launches require inspecting the existing child before another dispatch.
Run /connect, select Anthropic, and choose Claude Pro/Max. The plugin is pinned
to ex-machina-co/opencode-anthropic-auth
release v2.0.0-next.3, commit e03f023c8ea1771a829cee51024b8675ffede24c. API-key authentication retains
standard Anthropic request behavior.
Warning
Anthropic does not officially support using Claude Pro/Max through OpenCode.
This compatibility path may violate its terms or put an account at risk.
Disable it with programs.limitless.plugins.anthropicAuth.enable = false.
Connecting Max replaces the saved Anthropic API-key credential and vice versa.
Credentials from the earlier experimental Limitless plugin require signing in
again. OpenCode continues to display API prices for subscription models.
The upstream plugin targets SDK 2.0.4 and reports Claude Code compatibility
version 2.1.280. When Anthropic returns a structured newer-version requirement,
it adopts that exact minimum and permits one retry for the affected session,
agent, and model. Ordinary errors and rate limits do not trigger this recovery.
An optional override is available when an explicit version is required:
programs.limitless.plugins.anthropicAuth.claudeCodeVersion = "2.1.280";The option defaults to null, preserving the bundled version and automatic
recovery. Setting a version supplies ANTHROPIC_CLAUDE_CODE_VERSION to both managed
and on-demand OpenCode servers and disables automatic version adoption. An
explicit environment value takes precedence. The plugin reads it at startup and
uses it for both the user-agent and billing metadata; apply Home Manager and restart
the OpenCode server after changing it. This setting controls the plugin's reported
compatibility version, independently of any installed Claude Code executable.
Refresh rotation is deduplicated within a process. ANTHROPIC_BASE_URL can override
the endpoint; ANTHROPIC_INSECURE cannot disable TLS verification.
programs.limitless.github = {
enable = true;
allowedRepos = [ "owner/repo" ];
tokenFile = "/run/agenix/github-read-token";
};Without tokenFile, the tool reads tokenEnv, which defaults to GITHUB_TOKEN.
Use a fine-grained read-only token for private repositories. The token is passed
to Git through ephemeral github.com-scoped environment configuration and never
written to URLs, arguments, tool results, or repository configuration.
allowedRepos must be nonempty unless allowUnrestrictedRepos = true. The policy
also covers every transitive submodule; non-GitHub hosts are rejected. Clones are
depth-one, project-local snapshots. Default-branch requests refresh immediately;
explicit branches, tags, and SHAs use distinct deterministic paths. Dirty checkouts
are never overwritten. New clones are staged and published atomically. LFS objects
are not materialized.
Managed repositories are read-only supporting source. The edit policy denies
normal writes below .limitless/repos/; shell access is not sandboxed. Set
git.ignoreStorage = false if the repository should manage .limitless/ itself.
artifact_create creates a project-local folder under .limitless/artifacts/ with
a manifest recording its slug, optional title, timestamp, and creating session.
Use normal file tools to add notes, source, assets, or generated outputs.
artifact_list reports valid artifacts and folders with invalid/missing manifests.
Explicit duplicate slugs fail; generated slugs include a random suffix. Failed
creation cleans up only its own incomplete manifest and empty folder.
OpenCode starts its native background service on demand by default. Use
opencode service status and opencode service restart to inspect and manage it.
For automatic startup at user login and process supervision on Linux, enable the Home Manager systemd user service:
programs.limitless.opencode.service = {
enable = true;
hostname = "127.0.0.1";
port = 4096;
};hostname defaults to 127.0.0.1; port defaults to OpenCode's native 49374.
Declare 4096 explicitly to retain an existing Tailscale proxy target:
Tailscale HTTPS → 127.0.0.1:4096 → OpenCode
Reuse the declared port in your proxy configuration. The listener stays on loopback, and OpenCode's native authentication still applies through the proxy.
The service persists hostname and port through native opencode service commands
when they differ, stops any existing native daemon, then runs serve --service
in the foreground. It preserves the private password, CORS configuration, and
configured service environment. Private service files remain writable and outside
the Nix store. Disabling supervision leaves the last persisted binding in place.
With mcp.executor enabled, the unit first waits up to about 60 seconds for the
Executor endpoint to answer HTTP (any status below 500, normally 401). Tailnet DNS
and the gateway can come up after user services at boot, and OpenCode does not
retry an MCP connection that failed at startup. It starts anyway after the timeout.
Enabling, starting, or restarting this unit can interrupt active work and persistent terminals. The unit takes ownership of the shared native daemon. While enabled, use systemd for lifecycle commands:
systemctl --user status opencode
systemctl --user restart opencode
systemctl --user stop opencodeopencode service stop is normally undone by supervision. A local OpenCode client
can still start a native daemon after the unit is stopped. Keep local clients on
the configured OpenCode version; clients that replace the daemon with another
version can conflict with supervision. Restarting a failed process is automatic;
this unit does not detect a live but unhealthy process.
Home Manager applies changed units automatically when systemd.user.startServices
is enabled. Changes to the runtime, binding, configuration, agents, or installed
skills trigger a restart. With service switching disabled, apply the suggested
systemd commands yourself.
The unit starts with the systemd user manager, normally at login. Boot startup is optional: user lingering starts that manager before login and keeps it running after logout. To opt in, set this in your NixOS system configuration:
users.users."your-user".linger = true;On other systemd distributions, an administrator can enable lingering with
loginctl enable-linger <your-user>. Tailscale's proxy must also start at boot.
Limitless installs OpenCode Desktop, T3 Code and Paseo through independent flags:
programs.limitless = {
opencode.desktop.enable = true;
t3.desktop.enable = true;
paseo.desktop.enable = true;
};On x86_64 Linux, import limitless.homeModules.default into Home Manager.
The flags install pinned AppImages, also exported as packages.opencode-desktop,
packages.t3-desktop and packages.paseo-desktop. Each has a desktop.package
override. Desktop-only use does not require programs.limitless.enable and does
not provision agents, configuration or background services.
On macOS, import limitless.darwinModules.default into nix-darwin and set the
same flags in the system configuration. They install the native Homebrew casks
opencode-desktop, t3-code and paseo. Homebrew must already be installed.
The module enables Homebrew by default when a desktop is requested, but leaves
its upgrade and cleanup policy to the host. Cask versions follow Homebrew rather
than the Linux pins, and apps may update themselves. Use cleanup "uninstall",
not "zap", to preserve app data when removing or switching clients. Paseo's cask
also installs its CLI; cask upgrades/removal can stop the Mac's local Paseo daemon.
Linux Home Manager flags → pinned desktop packages
macOS nix-darwin flags → native Homebrew casks
The Linux desktop is pinned to the Limitless OpenCode release (packages.opencode). When the
Limitless workspace is enabled, module assertions reject a desktop/CLI version
mismatch: Desktop can stop and replace a registered local service at startup
when its bundled CLI version differs. Other, independently installed CLIs are
outside this check.
With the matching managed service healthy, Desktop adopts that service. If no healthy service exists, it starts its bundled CLI, without the Limitless launcher environment; systemd may then take ownership again and interrupt work. Start the managed service before opening Desktop and close Desktop before runtime upgrades. Selecting a remote server in Desktop does not disable local service startup. On Macs without a configured local Limitless workspace, this local service uses ordinary OpenCode configuration. Add remote servers through Desktop's pairing UI.
T3 and Paseo desktop installation likewise does not configure their local backend
settings. Follow the sections below when attaching them to managed Linux services.
Their Linux packages ship with matching server versions; independently updated
clients need compatible protocols and feature support. After upgrades, restart
the T3 and Paseo services when idle: their keep-old policy preserves running work.
Use the packaged opencode-t3 launcher to connect T3 to the existing managed
OpenCode service. It starts only a loopback proxy, never another OpenCode server.
OpenCode's native subagent tool owns Research and Oracle delegation and preserves
native child sessions and events. Both Oracles can invoke Research; Research cannot
delegate further. Resume an investigation with its sessionID.
On Linux, let Home Manager run T3 independently of its desktop GUI:
programs.limitless = {
enable = true;
opencode.service.enable = true;
t3.enable = true;
};T3 desktop / web → t3code.service → opencode-t3 → opencode.service
The t3code user service listens on 127.0.0.1:3773 and uses the existing
~/.t3 home, preserving threads and accounts. Options t3.port, t3.baseDir, and
t3.package override these defaults. Enable t3.desktop.enable for the GUI. Linux user
lingering and a private HTTPS proxy belong in the host configuration, as described
above; T3's own Tailscale Serve management is disabled.
For NixOS browser previews and HTML rendering, enable programs.nix-ld.enable = true
in the system configuration. The T3 package supplies its downloaded Chrome's
libraries through a nix-ld path inherited by T3's process tree, including
terminals and provider CLIs. It preserves inherited library paths and the host's
loader without introducing an architecture override. It does not add global libraries or set
LD_LIBRARY_PATH. Keep Limitless's nixpkgs input following the host's input so
the browser libraries and host loader stay compatible. Chrome's sandbox remains
enabled; the host must support unprivileged user namespaces. T3's suggested
sudo t3 browser setup uses apt and is not the NixOS installation path.
Before the first switch, finish active work, back up your T3 home, and close the
desktop's local environment. On startup, the service enables the default OpenCode
provider, points it to the absolute opencode-t3 profile path, and clears any remote
Server URL. It also sets localEnvironmentEnabled = false for the desktop sharing
that T3 home. Reopen the desktop and add http://127.0.0.1:3773 as a remote
environment, using a pairing link from t3 pair --base-dir "$HOME/.t3". Pairing
credentials remain runtime state, never Nix configuration. A desktop on another
machine must have its local environment disabled separately if desired.
Only those settings are managed, at service startup rather than activation.
Unrelated fields and file modes are preserved; settings must be regular files
containing plain JSON. JSONC, symlinks, malformed data, or a live server PID block
startup instead of being overwritten. t3.opencode.enable = false stops managing
the provider binding without undoing existing settings. Disabling the module does
not revert the mutable desktop or provider settings.
After an unclean shutdown, PID reuse can leave a stale runtime record blocking
startup. The error identifies the PID and file. Stop retries with
systemctl --user stop t3code, close any desktop backend, and inspect that PID.
Only after confirming the record is stale and no T3 backend uses this home, move
userdata/server-runtime.json aside and start t3code again. Do not kill an
unrelated process or remove settings or databases. Timestamps are not used to
bypass this guard because clock changes can make a live process look stale.
Home Manager leaves an already-running T3 service on its old version. When idle,
run systemctl --user restart t3code to apply changes. OpenCode configuration
changes can still restart OpenCode during a switch. Do not use t3 service install
or t3 service update alongside Home Manager: they own the same unit. T3 requests
OpenCode startup when both units are enabled, but stopping either does not stop
the other. With external OpenCode supervision, ensure that service is available.
For an independently managed T3 desktop or server:
- Finish or stop active work before applying Home Manager or restarting OpenCode;
a service restart can interrupt sessions. Load the updated plugin with
systemctl --user restart opencodewhen supervised, oropencode service restartotherwise. Check the endpoint withopencode service status. - In T3 Settings → Providers → OpenCode, set Binary path to
opencode-t3and leave Server URL blank. If T3 does not inherit your PATH, use the absolute path printed bycommand -v opencode-t3. Reconnect the provider.
Leaving Server URL blank lets T3 attach its thread-scoped MCP credentials, including
browser access. The launcher discovers and authenticates to the existing service;
no password needs copying into T3 settings. Other CLI use still goes through opencode.
All agents lose T3's delegate_task, task_status, and task_cancel, plus the
broken HTML visual tools html_preview and html_render. Primary agents
(limitless, lead) retain T3's other tools, including top-level thread
creation and management.
Unit, Research, and both Oracles receive no T3 tools, including browser tools and
future additions. They are hidden from Code Mode and search; there is no
execution-only deny hook. Subagents return findings and blockers through their
native results. By default, unbound TUI/CLI sessions do not receive thread-bound tools.
The plugin removes the blanket session allow that T3 Full access writes, which would
otherwise erase native role restrictions. Native configuration remains authoritative,
with T3's operator asks, Plan restrictions and specific grants preserved. The bridge
forwards T3's permission writes unchanged; the plugin repairs each one from OpenCode's
session.created and session.permissions events, in commit order, and again before
every prompt, including a native child's first. Without the Limitless plugin, T3's
rules stay as written, and native children inherit them. Normal mode selects the actual
configured default agent. Limitless ships with the built-in Plan agent disabled:
T3 Plan turns fail with agent-not-found unless a Plan agent is configured. Returning
to normal mode restores the default agent.
Only T3's named t3-code instruction entry is replaced, with a neutral marker before
persistence. The plugin expands that marker into T3 guidance for primary agents
when preparing model requests. Unit, Research, and both Oracles receive no T3
guidance, and any inherited marker is removed from their outgoing requests.
The primary template lives in packages/limitless/t3/prompts/; the base agent
Markdown files are unchanged. Ordinary sessions receive no T3 guidance.
Other context, conversation content, and
subagent events pass through unchanged. Existing provider history is not retroactively
erased; use a fresh thread for the cleanest instruction cutover.
Closing or reconnecting T3 only detaches the bridge. Native sessions and background children keep running, and can continue consuming tokens and changing files. Use explicit Stop before disconnecting when cancellation is wanted. T3's MCP cleanup calls pass through; browser/app capabilities may be unavailable while disconnected. The bridge never interrupts sessions or stops the service.
The bridge (packages/limitless/t3/{cli,bridge,rewrites}.ts over the shared
packages/limitless/bridge/) is an Effect loopback proxy. It authenticates T3,
forwards HTTP and server-sent events to the managed service with that service's
credentials, and rewrites three requests: the
t3-code instruction entry, T3 MCP server names, and normal mode's build agent.
It rejects WebSocket upgrades, which T3's OpenCode provider does not use. Permission
policy lives only in the plugin (t3/{plugin,session-policy,permissions}.ts):
between T3's write and the plugin's repair, a running session keeps T3's rules, and
the repair has no latency bound. Repairs follow the order of T3's writes; a plugin
reload or session move in the middle of one can leave an earlier write's rules,
still in repaired form, until T3 emits another permission write; turns that change
nothing emit none. This is not a shell or HTTP sandbox.
The t3-code and t3-code-* connection names are reserved; use t3-external for
independent T3 OAuth connections. Underscore-containing thread namespaces, such as
a fork's <thread>_fork, receive stable t3-code-<32 hex> aliases so
prefix-related names cannot grant sibling tools.
This adapter targets OpenCode 2.1.4 and upstream stable T3 0.1.2 on both
x86_64 and ARM Linux. T3 uses the configured default OpenCode agent; this release
does not include the per-thread agent picker proposed in
pingdotgg/t3code#21532.
Keep the integration checks with runtime upgrades.
Paseo runs agents in workspaces and worktrees from its desktop,
web and mobile apps. Limitless connects it to the managed OpenCode service through
the packaged opencode-paseo launcher, so Paseo agents, the TUI and T3 share one
OpenCode server, one session database, and its restart recovery. Paseo would
otherwise start OpenCode servers of its own against the same database.
Paseo desktop / web ─► paseo.service ─launches─► opencode-paseo ─► opencode.service
│ │ paseo plugin
└── registration ─┘ ⇄ Paseo tools
programs.limitless = {
enable = true;
opencode.service.enable = true;
paseo.enable = true;
};The paseo user service runs the pinned daemon (packages.paseo, Paseo 0.13.0)
on 127.0.0.1:6767 with PASEO_HOME=~/.paseo. Options paseo.port, paseo.home,
paseo.browserTools and paseo.package override these defaults. Before each start
it merges these keys into Paseo's config.json, preserving everything else,
including a password hash from paseo daemon set-password:
daemon.listenon loopback,daemon.relay.enabled = false;daemon.mcp.enabledandinjectIntoAgents, so agents receive Paseo's tools;daemon.browserTools.enabledfrompaseo.browserTools;agents.providers.opencodeenabled withcommand = [ "<profile>/bin/opencode-paseo" ].
Like T3's provisioning, the merge refuses to run while another daemon holds
paseo.pid in that home, ignoring a lock left by an earlier boot as Paseo does, and
refuses symlinked or malformed files. Limitless does not publish the port itself;
configure a private HTTPS proxy as described below. Set a password before exposing
the daemon API. Rebuilds keep the old daemon running; restart
it with systemctl --user restart paseo when idle.
Enable paseo.desktop.enable to install the desktop app (on Linux, the same
release). It attaches to the running daemon in ~/.paseo; turn off its built-in
daemon management in the desktop's settings so it never starts its own.
Keep the daemon on loopback and terminate HTTPS at the host's proxy, for example Tailscale Serve. Configure the exact external hostname without its scheme or port:
programs.limitless.paseo.hostnames = [ "pc.example.ts.net" ];Limitless supplies PASEO_HOSTNAMES at service startup. Paseo combines these names
with its persisted allowlist in memory, without saving them. Localhost and IP
addresses remain allowed. Removing a
name from Nix removes this launch allowance on restart, not any independently
persisted allowance. Host validation does not authenticate clients.
Set a password interactively on the daemon host with paseo daemon set-password,
then restart the managed unit with systemctl --user restart paseo when all
agents are idle. Use an ordinary terminal: restarting Paseo interrupts its agents.
For a non-default paseo.home, set PASEO_HOME to that directory when running the
password command. Nix never stores the password; Paseo stores its hash privately.
Rebuilds keep the previous daemon running, and neither setting the password nor
reloading Paseo applies password authentication to that running process.
Before starting the HTTPS proxy, an unauthenticated request to
http://127.0.0.1:6767/api/status with the external Host header must return
401. A 200 means no password is active; 403 means the hostname is not
allowed. /api/health cannot establish that authentication is enabled. Preserve
the external Host header and WebSocket upgrades through the proxy.
Workspace services have a separate trust boundary. Paseo routes registered workspace-service hostnames before daemon authentication. A peer able to reach the proxy can supply such a Host header and reach that service without the Paseo password; the service's own authentication still applies. A direct Tailscale Serve route therefore trusts permitted tailnet peers with those services. The password protects Paseo's daemon APIs, not every route on the listener.
On your phone, connect Tailscale, then use Paseo's Settings → Add host → Direct
connection with the hostname, the proxy's HTTPS port, Use SSL enabled, and
the password. A desktop app on the daemon host also needs that password saved for its connection to
the systemd-managed daemon; it does not receive Desktop's automatic local-daemon
credential. Local direct connections use localhost:6767 with SSL off. Paseo's
relay stays disabled, and agent tool bridges keep their own capability tokens.
Paseo starts opencode-paseo serve wherever it would start an OpenCode server.
The launcher answers --version and other subcommands with the pinned OpenCode,
and serve starts only a loopback proxy to the managed service, waiting up to 20
seconds for the service at login. Paseo's tools reach agents through Paseo's tool
bridge, a loopback endpoint in the Paseo daemon. The launcher records its address,
token and tool list in $XDG_RUNTIME_DIR/limitless/paseo-tool-bridge.json (mode
0600) for the paseo plugin in the managed service, leaving an unchanged
registration untouched. A launch fails when a different, still-running Paseo
daemon owns the registration; two daemons launching at the same moment can both
register, and the later one wins. If a launcher from a crashed Paseo worker wins,
Paseo sessions lose their tools until Paseo launches another server; restart the
paseo service to force it.
The proxy rewrites three kinds of request:
- Session creation drops Paseo's session permission rules, which would override the agents' own rules and pass to their native children.
- Paseo's default
buildagent, at creation or on a switch, becomes the configured default agent. - Per-agent MCP registration is refused: Paseo isolates those servers by giving the agent its own OpenCode server, and in the shared service they would reach every session in the directory.
The paseo plugin satisfies Paseo's readiness check and provides Paseo's tools as
direct paseo_* tools. Before each step it asks Paseo which agent drives the
session, walking up to the parent for native children: sessions Paseo does not
drive, such as TUI and T3 sessions, see no Paseo tools, and an agent sees only the
tools Paseo allows it. Paseo's terminal tools (list_terminals, create_terminal,
kill_terminal, capture_terminal, send_terminal_keys) are never provided;
agents use OpenCode's shell for commands and background processes such as dev
servers. Paseo sessions also receive a short Paseo guidance block. A
stopped daemon removes the tools at once; a stalled one after a 1.5-second lookup,
which then delays each step of limitless, lead and unit sessions until it
answers or stops. The plugin watches the registration and checks it again before
admitting a prompt, so a new agent's first step has the current tools.
Agent roles add their own limits. Unit, Research, and both Oracles receive no Paseo tools. Unit creation, follow-ups, and returns use OpenCode rather than Paseo agent or workspace management.
Paseo replaces a session's shell environment with its daemon's. Shells in Paseo
sessions instead keep the managed service's environment plus Paseo's PASEO_*
variables, such as PASEO_AGENT_ID. Process-level environment Paseo sets for a
single agent does not reach the shared service.
Paseo stops an agent by closing its binding and relies on that agent's OpenCode server exiting. When the daemon that bound an execution closes its binding, the plugin interrupts the execution at its next step, including after the session moves between directories. Once an execution ends, a later prompt to that session runs normally without Paseo tools.
The shared service outlives the launchers. If an opencode-paseo process dies
mid-turn, Paseo reports the turn as failed while OpenCode finishes it, and rejects
a new prompt until it does. Paseo's run --output-schema is unsupported: it needs
a structured-output tool the paseo plugin does not provide. Paseo's own generated
names and messages do not use it.
A working setup shows each of these:
systemctl --user status paseoshows the only Paseo daemon. The desktop's Daemon settings show "Attached to an existing daemon", with "Manage built-in daemon" off.$XDG_RUNTIME_DIR/limitless/paseo-tool-bridge.jsonexists, with mode 0600, once Paseo has first launchedopencode-paseo.- OpenCode's log,
~/.local/share/opencode/log/opencode.log, shows thepaseoplugin loading. Paseo's log isjournalctl --user -u paseo. - Inside a Paseo agent,
echo $PASEO_AGENT_IDprints its ID andpaseo_list_agentslists it. Its$PATHstarts with OpenCode's ripgrep entry followed by/run/wrappers/bin: the shell has the OpenCode service's environment rather than the Paseo daemon's.
opencode-paseo (packages/limitless/paseo/{cli,bridge,rewrites,registration}.ts
over the shared packages/limitless/bridge/), the plugin
(packages/limitless/paseo/{plugin,tool-bridge}.ts) and the settings merge
reimplement Paseo's OpenCode integration against Paseo internals that carry no
version: its launch contract, readiness check, /_internal/opencode tool bridge,
configuration schema and daemon lock. The paseo-protocol check pins the upstream
files they depend on; bump the Paseo input deliberately and review what the check
names. Upstream's recorded npm dependency hash does not match what fetchNpmDeps
produces for its lockfile, with its own nixpkgs or ours, so nix/packages/paseo.nix
records the verified hash; refresh it from the build's reported hash when bumping.
This is not a sandbox: Paseo's tool limits are agent permissions, and every agent can reach the host through its shell.
Limitless enables sound by default and supplies a TUI handler that dings for:
- A newly completed final assistant response in a top-level session.
- A permission request.
- A question or other input prompt.
Completion requires successful execution, a normally finished answer, and an empty inbox. Child completion, errors, interruptions, and compaction-only runs are silent. A later background result can resume the main session and produce another response. The handler uses OpenCode's native sound playback and preserves visual notifications and error toasts. Configure sound, volume, and visual notifications through the native CLI settings:
programs.limitless.opencode.cliSettings.attention = {
sound = true;
volume = 0.4;
notifications = true;
};Set opencode.cliSettings.attention.sound = false to disable the ding. Sound and
visual notifications are independent; visual notifications normally appear when
the terminal is unfocused. Preferences omitted from cliSettings can be changed
through the TUI and stored in ~/.config/opencode/cli.json.
The package's ./tui entry replaces the pinned built-in opencode.notifications
handler by ID. Existing -opencode.notifications directives also disable this
handler. OpenCode loads the TUI entry automatically from the Limitless plugin.
A dot in the prompt footer estimates whether the provider still holds the session's prompt cache:
| Color | Meaning |
|---|---|
| Green | A request is running, or the latest reply finished within the cache lifetime. |
| Red | The cache lifetime has elapsed since the latest reply; the next request likely rewrites it. |
| Gray | The provider's cache lifetime is unknown. |
Lifetimes are 5 minutes for anthropic (OpenCode sends only 5-minute cache
breakpoints) and 30 minutes for openai (GPT-5.6 and later guarantee at least
30 minutes). The dot is a timer estimate: warming requests are invisible to the
TUI, and providers can evict early.
The plugin is TUI-only and ships as Solid source that OpenCode compiles at load.
Home Manager links it into ~/.config/opencode/plugins/limitless-cache-status.
Disable it with programs.limitless.plugins.limitless.cacheStatus.enable = false.
This release removes these options and packages:
| Removed | Migration |
|---|---|
slack.*, Gary, Slack tools |
Remove Slack configuration and retire its bot credentials when no longer used. |
desktop.enable |
Use programs.limitless.opencode.desktop.enable. The opencode-desktop package is available again as the upstream V2 client; it does not restore the old patched launcher. |
tools.agentBrowser.*, agent-browser package/skill |
Use T3 Code's browser tools. OpenCode's native browser catalog is disabled. |
tools.acli.*, Atlassian CLI skill |
Add an atlassian MCP connection and authorize it. |
tools.notion.*, notion-cli package/skill |
Add separately named notion MCP connections for each workspace. |
tools.sentry.*, sentry package/skill |
Add a sentry MCP connection. Install specialized release tooling separately if required. |
mcp.linear.enable |
Set mcp.servers.linear.preset = "linear" and sign in through /mcps. |
notifications.* |
Use opencode.cliSettings.attention; sound defaults to enabled, and visual notifications are configured independently. Arbitrary command hooks are retired. |
opencode.service.alias and its attach alias |
Use native OpenCode service discovery. Optional Linux supervision uses opencode.service.{enable,hostname,port}. |
limitless-fast agent, agents.fastSubagents |
Use limitless. Select a -fast model alias to request Fast processing for a session. |
If migrating from the old systemd service, remove opencode.service.alias and
declare port = 4096 to preserve its old default. Applying the generation with
supervision enabled replaces the old unit and takes over any native daemon. If
using native on-demand startup instead, disable the unit before launching OpenCode.
Do not keep a separate OpenCode supervisor alongside this unit. Applying these
settings is a Home Manager operation; repository checks use isolated fixtures.
For an existing Linear API key, the connection's native settings can explicitly
set oauth = false and headers.Authorization = "Bearer {env:LINEAR_API_KEY}".
Default connections use OAuth. Former CLI token files are not imported into MCP
authentication; authorize and verify each new connection before retiring tokens.
OpenCode 1 sessions are not migrated. Back up OpenCode state and record your flake revision before switching major versions. Rollback requires the matching runtime and state backup; never run V1 and V2 against the same writable state directory.
Use nix develop, then bun install --frozen-lockfile and bun run ci. The gate
runs formatting/lint checks, TypeScript, tests, module checks, and all five package
builds. The suite covers Limitless-owned behavior: tool safety and lifecycle,
role permission outcomes, MCP namespace collisions, launcher environment handling,
and native service supervision. It leaves configuration values and upstream
packages to their owners.
The Linux t3-browser check verifies the actual package wrapper's environment
and the pinned browser's loader dependencies. Run its sandboxed browser smoke
test outside the Nix build sandbox on each NixOS host:
nix run .#checks.x86_64-linux.t3-browser
# On ARM Linux, use checks.aarch64-linux.t3-browser instead.It exercises isolated shared and persistent browser contexts plus direct HTML rendering, using temporary profiles and no live T3 state or external websites.
Runtime, Limitless plugin SDK, and schema are pinned to 2.1.4, with
effect@4.0.1; update them together. Hosted plugins use OpenCode's shared Effect
and plugin runtime; the standalone T3 bridge bundles its own dependencies.
The cache-status plugin's type-only dev dependencies (@opencode/theme,
@opentui/*, solid-js) follow the same OpenCode release. Re-audit native
capabilities and MCP read exceptions when upgrading. Vendor references for the
current allowlists are linked in the integration table above.
When upgrading OpenCode, T3 or Paseo, update the matching *-desktop.nix hash and
build .#opencode-desktop, .#t3-desktop and .#paseo-desktop. The x86_64 Linux
flake checks build all three to catch stale desktop pins before deployment.
When upgrading OpenCode, verify that the TUI still replaces a built-in handler with
a later external definition of the same ID, that the built-in notifications ID is
still opencode.notifications, and that execution events still project matching
idle-message boundaries. The sound filter depends on these pinned behaviors.