Skip to content

fix(skillkit,harness): restore the OpenCode legacy AGENTS.md cleanup dropped by #947's squash; mark nested harness sessions - #1003

Merged
santoshkumarradha merged 7 commits into
mainfrom
fix/skillkit-opencode-legacy
Aug 28, 2026
Merged

fix(skillkit,harness): restore the OpenCode legacy AGENTS.md cleanup dropped by #947's squash; mark nested harness sessions#1003
santoshkumarradha merged 7 commits into
mainfrom
fix/skillkit-opencode-legacy

Conversation

@AbirAbbas

Copy link
Copy Markdown
Contributor

Summary

Restores code that was reviewed on #947 but never reached main. The squash commit for #947 (9a14e21e) was built from the 08-25 head, not the enqueued head a2762e14: the four commits af606e78, b547f480, 3108d3cf, a2762e14opencodeTarget.legacyRulesPath(), removeLegacyMarkerBlock(), the 346-line cleanup contract test, and the testmain_test.go home snapshot — are absent (257 of the PR's 379 added lines). Every machine that ran af skill install before 0.1.136 therefore still carries three <!-- agentfield-skill:… --> blocks in ~/.config/opencode/AGENTS.md, which OpenCode loads into every session as always-on rules — including harness sessions AgentField itself spawns. That is the recursion reported on #960, and a permission.skill deny cannot touch it. The skillkit files on this branch are byte-identical to a2762e14.

Deliberately not the codex helper: ~/.config/opencode/AGENTS.md is user-authored, so the restored cleanup never opens it for writing unless one of our blocks is present (mtime preserved), deletes it only when removing our block emptied it, and is advisory on install / fatal on uninstall.

