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
65 changes: 65 additions & 0 deletions .trajectories/completed/2026-05/traj_zqwco4gl76g3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"id": "traj_zqwco4gl76g3",
"version": 1,
"task": {
"title": "Fix issue 878"
},
"status": "completed",
"startedAt": "2026-05-19T04:18:25.024Z",
"completedAt": "2026-05-19T04:27:18.903Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-05-19T04:22:25.398Z"
}
],
"chapters": [
{
"id": "chap_5yldelefew3p",
"title": "Work",
"agentName": "default",
"startedAt": "2026-05-19T04:22:25.398Z",
"endedAt": "2026-05-19T04:27:18.903Z",
"events": [
{
"ts": 1779164545398,
"type": "decision",
"content": "Route broker binary through library entry point: Route broker binary through library entry point",
"raw": {
"question": "Route broker binary through library entry point",
"chosen": "Route broker binary through library entry point",
"alternatives": [],
"reasoning": "Moving main.rs to call relay_broker::run_cli lets implementation modules live in the library crate and become pub(crate) instead of public Rust API while preserving the binary behavior."
},
"significance": "high"
},
{
"ts": 1779164631929,
"type": "decision",
"content": "Keep protocol and snippets as public Rust modules: Keep protocol and snippets as public Rust modules",
"raw": {
"question": "Keep protocol and snippets as public Rust modules",
"chosen": "Keep protocol and snippets as public Rust modules",
"alternatives": [],
"reasoning": "Current external Rust references only need snippets and the wire protocol remains the crate's intentional stable surface; broker runtime, relaycast plumbing, PTY, scheduling, metrics, and worker internals can be crate-private."
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Narrowed the broker Rust crate public API to protocol/snippets/run_cli, moved the binary entry through the library, colocated broker and worker tests, documented the Rust API break, and verified cargo fmt, cargo clippy -D warnings, and cargo test --release.",
"approach": "Standard approach",
"confidence": 0.9
},
"commits": [],
"filesChanged": [],
"projectId": "/Users/will/Projects/AgentWorkforce/relay",
"tags": [],
"_trace": {
"startRef": "c54e118806e98b9defe0e0b5022c35be8e64a52f",
"endRef": "c54e118806e98b9defe0e0b5022c35be8e64a52f"
}
}
39 changes: 39 additions & 0 deletions .trajectories/completed/2026-05/traj_zqwco4gl76g3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Trajectory: Fix issue 878

> **Status:** ✅ Completed
> **Confidence:** 90%
> **Started:** May 19, 2026 at 12:18 AM
> **Completed:** May 19, 2026 at 12:27 AM

---

## Summary

Narrowed the broker Rust crate public API to protocol/snippets/run_cli, moved the binary entry through the library, colocated broker and worker tests, documented the Rust API break, and verified cargo fmt, cargo clippy -D warnings, and cargo test --release.

**Approach:** Standard approach

---

## Key Decisions

### Route broker binary through library entry point

- **Chose:** Route broker binary through library entry point
- **Reasoning:** Moving main.rs to call relay_broker::run_cli lets implementation modules live in the library crate and become pub(crate) instead of public Rust API while preserving the binary behavior.

### Keep protocol and snippets as public Rust modules

- **Chose:** Keep protocol and snippets as public Rust modules
- **Reasoning:** Current external Rust references only need snippets and the wire protocol remains the crate's intentional stable surface; broker runtime, relaycast plumbing, PTY, scheduling, metrics, and worker internals can be crate-private.

---

## Chapters

### 1. Work

_Agent: default_

- Route broker binary through library entry point: Route broker binary through library entry point
- Keep protocol and snippets as public Rust modules: Keep protocol and snippets as public Rust modules
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-05-19T03:54:07.051Z",
"lastUpdated": "2026-05-19T04:27:19.071Z",
"trajectories": {
"traj_05xg7j388bc4": {
"title": "Add browser workflow step integration",
Expand Down Expand Up @@ -995,6 +995,13 @@
"startedAt": "2026-05-19T03:40:40.798Z",
"completedAt": "2026-05-19T03:54:06.889Z",
"path": "/Users/will/Projects/AgentWorkforce/relay/.trajectories/completed/2026-05/traj_5qbla7w4kzoi.json"
},
"traj_zqwco4gl76g3": {
"title": "Fix issue 878",
"status": "completed",
"startedAt": "2026-05-19T04:18:25.024Z",
"completedAt": "2026-05-19T04:27:18.903Z",
"path": "/Users/will/Projects/AgentWorkforce/relay/.trajectories/completed/2026-05/traj_zqwco4gl76g3.json"
}
}
}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `relay.spawn({ task })` now returns `success: false` and terminates the agent when task delivery fails after retries.
- `agent-relay send` now uses the orchestrator identity by default so `agent-relay replies <worker>` can correlate worker DMs.
- The `relay_broker` Rust crate now exposes only `protocol`, `snippets`, and `run_cli`; broker implementation modules are crate-private.

### Migration Guidance

Expand Down Expand Up @@ -69,51 +70,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [6.2.2] - 2026-05-18

### Technical Perspective

#### Architecture & API Changes

- Share interactive-attach prep helpers via attach.ts
- Split runDriveSession to drop below complexity 15 (#897)

#### Dependencies & Tooling

- Align trajectory title with retrospective scope
- Sanitize absolute paths in metadata (#899)

#### Releases

- v6.2.2

---

## [6.2.1] - 2026-05-18

### Technical Perspective

#### Releases

- v6.2.1

---

## [6.2.0] - 2026-05-18

### Product Perspective

#### User-Facing Features & Improvements

- **`new` / `relay` / `run` / `rm` verbs + `-n` silent alias (#864 sub-4)** (#864)
- **`agent-relay drive <name>` interactive take-over client (#864 sub-3)** (#864)
- **Per-agent session mode + pending-queue routes (#864 sub-2)** (#864)
- **`agent-relay view <name>` read-only PTY stream client (#864 sub-1)** (#864)

#### User-Impacting Fixes

- Defer spawn-and-attach import until --attach is set
- Surface drainer write failures from pty write_all
- Resolve #800 — broker: composable wait-conditions for CLI readiness (steal from ht) (#800)
- Resolve #802 — broker: add VT grid via alacritty_terminal (steal from ht, don't use libghostty) (#802)
- Resolve #802 — broker: add VT grid via alacritty_terminal (steal from ht, don't use libghostty) (#802)

### Technical Perspective

#### Architecture & API Changes

- Rename session-mode `relay` → `passthrough` across all surfaces
- `new` takes positional NAME (drop `-n` flag) + scrub PR refs (#864)
- Drop `run` verb, fold spawn-and-attach into `new --attach` (#889)
- Unify worker request/response correlation (#871) (#871)

#### Performance & Reliability

- Assert X-API-Key on every broker request
- Actually assert on the API-key header in the harness
- Cover drainer flush failure ack propagation
Expand All @@ -122,6 +135,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add stale preview environment cleanup

#### Dependencies & Tooling

- Drop PR references and legacy framing from code comments (#864)
- Record `run` -> `new --attach` refactor decision
- Record decisions for sub-PR 4 (#864) (#864)
Expand All @@ -130,6 +144,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Inject PostHog key at build time (P0.5 of #881) (#881)

#### Releases

- v6.2.0

---
Expand Down
87 changes: 85 additions & 2 deletions crates/broker/src/broker.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::{collections::HashMap, io::Write, path::Path};

use anyhow::{Context, Result};
use relay_broker::{
use crate::{
protocol::{AgentRuntime, AgentSpec},
supervisor::RestartPolicy,
};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

pub(crate) mod continuity;
Expand Down Expand Up @@ -108,3 +108,86 @@ impl BrokerState {
dead
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::AgentRuntime;

#[test]
fn broker_state_default_is_empty() {
let state = BrokerState::default();
assert!(state.agents.is_empty());
}

#[test]
fn broker_state_save_and_load_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("state.json");
let mut state = BrokerState::default();
state.agents.insert(
"w1".into(),
PersistedAgent {
runtime: AgentRuntime::Pty,
parent: None,
channels: vec![],
pid: Some(1),
started_at: None,
spec: None,
restart_policy: None,
initial_task: None,
},
);
state.save(&path).unwrap();
let loaded = BrokerState::load(&path).unwrap();
assert_eq!(loaded.agents.len(), 1);
assert!(loaded.agents.contains_key("w1"));
}

#[test]
fn broker_state_load_missing_file_errors() {
let result = BrokerState::load(Path::new("/nonexistent/state.json"));
assert!(result.is_err());
}

#[test]
fn reap_dead_agents_removes_stale_no_pid() {
let mut state = BrokerState::default();
state.agents.insert(
"ghost".into(),
PersistedAgent {
runtime: AgentRuntime::Pty,
parent: None,
channels: vec![],
pid: None,
started_at: None,
spec: None,
restart_policy: None,
initial_task: None,
},
);
let reaped = state.reap_dead_agents();
assert_eq!(reaped, vec!["ghost"]);
assert!(state.agents.is_empty());
}

#[test]
fn reap_dead_agents_keeps_live_processes() {
let mut state = BrokerState::default();
state.agents.insert(
"alive".into(),
PersistedAgent {
runtime: AgentRuntime::Pty,
parent: None,
channels: vec![],
pid: Some(std::process::id()),
started_at: None,
spec: None,
restart_policy: None,
initial_task: None,
},
);
assert!(state.reap_dead_agents().is_empty());
assert_eq!(state.agents.len(), 1);
}
}
Loading
Loading