diff --git a/docs/cross-project-deps.md b/docs/cross-project-deps.md new file mode 100644 index 00000000..0d5b4f40 --- /dev/null +++ b/docs/cross-project-deps.md @@ -0,0 +1,248 @@ +# Cross-Project Dependencies + +> Design for tracking dependencies across project boundaries without coupling to +> specific orchestrators. + +## Problem Statement + +When working on Gas Town, we frequently hit dependencies on Beads features (and +vice versa). Currently there's no formal mechanism to: + +1. Declare "I need capability X from project Y" +2. Signal "capability X is now available" +3. Park work waiting on external dependencies +4. Resume work when dependencies are satisfied + +## Design Principles + +1. **Beads-native**: The core mechanism lives in Beads, not Gas Town +2. **Orchestrator-agnostic**: Works with Gas Town, other orchestrators, or none +3. **Capability-based**: Reference published capabilities, not internal issue IDs +4. **Semaphore pattern**: Producer signals, consumers wait, no coordinator required + +## The Mechanism + +### Provider Side: Shipping Capabilities + +Projects declare and ship capabilities using labels: + +```bash +# Declare intent (optional, for visibility) +bd create --title="Add --assignee to mol run" \ + --add-label=export:mol-run-assignee + +# ... do work, close issue ... + +# Ship the capability (adds provides: label) +bd ship mol-run-assignee +``` + +The `bd ship` command: +- Finds issue with `export:mol-run-assignee` label +- Validates issue is closed (or `--force`) +- Adds `provides:mol-run-assignee` label +- Optionally notifies downstream (future) + +**Protected namespace**: `provides:*` labels can only be added via `bd ship`. + +### Consumer Side: Declaring Dependencies + +Projects declare external dependencies on capabilities: + +```bash +# At creation +bd create --title="Use --assignee in spawn.go" \ + --blocked-by="external:beads:mol-run-assignee" + +# Or later +bd update gt-xyz --blocked-by="external:beads:mol-run-assignee" +``` + +The `external:project:capability` syntax: +- `external:` - prefix indicating cross-project reference +- `project` - name from `external_projects` config +- `capability` - the capability name (matches `provides:X` label) + +### Configuration + +Projects configure paths to external projects: + +```yaml +# .beads/config.yaml +external_projects: + beads: ../beads + gastown: ../gastown + # Can also use absolute paths + other: /Users/steve/projects/other +``` + +### Resolution + +`bd ready` checks external dependencies: + +```bash +bd ready +# gt-xyz: blocked by external:beads:mol-run-assignee (not provided) +# gt-abc: ready + +# After beads ships the capability: +bd ready +# gt-xyz: ready +# gt-abc: ready +``` + +## Molecule Integration + +### Parking a Molecule + +When a polecat hits an external dependency mid-molecule: + +```bash +# Polecat discovers external dep not satisfied +gt park --step=gt-mol.3 --waiting="beads:mol-run-assignee" +``` + +This command: +1. Adds `blocked_by: external:beads:mol-run-assignee` to the step +2. Clears assignee on the step and molecule root +3. Sends handoff mail to self with context +4. Shuts down the polecat + +**"Parked" is a derived state**, not a new status: +- Molecule status: `in_progress` +- Molecule assignee: `null` +- Has step with unsatisfied external `blocked_by` + +### Querying Parked Work + +```bash +# Find parked molecules +bd list --status=in_progress --no-assignee --type=molecule + +# See what's blocked and on what +bd list --has-external-block +``` + +### Resuming Parked Work + +**Manual (launch):** +```bash +gt spawn --continue gt-mol-root +# Spawns polecat, which reads handoff mail and continues +``` + +**Automated (future):** +Deacon patrol checks parked molecules: +```yaml +- step: check-parked-molecules + action: | + For each molecule with status=in_progress, no assignee: + Check if external deps are satisfied + If yes: spawn polecat to resume +``` + +## Implementation Plan + +### Phase 1: Beads Core (bd-* issues) + +1. **bd ship command**: Add `provides:` label, protect namespace +2. **external: blocked_by**: Parse and store external references +3. **external_projects config**: Add to config schema +4. **bd ready resolution**: Check external deps via configured paths + +### Phase 2: Gas Town Integration (gt-* issues) + +1. **gt park command**: Set blocked_by, clear assignee, handoff, shutdown +2. **gt spawn --continue**: Resume parked molecule +3. **Patrol step**: Check parked molecules for unblocked + +### Phase 3: Automation (future) + +1. Push notifications on `bd ship` +2. Auto-resume via patrol +3. Cross-rig visibility in `gt status` + +## Examples + +### Full Flow: Adding --assignee to bd mol run + +**In beads repo:** +```bash +# Dave creates the issue +bd create --title="Add --assignee flag to bd mol run" \ + --type=feature \ + --add-label=export:mol-run-assignee + +# Dave implements, tests, closes +bd close bd-xyz + +# Dave ships the capability +bd ship mol-run-assignee +# Output: Shipped mol-run-assignee (bd-xyz) +``` + +**In gastown repo:** +```bash +# Earlier: Joe created dependent issue +bd create --title="Use --assignee in spawn.go" \ + --blocked-by="external:beads:mol-run-assignee" + +# bd ready showed it as blocked +bd ready +# gt-abc: blocked by external:beads:mol-run-assignee + +# After Dave ships: +bd ready +# gt-abc: ready + +# Joe picks it up +bd update gt-abc --status=in_progress --assignee=gastown/joe +``` + +### Parking Mid-Molecule + +```bash +# Polecat working on molecule, hits step 3 +# Step 3 needs beads:mol-run-assignee which isn't shipped + +gt park --step=gt-mol.3 --waiting="beads:mol-run-assignee" +# Setting blocked_by on gt-mol.3... +# Clearing assignee on gt-mol.3... +# Clearing assignee on gt-mol-root... +# Sending handoff mail... +# Polecat shutting down. + +# Later, after beads ships: +gt spawn --continue gt-mol-root +# Resuming molecule gt-mol-root... +# Reading handoff context... +# Continuing from step gt-mol.3 +``` + +## Design Decisions + +### Why capability labels, not issue references? + +Referencing `beads:bd-xyz` couples consumer to producer's internal tracking. +Referencing `beads:mol-run-assignee` couples to a published interface. + +The producer can refactor, split, or reimplement bd-xyz without breaking consumers. + +### Why "parked" as derived state? + +Adding a new status creates migration burden and complicates the state machine. +Deriving from existing fields (in_progress + no assignee + blocked) is simpler. + +### Why handoff via mail? + +Mail already handles context preservation. Parking is just a handoff to future-self +(or future-polecat). No new mechanism needed. + +### Why config-based project resolution? + +Alternatives considered: +- Git remote queries (complex, requires network) +- Hardcoded paths (inflexible) +- Central registry (single point of failure) + +Config is simple, explicit, and works offline. diff --git a/docs/molecules.md b/docs/molecules.md index 43057bad..867b4307 100644 --- a/docs/molecules.md +++ b/docs/molecules.md @@ -621,6 +621,45 @@ Codebase improves overnight Repeat weekly ``` +## Cross-Project Dependencies and Parking + +Molecules can hit external dependencies that block progress. See +[cross-project-deps.md](cross-project-deps.md) for the full design. + +### Parking a Molecule + +When a molecule step depends on a capability from another project: + +```bash +# Polecat discovers external dependency not satisfied +gt park --step=gt-mol.3 --waiting="beads:mol-run-assignee" +``` + +The molecule enters a **parked** state (derived from: in_progress + no assignee + blocked step). + +### Resuming a Parked Molecule + +When the external dependency is satisfied: + +```bash +# Manual resume +gt spawn --continue gt-mol-root + +# Or automated via Deacon patrol (future) +``` + +### Parked State + +"Parked" is not a new status - it's derived from existing fields: +- Molecule status: `in_progress` +- Molecule assignee: `null` (no polecat owns it) +- Has step with unsatisfied `blocked_by: external:...` + +Query parked molecules: +```bash +bd list --status=in_progress --no-assignee --type=molecule +``` + ## Future Extensions ### Custom Molecule Types