Nested-harness marker. Every CLI harness provider in Python, Go and TypeScript (including the Pi/OMP providers from #913) now sets AGENTFIELD_HARNESS_DEPTH in the child environment — 1, or parent+1 — with a caller-supplied value winning. Nothing else in the environment is removed or altered (whether harness children should be stripped of AGENTFIELD_SERVER/API key by default is a separate product decision). Both shipped skills gain a note: inside an AgentField harness session, do not dispatch to AgentField agents unless explicitly asked.

Docs. docs/harness-providers.md credited OpenCode with native tool/permission flags; no OpenCode provider reads tools or permission_mode at all. The table and prose now say what OpenCode actually receives (model, directory, prompt).

Validation contract → tests

  1. Install strips exactly our legacy blocks; a user file without our block is byte-identical with mtime unchanged; a file emptied by the removal is deleted; uninstall removes the block; install cleanup is warn-only, uninstall cleanup is fatal — TestOpenCodeInstallStripsLegacyMarkerBlockAndKeepsForeignContent, TestOpenCodeInstallDeletesLegacyRulesFileItOwnedAlone, TestOpenCodeLeavesARulesFileWithoutOurBlockUntouched, TestOpenCodeUninstallRemovesLinksAndLegacyBlocks, TestOpenCodeCleanupIsANoOpWithoutALegacyRulesFile, TestOpenCodeUninstallReportsAnUnreadableLegacyRulesFile, TestOpenCodeInstallSurvivesALegacyRulesFileItCannotClean, TestOpenCodeUninstallReportsLegacyRewriteFailures
  2. Depth marker default / increment / caller override in each SDK — test_run_cli_sets_harness_depth, test_run_cli_caller_harness_depth_wins (py); TestRunCLI/sets_and_increments_harness_depth_while_caller_env_wins (go); sets harness depth from parent %s and caller %s (ts)
  3. Embedded skill mirrors stay in sync — TestAgentfieldUseEmbeddedFallbackContractMatchesSource, TestSkillCatalogAndEmbeddedMirrorsStayAligned, TestEmbeddedSkillSyncCheck

Test plan

  • cd control-plane && go build ./... && go test ./internal/skillkit/... -count=1
  • cd sdk/python && ruff check . && ./scripts/run_pytest.sh
  • cd sdk/go && go mod tidy && go build ./... && go test ./... -count=1
  • cd sdk/typescript && npm ci && npm run build && npm test
  • gofmt -l on touched Go files: clean

Refs #960 #947 #813

🤖 Generated with Claude Code

AbirAbbas and others added 7 commits August 27, 2026 18:26
Moving the OpenCode target to a native ~/.config/opencode/skills/<name>
symlink leaves the marker block older af binaries appended to
~/.config/opencode/AGENTS.md behind forever: uninstallMarkerBlock is no
longer reachable for this target, so nothing can remove it. Upgrading
users end up with the native skill *and* the stale instructions — the
AGENTS.md bloat #813 was actually about. Codex made the same migration in
#910 and shipped removeLegacyMarkerBlock for exactly this reason.

Install (once the symlink is in place) and Uninstall (per catalog skill)
now strip that block. The rules are deliberately stricter than the Codex
helper, because the two files are not alike: Codex's AGENTS.override.md
was created by af for itself, while ~/.config/opencode/AGENTS.md is
written by the user and read by OpenCode. So a file holding no block of
ours is never opened for writing — bytes and mtime stay exactly as the
user left them — and the file is deleted only when removing our block is
what emptied it. Reusing uninstallMarkerBlock verbatim would instead
rewrite any AGENTS.md it can read (measured: a user file with no
agentfield block goes 23 -> 21 bytes) and delete a deliberately empty
one on every install.

Other tools' marker blocks and user prose on both sides of ours survive;
a missing file is a no-op; read/write failures propagate, matching the
target's existing Uninstall error contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…new root

TestOpenCodeTargetUninstallRemovesCatalogEntries was the one OpenCode
test that did not call withTempHome, so it built and tore down catalog
entries in the home shared by the whole package instead of its own.

realHomeSnapshot also still only fingerprinted the old
~/.config/opencode/AGENTS.md. Now that OpenCode installs a directory of
symlinks, add ~/.config/opencode/skills so the real-home pollution guard
covers the path this target actually writes to.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kage seams

removeLegacyMarkerBlock called os.Remove/os.WriteFile/os.Rename directly
while every other write path in the package goes through the reconcile*
seams (reconcile.go), which exist precisely so a test can force a failure.
The consequence was that its "remove", "write" and "rename into" branches
could not be exercised at all: six lines that never ran once, and error
strings that could ship wrongly wrapped without anything noticing.

Switch the four filesystem calls to reconcileReadFile/reconcileRemove/
reconcileWriteFile/reconcileRename and cover each failure through
Uninstall, modelled on the reconciler's own rewrite-failure subtests.

Also drop legacyRulesPath's error return. It could only fail when
TargetPath() fails, and both call sites have already proven TargetPath()
succeeds before reaching it — so the branch was unreachable and told a
reader about a failure mode that does not exist. It now takes the resolved
skills root, which lets Uninstall use the TargetPath() result it was
already computing and discarding instead of re-resolving it per skill.

No behaviour change: same files read, same files written, same errors
returned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…block cannot be cleaned

Restores skillkit changes from a2762e1 (PR #947) dropped by the squash 9a14e21.
Documents AGENTFIELD_HARNESS_DEPTH and removes unsupported OpenCode tool and permission claims.
@AbirAbbas
AbirAbbas requested a review from a team as a code owner August 27, 2026 22:51
@github-actions

Copy link
Copy Markdown
Contributor

Performance

SDK Memory Δ Latency Δ Tests Status
Python 9.0 KB - 0.30 µs -14%
Go 221 B -21% 0.60 µs -40%
TS 487 B +39% 1.55 µs -22%

Regression detected:

  • TypeScript memory: 350 B → 487 B (+39%)

@github-actions

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 87.50% 87.40% ↑ +0.10 pp 🟡
sdk-go 93.00% 92.00% ↑ +1.00 pp 🟢
sdk-python 94.38% 93.73% ↑ +0.65 pp 🟢
sdk-typescript 91.61% 90.42% ↑ +1.19 pp 🟢
web-ui 84.76% 84.79% ↓ -0.03 pp 🟡
aggregate 85.77% 85.75% ↑ +0.02 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 43 100.00%
sdk-go 5 100.00%
sdk-python 0 ➖ no changes
sdk-typescript 3 100.00%
web-ui 0 ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

@santoshkumarradha santoshkumarradha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tested this locally with go test ./internal/skillkit -count=1 in control-plane plus uv run --project sdk/python --extra dev pytest sdk/python/tests/test_run_cli_env.py -q. The legacy OpenCode cleanup behavior and the nested harness-depth propagation both look consistent across the touched surfaces.

@santoshkumarradha
santoshkumarradha added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 29c209f Aug 28, 2026
45 checks passed
@santoshkumarradha
santoshkumarradha deleted the fix/skillkit-opencode-legacy branch August 28, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants