Skip to content

Preserve Git working context across sessions and subagents#1630

Merged
Aaronontheweb merged 4 commits into
devfrom
feat/subagent-working-context
Jul 14, 2026
Merged

Preserve Git working context across sessions and subagents#1630
Aaronontheweb merged 4 commits into
devfrom
feat/subagent-working-context

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add a bounded, audience-aware Git working-context snapshot at the volatile end of the system prompt so branch, worktree, repository, upstream, and changed-file state stay current without disrupting the stable prompt prefix.
  • Pass recent-file and project context into coding subagents, track first-party file activity during each run, and merge only confirmed successful child edits back into the parent session.
  • Keep public sessions free of repository metadata, sanitize Git failures, avoid remote URL disclosure, and run Git directly with strict output and timeout bounds.
  • Add a repeatable linked-worktree behavioral eval, eval-only trust fixtures, updated operational guidance, and synchronized OpenSpec coverage.

Agent effectiveness in plain language

Before this change, the agent often lost track of which checkout it was working in after switching branches or linked worktrees. It then either edited the matching file in the wrong worktree or spent its turn trying to rediscover context and made no edit.

In ten matched trials using the same model and the same deliberately ambiguous coding task:

  • Before: 2 of 10 tasks succeeded, for a 20% success rate.
  • After: 10 of 10 tasks succeeded, for a 100% success rate.
  • The baseline produced five wrong-file or wrong-worktree edits and three no-edit outcomes. The updated build produced none.
  • Median subagent completion time fell from 53.4 seconds to 21.9 seconds, about 59% faster.

Put simply: the agent went from getting the task right about once in five attempts to getting all ten attempts right because it consistently remembered where it was and which files mattered when handing work to a subagent.

The eval alternated between two linked worktrees and four plausible target files over several turns. The child received a path-free task, so success depended on preserving working context rather than leaking the answer into the request. These are ten-trial results on one configured model/provider, so they are strong evidence for this regression scenario rather than a universal benchmark.

Validation

  • dotnet build src/Netclaw.Daemon/Netclaw.Daemon.csproj -m:1 --no-restore -v:q
  • 75 targeted working-context, session-message, and subagent tests passed
  • Netclaw.Actors.Tests passed 2,575 of 2,575 during the full solution run
  • dotnet slopwatch analyze: 0 new issues
  • pwsh ./scripts/Add-FileHeaders.ps1 -Verify
  • openspec validate --all: 68 passed, 0 failed
  • git diff --check
  • bash -n evals/run-evals.sh
  • Eval SQLite integrity checks passed for both archived runs

The full solution run progressed through the actor test project successfully, then stopped when the host ran out of disk while copying native integration-test assets. The targeted suite and all repository quality gates relevant to this change passed after that interruption.

Security and operational notes

  • Built-in context collection does not request tool approval because it is internal runtime assembly, not an agent-invoked tool.
  • Public-audience prompts suppress Git context.
  • Git is invoked without a shell, with bounded output and timeout behavior.
  • Remote URLs and raw Git error output are not included in prompts.

Closes #1520
Closes #1621

@Aaronontheweb Aaronontheweb added the context-pipeline LLM context assembly: prompt layers, dynamic injection, memory recall, temporal grounding label Jul 13, 2026
@Aaronontheweb
Aaronontheweb force-pushed the feat/subagent-working-context branch from d3efca3 to 6dab249 Compare July 13, 2026 15:43
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentSpawnerTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentSpawnerTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentSpawnerTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentSpawnerTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs Fixed
Comment thread src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs Fixed
ILogger? logger = null,
IActorRef? backgroundJobManager = null,
string? projectDirectory = null,
IReadOnlyList<string>? recentFiles = null,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method signature has gotten out of control and needs to be refactored.

public ImmutableHashSet<string> ChangedFiles { get; init; } = [];
}

public sealed record WorkingContextSnapshot

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems a little too couple to git imho

@Aaronontheweb

Copy link
Copy Markdown
Collaborator Author

Going to merge this because the eval results show this helps with coding A LOT, but launching an immediate clean-up refactor PR afterwards to get some of the tool / context APIs brought to heel.

