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>
This commit is contained in:
248
docs/cross-project-deps.md
Normal file
248
docs/cross-project-deps.md
Normal file
@@ -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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user