La Roca plugin that mirrors one or more firstmate homes into its own federated SQLite database and routes deterministic wakeups to attached agent seats.
This repository is the public normative full-size plugin example. Use it as the shape to copy: a plugin.json manifest, a custodial federated SQLite database with a semantic (and embeddings-only vector) fragment, a shipped executable, package checksums, and a release-please-driven GitHub release. La Roca the product stays untouched. Firstmate is mirrored, not modified. Tests use only the fabricated homes under testdata/homes/; public code and text contain no live home data or install details.
The minimal three-file walk (data only, no executable) lives in La Roca's docs/plugins.md. This repo is the next step: a federated plugin that also ships code.
A La Roca plugin is a verified package, not a git checkout. The installer accepts a directory, a .tar.gz whose files sit at the archive root, a Git URL, or owner/repo. This plugin's supported install path is the GitHub release archive. The git tree is the source that builds that archive. It does not contain firstmate.db or the executable, so roca plugin install thellmwhisperer/roca-firstmate (a git clone) is not an installable package.
After a successful install the plugin directory contains:
plugin.json # identity, database, semantic, and vector declarations
firstmate.db # empty schema on first install; operator data after that
roca-firstmate # plugin executable
checksums.txt # SHA-256 of the three payload files above
.roca-plugin.json # local inventory written by the installer, never shipped
The Dresser skill (skills/dresser/SKILL.md) and schema/schema.sql live in this repository. Schema is embedded in the executable. Dresser is a companion skill, not a package-root payload (La Roca extracts only regular files at the archive root).
plugin.json is schema 1. Required parts:
- Identity:
name(roca-firstmate),version,binary. binaryisroca-firstmatebecause this package ships an executable. A data-only package namesroca(the host) and ships no binary.databases: one custodial SQLite file,firstmate.db, aliasplugin_roca_firstmate, attachmenton-demand.semantic: every visible table and its ordered columns, plus questions the model can ask.vector: embeddings-only over the five familycontentcolumns. Noingestverb.
The engine rejects unknown fields. Do not add keys La Roca does not know. Manifest/database parity and vector selectivity are enforced by schema/package_test.go.
databases[].path is a file shipped in the package. La Roca's installer requires checksums.txt to name that file (together with plugin.json and, when present, the executable). A custodial database is still a payload on first install. On update the installer preserves the installed file and does not overwrite operator data with the empty schema from the new archive.
This repository never commits a .db file. make package / make dist generate an empty firstmate.db from schema/schema.sql at pack time. The first database-backed verb (attach, chart, follow, tick, scribe, watch) runs schema.Ensure and is idempotent on that file.
custody: true means uninstall archives the plugin directory instead of deleting it.
Third-party verbs do not take a roca CLI seat in current La Roca. Dispatch is the neighbor executable on PATH:
roca firstmate <verb> -> $ROCA_PREFIX/roca-firstmate <verb>
(default $ROCA_PREFIX is ~/.local/bin). Install copies the packaged roca-firstmate into the plugin directory and into that prefix. place remains for developers who built the binary some other way.
checksums.txt is one SHA-256 per immutable payload file, two spaces, then the filename. The installable set for this plugin is exactly:
<sha256> firstmate.db
<sha256> plugin.json
<sha256> roca-firstmate
The source-tree checksums.txt fingerprints plugin.json only (make sync-db). That file is not the install payload. A source whose checksums declare only plugin.json is refused with checksums.txt declares [plugin.json], want exactly [firstmate.db plugin.json]. That is the federated installer contract, not a reason to omit the database from the package.
make check # gofmt, go vet, go test
make package # host-platform install directory under .tmp/package
make dist # darwin-arm64 and linux-amd64 .tar.gz under .tmp/dist
make e2e # scratch-home install, dispatch, and release-to-release updateEach archive contains only package-root files. Nested paths are refused at extract time.
Do not push a release tag by hand. Releasable conventional commits on main update one release-please pull request. The workflow refreshes the source-tree checksums.txt on that PR and merges it after the check job succeeds. The merge lands matching versions in plugin.json and .release-please-manifest.json, creates the vX.Y.Z tag and GitHub Release, and starts the existing Release workflow. That workflow builds both platforms, checks the packaged checksums, and uploads the official archives. Local builds are not official releases. The repository secret RELEASE_PLEASE_TOKEN is a fine-grained PAT with Contents and Pull requests read/write; tags created with GITHUB_TOKEN would not start the Release workflow.
Published names:
roca-firstmate-vX.Y.Z-darwin-arm64.tar.gz
roca-firstmate-vX.Y.Z-linux-amd64.tar.gz
roca-firstmate-darwin-arm64.tar.gz # same bytes, stable latest/download name
roca-firstmate-linux-amd64.tar.gz
Release binaries are CGO_ENABLED=0 (portable polling watcher). Native FSEvents is a local make build on macOS.
Enable the experimental plugin surface (features.plugins = true), then install the archive for this machine:
# Darwin arm64
roca plugin install https://github.com/thellmwhisperer/roca-firstmate/releases/latest/download/roca-firstmate-darwin-arm64.tar.gz --yes
# Linux amd64
roca plugin install https://github.com/thellmwhisperer/roca-firstmate/releases/latest/download/roca-firstmate-linux-amd64.tar.gz --yes--yes accepts EXECUTABLE risk (the package ships code). --json also needs --yes; JSON never consents by accident.
Prove dispatch and first use from a scratch home, never a live captain La Roca:
roca firstmate --help
roca firstmate chart --db "$HOME/.roca/plugins/roca-firstmate/firstmate.db" --helpUpdate re-resolves the recorded source. Install from the stable latest/download URL so roca plugin update roca-firstmate --yes fetches the current release. A versioned download URL stays pinned to that version. Update replaces plugin.json and roca-firstmate and preserves firstmate.db.
If an older install recorded a git owner/repo source, plugin update still clones the git tree and cannot verify the package. One-time cutover: roca plugin uninstall roca-firstmate archives the custodial directory, install from the release archive, then copy firstmate.db from the custody archive over the empty shipped file.
Do not install this repository onto a live captain La Roca while developing. Prove the payload with make check and make e2e.
plugin.json version is the package version. Release-please owns it through extra-files together with .release-please-manifest.json. Git tags are vMAJOR.MINOR.PATCH and must match both. The Release workflow refuses a tag that does not.
Releasable conventional commits choose the next version after the last published tag:
fix:anddeps:produce a patch release.feat:produces a minor release. Before1.0.0a breaking change also stays MINOR.- A commit with
!after its type, such asfeat!:, or aBREAKING CHANGE:footer produces a major release after1.0.0. docs:,chore:, andbuild:do not trigger a release by themselves.
| Bump | When |
|---|---|
| MAJOR | After 1.0.0, breaking changes to the install contract, manifest schema, or declared database file list. Changing the declared binary or adding/removing a checksummed payload file is breaking because the installer verifies an exact file set. |
| MINOR | Compatible features: new verbs, Nerve surfaces, schema that Ensure can apply. Before 1.0.0, breaking changes also bump MINOR. |
| PATCH | Fixes and dependency updates. No payload-file or schema-list change. |
Do not retag; cut the next patch. 0.5.0 is the first public installable package; 0.4.0 was never published, so that jump stays MINOR as the initial public contract.
firstmate.dbis an external federated database. Its payload and first-run contract are defined above.- Firstmate remains the writer of its Markdown. Scribe mirrors it;
tasks-axiremains the backlog query layer. - Every rewrite becomes a new version row, with
is_current = 1on the latest file. - Distiller is deleted. The chart is an on-demand get-or-create with a database watermark.
- Nerve is deterministic and inference-free. Destinations are
captain,companion, andmachine; mobile is later. - There is no default daemon and no KeepAlive service.
attachandfolloware foreground subscriptions.tickis an ephemeral cron process.watchis the session-owned ear: a parent that owns stdin/stdout (no port, no pid file) can raise it as a child that dies when the session ends. Seat leases keep a single holder per home. state/telemetry is outside v1. The reserved later tables remain free.- Vector retrieval is embeddings-only over the five family
contentcolumns (home prose plus task text). The plugin never declares ingest.
Scribe classifies every Markdown file under a configured home's data/ into five versioned families:
- Startup working set:
captain.md,captain-shared.md,learnings.md,projects.md, andsecondmates.md. - Archives:
captain-archive.md,memory-archive.md, andnote-archive.md. - Task state:
backlog.mdanddone-archive.md, mirrored for cross-references only. - Every other root Markdown file as an operational document.
- Every nested Markdown file as a per-task artifact keyed to its task id.
Scribe's causal boundary is a SQLite transaction:
Markdown file -> version row -> SQL trigger -> companion wakeup
An unchanged fingerprint creates neither a duplicate version nor a wakeup. Cursor identities are home-relative; absolute home paths are never persisted.
The home-aware verbs accept repeated --home PATH --home-id ID positional pairs to cover more than one fabricated home in a single process; unbalanced flags exit 2. --label and --kind are also repeatable alongside those pairs. A single --kind broadcasts to every pair, and an omitted kind defaults to primary. Flags only, no config file. All homes and concurrent session-owned watchers coordinate through one firstmate.db. Existing single-home commands keep their byte-identical output.
roca firstmate scribe --home '<fabricated home>' --home-id northwind-harbor --db firstmate.db
roca firstmate watch --home '<fabricated home>' --home-id northwind-harbor --db firstmate.db
roca firstmate watch \
--home '<fabricated primary home>' --home-id northwind-harbor --kind primary \
--home '<fabricated second-mate home>' --home-id skiff-secondmate --kind secondmate \
--db firstmate.db
roca firstmate place --dir '<plugin directory>'watch opens one recursive FSEvents subscription per home on macOS when cgo is available, and polling elsewhere. On rise it fingerprint-sweeps first so writes made while nobody listened are absorbed, then it stays on live events. Concurrent watch processes compete for the existing seats lease of watch-<home-id> (destination machine): the holder watches, the others stand down, and a released or expired lease is inherited on the next retry. scribe ingests each home in sequence. Nerve adds ingest-on-read: attach, chart, follow, and tick run Scribe's fingerprint sweep for each supplied pair before answering. For chart and follow, unpaired --home-id values filter already-registered homes, while omitting the filter reads every registered home. Supplying paired --home PATH --home-id ID values refreshes those homes but does not filter output.
Watch telemetry is JSONL next to firstmate.db (logs/watch-YYYY-MM-DD.jsonl), never a database table. Lines record raise, lease-acquired, lease-lost, sweep counts, and crash-retry. A crash inside the child is logged and retried with backoff; it does not require a daemon.
place copies this executable into the plugin directory (default: the directory of ROCA_FIRSTMATE_DB) as roca-firstmate, so a session parent resolves it from that directory instead of PATH luck. The release installer already places that file. This plugin does not add unknown manifest fields: current La Roca rejects them. The watch child is ready for a generic session-companion declaration any plugin could name; that kernel seam, if added, must not mention firstmate.
Set local configuration, enter the workspace the thinker occupies, and attach:
export ROCA_FIRSTMATE_DB='<plugin directory>/firstmate.db'
export FIRSTMATE_HOME='<firstmate home>'
export FIRSTMATE_HOME_ID='local-primary'
roca firstmate attachRunning roca-firstmate without a subcommand is equivalent. Attach:
- Reconciles Markdown fingerprints and ingests changes.
- Registers one workspace seat per supplied home. Only a SHA-256 fingerprint and opaque default label are stored, never the path;
--labelopts into a human-readable label. - Prints the chart get-or-create, including its database watermark, and the latest mirrored handoff.
- Drains pending wakeups for the selected destination.
- Subscribes to database/WAL changes in the same foreground gesture.
Attaching is subscribing. There is no init ceremony.
follow observes firstmate.db-wal. Native macOS builds use FSEvents; portable builds use a polling fallback.
roca firstmate follow --destination companion
roca firstmate follow --destination companion --home-id skiff-secondmateAcross multiple homes, follow interleaves wakeups in global generation order and includes home_id in every JSON line.
For each delivery attempt, follow writes one JSON line to stdout before committing handled = 1 and handled_generation = generation. Output failure rolls the claim back. Process death or commit failure after a successful write can emit the same line again, so delivery across the stdout/SQLite boundary is at-least-once, never cross-system exactly-once. Adapters deduplicate retries by (destination, generation).
Direct follow derives and heartbeats one opaque seat per selected home from the current workspace unless --workspace or --seat-id is supplied. An explicit --seat-id is valid only when one home is selected. During an adapter handoff, attach and direct follow are mutually exclusive subscription owners for the same workspace and destination: stop one before starting the other so they cannot race to consume the queue.
Dresser documents three adapter recipes and their intended latency:
- Native wake APIs: milliseconds. Claude Monitor or Claude
asyncRewakeas separate alternatives, OpenCodepromptAsyncwithout--pure, Pi TypeScript extensions, Grokbackground-notify, and probable Hermes hooks after verification. - Injection: seconds. Codex CLI and Cursor CLI pipe
followtofm-send. - Passive desktop sync: on open. Attach, consume the chart/handoff and pending lines, then close with the app.
Cron may run a process that is born, reconciles fingerprints, advances the silence clock, drains orphan wakeups, and dies:
roca firstmate tick --silence-after 5mSeats keep leases in SQLite. The silence clock records durable generations per expired seat, and each silence wakeup keeps that seat's home_id. A wakeup is orphaned when its destination has no unexpired seat lease; tick delivers it with the same one-line and handled-generation contract as follow. No tick state survives in memory.
chart is a bounded AXI get-or-create: generate, serve cached, or regenerate when its watermark moves.
roca firstmate chart --home '<fabricated home>' --home-id northwind-harbor --db firstmate.db
roca firstmate chart --home '<fabricated home>' --home-id northwind-harbor --db firstmate.db --json
roca firstmate chart --db firstmate.db --home-id skiff-secondmateThe cache lives in chart_cache, not Markdown. Default output is bounded TOON with help[]; --json returns the complete envelope. Use gated SQL for history:
roca exec 'SELECT home_id, relative_path, version, observed_at FROM plugin_roca_firstmate.working_set_versions WHERE is_current = 1 ORDER BY home_id, relative_path'
roca exec 'SELECT id, home_id, destination, generation, handled_generation, kind, created_at FROM plugin_roca_firstmate.wakeups ORDER BY generation'Schema source: schema/schema.sql. The visible tables are the five *_versions families, homes, tasks, ingest_file_state, seats, wakeups, and chart_cache. La Roca hides plugin_schema bookkeeping. plugin.json owns the semantic and vector declarations; schema/package_test.go enforces their parity and selectivity.
plugin.json # identity, database, semantic, and vector declarations
schema/schema.sql # source of truth for database-backed first-run creation
cmd/roca-firstmate/ # attach, follow, tick, chart, place, and Scribe verbs
cmd/package/ # release packager: empty db, executable, checksums, archive
internal/release/ # packager contract tests and scratch-home e2e
internal/nerve/ # WAL subscription, seats, watch leases, silence clock, orphan drain
internal/scribe/ # total versioned Markdown mirror
internal/watch/ # Scribe FSEvents and polling backends
internal/watchlog/ # JSONL telemetry for the session-owned watcher
skills/dresser/SKILL.md # companion operating skill and adapter recipes
testdata/homes/ # fabricated homes only
Mobile routing, state/ telemetry tables, and an optional resident daemon remain additive later work. Session-owned watch is not a daemon. None blocks Nerve v1. Do not add Distiller or edit the La Roca product repository from this example.