Skip to content

fix: materialize bundle agent identity (SOUL.md) on upgrade (#2818)#2819

Open
chubes4 wants to merge 2 commits into
mainfrom
fix-bundle-soul-materialization
Open

fix: materialize bundle agent identity (SOUL.md) on upgrade (#2818)#2819
chubes4 wants to merge 2 commits into
mainfrom
fix-bundle-soul-materialization

Conversation

@chubes4

@chubes4 chubes4 commented Jun 28, 2026

Copy link
Copy Markdown
Member

Summary

wp datamachine agent upgrade <bundle> --slug=<agent> did not materialize a committed bundle's agent-level memory (agent/SOUL.md) into the live agent store. A flow-less, memory-bearing bundle (manifest.included.memory=["agent/SOUL.md"], flows:[], pipelines:[]) shipped canonical identity in git but the live agent kept running its stale SOUL.

Closes #2818.

Root cause — where agent-level memory was dropped

agent upgrade plans through AgentBundleAbilityService::plan_for_bundle()AgentBundleLifecycleProjection::target_artifacts(). That projection emitted agent_config, pipeline, flow, and file artifacts (prompts/rubrics/tool-policies/auth-refs/seed-queues) but never iterated the bundle's agent-layer memory files. So memory:agent/SOUL.md was absent from the plan entirely (not auto-apply, not needs-approval, not no-op).

AgentBundleUpgradePlanner::artifacts_from_bundle() does build memory artifacts (line 51) — but that method is a separate path not used by plan_for_bundle(). The package planner itself buckets memory artifacts correctly when given them (see tests/agent-bundle-upgrade-planner-smoke.php [1]); the artifacts simply never reached it. The apply side (AgentBundleCoreArtifactApply) likewise had no memory branch, and AgentBundler::import()'s write_agent_files() wrote all $bundle['files'] unconditionally — which would clobber learned memory if a bundle ever carried it.

Confirmed live before the fix (--dry-run against extrachill-roadie/bundles/roadie, agent roadie): plan showed agent_config + 4 prompts as no-ops, memory:agent/SOUL.md absent; live uploads/.../agents/roadie/SOUL.md (Mar 20) differed from the committed bundle SOUL.

The fix

New AgentBundleMemoryArtifact unit owns the authored-identity-vs-learned-memory rule in exactly one place:

  • target_artifacts() — surfaces authored identity (SOUL.md, authority tier agent_identity) as a memory artifact in the plan, with the same hash/diff/approval semantics as every other artifact.
  • current_artifacts() — projects the live store content for installed memory artifacts (ledger-gated) so diffs classify as no-op / needs-approval cleanly and an unmanaged live SOUL is never reported as drift.
  • apply() — writes only bundle-declared authored identity to the live store via AgentMemory::replace_all(), and hard-refuses learned runtime memory with a WP_Error.

Wiring:

  • AgentBundleLifecycleProjection::target_artifacts() / current_artifacts() now include memory artifacts.
  • AgentBundleCoreArtifactApply::apply() gains a memory branch (needs-approval / PendingAction path).
  • AgentBundler::import() materializes SOUL through a ledger-aware, local-modification-protected block on upgrade, and write_agent_files() is skipped for identity files on upgrade (fresh installs still seed every bundle-carried file).

Authored-identity vs learned-memory safeguard

The whole point of committing SOUL is versioned, deployable identity — so authored identity (SOUL.md) is upgradeable. Learned runtime memory (MEMORY.md, WAKE.md, daily/*) must not be clobbered by a deploy. The gate keys on the memory file registry's authority_tier: only agent_identity is materializable; everything else is refused (with a 'SOUL.md' fallback when the registry isn't loaded). On upgrade, learned-memory files are skipped entirely in both the importer and the artifact applier, and apply is local-modification-aware (parity with the existing prompt/rubric/--rebase-local conflict handling) so local SOUL edits surface as needs_approval instead of being blown away.

Verification

  • Repro confirmed before fix (live --dry-run): memory:agent/SOUL.md absent from plan; live SOUL ≠ bundle SOUL.
  • New pure-PHP smoke tests/agent-bundle-memory-artifact-smoke.php (15 assertions, all pass): SOUL surfaces as a target; learned MEMORY.md/daily/* do not; the upgrade guard and apply() refuse learned memory; the unregistered-SOUL.md fallback holds.
  • New integration test test_upgrade_materializes_authored_soul_without_clobbering_learned_memory in AgentBundlerImportTest (full wp_agent_memory_store fake): fresh install writes SOUL + tracks it in the ledger; upgrade materializes the new SOUL into the live store; a learned MEMORY.md written post-install survives the upgrade.
  • Existing bundle smoke tests pass unchanged (agent-bundle-upgrade-planner 37, agent-bundler-directory-adapter 55, agent-bundle-template-upgrade-tracking 9). The 4 pre-existing standalone-smoke failures (extension-artifacts, package-portability, portable-update, runtime-drift) reproduce identically on origin/main and are unrelated to this change.
  • php -l + phpcs clean on all changed files.
  • NOTE: homeboy test data-machine could not bootstrap the WP unit suite — the host wp-codebox runner is currently broken (wp-codebox --version probe regression), as flagged in the task. The new integration test is well-formed and will run once that runner is restored.

Agent-level memory carried by a committed bundle (manifest
included.memory=["agent/SOUL.md"]) was never applied to the live agent
store on `agent upgrade`. The upgrade planner's projection
(AgentBundleLifecycleProjection) emitted agent_config, pipeline, flow,
and file artifacts (prompts/rubrics/etc.) but skipped the bundle's
agent-layer memory entirely, so SOUL.md was absent from the plan and the
live identity stayed stale.

Add a focused AgentBundleMemoryArtifact unit that owns the
authored-identity-vs-learned-memory rule in one place:

- target_artifacts(): surface authored identity (SOUL.md, authority tier
  agent_identity) as a `memory` artifact in the plan.
- current_artifacts(): project the live store content for installed
  memory artifacts so diffs classify as no-op / needs-approval cleanly.
- apply(): write only bundle-declared authored identity to the live store
  via AgentMemory::replace_all(), and hard-refuse learned runtime memory.

Wire it into the planner projection and the core artifact applier, and
make AgentBundler::import() materialize SOUL through a ledger-aware,
local-modification-protected path on upgrade while skipping learned
runtime memory (MEMORY.md, WAKE.md, daily/*) so a deploy never clobbers
what the live agent accumulated. Fresh installs still seed every
bundle-carried file.

Closes #2818
@homeboy-ci

homeboy-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Homeboy Results — data-machine

Lint

lint — passed

ℹ️ Full options: homeboy docs commands/lint
Deep dive: homeboy lint data-machine --changed-since e5becf8

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-lint-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-lint-quality-Linux-node24 contains exported Homeboy run history for deeper queries.
  • Drill-down: download the observation artifact, then run homeboy runs import <dir>, homeboy runs list, and homeboy runs findings <run-id>.
  • Artifacts are attached to the workflow run: https://github.com/Extra-Chill/data-machine/actions/runs/28340456722

Test

test — passed

  • 850 passed
  • 4 skipped

ℹ️ Auto-fix lint issues: homeboy refactor data-machine --from lint --write
ℹ️ Collect coverage: homeboy test data-machine --coverage
ℹ️ Save test baseline: homeboy test data-machine --baseline
ℹ️ Pass args to test runner: homeboy test -- [args]
ℹ️ Full options: homeboy docs commands/test
Deep dive: homeboy test data-machine --changed-since e5becf8

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-test-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-test-quality-Linux-node24 contains exported Homeboy run history for deeper queries.
  • Drill-down: download the observation artifact, then run homeboy runs import <dir>, homeboy runs list, and homeboy runs findings <run-id>.
  • Artifacts are attached to the workflow run: https://github.com/Extra-Chill/data-machine/actions/runs/28340456722

Audit

audit — passed

  • audit — 45 finding(s)
  • Total: 45 finding(s)

Deep dive: homeboy audit data-machine --changed-since e5becf8

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-audit-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-audit-quality-Linux-node24 contains exported Homeboy run history for deeper queries.
  • Drill-down: download the observation artifact, then run homeboy runs import <dir>, homeboy runs list, and homeboy runs findings <run-id>.
  • Artifacts are attached to the workflow run: https://github.com/Extra-Chill/data-machine/actions/runs/28340456722
Tooling versions
  • Homeboy CLI: homeboy 0.269.3+e2ca18f6b37e+d2791d22
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: e124cf1f
  • Action: unknown@unknown

The fresh-install path materialized agent identity via write_agent_files'
direct file_put_contents, which bypasses the WP_Agent_Memory_Store seam.
Against a non-disk store (the wp_agent_memory_store fake that CI registers)
the installed SOUL never landed in the store, so reading it back reported
not-found — failing the new test's "Fresh install writes SOUL.md to the
live store" assertion.

Materialize authored identity through AgentBundleMemoryArtifact::apply()
(the store seam) on BOTH fresh install and upgrade, and skip authored
identity in write_agent_files so it is never double-written and non-disk
stores stay authoritative. Learned runtime memory (MEMORY.md, WAKE.md,
daily/*) is still seeded on fresh install and preserved on upgrade.
@chubes4

chubes4 commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

CI fix — fresh-install SOUL now materializes through the store seam

The failing test (test_upgrade_materializes_authored_soul_without_clobbering_learned_memory, "Fresh install writes SOUL.md to the live store") exposed a real bug in my own fix, not a flaky test.

Root cause: the fresh-install path wrote agent identity via write_agent_files()file_put_contents() (disk-store only), bypassing the WP_Agent_Memory_Store seam. Against the non-disk fake store CI registers via wp_agent_memory_store, the installed SOUL never landed in the store, so reading it back via AgentMemory returned not-found. The upgrade path I'd added used the store seam correctly; the install path did not.

Fix (commit d2791d2):

  • Materialize authored identity through AgentBundleMemoryArtifact::apply() (the store seam) on both fresh install and upgrade — previously the apply() call was gated behind if ( $existing ) (upgrade-only).
  • Skip authored identity in write_agent_files() so it is never double-written and non-disk stores stay authoritative.
  • Learned runtime memory (MEMORY.md, WAKE.md, daily/*) is still seeded on fresh install and preserved on upgrade — unchanged.

Verification:

  • CI is now fully green: Test pass (3m40s, 854 tests), Audit pass, Lint pass, Plan pass.
  • The local host wp-codebox runner is still broken (wp-codebox --version probe), so I additionally proved the exact broken link with a standalone harness that mirrors CI's store-fake: register a fake store via wp_agent_memory_store, call AgentBundleMemoryArtifact::apply(), read back via AgentMemoryexists is now true and content matches; learned MEMORY.md survives a subsequent SOUL upgrade. All bundle smoke tests (agent-bundle-memory-artifact 15, agent-bundle-upgrade-planner 37, agent-bundler-directory-adapter 55) pass; php -l + phpcs clean.

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.

Agent bundle: agent-level memory (agent/SOUL.md) is not materialized into the live agent store on upgrade

1 participant