What Is a Wisp in Beads? Understanding Ephemeral Workflows
A Wisp is Beads’ concept for an ephemeral, non-persisted workflow represented as an issue with Ephemeral=true, stored in a local-only .beads-wisp/ directory that is git-ignored and automatically cleaned up via TTL-based policies.
In the Beads issue tracking system, not all work is meant to persist forever. For temporary experiments, CI/CD coordination, and agent-to-agent communication, the repository implements Wisps—a special class of ephemeral issues that exist only in your local environment. Understanding this concept is essential for developers building automated workflows or managing short-lived operational tasks.
What Defines a Wisp?
A Wisp is fundamentally a regular Beads issue with one critical distinction: the Ephemeral=true flag. This single attribute routes all queries to a separate wisps table (stored under .beads-wisp/) and ensures the data never syncs to remote repositories.
The storage location is explicitly excluded from version control. Because the directory is git-ignored, Wisps remain strictly local to the machine where they are created. This makes them ideal for throw-away work such as local debugging sessions, temporary coordination between automation agents, or short-lived CI/CD steps that do not require historical persistence.
Wisp Classification and TTL Policies
Every Wisp carries a WispType that determines its Time-To-Live (TTL) compaction policy. These definitions live in internal/types/types.go (lines 68‑85), categorizing Wisps by their forensic value and churn rate.
High-Churn Types (6 Hour TTL)
Types in this category generate frequent updates with low long-term value.
heartbeat– Regular health checks from agentsping– Connectivity verification signals
Operational State Types (24 Hour TTL)
These capture medium-term operational data useful for daily monitoring.
patrol– Automated scanning resultsgc_report– Garbage collection summaries
Significant Event Types (7 Day TTL)
High-value events requiring longer retention for debugging and auditing.
recovery– System recovery eventserror– Critical error reportsescalation– Alert escalation chains
Storage Layer Implementation
The core implementation treats Wisps as issues filtered by the ephemeral flag, ensuring seamless integration with existing search machinery while maintaining strict isolation.
Core Storage Functions
In internal/storage/dolt/wisps.go (lines 63‑68), the storage layer exposes dedicated methods that wrap the generic issue search with wisp-specific routing.
func (s *DoltStore) ListWisps(ctx context.Context, filter types.WispFilter) ([]*types.Issue, error) {
issueFilter := issueops.WispFilterToIssueFilter(filter)
return s.searchWisps(ctx, "", issueFilter)
}
Filter Conversion and Routing
The guarantee that all Wisp queries hit the correct table lives in internal/storage/issueops/wisp_filter_convert.go (lines 5‑25). This helper force-injects Ephemeral=true into every filter, ensuring that even if a caller forgets to set the flag, the query is automatically routed to the wisps table rather than the permanent issues table.
// The returned filter always has Ephemeral=true so queries are routed to the wisps table.
Additional instrumentation appears in internal/telemetry/storage.go (lines 441‑447), which adds tracing around ListWisps calls for observability.
Working with Wisps
Wisps support both CLI interaction for manual debugging and programmatic access for automation.
CLI Commands
Create a new Wisp from a formula file for ad-hoc tasks.
bd wisp create quick-check --var target=auth-module
List active (non-closed) Wisps in human-readable or JSON format.
bd wisp list
bd wisp list --json
Delete a specific Wisp manually when cleanup is required immediately.
bd wisp delete bd-wisp-abc123
Programmatic Access
Use the Go API to query Wisps with type and status filters. The following example demonstrates retrieving active task-type Wisps using the storage interface.
package main
import (
"context"
"log"
"github.com/gastownhall/beads/internal/types"
"github.com/gastownhall/beads/internal/storage"
)
func main() {
ctx := context.Background()
var store storage.Storage // Assume initialized DoltStore
// Retrieve all active wisps (default filter excludes closed)
wisps, err := store.ListWisps(ctx, types.WispFilter{})
if err != nil {
log.Fatalf("ListWisps failed: %v", err)
}
for _, w := range wisps {
log.Printf("Wisp %s – %s (type=%s)", w.ID, w.Title, w.WispType)
}
}
Filter by specific criteria using the WispFilter struct fields.
filter := types.WispFilter{
Type: ptrIssueType(types.TypeTask),
Status: ptrStatus(types.StatusInProgress),
IncludeClosed: false,
}
wisps, _ := store.ListWisps(ctx, filter)
Summary
- A Wisp is an ephemeral issue marked with
Ephemeral=true, stored locally in.beads-wisp/and never synced to remotes. - TTL policies vary by
WispType, ranging from 6 hours for heartbeats to 7 days for escalations. - Storage isolation is enforced by
internal/storage/issueops/wisp_filter_convert.go, which guarantees all queries route to the wisps table. - APIs include
ListWisps,GetWisp, andDeleteWispininternal/storage/dolt/wisps.go, available via CLI and Go SDK.
Frequently Asked Questions
How is a Wisp different from a regular Beads issue?
A Wisp is technically a regular issue with the Ephemeral=true flag set, but it is stored in a separate table (.beads-wisp/) that is git-ignored. Unlike standard issues, Wisps have automatic TTL-based expiration and are hidden by default when querying closed items unless the --all flag is used.
Can Wisps be synced to a remote repository?
No. By design, the .beads-wisp/ directory is git-ignored, meaning Wisps remain strictly local to the machine where they are created. This ensures temporary workflows and agent coordination do not pollute the shared repository history or trigger unnecessary CI runs.
How do I clean up old Wisps manually?
Use the CLI command bd wisp delete <wisp-id> for immediate removal of specific Wisps. While the system automatically compacts expired Wisps based on their WispType TTL, manual deletion is useful when you need to free space or clear sensitive temporary data before the TTL expires.
What happens when a Wisp reaches its TTL?
Once a Wisp exceeds its Time-To-Live based on its WispType classification (6 hours, 24 hours, or 7 days), the system automatically expires and cleans it up during compaction cycles. Closed Wisps are also hidden from default list views, though they persist in the local database until the TTL-driven garbage collection removes them.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →