Support a managed-hosting posture: agent edits live source, no DMC/git - #315
Merged
Conversation
wp-coding-agents produced exactly one shape of install: an engineering
posture where installed WordPress source is read-only reference and every
code change routes through a Data Machine Code workspace, git, and GitHub.
That is wrong for managed agentic hosting, where the agent edits the live
theme and plugins in place and changes are captured out-of-band. On
h44lacrosse.com the generated install actively blocked its own architecture:
opencode.json denied edit on wp-content/{plugins,themes} — the two trees the
agent exists to edit — while granting an empty workspace directory, and the
AGENTS.md prose told it to open pull requests it had no repo for.
Posture is a cross-cutting install-time concern, so the same three globs were
hardcoded independently in five places: both runtime permission writers, the
Codex filesystem profile, the opencode.json reconciler, and the guidance
prose. Adding a conditional to each is how they drift apart again.
- lib/source-policy.sh is the single answer all five derive from. Duplicated
policy is the reason prose and permissions could disagree; now they cannot.
- guidance/ splits AGENTS.md content from the mu-plugin mechanism, following
the existing runtimes/ and bridges/ dispatcher pattern. Sections whose prose
depends on posture ship one file per variant rather than a conditional
wrapped around a heredoc. lib/agents-md-guidance.sh no longer knows what
WordPress or Homeboy are.
- managed skips data-machine-code entirely on both setup and upgrade. Hand
deactivation never stuck because update_plugin_to_latest_tag reactivates.
AGENTS.md still composes: data-machine core registers the file itself.
- The managed wordpress-source variant carries the production contract an
agent editing live source needs — edits are live on save, work is unbacked
until captured, and which paths a capture silently skips.
- Posture is recorded on the install so upgrades converge instead of
reverting a managed box to engineering.
Engineering output is unchanged: opencode.json and claude-code settings.json
verified byte-identical to pre-refactor generation, and tests/posture.sh pins
both that invariant and the agreement between managed prose and permissions.
This was referenced Aug 2, 2026
chubes4
added a commit
that referenced
this pull request
Aug 3, 2026
…e gaps Closes #322. Supersedes #320, which fixed false claims inside a shape that was itself wrong. PURPOSE. The section exists so the agent is an expert on its own runtime by reading the WordPress installed underneath it — that is what lets a small model be competent about WordPress with no skills and no fine-tuning. Direct Reference was the point; Read-Only was a qualifier. The managed variant added in #315 inverted that into an editable list, a read-only list, a justification, a production contract, and a "not source" appendix, with the reference clause buried in the middle. The restriction half was also redundant. lib/source-policy.sh makes the runtime enforce it; prose enforces nothing. Restating the deny list bought no safety while crowding out the one thing prose is uniquely good at. Both variants now lead with what the source is FOR and name the boundary only so the agent is not misinformed about it. The managed section is roughly half its previous length. CORE WAS HALF-DEFINED. wp-admin and wp-includes are siblings, not nested. The root set was wp-content/plugins, wp-content/themes, wp-includes — so on every install ever created the agent could edit wp-admin and every root PHP file, including wp-config.php, which holds the database credentials and the constants that gate AGENTS.md composition. As reference the same omission pointed the agent away from list tables, media, and the upgrade routines. Both halves fixed by defining core once. wp-content/mu-plugins is now denied too. It is agent governance, not a site extension: wp-coding-agents installs the mu-plugin that GENERATES AGENTS.md there. wp-content/uploads stays open because the agent's memory lives under it. DECLARED EXCEPTIONS. wp-config.php has real uses (WP_DEBUG, memory limits), so --managed-writable re-opens a denied path. It cannot reuse --managed-source: that flag means editable AND captured, which is what makes the "your work is recorded" promise true, and wp-config.php is not captured by a component harvest. Conflating them would assert a safety property that does not hold — the #318 failure. Separate category, separate prose. LOGS. The limitation was backwards. We were strict about editing core (correct; an update overwrites it anyway) and accidentally strict about READING the one thing needed to recover from a fatal. OpenCode gates paths outside the site root behind external_directory, which defaults to ask, and an autonomous agent has nobody to ask — so the most important recovery capability in the design sat behind an unanswerable prompt. --log-path grants read and explicitly denies edit. GLOB TRAP. Root files are exact literals, not globs. OpenCode's matcher turns * into .* which spans slashes, so wp-*.php would also match wp-content/plugins/acme/wp-thing.php and over-deny inside a site's own component. source_policy_edit_rules now emits a dir/file kind so each runtime can format correctly. tests/posture.sh pins the purpose (reference framing present in both variants), the enumerate-never-generalise rule, the writable-is-not-captured distinction, log paths readable but not writable, uploads untouched, ordering, and the #320 guard against naming one install's stack.
chubes4
added a commit
that referenced
this pull request
Aug 3, 2026
…-config/mu-plugins gaps (#321) * fix: stop asserting one install's stack in managed guidance (#320) The managed wordpress-source section is generated into the AGENTS.md of every managed install, but it described h44lacrosse.com's stack as if it were universal: - "including commerce and payment code" and "the site's ability to take money" — there may be no store and no gateway - "the runtime that gives you memory and tools" — that is Data Machine, this install's happenstance, not a property of wp-content/plugins - a specific never-captured list (vendor, node_modules, composer.lock, package-lock.json, package.json) — one operator's harvest excludes copied verbatim, from a config this tool does not own and cannot read - "exactly what the operator's capture records" — the intended invariant of the posture, which the tool has no way to verify This is the #314/#318 failure in a third form. Those were prose claiming things the enforced policy could not back; this is prose claiming things about the environment that the tool never inspected. An agent told that editing a plugin breaks the site's ability to take money, on a site with no commerce plugin, has been handed a false model of its own install by the file that is supposed to be its most reliable source of truth. Presenting a guessed exclude list is worse than silence, because the agent will trust it. Now describes the category and the reason and leaves the specifics to the operator: plugins this site did not author, including whatever this agent depends on; an update overwrites the change and nothing captures it, both true regardless of what any plugin does; installed and generated files are not authored source, so ask the operator which paths this site actually captures rather than assume. tests/posture.sh gains a guard asserting the rendered prose names none of WooCommerce, Stripe, commerce, payment, money, composer.lock, package-lock, node_modules, Data Machine, homeboy, harvest.yml, or wp-admin. Verified non-vacuous: reintroducing a single product name fails the suite. The two rules this file must follow are now recorded at the top of it. * fix: restore the wordpress-source section's purpose and close the core gaps Closes #322. Supersedes #320, which fixed false claims inside a shape that was itself wrong. PURPOSE. The section exists so the agent is an expert on its own runtime by reading the WordPress installed underneath it — that is what lets a small model be competent about WordPress with no skills and no fine-tuning. Direct Reference was the point; Read-Only was a qualifier. The managed variant added in #315 inverted that into an editable list, a read-only list, a justification, a production contract, and a "not source" appendix, with the reference clause buried in the middle. The restriction half was also redundant. lib/source-policy.sh makes the runtime enforce it; prose enforces nothing. Restating the deny list bought no safety while crowding out the one thing prose is uniquely good at. Both variants now lead with what the source is FOR and name the boundary only so the agent is not misinformed about it. The managed section is roughly half its previous length. CORE WAS HALF-DEFINED. wp-admin and wp-includes are siblings, not nested. The root set was wp-content/plugins, wp-content/themes, wp-includes — so on every install ever created the agent could edit wp-admin and every root PHP file, including wp-config.php, which holds the database credentials and the constants that gate AGENTS.md composition. As reference the same omission pointed the agent away from list tables, media, and the upgrade routines. Both halves fixed by defining core once. wp-content/mu-plugins is now denied too. It is agent governance, not a site extension: wp-coding-agents installs the mu-plugin that GENERATES AGENTS.md there. wp-content/uploads stays open because the agent's memory lives under it. DECLARED EXCEPTIONS. wp-config.php has real uses (WP_DEBUG, memory limits), so --managed-writable re-opens a denied path. It cannot reuse --managed-source: that flag means editable AND captured, which is what makes the "your work is recorded" promise true, and wp-config.php is not captured by a component harvest. Conflating them would assert a safety property that does not hold — the #318 failure. Separate category, separate prose. LOGS. The limitation was backwards. We were strict about editing core (correct; an update overwrites it anyway) and accidentally strict about READING the one thing needed to recover from a fatal. OpenCode gates paths outside the site root behind external_directory, which defaults to ask, and an autonomous agent has nobody to ask — so the most important recovery capability in the design sat behind an unanswerable prompt. --log-path grants read and explicitly denies edit. GLOB TRAP. Root files are exact literals, not globs. OpenCode's matcher turns * into .* which spans slashes, so wp-*.php would also match wp-content/plugins/acme/wp-thing.php and over-deny inside a site's own component. source_policy_edit_rules now emits a dir/file kind so each runtime can format correctly. tests/posture.sh pins the purpose (reference framing present in both variants), the enumerate-never-generalise rule, the writable-is-not-captured distinction, log paths readable but not writable, uploads untouched, ordering, and the #320 guard against naming one install's stack.
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.
Closes #314.
The bug this starts from
h44lacrosse.comis managed agentic hosting: h44-bot edits the live theme and plugins in place, andchubes4/h44-lacrosse.github/workflows/harvest.ymlcaptures the result into git out-of-band (homeboy harvest --check/--apply, daily +workflow_dispatch, read-only over SSH, deploy stays manual). The agent has no repo, no homeboy, and no role in that pipeline.The install wp-coding-agents gave it says the opposite three times over. Live
opencode.json:Hard-denied on exactly the two trees it exists to edit, while granted a workspace directory that is empty (
workspace list→ "No repos in workspace"). The AGENTS.md prose separately told it to route changes through a workspace and pull requests.Why a refactor rather than a flag
Posture touches five files that had independently hardcoded the same three globs:
permission.editruntimes/opencode.shpermissions.denyruntimes/claude-code.shruntimes/codex.shopencode.jsonreconciliationlib/repair-opencode-json.pylib/agents-md-guidance.shBolting
if postureonto each is how prose and permissions drift apart again.What changed
lib/source-policy.sh— one answer, five consumers. Returns the root/action matrix and whether a workspace exists for the active posture. This de-duplicates policy that was already duplicated before posture existed.guidance/— AGENTS.md content split out of the mu-plugin mechanism, following the existingruntimes/andbridges/_dispatch.shpattern. A section whose prose depends on posture ships<id>.<posture>.sh; posture-neutral sections ship<id>.sh.lib/agents-md-guidance.shis now mechanism only and no longer knows what WordPress or Homeboy are.setup.sh/upgrade.shcallguidance_sync_all.lib/data-machine.sh—managedskips data-machine-code on setup and upgrade. Hand-deactivating never stuck becauseupdate_plugin_to_latest_tagactivates. AGENTS.md composition is unaffected: data-machine core registers the composable file itself behind the sameDATAMACHINE_COMPOSE_AGENTS_MDgate, so a managed install still composes correctly, just without the workspace architecture.Posture persistence — recorded as a wp option at setup (mirroring
datamachine_code_homeboy_available), soupgrade.shconverges a managed box instead of silently reverting it.--postureon either script overrides and re-records.Managed guidance content — carries the facts the engineering variant has no reason to state: edits are live on save, work is unbacked until captured, rollback is an operator action, and the paths a capture silently skips (
vendor/,node_modules/, lockfiles, manifests, build output) are destroyed by the next deploy if edited live.Posture matrix
engineering(default)managedwp-content/themes/,wp-content/plugins/wp-includes/Verification
opencode.jsonand.claude/settings.jsondiffed byte-for-byte against generation fromorigin/main(modulo tmpdir paths): identical. Codex covered by the existingtests/codex-permissions.sh.tests/posture.sh(new, wired intoshell.yml) pins both invariants: engineering byte-identity, and agreement between managed prose and managed permissions. Also covers an unknown posture degrading to read-only rather than to write, and a posture switch actively clearing stale Claude Code denies — leaving them behind is the exact failure mode that blocked h44-bot.tests/*.shrun: no new failures. Seven pre-existing failures (agents-md-backup-retention,carried-claude-code-plugin,cli-channel-binary-path,datamachine-worker,homeboy-dmc-provider,homeboy-project-id,kimaki-agent-fallback) fail identically onorigin/mainin this environment; none are inshell.yml.bash -nclean across every.sh.Follow-up, not in this PR
Applying
--posture managedto the live h44 box is an operator action and is deliberately left out.