Self-modification via PR (propose_config_change + nerve-workspace skill) (11/13) - #214
Open
alex-clickhouse wants to merge 2 commits into
Open
Self-modification via PR (propose_config_change + nerve-workspace skill) (11/13)#214alex-clickhouse wants to merge 2 commits into
alex-clickhouse wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “self-modification via PR” path so the agent can propose reviewed workspace-config changes (skills/cron/settings/instruction files) by staging them in an isolated git worktree, validating, pushing a branch, and opening a PR via gh. This supports lockdown mode where direct edits to tracked config are blocked.
Changes:
- Introduces
nerve.config_pr.propose_config_changewith path-surface allowlisting, traversal/symlink containment guards, validation-before-PR, and executable-effect announcements. - Adds an agent tool (
propose_config_change) with schema + handler, and registers it in the default tool registry. - Adds extensive unit + integration tests and a new
nerve-workspaceskill documenting the workflow (plus a pointer fromnerve-dev).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_config_pr.py |
Comprehensive tests for PR-based config proposals, including real-git integration cases and safety/effect announcements. |
nerve/config_pr.py |
Implements PR proposal workflow: path/scope guards, worktree staging, validation, push, and PR creation via gh. |
nerve/agent/tools/handlers/config_pr.py |
Tool handler + spec for propose_config_change, running the workflow off-thread and formatting agent-facing output. |
nerve/agent/tools/schemas.py |
Adds JSON schema for propose_config_change tool inputs. |
nerve/agent/tools/handlers/__init__.py |
Registers config PR tool specs into the default tool registry. |
nerve/templates/skills/nerve-workspace/SKILL.md |
New skill instructing the agent to always propose workspace config via PR (esp. under lockdown) and clarifying scope. |
nerve/templates/skills/nerve-dev/SKILL.md |
Directs “config changes” requests to the nerve-workspace skill instead of app-code development. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 12:11
ffc39b8 to
f85e621
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 12:36
f85e621 to
abfd8e9
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 13:15
abfd8e9 to
f60b5a3
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 14:23
f60b5a3 to
fa47c06
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 14:54
fa47c06 to
f8ace59
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 15:34
f8ace59 to
da545bb
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
2 times, most recently
from
July 28, 2026 16:20
a505bc8 to
86739f0
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 28, 2026 18:40
86739f0 to
7af2ebc
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 29, 2026 08:29
7af2ebc to
a6da8ad
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 29, 2026 09:18
a6da8ad to
ee9a90e
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 29, 2026 09:31
ee9a90e to
486db4c
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 29, 2026 10:31
486db4c to
c3335b4
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 29, 2026 13:28
c3335b4 to
87672ab
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
2 times, most recently
from
July 30, 2026 08:25
d90e6cc to
83ecee6
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
2 times, most recently
from
July 30, 2026 09:04
ba04b55 to
737e878
Compare
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 30, 2026 09:48
737e878 to
1a8b5c1
Compare
Under lockdown the agent cannot edit tracked config directly, so it proposes: branch, commit, push, and open a PR against the workspace repo where a human reviews it. Unlocked instances keep editing directly. This ships as its own skill so the existing app-codebase workflow is untouched, with a one-line pointer added to it. Proposals are restricted to the reviewed-config surface and classified by effect, not by file extension — the question is what a file causes the daemon to run, not what it is named. Path traversal is refused and the branch ref is not leaked. A proposal is based on the remote's default branch rather than local HEAD, so it cannot carry unreviewed local commits into the very PR meant to get them reviewed, and it can commit on a box with no git identity configured. The reviewer notice flags a change to lockdown by diffing the proposal against the current tracked file rather than by presence: presence would flag every proposal from a locked instance for its own unchanged flag, and a notice that always fires teaches reviewers to skip it. Keys that are dangerous whenever they appear stay on presence; keys that are dangerous when they change are diffed. The split is commented so it is not unified back into a bug. Co-Authored-By: Claude <noreply@anthropic.com>
alex-clickhouse
force-pushed
the
config-refactor/11-self-modify
branch
from
July 30, 2026 10:06
1a8b5c1 to
7ebd10e
Compare
…leniently Three from the review, and the case the first one made visible. Require an `origin` remote up front rather than "a remote". Everything downstream names origin — the ls-remote, the fetch, the branch the worktree comes from, the push — as does the sync that pulls a merged change back, which has no remote setting either. So a workspace whose only remote is `upstream` passed the check and was then told origin was unreachable, and prescribed a `git remote set-head origin -a` that fails for the same reason. Following that name here instead would be worse: the proposal has to target the branch sync pulls from, so the PR would be one that can never reach the instance. Stage through a helper that falls back to $TMPDIR. The mkdtemp sat outside the try, so an unwritable workspace parent raised PermissionError out of a function documented "Never raises". Beside the workspace is still the preference — an untracked directory inside it would show up in the status ff-only sync reads — but the parent being writable is an assumption nothing else makes, since sync needs the workspace itself and no more. A workspace provisioned into a root-owned directory with the daemon unprivileged is an ordinary locked-fleet layout. Decode gh's output as UTF-8 with errors="replace" and catch broad Exception, as sync_service._git already does. errors="strict" is the default under every codec, so this was not a C-locale edge case: one byte gh relayed from elsewhere raised. It raised after the push, which replaced "branch pushed but 'gh pr create' failed … Open the PR manually" with an internal error naming a codec, leaving a pushed branch with nothing pointing at it. The origin check then made the local case visible. The nerve-workspace skill offered the PR route and only that route — "always propose", "don't edit tracked config files directly", "don't try to bypass it by editing files directly" — while being installed by every `nerve init`, which never git-inits the workspace. An unlocked instance with a local workspace was told to always propose, refused by the tool, and told not to edit: it would report that its config could not be changed on a box where writing the file is both allowed and correct. The refusal now says which situation it is, split by lockdown in the handler because that is the layer told whether the instance is locked, and the skill establishes whether the workspace is shared before choosing a route. Co-Authored-By: Claude <noreply@anthropic.com>
alex-clickhouse
marked this pull request as ready for review
July 30, 2026 11:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
propose_config_change, plus anerve-workspaceskill that tells the agent how to use it. Under lockdown the agent cannot write tracked config directly, so it opens a pull request against the workspace repo instead: branch, commit, push, PR. Unlocked installs keep editing directly.Proposals are confined to the reviewed-config surface and classified by effect rather than by file extension, and the destination path is resolved before containment is checked.
Why
Lockdown removes the agent's ability to change its own configuration, which is the intent — but with nothing in its place, the agent's only recourse is to ask a human to make the edit, and the useful half of self-modification is lost along with the dangerous half. A pull request keeps the capability and puts a review in front of it.
Classifying by effect rather than extension is the security-relevant part. A gate plugin is executable code regardless of what it is called, so a proposal touching one is a different kind of change from a schedule edit, and deciding that from the filename would be trivially avoidable.
Resolving the destination before checking containment is the ordering that makes traversal detectable: checking a path that still contains
..tests the string rather than the location it names.🤖 Generated with Claude Code