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>
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:
- Declare "I need capability X from project Y"
- Signal "capability X is now available"
- Park work waiting on external dependencies
- Resume work when dependencies are satisfied
Design Principles
- Beads-native: The core mechanism lives in Beads, not Gas Town
- Orchestrator-agnostic: Works with Gas Town, other orchestrators, or none
- Capability-based: Reference published capabilities, not internal issue IDs
- 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-assigneelabel - Validates issue is closed (or
--force) - Adds
provides:mol-run-assigneelabel - 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 referenceproject- name fromexternal_projectsconfigcapability- the capability name (matchesprovides:Xlabel)
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:
- Adds
blocked_by: external:beads:mol-run-assigneeto the step - Clears assignee on the step and molecule root
- Sends handoff mail to self with context
- 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)
- bd ship command: Add
provides:label, protect namespace - external: blocked_by: Parse and store external references
- external_projects config: Add to config schema
- bd ready resolution: Check external deps via configured paths
Phase 2: Gas Town Integration (gt-* issues)
- gt park command: Set blocked_by, clear assignee, handoff, shutdown
- gt spawn --continue: Resume parked molecule
- Patrol step: Check parked molecules for unblocked
Phase 3: Automation (future)
- Push notifications on
bd ship - Auto-resume via patrol
- 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.