Metadata
| Status | done |
|---|---|
| Assigned | agent-2371 |
| Agent identity | f51439356729d112a6c404803d88015d5b44832c6c584c62b96732b63c2b0c7e |
| Model | claude:opus |
| Created | 2026-05-04T17:37:36.240373654+00:00 |
| Started | 2026-05-04T18:37:32.929910484+00:00 |
| Completed | 2026-05-04T18:41:56.000888949+00:00 |
| Tags | agency,sync,research,federation, eval-scheduled |
| Eval score | 0.92 |
| └ blocking impact | 0.95 |
| └ completeness | 0.95 |
| └ coordination overhead | 0.94 |
| └ correctness | 0.94 |
| └ downstream usability | 0.96 |
| └ efficiency | 0.88 |
| └ intent fidelity | 0.90 |
| └ style adherence | 0.92 |
Description
Description
Workgraph and agentbureau/agency both use SHA-256 content-hash IDs for primitives, but they hash DIFFERENT inputs. This is the load-bearing federation primitive: if hashes diverge, the same primitive imported into both systems gets different IDs and federation breaks.
Concrete delta (cited)
Workgraph hash inputs (src/agency/hash.rs):
- component (
hash.rs:16-35):description + category + content— wider than agency - outcome (
hash.rs:39-52):description + success_criteria— wider than agency - tradeoff (
hash.rs:56-75):acceptable_tradeoffs + unacceptable_tradeoffs + description— wider than agency - role (
hash.rs:79-94): sortedcomponent_ids + outcome_id(composition hash) — agency analog:agent_hash - agent (
hash.rs:98-112):role_id + tradeoff_id(composition hash, withmotivation_idrename alias)
Agency v1.2.4 hash inputs (per spec): SHA-256 of description field only. Identity field is description.
What needs to be decided
- Does wg adopt description-only canonical hashing? Pros: federation hash equality. Cons: two primitives with same description but different category/content/success_criteria collide.
- Or does wg keep wider hashing and add an
agency_compat_hashsidecar for federation? Pros: no breaking change. Cons: two ID spaces, mapping table needed. - Or does wg add
description_hashas a federation-only equivalence key while keeping the wideridfor local identity? - Composition hashes (role, agent): Agency uses
agent_hashas composition identifier. Verify that wg's role/agent hash inputs match agency's composition hash inputs.
Deliverable
wg log entry with:
- Per-primitive hash-input table: wg vs agency, what overlaps, what differs
- Three-way recommendation (subset / sidecar / dual-hash) with the trade-offs scored on: federation correctness, local identity stability, migration cost
- A 5-line pseudocode sketch of the chosen approach
- Acknowledge breakage: every existing
.wg/agency/primitives/*.yamlfilename is the OLD hash; if hash inputs change, all files must be re-hashed and renamed.
Validation
- All five hash functions in src/agency/hash.rs cited with line ranges
- Description-only hashing tradeoffs analyzed (collision risk for outcomes / tradeoffs that share description but differ on success_criteria / acceptable lists)
- Migration plan sketched (re-hash all files, update primitives/{components,outcomes,tradeoffs}/{hash}.yaml filenames, regenerate cache/{roles,agents}/{hash}.yaml)
- Recommendation justified against the user's "deep alignment" framing (exact federation match vs strict superset)
Depends on
Required by
- (none)
Log
- 2026-05-04T17:37:36.218594726+00:00 Task paused
- 2026-05-04T18:37:11.943689565+00:00 Task published
- 2026-05-04T18:37:25.599271126+00:00 Lightweight assignment: agent=Careful Programmer (f5143935), exec_mode=light, context_scope=graph, reason=Careful Programmer is the best fit for correctness-critical repo research and hash-compatibility analysis, with light read-only access sufficient for cited line-range verification and migration planning.
- 2026-05-04T18:37:32.929914992+00:00 Spawned by coordinator --executor claude --model opus
- 2026-05-04T18:37:42.269728946+00:00 Starting research on agency hash-input compatibility — reading hash.rs + types.rs + agency_bridge.rs
- 2026-05-04T18:40:42.250901212+00:00 === RESEARCH: agency hash-input compatibility — federation primitive === ## A. Per-primitive hash-input table (wg vs agency v1.2.4) Format: PRIMITIVE | wg inputs (file:line) | agency inputs | overlap | wg-only delta | Primitive | wg hash inputs | agency hash inputs | overlap | wg-only delta | |--------------|----------------------------------------------------------------|-----------------------|-------------|----------------------------------------------| | Component | description + category + content | description | description | category, content | | | (src/agency/hash.rs:16-35) | | | | | Outcome | description + success_criteria | description | description | success_criteria | | | (src/agency/hash.rs:39-52) | | | | | Tradeoff | description + acceptable_tradeoffs + unacceptable_tradeoffs | description | description | acceptable_tradeoffs, unacceptable_tradeoffs | | | (src/agency/hash.rs:56-75) | | | | | Role (comp) | sorted(component_ids) + outcome_id | n/a — agency has no | none | the entire concept (agency goes | | | (src/agency/hash.rs:79-94) | intermediate Role | | components→agent direct, no Role layer) | | Agent (comp) | role_id + tradeoff_id (serialized as motivation_id) | components_set + outcome + tradeoff (FLAT 3-input agent_hash) | structurally different | wg is 2-level nested SHA(SHA(c+o)+t); agency is 1-level flat SHA(c+o+t) | | | (src/agency/hash.rs:98-112) | | | | Note on the YAML-based hashing in hash.rs: each function builds an inline serde struct, calls serde_yaml::to_string, then SHA-256s the bytes. Agency's spec is "SHA-256 of description field" — a single string, not a serialized struct. So even when description is the only input, byte-equality requires that wg drop the YAML envelope (the keys "description: ", indentation, trailing newline) and hash the raw description string instead. ## B. Description-only collision risk (the load-bearing trade-off) If wg adopts description-only canonical hashing and ALL existing primitives are re-hashed: 1. Outcome collision: `.wg/agency/primitives/outcomes/` has 103 files. Two outcomes can legitimately share a description but differ on success_criteria — e.g. "Working, tested code" with criteria=[unit tests pass] vs criteria=[unit tests pass, integration tests pass, coverage > 80%]. Under description-only hashing these become the SAME id. Currently distinct primitives (with distinct task histories in performance.evaluations) collapse into one. Performance signal is corrupted: high-bar outcomes inherit low-bar evaluations. 2. Tradeoff collision: 206 files. Two tradeoffs sharing description but differing on the acceptable/unacceptable bullet lists collide. The bullet lists are the *operational mechanism* of a tradeoff — collapsing them makes evolve's tradeoff-mutation operators (operations.rs:301, 381, 586) silently produce duplicates: a description-preserving mutation of acceptable_tradeoffs returns the same id as the parent, breaking lineage (parent_id == child_id is currently impossible). 3. Component collision: 363 files. Same description, different ContentRef (Inline vs File) or different category (translated vs novel) collide. ComponentCategory is the wider type — the same description with category=translated has provenance "human skill literally translated" vs novel "machine-only capability"; collapsing them loses provenance. 4. Role collision: agency has no Role-level hash (agency goes components→agent flat). If wg drops Role-as-primitive, the cache/roles/ layer disappears; if wg keeps it, its hash has no agency analog and is wg-private regardless. 5. Agent collision: even with all primitive ids byte-equal, wg's nested composition `SHA(role_id + tradeoff_id)` ≠ agency's flat `SHA(component_set + outcome_id + tradeoff_id)`. Byte-equal agent_hash requires either (a) folding wg's Role layer into Agent at hash time, or (b) abandoning Role as a hashed primitive. ## C. Three-way recommendation (scored on three axes) Scoring scale: + (good) / 0 (neutral) / - (bad) | Approach | Federation correctness | Local identity stability | Migration cost | |-------------------------------------|------------------------|--------------------------|----------------| | 1. Subset (description-only) | + | - | - | | 2. Sidecar (agency_compat_hash) | + | + | + | | 3. Dual-hash (description_hash key) | + | + | 0 | (1) Subset — adopt agency's narrow inputs verbatim. id field becomes SHA-256(description). Pros: byte-equal federation hashes, no translation layer at the agency_bridge boundary. Cons: ~672 primitive YAML files plus all roles + agents in cache must be re-hashed and renamed. ~40 call sites use content_hash_*; all stay structurally identical but every existing performance.evaluations record is keyed on old ids and breaks. The collisions enumerated in §B happen for real today (the bundled starter set has multiple outcomes that share descriptions — confirmed by spot-check of `.wg/agency/primitives/outcomes/`). Lossy. (2) Sidecar — keep current id field (wider hash); add `agency_compat_hash: String` populated at save time. Pros: zero data migration, no YAML rename, no callsite churn. Cons: every code path that interacts with agency (agency_bridge.rs, future starter-CSV import/export) must translate id ↔ agency_compat_hash. Two ID spaces drift over time. CSV roundtrip becomes "import agency starter, recompute agency_compat_hash, store under new wg id" — so the CSV's natural-key column can no longer be the wg primary key. Federation-correct but federation surface gets clumsier. (3) Dual-hash — keep current `id` (wider hash, local identity). ADD `description_hash` field, computed deterministically from raw description string (no YAML envelope), used as the federation equivalence key. Local YAML filename stays on `id`. The agency_bridge boundary maps via description_hash. Importing an agency primitive: compute description_hash from agency's id, look up by description_hash; if absent, instantiate with the agency_compat-narrow extension fields defaulted, store under a wg-id derived from the extension defaults. Exporting: emit description_hash as the agency-side id. Pros: federation-correct (description_hash byte-equals agency's id by construction); local identity stable (existing 672 YAMLs do NOT need renaming); collision risk is contained to the federation surface (resolved at import-time policy: error / merge / fork). Aligns exactly with the design-agency-sync "strict superset" framing — agency primitives drop in unchanged because the description-hash key matches; wg's extensions persist because they still factor into the local id. Cons: two-hash discipline. Code that walks the boundary must remember to use description_hash, not id. One-time work to add the field + populate it + wire agency_bridge. Recommendation: **(3) Dual-hash** — strict-superset framing requires preserving wg's wider-input identity (so extensions are load-bearing locally) AND providing a federation equivalence key that byte-equals agency's id. Subset throws away wg's extensions from identity; sidecar makes the federation surface awkward. Dual-hash is the only option where both invariants hold. ## D. Pseudocode sketch (5 lines) ```rust // In src/agency/hash.rs — add alongside existing content_hash_*: pub fn description_hash(description: &str) -> String { format!("{:x}", Sha256::digest(description.as_bytes())) // raw string, no YAML envelope } // In types.rs RoleComponent / DesiredOutcome / TradeoffConfig: // #[serde(default)] pub description_hash: String, // populated on save, indexed for lookup // In agency_bridge.rs: lookup imports/exports via description_hash; id stays local-only. ``` Composition agent-hash compat (separate function — wg keeps its nested role+tradeoff hash for local id): ```rust pub fn agency_compat_agent_hash(component_descs: &[String], outcome_desc: &str, tradeoff_desc: &str) -> String { // Flat 3-input concatenation hashed identically to agency's spec; emitted only at the federation boundary. } ``` ## E. Migration / breakage acknowledgement Dual-hash chosen approach has the smallest blast radius: - NO existing `.wg/agency/primitives/{components,outcomes,tradeoffs}/{hash}.yaml` filename changes. - NO existing `cache/{roles,agents}/{hash}.yaml` filename changes. - ~40 call sites (graph.rs:1637, executor.rs:1925, match_cmd.rs:158, agency_import.rs:194/231/276, agency_init.rs:51/119, agent_crud.rs:90, role.rs:256, evolve/{operations,meta,mod}.rs:*) all keep working unchanged — they use the wider hashes for local id. - New work: (1) add description_hash field to 3 primitive structs with #[serde(default)] backfill; (2) add description_hash() function to hash.rs; (3) add a one-shot backfill walker (load each YAML, compute description_hash, save — idempotent because description doesn't change); (4) wire agency_bridge.rs lookups through description_hash. If subset (option 1) had been chosen, migration would have been: re-hash 672 primitive files + cache/roles + cache/agents, rename every YAML, rewrite every performance.evaluations key, audit ~40 call sites for tests that pin ids, update bundled starter ids, and bless a new schema_version. ~10x bigger blast radius and lossy via collisions. ## F. Justification against "deep alignment" / strict-superset framing design-agency-sync stated the goal: "make wg a STRICT SUPERSET of agency — agency primitives drop in unchanged, wg's extensions persist." Two invariants: - I1 (drop in unchanged): an agency starter row with description X must be findable by the same hash agency would produce for X. - I2 (extensions persist): wg's extension fields (success_criteria, acceptable_tradeoffs, ContentRef variant, ComponentCategory) must remain part of identity — otherwise two semantically-distinct primitives collide locally. Subset satisfies I1 and violates I2. Sidecar satisfies both but at the cost of an awkward translation surface. Dual-hash satisfies both with the translation surface localized to the federation boundary, which is the only place it logically belongs. Recommendation stands: dual-hash.
- 2026-05-04T18:41:20.377116027+00:00 Validated: §A cites all 5 hash functions (hash.rs:16-35, 39-52, 56-75, 79-94, 98-112); §B analyzes description-only collision risk for outcomes/tradeoffs/components with concrete examples; §E sketches migration plan (dual-hash → no rename, ~40 callsites untouched, vs subset → 672+ files renamed lossy); §F justifies dual-hash against strict-superset I1+I2 invariants. Research-only task — no source/doc edits. No messages to reply to. Marking done.
- 2026-05-04T18:41:56.000903276+00:00 Task pending eval (agent reported done; awaiting `.evaluate-*` to score)
- 2026-05-04T18:43:12.761663099+00:00 PendingEval → Done (evaluator passed; downstream unblocks)