A local Paseo plugin that opens one durable session per eligible GitHub issue.
The dispatcher runs deterministic TypeScript every five minutes, not an LLM
coordinator. Each issue gets a separate worktree named <prefix>-<issue_number>
and a persistent issue/workspace/agent binding.
This implementation targets Paseo 0.10.x, pinning the client and plugin SDKs
to 0.10.0, the latest stable release checked on 2026-09-28. It uses separate
index.client.tsx / index.server.ts entries and client/, server/, shared/
directories. Node 22.13+ is required for built-in SQLite.
It does not install, enable, or upgrade the daemon itself.
The paseo-arm host was upgraded to Paseo 0.10.0 before the live integration
test on 2026-09-28. It runs Linux aarch64 and Node 22.23.2.
The connected Paseo app must also support 0.10.0. The Node-based daemon/plugin is
distinct from Paseo's Linux desktop downloads, which currently list x64 builds.
- In
plugin/, runnpm ci,npm run typecheck, andnpm test. - Copy
config.example.jsonto$PASEO_HOME/wayfinder/config.json. IfPASEO_HOMEis unset, use~/.paseo/wayfinder/config.json. Keep this directory outside all project worktrees. Set the canonical checkout, GitHub repository, claim owner, an availableprovider/model, and a capacity appropriate to the host. The sample provider is illustrative: confirm availability on your daemon. - Authenticate
ghon the daemon machine with access to read issues, native dependencies and subissues, and assign issues. Install/discover the ticket-specific skills in the selected harness. The plugin only checks skill files; workers must verify actual harness discovery before working. - Install the trusted plugin with
paseo plugin install /absolute/path/to/paseo-wayfinder/pluginonly after plugins have been explicitly enabled on that daemon. Plugins are trusted, unsandboxed code with the daemon user's filesystem, process, credential and network access. Client code runs inside Paseo. - Check
paseo plugin lsandpaseo plugin logs paseo-wayfinder. Open Wayfinder in the sidebar. With the sample'senabled: false, polling performs no GitHub or Paseo operations. - After inspecting the configuration and existing assignments, change
enabledtotrue. The next timer tick (or Reconcile now) reads the new configuration. Each eligible unassigned issue may now be assigned and launched.
The timer starts when the plugin starts, reconciles immediately when enabled, and stops on plugin cleanup. It does not depend on an app window being open, create a Paseo cron agent, or spend model tokens polling. This deliberately replaces the research proposal's agent-based cron tick with plugin-owned code. Reload/restart starts a fresh timer; the SQLite bindings survive.
The 0.10.0 plugin contribution API provides its IPC SDK to RPC handlers and
lifecycle event handlers, but not the startup entry. For unattended startup/reload, the backend owns
an SDK connection to the configured daemonUrl. Point it at the same daemon
where the plugin is installed. No client surface opens another connection.
If required, provide its password through the daemon subprocess environment
variable PASEO_WAYFINDER_PASSWORD, not the repository or JSON example.
| Field | Meaning |
|---|---|
enabled |
Defaults to false; enables dispatch and automatic update delivery. |
daemonUrl |
Explicit daemon WebSocket URL, including /ws. |
repository |
GitHub.com owner/repo; GitHub Enterprise is not supported. |
cwd |
Absolute canonical Git checkout; workers get separate worktrees. |
mapIssue |
Parent map's issue number, or null to inspect all open repository issues. |
claimOwner |
Account to assign, and sole expected owner of mapped issues. |
provider |
Explicit provider/model, using the daemon's configured credentials. |
prefix |
Worktree and branch prefix; defaults to wayfinder. Use a repository-specific prefix. |
baseBranch |
Base for new branches; defaults to main. |
maxActiveAgents |
Required admission cap, counting all running/initializing agents on the host. |
skillDirectories |
Optional absolute discovery roots, defaulting to ~/.agents/skills and ~/.copilot/skills. Project skill roots are also checked. |
With defaults, issue 42 gets branch/worktree slug wayfinder-42.
The example config explicitly sets prefix: "copy-trades", producing
copy-trades-42 instead. The session title is owner/repo#42.
Workspace and session IDs are generated by Paseo; they do not use the prefix.
Their exact IDs are stored in the binding and shown in the sidebar.
One shared configuration currently manages one repository/map per daemon.
All installations on that daemon share one binding database and lock. Do not
change the daemon URL of existing state: the database rejects that change.
Changing the checkout or map for a repository with bindings also requires an
explicit state migration; it cannot silently give existing workers a different map.
Configuration errors appear in the sidebar and plugin logs; they never enable
dispatch with guessed defaults. Pause by setting enabled: false, or disable
the plugin to stop it entirely. Pausing does not cancel running workers.
Each tick paginates the complete open repository issue list or the parent map's
direct subissues. The map must be open and labeled wayfinder:map. Cross-repository
children are diagnosed, not dispatched into the wrong checkout. PRs are excluded.
An eligible issue must be open, unassigned, have exactly one recognized
wayfinder:<type> label, have the required skill files, and have every native
and body-fallback blocker closed. All API failures are blocked diagnostics.
A closing blocker can admit its dependent on the next tick.
| Label | Routing |
|---|---|
wayfinder:research |
/research. |
wayfinder:prototype |
/prototype; human feedback required. |
wayfinder:grilling |
/grilling and /domain-modeling; human answers required. |
wayfinder:task |
Only the prerequisite described by the ticket; no automatically required skill and no invented /task skill. |
wayfinder:map |
Coordinator context, never a worker. |
These are all five labels defined by the
pinned Wayfinder protocol.
All five also exist in jsilhan/copy-trades. At the 2026-09-28 check, its child
issue bodies generally contained only ## Question: for example,
jsilhan/copy-trades#9 uses
wayfinder:grilling, while
jsilhan/copy-trades#12 uses
wayfinder:prototype. Their prose alone does not specify the required skill.
The parent map additionally
requires grilling/domain-modeling for decisions and research for investigations.
The dispatcher implements selection, claiming and session identity in code, so
workers do not need /wayfinder to repeat that workflow. They invoke the
concrete skill(s) for the label and consult any additional skills named by the
issue or map Notes. For a task, the issue itself specifies what prerequisite
to perform; ask the human when needed, rather than inventing a task skill.
An issue added outside Wayfinder with no wayfinder:* label is reported as
blocked-label and is not assigned or launched, even if it has bug,
enhancement, or ready-for-agent. Unknown or multiple Wayfinder labels also
block. This is a Wayfinder intake, not an opt-in-by-default runner for every
GitHub issue. The RPC names wayfinder.status, wayfinder.tick, and
wayfinder.adopt are internal plugin operations, not GitHub labels.
Planning is the default. Worker prompts require reading map Notes and the
repository's docs/agents/issue-tracker.md, forbid selecting another issue, and
require human confirmation before resolving HITL work. GitHub content is passed
as untrusted task data, not interpolated into shell commands.
Fallback blocker lines support these exact forms, with optional Markdown bold
around Blocked by::
Blocked by: #12, another/repository#34 https://github.com/owner/repo/issues/56
Blocked by: none
References can be comma- or whitespace-separated. Ambiguous lines (TBD,
Markdown links, prose after references, a missing colon) fail closed. Native
dependency API failure also fails closed; a body fallback does not suppress it.
SQLite is a local durable dispatch ledger, not a second issue tracker or a database service to install. GitHub remains authoritative for ticket content and state. Paseo labels identify a successfully created worker, but cannot record a reservation before creation or an uncertain assignment/send result after a crash. The ledger records those intermediate states, prevents duplicate bindings, and provides a cross-process lock. An in-memory map would lose those guarantees on reload; a JSON file would need equivalent locking and atomic-update machinery.
bindings.sqlite is keyed by immutable GitHub repository ID + issue ID and
stores the issue number, dispatch UUID, branch, workspace ID, agent ID, phase,
revision and diagnostic. Agent and workspace IDs have unique constraints. A separate SQLite
write lock serializes cooperating ticks and adoption across processes; the OS
releases it after a crash. State transitions commit before each external write.
Do not delete this directory to clear an error: doing so discards deduplication
history. Back it up with SQLite-aware tooling while active, or with the plugin
disabled and no in-flight tick.
Assignment is the first GitHub write, after local reservation and eligibility recheck. Ownership is checked again before agent creation. GitHub assignment is not compare-and-swap: unrelated manual sessions remain outside the local lock. The plugin never removes someone else's assignment or automatically adopts it.
In Wayfinder, enter an assigned issue number and its existing Paseo agent ID, then choose Confirm adoption. Adoption verifies sole ownership, map membership when configured, and a live worktree in the configured project. It persists the binding without sending a prompt. Titles alone are never evidence of identity. An existing binding cannot be replaced with a different agent.
On a workspace-creation timeout, reconciliation looks for the deterministic
worktree slug in that project. On an agent-creation timeout it looks for exact
wayfinder.issue and wayfinder.dispatch labels through SDK agent listing,
including archived records. A single matching live session is reused. Missing,
ambiguous, archived, or errored evidence is needs-recovery, never permission
to blindly launch again. An uncertain assignment also requires human recovery.
Use the existing session/worktree to finish recovery, then explicitly adopt it.
The plugin does not automatically delete branches, unassign issues, archive
workers, or retry a failed external creation without evidence.
If prompt acceptance times out, the pending revision remains durable. The plugin does not replay it: a message ID alone is not treated as a verified exactly-once-send contract. Inspect the existing conversation, manually deliver anything missing, then re-adopt the same session to acknowledge the current revision. This explicit action clears the uncertain delivery marker without injecting a turn.
Mapped issues are re-read even when closed or absent from the open frontier. Body, title, label, dependency-state and map-body revisions are delivered to the same session only when idle and capacity is available. Busy changes are coalesced to the latest snapshot on the next tick. Closed/reopened issues retain their bindings; a missing/transferred issue is diagnosed rather than respawned. Once a closed issue's worker is idle, closure is recorded without another agent turn. Reopening still delivers the changed snapshot to that same worker. Idle means available for input, not resolved.
The cap controls this dispatcher's admissions and update turns. All host agents, including nested research and unrelated workloads, count when running or initializing. Idle HITL sessions retain their bindings without consuming a generation slot. Newly created sessions reserve the current tick's slot even while initializing or when their creation response is lost. Independent users or other schedulers can still start work concurrently; this is not a provider quota enforcement mechanism. Worker prompts prohibit automatically spawning nested agents without operator capacity approval, so a research skill that requires a subagent may pause for that approval.
Intentional boundaries: no GitHub comment-to-chat bridge, no automatic map-body writer, no dispatcher-driven issue closure, no automatic adoption of human assignments, no destructive session replacement. HITL answers stay in Paseo. Workers post their own issue resolution and close completed AFK prerequisites; HITL closure still requires human confirmation. Map consolidation remains with a human/coordinator. The plugin's only GitHub mutation is assignment, so it does not create a self-triggering comment/map-edit loop. Assignment churn is excluded from revision hashes but ownership is still checked.
The reconciliation path is separated into small named steps: validate the issue, recover uncertain creation, claim, create the workspace, start the worker, and deliver updates. Persistence is deliberately kept around each external write; those checkpoints prevent a refactor from turning a timeout into a duplicate.
cd plugin
npm run typecheck
npm testThe suite uses temporary SQLite databases and fake tracker/session adapters:
no running Paseo or GitHub connection is required. The SDK construction test
never calls connect(); it mocks the underlying socket connection method to
throw on attempted network access and asserts zero calls. No live claims, agents,
or worktrees are created. The suite covers admission, pagination,
blockers, routing, ownership races, adoption, crash recovery, lock contention,
busy update delivery, uncertain sends, and closed/reopened issues.
The diamond-dependency regression explicitly verifies that closing only one of
two blockers does not release a dependent.
The daemon's PATH can resolve a different gh version than an interactive
shell. Pagination uses gh api --paginate --jq tojson, not the newer --slurp
flag, and validates each JSON page. This was corrected after the first live
installation exposed an older daemon-side gh.
Provider permissions are unchanged by the plugin. An AFK ticket can still pause for a tool permission under the provider's normal policy. The live test approved only individually inspected, issue-scoped commands; it did not enable global auto-approval or prove zero-intervention execution.
After source changes, typecheck and use paseo plugin reload paseo-wayfinder;
do not restart the daemon. Inspect the plugin logs for startup and tick failures.
Before production activation, verify on the intended host that a worker remains
available after the plugin is disabled/reloaded and inspect both wide and
compact clients. Workers have their own explicit workspace, autoArchive: false,
and no scheduler-agent parent; this design does not depend on schedule-parent
detach/archive behavior.
API compatibility targets the published @getpaseo/client@0.10.0 and
@getpaseo/plugin@0.10.0 contracts, following the
runtime-entry migration guide and
plugin reference.
The old single-entry file and handwritten SDK declarations have been removed.
This plugin no longer supports Paseo 0.6.1.
Both bundles were compiled and their registrations, missing-config error state,
and cleanup exercised with the published 0.10.0 compiler on the ARM64 host.
That offline check does not upgrade or start a daemon.