Files
PaperClipAI/doc/plugins/AGENT_RUNTIME_SYNC.md
T
Nicky LeachandPaperclip 5fbece9dcc Synchronize assigned skills through agent runtime hooks (#16501)
## Thinking Path

> - Paperclip manages shared skills and agent assignments.
> - Runtime plugins need installed skills before a task starts.
> - Skill content can change without changing the agent row.
> - Source access and version pins also affect delivered files.
> - This pull request feeds those changes into the durable runtime
stream.
> - The runtime reader resolves the same immutable skill sources used
for execution.

## Linked Issues or Issue Description

Builds on merged PR #16499. This pull request now targets `master` and
contains only the five skill synchronization commits.

**Problem**

Plugins receive no durable notification when an assigned skill changes.
A saved assignment can also become inaccessible after private-source
access changes.

**Proposed solution**

Invalidate referencing agents at installed skill mutations. Invalidate
inherited access when source bindings, project privacy, or policy
change. Return authorized skill sources and version IDs through the
owning runtime reader.

**Alternatives considered**

Activity-only subscriptions miss service writers. Per-task
synchronization adds preparation work. Copying current files would
ignore version pins.

**Roadmap alignment**

This extends existing skills, source synchronization, and plugin runtime
support.

## What Changed

- Defer multi-skill source runtime hooks until commit, after publication
holds all skill rows. Keep file-save journaling immediate to preserve
filesystem rollback.

- Add transactional invalidations for skill content, companion files,
versions, rename, deletion, policy, source access, and pin-setting
changes.
- Recognize canonical keys and supported legacy aliases.
- Reuse the execution skill resolver, current agent access, and instance
version-pin rules.
- Resolve and materialize sources outside the agent row lock. Recheck
ownership and runtime revision afterward.
- Keep task-only mentions and running-task snapshots in the execution
path.
- Document conservative invalidations for pinned assignments and
inherited access.

## Verification

- Rebased onto origin/master `9f4a956b90`, which contains merged PR
#16499. All five commits are unchanged according to `git range-diff`; no
conflict resolutions or source changes were needed.
- On rebased head `58b065941`, all 237 focused tests passed: runtime
skill hooks/source publication (41), plus content service, assignment
routes, policy routes, and project source access (196).
- Server typecheck passed after rebuilding Runner with the installed
Rust toolchain. Migration numbering/safety and map checks passed;
snapshot pruning made no changes. Migration CLI checks required local
IPC permission.
- Real PostgreSQL coverage includes concurrent source
publication/version saves, policy-only tightening/restoration,
referencing-agent fanout, company isolation, no-op counters, supporting
files, rename/deletion, rollback, private-source access, pinned/latest
bytes, and unassignment.
- Earlier stack validation passed workspace typecheck/build. Fresh
hosted CI and review must verify the rebased head before handoff.
- The broad local suite was previously limited by an unchanged
diagnostic test that rejects "private" in an error stack under this
/private/tmp worktree; that unchanged suite passed all 108 tests in the
original checkout. Hosted full-suite checks provide exact-head
verification.
- Diff whitespace and full PR secret/PII scan passed.
- All hosted CI/security checks are green on `58b065941`; fresh standard
Greptile and Apex both returned 5/5 with no findings or unresolved
threads. The PR targets master and is mergeable without conflicts.

## Risks

- Apply migration 0348 after the runtime-stream migration in #16499.
- Content invalidations include pinned assignments. The resolved version
stays pinned; consumers can avoid writes when its version/hash is
unchanged.
- Project access and source changes invalidate only agents assigned
skills from the affected sources. Company policy still invalidates the
company. Source polling bookkeeping and popularity counters do not
create work.
- The reader rebuilds installed caches without upstream requests.
Concurrent changes cause a bounded retry and then a conflict.
- Private transport integration and live remote computer qualification
remain separate.

## Model Used

OpenAI Codex, GPT-6, with reasoning, repository inspection, code
execution, and testing. The exact deployment model ID and context window
are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass (focused suites; broad
local limitation documented above)
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-16 08:28:47 -07:00

6.2 KiB

Agent runtime synchronization

Plugins can opt into committed agent runtime changes with resourceLifecycle: ["agent_runtime"] and events.subscribe. Implement onResourceLifecycle and handle update and delete. Existing agent and project subscriptions do not receive this new resource type. Manual inbox reads retain both legacy streams regardless of automatic subscriptions. They include runtime events only when the manifest opts into agent_runtime.

An event contains company identity, agent identity in resourceId, a numeric sequence serialized as id, and its action. It contains no file contents, configuration, or credentials. Treat id as an opaque decimal revision, not a JavaScript floating-point value. Delivery is at least once and ordered per agent. Return acknowledged only after external effects finish. Return retry after an unfinished operation, or deferred when configuration is missing. Startup, plugin startup, and configuration changes recover unacknowledged work.

The host records configuration changes at the database mutation boundary. This covers API saves, rollback, plugin writes, and background writers. No-op updates and heartbeat accounting do not create runtime work. Instruction edits, file deletions, restores, and run writeback use the file audit boundary. Pending hires produce no update until approval. Returning to the ready lifecycle phase requests synchronization after preparation or resume. Existing agents receive a one-time baseline when the migration runs. Agent deletion leaves a durable delete event.

PostgreSQL notifications wake the existing durable dispatcher only after commit, including writes through independent clients. Notifications carry no identifiers or contents. The journal remains authoritative. The listener scans on connection and reconnection so a dropped notification does not lose committed work. There is no recurring empty-inbox poll. Existing retry deadlines still retry failed work. A process opens one listener connection only after a ready plugin opts into agent_runtime. The database connection must support session-level LISTEN; transaction-mode poolers are not supported for this opt-in feature. Startup waits for listener registration. It releases the listener after the last runtime subscriber is disabled or removed. Legacy subscriptions keep process-local work signals.

A plugin that also has agents.lifecycle.manage can call ctx.agents.getRuntime(agentId, companyId) from a company-scoped invocation. Only the enabled, ready plugin selected in that agent's required lifecycle set can read these inputs. The result supplies the latest runtime revision, the normalized agent configuration, instruction root and entry filename, and that plugin's own instance secrets. Instruction paths use the same managed-folder recovery as the instruction reader. A missing or cross-company agent returns null. The instruction root identifies host files that the trusted worker can package through its existing filesystem access. This API grants no arbitrary path reads. Never expose instance secrets through UI data, logs, plugin state, or agent files.

Re-read current state before external effects. A queued event can be older than the returned revision. Fence stale revisions at the destination and use its conditional writes. Current configuration is desired state, not a frozen run snapshot or permission to change an active provider session. Execution admission and provider activation keep their existing checks. The private transport consumer is a separate integration.

Filesystem changes outside Paperclip's managed write APIs do not emit these hooks. Filesystem publication and the database journal are not one filesystem transaction. A failed managed write must be repaired through the same API before claiming synchronization. Upstream repository detection remains owned by the source integration.

Successful file saves record the current desired state. Identical retries and already-applied deletions repair a missing journal event if a prior save published bytes before its database transaction failed. Audit rows carry their runtime revision, so repeated identical saves against that revision create no audit rows or events. Later runtime changes invalidate that deduplication. The file API still returns changed: false for identical content and keeps conditional-write checks. Consumers can skip rewriting content whose hash already matches.

Skills

Migration 0348 invalidates referencing agents when installed skill content, companion files, versions, source bindings, or identities change. Rename checks both old and new keys. String selections, structured selections, and supported legacy aliases are recognized. Unrelated agents and other companies receive no content invalidation. Private-source bindings, membership, and privacy changes invalidate only agents assigned skills from the affected sources. Project workspace authorization-policy changes use the same boundary. Company policy changes conservatively invalidate the company. Source retry/lease bookkeeping and skill popularity counters do not emit changes. Multi-skill source publication defers its skill invalidation until transaction commit, after it acquires all skill rows. This avoids an agent-lock/skill-lock cycle with concurrent version saves. Individual file edits retain immediate journaling and their existing filesystem rollback behavior.

Assignment edits already use the configuration boundary. The runtime reader returns selected skill entries with host source paths and immutable version IDs. It uses the execution resolver, current agent access, the instance pin flag, and legacy operational-skill rules. It performs no upstream refresh. The resolver can rebuild an expired version cache from saved bytes. Slow materialization happens outside the agent row lock. The reader rechecks ownership and revision afterward; it retries concurrent changes three times and then returns a conflict.

Pinned assignments receive conservative content invalidations too. Their resolved version and bytes stay pinned when pin support is enabled. Consumers can compare versions or destination hashes and avoid rewriting unchanged trees. Task-only skill mentions and a running task's saved version selection remain task-owned.