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-09/traj_0d21k8ac1mdg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "traj_0d21k8ac1mdg",
"version": 1,
"task": {
"title": "Repair SDK/mount cache and retry changes from branch check failures"
},
"status": "completed",
"startedAt": "2026-09-27T20:02:30.999Z",
"completedAt": "2026-09-27T20:11:31.271Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-09-27T20:08:43.980Z"
}
],
"chapters": [
{
"id": "chap_tntqc5ip89bb",
"title": "Work",
"agentName": "default",
"startedAt": "2026-09-27T20:08:43.980Z",
"endedAt": "2026-09-27T20:11:31.271Z",
"events": [
{
"ts": 1790539723981,
"type": "decision",
"content": "Poll for foreground mount state after exit 0 until readiness timeout: Poll for foreground mount state after exit 0 until readiness timeout",
"raw": {
"question": "Poll for foreground mount state after exit 0 until readiness timeout",
"chosen": "Poll for foreground mount state after exit 0 until readiness timeout",
"alternatives": [],
"reasoning": "The child can exit immediately after publishing state; one overlapping read can observe an absent/partial file and previously caused a false early-exit failure."
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Provisioned CI toolchains locally, fixed foreground mount readiness publication race, and passed the full repository check.",
"approach": "Standard approach",
"confidence": 0.95
},
"commits": [],
"filesChanged": [],
"projectId": "AgentWorkforce/relayfile",
"tags": [],
"_trace": {
"startRef": "cafa926c6f07be6dfcf4a04fadd026db93902b84",
"endRef": "cafa926c6f07be6dfcf4a04fadd026db93902b84"
}
}
31 changes: 31 additions & 0 deletions .trajectories/completed/2026-09/traj_0d21k8ac1mdg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Trajectory: Repair SDK/mount cache and retry changes from branch check failures

> **Status:** ✅ Completed
> **Confidence:** 95%
> **Started:** September 27, 2026 at 08:02 PM
> **Completed:** September 27, 2026 at 08:11 PM

---

## Summary

Provisioned CI toolchains locally, fixed foreground mount readiness publication race, and passed the full repository check.

**Approach:** Standard approach

---

## Key Decisions

### Poll for foreground mount state after exit 0 until readiness timeout
- **Chose:** Poll for foreground mount state after exit 0 until readiness timeout
- **Reasoning:** The child can exit immediately after publishing state; one overlapping read can observe an absent/partial file and previously caused a false early-exit failure.

---

## Chapters

### 1. Work
*Agent: default*

- Poll for foreground mount state after exit 0 until readiness timeout: Poll for foreground mount state after exit 0 until readiness timeout
65 changes: 65 additions & 0 deletions .trajectories/completed/2026-09/traj_6qsrb702mx1s.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"id": "traj_6qsrb702mx1s",
"version": 1,
"task": {
"title": "Implement content-hash client cache and jittered Retry-After handling"
},
"status": "completed",
"startedAt": "2026-09-27T19:55:27.193Z",
"completedAt": "2026-09-27T20:01:45.765Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-09-27T19:55:27.421Z"
}
],
"chapters": [
{
"id": "chap_yvjv91bjz5w9",
"title": "Work",
"agentName": "default",
"startedAt": "2026-09-27T19:55:27.421Z",
"endedAt": "2026-09-27T20:01:45.765Z",
"events": [
{
"ts": 1790538927421,
"type": "decision",
"content": "Use content hashes rather than the existing revision ETag for conditional cache identity: Use content hashes rather than the existing revision ETag for conditional cache identity",
"raw": {
"question": "Use content hashes rather than the existing revision ETag for conditional cache identity",
"chosen": "Use content hashes rather than the existing revision ETag for conditional cache identity",
"alternatives": [],
"reasoning": "Current ETag semantics are revision-based while contentHash is already in response bodies and tree entries."
},
"significance": "high"
},
{
"ts": 1790538927631,
"type": "decision",
"content": "Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops: Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops",
"raw": {
"question": "Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops",
"chosen": "Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops",
"alternatives": [],
"reasoning": "FUSE reproduces the shipped reconnect-storm path; setup retries, agents reconnect, outbox writes, and polite polling are outside the read fan-in failure mode."
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Added SDK content-hash caches, persistent mount object reuse, polite full-jitter retries, bounded bootstrap fan-out, and cursor-safe listen reconnects",
"approach": "Standard approach",
"confidence": 0.85
},
"commits": [],
"filesChanged": [],
"projectId": "AgentWorkforce/relayfile",
"tags": [],
"_trace": {
"startRef": "2d32cfddbfc06e9e371462a50bebd0373e7343a6",
"endRef": "2d32cfddbfc06e9e371462a50bebd0373e7343a6"
}
}
36 changes: 36 additions & 0 deletions .trajectories/completed/2026-09/traj_6qsrb702mx1s.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Trajectory: Implement content-hash client cache and jittered Retry-After handling

> **Status:** ✅ Completed
> **Confidence:** 85%
> **Started:** September 27, 2026 at 07:55 PM
> **Completed:** September 27, 2026 at 08:01 PM

---

## Summary

Added SDK content-hash caches, persistent mount object reuse, polite full-jitter retries, bounded bootstrap fan-out, and cursor-safe listen reconnects

**Approach:** Standard approach

---

## Key Decisions

### Use content hashes rather than the existing revision ETag for conditional cache identity
- **Chose:** Use content hashes rather than the existing revision ETag for conditional cache identity
- **Reasoning:** Current ETag semantics are revision-based while contentHash is already in response bodies and tree entries.

### Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops
- **Chose:** Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops
- **Reasoning:** FUSE reproduces the shipped reconnect-storm path; setup retries, agents reconnect, outbox writes, and polite polling are outside the read fan-in failure mode.

---

## Chapters

### 1. Work
*Agent: default*

- Use content hashes rather than the existing revision ETag for conditional cache identity: Use content hashes rather than the existing revision ETag for conditional cache identity
- Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops: Include the FUSE websocket invalidator but defer unrelated write and control-plane retry loops
16 changes: 15 additions & 1 deletion .trajectories/index.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"lastUpdated": "2026-09-18T14:07:19.400Z",
"lastUpdated": "2026-09-27T20:11:31.329Z",
"trajectories": {
"traj_4pvrlmqfnzng": {
"title": "Review PR #278 in AgentWorkforce/relayfile",
Expand Down Expand Up @@ -407,6 +407,20 @@
"startedAt": "2026-09-18T13:58:18.225Z",
"completedAt": "2026-09-18T14:07:19.346Z",
"path": "/home/khaliqgant/Projects/AgentWorkforce/relayfile/.trajectories/completed/2026-09/traj_p2o6b2q8epj1.json"
},
"traj_6qsrb702mx1s": {
"title": "Implement content-hash client cache and jittered Retry-After handling",
"status": "completed",
"startedAt": "2026-09-27T19:55:27.193Z",
"completedAt": "2026-09-27T20:01:45.765Z",
"path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-09/traj_6qsrb702mx1s.json"
},
"traj_0d21k8ac1mdg": {
"title": "Repair SDK/mount cache and retry changes from branch check failures",
"status": "completed",
"startedAt": "2026-09-27T20:02:30.999Z",
"completedAt": "2026-09-27T20:11:31.271Z",
"path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-09/traj_0d21k8ac1mdg.json"
}
}
}
57 changes: 47 additions & 10 deletions cmd/relayfile-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8785,25 +8785,34 @@ func runListen(args []string, stdout io.Writer) error {
)
backoff := baseBackoff
everConnected := false
lastCursor := ""
for {
connected, err := runListenSession(rootCtx, cfg)
if connected {
cfg.dialURL = listenDialURLWithCursor(cfg.dialURL, lastCursor)
result, err := runListenSession(rootCtx, cfg)
if result.cursor != "" {
lastCursor = result.cursor
}
if result.connected {
everConnected = true
backoff = baseBackoff
}
if err == nil {
return nil
}
if !everConnected {
if !everConnected && result.status != http.StatusTooManyRequests && result.status != http.StatusServiceUnavailable {
return fmt.Errorf("connect to event stream: %w", err)
}
delay := time.Duration(mathrand.Float64() * float64(backoff))
if result.retryAfter > delay {
delay = result.retryAfter
}
if !*daemonized {
fmt.Fprintf(os.Stderr, "listen: stream error (%v); reconnecting in %s\n", err, backoff)
fmt.Fprintf(os.Stderr, "listen: stream error (%v); reconnecting in %s\n", err, delay)
}
select {
case <-rootCtx.Done():
return nil
case <-time.After(backoff):
case <-time.After(delay):
}
if backoff < maxBackoff {
backoff *= 2
Expand All @@ -8814,6 +8823,18 @@ func runListen(args []string, stdout io.Writer) error {
}
}

func listenDialURLWithCursor(rawURL, cursor string) string {
u, err := url.Parse(rawURL)
if err != nil || strings.TrimSpace(cursor) == "" {
return rawURL
}
q := u.Query()
q.Del("from")
q.Set("cursor", strings.TrimSpace(cursor))
u.RawQuery = q.Encode()
return u.String()
}

// listenSessionConfig carries the per-session inputs for runListenSession so a
// dropped connection can be re-established without recomputing filters.
type listenSessionConfig struct {
Expand All @@ -8833,15 +8854,27 @@ type listenSessionConfig struct {
// bool reports whether the websocket was successfully established, so the
// caller can tell a first-attempt dial failure (fatal) from a mid-flight
// disconnect (retryable).
func runListenSession(rootCtx context.Context, cfg listenSessionConfig) (bool, error) {
conn, _, err := websocket.Dial(rootCtx, cfg.dialURL, &websocket.DialOptions{
type listenSessionResult struct {
connected bool
cursor string
status int
retryAfter time.Duration
}

func runListenSession(rootCtx context.Context, cfg listenSessionConfig) (listenSessionResult, error) {
conn, response, err := websocket.Dial(rootCtx, cfg.dialURL, &websocket.DialOptions{
HTTPClient: cfg.httpClient,
HTTPHeader: http.Header{
"Authorization": []string{"Bearer " + cfg.token},
},
})
if err != nil {
return false, err
result := listenSessionResult{}
if response != nil {
result.status = response.StatusCode
result.retryAfter = parseRetryAfter(response.Header.Get("Retry-After"))
}
return result, err
}
defer conn.Close(websocket.StatusNormalClosure, "")

Expand All @@ -8852,6 +8885,7 @@ func runListenSession(rootCtx context.Context, cfg listenSessionConfig) (bool, e
conn.SetReadLimit(8 << 20) // 8 MiB

stdout := cfg.stdout
lastCursor := ""
for {
var raw json.RawMessage
if err := wsjson.Read(rootCtx, conn, &raw); err != nil {
Expand All @@ -8863,15 +8897,18 @@ func runListenSession(rootCtx context.Context, cfg listenSessionConfig) (bool, e
// would exit 0 and a `Restart=on-failure` supervisor unit would
// not bring the listener back.
if errors.Is(err, context.Canceled) || rootCtx.Err() != nil {
return true, nil
return listenSessionResult{connected: true, cursor: lastCursor}, nil
}
return true, fmt.Errorf("event stream error: %w", err)
return listenSessionResult{connected: true, cursor: lastCursor}, fmt.Errorf("event stream error: %w", err)
}

var evt listenEvent
if err := json.Unmarshal(raw, &evt); err != nil || evt.Type == "" || evt.Type == "pong" {
continue
}
if cursor := strings.TrimSpace(evt.EventID); cursor != "" {
lastCursor = cursor
}
if cfg.typeFilter != "" && evt.Type != cfg.typeFilter {
continue
}
Expand Down
Loading
Loading