Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .trajectories/completed/2026-04/traj_d8emfg3tc8yi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "traj_d8emfg3tc8yi",
"version": 1,
"task": {
"title": "Reduce relayfile mount polling noise"
},
"status": "completed",
"startedAt": "2026-04-17T13:28:00.972Z",
"completedAt": "2026-04-17T13:28:35.109Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-04-17T13:28:04.956Z"
}
],
"chapters": [
{
"id": "chap_disu8q4jucfi",
"title": "Work",
"agentName": "default",
"startedAt": "2026-04-17T13:28:04.956Z",
"endedAt": "2026-04-17T13:28:35.109Z",
"events": [
{
"ts": 1776432484957,
"type": "decision",
"content": "Use file watching for CLI mount local writes: Use file watching for CLI mount local writes",
"raw": {
"question": "Use file watching for CLI mount local writes",
"chosen": "Use file watching for CLI mount local writes",
"alternatives": [],
"reasoning": "The CLI mount entrypoint was polling and scanning the local mirror every two seconds even though mountsync already has an fsnotify watcher. Wiring the watcher lets websocket mounts skip no-op cycles while still pushing local edits immediately."
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Reduced mount-loop noise and local scan overhead by wiring fsnotify into the CLI mount command and skipping websocket no-op cycles in both mount entrypoints.",
"approach": "Standard approach",
"confidence": 0.92
},
"commits": [],
"filesChanged": [],
"projectId": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile",
"tags": [],
"_trace": {
"startRef": "df342b6d7c423b09a088577b6e4142a550d82062",
"endRef": "df342b6d7c423b09a088577b6e4142a550d82062"
}
}
31 changes: 31 additions & 0 deletions .trajectories/completed/2026-04/traj_d8emfg3tc8yi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Trajectory: Reduce relayfile mount polling noise

> **Status:** ✅ Completed
> **Confidence:** 92%
> **Started:** April 17, 2026 at 03:28 PM
> **Completed:** April 17, 2026 at 03:28 PM
---

## Summary

Reduced mount-loop noise and local scan overhead by wiring fsnotify into the CLI mount command and skipping websocket no-op cycles in both mount entrypoints.

**Approach:** Standard approach

---

## Key Decisions

### Use file watching for CLI mount local writes
- **Chose:** Use file watching for CLI mount local writes
- **Reasoning:** The CLI mount entrypoint was polling and scanning the local mirror every two seconds even though mountsync already has an fsnotify watcher. Wiring the watcher lets websocket mounts skip no-op cycles while still pushing local edits immediately.

---

## Chapters

### 1. Work
*Agent: default*

- Use file watching for CLI mount local writes: Use file watching for CLI mount local writes
9 changes: 8 additions & 1 deletion .trajectories/index.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"lastUpdated": "2026-04-17T13:12:15.026Z",
"lastUpdated": "2026-04-17T13:28:35.212Z",
"trajectories": {
"traj_lcvvpzmuroqb": {
"title": "file-observer-dashboard-workflow",
Expand Down Expand Up @@ -57,6 +57,13 @@
"startedAt": "2026-04-17T13:06:18.420Z",
"completedAt": "2026-04-17T13:12:14.864Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile/.trajectories/completed/2026-04/traj_nv8vh0eav3v0.json"
},
"traj_d8emfg3tc8yi": {
"title": "Reduce relayfile mount polling noise",
"status": "completed",
"startedAt": "2026-04-17T13:28:00.972Z",
"completedAt": "2026-04-17T13:28:35.109Z",
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayfile/.trajectories/completed/2026-04/traj_d8emfg3tc8yi.json"
}
}
}
25 changes: 22 additions & 3 deletions cmd/relayfile-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"unicode/utf8"

"github.com/agentworkforce/relayfile/internal/mountsync"
"github.com/fsnotify/fsnotify"
)

const (
Expand Down Expand Up @@ -525,7 +526,7 @@ func runMount(args []string) error {
return err
}

return runMountLoop(rootCtx, syncer, *timeout, *interval, *intervalJitter, *websocketEnabled, *once)
return runMountLoop(rootCtx, syncer, absLocalDir, *timeout, *interval, *intervalJitter, *websocketEnabled, *once)
}

func runTree(args []string, stdout io.Writer) error {
Expand Down Expand Up @@ -1598,7 +1599,7 @@ func jitteredIntervalWithSample(base time.Duration, jitterRatio, sample float64)
return delay
}

func runMountLoop(rootCtx context.Context, syncer *mountsync.Syncer, timeout, interval time.Duration, intervalJitter float64, websocketEnabled, once bool) error {
func runMountLoop(rootCtx context.Context, syncer *mountsync.Syncer, localDir string, timeout, interval time.Duration, intervalJitter float64, websocketEnabled, once bool) error {
runCycle := func(reconcile bool) error {
ctx, cancel := context.WithTimeout(rootCtx, timeout)
defer cancel()
Expand All @@ -1621,6 +1622,22 @@ func runMountLoop(rootCtx context.Context, syncer *mountsync.Syncer, timeout, in
return initialErr
}

watcher, err := mountsync.NewFileWatcher(localDir, func(relativePath string, op fsnotify.Op) {
ctx, cancel := context.WithTimeout(rootCtx, timeout)
defer cancel()
if err := syncer.HandleLocalChange(ctx, relativePath, op); err != nil {
log.Printf("mount local change failed: %v", err)
}
})
if err != nil {
return fmt.Errorf("create file watcher: %w", err)
}
if err := watcher.Start(rootCtx); err != nil {
_ = watcher.Close()
return fmt.Errorf("start file watcher: %w", err)
}
defer watcher.Close()

timer := time.NewTimer(jitteredIntervalWithSample(interval, intervalJitter, mathrand.Float64()))
defer timer.Stop()
cycle := 0
Expand All @@ -1632,7 +1649,9 @@ func runMountLoop(rootCtx context.Context, syncer *mountsync.Syncer, timeout, in
case <-timer.C:
cycle++
reconcile := !websocketEnabled || cycle%websocketReconcileEvery == 0
_ = runCycle(reconcile)
if reconcile {
_ = runCycle(true)
Comment on lines 1651 to +1653
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep polling when websocket transport is disconnected

When --websocket is enabled, this branch skips nine out of ten timer ticks entirely, so no sync runs on those ticks. That regresses fallback behavior: if websocket setup fails or later disconnects, SyncOnce is no longer called each interval to poll (sync only polls when wsConn == nil), so local uploads and remote pulls can be delayed by up to websocketReconcileEvery*interval instead of the configured interval. This same pattern is also present in cmd/relayfile-mount/main.go, so both entrypoints throttle polling even when websocket is unavailable.

Useful? React with 👍 / 👎.

}
timer.Reset(jitteredIntervalWithSample(interval, intervalJitter, mathrand.Float64()))
}
}
Expand Down
12 changes: 9 additions & 3 deletions cmd/relayfile-mount/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (
)

const (
mountModePoll = "poll"
mountModeFuse = "fuse"
mountModePoll = "poll"
mountModeFuse = "fuse"
websocketReconcileEvery = 10
)

var errFuseModeUnavailable = errors.New("fuse mode is not available in this build")
Expand Down Expand Up @@ -201,13 +202,18 @@ func runPollingMount(rootCtx context.Context, cfg mountConfig) error {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
timer := time.NewTimer(jitteredIntervalWithSample(cfg.interval, cfg.intervalJitter, rng.Float64()))
defer timer.Stop()
cycle := 0
for {
select {
case <-rootCtx.Done():
log.Printf("mount sync stopping: %v", rootCtx.Err())
return nil
case <-timer.C:
run(true)
cycle++
reconcile := !cfg.websocketEnabled || cycle%websocketReconcileEvery == 0
if reconcile {
run(true)
}
timer.Reset(jitteredIntervalWithSample(cfg.interval, cfg.intervalJitter, rng.Float64()))
}
}
Expand Down
Loading