@Aaronontheweb
Aaronontheweb merged commit ab491a2 into dev Jul 14, 2026
20 of 21 checks passed
@Aaronontheweb
Aaronontheweb deleted the feat/subagent-working-context branch July 14, 2026 17:27
Aaronontheweb added a commit that referenced this pull request Jul 14, 2026
…0.25.0-alpha.onnx.6 (#1642)

* fix: support Discord DM reminders (#1609)

* Refactor ModelContextProtocol versioning in props file (#1614)

Updated ModelContextProtocol package versions to use a variable for versioning.

Signed-off-by: Aaron Stannard <[email protected]>

* fix: serialize Slack processing status updates (#1556)

Co-authored-by: Aaron Stannard <[email protected]>

* ci: run required checks for merge queue groups (#1617)

* Bump MessagePack from 3.1.7 to 3.1.8 (#1605)

---
updated-dependencies:
- dependency-name: MessagePack
  dependency-version: 3.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(cli): model set/picker preserve hand-set modalities on re-set (#1127) (#1610)

* fix(cli): model set/picker preserve hand-set modalities on re-set (#1127)

Re-selecting a model that is already configured wiped operator-set attributes on it.
The write path rebuilt the Models[role] entry from scratch via ModelEntryWriter, which
only writes modalities it was handed (a probe result). Modalities have no CLI input and
can only be hand-edited, so a manual 'model set' (or a context-window tweak, or the TUI
picker re-selecting the same model) passed null and silently deleted a hand-set
InputModalities/OutputModalities — the concrete #1127 loss.

Add ModelEntryWriter.WriteRole, a non-destructive persist: when the role already points
at the same (provider, modelId), preserve the existing modalities and context window the
caller did not supply; switching to a different model still starts clean (old attributes
belonged to the old model). Routed 'model set' and the TUI model manager through it.

Verified against the shipped binary: on stock beta 0.25.0, 'model set main <same-model>
--context-window N' wipes InputModalities; with this fix the same command preserves it.

No config-shape or schema change, so this is fully backwards compatible.

Tests: WriteRole_SameModelWithoutModalities_PreservesHandSetModalities,
WriteRole_DifferentModel_DropsPreviousModelModalities.

* fix(cli): make model-set metadata operator-owned; discovery never clobbers it

Hardens the non-destructive `model set`/picker rewrite (#1127) against every issue
surfaced reviewing #1610, and closes the loop on modality overrides.

ContextWindow and modalities are documented to "take precedence over provider-reported
capability detection", so they are now treated as operator-owned overrides with a single
precedence rule: explicit operator input > existing stored value > probe. A fresh probe
seeds a first-time set or a model switch but never overwrites a value already on disk.

Changes:

1. ContextWindow clamp preserved on same-model re-set. WriteRole takes the explicit
   --context-window and the probe default separately; the old callers collapsed them
   (`contextWindow ?? discovered`), so probe/picker paths always passed a non-null value
   and the operator's clamp was overwritten on every re-selection.

2. Modalities are no longer silently overwritten by discovery. Previously a probe that
   reported modalities replaced a stored override (the #1127 loss's twin); now the stored
   value wins, matching the field's "manual override bypasses detection" contract.

3. Operators can change/remove those overrides. Since discovery no longer edits them, add
   `--input-modalities`, `--output-modalities`, and `--clear-modalities` to `model set`.
   Explicit set replaces the stored value; clear removes it (runtime detection resolves).
   Supplying any of them (like --context-window) skips the probe as manual configuration.

4. Corrupt/legacy existing entry no longer aborts the command. ReadSameModelEntry guards
   the deserialize (catch JsonException): an unreadable entry (e.g. an unrecognized modality
   enum string) degrades to "nothing to preserve" and the command overwrites/repairs it.

5. No false-match on ModelReference defaults. Provider/ModelId default to the stock
   local-ollama model, so an entry omitting either key deserialized to that default and
   would false-match a re-set of the stock model; preservation now requires both keys.

6. Provenance not downgraded. A same-model re-set that did not re-resolve the ID (no probe
   → Manual) keeps a previously discovered origin (Live/Defaults); only a fresh discovery
   updates it.

Tests: ModelEntryWriter unit coverage for each precedence path (clamp-over-probe, probe-
does-not-override-existing-modalities, explicit set, clear-over-probe, first-time seeding,
default-model false-match, corrupt-entry overwrite, provenance preserve/update) plus CLI
end-to-end coverage for the new flags. Full CLI suite green; slopwatch clean; model-manager
smoke tape passes.

* fix(cli): harden model-set overrides + add --clear-context-window (#1610)

Addresses code-review findings on the non-destructive model-set change:

- probe gate: only --context-window short-circuits the probe; a modality flag
  no longer skips model-existence validation and context-window discovery
- preservation read: a corrupt modality enum string no longer discards a valid
  operator-owned ContextWindow (field-tolerant recovery)
- arg parsing: missing flag values and unknown args fail loudly instead of
  being silently dropped
- cleared modality is now sticky: discovery is hands-off once a same-model
  entry exists, so a later probe cannot resurrect a --clear-modalities removal
- TryParseModalities rejects raw numeric strings (named flags only)
- provenance: preserve a prior discovered origin on any non-Live re-set
  (was only guarding Manual)
- new --clear-context-window flag to force window re-detection (symmetry with
  --clear-modalities)

Also hardens LoadModelSelection: a corrupt/legacy config no longer crashes
`model set` (repairs it) or `model list` (reports it cleanly) or the TUI.

Generalizes ModalityOverride into a shared ValueOverride<T> tri-state.
Updates netclaw-operations skill (providers.md). Docs website tracked in
netclaw-dev/netclaw-website#83.

* feat(config): preserve model definitions across role switches

* fix(config): validate named model role references

* fix(cli): preserve models when editing providers

* chore(deps): bump dotnet-sdk from 10.0.300 to 10.0.301 (#1381)

Bumps [dotnet-sdk](https://github.com/dotnet/sdk) from 10.0.300 to 10.0.301.
- [Release notes](https://github.com/dotnet/sdk/releases)
- [Commits](dotnet/sdk@v10.0.300...v10.0.301)

---
updated-dependencies:
- dependency-name: dotnet-sdk
  dependency-version: 10.0.301
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(subagents): fail closed for unattended approvals (#1616)

* Release 0.25.0-beta.3: update release notes and version metadata (#1618)

* Add user-written `AGENTS.md` for application-specific agent guidelines (#1622)

* Add deployment agent mission playbook

* Keep identity routing in embedded guidance

* Evaluate embedded identity routing

* Prioritize specialized subagent guidance

* test: skip SearXNG container test on Windows (#1625)

* Use logical skill access and authoritative inventory refresh (#1634)

* feat(skills): use logical skill access

* docs(evals): restore README

* test(skills): use root-preserving path joins

* Preserve Git working context across sessions and subagents (#1630)

* feat: preserve git context across subagents

* fix: make subagent git context deterministic

* test: make fixture path intent explicit

* Stabilize config search screenshots (#1635)

* Simplify STDIO MCP process ownership (#1636)

* chore: bump Netclaw.SkillClient from 0.4.0-beta.4 to 0.4.0 stable (#1638)

* fix(memory): stop curation dedup from overwriting existing documents (#1637)

When a curation Create decision landed on an anchor that already had a
document, both batch appliers reused the existing document_id, and the
ON CONFLICT DO UPDATE overwrote that document's title, body, and
classification with the new proposal. The old content was lost; there
is no history table to recover it from.

Now a Create collision appends the new content below a dated separator
and keeps the existing title, boundary, audience, and sensitivity. If
the incoming content is already present verbatim, the write is skipped.
Consolidate decisions carry an explicit target document id, so they
keep replacing near-duplicates as designed. Update decisions and
no-collision inserts are unchanged.

* Release 0.25.0-beta.4: update release notes and version metadata (#1640)

---------

Signed-off-by: Aaron Stannard <[email protected]>
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: petabridge-netclaw[bot] <289234546+petabridge-netclaw[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

context-pipeline LLM context assembly: prompt layers, dynamic injection, memory recall, temporal grounding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(subagents): add run-scoped working context and structured file-change handoff feat(sessions): add git context layer to system prompt

1 participant