Files
gastown/docs/cross-project-deps.md
Steve Yegge 0e942c71f2 Add cross-project dependency design and update molecules doc (gt-hbg5)
New design for tracking dependencies across project boundaries:
- Capability-based: reference provides:X labels, not issue IDs
- bd ship command for publishing capabilities
- external: prefix in blocked_by for cross-project refs
- Molecule parking for blocked work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 22:41:07 -08:00

6.3 KiB

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:

# 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:

# 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:

# .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:

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:

# 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

# 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):

gt spawn --continue gt-mol-root
# Spawns polecat, which reads handoff mail and continues

Automated (future): Deacon patrol checks parked molecules:

- 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:

# 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:

# 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

# 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.