Skip to content
Chasewhip8Public

About

An opinionated OpenCode setup powered by Nix.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Limitless

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.

Use it

{
  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.

Included capabilities

  • Agents: limitless, a research subagent, and technical and design Oracle advisers, plus lead and unit for 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.

MCP connections

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.

GitHub authentication

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.

Permissions and coverage

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.

Executor

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

Server

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.

Accounts and scopes

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.

Clients

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-athena

Forward 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.

Pilot acceptance

Keep direct MCP connections until all of these are verified:

  1. ARM64 startup, private routing, owner creation, and closed signup afterward.
  2. Both Notion identities, the intended Atlassian site, and Sentry scope through the exact callback origin.
  3. Main/Oracle reads and separately authorized writes; Research mutation denial for both direct calls and programs. Scoped connections exclude administration; an explicitly chosen fullAccess read/write connection for main and Oracles intentionally includes it.
  4. Device revocation, reconnect, upstream refresh after expiry, and restart.
  5. 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.

Configuration and layout

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.

Agents and models

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 and Units

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.

Native Unit compatibility and known limitation

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.

Anthropic subscription authentication

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.

Guarded source research

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.

Artifacts

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.

Native service and Tailscale

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 opencode

opencode 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.

Desktop clients

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

OpenCode Desktop and the shared service

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.

T3 Code

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.

Setup

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:

  1. 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 opencode when supervised, or opencode service restart otherwise. Check the endpoint with opencode service status.
  2. In T3 Settings → Providers → OpenCode, set Binary path to opencode-t3 and leave Server URL blank. If T3 does not inherit your PATH, use the absolute path printed by command -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.

Behavior

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.

Compatibility boundary

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

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

Paseo setup

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.listen on loopback, daemon.relay.enabled = false;
  • daemon.mcp.enabled and injectIntoAgents, so agents receive Paseo's tools;
  • daemon.browserTools.enabled from paseo.browserTools;
  • agents.providers.opencode enabled with command = [ "<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.

Paseo over a private HTTPS proxy

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 behavior

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 build agent, 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.

Paseo troubleshooting

A working setup shows each of these:

  • systemctl --user status paseo shows 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.json exists, with mode 0600, once Paseo has first launched opencode-paseo.
  • OpenCode's log, ~/.local/share/opencode/log/opencode.log, shows the paseo plugin loading. Paseo's log is journalctl --user -u paseo.
  • Inside a Paseo agent, echo $PASEO_AGENT_ID prints its ID and paseo_list_agents lists it. Its $PATH starts with OpenCode's ripgrep entry followed by /run/wrappers/bin: the shell has the OpenCode service's environment rather than the Paseo daemon's.

Paseo compatibility boundary

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.

Notifications

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.

Prompt-cache status

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.

Migration from the previous Limitless layout

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.

Development

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.

About

An opinionated OpenCode setup powered by Nix.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages