diff --git a/.trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.json b/.agentworkforce/trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.json similarity index 100% rename from .trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.json rename to .agentworkforce/trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.json diff --git a/.trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.md b/.agentworkforce/trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.md similarity index 100% rename from .trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.md rename to .agentworkforce/trajectories/compacted/compact_gqmrna24cmm4_2026-05-19.md diff --git a/.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json b/.agentworkforce/trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json similarity index 100% rename from .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json rename to .agentworkforce/trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json diff --git a/.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md b/.agentworkforce/trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md similarity index 100% rename from .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md rename to .agentworkforce/trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md diff --git a/.trajectories/compacted/release-6.0.13.json b/.agentworkforce/trajectories/compacted/release-6.0.13.json similarity index 100% rename from .trajectories/compacted/release-6.0.13.json rename to .agentworkforce/trajectories/compacted/release-6.0.13.json diff --git a/.trajectories/compacted/release-6.0.13.md b/.agentworkforce/trajectories/compacted/release-6.0.13.md similarity index 100% rename from .trajectories/compacted/release-6.0.13.md rename to .agentworkforce/trajectories/compacted/release-6.0.13.md diff --git a/.trajectories/compacted/release-6.2.3.json b/.agentworkforce/trajectories/compacted/release-6.2.3.json similarity index 100% rename from .trajectories/compacted/release-6.2.3.json rename to .agentworkforce/trajectories/compacted/release-6.2.3.json diff --git a/.trajectories/compacted/release-6.2.3.md b/.agentworkforce/trajectories/compacted/release-6.2.3.md similarity index 100% rename from .trajectories/compacted/release-6.2.3.md rename to .agentworkforce/trajectories/compacted/release-6.2.3.md diff --git a/.trajectories/completed/2026-04/traj_05xg7j388bc4.json b/.agentworkforce/trajectories/completed/2026-04/traj_05xg7j388bc4.json similarity index 100% rename from .trajectories/completed/2026-04/traj_05xg7j388bc4.json rename to .agentworkforce/trajectories/completed/2026-04/traj_05xg7j388bc4.json diff --git a/.trajectories/completed/2026-04/traj_05xg7j388bc4.md b/.agentworkforce/trajectories/completed/2026-04/traj_05xg7j388bc4.md similarity index 100% rename from .trajectories/completed/2026-04/traj_05xg7j388bc4.md rename to .agentworkforce/trajectories/completed/2026-04/traj_05xg7j388bc4.md diff --git a/.trajectories/completed/2026-04/traj_0t92gxaz6igh.json b/.agentworkforce/trajectories/completed/2026-04/traj_0t92gxaz6igh.json similarity index 100% rename from .trajectories/completed/2026-04/traj_0t92gxaz6igh.json rename to .agentworkforce/trajectories/completed/2026-04/traj_0t92gxaz6igh.json diff --git a/.trajectories/completed/2026-04/traj_0t92gxaz6igh.md b/.agentworkforce/trajectories/completed/2026-04/traj_0t92gxaz6igh.md similarity index 100% rename from .trajectories/completed/2026-04/traj_0t92gxaz6igh.md rename to .agentworkforce/trajectories/completed/2026-04/traj_0t92gxaz6igh.md diff --git a/.trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.json b/.agentworkforce/trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.json similarity index 100% rename from .trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.json rename to .agentworkforce/trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.json diff --git a/.trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.md b/.agentworkforce/trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.md similarity index 100% rename from .trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.md rename to .agentworkforce/trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.md diff --git a/.trajectories/completed/2026-04/traj_1776105988184_29f1270c.json b/.agentworkforce/trajectories/completed/2026-04/traj_1776105988184_29f1270c.json similarity index 100% rename from .trajectories/completed/2026-04/traj_1776105988184_29f1270c.json rename to .agentworkforce/trajectories/completed/2026-04/traj_1776105988184_29f1270c.json diff --git a/.trajectories/completed/2026-04/traj_1776105988184_29f1270c.md b/.agentworkforce/trajectories/completed/2026-04/traj_1776105988184_29f1270c.md similarity index 100% rename from .trajectories/completed/2026-04/traj_1776105988184_29f1270c.md rename to .agentworkforce/trajectories/completed/2026-04/traj_1776105988184_29f1270c.md diff --git a/.trajectories/completed/2026-04/traj_222ha5671idc.json b/.agentworkforce/trajectories/completed/2026-04/traj_222ha5671idc.json similarity index 100% rename from .trajectories/completed/2026-04/traj_222ha5671idc.json rename to .agentworkforce/trajectories/completed/2026-04/traj_222ha5671idc.json diff --git a/.trajectories/completed/2026-04/traj_222ha5671idc.md b/.agentworkforce/trajectories/completed/2026-04/traj_222ha5671idc.md similarity index 100% rename from .trajectories/completed/2026-04/traj_222ha5671idc.md rename to .agentworkforce/trajectories/completed/2026-04/traj_222ha5671idc.md diff --git a/.trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json b/.agentworkforce/trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json similarity index 100% rename from .trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json rename to .agentworkforce/trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json diff --git a/.trajectories/completed/2026-04/traj_3b3p1z4y7qlo.md b/.agentworkforce/trajectories/completed/2026-04/traj_3b3p1z4y7qlo.md similarity index 100% rename from .trajectories/completed/2026-04/traj_3b3p1z4y7qlo.md rename to .agentworkforce/trajectories/completed/2026-04/traj_3b3p1z4y7qlo.md diff --git a/.trajectories/completed/2026-04/traj_4zqhfqw7g28l.json b/.agentworkforce/trajectories/completed/2026-04/traj_4zqhfqw7g28l.json similarity index 100% rename from .trajectories/completed/2026-04/traj_4zqhfqw7g28l.json rename to .agentworkforce/trajectories/completed/2026-04/traj_4zqhfqw7g28l.json diff --git a/.trajectories/completed/2026-04/traj_4zqhfqw7g28l.md b/.agentworkforce/trajectories/completed/2026-04/traj_4zqhfqw7g28l.md similarity index 100% rename from .trajectories/completed/2026-04/traj_4zqhfqw7g28l.md rename to .agentworkforce/trajectories/completed/2026-04/traj_4zqhfqw7g28l.md diff --git a/.trajectories/completed/2026-04/traj_530xmbfeljyb.json b/.agentworkforce/trajectories/completed/2026-04/traj_530xmbfeljyb.json similarity index 100% rename from .trajectories/completed/2026-04/traj_530xmbfeljyb.json rename to .agentworkforce/trajectories/completed/2026-04/traj_530xmbfeljyb.json diff --git a/.trajectories/completed/2026-04/traj_530xmbfeljyb.md b/.agentworkforce/trajectories/completed/2026-04/traj_530xmbfeljyb.md similarity index 100% rename from .trajectories/completed/2026-04/traj_530xmbfeljyb.md rename to .agentworkforce/trajectories/completed/2026-04/traj_530xmbfeljyb.md diff --git a/.trajectories/completed/2026-04/traj_703m7sqyq89t.json b/.agentworkforce/trajectories/completed/2026-04/traj_703m7sqyq89t.json similarity index 100% rename from .trajectories/completed/2026-04/traj_703m7sqyq89t.json rename to .agentworkforce/trajectories/completed/2026-04/traj_703m7sqyq89t.json diff --git a/.trajectories/completed/2026-04/traj_703m7sqyq89t.md b/.agentworkforce/trajectories/completed/2026-04/traj_703m7sqyq89t.md similarity index 100% rename from .trajectories/completed/2026-04/traj_703m7sqyq89t.md rename to .agentworkforce/trajectories/completed/2026-04/traj_703m7sqyq89t.md diff --git a/.trajectories/completed/2026-04/traj_8oh4r5km5eic.json b/.agentworkforce/trajectories/completed/2026-04/traj_8oh4r5km5eic.json similarity index 100% rename from .trajectories/completed/2026-04/traj_8oh4r5km5eic.json rename to .agentworkforce/trajectories/completed/2026-04/traj_8oh4r5km5eic.json diff --git a/.trajectories/completed/2026-04/traj_8oh4r5km5eic.md b/.agentworkforce/trajectories/completed/2026-04/traj_8oh4r5km5eic.md similarity index 100% rename from .trajectories/completed/2026-04/traj_8oh4r5km5eic.md rename to .agentworkforce/trajectories/completed/2026-04/traj_8oh4r5km5eic.md diff --git a/.trajectories/completed/2026-04/traj_9tt55is74dq5.json b/.agentworkforce/trajectories/completed/2026-04/traj_9tt55is74dq5.json similarity index 100% rename from .trajectories/completed/2026-04/traj_9tt55is74dq5.json rename to .agentworkforce/trajectories/completed/2026-04/traj_9tt55is74dq5.json diff --git a/.trajectories/completed/2026-04/traj_9tt55is74dq5.md b/.agentworkforce/trajectories/completed/2026-04/traj_9tt55is74dq5.md similarity index 100% rename from .trajectories/completed/2026-04/traj_9tt55is74dq5.md rename to .agentworkforce/trajectories/completed/2026-04/traj_9tt55is74dq5.md diff --git a/.trajectories/completed/2026-04/traj_abjovknvcijv.json b/.agentworkforce/trajectories/completed/2026-04/traj_abjovknvcijv.json similarity index 100% rename from .trajectories/completed/2026-04/traj_abjovknvcijv.json rename to .agentworkforce/trajectories/completed/2026-04/traj_abjovknvcijv.json diff --git a/.trajectories/completed/2026-04/traj_abjovknvcijv.md b/.agentworkforce/trajectories/completed/2026-04/traj_abjovknvcijv.md similarity index 100% rename from .trajectories/completed/2026-04/traj_abjovknvcijv.md rename to .agentworkforce/trajectories/completed/2026-04/traj_abjovknvcijv.md diff --git a/.trajectories/completed/2026-04/traj_avmkyoo2s3rt.json b/.agentworkforce/trajectories/completed/2026-04/traj_avmkyoo2s3rt.json similarity index 100% rename from .trajectories/completed/2026-04/traj_avmkyoo2s3rt.json rename to .agentworkforce/trajectories/completed/2026-04/traj_avmkyoo2s3rt.json diff --git a/.trajectories/completed/2026-04/traj_avmkyoo2s3rt.md b/.agentworkforce/trajectories/completed/2026-04/traj_avmkyoo2s3rt.md similarity index 100% rename from .trajectories/completed/2026-04/traj_avmkyoo2s3rt.md rename to .agentworkforce/trajectories/completed/2026-04/traj_avmkyoo2s3rt.md diff --git a/.trajectories/completed/2026-04/traj_d48czxmgx4ac.json b/.agentworkforce/trajectories/completed/2026-04/traj_d48czxmgx4ac.json similarity index 100% rename from .trajectories/completed/2026-04/traj_d48czxmgx4ac.json rename to .agentworkforce/trajectories/completed/2026-04/traj_d48czxmgx4ac.json diff --git a/.trajectories/completed/2026-04/traj_d48czxmgx4ac.md b/.agentworkforce/trajectories/completed/2026-04/traj_d48czxmgx4ac.md similarity index 100% rename from .trajectories/completed/2026-04/traj_d48czxmgx4ac.md rename to .agentworkforce/trajectories/completed/2026-04/traj_d48czxmgx4ac.md diff --git a/.trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json b/.agentworkforce/trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json similarity index 100% rename from .trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json rename to .agentworkforce/trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json diff --git a/.trajectories/completed/2026-04/traj_dw8ihhdb8ip7.md b/.agentworkforce/trajectories/completed/2026-04/traj_dw8ihhdb8ip7.md similarity index 100% rename from .trajectories/completed/2026-04/traj_dw8ihhdb8ip7.md rename to .agentworkforce/trajectories/completed/2026-04/traj_dw8ihhdb8ip7.md diff --git a/.trajectories/completed/2026-04/traj_e5i62wdjx0jd.json b/.agentworkforce/trajectories/completed/2026-04/traj_e5i62wdjx0jd.json similarity index 100% rename from .trajectories/completed/2026-04/traj_e5i62wdjx0jd.json rename to .agentworkforce/trajectories/completed/2026-04/traj_e5i62wdjx0jd.json diff --git a/.trajectories/completed/2026-04/traj_e5i62wdjx0jd.md b/.agentworkforce/trajectories/completed/2026-04/traj_e5i62wdjx0jd.md similarity index 100% rename from .trajectories/completed/2026-04/traj_e5i62wdjx0jd.md rename to .agentworkforce/trajectories/completed/2026-04/traj_e5i62wdjx0jd.md diff --git a/.trajectories/completed/2026-04/traj_g3muawdq6bsb.json b/.agentworkforce/trajectories/completed/2026-04/traj_g3muawdq6bsb.json similarity index 100% rename from .trajectories/completed/2026-04/traj_g3muawdq6bsb.json rename to .agentworkforce/trajectories/completed/2026-04/traj_g3muawdq6bsb.json diff --git a/.trajectories/completed/2026-04/traj_g3muawdq6bsb.md b/.agentworkforce/trajectories/completed/2026-04/traj_g3muawdq6bsb.md similarity index 100% rename from .trajectories/completed/2026-04/traj_g3muawdq6bsb.md rename to .agentworkforce/trajectories/completed/2026-04/traj_g3muawdq6bsb.md diff --git a/.trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json b/.agentworkforce/trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json similarity index 100% rename from .trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json rename to .agentworkforce/trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json diff --git a/.trajectories/completed/2026-04/traj_mk0t0cgn4ytq.md b/.agentworkforce/trajectories/completed/2026-04/traj_mk0t0cgn4ytq.md similarity index 100% rename from .trajectories/completed/2026-04/traj_mk0t0cgn4ytq.md rename to .agentworkforce/trajectories/completed/2026-04/traj_mk0t0cgn4ytq.md diff --git a/.trajectories/completed/2026-04/traj_o8kgzhfu6jth.json b/.agentworkforce/trajectories/completed/2026-04/traj_o8kgzhfu6jth.json similarity index 100% rename from .trajectories/completed/2026-04/traj_o8kgzhfu6jth.json rename to .agentworkforce/trajectories/completed/2026-04/traj_o8kgzhfu6jth.json diff --git a/.trajectories/completed/2026-04/traj_o8kgzhfu6jth.md b/.agentworkforce/trajectories/completed/2026-04/traj_o8kgzhfu6jth.md similarity index 100% rename from .trajectories/completed/2026-04/traj_o8kgzhfu6jth.md rename to .agentworkforce/trajectories/completed/2026-04/traj_o8kgzhfu6jth.md diff --git a/.trajectories/completed/2026-04/traj_qb54w47qwod6.json b/.agentworkforce/trajectories/completed/2026-04/traj_qb54w47qwod6.json similarity index 100% rename from .trajectories/completed/2026-04/traj_qb54w47qwod6.json rename to .agentworkforce/trajectories/completed/2026-04/traj_qb54w47qwod6.json diff --git a/.trajectories/completed/2026-04/traj_qb54w47qwod6.md b/.agentworkforce/trajectories/completed/2026-04/traj_qb54w47qwod6.md similarity index 100% rename from .trajectories/completed/2026-04/traj_qb54w47qwod6.md rename to .agentworkforce/trajectories/completed/2026-04/traj_qb54w47qwod6.md diff --git a/.trajectories/completed/2026-04/traj_rs2bt3x0fqba.json b/.agentworkforce/trajectories/completed/2026-04/traj_rs2bt3x0fqba.json similarity index 100% rename from .trajectories/completed/2026-04/traj_rs2bt3x0fqba.json rename to .agentworkforce/trajectories/completed/2026-04/traj_rs2bt3x0fqba.json diff --git a/.trajectories/completed/2026-04/traj_rs2bt3x0fqba.md b/.agentworkforce/trajectories/completed/2026-04/traj_rs2bt3x0fqba.md similarity index 100% rename from .trajectories/completed/2026-04/traj_rs2bt3x0fqba.md rename to .agentworkforce/trajectories/completed/2026-04/traj_rs2bt3x0fqba.md diff --git a/.trajectories/completed/2026-04/traj_tjadoebpscps.json b/.agentworkforce/trajectories/completed/2026-04/traj_tjadoebpscps.json similarity index 100% rename from .trajectories/completed/2026-04/traj_tjadoebpscps.json rename to .agentworkforce/trajectories/completed/2026-04/traj_tjadoebpscps.json diff --git a/.trajectories/completed/2026-04/traj_tjadoebpscps.md b/.agentworkforce/trajectories/completed/2026-04/traj_tjadoebpscps.md similarity index 100% rename from .trajectories/completed/2026-04/traj_tjadoebpscps.md rename to .agentworkforce/trajectories/completed/2026-04/traj_tjadoebpscps.md diff --git a/.trajectories/completed/2026-04/traj_tv1x9pamkqad.json b/.agentworkforce/trajectories/completed/2026-04/traj_tv1x9pamkqad.json similarity index 100% rename from .trajectories/completed/2026-04/traj_tv1x9pamkqad.json rename to .agentworkforce/trajectories/completed/2026-04/traj_tv1x9pamkqad.json diff --git a/.trajectories/completed/2026-04/traj_tv1x9pamkqad.md b/.agentworkforce/trajectories/completed/2026-04/traj_tv1x9pamkqad.md similarity index 100% rename from .trajectories/completed/2026-04/traj_tv1x9pamkqad.md rename to .agentworkforce/trajectories/completed/2026-04/traj_tv1x9pamkqad.md diff --git a/.trajectories/completed/2026-04/traj_ui5omrgz819d.json b/.agentworkforce/trajectories/completed/2026-04/traj_ui5omrgz819d.json similarity index 100% rename from .trajectories/completed/2026-04/traj_ui5omrgz819d.json rename to .agentworkforce/trajectories/completed/2026-04/traj_ui5omrgz819d.json diff --git a/.trajectories/completed/2026-04/traj_ui5omrgz819d.md b/.agentworkforce/trajectories/completed/2026-04/traj_ui5omrgz819d.md similarity index 100% rename from .trajectories/completed/2026-04/traj_ui5omrgz819d.md rename to .agentworkforce/trajectories/completed/2026-04/traj_ui5omrgz819d.md diff --git a/.trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json b/.agentworkforce/trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json similarity index 100% rename from .trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json rename to .agentworkforce/trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json diff --git a/.trajectories/completed/2026-04/traj_w0xpsaoxuiyw.md b/.agentworkforce/trajectories/completed/2026-04/traj_w0xpsaoxuiyw.md similarity index 100% rename from .trajectories/completed/2026-04/traj_w0xpsaoxuiyw.md rename to .agentworkforce/trajectories/completed/2026-04/traj_w0xpsaoxuiyw.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_0d1efjk6aeo2.json b/.agentworkforce/trajectories/completed/2026-05/traj_0d1efjk6aeo2.json new file mode 100644 index 000000000..5fc4b5b7d --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_0d1efjk6aeo2.json @@ -0,0 +1,53 @@ +{ + "id": "traj_0d1efjk6aeo2", + "version": 1, + "task": { + "title": "Respond to harness PR review comments" + }, + "status": "completed", + "startedAt": "2026-05-25T17:14:45.927Z", + "completedAt": "2026-05-25T17:14:53.457Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-25T17:14:49.998Z" + } + ], + "chapters": [ + { + "id": "chap_9esy8vjwbhf5", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-25T17:14:49.998Z", + "endedAt": "2026-05-25T17:14:53.457Z", + "events": [ + { + "ts": 1779729289999, + "type": "decision", + "content": "Fixed valid automated review findings: Fixed valid automated review findings", + "raw": { + "question": "Fixed valid automated review findings", + "chosen": "Fixed valid automated review findings", + "alternatives": [], + "reasoning": "Verified each PR comment against current code; patched runtime compatibility, hook ordering, app-server request boundedness, auth isolation, agent metadata updates, and docs mismatches." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Addressed PR review comments on harness runtime plans with compatibility, reliability, SDK metadata, and docs fixes.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/private/tmp/relay-harness-runtime-plans", + "tags": [], + "_trace": { + "startRef": "6b4ee74f302b2b96fb678627d27b9f4dbb3a7327", + "endRef": "6b4ee74f302b2b96fb678627d27b9f4dbb3a7327" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_0d1efjk6aeo2.md b/.agentworkforce/trajectories/completed/2026-05/traj_0d1efjk6aeo2.md new file mode 100644 index 000000000..04077f444 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_0d1efjk6aeo2.md @@ -0,0 +1,33 @@ +# Trajectory: Respond to harness PR review comments + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 25, 2026 at 01:14 PM +> **Completed:** May 25, 2026 at 01:14 PM + +--- + +## Summary + +Addressed PR review comments on harness runtime plans with compatibility, reliability, SDK metadata, and docs fixes. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Fixed valid automated review findings + +- **Chose:** Fixed valid automated review findings +- **Reasoning:** Verified each PR comment against current code; patched runtime compatibility, hook ordering, app-server request boundedness, auth isolation, agent metadata updates, and docs mismatches. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Fixed valid automated review findings: Fixed valid automated review findings diff --git a/.trajectories/completed/2026-05/traj_0e8i20oitwvz.json b/.agentworkforce/trajectories/completed/2026-05/traj_0e8i20oitwvz.json similarity index 100% rename from .trajectories/completed/2026-05/traj_0e8i20oitwvz.json rename to .agentworkforce/trajectories/completed/2026-05/traj_0e8i20oitwvz.json diff --git a/.trajectories/completed/2026-05/traj_0e8i20oitwvz.md b/.agentworkforce/trajectories/completed/2026-05/traj_0e8i20oitwvz.md similarity index 100% rename from .trajectories/completed/2026-05/traj_0e8i20oitwvz.md rename to .agentworkforce/trajectories/completed/2026-05/traj_0e8i20oitwvz.md diff --git a/.trajectories/completed/2026-05/traj_0o6gb2wvk59t.json b/.agentworkforce/trajectories/completed/2026-05/traj_0o6gb2wvk59t.json similarity index 100% rename from .trajectories/completed/2026-05/traj_0o6gb2wvk59t.json rename to .agentworkforce/trajectories/completed/2026-05/traj_0o6gb2wvk59t.json diff --git a/.trajectories/completed/2026-05/traj_0o6gb2wvk59t.md b/.agentworkforce/trajectories/completed/2026-05/traj_0o6gb2wvk59t.md similarity index 100% rename from .trajectories/completed/2026-05/traj_0o6gb2wvk59t.md rename to .agentworkforce/trajectories/completed/2026-05/traj_0o6gb2wvk59t.md diff --git a/.trajectories/completed/2026-05/traj_0z98tkaigaxg.json b/.agentworkforce/trajectories/completed/2026-05/traj_0z98tkaigaxg.json similarity index 100% rename from .trajectories/completed/2026-05/traj_0z98tkaigaxg.json rename to .agentworkforce/trajectories/completed/2026-05/traj_0z98tkaigaxg.json diff --git a/.trajectories/completed/2026-05/traj_0z98tkaigaxg.md b/.agentworkforce/trajectories/completed/2026-05/traj_0z98tkaigaxg.md similarity index 100% rename from .trajectories/completed/2026-05/traj_0z98tkaigaxg.md rename to .agentworkforce/trajectories/completed/2026-05/traj_0z98tkaigaxg.md diff --git a/.trajectories/completed/2026-05/traj_1775914133873_35667beb.json b/.agentworkforce/trajectories/completed/2026-05/traj_1775914133873_35667beb.json similarity index 100% rename from .trajectories/completed/2026-05/traj_1775914133873_35667beb.json rename to .agentworkforce/trajectories/completed/2026-05/traj_1775914133873_35667beb.json diff --git a/.trajectories/completed/2026-05/traj_1775914133873_35667beb.md b/.agentworkforce/trajectories/completed/2026-05/traj_1775914133873_35667beb.md similarity index 100% rename from .trajectories/completed/2026-05/traj_1775914133873_35667beb.md rename to .agentworkforce/trajectories/completed/2026-05/traj_1775914133873_35667beb.md diff --git a/.trajectories/completed/2026-05/traj_1776073106646_1839be2d.json b/.agentworkforce/trajectories/completed/2026-05/traj_1776073106646_1839be2d.json similarity index 100% rename from .trajectories/completed/2026-05/traj_1776073106646_1839be2d.json rename to .agentworkforce/trajectories/completed/2026-05/traj_1776073106646_1839be2d.json diff --git a/.trajectories/completed/2026-05/traj_1776073106646_1839be2d.md b/.agentworkforce/trajectories/completed/2026-05/traj_1776073106646_1839be2d.md similarity index 100% rename from .trajectories/completed/2026-05/traj_1776073106646_1839be2d.md rename to .agentworkforce/trajectories/completed/2026-05/traj_1776073106646_1839be2d.md diff --git a/.trajectories/completed/2026-05/traj_1776113772922_bc92f121.json b/.agentworkforce/trajectories/completed/2026-05/traj_1776113772922_bc92f121.json similarity index 100% rename from .trajectories/completed/2026-05/traj_1776113772922_bc92f121.json rename to .agentworkforce/trajectories/completed/2026-05/traj_1776113772922_bc92f121.json diff --git a/.trajectories/completed/2026-05/traj_1776113772922_bc92f121.md b/.agentworkforce/trajectories/completed/2026-05/traj_1776113772922_bc92f121.md similarity index 100% rename from .trajectories/completed/2026-05/traj_1776113772922_bc92f121.md rename to .agentworkforce/trajectories/completed/2026-05/traj_1776113772922_bc92f121.md diff --git a/.trajectories/completed/2026-05/traj_1778873209642_c70e32ab.json b/.agentworkforce/trajectories/completed/2026-05/traj_1778873209642_c70e32ab.json similarity index 100% rename from .trajectories/completed/2026-05/traj_1778873209642_c70e32ab.json rename to .agentworkforce/trajectories/completed/2026-05/traj_1778873209642_c70e32ab.json diff --git a/.trajectories/completed/2026-05/traj_1778873209642_c70e32ab.md b/.agentworkforce/trajectories/completed/2026-05/traj_1778873209642_c70e32ab.md similarity index 100% rename from .trajectories/completed/2026-05/traj_1778873209642_c70e32ab.md rename to .agentworkforce/trajectories/completed/2026-05/traj_1778873209642_c70e32ab.md diff --git a/.trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.json b/.agentworkforce/trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.json similarity index 100% rename from .trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.json rename to .agentworkforce/trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.json diff --git a/.trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.md b/.agentworkforce/trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.md similarity index 100% rename from .trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.md rename to .agentworkforce/trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.md diff --git a/.trajectories/completed/2026-05/traj_17t39ue8exte/summary.md b/.agentworkforce/trajectories/completed/2026-05/traj_17t39ue8exte/summary.md similarity index 100% rename from .trajectories/completed/2026-05/traj_17t39ue8exte/summary.md rename to .agentworkforce/trajectories/completed/2026-05/traj_17t39ue8exte/summary.md diff --git a/.trajectories/completed/2026-05/traj_17t39ue8exte/trajectory.json b/.agentworkforce/trajectories/completed/2026-05/traj_17t39ue8exte/trajectory.json similarity index 100% rename from .trajectories/completed/2026-05/traj_17t39ue8exte/trajectory.json rename to .agentworkforce/trajectories/completed/2026-05/traj_17t39ue8exte/trajectory.json diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_1fjub7c9rlap.json b/.agentworkforce/trajectories/completed/2026-05/traj_1fjub7c9rlap.json new file mode 100644 index 000000000..124bdd9ff --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_1fjub7c9rlap.json @@ -0,0 +1,25 @@ +{ + "id": "traj_1fjub7c9rlap", + "version": 1, + "task": { + "title": "Rewrite harness docs around SDK adapters" + }, + "status": "completed", + "startedAt": "2026-05-25T16:53:04.706Z", + "completedAt": "2026-05-25T16:53:16.077Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Reworked harness docs to be concise and example-led around SDK adapter definitions for Claude, Codex, and OpenCode.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/private/tmp/relay-harness-runtime-plans", + "tags": [], + "_trace": { + "startRef": "298ec2169dcc7827c076a37099824c62ad78d136", + "endRef": "298ec2169dcc7827c076a37099824c62ad78d136" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_1fjub7c9rlap.md b/.agentworkforce/trajectories/completed/2026-05/traj_1fjub7c9rlap.md new file mode 100644 index 000000000..157d81464 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_1fjub7c9rlap.md @@ -0,0 +1,14 @@ +# Trajectory: Rewrite harness docs around SDK adapters + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 25, 2026 at 12:53 PM +> **Completed:** May 25, 2026 at 12:53 PM + +--- + +## Summary + +Reworked harness docs to be concise and example-led around SDK adapter definitions for Claude, Codex, and OpenCode. + +**Approach:** Standard approach diff --git a/.trajectories/completed/2026-05/traj_1rrpe2r7fyem.json b/.agentworkforce/trajectories/completed/2026-05/traj_1rrpe2r7fyem.json similarity index 100% rename from .trajectories/completed/2026-05/traj_1rrpe2r7fyem.json rename to .agentworkforce/trajectories/completed/2026-05/traj_1rrpe2r7fyem.json diff --git a/.trajectories/completed/2026-05/traj_1rrpe2r7fyem.md b/.agentworkforce/trajectories/completed/2026-05/traj_1rrpe2r7fyem.md similarity index 100% rename from .trajectories/completed/2026-05/traj_1rrpe2r7fyem.md rename to .agentworkforce/trajectories/completed/2026-05/traj_1rrpe2r7fyem.md diff --git a/.trajectories/completed/2026-05/traj_2gpglosdsq7s.json b/.agentworkforce/trajectories/completed/2026-05/traj_2gpglosdsq7s.json similarity index 100% rename from .trajectories/completed/2026-05/traj_2gpglosdsq7s.json rename to .agentworkforce/trajectories/completed/2026-05/traj_2gpglosdsq7s.json diff --git a/.trajectories/completed/2026-05/traj_2gpglosdsq7s.md b/.agentworkforce/trajectories/completed/2026-05/traj_2gpglosdsq7s.md similarity index 100% rename from .trajectories/completed/2026-05/traj_2gpglosdsq7s.md rename to .agentworkforce/trajectories/completed/2026-05/traj_2gpglosdsq7s.md diff --git a/.trajectories/completed/2026-05/traj_2tqxnib25omk.json b/.agentworkforce/trajectories/completed/2026-05/traj_2tqxnib25omk.json similarity index 100% rename from .trajectories/completed/2026-05/traj_2tqxnib25omk.json rename to .agentworkforce/trajectories/completed/2026-05/traj_2tqxnib25omk.json diff --git a/.trajectories/completed/2026-05/traj_2tqxnib25omk.md b/.agentworkforce/trajectories/completed/2026-05/traj_2tqxnib25omk.md similarity index 100% rename from .trajectories/completed/2026-05/traj_2tqxnib25omk.md rename to .agentworkforce/trajectories/completed/2026-05/traj_2tqxnib25omk.md diff --git a/.trajectories/completed/2026-05/traj_2yicjxgajt0a.json b/.agentworkforce/trajectories/completed/2026-05/traj_2yicjxgajt0a.json similarity index 100% rename from .trajectories/completed/2026-05/traj_2yicjxgajt0a.json rename to .agentworkforce/trajectories/completed/2026-05/traj_2yicjxgajt0a.json diff --git a/.trajectories/completed/2026-05/traj_2yicjxgajt0a.md b/.agentworkforce/trajectories/completed/2026-05/traj_2yicjxgajt0a.md similarity index 100% rename from .trajectories/completed/2026-05/traj_2yicjxgajt0a.md rename to .agentworkforce/trajectories/completed/2026-05/traj_2yicjxgajt0a.md diff --git a/.trajectories/completed/2026-05/traj_34b1u84b19gz.json b/.agentworkforce/trajectories/completed/2026-05/traj_34b1u84b19gz.json similarity index 100% rename from .trajectories/completed/2026-05/traj_34b1u84b19gz.json rename to .agentworkforce/trajectories/completed/2026-05/traj_34b1u84b19gz.json diff --git a/.trajectories/completed/2026-05/traj_34b1u84b19gz.md b/.agentworkforce/trajectories/completed/2026-05/traj_34b1u84b19gz.md similarity index 100% rename from .trajectories/completed/2026-05/traj_34b1u84b19gz.md rename to .agentworkforce/trajectories/completed/2026-05/traj_34b1u84b19gz.md diff --git a/.trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json b/.agentworkforce/trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json similarity index 100% rename from .trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json rename to .agentworkforce/trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json diff --git a/.trajectories/completed/2026-05/traj_3b3p1z4y7qlo.md b/.agentworkforce/trajectories/completed/2026-05/traj_3b3p1z4y7qlo.md similarity index 100% rename from .trajectories/completed/2026-05/traj_3b3p1z4y7qlo.md rename to .agentworkforce/trajectories/completed/2026-05/traj_3b3p1z4y7qlo.md diff --git a/.trajectories/completed/2026-05/traj_3gjtcykvybt5.json b/.agentworkforce/trajectories/completed/2026-05/traj_3gjtcykvybt5.json similarity index 100% rename from .trajectories/completed/2026-05/traj_3gjtcykvybt5.json rename to .agentworkforce/trajectories/completed/2026-05/traj_3gjtcykvybt5.json diff --git a/.trajectories/completed/2026-05/traj_3gjtcykvybt5.md b/.agentworkforce/trajectories/completed/2026-05/traj_3gjtcykvybt5.md similarity index 100% rename from .trajectories/completed/2026-05/traj_3gjtcykvybt5.md rename to .agentworkforce/trajectories/completed/2026-05/traj_3gjtcykvybt5.md diff --git a/.trajectories/completed/2026-05/traj_47akjihewlow.json b/.agentworkforce/trajectories/completed/2026-05/traj_47akjihewlow.json similarity index 100% rename from .trajectories/completed/2026-05/traj_47akjihewlow.json rename to .agentworkforce/trajectories/completed/2026-05/traj_47akjihewlow.json diff --git a/.trajectories/completed/2026-05/traj_47akjihewlow.md b/.agentworkforce/trajectories/completed/2026-05/traj_47akjihewlow.md similarity index 100% rename from .trajectories/completed/2026-05/traj_47akjihewlow.md rename to .agentworkforce/trajectories/completed/2026-05/traj_47akjihewlow.md diff --git a/.trajectories/completed/2026-05/traj_47akjihewlow.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_47akjihewlow.trace.json similarity index 100% rename from .trajectories/completed/2026-05/traj_47akjihewlow.trace.json rename to .agentworkforce/trajectories/completed/2026-05/traj_47akjihewlow.trace.json diff --git a/.trajectories/completed/2026-05/traj_4chzkm724ufo.json b/.agentworkforce/trajectories/completed/2026-05/traj_4chzkm724ufo.json similarity index 100% rename from .trajectories/completed/2026-05/traj_4chzkm724ufo.json rename to .agentworkforce/trajectories/completed/2026-05/traj_4chzkm724ufo.json diff --git a/.trajectories/completed/2026-05/traj_4chzkm724ufo.md b/.agentworkforce/trajectories/completed/2026-05/traj_4chzkm724ufo.md similarity index 100% rename from .trajectories/completed/2026-05/traj_4chzkm724ufo.md rename to .agentworkforce/trajectories/completed/2026-05/traj_4chzkm724ufo.md diff --git a/.trajectories/completed/2026-05/traj_4t07itef99ug.json b/.agentworkforce/trajectories/completed/2026-05/traj_4t07itef99ug.json similarity index 100% rename from .trajectories/completed/2026-05/traj_4t07itef99ug.json rename to .agentworkforce/trajectories/completed/2026-05/traj_4t07itef99ug.json diff --git a/.trajectories/completed/2026-05/traj_4t07itef99ug.md b/.agentworkforce/trajectories/completed/2026-05/traj_4t07itef99ug.md similarity index 100% rename from .trajectories/completed/2026-05/traj_4t07itef99ug.md rename to .agentworkforce/trajectories/completed/2026-05/traj_4t07itef99ug.md diff --git a/.trajectories/completed/2026-05/traj_4vucir4qvqa2.json b/.agentworkforce/trajectories/completed/2026-05/traj_4vucir4qvqa2.json similarity index 100% rename from .trajectories/completed/2026-05/traj_4vucir4qvqa2.json rename to .agentworkforce/trajectories/completed/2026-05/traj_4vucir4qvqa2.json diff --git a/.trajectories/completed/2026-05/traj_4vucir4qvqa2.md b/.agentworkforce/trajectories/completed/2026-05/traj_4vucir4qvqa2.md similarity index 100% rename from .trajectories/completed/2026-05/traj_4vucir4qvqa2.md rename to .agentworkforce/trajectories/completed/2026-05/traj_4vucir4qvqa2.md diff --git a/.trajectories/completed/2026-05/traj_5k0jtc1g5l33.json b/.agentworkforce/trajectories/completed/2026-05/traj_5k0jtc1g5l33.json similarity index 100% rename from .trajectories/completed/2026-05/traj_5k0jtc1g5l33.json rename to .agentworkforce/trajectories/completed/2026-05/traj_5k0jtc1g5l33.json diff --git a/.trajectories/completed/2026-05/traj_5k0jtc1g5l33.md b/.agentworkforce/trajectories/completed/2026-05/traj_5k0jtc1g5l33.md similarity index 100% rename from .trajectories/completed/2026-05/traj_5k0jtc1g5l33.md rename to .agentworkforce/trajectories/completed/2026-05/traj_5k0jtc1g5l33.md diff --git a/.trajectories/completed/2026-05/traj_5nzj6v56id4z.json b/.agentworkforce/trajectories/completed/2026-05/traj_5nzj6v56id4z.json similarity index 100% rename from .trajectories/completed/2026-05/traj_5nzj6v56id4z.json rename to .agentworkforce/trajectories/completed/2026-05/traj_5nzj6v56id4z.json diff --git a/.trajectories/completed/2026-05/traj_5q8i0iz4klpo.json b/.agentworkforce/trajectories/completed/2026-05/traj_5q8i0iz4klpo.json similarity index 100% rename from .trajectories/completed/2026-05/traj_5q8i0iz4klpo.json rename to .agentworkforce/trajectories/completed/2026-05/traj_5q8i0iz4klpo.json diff --git a/.trajectories/completed/2026-05/traj_5q8i0iz4klpo.md b/.agentworkforce/trajectories/completed/2026-05/traj_5q8i0iz4klpo.md similarity index 100% rename from .trajectories/completed/2026-05/traj_5q8i0iz4klpo.md rename to .agentworkforce/trajectories/completed/2026-05/traj_5q8i0iz4klpo.md diff --git a/.trajectories/completed/2026-05/traj_5qbla7w4kzoi.json b/.agentworkforce/trajectories/completed/2026-05/traj_5qbla7w4kzoi.json similarity index 100% rename from .trajectories/completed/2026-05/traj_5qbla7w4kzoi.json rename to .agentworkforce/trajectories/completed/2026-05/traj_5qbla7w4kzoi.json diff --git a/.trajectories/completed/2026-05/traj_5qbla7w4kzoi.md b/.agentworkforce/trajectories/completed/2026-05/traj_5qbla7w4kzoi.md similarity index 100% rename from .trajectories/completed/2026-05/traj_5qbla7w4kzoi.md rename to .agentworkforce/trajectories/completed/2026-05/traj_5qbla7w4kzoi.md diff --git a/.trajectories/completed/2026-05/traj_60qc24ufr96g.json b/.agentworkforce/trajectories/completed/2026-05/traj_60qc24ufr96g.json similarity index 100% rename from .trajectories/completed/2026-05/traj_60qc24ufr96g.json rename to .agentworkforce/trajectories/completed/2026-05/traj_60qc24ufr96g.json diff --git a/.trajectories/completed/2026-05/traj_60qc24ufr96g.md b/.agentworkforce/trajectories/completed/2026-05/traj_60qc24ufr96g.md similarity index 100% rename from .trajectories/completed/2026-05/traj_60qc24ufr96g.md rename to .agentworkforce/trajectories/completed/2026-05/traj_60qc24ufr96g.md diff --git a/.trajectories/completed/2026-05/traj_6sjeohtm3php.json b/.agentworkforce/trajectories/completed/2026-05/traj_6sjeohtm3php.json similarity index 100% rename from .trajectories/completed/2026-05/traj_6sjeohtm3php.json rename to .agentworkforce/trajectories/completed/2026-05/traj_6sjeohtm3php.json diff --git a/.trajectories/completed/2026-05/traj_6sjeohtm3php.md b/.agentworkforce/trajectories/completed/2026-05/traj_6sjeohtm3php.md similarity index 100% rename from .trajectories/completed/2026-05/traj_6sjeohtm3php.md rename to .agentworkforce/trajectories/completed/2026-05/traj_6sjeohtm3php.md diff --git a/.trajectories/completed/2026-05/traj_6ujzpx82gqs9.json b/.agentworkforce/trajectories/completed/2026-05/traj_6ujzpx82gqs9.json similarity index 100% rename from .trajectories/completed/2026-05/traj_6ujzpx82gqs9.json rename to .agentworkforce/trajectories/completed/2026-05/traj_6ujzpx82gqs9.json diff --git a/.trajectories/completed/2026-05/traj_6ujzpx82gqs9.md b/.agentworkforce/trajectories/completed/2026-05/traj_6ujzpx82gqs9.md similarity index 100% rename from .trajectories/completed/2026-05/traj_6ujzpx82gqs9.md rename to .agentworkforce/trajectories/completed/2026-05/traj_6ujzpx82gqs9.md diff --git a/.trajectories/completed/2026-05/traj_78ytpicts778.json b/.agentworkforce/trajectories/completed/2026-05/traj_78ytpicts778.json similarity index 100% rename from .trajectories/completed/2026-05/traj_78ytpicts778.json rename to .agentworkforce/trajectories/completed/2026-05/traj_78ytpicts778.json diff --git a/.trajectories/completed/2026-05/traj_78ytpicts778.md b/.agentworkforce/trajectories/completed/2026-05/traj_78ytpicts778.md similarity index 100% rename from .trajectories/completed/2026-05/traj_78ytpicts778.md rename to .agentworkforce/trajectories/completed/2026-05/traj_78ytpicts778.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_7i9tigaejfje.json b/.agentworkforce/trajectories/completed/2026-05/traj_7i9tigaejfje.json new file mode 100644 index 000000000..814f08bfa --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_7i9tigaejfje.json @@ -0,0 +1,53 @@ +{ + "id": "traj_7i9tigaejfje", + "version": 1, + "task": { + "title": "Clarify headless app-server harness terminology" + }, + "status": "completed", + "startedAt": "2026-05-25T16:34:21.397Z", + "completedAt": "2026-05-25T16:34:33.412Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-25T16:34:29.105Z" + } + ], + "chapters": [ + { + "id": "chap_vz66cc5xfiai", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-25T16:34:29.105Z", + "endedAt": "2026-05-25T16:34:33.412Z", + "events": [ + { + "ts": 1779726869106, + "type": "decision", + "content": "Collapsed app-server into headless runtime driver: Collapsed app-server into headless runtime driver", + "raw": { + "question": "Collapsed app-server into headless runtime driver", + "chosen": "Collapsed app-server into headless runtime driver", + "alternatives": [], + "reasoning": "App-server and provider command workers have the same non-PTY broker capability surface; keeping app_server as a separate public runtime made docs and API shape more confusing." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Clarified app-server as a headless harness driver instead of a separate public runtime.", + "approach": "Standard approach", + "confidence": 0.88 + }, + "commits": [], + "filesChanged": [], + "projectId": "/private/tmp/relay-harness-runtime-plans", + "tags": [], + "_trace": { + "startRef": "84d87355ef3a727947607690dd0df7bb0e158f5d", + "endRef": "84d87355ef3a727947607690dd0df7bb0e158f5d" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_7i9tigaejfje.md b/.agentworkforce/trajectories/completed/2026-05/traj_7i9tigaejfje.md new file mode 100644 index 000000000..17dd1bbfd --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_7i9tigaejfje.md @@ -0,0 +1,33 @@ +# Trajectory: Clarify headless app-server harness terminology + +> **Status:** ✅ Completed +> **Confidence:** 88% +> **Started:** May 25, 2026 at 12:34 PM +> **Completed:** May 25, 2026 at 12:34 PM + +--- + +## Summary + +Clarified app-server as a headless harness driver instead of a separate public runtime. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Collapsed app-server into headless runtime driver + +- **Chose:** Collapsed app-server into headless runtime driver +- **Reasoning:** App-server and provider command workers have the same non-PTY broker capability surface; keeping app_server as a separate public runtime made docs and API shape more confusing. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Collapsed app-server into headless runtime driver: Collapsed app-server into headless runtime driver diff --git a/.trajectories/completed/2026-05/traj_7uznwzoxbao6.json b/.agentworkforce/trajectories/completed/2026-05/traj_7uznwzoxbao6.json similarity index 100% rename from .trajectories/completed/2026-05/traj_7uznwzoxbao6.json rename to .agentworkforce/trajectories/completed/2026-05/traj_7uznwzoxbao6.json diff --git a/.trajectories/completed/2026-05/traj_7uznwzoxbao6.md b/.agentworkforce/trajectories/completed/2026-05/traj_7uznwzoxbao6.md similarity index 100% rename from .trajectories/completed/2026-05/traj_7uznwzoxbao6.md rename to .agentworkforce/trajectories/completed/2026-05/traj_7uznwzoxbao6.md diff --git a/.trajectories/completed/2026-05/traj_7zu7et53ph3l.json b/.agentworkforce/trajectories/completed/2026-05/traj_7zu7et53ph3l.json similarity index 100% rename from .trajectories/completed/2026-05/traj_7zu7et53ph3l.json rename to .agentworkforce/trajectories/completed/2026-05/traj_7zu7et53ph3l.json diff --git a/.trajectories/completed/2026-05/traj_7zu7et53ph3l.md b/.agentworkforce/trajectories/completed/2026-05/traj_7zu7et53ph3l.md similarity index 100% rename from .trajectories/completed/2026-05/traj_7zu7et53ph3l.md rename to .agentworkforce/trajectories/completed/2026-05/traj_7zu7et53ph3l.md diff --git a/.trajectories/completed/2026-05/traj_81kobstnzzwk.json b/.agentworkforce/trajectories/completed/2026-05/traj_81kobstnzzwk.json similarity index 100% rename from .trajectories/completed/2026-05/traj_81kobstnzzwk.json rename to .agentworkforce/trajectories/completed/2026-05/traj_81kobstnzzwk.json diff --git a/.trajectories/completed/2026-05/traj_8ljgydz61do5.json b/.agentworkforce/trajectories/completed/2026-05/traj_8ljgydz61do5.json similarity index 100% rename from .trajectories/completed/2026-05/traj_8ljgydz61do5.json rename to .agentworkforce/trajectories/completed/2026-05/traj_8ljgydz61do5.json diff --git a/.trajectories/completed/2026-05/traj_8ljgydz61do5.md b/.agentworkforce/trajectories/completed/2026-05/traj_8ljgydz61do5.md similarity index 100% rename from .trajectories/completed/2026-05/traj_8ljgydz61do5.md rename to .agentworkforce/trajectories/completed/2026-05/traj_8ljgydz61do5.md diff --git a/.trajectories/completed/2026-05/traj_8nhd9lljhbsw.json b/.agentworkforce/trajectories/completed/2026-05/traj_8nhd9lljhbsw.json similarity index 100% rename from .trajectories/completed/2026-05/traj_8nhd9lljhbsw.json rename to .agentworkforce/trajectories/completed/2026-05/traj_8nhd9lljhbsw.json diff --git a/.trajectories/completed/2026-05/traj_8nhd9lljhbsw.md b/.agentworkforce/trajectories/completed/2026-05/traj_8nhd9lljhbsw.md similarity index 100% rename from .trajectories/completed/2026-05/traj_8nhd9lljhbsw.md rename to .agentworkforce/trajectories/completed/2026-05/traj_8nhd9lljhbsw.md diff --git a/.trajectories/completed/2026-05/traj_90jmd9z27oap.json b/.agentworkforce/trajectories/completed/2026-05/traj_90jmd9z27oap.json similarity index 100% rename from .trajectories/completed/2026-05/traj_90jmd9z27oap.json rename to .agentworkforce/trajectories/completed/2026-05/traj_90jmd9z27oap.json diff --git a/.trajectories/completed/2026-05/traj_90jmd9z27oap.md b/.agentworkforce/trajectories/completed/2026-05/traj_90jmd9z27oap.md similarity index 100% rename from .trajectories/completed/2026-05/traj_90jmd9z27oap.md rename to .agentworkforce/trajectories/completed/2026-05/traj_90jmd9z27oap.md diff --git a/.trajectories/completed/2026-05/traj_947wzpddsg9j.json b/.agentworkforce/trajectories/completed/2026-05/traj_947wzpddsg9j.json similarity index 100% rename from .trajectories/completed/2026-05/traj_947wzpddsg9j.json rename to .agentworkforce/trajectories/completed/2026-05/traj_947wzpddsg9j.json diff --git a/.trajectories/completed/2026-05/traj_947wzpddsg9j.md b/.agentworkforce/trajectories/completed/2026-05/traj_947wzpddsg9j.md similarity index 100% rename from .trajectories/completed/2026-05/traj_947wzpddsg9j.md rename to .agentworkforce/trajectories/completed/2026-05/traj_947wzpddsg9j.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_9dj3qiugt26j.json b/.agentworkforce/trajectories/completed/2026-05/traj_9dj3qiugt26j.json new file mode 100644 index 000000000..5bcebc50f --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_9dj3qiugt26j.json @@ -0,0 +1,53 @@ +{ + "id": "traj_9dj3qiugt26j", + "version": 1, + "task": { + "title": "Fix harness runtime review issues" + }, + "status": "completed", + "startedAt": "2026-05-25T17:41:16.384Z", + "completedAt": "2026-05-25T17:53:06.394Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-25T17:52:56.230Z" + } + ], + "chapters": [ + { + "id": "chap_h9qfyvlzhsw1", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-25T17:52:56.230Z", + "endedAt": "2026-05-25T17:53:06.394Z", + "events": [ + { + "ts": 1779731576231, + "type": "decision", + "content": "Keep app-server plans attach-only for now: Keep app-server plans attach-only for now", + "raw": { + "question": "Keep app-server plans attach-only for now", + "chosen": "Keep app-server plans attach-only for now", + "alternatives": [], + "reasoning": "The Rust broker can execute durable JSON plans and report an attached host PID, but broker-owned app-server supervision would need explicit lifecycle ownership; rejecting broker-owned host plans avoids a half-supported mode." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Fixed harness runtime review issues: broker now tracks harness PID separately from worker wrapper PID, validates app-server plans at spawn, extends app-server release grace, avoids app-server delivery_verified overclaiming, and updates SDK/docs examples around env allowlists, permission flags, and attached app-server hosts.", + "approach": "Standard approach", + "confidence": 0.86 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "29c9e3af3bda02dd5e601dd1f648bab4cbf1384a", + "endRef": "29c9e3af3bda02dd5e601dd1f648bab4cbf1384a" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_9dj3qiugt26j.md b/.agentworkforce/trajectories/completed/2026-05/traj_9dj3qiugt26j.md new file mode 100644 index 000000000..dd90ae065 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_9dj3qiugt26j.md @@ -0,0 +1,33 @@ +# Trajectory: Fix harness runtime review issues + +> **Status:** ✅ Completed +> **Confidence:** 86% +> **Started:** May 25, 2026 at 01:41 PM +> **Completed:** May 25, 2026 at 01:53 PM + +--- + +## Summary + +Fixed harness runtime review issues: broker now tracks harness PID separately from worker wrapper PID, validates app-server plans at spawn, extends app-server release grace, avoids app-server delivery_verified overclaiming, and updates SDK/docs examples around env allowlists, permission flags, and attached app-server hosts. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Keep app-server plans attach-only for now + +- **Chose:** Keep app-server plans attach-only for now +- **Reasoning:** The Rust broker can execute durable JSON plans and report an attached host PID, but broker-owned app-server supervision would need explicit lifecycle ownership; rejecting broker-owned host plans avoids a half-supported mode. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Keep app-server plans attach-only for now: Keep app-server plans attach-only for now diff --git a/.trajectories/completed/2026-05/traj_9fdv7hxm0b60.json b/.agentworkforce/trajectories/completed/2026-05/traj_9fdv7hxm0b60.json similarity index 100% rename from .trajectories/completed/2026-05/traj_9fdv7hxm0b60.json rename to .agentworkforce/trajectories/completed/2026-05/traj_9fdv7hxm0b60.json diff --git a/.trajectories/completed/2026-05/traj_9fdv7hxm0b60.md b/.agentworkforce/trajectories/completed/2026-05/traj_9fdv7hxm0b60.md similarity index 100% rename from .trajectories/completed/2026-05/traj_9fdv7hxm0b60.md rename to .agentworkforce/trajectories/completed/2026-05/traj_9fdv7hxm0b60.md diff --git a/.trajectories/completed/2026-05/traj_9gq96irkj00s.json b/.agentworkforce/trajectories/completed/2026-05/traj_9gq96irkj00s.json similarity index 100% rename from .trajectories/completed/2026-05/traj_9gq96irkj00s.json rename to .agentworkforce/trajectories/completed/2026-05/traj_9gq96irkj00s.json diff --git a/.trajectories/completed/2026-05/traj_9gq96irkj00s.md b/.agentworkforce/trajectories/completed/2026-05/traj_9gq96irkj00s.md similarity index 100% rename from .trajectories/completed/2026-05/traj_9gq96irkj00s.md rename to .agentworkforce/trajectories/completed/2026-05/traj_9gq96irkj00s.md diff --git a/.trajectories/completed/2026-05/traj_aw7stgf4qau0.json b/.agentworkforce/trajectories/completed/2026-05/traj_aw7stgf4qau0.json similarity index 100% rename from .trajectories/completed/2026-05/traj_aw7stgf4qau0.json rename to .agentworkforce/trajectories/completed/2026-05/traj_aw7stgf4qau0.json diff --git a/.trajectories/completed/2026-05/traj_aw7stgf4qau0.md b/.agentworkforce/trajectories/completed/2026-05/traj_aw7stgf4qau0.md similarity index 100% rename from .trajectories/completed/2026-05/traj_aw7stgf4qau0.md rename to .agentworkforce/trajectories/completed/2026-05/traj_aw7stgf4qau0.md diff --git a/.trajectories/completed/2026-05/traj_b3g40827t5zh/summary.md b/.agentworkforce/trajectories/completed/2026-05/traj_b3g40827t5zh/summary.md similarity index 100% rename from .trajectories/completed/2026-05/traj_b3g40827t5zh/summary.md rename to .agentworkforce/trajectories/completed/2026-05/traj_b3g40827t5zh/summary.md diff --git a/.trajectories/completed/2026-05/traj_b3g40827t5zh/trajectory.json b/.agentworkforce/trajectories/completed/2026-05/traj_b3g40827t5zh/trajectory.json similarity index 100% rename from .trajectories/completed/2026-05/traj_b3g40827t5zh/trajectory.json rename to .agentworkforce/trajectories/completed/2026-05/traj_b3g40827t5zh/trajectory.json diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_bd431l65n9lg.json b/.agentworkforce/trajectories/completed/2026-05/traj_bd431l65n9lg.json new file mode 100644 index 000000000..aff85cb8a --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_bd431l65n9lg.json @@ -0,0 +1,25 @@ +{ + "id": "traj_bd431l65n9lg", + "version": 1, + "task": { + "title": "Remove broker harness registry footgun" + }, + "status": "completed", + "startedAt": "2026-05-25T21:55:14.197Z", + "completedAt": "2026-05-25T22:02:22.902Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Removed broker harness registry and harnessId from the PR. SDK named harnesses now resolve to inline harnessConfig before spawn; broker and Relaycast reject harnessId and require concrete configs for custom harness behavior.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "4fdda79ad264412bfc725e4d22a611a78b50df21", + "endRef": "4fdda79ad264412bfc725e4d22a611a78b50df21" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_bd431l65n9lg.md b/.agentworkforce/trajectories/completed/2026-05/traj_bd431l65n9lg.md new file mode 100644 index 000000000..362edd37b --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_bd431l65n9lg.md @@ -0,0 +1,14 @@ +# Trajectory: Remove broker harness registry footgun + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 25, 2026 at 05:55 PM +> **Completed:** May 25, 2026 at 06:02 PM + +--- + +## Summary + +Removed broker harness registry and harnessId from the PR. SDK named harnesses now resolve to inline harnessConfig before spawn; broker and Relaycast reject harnessId and require concrete configs for custom harness behavior. + +**Approach:** Standard approach diff --git a/.trajectories/completed/2026-05/traj_bdrlknyl8twj.json b/.agentworkforce/trajectories/completed/2026-05/traj_bdrlknyl8twj.json similarity index 100% rename from .trajectories/completed/2026-05/traj_bdrlknyl8twj.json rename to .agentworkforce/trajectories/completed/2026-05/traj_bdrlknyl8twj.json diff --git a/.trajectories/completed/2026-05/traj_bdrlknyl8twj.md b/.agentworkforce/trajectories/completed/2026-05/traj_bdrlknyl8twj.md similarity index 100% rename from .trajectories/completed/2026-05/traj_bdrlknyl8twj.md rename to .agentworkforce/trajectories/completed/2026-05/traj_bdrlknyl8twj.md diff --git a/.trajectories/completed/2026-05/traj_bz1a1o15p7px.json b/.agentworkforce/trajectories/completed/2026-05/traj_bz1a1o15p7px.json similarity index 100% rename from .trajectories/completed/2026-05/traj_bz1a1o15p7px.json rename to .agentworkforce/trajectories/completed/2026-05/traj_bz1a1o15p7px.json diff --git a/.trajectories/completed/2026-05/traj_bz1a1o15p7px.md b/.agentworkforce/trajectories/completed/2026-05/traj_bz1a1o15p7px.md similarity index 100% rename from .trajectories/completed/2026-05/traj_bz1a1o15p7px.md rename to .agentworkforce/trajectories/completed/2026-05/traj_bz1a1o15p7px.md diff --git a/.trajectories/completed/2026-05/traj_cbmwd07phhm2.json b/.agentworkforce/trajectories/completed/2026-05/traj_cbmwd07phhm2.json similarity index 100% rename from .trajectories/completed/2026-05/traj_cbmwd07phhm2.json rename to .agentworkforce/trajectories/completed/2026-05/traj_cbmwd07phhm2.json diff --git a/.trajectories/completed/2026-05/traj_cbmwd07phhm2.md b/.agentworkforce/trajectories/completed/2026-05/traj_cbmwd07phhm2.md similarity index 100% rename from .trajectories/completed/2026-05/traj_cbmwd07phhm2.md rename to .agentworkforce/trajectories/completed/2026-05/traj_cbmwd07phhm2.md diff --git a/.trajectories/completed/2026-05/traj_ceo5q9bh2od3.json b/.agentworkforce/trajectories/completed/2026-05/traj_ceo5q9bh2od3.json similarity index 100% rename from .trajectories/completed/2026-05/traj_ceo5q9bh2od3.json rename to .agentworkforce/trajectories/completed/2026-05/traj_ceo5q9bh2od3.json diff --git a/.trajectories/completed/2026-05/traj_ceo5q9bh2od3.md b/.agentworkforce/trajectories/completed/2026-05/traj_ceo5q9bh2od3.md similarity index 100% rename from .trajectories/completed/2026-05/traj_ceo5q9bh2od3.md rename to .agentworkforce/trajectories/completed/2026-05/traj_ceo5q9bh2od3.md diff --git a/.trajectories/completed/2026-05/traj_cszfl2icaj2t.json b/.agentworkforce/trajectories/completed/2026-05/traj_cszfl2icaj2t.json similarity index 100% rename from .trajectories/completed/2026-05/traj_cszfl2icaj2t.json rename to .agentworkforce/trajectories/completed/2026-05/traj_cszfl2icaj2t.json diff --git a/.trajectories/completed/2026-05/traj_cszfl2icaj2t.md b/.agentworkforce/trajectories/completed/2026-05/traj_cszfl2icaj2t.md similarity index 100% rename from .trajectories/completed/2026-05/traj_cszfl2icaj2t.md rename to .agentworkforce/trajectories/completed/2026-05/traj_cszfl2icaj2t.md diff --git a/.trajectories/completed/2026-05/traj_d89s38ddu7cj.json b/.agentworkforce/trajectories/completed/2026-05/traj_d89s38ddu7cj.json similarity index 100% rename from .trajectories/completed/2026-05/traj_d89s38ddu7cj.json rename to .agentworkforce/trajectories/completed/2026-05/traj_d89s38ddu7cj.json diff --git a/.trajectories/completed/2026-05/traj_d89s38ddu7cj.md b/.agentworkforce/trajectories/completed/2026-05/traj_d89s38ddu7cj.md similarity index 100% rename from .trajectories/completed/2026-05/traj_d89s38ddu7cj.md rename to .agentworkforce/trajectories/completed/2026-05/traj_d89s38ddu7cj.md diff --git a/.trajectories/completed/2026-05/traj_dbsnr453nxjw.json b/.agentworkforce/trajectories/completed/2026-05/traj_dbsnr453nxjw.json similarity index 100% rename from .trajectories/completed/2026-05/traj_dbsnr453nxjw.json rename to .agentworkforce/trajectories/completed/2026-05/traj_dbsnr453nxjw.json diff --git a/.trajectories/completed/2026-05/traj_dbsnr453nxjw.md b/.agentworkforce/trajectories/completed/2026-05/traj_dbsnr453nxjw.md similarity index 100% rename from .trajectories/completed/2026-05/traj_dbsnr453nxjw.md rename to .agentworkforce/trajectories/completed/2026-05/traj_dbsnr453nxjw.md diff --git a/.trajectories/completed/2026-05/traj_dcl9hgoiuac5.json b/.agentworkforce/trajectories/completed/2026-05/traj_dcl9hgoiuac5.json similarity index 100% rename from .trajectories/completed/2026-05/traj_dcl9hgoiuac5.json rename to .agentworkforce/trajectories/completed/2026-05/traj_dcl9hgoiuac5.json diff --git a/.trajectories/completed/2026-05/traj_dcl9hgoiuac5.md b/.agentworkforce/trajectories/completed/2026-05/traj_dcl9hgoiuac5.md similarity index 100% rename from .trajectories/completed/2026-05/traj_dcl9hgoiuac5.md rename to .agentworkforce/trajectories/completed/2026-05/traj_dcl9hgoiuac5.md diff --git a/.trajectories/completed/2026-05/traj_dpgn0am1jq1c.json b/.agentworkforce/trajectories/completed/2026-05/traj_dpgn0am1jq1c.json similarity index 100% rename from .trajectories/completed/2026-05/traj_dpgn0am1jq1c.json rename to .agentworkforce/trajectories/completed/2026-05/traj_dpgn0am1jq1c.json diff --git a/.trajectories/completed/2026-05/traj_dpgn0am1jq1c.md b/.agentworkforce/trajectories/completed/2026-05/traj_dpgn0am1jq1c.md similarity index 100% rename from .trajectories/completed/2026-05/traj_dpgn0am1jq1c.md rename to .agentworkforce/trajectories/completed/2026-05/traj_dpgn0am1jq1c.md diff --git a/.trajectories/completed/2026-05/traj_e1b7ww3un1u3.json b/.agentworkforce/trajectories/completed/2026-05/traj_e1b7ww3un1u3.json similarity index 100% rename from .trajectories/completed/2026-05/traj_e1b7ww3un1u3.json rename to .agentworkforce/trajectories/completed/2026-05/traj_e1b7ww3un1u3.json diff --git a/.trajectories/completed/2026-05/traj_e1b7ww3un1u3.md b/.agentworkforce/trajectories/completed/2026-05/traj_e1b7ww3un1u3.md similarity index 100% rename from .trajectories/completed/2026-05/traj_e1b7ww3un1u3.md rename to .agentworkforce/trajectories/completed/2026-05/traj_e1b7ww3un1u3.md diff --git a/.trajectories/completed/2026-05/traj_elx0fcwgs37x.json b/.agentworkforce/trajectories/completed/2026-05/traj_elx0fcwgs37x.json similarity index 100% rename from .trajectories/completed/2026-05/traj_elx0fcwgs37x.json rename to .agentworkforce/trajectories/completed/2026-05/traj_elx0fcwgs37x.json diff --git a/.trajectories/completed/2026-05/traj_elx0fcwgs37x.md b/.agentworkforce/trajectories/completed/2026-05/traj_elx0fcwgs37x.md similarity index 100% rename from .trajectories/completed/2026-05/traj_elx0fcwgs37x.md rename to .agentworkforce/trajectories/completed/2026-05/traj_elx0fcwgs37x.md diff --git a/.trajectories/completed/2026-05/traj_erzd7j9nto9r.json b/.agentworkforce/trajectories/completed/2026-05/traj_erzd7j9nto9r.json similarity index 100% rename from .trajectories/completed/2026-05/traj_erzd7j9nto9r.json rename to .agentworkforce/trajectories/completed/2026-05/traj_erzd7j9nto9r.json diff --git a/.trajectories/completed/2026-05/traj_erzd7j9nto9r.md b/.agentworkforce/trajectories/completed/2026-05/traj_erzd7j9nto9r.md similarity index 100% rename from .trajectories/completed/2026-05/traj_erzd7j9nto9r.md rename to .agentworkforce/trajectories/completed/2026-05/traj_erzd7j9nto9r.md diff --git a/.trajectories/completed/2026-05/traj_f1iac9ngymlj.json b/.agentworkforce/trajectories/completed/2026-05/traj_f1iac9ngymlj.json similarity index 100% rename from .trajectories/completed/2026-05/traj_f1iac9ngymlj.json rename to .agentworkforce/trajectories/completed/2026-05/traj_f1iac9ngymlj.json diff --git a/.trajectories/completed/2026-05/traj_f1iac9ngymlj.md b/.agentworkforce/trajectories/completed/2026-05/traj_f1iac9ngymlj.md similarity index 100% rename from .trajectories/completed/2026-05/traj_f1iac9ngymlj.md rename to .agentworkforce/trajectories/completed/2026-05/traj_f1iac9ngymlj.md diff --git a/.trajectories/completed/2026-05/traj_f3arvbmmlomn.json b/.agentworkforce/trajectories/completed/2026-05/traj_f3arvbmmlomn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_f3arvbmmlomn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_f3arvbmmlomn.json diff --git a/.trajectories/completed/2026-05/traj_f3arvbmmlomn.md b/.agentworkforce/trajectories/completed/2026-05/traj_f3arvbmmlomn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_f3arvbmmlomn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_f3arvbmmlomn.md diff --git a/.trajectories/completed/2026-05/traj_f9wxa8ujeg78.json b/.agentworkforce/trajectories/completed/2026-05/traj_f9wxa8ujeg78.json similarity index 100% rename from .trajectories/completed/2026-05/traj_f9wxa8ujeg78.json rename to .agentworkforce/trajectories/completed/2026-05/traj_f9wxa8ujeg78.json diff --git a/.trajectories/completed/2026-05/traj_f9wxa8ujeg78.md b/.agentworkforce/trajectories/completed/2026-05/traj_f9wxa8ujeg78.md similarity index 100% rename from .trajectories/completed/2026-05/traj_f9wxa8ujeg78.md rename to .agentworkforce/trajectories/completed/2026-05/traj_f9wxa8ujeg78.md diff --git a/.trajectories/completed/2026-05/traj_fh8oosbijpwc.json b/.agentworkforce/trajectories/completed/2026-05/traj_fh8oosbijpwc.json similarity index 100% rename from .trajectories/completed/2026-05/traj_fh8oosbijpwc.json rename to .agentworkforce/trajectories/completed/2026-05/traj_fh8oosbijpwc.json diff --git a/.trajectories/completed/2026-05/traj_fh8oosbijpwc.md b/.agentworkforce/trajectories/completed/2026-05/traj_fh8oosbijpwc.md similarity index 100% rename from .trajectories/completed/2026-05/traj_fh8oosbijpwc.md rename to .agentworkforce/trajectories/completed/2026-05/traj_fh8oosbijpwc.md diff --git a/.trajectories/completed/2026-05/traj_fh8oosbijpwc.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_fh8oosbijpwc.trace.json similarity index 100% rename from .trajectories/completed/2026-05/traj_fh8oosbijpwc.trace.json rename to .agentworkforce/trajectories/completed/2026-05/traj_fh8oosbijpwc.trace.json diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_fiygtgr3tfey.json b/.agentworkforce/trajectories/completed/2026-05/traj_fiygtgr3tfey.json new file mode 100644 index 000000000..601d27596 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_fiygtgr3tfey.json @@ -0,0 +1,25 @@ +{ + "id": "traj_fiygtgr3tfey", + "version": 1, + "task": { + "title": "Fix harness config clippy issues" + }, + "status": "completed", + "startedAt": "2026-05-25T18:31:39.975Z", + "completedAt": "2026-05-25T18:34:10.562Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Fixed broker clippy failures by boxing AgentSpec in large protocol enum variants, replacing a manual iter-any check with contains, and moving runtime util tests after all non-test items.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "c2629f897d674166d1614651c41003893b8c0eb8", + "endRef": "c2629f897d674166d1614651c41003893b8c0eb8" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_fiygtgr3tfey.md b/.agentworkforce/trajectories/completed/2026-05/traj_fiygtgr3tfey.md new file mode 100644 index 000000000..12f1cfa63 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_fiygtgr3tfey.md @@ -0,0 +1,14 @@ +# Trajectory: Fix harness config clippy issues + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 25, 2026 at 02:31 PM +> **Completed:** May 25, 2026 at 02:34 PM + +--- + +## Summary + +Fixed broker clippy failures by boxing AgentSpec in large protocol enum variants, replacing a manual iter-any check with contains, and moving runtime util tests after all non-test items. + +**Approach:** Standard approach diff --git a/.trajectories/completed/2026-05/traj_gh05rj5gwsap.json b/.agentworkforce/trajectories/completed/2026-05/traj_gh05rj5gwsap.json similarity index 100% rename from .trajectories/completed/2026-05/traj_gh05rj5gwsap.json rename to .agentworkforce/trajectories/completed/2026-05/traj_gh05rj5gwsap.json diff --git a/.trajectories/completed/2026-05/traj_gh05rj5gwsap.md b/.agentworkforce/trajectories/completed/2026-05/traj_gh05rj5gwsap.md similarity index 100% rename from .trajectories/completed/2026-05/traj_gh05rj5gwsap.md rename to .agentworkforce/trajectories/completed/2026-05/traj_gh05rj5gwsap.md diff --git a/.trajectories/completed/2026-05/traj_gh05rj5gwsap.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_gh05rj5gwsap.trace.json similarity index 100% rename from .trajectories/completed/2026-05/traj_gh05rj5gwsap.trace.json rename to .agentworkforce/trajectories/completed/2026-05/traj_gh05rj5gwsap.trace.json diff --git a/.trajectories/completed/2026-05/traj_gkxajksmwoea.json b/.agentworkforce/trajectories/completed/2026-05/traj_gkxajksmwoea.json similarity index 100% rename from .trajectories/completed/2026-05/traj_gkxajksmwoea.json rename to .agentworkforce/trajectories/completed/2026-05/traj_gkxajksmwoea.json diff --git a/.trajectories/completed/2026-05/traj_gkxajksmwoea.md b/.agentworkforce/trajectories/completed/2026-05/traj_gkxajksmwoea.md similarity index 100% rename from .trajectories/completed/2026-05/traj_gkxajksmwoea.md rename to .agentworkforce/trajectories/completed/2026-05/traj_gkxajksmwoea.md diff --git a/.trajectories/completed/2026-05/traj_gnqvtoxtc8dy.json b/.agentworkforce/trajectories/completed/2026-05/traj_gnqvtoxtc8dy.json similarity index 100% rename from .trajectories/completed/2026-05/traj_gnqvtoxtc8dy.json rename to .agentworkforce/trajectories/completed/2026-05/traj_gnqvtoxtc8dy.json diff --git a/.trajectories/completed/2026-05/traj_gnqvtoxtc8dy.md b/.agentworkforce/trajectories/completed/2026-05/traj_gnqvtoxtc8dy.md similarity index 100% rename from .trajectories/completed/2026-05/traj_gnqvtoxtc8dy.md rename to .agentworkforce/trajectories/completed/2026-05/traj_gnqvtoxtc8dy.md diff --git a/.trajectories/completed/2026-05/traj_hfkww5z7trxn.json b/.agentworkforce/trajectories/completed/2026-05/traj_hfkww5z7trxn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_hfkww5z7trxn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_hfkww5z7trxn.json diff --git a/.trajectories/completed/2026-05/traj_hfkww5z7trxn.md b/.agentworkforce/trajectories/completed/2026-05/traj_hfkww5z7trxn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_hfkww5z7trxn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_hfkww5z7trxn.md diff --git a/.trajectories/completed/2026-05/traj_hrsndfzk0qay.json b/.agentworkforce/trajectories/completed/2026-05/traj_hrsndfzk0qay.json similarity index 100% rename from .trajectories/completed/2026-05/traj_hrsndfzk0qay.json rename to .agentworkforce/trajectories/completed/2026-05/traj_hrsndfzk0qay.json diff --git a/.trajectories/completed/2026-05/traj_hrsndfzk0qay.md b/.agentworkforce/trajectories/completed/2026-05/traj_hrsndfzk0qay.md similarity index 100% rename from .trajectories/completed/2026-05/traj_hrsndfzk0qay.md rename to .agentworkforce/trajectories/completed/2026-05/traj_hrsndfzk0qay.md diff --git a/.trajectories/completed/2026-05/traj_hysw5o7idqas.json b/.agentworkforce/trajectories/completed/2026-05/traj_hysw5o7idqas.json similarity index 100% rename from .trajectories/completed/2026-05/traj_hysw5o7idqas.json rename to .agentworkforce/trajectories/completed/2026-05/traj_hysw5o7idqas.json diff --git a/.trajectories/completed/2026-05/traj_hysw5o7idqas.md b/.agentworkforce/trajectories/completed/2026-05/traj_hysw5o7idqas.md similarity index 100% rename from .trajectories/completed/2026-05/traj_hysw5o7idqas.md rename to .agentworkforce/trajectories/completed/2026-05/traj_hysw5o7idqas.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.json b/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.json new file mode 100644 index 000000000..be920a3ab --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.json @@ -0,0 +1,26 @@ +{ + "id": "traj_i2pjnx3dll5b", + "version": 1, + "task": { + "title": "Fresh harness runtime plan and implementation" + }, + "status": "completed", + "startedAt": "2026-05-25T15:49:27.102Z", + "completedAt": "2026-05-25T16:22:35.339Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Documented and implemented harness runtime plans with SDK static/dynamic resolution, broker PTY plan execution, and OpenCode app-server delivery.", + "approach": "Standard approach", + "confidence": 0.84 + }, + "commits": ["8ddc2f9d"], + "filesChanged": ["docs/harness-runtime-plan.md", "web/content/docs/harnesses.mdx", "web/lib/docs-nav.ts"], + "projectId": "/private/tmp/relay-harness-runtime-plans", + "tags": [], + "_trace": { + "startRef": "ff1d4da534c314ece4f3a0f66d29de59c3363232", + "endRef": "8ddc2f9d9e9fcf0504ad4bd76e8d7e0b1da8d299", + "traceId": "bb0dfc23-cfa6-4d25-9d72-4d0448733f1f" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.md b/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.md new file mode 100644 index 000000000..1b4401285 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.md @@ -0,0 +1,21 @@ +# Trajectory: Fresh harness runtime plan and implementation + +> **Status:** ✅ Completed +> **Confidence:** 84% +> **Started:** May 25, 2026 at 11:49 AM +> **Completed:** May 25, 2026 at 12:22 PM + +--- + +## Summary + +Documented and implemented harness runtime plans with SDK static/dynamic resolution, broker PTY plan execution, and OpenCode app-server delivery. + +**Approach:** Standard approach + +--- + +## Artifacts + +**Commits:** 8ddc2f9d +**Files changed:** 3 diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.trace.json new file mode 100644 index 000000000..04c1b1af0 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_i2pjnx3dll5b.trace.json @@ -0,0 +1,59 @@ +{ + "version": "1.0.0", + "id": "bb0dfc23-cfa6-4d25-9d72-4d0448733f1f", + "timestamp": "2026-05-25T16:22:35.421Z", + "trajectory": "traj_i2pjnx3dll5b", + "files": [ + { + "path": "docs/harness-runtime-plan.md", + "conversations": [ + { + "contributor": { + "type": "ai" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 184, + "revision": "8ddc2f9d9e9fcf0504ad4bd76e8d7e0b1da8d299" + } + ] + } + ] + }, + { + "path": "web/content/docs/harnesses.mdx", + "conversations": [ + { + "contributor": { + "type": "ai" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 144, + "revision": "8ddc2f9d9e9fcf0504ad4bd76e8d7e0b1da8d299" + } + ] + } + ] + }, + { + "path": "web/lib/docs-nav.ts", + "conversations": [ + { + "contributor": { + "type": "ai" + }, + "ranges": [ + { + "start_line": 20, + "end_line": 26, + "revision": "8ddc2f9d9e9fcf0504ad4bd76e8d7e0b1da8d299" + } + ] + } + ] + } + ] +} diff --git a/.trajectories/completed/2026-05/traj_ij5b3kcatvwn.json b/.agentworkforce/trajectories/completed/2026-05/traj_ij5b3kcatvwn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_ij5b3kcatvwn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_ij5b3kcatvwn.json diff --git a/.trajectories/completed/2026-05/traj_ij5b3kcatvwn.md b/.agentworkforce/trajectories/completed/2026-05/traj_ij5b3kcatvwn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_ij5b3kcatvwn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_ij5b3kcatvwn.md diff --git a/.trajectories/completed/2026-05/traj_iole5zdt9orr.json b/.agentworkforce/trajectories/completed/2026-05/traj_iole5zdt9orr.json similarity index 100% rename from .trajectories/completed/2026-05/traj_iole5zdt9orr.json rename to .agentworkforce/trajectories/completed/2026-05/traj_iole5zdt9orr.json diff --git a/.trajectories/completed/2026-05/traj_iole5zdt9orr.md b/.agentworkforce/trajectories/completed/2026-05/traj_iole5zdt9orr.md similarity index 100% rename from .trajectories/completed/2026-05/traj_iole5zdt9orr.md rename to .agentworkforce/trajectories/completed/2026-05/traj_iole5zdt9orr.md diff --git a/.trajectories/completed/2026-05/traj_irafiyk6wpw0.json b/.agentworkforce/trajectories/completed/2026-05/traj_irafiyk6wpw0.json similarity index 100% rename from .trajectories/completed/2026-05/traj_irafiyk6wpw0.json rename to .agentworkforce/trajectories/completed/2026-05/traj_irafiyk6wpw0.json diff --git a/.trajectories/completed/2026-05/traj_itgr2w8qs3xn.json b/.agentworkforce/trajectories/completed/2026-05/traj_itgr2w8qs3xn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_itgr2w8qs3xn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_itgr2w8qs3xn.json diff --git a/.trajectories/completed/2026-05/traj_itgr2w8qs3xn.md b/.agentworkforce/trajectories/completed/2026-05/traj_itgr2w8qs3xn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_itgr2w8qs3xn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_itgr2w8qs3xn.md diff --git a/.trajectories/completed/2026-05/traj_j9k10fez3e81.json b/.agentworkforce/trajectories/completed/2026-05/traj_j9k10fez3e81.json similarity index 100% rename from .trajectories/completed/2026-05/traj_j9k10fez3e81.json rename to .agentworkforce/trajectories/completed/2026-05/traj_j9k10fez3e81.json diff --git a/.trajectories/completed/2026-05/traj_j9k10fez3e81.md b/.agentworkforce/trajectories/completed/2026-05/traj_j9k10fez3e81.md similarity index 100% rename from .trajectories/completed/2026-05/traj_j9k10fez3e81.md rename to .agentworkforce/trajectories/completed/2026-05/traj_j9k10fez3e81.md diff --git a/.trajectories/completed/2026-05/traj_jbo2x14y7ovt.json b/.agentworkforce/trajectories/completed/2026-05/traj_jbo2x14y7ovt.json similarity index 100% rename from .trajectories/completed/2026-05/traj_jbo2x14y7ovt.json rename to .agentworkforce/trajectories/completed/2026-05/traj_jbo2x14y7ovt.json diff --git a/.trajectories/completed/2026-05/traj_jbo2x14y7ovt.md b/.agentworkforce/trajectories/completed/2026-05/traj_jbo2x14y7ovt.md similarity index 100% rename from .trajectories/completed/2026-05/traj_jbo2x14y7ovt.md rename to .agentworkforce/trajectories/completed/2026-05/traj_jbo2x14y7ovt.md diff --git a/.trajectories/completed/2026-05/traj_jmf9pyt3zikn.json b/.agentworkforce/trajectories/completed/2026-05/traj_jmf9pyt3zikn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_jmf9pyt3zikn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_jmf9pyt3zikn.json diff --git a/.trajectories/completed/2026-05/traj_jmf9pyt3zikn.md b/.agentworkforce/trajectories/completed/2026-05/traj_jmf9pyt3zikn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_jmf9pyt3zikn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_jmf9pyt3zikn.md diff --git a/.trajectories/completed/2026-05/traj_k7njijv51iq4.json b/.agentworkforce/trajectories/completed/2026-05/traj_k7njijv51iq4.json similarity index 100% rename from .trajectories/completed/2026-05/traj_k7njijv51iq4.json rename to .agentworkforce/trajectories/completed/2026-05/traj_k7njijv51iq4.json diff --git a/.trajectories/completed/2026-05/traj_k7njijv51iq4.md b/.agentworkforce/trajectories/completed/2026-05/traj_k7njijv51iq4.md similarity index 100% rename from .trajectories/completed/2026-05/traj_k7njijv51iq4.md rename to .agentworkforce/trajectories/completed/2026-05/traj_k7njijv51iq4.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_kgl2opmmfvus.json b/.agentworkforce/trajectories/completed/2026-05/traj_kgl2opmmfvus.json new file mode 100644 index 000000000..0c83dd81d --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_kgl2opmmfvus.json @@ -0,0 +1,25 @@ +{ + "id": "traj_kgl2opmmfvus", + "version": 1, + "task": { + "title": "Simplify harness config PR around broker-owned configs" + }, + "status": "completed", + "startedAt": "2026-05-25T21:32:14.085Z", + "completedAt": "2026-05-25T21:47:15.455Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Simplified harness work around broker-executable configs: added broker harness registry APIs, SDK harnessId registration/selection, Relaycast harnessId/inline config handling, and concise docs that remove dynamic SDK resolvers.", + "approach": "Standard approach", + "confidence": 0.86 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "75f019cf5c2601ae195315ea43ae601eb39cfed7", + "endRef": "75f019cf5c2601ae195315ea43ae601eb39cfed7" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_kgl2opmmfvus.md b/.agentworkforce/trajectories/completed/2026-05/traj_kgl2opmmfvus.md new file mode 100644 index 000000000..ffc74890c --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_kgl2opmmfvus.md @@ -0,0 +1,14 @@ +# Trajectory: Simplify harness config PR around broker-owned configs + +> **Status:** ✅ Completed +> **Confidence:** 86% +> **Started:** May 25, 2026 at 05:32 PM +> **Completed:** May 25, 2026 at 05:47 PM + +--- + +## Summary + +Simplified harness work around broker-executable configs: added broker harness registry APIs, SDK harnessId registration/selection, Relaycast harnessId/inline config handling, and concise docs that remove dynamic SDK resolvers. + +**Approach:** Standard approach diff --git a/.trajectories/completed/2026-05/traj_l1349adi1g0o.json b/.agentworkforce/trajectories/completed/2026-05/traj_l1349adi1g0o.json similarity index 100% rename from .trajectories/completed/2026-05/traj_l1349adi1g0o.json rename to .agentworkforce/trajectories/completed/2026-05/traj_l1349adi1g0o.json diff --git a/.trajectories/completed/2026-05/traj_l1349adi1g0o.md b/.agentworkforce/trajectories/completed/2026-05/traj_l1349adi1g0o.md similarity index 100% rename from .trajectories/completed/2026-05/traj_l1349adi1g0o.md rename to .agentworkforce/trajectories/completed/2026-05/traj_l1349adi1g0o.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_l67sex3nkzfq.json b/.agentworkforce/trajectories/completed/2026-05/traj_l67sex3nkzfq.json new file mode 100644 index 000000000..8838acaaf --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_l67sex3nkzfq.json @@ -0,0 +1,53 @@ +{ + "id": "traj_l67sex3nkzfq", + "version": 1, + "task": { + "title": "Fix failing harness PR e2e tests" + }, + "status": "completed", + "startedAt": "2026-05-25T23:22:50.714Z", + "completedAt": "2026-05-25T23:32:19.352Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-25T23:32:15.085Z" + } + ], + "chapters": [ + { + "id": "chap_8194bsxpmfsk", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-25T23:32:15.085Z", + "endedAt": "2026-05-25T23:32:19.352Z", + "events": [ + { + "ts": 1779751935086, + "type": "decision", + "content": "Normalize null spawn pid in SDK schema: Normalize null spawn pid in SDK schema", + "raw": { + "question": "Normalize null spawn pid in SDK schema", + "chosen": "Normalize null spawn pid in SDK schema", + "alternatives": [], + "reasoning": "CI showed /api/spawn can return pid:null before the PTY worker emits worker_ready with the harness PID; the SDK public type already treats pid as optional, so null should parse as undefined like sessionId." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Fixed the SDK spawn result schema so broker pid:null responses parse as undefined, added a regression test, and validated SDK checks/build plus daemon-only E2E smoke.", + "approach": "Standard approach", + "confidence": 0.88 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "af884f5961dbc9a62b751e2d29994e5755cc3552", + "endRef": "af884f5961dbc9a62b751e2d29994e5755cc3552" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_l67sex3nkzfq.md b/.agentworkforce/trajectories/completed/2026-05/traj_l67sex3nkzfq.md new file mode 100644 index 000000000..7666c2a1d --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_l67sex3nkzfq.md @@ -0,0 +1,33 @@ +# Trajectory: Fix failing harness PR e2e tests + +> **Status:** ✅ Completed +> **Confidence:** 88% +> **Started:** May 25, 2026 at 07:22 PM +> **Completed:** May 25, 2026 at 07:32 PM + +--- + +## Summary + +Fixed the SDK spawn result schema so broker pid:null responses parse as undefined, added a regression test, and validated SDK checks/build plus daemon-only E2E smoke. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Normalize null spawn pid in SDK schema + +- **Chose:** Normalize null spawn pid in SDK schema +- **Reasoning:** CI showed /api/spawn can return pid:null before the PTY worker emits worker_ready with the harness PID; the SDK public type already treats pid as optional, so null should parse as undefined like sessionId. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Normalize null spawn pid in SDK schema: Normalize null spawn pid in SDK schema diff --git a/.trajectories/completed/2026-05/traj_lhyrcib40kao.json b/.agentworkforce/trajectories/completed/2026-05/traj_lhyrcib40kao.json similarity index 100% rename from .trajectories/completed/2026-05/traj_lhyrcib40kao.json rename to .agentworkforce/trajectories/completed/2026-05/traj_lhyrcib40kao.json diff --git a/.trajectories/completed/2026-05/traj_lhyrcib40kao.md b/.agentworkforce/trajectories/completed/2026-05/traj_lhyrcib40kao.md similarity index 100% rename from .trajectories/completed/2026-05/traj_lhyrcib40kao.md rename to .agentworkforce/trajectories/completed/2026-05/traj_lhyrcib40kao.md diff --git a/.trajectories/completed/2026-05/traj_lieyyspidhfj.json b/.agentworkforce/trajectories/completed/2026-05/traj_lieyyspidhfj.json similarity index 100% rename from .trajectories/completed/2026-05/traj_lieyyspidhfj.json rename to .agentworkforce/trajectories/completed/2026-05/traj_lieyyspidhfj.json diff --git a/.trajectories/completed/2026-05/traj_lieyyspidhfj.md b/.agentworkforce/trajectories/completed/2026-05/traj_lieyyspidhfj.md similarity index 100% rename from .trajectories/completed/2026-05/traj_lieyyspidhfj.md rename to .agentworkforce/trajectories/completed/2026-05/traj_lieyyspidhfj.md diff --git a/.trajectories/completed/2026-05/traj_lieyyspidhfj.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_lieyyspidhfj.trace.json similarity index 100% rename from .trajectories/completed/2026-05/traj_lieyyspidhfj.trace.json rename to .agentworkforce/trajectories/completed/2026-05/traj_lieyyspidhfj.trace.json diff --git a/.trajectories/completed/2026-05/traj_m7mpv7j8n78h.json b/.agentworkforce/trajectories/completed/2026-05/traj_m7mpv7j8n78h.json similarity index 100% rename from .trajectories/completed/2026-05/traj_m7mpv7j8n78h.json rename to .agentworkforce/trajectories/completed/2026-05/traj_m7mpv7j8n78h.json diff --git a/.trajectories/completed/2026-05/traj_m7mpv7j8n78h.md b/.agentworkforce/trajectories/completed/2026-05/traj_m7mpv7j8n78h.md similarity index 100% rename from .trajectories/completed/2026-05/traj_m7mpv7j8n78h.md rename to .agentworkforce/trajectories/completed/2026-05/traj_m7mpv7j8n78h.md diff --git a/.trajectories/completed/2026-05/traj_mi9eqd4rjfea.json b/.agentworkforce/trajectories/completed/2026-05/traj_mi9eqd4rjfea.json similarity index 100% rename from .trajectories/completed/2026-05/traj_mi9eqd4rjfea.json rename to .agentworkforce/trajectories/completed/2026-05/traj_mi9eqd4rjfea.json diff --git a/.trajectories/completed/2026-05/traj_mi9eqd4rjfea.md b/.agentworkforce/trajectories/completed/2026-05/traj_mi9eqd4rjfea.md similarity index 100% rename from .trajectories/completed/2026-05/traj_mi9eqd4rjfea.md rename to .agentworkforce/trajectories/completed/2026-05/traj_mi9eqd4rjfea.md diff --git a/.trajectories/completed/2026-05/traj_mytnzgfayj3d.json b/.agentworkforce/trajectories/completed/2026-05/traj_mytnzgfayj3d.json similarity index 100% rename from .trajectories/completed/2026-05/traj_mytnzgfayj3d.json rename to .agentworkforce/trajectories/completed/2026-05/traj_mytnzgfayj3d.json diff --git a/.trajectories/completed/2026-05/traj_mytnzgfayj3d.md b/.agentworkforce/trajectories/completed/2026-05/traj_mytnzgfayj3d.md similarity index 100% rename from .trajectories/completed/2026-05/traj_mytnzgfayj3d.md rename to .agentworkforce/trajectories/completed/2026-05/traj_mytnzgfayj3d.md diff --git a/.trajectories/completed/2026-05/traj_mz5m5ysjj31e.json b/.agentworkforce/trajectories/completed/2026-05/traj_mz5m5ysjj31e.json similarity index 100% rename from .trajectories/completed/2026-05/traj_mz5m5ysjj31e.json rename to .agentworkforce/trajectories/completed/2026-05/traj_mz5m5ysjj31e.json diff --git a/.trajectories/completed/2026-05/traj_mz5m5ysjj31e.md b/.agentworkforce/trajectories/completed/2026-05/traj_mz5m5ysjj31e.md similarity index 100% rename from .trajectories/completed/2026-05/traj_mz5m5ysjj31e.md rename to .agentworkforce/trajectories/completed/2026-05/traj_mz5m5ysjj31e.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_n0qwpjvmdl2s.json b/.agentworkforce/trajectories/completed/2026-05/traj_n0qwpjvmdl2s.json new file mode 100644 index 000000000..7ee04b70c --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_n0qwpjvmdl2s.json @@ -0,0 +1,25 @@ +{ + "id": "traj_n0qwpjvmdl2s", + "version": 1, + "task": { + "title": "Default headless harness driver" + }, + "status": "completed", + "startedAt": "2026-05-25T19:50:47.661Z", + "completedAt": "2026-05-25T19:52:52.791Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Made headless harness driver optional by defaulting missing driver values to app_server in broker deserialization and SDK static resolution, then updated docs/examples and tests to use the simpler headless config shape.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "e02a2952e146b882f9ae30223ab99b6b1f80f312", + "endRef": "e02a2952e146b882f9ae30223ab99b6b1f80f312" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_n0qwpjvmdl2s.md b/.agentworkforce/trajectories/completed/2026-05/traj_n0qwpjvmdl2s.md new file mode 100644 index 000000000..2f3dc0e81 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_n0qwpjvmdl2s.md @@ -0,0 +1,14 @@ +# Trajectory: Default headless harness driver + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 25, 2026 at 03:50 PM +> **Completed:** May 25, 2026 at 03:52 PM + +--- + +## Summary + +Made headless harness driver optional by defaulting missing driver values to app_server in broker deserialization and SDK static resolution, then updated docs/examples and tests to use the simpler headless config shape. + +**Approach:** Standard approach diff --git a/.trajectories/completed/2026-05/traj_n8duofq5vq1a.json b/.agentworkforce/trajectories/completed/2026-05/traj_n8duofq5vq1a.json similarity index 100% rename from .trajectories/completed/2026-05/traj_n8duofq5vq1a.json rename to .agentworkforce/trajectories/completed/2026-05/traj_n8duofq5vq1a.json diff --git a/.trajectories/completed/2026-05/traj_n8duofq5vq1a.md b/.agentworkforce/trajectories/completed/2026-05/traj_n8duofq5vq1a.md similarity index 100% rename from .trajectories/completed/2026-05/traj_n8duofq5vq1a.md rename to .agentworkforce/trajectories/completed/2026-05/traj_n8duofq5vq1a.md diff --git a/.trajectories/completed/2026-05/traj_o251whkvy9rl.json b/.agentworkforce/trajectories/completed/2026-05/traj_o251whkvy9rl.json similarity index 100% rename from .trajectories/completed/2026-05/traj_o251whkvy9rl.json rename to .agentworkforce/trajectories/completed/2026-05/traj_o251whkvy9rl.json diff --git a/.trajectories/completed/2026-05/traj_o251whkvy9rl.md b/.agentworkforce/trajectories/completed/2026-05/traj_o251whkvy9rl.md similarity index 100% rename from .trajectories/completed/2026-05/traj_o251whkvy9rl.md rename to .agentworkforce/trajectories/completed/2026-05/traj_o251whkvy9rl.md diff --git a/.trajectories/completed/2026-05/traj_o9cx33xn5u39.json b/.agentworkforce/trajectories/completed/2026-05/traj_o9cx33xn5u39.json similarity index 100% rename from .trajectories/completed/2026-05/traj_o9cx33xn5u39.json rename to .agentworkforce/trajectories/completed/2026-05/traj_o9cx33xn5u39.json diff --git a/.trajectories/completed/2026-05/traj_o9cx33xn5u39.md b/.agentworkforce/trajectories/completed/2026-05/traj_o9cx33xn5u39.md similarity index 100% rename from .trajectories/completed/2026-05/traj_o9cx33xn5u39.md rename to .agentworkforce/trajectories/completed/2026-05/traj_o9cx33xn5u39.md diff --git a/.trajectories/completed/2026-05/traj_ootb5rt3tozd.json b/.agentworkforce/trajectories/completed/2026-05/traj_ootb5rt3tozd.json similarity index 100% rename from .trajectories/completed/2026-05/traj_ootb5rt3tozd.json rename to .agentworkforce/trajectories/completed/2026-05/traj_ootb5rt3tozd.json diff --git a/.trajectories/completed/2026-05/traj_ootb5rt3tozd.md b/.agentworkforce/trajectories/completed/2026-05/traj_ootb5rt3tozd.md similarity index 100% rename from .trajectories/completed/2026-05/traj_ootb5rt3tozd.md rename to .agentworkforce/trajectories/completed/2026-05/traj_ootb5rt3tozd.md diff --git a/.trajectories/completed/2026-05/traj_oyc528j7suvo.json b/.agentworkforce/trajectories/completed/2026-05/traj_oyc528j7suvo.json similarity index 100% rename from .trajectories/completed/2026-05/traj_oyc528j7suvo.json rename to .agentworkforce/trajectories/completed/2026-05/traj_oyc528j7suvo.json diff --git a/.trajectories/completed/2026-05/traj_oyc528j7suvo.md b/.agentworkforce/trajectories/completed/2026-05/traj_oyc528j7suvo.md similarity index 100% rename from .trajectories/completed/2026-05/traj_oyc528j7suvo.md rename to .agentworkforce/trajectories/completed/2026-05/traj_oyc528j7suvo.md diff --git a/.trajectories/completed/2026-05/traj_piik8r6zu3i7.json b/.agentworkforce/trajectories/completed/2026-05/traj_piik8r6zu3i7.json similarity index 100% rename from .trajectories/completed/2026-05/traj_piik8r6zu3i7.json rename to .agentworkforce/trajectories/completed/2026-05/traj_piik8r6zu3i7.json diff --git a/.trajectories/completed/2026-05/traj_piik8r6zu3i7.md b/.agentworkforce/trajectories/completed/2026-05/traj_piik8r6zu3i7.md similarity index 100% rename from .trajectories/completed/2026-05/traj_piik8r6zu3i7.md rename to .agentworkforce/trajectories/completed/2026-05/traj_piik8r6zu3i7.md diff --git a/.trajectories/completed/2026-05/traj_pmrcfj6or3pz.json b/.agentworkforce/trajectories/completed/2026-05/traj_pmrcfj6or3pz.json similarity index 100% rename from .trajectories/completed/2026-05/traj_pmrcfj6or3pz.json rename to .agentworkforce/trajectories/completed/2026-05/traj_pmrcfj6or3pz.json diff --git a/.trajectories/completed/2026-05/traj_pmrcfj6or3pz.md b/.agentworkforce/trajectories/completed/2026-05/traj_pmrcfj6or3pz.md similarity index 100% rename from .trajectories/completed/2026-05/traj_pmrcfj6or3pz.md rename to .agentworkforce/trajectories/completed/2026-05/traj_pmrcfj6or3pz.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_q2r3c9dmdep7.json b/.agentworkforce/trajectories/completed/2026-05/traj_q2r3c9dmdep7.json new file mode 100644 index 000000000..295e8fa1e --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_q2r3c9dmdep7.json @@ -0,0 +1,53 @@ +{ + "id": "traj_q2r3c9dmdep7", + "version": 1, + "task": { + "title": "Rename harness plans to harness configs" + }, + "status": "completed", + "startedAt": "2026-05-25T18:00:40.591Z", + "completedAt": "2026-05-25T18:06:34.954Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-25T18:06:31.002Z" + } + ], + "chapters": [ + { + "id": "chap_fqok6eor5g3t", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-25T18:06:31.002Z", + "endedAt": "2026-05-25T18:06:34.954Z", + "events": [ + { + "ts": 1779732391003, + "type": "decision", + "content": "Rename harness plan API to harness config: Rename harness plan API to harness config", + "raw": { + "question": "Rename harness plan API to harness config", + "chosen": "Rename harness plan API to harness config", + "alternatives": [], + "reasoning": "The adapter output is declarative runtime configuration, not an execution plan; SDK and docs should expose harnessConfig/ResolvedHarnessConfig while the broker keeps legacy harnessPlan aliases for payload tolerance." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Renamed harness runtime terminology from plan to config across SDK, broker protocol, API payloads, tests, docs, and changelog. The broker now accepts harnessConfig as the primary field while retaining harnessPlan/harness_plan deserialization aliases for compatibility.", + "approach": "Standard approach", + "confidence": 0.88 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "8c429f257c346d3917c783b637d890eda045bbee", + "endRef": "8c429f257c346d3917c783b637d890eda045bbee" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_q2r3c9dmdep7.md b/.agentworkforce/trajectories/completed/2026-05/traj_q2r3c9dmdep7.md new file mode 100644 index 000000000..530785825 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_q2r3c9dmdep7.md @@ -0,0 +1,33 @@ +# Trajectory: Rename harness plans to harness configs + +> **Status:** ✅ Completed +> **Confidence:** 88% +> **Started:** May 25, 2026 at 02:00 PM +> **Completed:** May 25, 2026 at 02:06 PM + +--- + +## Summary + +Renamed harness runtime terminology from plan to config across SDK, broker protocol, API payloads, tests, docs, and changelog. The broker now accepts harnessConfig as the primary field while retaining harnessPlan/harness_plan deserialization aliases for compatibility. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Rename harness plan API to harness config + +- **Chose:** Rename harness plan API to harness config +- **Reasoning:** The adapter output is declarative runtime configuration, not an execution plan; SDK and docs should expose harnessConfig/ResolvedHarnessConfig while the broker keeps legacy harnessPlan aliases for payload tolerance. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Rename harness plan API to harness config: Rename harness plan API to harness config diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_qbq3laxbvhzf.json b/.agentworkforce/trajectories/completed/2026-05/traj_qbq3laxbvhzf.json new file mode 100644 index 000000000..a367ed2b0 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_qbq3laxbvhzf.json @@ -0,0 +1,53 @@ +{ + "id": "traj_qbq3laxbvhzf", + "version": 1, + "task": { + "title": "Resolve harness PR conflicts and comments" + }, + "status": "completed", + "startedAt": "2026-05-25T22:03:51.255Z", + "completedAt": "2026-05-25T22:25:18.889Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-25T22:25:14.976Z" + } + ], + "chapters": [ + { + "id": "chap_qdbantp2hi41", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-25T22:25:14.976Z", + "endedAt": "2026-05-25T22:25:18.889Z", + "events": [ + { + "ts": 1779747914977, + "type": "decision", + "content": "Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary: Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary", + "raw": { + "question": "Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary", + "chosen": "Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary", + "alternatives": [], + "reasoning": "Main added typed IDs, persona spawn plans, and Codex session creation while the PR added harness config plumbing. The resolution preserves typed IDs and main runtime changes, removes broker harness registry state, and keeps SDK named harnesses as local shortcuts that resolve to inline configs." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Resolved PR 987 merge conflicts with origin/main, applied outstanding review fixes for harness config handling, restored SDK and broker validation, and reran focused checks.", + "approach": "Standard approach", + "confidence": 0.88 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/will/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "e86f7d05f9fc6c8c4f044ac0a6c9b580a927cb07", + "endRef": "e86f7d05f9fc6c8c4f044ac0a6c9b580a927cb07" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_qbq3laxbvhzf.md b/.agentworkforce/trajectories/completed/2026-05/traj_qbq3laxbvhzf.md new file mode 100644 index 000000000..2089ecc51 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_qbq3laxbvhzf.md @@ -0,0 +1,33 @@ +# Trajectory: Resolve harness PR conflicts and comments + +> **Status:** ✅ Completed +> **Confidence:** 88% +> **Started:** May 25, 2026 at 06:03 PM +> **Completed:** May 25, 2026 at 06:25 PM + +--- + +## Summary + +Resolved PR 987 merge conflicts with origin/main, applied outstanding review fixes for harness config handling, restored SDK and broker validation, and reran focused checks. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary + +- **Chose:** Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary +- **Reasoning:** Main added typed IDs, persona spawn plans, and Codex session creation while the PR added harness config plumbing. The resolution preserves typed IDs and main runtime changes, removes broker harness registry state, and keeps SDK named harnesses as local shortcuts that resolve to inline configs. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary: Resolved PR conflicts by merging origin/main and keeping inline harnessConfig as the broker boundary diff --git a/.trajectories/completed/2026-05/traj_qtmid2nzz0kz.json b/.agentworkforce/trajectories/completed/2026-05/traj_qtmid2nzz0kz.json similarity index 100% rename from .trajectories/completed/2026-05/traj_qtmid2nzz0kz.json rename to .agentworkforce/trajectories/completed/2026-05/traj_qtmid2nzz0kz.json diff --git a/.trajectories/completed/2026-05/traj_qtmid2nzz0kz.md b/.agentworkforce/trajectories/completed/2026-05/traj_qtmid2nzz0kz.md similarity index 100% rename from .trajectories/completed/2026-05/traj_qtmid2nzz0kz.md rename to .agentworkforce/trajectories/completed/2026-05/traj_qtmid2nzz0kz.md diff --git a/.trajectories/completed/2026-05/traj_ryf5sstno6p3.json b/.agentworkforce/trajectories/completed/2026-05/traj_ryf5sstno6p3.json similarity index 100% rename from .trajectories/completed/2026-05/traj_ryf5sstno6p3.json rename to .agentworkforce/trajectories/completed/2026-05/traj_ryf5sstno6p3.json diff --git a/.trajectories/completed/2026-05/traj_ryf5sstno6p3.md b/.agentworkforce/trajectories/completed/2026-05/traj_ryf5sstno6p3.md similarity index 100% rename from .trajectories/completed/2026-05/traj_ryf5sstno6p3.md rename to .agentworkforce/trajectories/completed/2026-05/traj_ryf5sstno6p3.md diff --git a/.trajectories/completed/2026-05/traj_s5ojo1f4srz4.json b/.agentworkforce/trajectories/completed/2026-05/traj_s5ojo1f4srz4.json similarity index 100% rename from .trajectories/completed/2026-05/traj_s5ojo1f4srz4.json rename to .agentworkforce/trajectories/completed/2026-05/traj_s5ojo1f4srz4.json diff --git a/.trajectories/completed/2026-05/traj_s5ojo1f4srz4.md b/.agentworkforce/trajectories/completed/2026-05/traj_s5ojo1f4srz4.md similarity index 100% rename from .trajectories/completed/2026-05/traj_s5ojo1f4srz4.md rename to .agentworkforce/trajectories/completed/2026-05/traj_s5ojo1f4srz4.md diff --git a/.trajectories/completed/2026-05/traj_sh2ahp9z2xg6.json b/.agentworkforce/trajectories/completed/2026-05/traj_sh2ahp9z2xg6.json similarity index 100% rename from .trajectories/completed/2026-05/traj_sh2ahp9z2xg6.json rename to .agentworkforce/trajectories/completed/2026-05/traj_sh2ahp9z2xg6.json diff --git a/.trajectories/completed/2026-05/traj_sh2ahp9z2xg6.md b/.agentworkforce/trajectories/completed/2026-05/traj_sh2ahp9z2xg6.md similarity index 100% rename from .trajectories/completed/2026-05/traj_sh2ahp9z2xg6.md rename to .agentworkforce/trajectories/completed/2026-05/traj_sh2ahp9z2xg6.md diff --git a/.trajectories/completed/2026-05/traj_sqerp89tc436.json b/.agentworkforce/trajectories/completed/2026-05/traj_sqerp89tc436.json similarity index 100% rename from .trajectories/completed/2026-05/traj_sqerp89tc436.json rename to .agentworkforce/trajectories/completed/2026-05/traj_sqerp89tc436.json diff --git a/.trajectories/completed/2026-05/traj_sqerp89tc436.md b/.agentworkforce/trajectories/completed/2026-05/traj_sqerp89tc436.md similarity index 100% rename from .trajectories/completed/2026-05/traj_sqerp89tc436.md rename to .agentworkforce/trajectories/completed/2026-05/traj_sqerp89tc436.md diff --git a/.trajectories/completed/2026-05/traj_t5uknesn2fcw.json b/.agentworkforce/trajectories/completed/2026-05/traj_t5uknesn2fcw.json similarity index 100% rename from .trajectories/completed/2026-05/traj_t5uknesn2fcw.json rename to .agentworkforce/trajectories/completed/2026-05/traj_t5uknesn2fcw.json diff --git a/.trajectories/completed/2026-05/traj_t5uknesn2fcw.md b/.agentworkforce/trajectories/completed/2026-05/traj_t5uknesn2fcw.md similarity index 100% rename from .trajectories/completed/2026-05/traj_t5uknesn2fcw.md rename to .agentworkforce/trajectories/completed/2026-05/traj_t5uknesn2fcw.md diff --git a/.trajectories/completed/2026-05/traj_t6h534vn0bpg.json b/.agentworkforce/trajectories/completed/2026-05/traj_t6h534vn0bpg.json similarity index 100% rename from .trajectories/completed/2026-05/traj_t6h534vn0bpg.json rename to .agentworkforce/trajectories/completed/2026-05/traj_t6h534vn0bpg.json diff --git a/.trajectories/completed/2026-05/traj_t6h534vn0bpg.md b/.agentworkforce/trajectories/completed/2026-05/traj_t6h534vn0bpg.md similarity index 100% rename from .trajectories/completed/2026-05/traj_t6h534vn0bpg.md rename to .agentworkforce/trajectories/completed/2026-05/traj_t6h534vn0bpg.md diff --git a/.trajectories/completed/2026-05/traj_tavtex0db4b0.json b/.agentworkforce/trajectories/completed/2026-05/traj_tavtex0db4b0.json similarity index 100% rename from .trajectories/completed/2026-05/traj_tavtex0db4b0.json rename to .agentworkforce/trajectories/completed/2026-05/traj_tavtex0db4b0.json diff --git a/.trajectories/completed/2026-05/traj_tavtex0db4b0.md b/.agentworkforce/trajectories/completed/2026-05/traj_tavtex0db4b0.md similarity index 100% rename from .trajectories/completed/2026-05/traj_tavtex0db4b0.md rename to .agentworkforce/trajectories/completed/2026-05/traj_tavtex0db4b0.md diff --git a/.trajectories/completed/2026-05/traj_tgism98me5na.json b/.agentworkforce/trajectories/completed/2026-05/traj_tgism98me5na.json similarity index 100% rename from .trajectories/completed/2026-05/traj_tgism98me5na.json rename to .agentworkforce/trajectories/completed/2026-05/traj_tgism98me5na.json diff --git a/.trajectories/completed/2026-05/traj_tgism98me5na.md b/.agentworkforce/trajectories/completed/2026-05/traj_tgism98me5na.md similarity index 100% rename from .trajectories/completed/2026-05/traj_tgism98me5na.md rename to .agentworkforce/trajectories/completed/2026-05/traj_tgism98me5na.md diff --git a/.trajectories/completed/2026-05/traj_u33qn99ijbh4.json b/.agentworkforce/trajectories/completed/2026-05/traj_u33qn99ijbh4.json similarity index 100% rename from .trajectories/completed/2026-05/traj_u33qn99ijbh4.json rename to .agentworkforce/trajectories/completed/2026-05/traj_u33qn99ijbh4.json diff --git a/.trajectories/completed/2026-05/traj_u33qn99ijbh4.md b/.agentworkforce/trajectories/completed/2026-05/traj_u33qn99ijbh4.md similarity index 100% rename from .trajectories/completed/2026-05/traj_u33qn99ijbh4.md rename to .agentworkforce/trajectories/completed/2026-05/traj_u33qn99ijbh4.md diff --git a/.trajectories/completed/2026-05/traj_u3loicehnwb4.json b/.agentworkforce/trajectories/completed/2026-05/traj_u3loicehnwb4.json similarity index 100% rename from .trajectories/completed/2026-05/traj_u3loicehnwb4.json rename to .agentworkforce/trajectories/completed/2026-05/traj_u3loicehnwb4.json diff --git a/.trajectories/completed/2026-05/traj_u3loicehnwb4.md b/.agentworkforce/trajectories/completed/2026-05/traj_u3loicehnwb4.md similarity index 100% rename from .trajectories/completed/2026-05/traj_u3loicehnwb4.md rename to .agentworkforce/trajectories/completed/2026-05/traj_u3loicehnwb4.md diff --git a/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json b/.agentworkforce/trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json similarity index 100% rename from .trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json rename to .agentworkforce/trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json diff --git a/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md b/.agentworkforce/trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md similarity index 100% rename from .trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md rename to .agentworkforce/trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md diff --git a/.trajectories/completed/2026-05/traj_uf8y40ewrfh0.json b/.agentworkforce/trajectories/completed/2026-05/traj_uf8y40ewrfh0.json similarity index 100% rename from .trajectories/completed/2026-05/traj_uf8y40ewrfh0.json rename to .agentworkforce/trajectories/completed/2026-05/traj_uf8y40ewrfh0.json diff --git a/.trajectories/completed/2026-05/traj_uf8y40ewrfh0.md b/.agentworkforce/trajectories/completed/2026-05/traj_uf8y40ewrfh0.md similarity index 100% rename from .trajectories/completed/2026-05/traj_uf8y40ewrfh0.md rename to .agentworkforce/trajectories/completed/2026-05/traj_uf8y40ewrfh0.md diff --git a/.trajectories/completed/2026-05/traj_v1wexlfur5zr.json b/.agentworkforce/trajectories/completed/2026-05/traj_v1wexlfur5zr.json similarity index 100% rename from .trajectories/completed/2026-05/traj_v1wexlfur5zr.json rename to .agentworkforce/trajectories/completed/2026-05/traj_v1wexlfur5zr.json diff --git a/.trajectories/completed/2026-05/traj_v1wexlfur5zr.md b/.agentworkforce/trajectories/completed/2026-05/traj_v1wexlfur5zr.md similarity index 100% rename from .trajectories/completed/2026-05/traj_v1wexlfur5zr.md rename to .agentworkforce/trajectories/completed/2026-05/traj_v1wexlfur5zr.md diff --git a/.trajectories/completed/2026-05/traj_v87cyrs8dke9.json b/.agentworkforce/trajectories/completed/2026-05/traj_v87cyrs8dke9.json similarity index 100% rename from .trajectories/completed/2026-05/traj_v87cyrs8dke9.json rename to .agentworkforce/trajectories/completed/2026-05/traj_v87cyrs8dke9.json diff --git a/.trajectories/completed/2026-05/traj_v87cyrs8dke9.md b/.agentworkforce/trajectories/completed/2026-05/traj_v87cyrs8dke9.md similarity index 100% rename from .trajectories/completed/2026-05/traj_v87cyrs8dke9.md rename to .agentworkforce/trajectories/completed/2026-05/traj_v87cyrs8dke9.md diff --git a/.trajectories/completed/2026-05/traj_v87cyrs8dke9.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_v87cyrs8dke9.trace.json similarity index 100% rename from .trajectories/completed/2026-05/traj_v87cyrs8dke9.trace.json rename to .agentworkforce/trajectories/completed/2026-05/traj_v87cyrs8dke9.trace.json diff --git a/.trajectories/completed/2026-05/traj_v9x3o92ag682.json b/.agentworkforce/trajectories/completed/2026-05/traj_v9x3o92ag682.json similarity index 100% rename from .trajectories/completed/2026-05/traj_v9x3o92ag682.json rename to .agentworkforce/trajectories/completed/2026-05/traj_v9x3o92ag682.json diff --git a/.trajectories/completed/2026-05/traj_v9x3o92ag682.md b/.agentworkforce/trajectories/completed/2026-05/traj_v9x3o92ag682.md similarity index 100% rename from .trajectories/completed/2026-05/traj_v9x3o92ag682.md rename to .agentworkforce/trajectories/completed/2026-05/traj_v9x3o92ag682.md diff --git a/.trajectories/completed/2026-05/traj_vfa1jr6otnjn.json b/.agentworkforce/trajectories/completed/2026-05/traj_vfa1jr6otnjn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_vfa1jr6otnjn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_vfa1jr6otnjn.json diff --git a/.trajectories/completed/2026-05/traj_vfa1jr6otnjn.md b/.agentworkforce/trajectories/completed/2026-05/traj_vfa1jr6otnjn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_vfa1jr6otnjn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_vfa1jr6otnjn.md diff --git a/.trajectories/completed/2026-05/traj_vkozdglobkyg.json b/.agentworkforce/trajectories/completed/2026-05/traj_vkozdglobkyg.json similarity index 100% rename from .trajectories/completed/2026-05/traj_vkozdglobkyg.json rename to .agentworkforce/trajectories/completed/2026-05/traj_vkozdglobkyg.json diff --git a/.trajectories/completed/2026-05/traj_vkozdglobkyg.md b/.agentworkforce/trajectories/completed/2026-05/traj_vkozdglobkyg.md similarity index 100% rename from .trajectories/completed/2026-05/traj_vkozdglobkyg.md rename to .agentworkforce/trajectories/completed/2026-05/traj_vkozdglobkyg.md diff --git a/.trajectories/completed/2026-05/traj_wbn62q4cq16h.json b/.agentworkforce/trajectories/completed/2026-05/traj_wbn62q4cq16h.json similarity index 100% rename from .trajectories/completed/2026-05/traj_wbn62q4cq16h.json rename to .agentworkforce/trajectories/completed/2026-05/traj_wbn62q4cq16h.json diff --git a/.trajectories/completed/2026-05/traj_wbn62q4cq16h.md b/.agentworkforce/trajectories/completed/2026-05/traj_wbn62q4cq16h.md similarity index 100% rename from .trajectories/completed/2026-05/traj_wbn62q4cq16h.md rename to .agentworkforce/trajectories/completed/2026-05/traj_wbn62q4cq16h.md diff --git a/.trajectories/completed/2026-05/traj_whd40oxptlhn.json b/.agentworkforce/trajectories/completed/2026-05/traj_whd40oxptlhn.json similarity index 100% rename from .trajectories/completed/2026-05/traj_whd40oxptlhn.json rename to .agentworkforce/trajectories/completed/2026-05/traj_whd40oxptlhn.json diff --git a/.trajectories/completed/2026-05/traj_whd40oxptlhn.md b/.agentworkforce/trajectories/completed/2026-05/traj_whd40oxptlhn.md similarity index 100% rename from .trajectories/completed/2026-05/traj_whd40oxptlhn.md rename to .agentworkforce/trajectories/completed/2026-05/traj_whd40oxptlhn.md diff --git a/.trajectories/completed/2026-05/traj_whd40oxptlhn.trace.json b/.agentworkforce/trajectories/completed/2026-05/traj_whd40oxptlhn.trace.json similarity index 100% rename from .trajectories/completed/2026-05/traj_whd40oxptlhn.trace.json rename to .agentworkforce/trajectories/completed/2026-05/traj_whd40oxptlhn.trace.json diff --git a/.trajectories/completed/2026-05/traj_wx00tjvpptvg.json b/.agentworkforce/trajectories/completed/2026-05/traj_wx00tjvpptvg.json similarity index 100% rename from .trajectories/completed/2026-05/traj_wx00tjvpptvg.json rename to .agentworkforce/trajectories/completed/2026-05/traj_wx00tjvpptvg.json diff --git a/.trajectories/completed/2026-05/traj_wx00tjvpptvg.md b/.agentworkforce/trajectories/completed/2026-05/traj_wx00tjvpptvg.md similarity index 100% rename from .trajectories/completed/2026-05/traj_wx00tjvpptvg.md rename to .agentworkforce/trajectories/completed/2026-05/traj_wx00tjvpptvg.md diff --git a/.trajectories/completed/2026-05/traj_wzzboitm85ee.json b/.agentworkforce/trajectories/completed/2026-05/traj_wzzboitm85ee.json similarity index 100% rename from .trajectories/completed/2026-05/traj_wzzboitm85ee.json rename to .agentworkforce/trajectories/completed/2026-05/traj_wzzboitm85ee.json diff --git a/.trajectories/completed/2026-05/traj_wzzboitm85ee.md b/.agentworkforce/trajectories/completed/2026-05/traj_wzzboitm85ee.md similarity index 100% rename from .trajectories/completed/2026-05/traj_wzzboitm85ee.md rename to .agentworkforce/trajectories/completed/2026-05/traj_wzzboitm85ee.md diff --git a/.trajectories/completed/2026-05/traj_x37bhga2j5ph.json b/.agentworkforce/trajectories/completed/2026-05/traj_x37bhga2j5ph.json similarity index 100% rename from .trajectories/completed/2026-05/traj_x37bhga2j5ph.json rename to .agentworkforce/trajectories/completed/2026-05/traj_x37bhga2j5ph.json diff --git a/.trajectories/completed/2026-05/traj_x37bhga2j5ph.md b/.agentworkforce/trajectories/completed/2026-05/traj_x37bhga2j5ph.md similarity index 100% rename from .trajectories/completed/2026-05/traj_x37bhga2j5ph.md rename to .agentworkforce/trajectories/completed/2026-05/traj_x37bhga2j5ph.md diff --git a/.trajectories/completed/2026-05/traj_ybcrij9wg8m1.json b/.agentworkforce/trajectories/completed/2026-05/traj_ybcrij9wg8m1.json similarity index 100% rename from .trajectories/completed/2026-05/traj_ybcrij9wg8m1.json rename to .agentworkforce/trajectories/completed/2026-05/traj_ybcrij9wg8m1.json diff --git a/.trajectories/completed/2026-05/traj_ybcrij9wg8m1.md b/.agentworkforce/trajectories/completed/2026-05/traj_ybcrij9wg8m1.md similarity index 100% rename from .trajectories/completed/2026-05/traj_ybcrij9wg8m1.md rename to .agentworkforce/trajectories/completed/2026-05/traj_ybcrij9wg8m1.md diff --git a/.trajectories/completed/2026-05/traj_z171lng2fbbi.json b/.agentworkforce/trajectories/completed/2026-05/traj_z171lng2fbbi.json similarity index 100% rename from .trajectories/completed/2026-05/traj_z171lng2fbbi.json rename to .agentworkforce/trajectories/completed/2026-05/traj_z171lng2fbbi.json diff --git a/.trajectories/completed/2026-05/traj_z171lng2fbbi.md b/.agentworkforce/trajectories/completed/2026-05/traj_z171lng2fbbi.md similarity index 100% rename from .trajectories/completed/2026-05/traj_z171lng2fbbi.md rename to .agentworkforce/trajectories/completed/2026-05/traj_z171lng2fbbi.md diff --git a/.trajectories/completed/2026-05/traj_zfa6skfr32vy.json b/.agentworkforce/trajectories/completed/2026-05/traj_zfa6skfr32vy.json similarity index 100% rename from .trajectories/completed/2026-05/traj_zfa6skfr32vy.json rename to .agentworkforce/trajectories/completed/2026-05/traj_zfa6skfr32vy.json diff --git a/.trajectories/completed/2026-05/traj_zfa6skfr32vy.md b/.agentworkforce/trajectories/completed/2026-05/traj_zfa6skfr32vy.md similarity index 100% rename from .trajectories/completed/2026-05/traj_zfa6skfr32vy.md rename to .agentworkforce/trajectories/completed/2026-05/traj_zfa6skfr32vy.md diff --git a/.trajectories/completed/2026-05/traj_zqwco4gl76g3.json b/.agentworkforce/trajectories/completed/2026-05/traj_zqwco4gl76g3.json similarity index 100% rename from .trajectories/completed/2026-05/traj_zqwco4gl76g3.json rename to .agentworkforce/trajectories/completed/2026-05/traj_zqwco4gl76g3.json diff --git a/.trajectories/completed/2026-05/traj_zqwco4gl76g3.md b/.agentworkforce/trajectories/completed/2026-05/traj_zqwco4gl76g3.md similarity index 100% rename from .trajectories/completed/2026-05/traj_zqwco4gl76g3.md rename to .agentworkforce/trajectories/completed/2026-05/traj_zqwco4gl76g3.md diff --git a/.trajectories/completed/2026-05/traj_zu3252hxzoqh.json b/.agentworkforce/trajectories/completed/2026-05/traj_zu3252hxzoqh.json similarity index 100% rename from .trajectories/completed/2026-05/traj_zu3252hxzoqh.json rename to .agentworkforce/trajectories/completed/2026-05/traj_zu3252hxzoqh.json diff --git a/.trajectories/completed/2026-05/traj_zu3252hxzoqh.md b/.agentworkforce/trajectories/completed/2026-05/traj_zu3252hxzoqh.md similarity index 100% rename from .trajectories/completed/2026-05/traj_zu3252hxzoqh.md rename to .agentworkforce/trajectories/completed/2026-05/traj_zu3252hxzoqh.md diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_zyluvmlqo5j7.json b/.agentworkforce/trajectories/completed/2026-05/traj_zyluvmlqo5j7.json new file mode 100644 index 000000000..d2924c639 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_zyluvmlqo5j7.json @@ -0,0 +1,65 @@ +{ + "id": "traj_zyluvmlqo5j7", + "version": 1, + "task": { + "title": "Resolve harness PR merge conflicts" + }, + "status": "completed", + "startedAt": "2026-05-26T11:24:20.682Z", + "completedAt": "2026-05-26T11:26:52.563Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-26T11:24:25.518Z" + } + ], + "chapters": [ + { + "id": "chap_jq24mv749pnp", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-26T11:24:25.518Z", + "endedAt": "2026-05-26T11:26:52.563Z", + "events": [ + { + "ts": 1779794665519, + "type": "decision", + "content": "Resolved SDK conflict by retaining ensureAgentHandle metadata flow: Resolved SDK conflict by retaining ensureAgentHandle metadata flow", + "raw": { + "question": "Resolved SDK conflict by retaining ensureAgentHandle metadata flow", + "chosen": "Resolved SDK conflict by retaining ensureAgentHandle metadata flow", + "alternatives": [], + "reasoning": "The harness PR added pid/session metadata on spawned agents, so keeping the new result-aware handle creation preserves that behavior after merging main." + }, + "significance": "high" + }, + { + "ts": 1779794805873, + "type": "decision", + "content": "Validated conflict resolution with SDK typecheck and broker cargo check: Validated conflict resolution with SDK typecheck and broker cargo check", + "raw": { + "question": "Validated conflict resolution with SDK typecheck and broker cargo check", + "chosen": "Validated conflict resolution with SDK typecheck and broker cargo check", + "alternatives": [], + "reasoning": "The merge touched SDK and broker surfaces, so both checks cover the manually resolved TypeScript path and the incoming Rust changes." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Merged origin/main into the harness runtime plans PR, resolved conflicts in package-lock.json, packages/sdk/src/relay.ts, and .trajectories/index.json, then validated with SDK typecheck and broker cargo check.", + "approach": "Standard approach", + "confidence": 0.86 + }, + "commits": [], + "filesChanged": [], + "projectId": "repo:relay", + "tags": [], + "_trace": { + "startRef": "9061a034dca4763de6957ea4b74974fab99e63ce", + "endRef": "9061a034dca4763de6957ea4b74974fab99e63ce" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-05/traj_zyluvmlqo5j7.md b/.agentworkforce/trajectories/completed/2026-05/traj_zyluvmlqo5j7.md new file mode 100644 index 000000000..7ab83e4e9 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-05/traj_zyluvmlqo5j7.md @@ -0,0 +1,39 @@ +# Trajectory: Resolve harness PR merge conflicts + +> **Status:** ✅ Completed +> **Confidence:** 86% +> **Started:** May 26, 2026 at 07:24 AM +> **Completed:** May 26, 2026 at 07:26 AM + +--- + +## Summary + +Merged origin/main into the harness runtime plans PR, resolved conflicts in package-lock.json, packages/sdk/src/relay.ts, and .trajectories/index.json, then validated with SDK typecheck and broker cargo check. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Resolved SDK conflict by retaining ensureAgentHandle metadata flow + +- **Chose:** Resolved SDK conflict by retaining ensureAgentHandle metadata flow +- **Reasoning:** The harness PR added pid/session metadata on spawned agents, so keeping the new result-aware handle creation preserves that behavior after merging main. + +### Validated conflict resolution with SDK typecheck and broker cargo check + +- **Chose:** Validated conflict resolution with SDK typecheck and broker cargo check +- **Reasoning:** The merge touched SDK and broker surfaces, so both checks cover the manually resolved TypeScript path and the incoming Rust changes. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Resolved SDK conflict by retaining ensureAgentHandle metadata flow: Resolved SDK conflict by retaining ensureAgentHandle metadata flow +- Validated conflict resolution with SDK typecheck and broker cargo check: Validated conflict resolution with SDK typecheck and broker cargo check diff --git a/.trajectories/completed/traj_1775914296101_a4397efe.json b/.agentworkforce/trajectories/completed/traj_1775914296101_a4397efe.json similarity index 100% rename from .trajectories/completed/traj_1775914296101_a4397efe.json rename to .agentworkforce/trajectories/completed/traj_1775914296101_a4397efe.json diff --git a/.trajectories/completed/traj_1776024661304_cfc829b9.json b/.agentworkforce/trajectories/completed/traj_1776024661304_cfc829b9.json similarity index 100% rename from .trajectories/completed/traj_1776024661304_cfc829b9.json rename to .agentworkforce/trajectories/completed/traj_1776024661304_cfc829b9.json diff --git a/.trajectories/completed/traj_1776105620545_9dcebb3d.json b/.agentworkforce/trajectories/completed/traj_1776105620545_9dcebb3d.json similarity index 100% rename from .trajectories/completed/traj_1776105620545_9dcebb3d.json rename to .agentworkforce/trajectories/completed/traj_1776105620545_9dcebb3d.json diff --git a/.trajectories/completed/traj_1778873052429_03a4dacb.json b/.agentworkforce/trajectories/completed/traj_1778873052429_03a4dacb.json similarity index 100% rename from .trajectories/completed/traj_1778873052429_03a4dacb.json rename to .agentworkforce/trajectories/completed/traj_1778873052429_03a4dacb.json diff --git a/.trajectories/completed/traj_1778873197540_01102ade.json b/.agentworkforce/trajectories/completed/traj_1778873197540_01102ade.json similarity index 100% rename from .trajectories/completed/traj_1778873197540_01102ade.json rename to .agentworkforce/trajectories/completed/traj_1778873197540_01102ade.json diff --git a/.trajectories/completed/traj_1778873199489_f2ce4060.json b/.agentworkforce/trajectories/completed/traj_1778873199489_f2ce4060.json similarity index 100% rename from .trajectories/completed/traj_1778873199489_f2ce4060.json rename to .agentworkforce/trajectories/completed/traj_1778873199489_f2ce4060.json diff --git a/.trajectories/completed/traj_1778873201502_0dacf7c5.json b/.agentworkforce/trajectories/completed/traj_1778873201502_0dacf7c5.json similarity index 100% rename from .trajectories/completed/traj_1778873201502_0dacf7c5.json rename to .agentworkforce/trajectories/completed/traj_1778873201502_0dacf7c5.json diff --git a/.trajectories/completed/traj_1778873203502_4c225b7e.json b/.agentworkforce/trajectories/completed/traj_1778873203502_4c225b7e.json similarity index 100% rename from .trajectories/completed/traj_1778873203502_4c225b7e.json rename to .agentworkforce/trajectories/completed/traj_1778873203502_4c225b7e.json diff --git a/.trajectories/completed/traj_1778873205470_a4e5f0cb.json b/.agentworkforce/trajectories/completed/traj_1778873205470_a4e5f0cb.json similarity index 100% rename from .trajectories/completed/traj_1778873205470_a4e5f0cb.json rename to .agentworkforce/trajectories/completed/traj_1778873205470_a4e5f0cb.json diff --git a/.trajectories/completed/traj_1778873207471_b7def991.json b/.agentworkforce/trajectories/completed/traj_1778873207471_b7def991.json similarity index 100% rename from .trajectories/completed/traj_1778873207471_b7def991.json rename to .agentworkforce/trajectories/completed/traj_1778873207471_b7def991.json diff --git a/.trajectories/completed/traj_1778873209642_c70e32ab.json b/.agentworkforce/trajectories/completed/traj_1778873209642_c70e32ab.json similarity index 100% rename from .trajectories/completed/traj_1778873209642_c70e32ab.json rename to .agentworkforce/trajectories/completed/traj_1778873209642_c70e32ab.json diff --git a/.trajectories/completed/traj_1778873211616_6db3b2cd.json b/.agentworkforce/trajectories/completed/traj_1778873211616_6db3b2cd.json similarity index 100% rename from .trajectories/completed/traj_1778873211616_6db3b2cd.json rename to .agentworkforce/trajectories/completed/traj_1778873211616_6db3b2cd.json diff --git a/.trajectories/completed/traj_1778874205797_81e92307.json b/.agentworkforce/trajectories/completed/traj_1778874205797_81e92307.json similarity index 100% rename from .trajectories/completed/traj_1778874205797_81e92307.json rename to .agentworkforce/trajectories/completed/traj_1778874205797_81e92307.json diff --git a/.trajectories/completed/traj_1778874216773_c6b12ab2.json b/.agentworkforce/trajectories/completed/traj_1778874216773_c6b12ab2.json similarity index 100% rename from .trajectories/completed/traj_1778874216773_c6b12ab2.json rename to .agentworkforce/trajectories/completed/traj_1778874216773_c6b12ab2.json diff --git a/.trajectories/completed/traj_1778874218579_a0225559.json b/.agentworkforce/trajectories/completed/traj_1778874218579_a0225559.json similarity index 100% rename from .trajectories/completed/traj_1778874218579_a0225559.json rename to .agentworkforce/trajectories/completed/traj_1778874218579_a0225559.json diff --git a/.trajectories/completed/traj_1778874224855_9c722c4b.json b/.agentworkforce/trajectories/completed/traj_1778874224855_9c722c4b.json similarity index 100% rename from .trajectories/completed/traj_1778874224855_9c722c4b.json rename to .agentworkforce/trajectories/completed/traj_1778874224855_9c722c4b.json diff --git a/.trajectories/completed/traj_1778874226983_3367d527.json b/.agentworkforce/trajectories/completed/traj_1778874226983_3367d527.json similarity index 100% rename from .trajectories/completed/traj_1778874226983_3367d527.json rename to .agentworkforce/trajectories/completed/traj_1778874226983_3367d527.json diff --git a/.trajectories/completed/traj_1778874229373_9cce9465.json b/.agentworkforce/trajectories/completed/traj_1778874229373_9cce9465.json similarity index 100% rename from .trajectories/completed/traj_1778874229373_9cce9465.json rename to .agentworkforce/trajectories/completed/traj_1778874229373_9cce9465.json diff --git a/.trajectories/completed/traj_1778874240339_51b823cd.json b/.agentworkforce/trajectories/completed/traj_1778874240339_51b823cd.json similarity index 100% rename from .trajectories/completed/traj_1778874240339_51b823cd.json rename to .agentworkforce/trajectories/completed/traj_1778874240339_51b823cd.json diff --git a/.trajectories/completed/traj_1778874241076_caa675a9.json b/.agentworkforce/trajectories/completed/traj_1778874241076_caa675a9.json similarity index 100% rename from .trajectories/completed/traj_1778874241076_caa675a9.json rename to .agentworkforce/trajectories/completed/traj_1778874241076_caa675a9.json diff --git a/.trajectories/completed/traj_1778874248966_e29c4c54.json b/.agentworkforce/trajectories/completed/traj_1778874248966_e29c4c54.json similarity index 100% rename from .trajectories/completed/traj_1778874248966_e29c4c54.json rename to .agentworkforce/trajectories/completed/traj_1778874248966_e29c4c54.json diff --git a/.trajectories/completed/traj_1778874249983_12a98df3.json b/.agentworkforce/trajectories/completed/traj_1778874249983_12a98df3.json similarity index 100% rename from .trajectories/completed/traj_1778874249983_12a98df3.json rename to .agentworkforce/trajectories/completed/traj_1778874249983_12a98df3.json diff --git a/.trajectories/completed/traj_1778874258229_0bdc53d8.json b/.agentworkforce/trajectories/completed/traj_1778874258229_0bdc53d8.json similarity index 100% rename from .trajectories/completed/traj_1778874258229_0bdc53d8.json rename to .agentworkforce/trajectories/completed/traj_1778874258229_0bdc53d8.json diff --git a/.trajectories/completed/traj_1778874261453_55f49624.json b/.agentworkforce/trajectories/completed/traj_1778874261453_55f49624.json similarity index 100% rename from .trajectories/completed/traj_1778874261453_55f49624.json rename to .agentworkforce/trajectories/completed/traj_1778874261453_55f49624.json diff --git a/.trajectories/completed/traj_1778874261608_48fb9bf5.json b/.agentworkforce/trajectories/completed/traj_1778874261608_48fb9bf5.json similarity index 100% rename from .trajectories/completed/traj_1778874261608_48fb9bf5.json rename to .agentworkforce/trajectories/completed/traj_1778874261608_48fb9bf5.json diff --git a/.trajectories/completed/traj_1778874269139_d7d7485a.json b/.agentworkforce/trajectories/completed/traj_1778874269139_d7d7485a.json similarity index 100% rename from .trajectories/completed/traj_1778874269139_d7d7485a.json rename to .agentworkforce/trajectories/completed/traj_1778874269139_d7d7485a.json diff --git a/.trajectories/completed/traj_1778874274412_70843e0e.json b/.agentworkforce/trajectories/completed/traj_1778874274412_70843e0e.json similarity index 100% rename from .trajectories/completed/traj_1778874274412_70843e0e.json rename to .agentworkforce/trajectories/completed/traj_1778874274412_70843e0e.json diff --git a/.trajectories/completed/traj_1778874274581_71efa470.json b/.agentworkforce/trajectories/completed/traj_1778874274581_71efa470.json similarity index 100% rename from .trajectories/completed/traj_1778874274581_71efa470.json rename to .agentworkforce/trajectories/completed/traj_1778874274581_71efa470.json diff --git a/.trajectories/completed/traj_1778874282200_39ad11db.json b/.agentworkforce/trajectories/completed/traj_1778874282200_39ad11db.json similarity index 100% rename from .trajectories/completed/traj_1778874282200_39ad11db.json rename to .agentworkforce/trajectories/completed/traj_1778874282200_39ad11db.json diff --git a/.trajectories/completed/traj_1778874283570_ce3585b8.json b/.agentworkforce/trajectories/completed/traj_1778874283570_ce3585b8.json similarity index 100% rename from .trajectories/completed/traj_1778874283570_ce3585b8.json rename to .agentworkforce/trajectories/completed/traj_1778874283570_ce3585b8.json diff --git a/.trajectories/completed/traj_1778874289674_e3f868c8.json b/.agentworkforce/trajectories/completed/traj_1778874289674_e3f868c8.json similarity index 100% rename from .trajectories/completed/traj_1778874289674_e3f868c8.json rename to .agentworkforce/trajectories/completed/traj_1778874289674_e3f868c8.json diff --git a/.trajectories/completed/traj_1778874291950_0b1b5c1f.json b/.agentworkforce/trajectories/completed/traj_1778874291950_0b1b5c1f.json similarity index 100% rename from .trajectories/completed/traj_1778874291950_0b1b5c1f.json rename to .agentworkforce/trajectories/completed/traj_1778874291950_0b1b5c1f.json diff --git a/.trajectories/completed/traj_1778874295927_4083d181.json b/.agentworkforce/trajectories/completed/traj_1778874295927_4083d181.json similarity index 100% rename from .trajectories/completed/traj_1778874295927_4083d181.json rename to .agentworkforce/trajectories/completed/traj_1778874295927_4083d181.json diff --git a/.trajectories/completed/traj_1778874296362_bdf727ff.json b/.agentworkforce/trajectories/completed/traj_1778874296362_bdf727ff.json similarity index 100% rename from .trajectories/completed/traj_1778874296362_bdf727ff.json rename to .agentworkforce/trajectories/completed/traj_1778874296362_bdf727ff.json diff --git a/.trajectories/index.json b/.trajectories/index.json deleted file mode 100644 index 33349b869..000000000 --- a/.trajectories/index.json +++ /dev/null @@ -1,1168 +0,0 @@ -{ - "version": 1, - "lastUpdated": "2026-05-26T13:45:48.966Z", - "trajectories": { - "traj_05xg7j388bc4": { - "title": "Add browser workflow step integration", - "status": "completed", - "startedAt": "2026-04-10T14:56:33.229Z", - "completedAt": "2026-04-10T15:05:14.660Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_05xg7j388bc4.json" - }, - "traj_0t92gxaz6igh": { - "title": "Move docs sidebar into the mobile hamburger menu", - "status": "completed", - "startedAt": "2026-04-10T16:29:40.674Z", - "completedAt": "2026-04-10T16:32:14.544Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_0t92gxaz6igh.json" - }, - "traj_1776105620545_9dcebb3d": { - "title": "fix-inbox-agent-flag-workflow", - "status": "completed", - "startedAt": "2026-04-13T18:40:20.545Z", - "completedAt": "2026-04-13T18:41:52.831Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_1776105620545_9dcebb3d.json" - }, - "traj_1776105988184_29f1270c": { - "title": "fix-inbox-agent-flag-workflow", - "status": "completed", - "startedAt": "2026-04-13T18:46:28.184Z", - "completedAt": "2026-04-13T20:23:54.308Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_1776105988184_29f1270c.json" - }, - "traj_222ha5671idc": { - "title": "validate-cloud-connect-e2e-workflow", - "status": "completed", - "startedAt": "2026-04-15T21:32:51.980Z", - "completedAt": "2026-04-15T21:45:41.024Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_222ha5671idc.json" - }, - "traj_3b3p1z4y7qlo": { - "title": "add-mcp-args-subcommand-workflow", - "status": "completed", - "startedAt": "2026-04-20T13:16:22.009Z", - "completedAt": "2026-04-20T13:26:35.142Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json" - }, - "traj_4zqhfqw7g28l": { - "title": "Investigate GitHub Actions failure for run 24255758219 job 70826792063", - "status": "completed", - "startedAt": "2026-04-10T17:48:33.502Z", - "completedAt": "2026-04-10T17:49:14.485Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_4zqhfqw7g28l.json" - }, - "traj_530xmbfeljyb": { - "title": "Implement GitHub primitive adapter base layer", - "status": "completed", - "startedAt": "2026-04-10T15:16:25.682Z", - "completedAt": "2026-04-10T15:25:16.937Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_530xmbfeljyb.json" - }, - "traj_703m7sqyq89t": { - "title": "Fix production docs loader using build-machine absolute MDX paths", - "status": "completed", - "startedAt": "2026-04-10T16:33:10.601Z", - "completedAt": "2026-04-10T16:35:33.660Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_703m7sqyq89t.json" - }, - "traj_8oh4r5km5eic": { - "title": "Implement GitHub primitive actions", - "status": "completed", - "startedAt": "2026-04-10T15:26:11.355Z", - "completedAt": "2026-04-10T15:33:35.150Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_8oh4r5km5eic.json" - }, - "traj_9tt55is74dq5": { - "title": "Pin TypeScript build resolution for acp-bridge, memory, trajectory, and cloud", - "status": "completed", - "startedAt": "2026-04-11T13:34:46.304Z", - "completedAt": "2026-04-11T13:35:22.677Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_9tt55is74dq5.json" - }, - "traj_abjovknvcijv": { - "title": "Scope CI so web-only changes do not run unrelated relay and SDK tests", - "status": "completed", - "startedAt": "2026-04-10T16:08:30.070Z", - "completedAt": "2026-04-10T16:11:08.673Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_abjovknvcijv.json" - }, - "traj_avmkyoo2s3rt": { - "title": "Implement Browser primitive client", - "status": "completed", - "startedAt": "2026-04-10T14:42:17.242Z", - "completedAt": "2026-04-10T14:55:45.196Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_avmkyoo2s3rt.json" - }, - "traj_d48czxmgx4ac": { - "title": "Shift dark-mode footer from black to Relay blue", - "status": "completed", - "startedAt": "2026-04-10T16:12:27.477Z", - "completedAt": "2026-04-10T16:13:14.348Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_d48czxmgx4ac.json" - }, - "traj_dw8ihhdb8ip7": { - "title": "fix-dm-history-workflow", - "status": "abandoned", - "startedAt": "2026-04-13T19:51:57.984Z", - "completedAt": "2026-04-13T19:57:27.195Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json" - }, - "traj_e5i62wdjx0jd": { - "title": "Plan autofix finding groups", - "status": "completed", - "startedAt": "2026-04-13T09:40:42.044Z", - "completedAt": "2026-04-13T09:41:07.789Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_e5i62wdjx0jd.json" - }, - "traj_g3muawdq6bsb": { - "title": "Invert dark footer gradient so the lighter blue is at the top", - "status": "completed", - "startedAt": "2026-04-10T16:13:19.744Z", - "completedAt": "2026-04-10T16:13:43.289Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_g3muawdq6bsb.json" - }, - "traj_mk0t0cgn4ytq": { - "title": "Merge origin/main into better-nav and resolve trajectory conflicts", - "status": "completed", - "startedAt": "2026-04-10T15:10:03.877Z", - "completedAt": "2026-04-10T15:10:29.410Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json" - }, - "traj_o8kgzhfu6jth": { - "title": "Add /cloud link to footer", - "status": "completed", - "startedAt": "2026-04-10T16:07:15.131Z", - "completedAt": "2026-04-10T16:07:42.930Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_o8kgzhfu6jth.json" - }, - "traj_qb54w47qwod6": { - "title": "fix-history-from-workflow", - "status": "completed", - "startedAt": "2026-04-13T20:16:10.459Z", - "completedAt": "2026-04-13T20:25:09.219Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_qb54w47qwod6.json" - }, - "traj_rs2bt3x0fqba": { - "title": "Compare failed web deploy to prior deploys and identify no-downtime fix", - "status": "completed", - "startedAt": "2026-04-10T17:50:43.088Z", - "completedAt": "2026-04-10T18:00:44.095Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_rs2bt3x0fqba.json" - }, - "traj_tjadoebpscps": { - "title": "fix-dm-history-workflow", - "status": "completed", - "startedAt": "2026-04-13T20:02:27.719Z", - "completedAt": "2026-04-13T20:02:35.662Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_tjadoebpscps.json" - }, - "traj_tv1x9pamkqad": { - "title": "Add GitHub primitive workflow step integration", - "status": "completed", - "startedAt": "2026-04-10T15:34:36.611Z", - "completedAt": "2026-04-10T15:42:17.590Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_tv1x9pamkqad.json" - }, - "traj_ui5omrgz819d": { - "title": "cloud-run-start-from-workflow", - "status": "completed", - "startedAt": "2026-04-27T20:00:33.269Z", - "completedAt": "2026-04-27T20:08:46.379Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_ui5omrgz819d.json" - }, - "traj_w0xpsaoxuiyw": { - "title": "Pin TypeScript compiler version and build invocation in selected packages", - "status": "completed", - "startedAt": "2026-04-11T13:35:52.600Z", - "completedAt": "2026-04-11T13:36:48.341Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json" - }, - "traj_0e8i20oitwvz": { - "title": "Final fresh-eyes review Codex GPT-5.5 fix", - "status": "completed", - "startedAt": "2026-05-15T12:46:11.342Z", - "completedAt": "2026-05-15T12:46:11.500Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_0e8i20oitwvz.json" - }, - "traj_0o6gb2wvk59t": { - "title": "Fresh end-to-end validation for headless readiness", - "status": "completed", - "startedAt": "2026-05-15T10:55:49.188Z", - "completedAt": "2026-05-15T11:11:52.324Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_0o6gb2wvk59t.json" - }, - "traj_0z98tkaigaxg": { - "title": "ricky-child-update-issue-860-transcript-test-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:06:58.558Z", - "completedAt": "2026-05-15T21:35:56.724Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_0z98tkaigaxg.json" - }, - "traj_1775914133873_35667beb": { - "title": "fix-sdk-build-resolution-workflow", - "status": "completed", - "startedAt": "2026-04-11T13:28:53.873Z", - "completedAt": "2026-05-08T13:33:48.161Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_1775914133873_35667beb.json" - }, - "traj_1776073106646_1839be2d": { - "title": "autofix-swarm-Agentworkforce-relay-workflow", - "status": "completed", - "startedAt": "2026-04-13T09:38:26.646Z", - "completedAt": "2026-05-08T13:33:45.944Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_1776073106646_1839be2d.json" - }, - "traj_1776113772922_bc92f121": { - "title": "autofix-swarm-Agentworkforce-relay-workflow", - "status": "completed", - "startedAt": "2026-04-13T20:56:12.922Z", - "completedAt": "2026-05-08T13:33:43.489Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_1776113772922_bc92f121.json" - }, - "traj_1778873209642_c70e32ab": { - "title": "ricky-child-update-2-workflow", - "status": "completed", - "startedAt": "2026-05-15T19:26:49.642Z", - "completedAt": "2026-05-15T19:36:18.257Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_1778873209642_c70e32ab.json" - }, - "traj_1778873211616_6db3b2cd": { - "title": "ricky-child-update-docs-sync-workflow", - "status": "completed", - "startedAt": "2026-05-15T19:26:51.616Z", - "completedAt": "2026-05-15T19:35:07.059Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_1778873211616_6db3b2cd.json" - }, - "traj_1rrpe2r7fyem": { - "title": "Use streaming PTY input in CLI drive", - "status": "completed", - "startedAt": "2026-05-20T07:20:33.009Z", - "completedAt": "2026-05-20T07:26:17.567Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_1rrpe2r7fyem.json" - }, - "traj_2gpglosdsq7s": { - "title": "Fix broker session read paths and agent listing errors", - "status": "completed", - "startedAt": "2026-05-19T12:37:18.367Z", - "completedAt": "2026-05-19T12:48:50.116Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_2gpglosdsq7s.json" - }, - "traj_2tqxnib25omk": { - "title": "Add workflow reliability contract coverage", - "status": "completed", - "startedAt": "2026-05-08T15:27:50.875Z", - "completedAt": "2026-05-08T15:28:02.639Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_2tqxnib25omk.json" - }, - "traj_2yicjxgajt0a": { - "title": "Review GPT-5.5 hardening", - "status": "completed", - "startedAt": "2026-05-15T10:54:19.300Z", - "completedAt": "2026-05-15T10:54:19.476Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_2yicjxgajt0a.json" - }, - "traj_34b1u84b19gz": { - "title": "Address PR 827 review feedback", - "status": "completed", - "startedAt": "2026-05-08T18:29:34.717Z", - "completedAt": "2026-05-08T18:33:55.607Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_34b1u84b19gz.json" - }, - "traj_3gjtcykvybt5": { - "title": "Fix PR CI failures", - "status": "completed", - "startedAt": "2026-05-15T11:24:06.054Z", - "completedAt": "2026-05-15T11:25:49.087Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_3gjtcykvybt5.json" - }, - "traj_47akjihewlow": { - "title": "Further split broker runtime module for issue 875", - "status": "completed", - "startedAt": "2026-05-19T01:28:35.746Z", - "completedAt": "2026-05-19T01:38:29.105Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_47akjihewlow.json" - }, - "traj_4chzkm724ufo": { - "title": "Fix headless orchestrator worktree CLI E2E issues", - "status": "completed", - "startedAt": "2026-05-15T11:44:28.338Z", - "completedAt": "2026-05-15T11:51:05.319Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_4chzkm724ufo.json" - }, - "traj_4t07itef99ug": { - "title": "Implement relay CLI bootstrap commands for proactive runtime M1", - "status": "completed", - "startedAt": "2026-05-11T21:47:37.805Z", - "completedAt": "2026-05-11T21:49:49.859Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_4t07itef99ug.json" - }, - "traj_4vucir4qvqa2": { - "title": "Harden headless broker readiness semantics", - "status": "completed", - "startedAt": "2026-05-15T09:46:07.617Z", - "completedAt": "2026-05-15T09:59:00.460Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_4vucir4qvqa2.json" - }, - "traj_5k0jtc1g5l33": { - "title": "Resolve PR 932 conflicts and review comments", - "status": "completed", - "startedAt": "2026-05-22T19:13:09.359Z", - "completedAt": "2026-05-22T19:13:16.971Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_5k0jtc1g5l33.json" - }, - "traj_5nzj6v56id4z": { - "title": "Fix PR914 review comments", - "status": "completed", - "startedAt": "2026-05-19T13:20:42.407Z", - "completedAt": "2026-05-19T13:26:28.697Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_5nzj6v56id4z.json" - }, - "traj_5q8i0iz4klpo": { - "title": "ricky-child-update-2-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:07:02.452Z", - "completedAt": "2026-05-15T21:36:03.688Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_5q8i0iz4klpo.json" - }, - "traj_5qbla7w4kzoi": { - "title": "Fix issue 877", - "status": "completed", - "startedAt": "2026-05-19T03:40:40.798Z", - "completedAt": "2026-05-19T03:54:06.889Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_5qbla7w4kzoi.json" - }, - "traj_60qc24ufr96g": { - "title": "Expand workflow reliability contract matrix", - "status": "completed", - "startedAt": "2026-05-08T15:40:11.699Z", - "completedAt": "2026-05-08T15:40:22.521Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_60qc24ufr96g.json" - }, - "traj_6sjeohtm3php": { - "title": "Address broker headless reliability review findings", - "status": "completed", - "startedAt": "2026-05-15T09:30:56.316Z", - "completedAt": "2026-05-15T09:32:47.870Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_6sjeohtm3php.json" - }, - "traj_6ujzpx82gqs9": { - "title": "ricky-slack-primitive-implementation-workflow-status-r-workflow", - "status": "completed", - "startedAt": "2026-05-08T16:06:54.844Z", - "completedAt": "2026-05-08T16:18:16.119Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_6ujzpx82gqs9.json" - }, - "traj_78ytpicts778": { - "title": "Address PR 932 result callback review findings", - "status": "completed", - "startedAt": "2026-05-22T15:59:25.187Z", - "completedAt": "2026-05-22T16:05:12.320Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_78ytpicts778.json" - }, - "traj_7uznwzoxbao6": { - "title": "Fix standalone detached headless startup", - "status": "completed", - "startedAt": "2026-05-15T10:18:46.273Z", - "completedAt": "2026-05-15T10:25:00.598Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_7uznwzoxbao6.json" - }, - "traj_7zu7et53ph3l": { - "title": "Harden Codex GPT-5.5 local CLI compatibility", - "status": "completed", - "startedAt": "2026-05-15T10:35:25.212Z", - "completedAt": "2026-05-15T10:40:53.355Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_7zu7et53ph3l.json" - }, - "traj_81kobstnzzwk": { - "title": "Orchestrate team review cycle for #892 #893 #894 #895", - "status": "completed", - "startedAt": "2026-05-19T08:16:32.762Z", - "completedAt": "2026-05-19T08:37:32.966Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_81kobstnzzwk.json" - }, - "traj_8ljgydz61do5": { - "title": "ricky-child-update-messaging-2-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:06:54.217Z", - "completedAt": "2026-05-15T21:41:56.478Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_8ljgydz61do5.json" - }, - "traj_90jmd9z27oap": { - "title": "Resolve PR 948 merge conflicts", - "status": "completed", - "startedAt": "2026-05-22T19:49:08.797Z", - "completedAt": "2026-05-22T20:45:01.262Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_90jmd9z27oap.json" - }, - "traj_947wzpddsg9j": { - "title": "Implement relay CLI bootstrap commands for proactive runtime M1", - "status": "completed", - "startedAt": "2026-05-11T23:11:57.326Z", - "completedAt": "2026-05-11T23:14:23.136Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_947wzpddsg9j.json" - }, - "traj_9fdv7hxm0b60": { - "title": "Strict standalone smoke follow-up", - "status": "completed", - "startedAt": "2026-05-15T10:37:17.693Z", - "completedAt": "2026-05-15T10:43:11.587Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_9fdv7hxm0b60.json" - }, - "traj_9gq96irkj00s": { - "title": "Update relay to use published relaycast Rust reclaim fix", - "status": "completed", - "startedAt": "2026-05-10T18:45:02.118Z", - "completedAt": "2026-05-10T18:48:11.532Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_9gq96irkj00s.json" - }, - "traj_aw7stgf4qau0": { - "title": "Fix publish smoke cloud tarball dependency", - "status": "completed", - "startedAt": "2026-05-11T07:49:42.778Z", - "completedAt": "2026-05-11T07:50:37.848Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_aw7stgf4qau0.json" - }, - "traj_bdrlknyl8twj": { - "title": "Add workflow reliability defaults and E2E matrix", - "status": "completed", - "startedAt": "2026-05-08T17:54:45.069Z", - "completedAt": "2026-05-08T18:05:37.305Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_bdrlknyl8twj.json" - }, - "traj_bz1a1o15p7px": { - "title": "Fix PR 949 CI failure", - "status": "completed", - "startedAt": "2026-05-22T16:56:55.021Z", - "completedAt": "2026-05-22T16:57:55.372Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_bz1a1o15p7px.json" - }, - "traj_cbmwd07phhm2": { - "title": "Implement #869 snapshot module + dump-pty command", - "status": "completed", - "startedAt": "2026-05-17T14:19:10.603Z", - "completedAt": "2026-05-17T14:33:32.293Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_cbmwd07phhm2.json" - }, - "traj_ceo5q9bh2od3": { - "title": "Add structured spawned-agent results", - "status": "completed", - "startedAt": "2026-05-20T21:24:17.929Z", - "completedAt": "2026-05-20T21:43:51.936Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_ceo5q9bh2od3.json" - }, - "traj_d89s38ddu7cj": { - "title": "Review Codex GPT-5.5 spawn fix", - "status": "completed", - "startedAt": "2026-05-15T12:25:39.946Z", - "completedAt": "2026-05-15T12:25:40.708Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_d89s38ddu7cj.json" - }, - "traj_dbsnr453nxjw": { - "title": "Confirm and remove unused package dependencies", - "status": "completed", - "startedAt": "2026-05-22T16:01:11.967Z", - "completedAt": "2026-05-22T16:12:01.466Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_dbsnr453nxjw.json" - }, - "traj_dcl9hgoiuac5": { - "title": "Verify --broker-name override for agent-relay up", - "status": "completed", - "startedAt": "2026-05-21T18:56:55.532Z", - "completedAt": "2026-05-21T19:00:06.831Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_dcl9hgoiuac5.json" - }, - "traj_dpgn0am1jq1c": { - "title": "Implement M1 relay CLI bootstrap commands", - "status": "completed", - "startedAt": "2026-05-11T18:43:20.429Z", - "completedAt": "2026-05-11T18:43:20.733Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_dpgn0am1jq1c.json" - }, - "traj_e1b7ww3un1u3": { - "title": "Harden agents logs raw and follow output", - "status": "completed", - "startedAt": "2026-05-19T10:59:00.118Z", - "completedAt": "2026-05-19T11:04:44.466Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_e1b7ww3un1u3.json" - }, - "traj_elx0fcwgs37x": { - "title": "ricky-child-update-messaging-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:06:50.250Z", - "completedAt": "2026-05-15T21:46:46.167Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_elx0fcwgs37x.json" - }, - "traj_erzd7j9nto9r": { - "title": "Strict review and PR prep for headless broker readiness", - "status": "completed", - "startedAt": "2026-05-15T10:02:10.164Z", - "completedAt": "2026-05-15T10:06:38.127Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_erzd7j9nto9r.json" - }, - "traj_f1iac9ngymlj": { - "title": "Fix reliability review findings 892-895", - "status": "completed", - "startedAt": "2026-05-19T09:52:54.932Z", - "completedAt": "2026-05-19T10:01:19.068Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_f1iac9ngymlj.json" - }, - "traj_f3arvbmmlomn": { - "title": "Address PR feedback for headless broker reliability", - "status": "completed", - "startedAt": "2026-05-15T12:09:02.122Z", - "completedAt": "2026-05-15T12:15:11.435Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_f3arvbmmlomn.json" - }, - "traj_f9wxa8ujeg78": { - "title": "Refactor broker main for issue 875", - "status": "completed", - "startedAt": "2026-05-19T00:54:40.328Z", - "completedAt": "2026-05-19T00:55:57.506Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_f9wxa8ujeg78.json" - }, - "traj_fh8oosbijpwc": { - "title": "Track A: relaycast subscribe + @self DM routing", - "status": "completed", - "startedAt": "2026-05-12T06:28:56.427Z", - "completedAt": "2026-05-12T11:21:33.352Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_fh8oosbijpwc.json" - }, - "traj_gh05rj5gwsap": { - "title": "Bump relaycast Rust SDK in relay", - "status": "completed", - "startedAt": "2026-05-14T16:41:17.430Z", - "completedAt": "2026-05-14T16:42:32.485Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_gh05rj5gwsap.json" - }, - "traj_gnqvtoxtc8dy": { - "title": "Fix broker half-start recovery", - "status": "completed", - "startedAt": "2026-05-19T12:34:36.057Z", - "completedAt": "2026-05-19T12:47:18.115Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_gnqvtoxtc8dy.json" - }, - "traj_hfkww5z7trxn": { - "title": "Fresh comprehensive review of PR 856", - "status": "completed", - "startedAt": "2026-05-15T12:56:14.439Z", - "completedAt": "2026-05-15T13:00:15.366Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_hfkww5z7trxn.json" - }, - "traj_hrsndfzk0qay": { - "title": "Tighten Codex 5.5 fallback coverage", - "status": "completed", - "startedAt": "2026-05-15T10:57:41.681Z", - "completedAt": "2026-05-15T10:57:41.827Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_hrsndfzk0qay.json" - }, - "traj_hysw5o7idqas": { - "title": "Fix issue 924", - "status": "completed", - "startedAt": "2026-05-20T05:35:07.262Z", - "completedAt": "2026-05-20T05:47:54.189Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_hysw5o7idqas.json" - }, - "traj_ij5b3kcatvwn": { - "title": "ricky-reading-worker-dm-replies-design-spec-status-draft-date-2026-05-15-issue-860-hea-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:06:45.545Z", - "completedAt": "2026-05-15T21:50:07.842Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_ij5b3kcatvwn.json" - }, - "traj_iole5zdt9orr": { - "title": "Fix PR 831 CI conflicts", - "status": "completed", - "startedAt": "2026-05-10T15:18:12.326Z", - "completedAt": "2026-05-10T15:29:41.840Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_iole5zdt9orr.json" - }, - "traj_irafiyk6wpw0": { - "title": "Fix agents:logs near-unparseable TTY redraw garbage (codex implement, claude review)", - "status": "completed", - "startedAt": "2026-05-19T10:30:38.222Z", - "completedAt": "2026-05-19T10:44:24.757Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_irafiyk6wpw0.json" - }, - "traj_itgr2w8qs3xn": { - "title": "Make workflow deterministic failures repairable by agents", - "status": "completed", - "startedAt": "2026-05-08T14:44:45.732Z", - "completedAt": "2026-05-08T14:44:45.984Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_itgr2w8qs3xn.json" - }, - "traj_j9k10fez3e81": { - "title": "review-loop-mpb2bvnf-1-workflow", - "status": "completed", - "startedAt": "2026-05-18T10:30:09.927Z", - "completedAt": "2026-05-18T14:53:04.092Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_j9k10fez3e81.json" - }, - "traj_jbo2x14y7ovt": { - "title": "Fix issue 876", - "status": "completed", - "startedAt": "2026-05-19T02:48:10.768Z", - "completedAt": "2026-05-19T02:56:24.584Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_jbo2x14y7ovt.json" - }, - "traj_jmf9pyt3zikn": { - "title": "Fix issue 874", - "status": "completed", - "startedAt": "2026-05-19T00:07:13.993Z", - "completedAt": "2026-05-19T00:17:27.680Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_jmf9pyt3zikn.json" - }, - "traj_k7njijv51iq4": { - "title": "ricky-slack-primitive-implementation-workflow-status-r-workflow", - "status": "completed", - "startedAt": "2026-05-08T16:18:55.300Z", - "completedAt": "2026-05-08T16:26:03.266Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_k7njijv51iq4.json" - }, - "traj_lhyrcib40kao": { - "title": "Address PR #914 CodeRabbit reliability review findings", - "status": "completed", - "startedAt": "2026-05-19T11:52:46.110Z", - "completedAt": "2026-05-19T12:07:22.401Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_lhyrcib40kao.json" - }, - "traj_lieyyspidhfj": { - "title": "Fix PR 823 conflicts checks and comments", - "status": "completed", - "startedAt": "2026-05-09T08:37:17.563Z", - "completedAt": "2026-05-09T08:47:54.686Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_lieyyspidhfj.json" - }, - "traj_m7mpv7j8n78h": { - "title": "Address Relay PR 826 review feedback", - "status": "completed", - "startedAt": "2026-05-08T15:17:53.113Z", - "completedAt": "2026-05-08T15:24:32.409Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_m7mpv7j8n78h.json" - }, - "traj_mi9eqd4rjfea": { - "title": "Address stdio fresh review findings", - "status": "abandoned", - "startedAt": "2026-05-11T18:25:24.626Z", - "completedAt": "2026-05-11T18:37:05.318Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_mi9eqd4rjfea.json" - }, - "traj_mytnzgfayj3d": { - "title": "Fix PR 948 telemetry package validation failure", - "status": "completed", - "startedAt": "2026-05-22T23:34:27.422Z", - "completedAt": "2026-05-22T23:36:15.152Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_mytnzgfayj3d.json" - }, - "traj_mz5m5ysjj31e": { - "title": "Fix Relay SDK broker stdout drain", - "status": "completed", - "startedAt": "2026-05-10T20:24:43.831Z", - "completedAt": "2026-05-10T20:35:47.359Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_mz5m5ysjj31e.json" - }, - "traj_n8duofq5vq1a": { - "title": "Update Codex registry for GPT-5.5", - "status": "completed", - "startedAt": "2026-05-15T10:27:57.532Z", - "completedAt": "2026-05-15T10:33:19.705Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_n8duofq5vq1a.json" - }, - "traj_o251whkvy9rl": { - "title": "Fix Codex GPT-5.5 E2E rough edges", - "status": "completed", - "startedAt": "2026-05-15T11:59:26.764Z", - "completedAt": "2026-05-15T12:12:25.515Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_o251whkvy9rl.json" - }, - "traj_o9cx33xn5u39": { - "title": "add-mcp-args-register-flag-workflow", - "status": "completed", - "startedAt": "2026-04-20T15:06:23.387Z", - "completedAt": "2026-05-08T13:33:35.341Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_o9cx33xn5u39.json" - }, - "traj_ootb5rt3tozd": { - "title": "ricky-child-update-docs-sync-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:07:04.494Z", - "completedAt": "2026-05-15T21:45:20.368Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_ootb5rt3tozd.json" - }, - "traj_oyc528j7suvo": { - "title": "Expose cloud workflow scheduling through Relay SDK", - "status": "completed", - "startedAt": "2026-05-09T19:43:34.805Z", - "completedAt": "2026-05-09T19:44:00.107Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_oyc528j7suvo.json" - }, - "traj_piik8r6zu3i7": { - "title": "Issue 867: RelayEventListener", - "status": "completed", - "startedAt": "2026-05-18T01:56:18.236Z", - "completedAt": "2026-05-18T02:01:49.991Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_piik8r6zu3i7.json" - }, - "traj_pmrcfj6or3pz": { - "title": "Address runtime split review comments", - "status": "completed", - "startedAt": "2026-05-19T02:03:43.962Z", - "completedAt": "2026-05-19T02:09:31.002Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_pmrcfj6or3pz.json" - }, - "traj_qtmid2nzz0kz": { - "title": "Address PR 929 review comments", - "status": "completed", - "startedAt": "2026-05-20T06:21:54.721Z", - "completedAt": "2026-05-20T06:26:56.700Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_qtmid2nzz0kz.json" - }, - "traj_ryf5sstno6p3": { - "title": "Telemetry key from env (P0.5 of #881)", - "status": "completed", - "startedAt": "2026-05-18T02:56:55.314Z", - "completedAt": "2026-05-18T03:02:35.202Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_ryf5sstno6p3.json" - }, - "traj_s5ojo1f4srz4": { - "title": "Remove unused user-directory package", - "status": "completed", - "startedAt": "2026-05-22T16:27:44.927Z", - "completedAt": "2026-05-22T16:36:20.545Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_s5ojo1f4srz4.json" - }, - "traj_sh2ahp9z2xg6": { - "title": "Fix uuid install deprecation warning", - "status": "completed", - "startedAt": "2026-05-19T17:21:40.756Z", - "completedAt": "2026-05-19T17:21:49.702Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_sh2ahp9z2xg6.json" - }, - "traj_sqerp89tc436": { - "title": "Remove legacy root bin fallback", - "status": "completed", - "startedAt": "2026-05-19T00:45:33.159Z", - "completedAt": "2026-05-19T00:50:03.857Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_sqerp89tc436.json" - }, - "traj_t5uknesn2fcw": { - "title": "ricky-child-update-skill-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:06:52.453Z", - "completedAt": "2026-05-15T21:40:04.209Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_t5uknesn2fcw.json" - }, - "traj_tavtex0db4b0": { - "title": "Make workflow failures repairable by agents", - "status": "completed", - "startedAt": "2026-05-08T14:34:19.969Z", - "completedAt": "2026-05-08T14:44:45.719Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_tavtex0db4b0.json" - }, - "traj_tgism98me5na": { - "title": "Implement relay CLI proactive runtime bootstrap commands", - "status": "completed", - "startedAt": "2026-05-11T20:04:48.053Z", - "completedAt": "2026-05-11T20:05:54.956Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_tgism98me5na.json" - }, - "traj_u33qn99ijbh4": { - "title": "ricky-child-update-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:07:00.444Z", - "completedAt": "2026-05-15T21:30:50.445Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_u33qn99ijbh4.json" - }, - "traj_u3loicehnwb4": { - "title": "Gate broker diagnostic logs behind env flag", - "status": "completed", - "startedAt": "2026-05-21T04:14:44.815Z", - "completedAt": "2026-05-21T04:14:45.063Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_u3loicehnwb4.json" - }, - "traj_u4ixmbqqm2y1": { - "title": "Add cloud workflow schedule CLI", - "status": "completed", - "startedAt": "2026-05-09T19:26:42.106Z", - "completedAt": "2026-05-09T19:29:18.024Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json" - }, - "traj_uf8y40ewrfh0": { - "title": "Address PR 831 review feedback and conflicts", - "status": "completed", - "startedAt": "2026-05-09T19:59:24.197Z", - "completedAt": "2026-05-09T19:59:24.403Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_uf8y40ewrfh0.json" - }, - "traj_v1wexlfur5zr": { - "title": "Fix broker headless reliability doc", - "status": "completed", - "startedAt": "2026-05-15T09:04:51.316Z", - "completedAt": "2026-05-15T09:13:50.970Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_v1wexlfur5zr.json" - }, - "traj_v87cyrs8dke9": { - "title": "Refactor runDriveSession below complexity 15 (#897)", - "status": "completed", - "startedAt": "2026-05-14T14:28:34.155Z", - "completedAt": "2026-05-18T18:06:04.950Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_v87cyrs8dke9.json" - }, - "traj_v9x3o92ag682": { - "title": "ricky-child-update-messaging-test-workflow", - "status": "completed", - "startedAt": "2026-05-15T21:06:56.418Z", - "completedAt": "2026-05-15T21:34:35.623Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_v9x3o92ag682.json" - }, - "traj_vfa1jr6otnjn": { - "title": "Refresh PR 948 after main advanced", - "status": "completed", - "startedAt": "2026-05-22T23:23:16.807Z", - "completedAt": "2026-05-22T23:23:26.380Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_vfa1jr6otnjn.json" - }, - "traj_vkozdglobkyg": { - "title": "Address Relay PR 826 review comments", - "status": "completed", - "startedAt": "2026-05-08T15:50:35.978Z", - "completedAt": "2026-05-08T15:51:38.854Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_vkozdglobkyg.json" - }, - "traj_wbn62q4cq16h": { - "title": "Update generated workflow to Codex agents only", - "status": "completed", - "startedAt": "2026-05-15T21:03:02.671Z", - "completedAt": "2026-05-15T21:06:00.384Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_wbn62q4cq16h.json" - }, - "traj_whd40oxptlhn": { - "title": "Review spawn persistence fix and open PR", - "status": "completed", - "startedAt": "2026-05-13T10:57:02.796Z", - "completedAt": "2026-05-13T11:00:43.100Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_whd40oxptlhn.json" - }, - "traj_wx00tjvpptvg": { - "title": "Investigate agent-relay spawn persistence", - "status": "completed", - "startedAt": "2026-05-13T10:49:12.464Z", - "completedAt": "2026-05-13T10:53:03.748Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_wx00tjvpptvg.json" - }, - "traj_wzzboitm85ee": { - "title": "Resolve PR conflicts around platform tradeoff copy", - "status": "completed", - "startedAt": "2026-05-15T12:47:36.508Z", - "completedAt": "2026-05-15T12:50:14.358Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_wzzboitm85ee.json" - }, - "traj_x37bhga2j5ph": { - "title": "Deepen broker runtime refactor for PR 906", - "status": "completed", - "startedAt": "2026-05-19T01:42:10.602Z", - "completedAt": "2026-05-19T01:50:40.359Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_x37bhga2j5ph.json" - }, - "traj_ybcrij9wg8m1": { - "title": "Implement agent-relay view read-only stream client (#864 sub-1)", - "status": "completed", - "startedAt": "2026-05-18T02:02:07.524Z", - "completedAt": "2026-05-18T02:05:41.120Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_ybcrij9wg8m1.json" - }, - "traj_z171lng2fbbi": { - "title": "Address PR 840 review feedback", - "status": "completed", - "startedAt": "2026-05-11T08:06:37.977Z", - "completedAt": "2026-05-11T08:07:48.097Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_z171lng2fbbi.json" - }, - "traj_zfa6skfr32vy": { - "title": "Implement relay CLI M1 bootstrap commands", - "status": "completed", - "startedAt": "2026-05-11T19:31:44.734Z", - "completedAt": "2026-05-11T19:34:54.971Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_zfa6skfr32vy.json" - }, - "traj_zqwco4gl76g3": { - "title": "Fix issue 878", - "status": "completed", - "startedAt": "2026-05-19T04:18:25.024Z", - "completedAt": "2026-05-19T04:27:18.903Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_zqwco4gl76g3.json" - }, - "traj_zu3252hxzoqh": { - "title": "Open PR for reading worker DM replies", - "status": "completed", - "startedAt": "2026-05-16T06:08:22.396Z", - "completedAt": "2026-05-16T06:09:24.599Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_zu3252hxzoqh.json" - }, - "traj_1775914296101_a4397efe": { - "title": "fix-sdk-build-resolution-workflow", - "status": "completed", - "startedAt": "2026-04-11T13:31:36.101Z", - "completedAt": "2026-04-11T13:39:53.105Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1775914296101_a4397efe.json" - }, - "traj_1776024661304_cfc829b9": { - "title": "fix-workflow-resume-elegant-workflow", - "status": "abandoned", - "startedAt": "2026-04-12T20:11:01.304Z", - "completedAt": "2026-04-12T20:11:16.381Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1776024661304_cfc829b9.json" - }, - "traj_1778873052429_03a4dacb": { - "title": "ricky-reading-worker-dm-replies-design-spec-status-draft-date-2026-05-15-issue-860-hea-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:24:12.429Z", - "completedAt": "2026-05-15T20:01:57.036Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873052429_03a4dacb.json" - }, - "traj_1778873197540_01102ade": { - "title": "ricky-child-update-messaging-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:26:37.540Z", - "completedAt": "2026-05-15T19:43:47.629Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873197540_01102ade.json" - }, - "traj_1778873199489_f2ce4060": { - "title": "ricky-child-update-skill-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:26:39.489Z", - "completedAt": "2026-05-15T19:43:43.149Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873199489_f2ce4060.json" - }, - "traj_1778873201502_0dacf7c5": { - "title": "ricky-child-update-messaging-2-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:26:41.502Z", - "completedAt": "2026-05-15T19:43:35.011Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873201502_0dacf7c5.json" - }, - "traj_1778873203502_4c225b7e": { - "title": "ricky-child-update-messaging-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:26:43.502Z", - "completedAt": "2026-05-15T19:44:33.074Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873203502_4c225b7e.json" - }, - "traj_1778873205470_a4e5f0cb": { - "title": "ricky-child-update-issue-860-transcript-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:26:45.470Z", - "completedAt": "2026-05-15T19:43:37.134Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873205470_a4e5f0cb.json" - }, - "traj_1778873207471_b7def991": { - "title": "ricky-child-update-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:26:47.471Z", - "completedAt": "2026-05-15T19:43:24.329Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778873207471_b7def991.json" - }, - "traj_1778874205797_81e92307": { - "title": "ricky-child-update-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:43:25.797Z", - "completedAt": "2026-05-15T19:43:43.995Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874205797_81e92307.json" - }, - "traj_1778874216773_c6b12ab2": { - "title": "ricky-child-update-messaging-2-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:43:36.773Z", - "completedAt": "2026-05-15T19:44:08.270Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874216773_c6b12ab2.json" - }, - "traj_1778874218579_a0225559": { - "title": "ricky-child-update-issue-860-transcript-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:43:38.579Z", - "completedAt": "2026-05-15T19:43:58.721Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874218579_a0225559.json" - }, - "traj_1778874224855_9c722c4b": { - "title": "ricky-child-update-skill-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:43:44.855Z", - "completedAt": "2026-05-15T19:44:16.528Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874224855_9c722c4b.json" - }, - "traj_1778874226983_3367d527": { - "title": "ricky-child-update-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:43:46.983Z", - "completedAt": "2026-05-15T19:44:05.612Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874226983_3367d527.json" - }, - "traj_1778874229373_9cce9465": { - "title": "ricky-child-update-messaging-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:43:49.374Z", - "completedAt": "2026-05-15T19:44:20.096Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874229373_9cce9465.json" - }, - "traj_1778874240339_51b823cd": { - "title": "ricky-child-update-issue-860-transcript-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:00.339Z", - "completedAt": "2026-05-15T19:44:18.916Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874240339_51b823cd.json" - }, - "traj_1778874241076_caa675a9": { - "title": "ricky-child-update-2-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:01.076Z", - "completedAt": "2026-05-15T19:44:32.521Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874241076_caa675a9.json" - }, - "traj_1778874248966_e29c4c54": { - "title": "ricky-child-update-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:08.966Z", - "completedAt": "2026-05-15T19:44:27.330Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874248966_e29c4c54.json" - }, - "traj_1778874249983_12a98df3": { - "title": "ricky-child-update-messaging-2-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:09.983Z", - "completedAt": "2026-05-15T19:44:41.100Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874249983_12a98df3.json" - }, - "traj_1778874258229_0bdc53d8": { - "title": "ricky-child-update-skill-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:18.229Z", - "completedAt": "2026-05-15T19:44:49.274Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874258229_0bdc53d8.json" - }, - "traj_1778874261453_55f49624": { - "title": "ricky-child-update-messaging-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:21.453Z", - "completedAt": "2026-05-15T19:44:53.643Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874261453_55f49624.json" - }, - "traj_1778874261608_48fb9bf5": { - "title": "ricky-child-update-issue-860-transcript-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:21.608Z", - "completedAt": "2026-05-15T19:44:40.761Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874261608_48fb9bf5.json" - }, - "traj_1778874269139_d7d7485a": { - "title": "ricky-child-update-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:29.139Z", - "completedAt": "2026-05-15T19:44:48.083Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874269139_d7d7485a.json" - }, - "traj_1778874274412_70843e0e": { - "title": "ricky-child-update-2-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:34.412Z", - "completedAt": "2026-05-15T19:45:06.798Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874274412_70843e0e.json" - }, - "traj_1778874274581_71efa470": { - "title": "ricky-child-update-messaging-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:34.581Z", - "completedAt": "2026-05-15T19:44:54.182Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874274581_71efa470.json" - }, - "traj_1778874282200_39ad11db": { - "title": "ricky-child-update-issue-860-transcript-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:42.200Z", - "completedAt": "2026-05-15T19:45:02.477Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874282200_39ad11db.json" - }, - "traj_1778874283570_ce3585b8": { - "title": "ricky-child-update-messaging-2-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:43.570Z", - "completedAt": "2026-05-15T19:45:14.888Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874283570_ce3585b8.json" - }, - "traj_1778874289674_e3f868c8": { - "title": "ricky-child-update-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:49.674Z", - "completedAt": "2026-05-15T19:45:08.337Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874289674_e3f868c8.json" - }, - "traj_1778874291950_0b1b5c1f": { - "title": "ricky-child-update-skill-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:51.950Z", - "completedAt": "2026-05-15T19:45:23.421Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874291950_0b1b5c1f.json" - }, - "traj_1778874295927_4083d181": { - "title": "ricky-child-update-messaging-test-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:55.927Z", - "completedAt": "2026-05-15T19:45:15.333Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874295927_4083d181.json" - }, - "traj_1778874296362_bdf727ff": { - "title": "ricky-child-update-messaging-workflow", - "status": "abandoned", - "startedAt": "2026-05-15T19:44:56.362Z", - "completedAt": "2026-05-15T19:45:27.624Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/traj_1778874296362_bdf727ff.json" - }, - "traj_l1349adi1g0o": { - "title": "Fix agent relay MCP PR CI failures", - "status": "completed", - "startedAt": "2026-05-25T21:42:41.236Z", - "completedAt": "2026-05-25T21:42:57.292Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_l1349adi1g0o.json" - }, - "traj_8nhd9lljhbsw": { - "title": "Switch owned MCP tool names to underscores", - "status": "completed", - "startedAt": "2026-05-25T22:05:23.828Z", - "completedAt": "2026-05-25T22:14:25.886Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_8nhd9lljhbsw.json" - }, - "traj_gkxajksmwoea": { - "title": "Document owned MCP tool table", - "status": "completed", - "startedAt": "2026-05-25T22:17:18.278Z", - "completedAt": "2026-05-25T22:19:29.639Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_gkxajksmwoea.json" - }, - "traj_t6h534vn0bpg": { - "title": "Resolve PR merge conflicts", - "status": "completed", - "startedAt": "2026-05-26T10:11:15.356Z", - "completedAt": "2026-05-26T10:21:56.502Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_t6h534vn0bpg.json" - }, - "traj_cszfl2icaj2t": { - "title": "Add Prettier auto-format workflow", - "status": "completed", - "startedAt": "2026-05-26T11:04:15.563Z", - "completedAt": "2026-05-26T11:05:23.716Z", - "path": "/private/tmp/relay-owned-mcp/.trajectories/completed/2026-05/traj_cszfl2icaj2t.json" - }, - "traj_17t39ue8exte": { - "title": "Add GitHub traffic to PostHog sync", - "status": "completed", - "startedAt": "2026-05-26T13:16:29.926Z", - "completedAt": "2026-05-26T13:17:08.417Z", - "path": "/private/tmp/relay-posthog-github-traffic/.trajectories/completed/2026-05/traj_17t39ue8exte/trajectory.json" - }, - "traj_b3g40827t5zh": { - "title": "Address GitHub traffic PostHog PR review comments", - "status": "completed", - "startedAt": "2026-05-26T13:42:50.509Z", - "completedAt": "2026-05-26T13:44:38.186Z", - "path": "/private/tmp/relay-posthog-github-traffic/.trajectories/completed/2026-05/traj_b3g40827t5zh/trajectory.json" - } - } -} diff --git a/CHANGELOG.md b/CHANGELOG.md index b316ea204..ffe10f9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GitHub Actions can sync repository traffic views, clones, popular paths, and referrers into PostHog with daily backfill across GitHub's available traffic window. - Broker and TypeScript SDK structured result contracts add the `submit_result` MCP tool, `agent.waitForResult()`, per-spawn `result.onResult`, and `relay.addListener('agentResult', ...)` for typed JSON worker outcomes. +- `@agent-relay/sdk` and `agent-relay-broker` add broker-executable `pty` and `headless` harness configs, so custom CLIs can be configured without Rust changes while spawn requests remain self-contained. +- `agent-relay-broker` accepts resolved harness configs on spawn and adds a headless app-server driver for delivering Relay messages to existing OpenCode server sessions. - `@agent-relay/sdk` adds `AgentRelay.getPersonaSpawnPlan(id)` and a `getPersonaSpawnPlan` export for dry-run inspection of a persona's resolved harness argv, skill installs, mount policy, sidecars, and inputs. ### Changed @@ -37,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `agent-relay-broker` harness configs now report harness PIDs instead of wrapper worker PIDs, validate app-server protocol/auth/host settings at spawn, and give app-server release requests time to finish. +- `@agent-relay/sdk` normalizes broker `pid: null` spawn responses to `undefined` while PTY harness PIDs are reported asynchronously. - `web`: PR preview SST deploys use and comment the generated CloudFront URL and AWS's managed disabled cache policy instead of creating per-preview Cloudflare DNS records, ACM certificates, and custom CloudFront cache policies. - `sdk-swift`: broker client now connects to the v7 broker's `/ws` event stream without a legacy `hello`/`hello_ack` handshake and routes `spawnAgent`, `releaseAgent`, channel `post`, and agent `dm` through the broker's HTTP API (`/api/spawn`, `/api/spawned/{name}`, `/api/send`). diff --git a/crates/broker/src/cli/mod.rs b/crates/broker/src/cli/mod.rs index 0f18c9f98..97c0aba2a 100644 --- a/crates/broker/src/cli/mod.rs +++ b/crates/broker/src/cli/mod.rs @@ -25,6 +25,9 @@ enum Commands { Init(InitCommand), Pty(PtyCommand), Headless(HeadlessCommand), + /// Internal: headless worker shim for app-server-backed harnesses. + #[command(name = "app-server", hide = true)] + HeadlessAppServer(HeadlessAppServerCommand), /// Compute MCP injection args and side-effect config file paths for a CLI /// without spawning it. Outputs JSON to stdout. McpArgs(McpArgsCommand), @@ -52,6 +55,7 @@ impl Commands { Commands::Init(_) => "init", Commands::Pty(_) => "pty", Commands::Headless(_) => "headless", + Commands::HeadlessAppServer(_) => "app_server", Commands::McpArgs(_) => "mcp_args", Commands::Swarm(_) => "swarm", Commands::DumpPty(_) => "dump_pty", @@ -86,6 +90,8 @@ impl Commands { } Commands::Headless(cmd) => non_empty_name(cmd.agent_name.as_deref()) .unwrap_or_else(|| format!("headless-{pid}")), + Commands::HeadlessAppServer(cmd) => non_empty_name(cmd.agent_name.as_deref()) + .unwrap_or_else(|| format!("headless-app-server-{pid}")), Commands::Wrap { cli, .. } => format!("wrap-{cli}-{pid}"), Commands::McpArgs(_) => format!("mcp_args-{pid}"), Commands::DumpPty(cmd) => format!("dump_pty-{}-{}", cmd.name, pid), @@ -114,6 +120,7 @@ pub(crate) async fn run() -> Result<()> { Commands::Init(cmd) => runtime::run_init(cmd, telemetry).await, Commands::Pty(cmd) => pty_worker::run_pty_worker(cmd).await, Commands::Headless(cmd) => runtime::run_headless_worker(cmd).await, + Commands::HeadlessAppServer(cmd) => runtime::run_headless_app_server_worker(cmd).await, Commands::McpArgs(cmd) => cli_mcp_args::run_mcp_args(cmd).await, Commands::Swarm(args) => swarm::run_swarm(args).await, Commands::DumpPty(cmd) => runtime::run_dump_pty(cmd).await, @@ -271,6 +278,27 @@ pub(crate) struct HeadlessCommand { pub(crate) agent_name: Option, } +#[derive(Debug, clap::Args, Clone)] +pub(crate) struct HeadlessAppServerCommand { + #[arg(long)] + pub(crate) protocol: String, + + #[arg(long)] + pub(crate) endpoint: String, + + #[arg(long = "session-id")] + pub(crate) session_id: String, + + #[arg(long = "host-pid")] + pub(crate) host_pid: Option, + + #[arg(long, default_value = "detach")] + pub(crate) release: String, + + #[arg(long)] + pub(crate) agent_name: Option, +} + #[derive(Debug, Clone, Copy, ValueEnum)] pub(crate) enum HeadlessCliProvider { Claude, diff --git a/crates/broker/src/listen_api.rs b/crates/broker/src/listen_api.rs index fe2d442a5..7f985f69f 100644 --- a/crates/broker/src/listen_api.rs +++ b/crates/broker/src/listen_api.rs @@ -12,7 +12,7 @@ use std::{ use crate::{ ids::{ChannelName, MessageTarget, ThreadId, WorkerName, WorkspaceAlias, WorkspaceId}, - protocol::MessageInjectionMode, + protocol::{MessageInjectionMode, ResolvedHarnessConfig}, relaycast::WorkspaceMembershipSummary, replay_buffer::ReplayBuffer, types::{InboundDeliveryMode, PendingRelayMessage}, @@ -51,6 +51,7 @@ pub enum ListenApiRequest { idle_threshold_secs: Option, skip_relay_prompt: bool, restart_policy: Box>, + harness_config: Option, agent_token: Option, agent_result_schema: Option, reply: tokio::sync::oneshot::Sender>, @@ -577,6 +578,24 @@ async fn listen_api_auth_middleware( Ok(next.run(request).await) } +fn parse_harness_config_value(value: Value) -> Result { + serde_json::from_value::(value) + .map_err(|error| format!("Invalid harnessConfig: {error}")) +} + +fn extract_harness_config(body: &Value) -> Result, String> { + match body + .get("harness_config") + .or_else(|| body.get("harnessConfig")) + .or_else(|| body.get("harness_plan")) + .or_else(|| body.get("harnessPlan")) + .cloned() + { + Some(value) => parse_harness_config_value(value).map(Some), + None => Ok(None), + } +} + async fn listen_api_spawn( axum::extract::State(state): axum::extract::State, axum::Json(body): axum::Json, @@ -649,6 +668,31 @@ async fn listen_api_spawn( .or_else(|| body.get("restartPolicy")) .cloned(), ); + if body + .get("harness_id") + .or_else(|| body.get("harnessId")) + .is_some() + { + return ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(json!({ + "success": false, + "error": "harnessId is not supported by the broker API; send harnessConfig" + })), + ); + } + let harness_config = match extract_harness_config(&body) { + Ok(config) => config, + Err(error) => { + return ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(json!({ + "success": false, + "error": error + })), + ); + } + }; let agent_token = body .get("agent_token") .or_else(|| body.get("agentToken")) @@ -686,6 +730,7 @@ async fn listen_api_spawn( idle_threshold_secs, skip_relay_prompt, restart_policy, + harness_config, agent_token, agent_result_schema, reply: reply_tx, @@ -2432,13 +2477,14 @@ mod auth_tests { idle_threshold_secs, skip_relay_prompt: _, restart_policy: _, + harness_config, agent_token: _, agent_result_schema, reply, }) => { assert_eq!(name, "worker-a"); assert_eq!(cli, "codex"); - assert_eq!(transport.as_deref(), Some("headless")); + assert_eq!(transport.as_deref(), Some("pty")); assert_eq!(model.as_deref(), Some("o3")); assert_eq!(args, vec!["--fast".to_string()]); assert_eq!(task.as_deref(), Some("Ship it")); @@ -2452,6 +2498,7 @@ mod auth_tests { assert_eq!(shadow_mode.as_deref(), Some("subagent")); assert_eq!(continue_from.as_deref(), Some("worker-prev")); assert_eq!(idle_threshold_secs, Some(30)); + assert!(harness_config.is_some()); assert_eq!( agent_result_schema, Some(json!({"type": "object", "properties": {"ok": {"type": "boolean"}}})) @@ -2475,7 +2522,7 @@ mod auth_tests { json!({ "name": "worker-a", "cli": "codex", - "transport": "headless", + "transport": "pty", "model": "o3", "args": ["--fast"], "task": "Ship it", @@ -2486,6 +2533,11 @@ mod auth_tests { "shadowMode": "subagent", "continueFrom": "worker-prev", "idleThresholdSecs": 30, + "harnessConfig": { + "runtime": "pty", + "command": "codex", + "args": ["--fast"] + }, "resultSchema": {"type": "object", "properties": {"ok": {"type": "boolean"}}}, }) .to_string(), @@ -2502,6 +2554,37 @@ mod auth_tests { spawn_replier.await.expect("spawn replier should complete"); } + #[tokio::test] + async fn spawn_route_rejects_harness_id() { + let (router, _rx) = test_router(Some("secret")); + let response = router + .oneshot( + Request::builder() + .uri("/api/spawn") + .method("POST") + .header("x-api-key", "secret") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "name": "worker-a", + "cli": "company-claude", + "harnessId": "company-claude" + }) + .to_string(), + )) + .expect("request should build"), + ) + .await + .expect("request should succeed"); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body = response_json(response).await; + assert!(body["error"] + .as_str() + .expect("error should be a string") + .contains("harnessId is not supported")); + } + #[tokio::test] async fn agent_result_route_accepts_callback_token_without_broker_auth() { let (router, mut rx) = test_router(Some("secret")); diff --git a/crates/broker/src/protocol.rs b/crates/broker/src/protocol.rs index 6e0d79a50..31b9c8dc0 100644 --- a/crates/broker/src/protocol.rs +++ b/crates/broker/src/protocol.rs @@ -1,4 +1,6 @@ -use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use crate::ids::{ @@ -24,6 +26,180 @@ pub enum HeadlessProvider { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum PtyHarnessDeliveryMode { + PtyInjection, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum PtyHarnessDeliveryFormat { + RelayBlock, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PtyHarnessDelivery { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub format: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PtyHarnessConfig { + pub command: String, + #[serde(default)] + pub args: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cwd: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub env: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub session_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delivery: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum AppServerAuthType { + Bearer, + Basic, + None, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AppServerHarnessAuth { + #[serde(rename = "type")] + pub auth_type: AppServerAuthType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub token: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub password: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum AppServerHostOwnership { + BrokerOwned, + Attached, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AppServerHarnessHost { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ownership: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pid: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum HarnessReleasePolicy { + Abort, + #[default] + Detach, + Delete, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HeadlessHarnessDriver { + AppServer, +} + +fn default_headless_harness_driver() -> HeadlessHarnessDriver { + HeadlessHarnessDriver::AppServer +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HeadlessHarnessConfig { + #[serde(default = "default_headless_harness_driver")] + pub driver: HeadlessHarnessDriver, + pub protocol: String, + pub endpoint: String, + pub session_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auth: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub host: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub release: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option>, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "runtime", rename_all = "snake_case")] +pub enum ResolvedHarnessConfig { + Pty(PtyHarnessConfig), + Headless(HeadlessHarnessConfig), +} + +impl<'de> Deserialize<'de> for ResolvedHarnessConfig { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut value = Value::deserialize(deserializer)?; + let runtime = value + .get("runtime") + .and_then(Value::as_str) + .map(str::to_owned) + .ok_or_else(|| serde::de::Error::missing_field("runtime"))?; + + match runtime.as_str() { + "pty" => serde_json::from_value(value) + .map(Self::Pty) + .map_err(serde::de::Error::custom), + "headless" => serde_json::from_value(value) + .map(Self::Headless) + .map_err(serde::de::Error::custom), + "app_server" => { + if let Some(object) = value.as_object_mut() { + object.insert( + "driver".to_string(), + Value::String("app_server".to_string()), + ); + } + serde_json::from_value(value) + .map(Self::Headless) + .map_err(serde::de::Error::custom) + } + other => Err(serde::de::Error::unknown_variant( + other, + &["pty", "headless", "app_server"], + )), + } + } +} + +impl ResolvedHarnessConfig { + pub(crate) fn runtime(&self) -> AgentRuntime { + match self { + Self::Pty(_) => AgentRuntime::Pty, + Self::Headless(_) => AgentRuntime::Headless, + } + } + + pub(crate) fn session_id(&self) -> Option<&str> { + match self { + Self::Pty(config) => config.session_id.as_deref(), + Self::Headless(config) => Some(config.session_id.as_str()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AgentSpec { pub name: WorkerName, pub runtime: AgentRuntime, @@ -31,12 +207,21 @@ pub struct AgentSpec { pub provider: Option, #[serde(default)] pub cli: Option, + #[serde(default, alias = "sessionId", skip_serializing_if = "Option::is_none")] + pub session_id: Option, + #[serde( + default, + rename = "harnessConfig", + alias = "harness_config", + alias = "harnessPlan", + alias = "harness_plan", + skip_serializing_if = "Option::is_none" + )] + pub harness_config: Option, #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cwd: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub session_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub team: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -96,7 +281,7 @@ pub enum SdkToBroker { client_version: String, }, SpawnAgent { - agent: AgentSpec, + agent: Box, }, SendMessage { to: MessageTarget, @@ -320,7 +505,7 @@ pub enum BrokerEvent { #[serde(tag = "type", content = "payload", rename_all = "snake_case")] pub enum BrokerToWorker { InitWorker { - agent: AgentSpec, + agent: Box, }, DeliverRelay(RelayDelivery), ShutdownWorker { @@ -376,8 +561,9 @@ mod tests { use serde_json::{json, Value}; use super::{ - AgentRuntime, AgentSpec, BrokerEvent, BrokerToSdk, BrokerToWorker, HeadlessProvider, - MessageInjectionMode, ProtocolEnvelope, RelayDelivery, WorkerToBroker, PROTOCOL_VERSION, + AgentRuntime, AgentSpec, BrokerEvent, BrokerToSdk, BrokerToWorker, HeadlessHarnessDriver, + HeadlessProvider, MessageInjectionMode, ProtocolEnvelope, RelayDelivery, + ResolvedHarnessConfig, WorkerToBroker, PROTOCOL_VERSION, }; use crate::ids::RequestId; @@ -558,6 +744,8 @@ mod tests { assert_eq!(spec.runtime, AgentRuntime::Pty); assert_eq!(spec.provider, None); assert_eq!(spec.cli, None); + assert_eq!(spec.session_id, None); + assert_eq!(spec.harness_config, None); assert_eq!(spec.model, None); assert_eq!(spec.cwd, None); assert_eq!(spec.team, None); @@ -579,6 +767,119 @@ mod tests { assert_eq!(decoded.provider, Some(HeadlessProvider::Opencode)); } + #[test] + fn agent_spec_accepts_camel_case_harness_config() { + let raw = r#"{ + "name": "QwenWorker", + "runtime": "pty", + "cli": "qwen", + "sessionId": "native-session", + "harnessConfig": { + "runtime": "pty", + "command": "qwen", + "args": ["run", "-m", "qwen3-coder"], + "cwd": "/tmp/project", + "env": { "QWEN_MODE": "code" }, + "sessionId": "native-session" + } + }"#; + + let spec: AgentSpec = serde_json::from_str(raw).unwrap(); + assert_eq!(spec.runtime, AgentRuntime::Pty); + assert_eq!(spec.session_id.as_deref(), Some("native-session")); + let Some(ResolvedHarnessConfig::Pty(config)) = spec.harness_config else { + panic!("expected pty harness config"); + }; + assert_eq!(config.command, "qwen"); + assert_eq!( + config.args, + vec![ + "run".to_string(), + "-m".to_string(), + "qwen3-coder".to_string() + ] + ); + assert_eq!(config.cwd.as_deref(), Some("/tmp/project")); + assert_eq!( + config + .env + .as_ref() + .and_then(|env| env.get("QWEN_MODE")) + .map(String::as_str), + Some("code") + ); + assert_eq!(config.session_id.as_deref(), Some("native-session")); + } + + #[test] + fn agent_spec_accepts_legacy_camel_case_harness_plan() { + let raw = r#"{ + "name": "LegacyHarnessWorker", + "runtime": "pty", + "cli": "codex", + "harnessPlan": { + "runtime": "pty", + "command": "codex", + "args": ["--model", "gpt-5.4"] + } + }"#; + + let spec: AgentSpec = serde_json::from_str(raw).unwrap(); + assert!(matches!( + spec.harness_config, + Some(ResolvedHarnessConfig::Pty(_)) + )); + } + + #[test] + fn headless_app_server_harness_config_round_trips() { + let raw = json!({ + "runtime": "headless", + "protocol": "opencode", + "endpoint": "http://127.0.0.1:4096", + "sessionId": "ses_123", + "auth": { + "type": "basic", + "username": "opencode", + "password": "secret" + }, + "release": "abort" + }); + + let config: ResolvedHarnessConfig = serde_json::from_value(raw).unwrap(); + assert_eq!(config.runtime(), AgentRuntime::Headless); + assert_eq!(config.session_id(), Some("ses_123")); + let ResolvedHarnessConfig::Headless(headless) = &config else { + panic!("expected headless harness config"); + }; + assert_eq!(headless.driver, HeadlessHarnessDriver::AppServer); + + let encoded = serde_json::to_string(&config).unwrap(); + assert!(encoded.contains("\"runtime\":\"headless\"")); + assert!(encoded.contains("\"driver\":\"app_server\"")); + assert!(encoded.contains("\"sessionId\":\"ses_123\"")); + let decoded: ResolvedHarnessConfig = serde_json::from_str(&encoded).unwrap(); + assert_eq!(decoded.session_id(), Some("ses_123")); + } + + #[test] + fn legacy_app_server_harness_config_deserializes_as_headless() { + let raw = json!({ + "runtime": "app_server", + "protocol": "opencode", + "endpoint": "http://127.0.0.1:4096", + "sessionId": "ses_legacy" + }); + + let config: ResolvedHarnessConfig = serde_json::from_value(raw).unwrap(); + assert_eq!(config.runtime(), AgentRuntime::Headless); + assert_eq!(config.session_id(), Some("ses_legacy")); + let ResolvedHarnessConfig::Headless(config) = config else { + panic!("expected headless harness config"); + }; + assert_eq!(config.driver, HeadlessHarnessDriver::AppServer); + } + #[test] fn broker_to_worker_resize_pty_round_trip() { let msg = BrokerToWorker::ResizePty { diff --git a/crates/broker/src/pty.rs b/crates/broker/src/pty.rs index 75530b4ce..02bb66699 100644 --- a/crates/broker/src/pty.rs +++ b/crates/broker/src/pty.rs @@ -461,6 +461,11 @@ impl PtySession { (grid.screen_lines() as u16, grid.columns() as u16) } + pub fn child_pid(&self) -> Option { + let child = self.child.lock(); + child.process_id().or(self.child_pid) + } + /// Run a closure against the live `Term`, holding the term lock for the /// duration. Used by `snapshot::Snapshot::capture` to walk the grid /// (cells + colours + flags) without exposing the underlying `Term` type diff --git a/crates/broker/src/pty_worker.rs b/crates/broker/src/pty_worker.rs index effecd6b8..1ed5911f1 100644 --- a/crates/broker/src/pty_worker.rs +++ b/crates/broker/src/pty_worker.rs @@ -194,6 +194,7 @@ fn should_block_pending_injection( async fn try_emit_worker_ready( out_tx: &mpsc::Sender>, worker_name: &str, + child_pid: Option, init_request_id: &mut Option, init_received_at: Option, worker_ready_sent: &mut bool, @@ -227,7 +228,7 @@ async fn try_emit_worker_ready( out_tx, "worker_ready", request_id, - json!({"name": worker_name, "runtime": "pty"}), + json!({"name": worker_name, "runtime": "pty", "pid": child_pid}), ) .await; *worker_ready_sent = true; @@ -423,6 +424,7 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { try_emit_worker_ready( &out_tx, &worker_name, + pty.child_pid(), &mut init_request_id, init_received_at, &mut worker_ready_sent, @@ -667,6 +669,7 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { try_emit_worker_ready( &out_tx, &worker_name, + pty.child_pid(), &mut init_request_id, init_received_at, &mut worker_ready_sent, @@ -1027,6 +1030,7 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { try_emit_worker_ready( &out_tx, &worker_name, + pty.child_pid(), &mut init_request_id, init_received_at, &mut worker_ready_sent, diff --git a/crates/broker/src/runtime/api.rs b/crates/broker/src/runtime/api.rs index 2170d2622..f5e2884f9 100644 --- a/crates/broker/src/runtime/api.rs +++ b/crates/broker/src/runtime/api.rs @@ -45,6 +45,7 @@ impl BrokerRuntime { idle_threshold_secs, skip_relay_prompt, restart_policy, + harness_config, agent_token, agent_result_schema, reply, @@ -66,6 +67,7 @@ impl BrokerRuntime { shadow_of, shadow_mode, *restart_policy, + harness_config, ) { Ok(spec) => spec, Err(error) => { @@ -230,7 +232,7 @@ impl BrokerRuntime { is_shadow: effective_spec.shadow_of.is_some() || effective_spec.shadow_mode.is_some(), }); - let pid = workers.worker_pid(&name).unwrap_or(0); + let pid = workers.harness_pid(&name); state.agents.insert( name.clone(), broker::PersistedAgent { @@ -289,6 +291,7 @@ impl BrokerRuntime { "model": effective_spec.model.clone(), "sessionId": effective_spec.session_id.clone(), "pid": pid, + "sessionId": effective_spec.session_id.clone(), "pre_registered": worker_relay_key.is_some(), "warning": preregistration_warning, }))); @@ -942,21 +945,38 @@ impl BrokerRuntime { let _ = reply.send(Ok(json!({ "threads": threads }))); } ListenApiRequest::SendInput { name, data, reply } => { - if let Err(err) = workers - .send_to_worker( - &name, - "write_pty", - Some(RequestId::new(format!("api_{}", Uuid::new_v4().simple()))), - json!({ "data": data }), - ) - .await + match workers + .workers + .get(&name) + .map(|handle| handle.spec.runtime.clone()) { - let _ = reply.send(Err(format!("agent_not_found: {}", err))); - } else { - let _ = reply.send(Ok(json!({ - "name": name, - "bytes_written": data.len(), - }))); + None => { + let _ = + reply.send(Err(format!("agent_not_found: no worker named '{name}'"))); + } + Some(AgentRuntime::Headless) => { + let _ = reply.send(Err(format!( + "unsupported_runtime: worker '{name}' is headless; pty input is only supported on PTY workers" + ))); + } + Some(AgentRuntime::Pty) => { + if let Err(err) = workers + .send_to_worker( + &name, + "write_pty", + Some(RequestId::new(format!("api_{}", Uuid::new_v4().simple()))), + json!({ "data": data }), + ) + .await + { + let _ = reply.send(Err(format!("agent_not_found: {}", err))); + } else { + let _ = reply.send(Ok(json!({ + "name": name, + "bytes_written": data.len(), + }))); + } + } } } ListenApiRequest::CheckPtyInputTarget { name, reply } => { @@ -991,22 +1011,44 @@ impl BrokerRuntime { if rows == 0 || cols == 0 { let _ = reply.send(Err("invalid_dimensions: rows and cols must be >= 1".into())); - } else if let Err(err) = workers - .send_to_worker( - &name, - "resize_pty", - Some(RequestId::new(format!("api_{}", Uuid::new_v4().simple()))), - json!({ "rows": rows, "cols": cols }), - ) - .await - { - let _ = reply.send(Err(format!("agent_not_found: {}", err))); } else { - let _ = reply.send(Ok(json!({ - "name": name, - "rows": rows, - "cols": cols, - }))); + match workers + .workers + .get(&name) + .map(|handle| handle.spec.runtime.clone()) + { + None => { + let _ = reply + .send(Err(format!("agent_not_found: no worker named '{name}'"))); + } + Some(AgentRuntime::Headless) => { + let _ = reply.send(Err(format!( + "unsupported_runtime: worker '{name}' is headless; resize_pty is only supported on PTY workers" + ))); + } + Some(AgentRuntime::Pty) => { + if let Err(err) = workers + .send_to_worker( + &name, + "resize_pty", + Some(RequestId::new(format!( + "api_{}", + Uuid::new_v4().simple() + ))), + json!({ "rows": rows, "cols": cols }), + ) + .await + { + let _ = reply.send(Err(format!("agent_not_found: {}", err))); + } else { + let _ = reply.send(Ok(json!({ + "name": name, + "rows": rows, + "cols": cols, + }))); + } + } + } } } ListenApiRequest::WorkerRequest { diff --git a/crates/broker/src/runtime/app_server.rs b/crates/broker/src/runtime/app_server.rs new file mode 100644 index 000000000..c41fd2ed9 --- /dev/null +++ b/crates/broker/src/runtime/app_server.rs @@ -0,0 +1,408 @@ +use super::*; + +#[derive(Debug, Clone)] +struct AppServerAuthConfig { + auth_type: String, + token: Option, + username: Option, + password: Option, +} + +const APP_SERVER_HTTP_TIMEOUT: Duration = Duration::from_secs(30); + +pub(crate) async fn run_headless_app_server_worker(cmd: HeadlessAppServerCommand) -> Result<()> { + let protocol = cmd.protocol.trim().to_ascii_lowercase(); + let endpoint = cmd.endpoint.trim().trim_end_matches('/').to_string(); + let session_id = cmd.session_id.clone(); + let host_pid = cmd.host_pid; + let release = cmd.release.trim().to_ascii_lowercase(); + let auth = app_server_auth_from_env(); + let http = reqwest::Client::builder() + .timeout(APP_SERVER_HTTP_TIMEOUT) + .build() + .context("failed to build app-server HTTP client")?; + + let (out_tx, mut out_rx) = mpsc::channel::>(512); + let writer_task = tokio::spawn(async move { + let mut stdout = tokio::io::stdout(); + while let Some(frame) = out_rx.recv().await { + if let Ok(mut line) = serde_json::to_string(&frame) { + line.push('\n'); + if stdout.write_all(line.as_bytes()).await.is_err() || stdout.flush().await.is_err() + { + break; + } + } + } + }); + + let mut lines = BufReader::new(tokio::io::stdin()).lines(); + let mut worker_name = cmd + .agent_name + .clone() + .unwrap_or_else(|| format!("app-server-{protocol}")); + let mut final_exit_code: Option = None; + let final_exit_signal: Option = None; + + while let Ok(Some(line)) = lines.next_line().await { + let frame: ProtocolEnvelope = match serde_json::from_str(&line) { + Ok(frame) => frame, + Err(error) => { + let _ = send_frame( + &out_tx, + "worker_error", + None, + json!({ + "code":"invalid_frame", + "message": error.to_string(), + "retryable": false, + }), + ) + .await; + continue; + } + }; + + match frame.msg_type.as_str() { + "init_worker" => { + worker_name = cmd + .agent_name + .clone() + .or_else(|| { + frame + .payload + .get("agent") + .and_then(|a| a.get("name")) + .and_then(Value::as_str) + .map(ToOwned::to_owned) + }) + .unwrap_or_else(|| format!("app-server-{protocol}")); + + let _ = send_frame( + &out_tx, + "worker_ready", + frame.request_id, + json!({ + "name": &worker_name, + "runtime": "headless", + "driver": "app_server", + "sessionId": &session_id, + "pid": host_pid, + }), + ) + .await; + } + "deliver_relay" => { + let request_id = frame.request_id.clone(); + let delivery: RelayDelivery = match serde_json::from_value(frame.payload) { + Ok(d) => d, + Err(error) => { + let _ = send_frame( + &out_tx, + "worker_error", + request_id, + json!({ + "code":"invalid_delivery", + "message": error.to_string(), + "retryable": false, + }), + ) + .await; + continue; + } + }; + + let timestamp = chrono::Utc::now().timestamp_millis(); + let delivery_id = delivery.delivery_id.clone(); + let event_id = delivery.event_id.clone(); + let text = format_app_server_delivery(&delivery); + + let _ = send_frame( + &out_tx, + "delivery_queued", + None, + json!({ + "delivery_id": &delivery_id, + "event_id": &event_id, + "agent": &worker_name, + "timestamp": timestamp, + }), + ) + .await; + + let result = match protocol.as_str() { + "opencode" => { + send_opencode_prompt(&http, &endpoint, &session_id, &text, auth.as_ref()) + .await + } + other => Err(anyhow::anyhow!( + "unsupported app_server protocol '{other}' (supported: opencode)" + )), + }; + + match result { + Ok(()) => { + let _ = send_frame( + &out_tx, + "delivery_injected", + None, + json!({ + "delivery_id": &delivery_id, + "event_id": &event_id, + "agent": &worker_name, + "timestamp": chrono::Utc::now().timestamp_millis(), + }), + ) + .await; + let _ = send_frame( + &out_tx, + "delivery_ack", + request_id.clone(), + json!({ + "delivery_id": &delivery_id, + "event_id": &event_id, + }), + ) + .await; + } + Err(error) => { + let reason = error.to_string(); + let _ = send_frame( + &out_tx, + "delivery_failed", + None, + json!({ + "delivery_id": &delivery_id, + "event_id": &event_id, + "reason": reason, + }), + ) + .await; + let _ = send_frame( + &out_tx, + "worker_error", + request_id, + json!({ + "code":"app_server_delivery_failed", + "message": error.to_string(), + "retryable": false, + }), + ) + .await; + } + } + } + "ping" => { + let ts = frame + .payload + .get("ts_ms") + .and_then(Value::as_u64) + .unwrap_or_default(); + let _ = send_frame(&out_tx, "pong", frame.request_id, json!({"ts_ms": ts})).await; + } + "shutdown_worker" => { + if let Err(error) = release_app_server( + &http, + &protocol, + &endpoint, + &session_id, + &release, + auth.as_ref(), + ) + .await + { + final_exit_code = Some(1); + let _ = send_frame( + &out_tx, + "worker_error", + frame.request_id, + json!({ + "code":"app_server_release_failed", + "message": error.to_string(), + "retryable": false, + }), + ) + .await; + } + break; + } + other => { + let _ = send_frame( + &out_tx, + "worker_error", + frame.request_id, + json!({ + "code":"unknown_type", + "message": format!("unsupported message type '{}'", other), + "retryable": false, + }), + ) + .await; + } + } + } + + let _ = send_frame( + &out_tx, + "worker_exited", + None, + json!({"code": final_exit_code, "signal": final_exit_signal}), + ) + .await; + drop(out_tx); + let _ = writer_task.await; + + Ok(()) +} + +fn app_server_auth_from_env() -> Option { + let auth_type = std::env::var("AGENT_RELAY_APP_SERVER_AUTH_TYPE").ok()?; + let normalized = auth_type.trim().to_ascii_lowercase(); + if normalized.is_empty() || normalized == "none" { + return None; + } + + Some(AppServerAuthConfig { + auth_type: normalized, + token: std::env::var("AGENT_RELAY_APP_SERVER_AUTH_TOKEN").ok(), + username: std::env::var("AGENT_RELAY_APP_SERVER_AUTH_USERNAME").ok(), + password: std::env::var("AGENT_RELAY_APP_SERVER_AUTH_PASSWORD").ok(), + }) +} + +fn format_app_server_delivery(delivery: &RelayDelivery) -> String { + let target = if delivery.target.trim().is_empty() { + "agent" + } else { + delivery.target.as_str() + }; + format!( + "Relay message from {} to {}:\n\n{}", + delivery.from, target, delivery.body + ) +} + +async fn send_opencode_prompt( + http: &reqwest::Client, + endpoint: &str, + session_id: &str, + text: &str, + auth: Option<&AppServerAuthConfig>, +) -> Result<()> { + let url = opencode_session_url(endpoint, session_id, "prompt_async"); + let request = http.post(&url).json(&json!({ + "parts": [ + { + "type": "text", + "text": text, + } + ] + })); + send_app_server_request(apply_app_server_auth(request, auth)).await +} + +async fn release_app_server( + http: &reqwest::Client, + protocol: &str, + endpoint: &str, + session_id: &str, + release: &str, + auth: Option<&AppServerAuthConfig>, +) -> Result<()> { + if release == "detach" || release.is_empty() { + return Ok(()); + } + if protocol != "opencode" { + anyhow::bail!("release is unsupported for app_server protocol '{protocol}'"); + } + + match release { + "abort" => { + let url = opencode_session_url(endpoint, session_id, "abort"); + send_app_server_request(apply_app_server_auth(http.post(url), auth)).await + } + "delete" => { + let url = opencode_session_url(endpoint, session_id, ""); + send_app_server_request(apply_app_server_auth(http.delete(url), auth)).await + } + other => anyhow::bail!( + "unsupported app_server release policy '{other}' (expected abort, detach, or delete)" + ), + } +} + +fn apply_app_server_auth( + request: reqwest::RequestBuilder, + auth: Option<&AppServerAuthConfig>, +) -> reqwest::RequestBuilder { + let Some(auth) = auth else { + return request; + }; + + match auth.auth_type.as_str() { + "bearer" => match auth.token.as_deref() { + Some(token) if !token.trim().is_empty() => request.bearer_auth(token), + _ => request, + }, + "basic" => match (auth.username.as_deref(), auth.password.as_deref()) { + (Some(username), Some(password)) => request.basic_auth(username, Some(password)), + _ => request, + }, + _ => request, + } +} + +async fn send_app_server_request(request: reqwest::RequestBuilder) -> Result<()> { + let response = request.send().await.context("app-server request failed")?; + if response.status().is_success() { + return Ok(()); + } + + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + anyhow::bail!("app-server request failed with status {status}: {body}"); +} + +fn opencode_session_url(endpoint: &str, session_id: &str, action: &str) -> String { + let base = endpoint.trim_end_matches('/'); + let session = urlencoding::encode(session_id); + if action.is_empty() { + format!("{base}/session/{session}") + } else { + format!("{base}/session/{session}/{action}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn opencode_session_url_escapes_session_id() { + assert_eq!( + opencode_session_url("http://127.0.0.1:4096/", "ses/one", "prompt_async"), + "http://127.0.0.1:4096/session/ses%2Fone/prompt_async" + ); + } + + #[test] + fn format_app_server_delivery_includes_relay_context() { + let delivery = RelayDelivery { + delivery_id: "del_1".into(), + event_id: "evt_1".into(), + workspace_id: None, + workspace_alias: None, + from: "Lead".into(), + target: "Worker".into(), + body: "Do the thing".into(), + thread_id: None, + priority: None, + injection_mode: MessageInjectionMode::Wait, + }; + + assert_eq!( + format_app_server_delivery(&delivery), + "Relay message from Lead to Worker:\n\nDo the thing" + ); + } +} diff --git a/crates/broker/src/runtime/mod.rs b/crates/broker/src/runtime/mod.rs index 7b6699c90..55081d8f0 100644 --- a/crates/broker/src/runtime/mod.rs +++ b/crates/broker/src/runtime/mod.rs @@ -32,7 +32,8 @@ use crate::{ }, protocol::{ AgentRuntime, AgentSpec, BrokerEvent, HeadlessProvider as ProtocolHeadlessProvider, - MessageInjectionMode, ProtocolEnvelope, RelayDelivery, PROTOCOL_VERSION, + MessageInjectionMode, ProtocolEnvelope, RelayDelivery, ResolvedHarnessConfig, + PROTOCOL_VERSION, }, relaycast::{ agent_name_eq, format_worker_preregistration_error, is_self_name, map_ws_event, @@ -48,7 +49,9 @@ use crate::{ }, }; -use crate::cli::{DumpPtyCommand, DumpPtyFormat, HeadlessCommand, InitCommand}; +use crate::cli::{ + DumpPtyCommand, DumpPtyFormat, HeadlessAppServerCommand, HeadlessCommand, InitCommand, +}; use crate::worker::{WorkerEvent, WorkerHandle, WorkerRegistry}; use crate::{broker, listen_api, routing, worker_request}; @@ -62,6 +65,7 @@ const DEFAULT_HTTP_API_EVENT_EMIT_TIMEOUT_MS: u64 = 200; static TRACING_GUARD: OnceLock = OnceLock::new(); mod api; +mod app_server; mod connection; mod delivery; mod event_loop; @@ -80,6 +84,7 @@ mod tests; mod util; mod worker_events; +pub(crate) use app_server::*; pub(crate) use connection::*; pub(crate) use delivery::*; pub(crate) use event_loop::*; diff --git a/crates/broker/src/runtime/relaycast_events.rs b/crates/broker/src/runtime/relaycast_events.rs index c3cb9ba96..b7c30c5b2 100644 --- a/crates/broker/src/runtime/relaycast_events.rs +++ b/crates/broker/src/runtime/relaycast_events.rs @@ -204,6 +204,29 @@ impl BrokerRuntime { let cli = event.agent.cli; let task = Some(event.agent.task).filter(|value| !value.trim().is_empty()); let channel = event.agent.channel; + let harness_config = match relaycast_harness_config(&ws_value) { + Ok(config) => config, + Err(error) => { + tracing::warn!( + worker = %name, + error = %error, + "rejecting relaycast spawn with invalid harness config" + ); + eprintln!( + "[agent-relay] rejecting spawn request for '{}': {}", + name, error + ); + return; + } + }; + let runtime = harness_config + .as_ref() + .map(ResolvedHarnessConfig::runtime) + .unwrap_or(AgentRuntime::Pty); + let session_id = harness_config + .as_ref() + .and_then(ResolvedHarnessConfig::session_id) + .map(ToOwned::to_owned); tracing::info!(name = %name, cli = %cli, task = ?task, channel = ?channel, "handling spawn request from relaycast WS"); let channels = channel @@ -219,12 +242,13 @@ impl BrokerRuntime { .unwrap_or_else(default_spawn_channels); let spec = AgentSpec { name: name.clone(), - runtime: AgentRuntime::Pty, + runtime: runtime.clone(), provider: None, cli: Some(cli.clone()), + session_id, + harness_config, model: None, cwd: None, - session_id: None, team: None, shadow_of: None, shadow_mode: None, @@ -307,11 +331,11 @@ impl BrokerRuntime { has_task: effective_task.is_some(), is_shadow: false, }); - let pid = workers.worker_pid(&name).unwrap_or(0); + let pid = workers.harness_pid(&name); state.agents.insert( name.clone(), broker::PersistedAgent { - runtime: AgentRuntime::Pty, + runtime: effective_spec.runtime.clone(), parent: Some("Relaycast".to_string()), channels, pid: workers.worker_pid(&name), @@ -334,7 +358,7 @@ impl BrokerRuntime { json!({ "kind": "agent_spawned", "name": name, - "runtime": "pty", + "runtime": runtime_label(&effective_spec.runtime), "cli": cli, "model": effective_spec.model.clone(), "sessionId": effective_spec.session_id.clone(), @@ -351,7 +375,7 @@ impl BrokerRuntime { Some("relaycast_spawn"), ) .await; - tracing::info!(child = %name, pid, "spawned worker via relaycast WS"); + tracing::info!(child = %name, pid = ?pid, "spawned worker via relaycast WS"); eprintln!("[agent-relay] spawned worker '{}' via relaycast", name); } Err(e) => { @@ -430,14 +454,38 @@ impl BrokerRuntime { chs }) .unwrap_or_else(default_spawn_channels); + let harness_config = match relaycast_harness_config(&ws_value) { + Ok(config) => config, + Err(error) => { + tracing::warn!( + worker = %name, + error = %error, + "rejecting relaycast fallback spawn with invalid harness config" + ); + eprintln!( + "[agent-relay] rejecting spawn request for '{}': {}", + name, error + ); + return; + } + }; + let runtime = harness_config + .as_ref() + .map(ResolvedHarnessConfig::runtime) + .unwrap_or(AgentRuntime::Pty); + let session_id = harness_config + .as_ref() + .and_then(ResolvedHarnessConfig::session_id) + .map(ToOwned::to_owned); let spec = AgentSpec { name: WorkerName::from(name.clone()), - runtime: AgentRuntime::Pty, + runtime: runtime.clone(), provider: None, cli: Some(cli.clone()), + session_id, + harness_config, model: None, cwd: None, - session_id: None, team: None, shadow_of: None, shadow_mode: None, @@ -504,11 +552,11 @@ impl BrokerRuntime { has_task: effective_task.is_some(), is_shadow: false, }); - let pid = workers.worker_pid(&name).unwrap_or(0); + let pid = workers.harness_pid(&name); state.agents.insert( WorkerName::from(name.clone()), broker::PersistedAgent { - runtime: AgentRuntime::Pty, + runtime: effective_spec.runtime.clone(), parent: Some("Relaycast".to_string()), channels, pid: workers.worker_pid(&name), @@ -531,7 +579,7 @@ impl BrokerRuntime { json!({ "kind": "agent_spawned", "name": name, - "runtime": "pty", + "runtime": runtime_label(&effective_spec.runtime), "cli": cli, "model": effective_spec.model.clone(), "sessionId": effective_spec.session_id.clone(), @@ -883,3 +931,89 @@ impl BrokerRuntime { } } } + +fn relaycast_harness_config(value: &Value) -> Result, String> { + let agent = value.get("agent"); + let harness_id = agent + .and_then(|agent| { + agent + .get("harnessId") + .or_else(|| agent.get("harness_id")) + .and_then(Value::as_str) + }) + .or_else(|| { + value + .get("harnessId") + .or_else(|| value.get("harness_id")) + .and_then(Value::as_str) + }) + .map(str::trim) + .filter(|id| !id.is_empty()); + if harness_id.is_some() { + return Err( + "harnessId is not supported by Relaycast spawns; send harnessConfig".to_string(), + ); + } + + let raw = agent + .and_then(|agent| { + agent + .get("harnessConfig") + .or_else(|| agent.get("harness_config")) + }) + .or_else(|| { + value + .get("harnessConfig") + .or_else(|| value.get("harness_config")) + }); + + match raw { + Some(config) => serde_json::from_value::(config.clone()) + .map(Some) + .map_err(|error| format!("Invalid harnessConfig: {error}")), + None => Ok(None), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn relaycast_harness_config_accepts_inline_config() { + let value = json!({ + "type": "agent.spawn_requested", + "agent": { + "name": "ClaudeReviewer", + "cli": "company-claude", + "harnessConfig": { + "runtime": "pty", + "command": "claude", + "args": [] + } + } + }); + + let config = relaycast_harness_config(&value) + .expect("inline config should parse") + .expect("inline config should return config"); + + assert_eq!(config.runtime(), AgentRuntime::Pty); + } + + #[test] + fn relaycast_harness_config_rejects_harness_id() { + let value = json!({ + "type": "agent.spawn_requested", + "agent": { + "name": "ClaudeReviewer", + "cli": "company-claude", + "harnessId": "company-claude" + } + }); + + let error = relaycast_harness_config(&value).expect_err("harnessId should fail"); + + assert!(error.contains("harnessId is not supported")); + } +} diff --git a/crates/broker/src/runtime/spawn_spec.rs b/crates/broker/src/runtime/spawn_spec.rs index 3101db16c..feca1e02a 100644 --- a/crates/broker/src/runtime/spawn_spec.rs +++ b/crates/broker/src/runtime/spawn_spec.rs @@ -20,8 +20,9 @@ pub(crate) fn build_http_api_spawn_spec( shadow_of: Option, shadow_mode: Option, restart_policy: Option, + harness_config: Option, ) -> Result { - let runtime = match transport + let requested_runtime = match transport .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) @@ -34,6 +35,25 @@ pub(crate) fn build_http_api_spawn_spec( anyhow::bail!("unsupported transport '{other}' (expected 'pty' or 'headless')") } }; + let harness_runtime = harness_config.as_ref().map(ResolvedHarnessConfig::runtime); + let runtime = match ( + transport + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()), + harness_runtime, + ) { + (None, Some(harness_runtime)) => harness_runtime, + (_, Some(harness_runtime)) if harness_runtime == requested_runtime => requested_runtime, + (_, Some(harness_runtime)) => { + anyhow::bail!( + "harnessConfig runtime '{}' does not match requested transport '{}'", + runtime_label(&harness_runtime), + runtime_label(&requested_runtime) + ) + } + (_, None) => requested_runtime, + }; let parsed_restart_policy = match restart_policy { Some(v) => Some(serde_json::from_value(v).context("invalid restart_policy")?), None => None, @@ -41,24 +61,32 @@ pub(crate) fn build_http_api_spawn_spec( let (provider, cli_command, model) = match runtime { AgentRuntime::Pty => (None, Some(cli), model), - AgentRuntime::Headless => { - let provider = headless_provider_from_cli(&cli).with_context(|| { - format!( - "provider '{cli}' does not support headless transport (supported: claude, opencode)" - ) - })?; - (Some(provider), None, model) - } + AgentRuntime::Headless => match harness_config.as_ref() { + Some(ResolvedHarnessConfig::Headless(_)) => (None, Some(cli), model), + _ => { + let provider = headless_provider_from_cli(&cli).with_context(|| { + format!( + "provider '{cli}' does not support headless transport (supported: claude, opencode)" + ) + })?; + (Some(provider), None, model) + } + }, }; + let session_id = harness_config + .as_ref() + .and_then(ResolvedHarnessConfig::session_id) + .map(ToOwned::to_owned); Ok(AgentSpec { name, runtime, provider, cli: cli_command, + session_id, + harness_config, model, cwd, - session_id: None, team, shadow_of, shadow_mode, diff --git a/crates/broker/src/runtime/tests.rs b/crates/broker/src/runtime/tests.rs index 021669a42..b9bede14f 100644 --- a/crates/broker/src/runtime/tests.rs +++ b/crates/broker/src/runtime/tests.rs @@ -9,7 +9,10 @@ use std::{ use crate::ids::{ ChannelName, DeliveryId, EventId, MessageTarget, WorkerName, WorkspaceAlias, WorkspaceId, }; -use crate::protocol::{AgentSpec, MessageInjectionMode, RelayDelivery}; +use crate::protocol::{ + AgentSpec, HarnessReleasePolicy, HeadlessHarnessConfig, HeadlessHarnessDriver, + MessageInjectionMode, RelayDelivery, ResolvedHarnessConfig, +}; use crate::worker::{AgentWorkState, WorkerEvent, WorkerHandle, WorkerRegistry}; use crate::{ broker::injection_format::format_injection, @@ -71,9 +74,10 @@ async fn make_worker_registry_with_worker(name: &str) -> WorkerRegistry { runtime: AgentRuntime::Pty, provider: None, cli: Some("cat".to_string()), + session_id: None, + harness_config: None, model: None, cwd: None, - session_id: None, team: None, shadow_of: None, shadow_mode: None, @@ -85,6 +89,7 @@ async fn make_worker_registry_with_worker(name: &str) -> WorkerRegistry { workspace_id: Some(WorkspaceId::new("ws_demo")), child, stdin, + harness_pid: None, spawned_at: Instant::now(), last_activity_at: Instant::now(), context_budget_pct: None, @@ -318,9 +323,11 @@ async fn delivery_retry_transient_blip_emits_failed_event_for_present_worker() { { Ok(outcome @ DeliveryAttemptOutcome::Failed { attempts, .. }) => { assert_eq!(attempts, MAX_DELIVERY_RETRIES); + // Some platforms can accept a final pipe write after the child exits, + // so terminal failure may arrive on the immediate post-cap check. assert!( - retry_index <= MAX_DELIVERY_RETRIES, - "retry loop must terminate within the retry cap" + retry_index >= MAX_DELIVERY_RETRIES, + "delivery should not fail before the retry cap is exhausted" ); final_outcome = Some(outcome); break; @@ -1937,6 +1944,7 @@ fn http_api_spawn_spec_defaults_to_pty_runtime() { Some(WorkerName::from("Lead")), Some("subagent".to_string()), None, + None, ) .expect("spec should build"); @@ -1960,6 +1968,7 @@ fn http_api_spawn_spec_uses_headless_runtime_for_supported_providers() { None, None, None, + None, ) .expect("headless spec should build"); @@ -1972,6 +1981,71 @@ fn http_api_spawn_spec_uses_headless_runtime_for_supported_providers() { assert_eq!(spec.model.as_deref(), Some("ignored")); } +#[test] +fn http_api_spawn_spec_uses_headless_runtime_for_app_server_harness_config() { + let harness_config = ResolvedHarnessConfig::Headless(HeadlessHarnessConfig { + driver: HeadlessHarnessDriver::AppServer, + protocol: "opencode".to_string(), + endpoint: "http://127.0.0.1:4096".to_string(), + session_id: "ses_123".to_string(), + auth: None, + host: None, + release: Some(HarnessReleasePolicy::Abort), + metadata: None, + }); + + let spec = build_http_api_spawn_spec( + WorkerName::from("worker-a"), + "opencode-server".to_string(), + None, + None, + vec![], + vec![ChannelName::from("general")], + None, + None, + None, + None, + None, + Some(harness_config), + ) + .expect("headless app-server harness spec should build"); + + assert!(matches!(spec.runtime, AgentRuntime::Headless)); + assert!(spec.provider.is_none()); + assert_eq!(spec.cli.as_deref(), Some("opencode-server")); + assert_eq!(spec.session_id.as_deref(), Some("ses_123")); + assert!(matches!( + spec.harness_config, + Some(ResolvedHarnessConfig::Headless(_)) + )); +} + +#[test] +fn http_api_spawn_spec_rejects_unknown_headless_provider_without_harness_config() { + let error = build_http_api_spawn_spec( + WorkerName::from("worker-a"), + "opencode-server".to_string(), + Some("headless".to_string()), + None, + vec![], + vec![ChannelName::from("general")], + None, + None, + None, + None, + None, + None, + ) + .expect_err("custom headless provider without harness config should fail"); + + assert!( + error + .to_string() + .contains("does not support headless transport"), + "unexpected error: {error}" + ); +} + #[test] fn headless_provider_command_claude_places_flags_before_task() { let (bin, args) = super::headless_provider_command( @@ -2020,6 +2094,7 @@ fn http_api_spawn_spec_rejects_unknown_headless_providers() { None, None, None, + None, ) .expect_err("unsupported headless provider should fail"); diff --git a/crates/broker/src/runtime/util.rs b/crates/broker/src/runtime/util.rs index 0a7095c9c..ac2c399db 100644 --- a/crates/broker/src/runtime/util.rs +++ b/crates/broker/src/runtime/util.rs @@ -227,119 +227,6 @@ pub(crate) fn delivery_retry_interval() -> Duration { Duration::from_millis(ms.max(50)) } -#[cfg(test)] -mod tests { - use super::{ - broker_log_dir, broker_log_file_prefix, sanitize_filename_segment, tracing_destination, - tracing_filter_directive, TracingDestination, - }; - - #[test] - fn tracing_destination_defaults_to_file() { - assert_eq!(tracing_destination(None), TracingDestination::File); - assert_eq!(tracing_destination(Some("")), TracingDestination::File); - assert_eq!(tracing_destination(Some(" ")), TracingDestination::File); - } - - #[test] - fn tracing_destination_recognises_off_aliases() { - for value in ["off", "OFF", "none", "0", "false", "no"] { - assert_eq!( - tracing_destination(Some(value)), - TracingDestination::Off, - "value `{value}` should disable tracing" - ); - } - } - - #[test] - fn tracing_destination_recognises_stderr_aliases() { - for value in ["stderr", "STDERR", "print", "Print"] { - assert_eq!( - tracing_destination(Some(value)), - TracingDestination::Stderr, - "value `{value}` should route to stderr" - ); - } - } - - #[test] - fn tracing_destination_recognises_file_aliases() { - for value in ["file", "FILE", "1", "true", "yes", "on", "unknown-value"] { - assert_eq!( - tracing_destination(Some(value)), - TracingDestination::File, - "value `{value}` should route to file" - ); - } - } - - #[test] - fn tracing_filter_defaults_to_info_when_rust_log_unset() { - assert_eq!(tracing_filter_directive(None), "info"); - assert_eq!(tracing_filter_directive(Some("")), "info"); - assert_eq!(tracing_filter_directive(Some(" ")), "info"); - } - - #[test] - fn tracing_filter_prefers_rust_log_directive() { - assert_eq!( - tracing_filter_directive(Some("agent_relay::worker::pty=debug")), - "agent_relay::worker::pty=debug" - ); - } - - #[test] - fn sanitize_filename_segment_replaces_unsafe_chars() { - assert_eq!( - sanitize_filename_segment("agent name/with:weird*chars"), - "agent-name-with-weird-chars" - ); - } - - #[test] - fn sanitize_filename_segment_keeps_safe_chars() { - assert_eq!(sanitize_filename_segment("alpha-Beta_01"), "alpha-Beta_01"); - } - - #[test] - fn sanitize_filename_segment_falls_back_to_broker() { - assert_eq!(sanitize_filename_segment(""), "broker"); - assert_eq!(sanitize_filename_segment("///"), "---"); - } - - #[test] - fn broker_log_file_prefix_includes_log_suffix() { - assert_eq!(broker_log_file_prefix("my-broker"), "my-broker.log"); - assert_eq!(broker_log_file_prefix(""), "broker.log"); - } - - #[test] - fn broker_log_dir_uses_platform_standard_layout() { - let Some(dir) = broker_log_dir() else { - return; - }; - let path_str = dir.to_string_lossy().replace('\\', "/"); - - if cfg!(target_os = "macos") { - assert!( - path_str.contains("/Library/Logs/agent-relay"), - "expected macOS Library/Logs path, got: {path_str}" - ); - } else if cfg!(target_os = "windows") { - assert!( - path_str.to_ascii_lowercase().contains("agent-relay/logs"), - "expected Windows LocalAppData layout, got: {path_str}" - ); - } else { - assert!( - path_str.contains("agent-relay/logs"), - "expected Unix state path, got: {path_str}" - ); - } - } -} - pub(crate) fn http_api_local_delivery_timeout() -> Duration { let ms = std::env::var("AGENT_RELAY_HTTP_API_LOCAL_DELIVERY_TIMEOUT_MS") .ok() @@ -463,3 +350,116 @@ pub(crate) fn extract_mcp_message_ids(buffer: &str) -> Vec { } ids } + +#[cfg(test)] +mod tests { + use super::{ + broker_log_dir, broker_log_file_prefix, sanitize_filename_segment, tracing_destination, + tracing_filter_directive, TracingDestination, + }; + + #[test] + fn tracing_destination_defaults_to_file() { + assert_eq!(tracing_destination(None), TracingDestination::File); + assert_eq!(tracing_destination(Some("")), TracingDestination::File); + assert_eq!(tracing_destination(Some(" ")), TracingDestination::File); + } + + #[test] + fn tracing_destination_recognises_off_aliases() { + for value in ["off", "OFF", "none", "0", "false", "no"] { + assert_eq!( + tracing_destination(Some(value)), + TracingDestination::Off, + "value `{value}` should disable tracing" + ); + } + } + + #[test] + fn tracing_destination_recognises_stderr_aliases() { + for value in ["stderr", "STDERR", "print", "Print"] { + assert_eq!( + tracing_destination(Some(value)), + TracingDestination::Stderr, + "value `{value}` should route to stderr" + ); + } + } + + #[test] + fn tracing_destination_recognises_file_aliases() { + for value in ["file", "FILE", "1", "true", "yes", "on", "unknown-value"] { + assert_eq!( + tracing_destination(Some(value)), + TracingDestination::File, + "value `{value}` should route to file" + ); + } + } + + #[test] + fn tracing_filter_defaults_to_info_when_rust_log_unset() { + assert_eq!(tracing_filter_directive(None), "info"); + assert_eq!(tracing_filter_directive(Some("")), "info"); + assert_eq!(tracing_filter_directive(Some(" ")), "info"); + } + + #[test] + fn tracing_filter_prefers_rust_log_directive() { + assert_eq!( + tracing_filter_directive(Some("agent_relay::worker::pty=debug")), + "agent_relay::worker::pty=debug" + ); + } + + #[test] + fn sanitize_filename_segment_replaces_unsafe_chars() { + assert_eq!( + sanitize_filename_segment("agent name/with:weird*chars"), + "agent-name-with-weird-chars" + ); + } + + #[test] + fn sanitize_filename_segment_keeps_safe_chars() { + assert_eq!(sanitize_filename_segment("alpha-Beta_01"), "alpha-Beta_01"); + } + + #[test] + fn sanitize_filename_segment_falls_back_to_broker() { + assert_eq!(sanitize_filename_segment(""), "broker"); + assert_eq!(sanitize_filename_segment("///"), "---"); + } + + #[test] + fn broker_log_file_prefix_includes_log_suffix() { + assert_eq!(broker_log_file_prefix("my-broker"), "my-broker.log"); + assert_eq!(broker_log_file_prefix(""), "broker.log"); + } + + #[test] + fn broker_log_dir_uses_platform_standard_layout() { + let Some(dir) = broker_log_dir() else { + return; + }; + let path_str = dir.to_string_lossy().replace('\\', "/"); + + if cfg!(target_os = "macos") { + assert!( + path_str.contains("/Library/Logs/agent-relay"), + "expected macOS Library/Logs path, got: {path_str}" + ); + } else if cfg!(target_os = "windows") { + assert!( + path_str.to_ascii_lowercase().contains("agent-relay/logs"), + "expected Windows LocalAppData layout, got: {path_str}" + ); + } else { + assert!( + path_str.contains("agent-relay/logs"), + "expected Unix state path, got: {path_str}" + ); + } + } +} diff --git a/crates/broker/src/runtime/worker_events.rs b/crates/broker/src/runtime/worker_events.rs index 1e8b267d1..dc576a086 100644 --- a/crates/broker/src/runtime/worker_events.rs +++ b/crates/broker/src/runtime/worker_events.rs @@ -316,18 +316,28 @@ impl BrokerRuntime { .and_then(|p| p.get("runtime")) .and_then(Value::as_str) .unwrap_or("pty"); - let (provider_val, cli_val, model_val, session_id_val) = workers + let payload_pid = value + .get("payload") + .and_then(|p| p.get("pid")) + .and_then(Value::as_u64) + .filter(|pid| *pid <= u32::MAX as u64) + .map(|pid| pid as u32); + let (provider_val, cli_val, model_val, session_id_val, pid_val) = workers .workers - .get(&name) + .get_mut(&name) .map(|h| { + if let Some(pid) = payload_pid { + h.harness_pid = Some(pid); + } ( h.spec.provider.clone(), h.spec.cli.clone(), h.spec.model.clone(), h.spec.session_id.clone(), + h.harness_pid, ) }) - .unwrap_or((None, None, None, None)); + .unwrap_or((None, None, None, None, None)); let _ = send_event( sdk_out_tx, json!({ @@ -338,6 +348,7 @@ impl BrokerRuntime { "cli": cli_val, "model": model_val, "sessionId": session_id_val, + "pid": pid_val, }), ) .await; diff --git a/crates/broker/src/supervisor.rs b/crates/broker/src/supervisor.rs index 43b5f2fd1..6ab902e5c 100644 --- a/crates/broker/src/supervisor.rs +++ b/crates/broker/src/supervisor.rs @@ -230,9 +230,10 @@ mod tests { runtime: AgentRuntime::Pty, provider: None, cli: Some("claude".to_string()), + session_id: None, + harness_config: None, model: None, cwd: None, - session_id: None, team: None, shadow_of: None, shadow_mode: None, diff --git a/crates/broker/src/worker.rs b/crates/broker/src/worker.rs index fd2b765d5..25a9fe3d9 100644 --- a/crates/broker/src/worker.rs +++ b/crates/broker/src/worker.rs @@ -8,7 +8,11 @@ use std::{ use crate::{ ids::{RequestId, WorkerName}, metrics::MetricsCollector, - protocol::{AgentRuntime, AgentSpec, ProtocolEnvelope, RelayDelivery, PROTOCOL_VERSION}, + protocol::{ + AgentRuntime, AgentSpec, AppServerAuthType, AppServerHostOwnership, HarnessReleasePolicy, + HeadlessHarnessConfig, HeadlessHarnessDriver, ProtocolEnvelope, RelayDelivery, + ResolvedHarnessConfig, PROTOCOL_VERSION, + }, relaycast::configure_relaycast_mcp_with_result, supervisor::Supervisor, types::AgentResultMcpConfig, @@ -30,6 +34,15 @@ use crate::{ spawner::terminate_child, }; +const APP_SERVER_AUTH_ENV_KEYS: [&str; 4] = [ + "AGENT_RELAY_APP_SERVER_AUTH_TYPE", + "AGENT_RELAY_APP_SERVER_AUTH_TOKEN", + "AGENT_RELAY_APP_SERVER_AUTH_USERNAME", + "AGENT_RELAY_APP_SERVER_AUTH_PASSWORD", +]; +const DEFAULT_RELEASE_GRACE: Duration = Duration::from_secs(2); +const APP_SERVER_RELEASE_GRACE: Duration = Duration::from_secs(35); + pub(crate) mod detection; #[derive(Debug)] @@ -39,6 +52,7 @@ pub(crate) struct WorkerHandle { pub(crate) workspace_id: Option, pub(crate) child: Child, pub(crate) stdin: ChildStdin, + pub(crate) harness_pid: Option, pub(crate) spawned_at: Instant, pub(crate) last_activity_at: Instant, pub(crate) context_budget_pct: Option, @@ -138,7 +152,9 @@ impl WorkerRegistry { "team": handle.spec.team, "channels": handle.spec.channels, "parent": handle.parent, - "pid": handle.child.id(), + "sessionId": handle.spec.session_id, + "pid": handle.harness_pid, + "workerPid": handle.child.id(), "last_activity_ms": handle.last_activity_at.elapsed().as_millis() as u64, "last_activity_at": chrono::Utc::now() - chrono::Duration::from_std(handle.last_activity_at.elapsed()).unwrap_or_default(), @@ -198,6 +214,10 @@ impl WorkerRegistry { self.workers.get(name).and_then(|h| h.child.id()) } + pub(crate) fn harness_pid(&self, name: &str) -> Option { + self.workers.get(name).and_then(|h| h.harness_pid) + } + #[allow(clippy::too_many_arguments)] pub(crate) async fn spawn( &mut self, @@ -226,15 +246,28 @@ impl WorkerRegistry { let mut command = Command::new(std::env::current_exe().context("failed to locate current executable")?); + let mut harness_env: Vec<(String, String)> = Vec::new(); + let mut suppress_worker_env: Vec<&'static str> = Vec::new(); + let mut initial_harness_pid: Option = None; + + match spec.harness_config.clone() { + Some(ResolvedHarnessConfig::Pty(config)) => { + spec.runtime = AgentRuntime::Pty; + if spec.session_id.is_none() { + spec.session_id = config.session_id.clone(); + } + if spec.cwd.is_none() { + spec.cwd = config.cwd.clone(); + } + if let Some(env) = config.env { + harness_env.extend(env); + } - match spec.runtime { - AgentRuntime::Pty => { - let cli = spec.cli.as_deref().context("pty runtime requires `cli`")?; - let (resolved_cli, inline_cli_args) = parse_cli_command(cli) - .with_context(|| format!("invalid CLI command '{cli}'"))?; + let (resolved_cli, inline_cli_args) = parse_cli_command(&config.command) + .with_context(|| format!("invalid harness command '{}'", config.command))?; let normalized_cli = normalize_cli_name(&resolved_cli); let mut effective_args = inline_cli_args; - effective_args.extend(spec.args.clone()); + effective_args.extend(config.args.clone()); command.arg("pty"); command.arg("--agent-name").arg(&spec.name); @@ -258,46 +291,48 @@ impl WorkerRegistry { spec.model = Some(model); } let mut harness_session_args = Vec::new(); - if is_claude { - spec.session_id = prepare_claude_session_args(&mut effective_args); - } else if is_codex { - match codex_session_reference(&effective_args) { - CodexSessionReference::Known(thread_id) => { - spec.session_id = Some(thread_id); - } - CodexSessionReference::Unknown => {} - CodexSessionReference::None => { - if codex_has_positional_arg(&effective_args) { - tracing::debug!( - worker = %spec.name, - "not pre-creating Codex session because args contain a positional prompt or subcommand" - ); - } else { - let cwd = Path::new(spec.cwd.as_deref().unwrap_or(".")); - match crate::codex_session::create_resumable_codex_thread( - &resolved_cli, - cwd, - &self.worker_env, - &effective_args, - ) - .await - { - Ok(thread_id) => { - tracing::info!( - worker = %spec.name, - session_id = %thread_id, - "created resumable Codex session for spawned PTY" - ); - spec.session_id = Some(thread_id.clone()); - harness_session_args.push("resume".to_string()); - harness_session_args.push(thread_id); - } - Err(err) => { - tracing::warn!( - worker = %spec.name, - error = %err, - "failed to pre-create resumable Codex session; spawning without sessionId" - ); + if spec.session_id.is_none() { + if is_claude { + spec.session_id = prepare_claude_session_args(&mut effective_args); + } else if is_codex { + match codex_session_reference(&effective_args) { + CodexSessionReference::Known(thread_id) => { + spec.session_id = Some(thread_id); + } + CodexSessionReference::Unknown => {} + CodexSessionReference::None => { + if codex_has_positional_arg(&effective_args) { + tracing::debug!( + worker = %spec.name, + "not pre-creating Codex session because args contain a positional prompt or subcommand" + ); + } else { + let cwd = Path::new(spec.cwd.as_deref().unwrap_or(".")); + match crate::codex_session::create_resumable_codex_thread( + &resolved_cli, + cwd, + &self.worker_env, + &effective_args, + ) + .await + { + Ok(thread_id) => { + tracing::info!( + worker = %spec.name, + session_id = %thread_id, + "created resumable Codex session for spawned PTY" + ); + spec.session_id = Some(thread_id.clone()); + harness_session_args.push("resume".to_string()); + harness_session_args.push(thread_id); + } + Err(err) => { + tracing::warn!( + worker = %spec.name, + error = %err, + "failed to pre-create resumable Codex session; spawning without sessionId" + ); + } } } } @@ -335,7 +370,7 @@ impl WorkerRegistry { let mcp_args = self .build_mcp_args( - cli, + &resolved_cli, &spec.name, &effective_args, Path::new(spec.cwd.as_deref().unwrap_or(".")), @@ -382,64 +417,268 @@ impl WorkerRegistry { } } } - AgentRuntime::Headless => { - let provider = spec - .provider - .as_ref() - .context("headless runtime requires `provider`")?; - command.arg("headless"); + Some(ResolvedHarnessConfig::Headless(config)) => { + validate_app_server_config(&config)?; + spec.runtime = AgentRuntime::Headless; + spec.session_id = Some(config.session_id.clone()); + initial_harness_pid = config.host.as_ref().and_then(|host| host.pid); + match &config.driver { + HeadlessHarnessDriver::AppServer => {} + } + + command.arg("app-server"); command.arg("--agent-name").arg(&spec.name); - let provider_cli = headless_provider_cli_name(provider); - command.arg(provider_cli); - if let Some(model) = apply_codex_model_arg_fallback( - provider_cli, - provider_cli, - &spec.name, - &mut spec.args, - ) - .await - { - spec.model = Some(model); + command.arg("--protocol").arg(&config.protocol); + command.arg("--endpoint").arg(&config.endpoint); + command.arg("--session-id").arg(&config.session_id); + if let Some(pid) = initial_harness_pid { + command.arg("--host-pid").arg(pid.to_string()); } + command + .arg("--release") + .arg(release_policy_arg(config.release.as_ref())); - let mcp_args = self - .build_mcp_args( - provider_cli, + suppress_worker_env.extend(APP_SERVER_AUTH_ENV_KEYS); + for key in APP_SERVER_AUTH_ENV_KEYS { + command.env_remove(key); + } + + if let Some(auth) = config.auth { + harness_env.push(( + "AGENT_RELAY_APP_SERVER_AUTH_TYPE".to_string(), + app_server_auth_type_arg(&auth.auth_type).to_string(), + )); + if let Some(token) = auth.token { + harness_env.push(("AGENT_RELAY_APP_SERVER_AUTH_TOKEN".to_string(), token)); + } + if let Some(username) = auth.username { + harness_env + .push(("AGENT_RELAY_APP_SERVER_AUTH_USERNAME".to_string(), username)); + } + if let Some(password) = auth.password { + harness_env + .push(("AGENT_RELAY_APP_SERVER_AUTH_PASSWORD".to_string(), password)); + } + } + } + None => match spec.runtime { + AgentRuntime::Pty => { + let cli = spec.cli.as_deref().context("pty runtime requires `cli`")?; + let (resolved_cli, inline_cli_args) = parse_cli_command(cli) + .with_context(|| format!("invalid CLI command '{cli}'"))?; + let normalized_cli = normalize_cli_name(&resolved_cli); + let mut effective_args = inline_cli_args; + effective_args.extend(spec.args.clone()); + + command.arg("pty"); + command.arg("--agent-name").arg(&spec.name); + if let Some(secs) = idle_threshold_secs { + command.arg("--idle-threshold-secs").arg(secs.to_string()); + } + command.arg(&resolved_cli); + + let cli_lower = normalized_cli.to_lowercase(); + let is_claude = cli_lower == "claude" || cli_lower.starts_with("claude:"); + let is_codex = cli_lower == "codex"; + let is_gemini = cli_lower == "gemini"; + if let Some(model) = apply_codex_model_arg_fallback( + &resolved_cli, + &cli_lower, &spec.name, - &spec.args, - Path::new(spec.cwd.as_deref().unwrap_or(".")), - worker_relay_api_key.as_deref(), - skip_relay_prompt, - agent_result.as_ref(), + &mut effective_args, ) - .await?; + .await + { + spec.model = Some(model); + } + let mut harness_session_args = Vec::new(); + if spec.session_id.is_none() { + if is_claude { + spec.session_id = prepare_claude_session_args(&mut effective_args); + } else if is_codex { + match codex_session_reference(&effective_args) { + CodexSessionReference::Known(thread_id) => { + spec.session_id = Some(thread_id); + } + CodexSessionReference::Unknown => {} + CodexSessionReference::None => { + if codex_has_positional_arg(&effective_args) { + tracing::debug!( + worker = %spec.name, + "not pre-creating Codex session because args contain a positional prompt or subcommand" + ); + } else { + let cwd = Path::new(spec.cwd.as_deref().unwrap_or(".")); + match crate::codex_session::create_resumable_codex_thread( + &resolved_cli, + cwd, + &self.worker_env, + &effective_args, + ) + .await + { + Ok(thread_id) => { + tracing::info!( + worker = %spec.name, + session_id = %thread_id, + "created resumable Codex session for spawned PTY" + ); + spec.session_id = Some(thread_id.clone()); + harness_session_args.push("resume".to_string()); + harness_session_args.push(thread_id); + } + Err(err) => { + tracing::warn!( + worker = %spec.name, + error = %err, + "failed to pre-create resumable Codex session; spawning without sessionId" + ); + } + } + } + } + } + } + } + // NOTE: Permission-bypass flags are auto-injected for all spawned agents. + // This means any actor who can trigger agent.add gets agents with no permission + // guardrails. Future work should make this an explicit opt-in per step/agent. + let bypass_flag: Option<&str> = if is_claude + && !effective_args + .iter() + .any(|a| a.contains("dangerously-skip-permissions")) + { + Some("--dangerously-skip-permissions") + } else if is_codex + && !effective_args + .iter() + .any(|a| a.contains("dangerously-bypass") || a.contains("full-auto")) + { + Some("--dangerously-bypass-approvals-and-sandbox") + } else if is_gemini + && !effective_args.iter().any(|a| a == "--yolo" || a == "-y") + { + Some("--yolo") + } else { + None + }; - let model_arg = resolve_model_flag_for_cli( - provider_cli, - provider_cli, - &spec.name, - spec.model.as_deref(), - &spec.args, - ) - .await; - if let Some(ref model) = model_arg { - spec.model = Some(model.clone()); - } + if let Some(flag) = bypass_flag { + tracing::warn!( + worker = %spec.name, + flag = %flag, + "auto-injecting permission-bypass flag for spawned agent" + ); + } - if model_arg.is_some() || !spec.args.is_empty() || !mcp_args.is_empty() { - command.arg("--"); - if let Some(model) = model_arg { - command.arg("--model"); - command.arg(model); + let mcp_args = self + .build_mcp_args( + cli, + &spec.name, + &effective_args, + Path::new(spec.cwd.as_deref().unwrap_or(".")), + worker_relay_api_key.as_deref(), + skip_relay_prompt, + agent_result.as_ref(), + ) + .await?; + + let model_flag = resolve_model_flag_for_cli( + &resolved_cli, + &cli_lower, + &spec.name, + spec.model.as_deref(), + &effective_args, + ) + .await; + if let Some(ref model) = model_flag { + spec.model = Some(model.clone()); } - for arg in &mcp_args { - command.arg(arg); + + let has_extra = bypass_flag.is_some() + || model_flag.is_some() + || !effective_args.is_empty() + || !mcp_args.is_empty() + || !harness_session_args.is_empty(); + if has_extra { + command.arg("--"); + if let Some(flag) = bypass_flag { + command.arg(flag); + } + if let Some(ref model) = model_flag { + command.arg("--model"); + command.arg(model); + } + for arg in &mcp_args { + command.arg(arg); + } + for arg in &effective_args { + command.arg(arg); + } + for arg in &harness_session_args { + command.arg(arg); + } } - for arg in &spec.args { - command.arg(arg); + } + AgentRuntime::Headless => { + let provider = spec + .provider + .as_ref() + .context("headless runtime requires `provider`")?; + command.arg("headless"); + command.arg("--agent-name").arg(&spec.name); + let provider_cli = headless_provider_cli_name(provider); + command.arg(provider_cli); + if let Some(model) = apply_codex_model_arg_fallback( + provider_cli, + provider_cli, + &spec.name, + &mut spec.args, + ) + .await + { + spec.model = Some(model); + } + + let mcp_args = self + .build_mcp_args( + provider_cli, + &spec.name, + &spec.args, + Path::new(spec.cwd.as_deref().unwrap_or(".")), + worker_relay_api_key.as_deref(), + skip_relay_prompt, + agent_result.as_ref(), + ) + .await?; + + let model_arg = resolve_model_flag_for_cli( + provider_cli, + provider_cli, + &spec.name, + spec.model.as_deref(), + &spec.args, + ) + .await; + if let Some(ref model) = model_arg { + spec.model = Some(model.clone()); + } + + if model_arg.is_some() || !spec.args.is_empty() || !mcp_args.is_empty() { + command.arg("--"); + if let Some(model) = model_arg { + command.arg("--model"); + command.arg(model); + } + for arg in &mcp_args { + command.arg(arg); + } + for arg in &spec.args { + command.arg(arg); + } } } - } + }, } command @@ -447,6 +686,12 @@ impl WorkerRegistry { .stdout(Stdio::piped()) .stderr(Stdio::piped()); for (key, value) in &self.worker_env { + if suppress_worker_env.contains(&key.as_str()) { + continue; + } + command.env(key, value); + } + for (key, value) in &harness_env { command.env(key, value); } if let Some(config) = &agent_result { @@ -454,7 +699,7 @@ impl WorkerRegistry { command.env(key, value); } } - if !skip_relay_prompt && !matches!(spec.runtime, AgentRuntime::Headless) { + if !skip_relay_prompt && matches!(spec.runtime, AgentRuntime::Pty) { if let Some(relay_key) = worker_relay_api_key { command.env("RELAY_AGENT_TOKEN", relay_key); } @@ -498,6 +743,7 @@ impl WorkerRegistry { workspace_id, child, stdin, + harness_pid: initial_harness_pid, spawned_at: Instant::now(), last_activity_at: Instant::now(), context_budget_pct: None, @@ -584,19 +830,20 @@ impl WorkerRegistry { .workers .remove(name) .with_context(|| format!("unknown worker '{name}'"))?; + let release_grace = release_grace_for_spec(&handle.spec); let shutdown_frame = ProtocolEnvelope { v: PROTOCOL_VERSION, msg_type: "shutdown_worker".to_string(), request_id: None, - payload: json!({"reason":"release","grace_ms":2000}), + payload: json!({"reason":"release","grace_ms": release_grace.as_millis() as u64}), }; let encoded = serde_json::to_string(&shutdown_frame)?; let _ = handle.stdin.write_all(encoded.as_bytes()).await; let _ = handle.stdin.write_all(b"\n").await; let _ = handle.stdin.flush().await; - let result = terminate_child(&mut handle.child, Duration::from_secs(2)).await; + let result = terminate_child(&mut handle.child, release_grace).await; match &result { Ok(()) => tracing::info!(target = "broker::release", name = %name, "worker released"), Err(error) => { @@ -747,6 +994,122 @@ impl WorkerRegistry { } } +fn release_policy_arg(policy: Option<&HarnessReleasePolicy>) -> &'static str { + match policy { + Some(HarnessReleasePolicy::Abort) => "abort", + Some(HarnessReleasePolicy::Delete) => "delete", + Some(HarnessReleasePolicy::Detach) | None => "detach", + } +} + +fn app_server_auth_type_arg(auth_type: &AppServerAuthType) -> &'static str { + match auth_type { + AppServerAuthType::Bearer => "bearer", + AppServerAuthType::Basic => "basic", + AppServerAuthType::None => "none", + } +} + +fn release_grace_for_spec(spec: &AgentSpec) -> Duration { + match spec.harness_config.as_ref() { + Some(ResolvedHarnessConfig::Headless(config)) + if matches!(&config.driver, HeadlessHarnessDriver::AppServer) => + { + APP_SERVER_RELEASE_GRACE + } + _ => DEFAULT_RELEASE_GRACE, + } +} + +fn validate_app_server_config(config: &HeadlessHarnessConfig) -> Result<()> { + if !matches!(&config.driver, HeadlessHarnessDriver::AppServer) { + anyhow::bail!("unsupported headless harness driver"); + } + + let protocol = config.protocol.trim().to_ascii_lowercase(); + if protocol != "opencode" { + anyhow::bail!( + "unsupported app_server protocol '{}' (supported: opencode)", + config.protocol + ); + } + + let endpoint = config.endpoint.trim(); + if endpoint.is_empty() { + anyhow::bail!("app_server endpoint is required"); + } + let parsed_endpoint = reqwest::Url::parse(endpoint) + .with_context(|| format!("invalid app_server endpoint '{}'", config.endpoint))?; + match parsed_endpoint.scheme() { + "http" | "https" => {} + scheme => anyhow::bail!( + "invalid app_server endpoint scheme '{}' (expected http or https)", + scheme + ), + } + if config.auth.is_some() + && parsed_endpoint.scheme() == "http" + && !is_loopback_endpoint_host(&parsed_endpoint) + { + anyhow::bail!( + "app_server auth requires https unless the endpoint is loopback: {}", + config.endpoint + ); + } + + if config.session_id.trim().is_empty() { + anyhow::bail!("app_server sessionId is required"); + } + + if config + .host + .as_ref() + .and_then(|host| host.ownership.as_ref()) + .is_some_and(|ownership| matches!(ownership, AppServerHostOwnership::BrokerOwned)) + { + anyhow::bail!("broker-owned app_server hosts are not supported yet"); + } + + if let Some(auth) = config.auth.as_ref() { + match auth.auth_type { + AppServerAuthType::Bearer => { + if auth + .token + .as_deref() + .is_none_or(|value| value.trim().is_empty()) + { + anyhow::bail!("app_server bearer auth requires token"); + } + } + AppServerAuthType::Basic => { + if auth + .username + .as_deref() + .is_none_or(|value| value.trim().is_empty()) + { + anyhow::bail!("app_server basic auth requires username"); + } + if auth + .password + .as_deref() + .is_none_or(|value| value.trim().is_empty()) + { + anyhow::bail!("app_server basic auth requires password"); + } + } + AppServerAuthType::None => {} + } + } + + Ok(()) +} + +fn is_loopback_endpoint_host(endpoint: &reqwest::Url) -> bool { + endpoint.host_str().is_some_and(|host| { + host.eq_ignore_ascii_case("localhost") || host == "127.0.0.1" || host == "::1" + }) +} + #[derive(Debug, Clone, PartialEq, Eq)] enum CodexSessionReference { Known(String), @@ -1204,6 +1567,7 @@ fn spawn_worker_reader( #[cfg(test)] mod tests { use super::*; + use crate::protocol::{AppServerHarnessAuth, AppServerHarnessHost}; fn make_registry(env: Vec<(String, String)>) -> WorkerRegistry { let (tx, _rx) = mpsc::channel::(16); @@ -1243,6 +1607,106 @@ mod tests { assert_eq!(reg.env_value("MISSING"), None); } + fn make_app_server_config() -> HeadlessHarnessConfig { + HeadlessHarnessConfig { + driver: HeadlessHarnessDriver::AppServer, + protocol: "opencode".to_string(), + endpoint: "http://127.0.0.1:4096".to_string(), + session_id: "ses_123".to_string(), + auth: None, + host: Some(AppServerHarnessHost { + ownership: Some(AppServerHostOwnership::Attached), + pid: Some(12345), + }), + release: Some(HarnessReleasePolicy::Detach), + metadata: None, + } + } + + #[test] + fn app_server_config_validation_accepts_attached_opencode_config() { + let config = make_app_server_config(); + validate_app_server_config(&config).expect("valid app-server config"); + } + + #[test] + fn app_server_config_validation_rejects_missing_bearer_token() { + let mut config = make_app_server_config(); + config.auth = Some(AppServerHarnessAuth { + auth_type: AppServerAuthType::Bearer, + token: None, + username: None, + password: None, + }); + + let error = validate_app_server_config(&config).expect_err("missing token rejected"); + assert!(error.to_string().contains("bearer auth requires token")); + } + + #[test] + fn app_server_config_validation_rejects_authenticated_non_loopback_http() { + let mut config = make_app_server_config(); + config.endpoint = "http://example.com:4096".to_string(); + config.auth = Some(AppServerHarnessAuth { + auth_type: AppServerAuthType::Bearer, + token: Some("token".to_string()), + username: None, + password: None, + }); + + let error = validate_app_server_config(&config).expect_err("non-loopback http rejected"); + assert!(error + .to_string() + .contains("auth requires https unless the endpoint is loopback")); + } + + #[test] + fn app_server_config_validation_rejects_broker_owned_host() { + let mut config = make_app_server_config(); + config.host = Some(AppServerHarnessHost { + ownership: Some(AppServerHostOwnership::BrokerOwned), + pid: None, + }); + + let error = validate_app_server_config(&config).expect_err("broker-owned host rejected"); + assert!(error + .to_string() + .contains("broker-owned app_server hosts are not supported yet")); + } + + #[test] + fn app_server_config_validation_rejects_unsupported_protocol() { + let mut config = make_app_server_config(); + config.protocol = "custom".to_string(); + + let error = validate_app_server_config(&config).expect_err("unsupported protocol rejected"); + assert!(error + .to_string() + .contains("unsupported app_server protocol")); + } + + #[test] + fn app_server_release_uses_extended_grace() { + let spec = AgentSpec { + name: WorkerName::from("opencode-app"), + runtime: AgentRuntime::Headless, + provider: None, + cli: None, + session_id: Some("ses_123".to_string()), + harness_config: Some(ResolvedHarnessConfig::Headless(make_app_server_config())), + model: None, + cwd: None, + team: None, + shadow_of: None, + shadow_mode: None, + args: Vec::new(), + channels: Vec::new(), + restart_policy: None, + }; + + assert_eq!(release_grace_for_spec(&spec), APP_SERVER_RELEASE_GRACE); + } + #[test] fn routing_workers_empty_when_no_workers() { let reg = make_registry(vec![]); diff --git a/docs/doctor-orchestration-repros.md b/docs/doctor-orchestration-repros.md deleted file mode 100644 index cfba04850..000000000 --- a/docs/doctor-orchestration-repros.md +++ /dev/null @@ -1,140 +0,0 @@ -# Doctor Orchestration Repros - -These are deterministic local repros for orchestration states that previously -required comparing `status`, `who`, and messaging command output by hand. -Credential values in observed output are redacted. - -## Stale or Wrong Broker Connection - -Run from a temporary project with the built CLI: - -```bash -CLI=/Users/khaliqgant/Projects/AgentWorkforce/relay/dist/src/cli/index.js -TMP=$(mktemp -d /tmp/relay-repro-A-C-XXXXXX) -cd "$TMP" -node "$CLI" up --no-dashboard --port 49200 -cat .agent-relay/connection.json -node "$CLI" status -node "$CLI" who --json -kill -9 11078 -node "$CLI" up --no-dashboard --port 49300 -node -e 'const fs=require("fs"); const old=JSON.parse(process.argv[1]); const next=JSON.parse(process.argv[2]); old.pid=next.pid; fs.writeFileSync(".agent-relay/connection.json", JSON.stringify(old,null,2));' "$CONN1" "$CONN2" -cat .agent-relay/connection.json -node "$CLI" status -node "$CLI" status --wait-for=1 -node "$CLI" who --json -``` - -Observed output: - -```text -Broker started. -Broker PID: 11078 -Stop with: agent-relay down - -{ - "api_key": "br_", - "pid": 11078, - "port": 49201, - "url": "http://127.0.0.1:49201" -} - -Status: RUNNING -Mode: broker (stdio) -PID: 11078 -Project: /private/tmp/relay-repro-A-C-HuYoNc -Agents: 0 -Workspace Key: rk_live_ -Observer: https://agentrelay.com/observer?key=rk_live_ - -[] - -Broker started. -Broker PID: 11410 -Stop with: agent-relay down - -{ - "api_key": "br_", - "pid": 11410, - "port": 49201, - "url": "http://127.0.0.1:49201" -} - -Status: RUNNING -Mode: broker (stdio) -PID: 11410 -Project: /private/tmp/relay-repro-A-C-HuYoNc - -Status: STARTING -Mode: broker (stdio) -PID: 11410 -Project: /private/tmp/relay-repro-A-C-HuYoNc -Broker process is running, but the API did not become ready before timeout. - -[] -``` - -## Unresolved API Key Template - -With the same temporary project, the correctly resolved broker session key -allowed an orchestrator read to reach Relaycast: - -```bash -node "$CLI" replies WorkerA --json -``` - -Observed output: - -```text -No DM conversation with WorkerA. -``` - -The literal unresolved template fails before a meaningful orchestrator read: - -```bash -RELAY_API_KEY='${RELAY_API_KEY}' node "$CLI" replies WorkerA --json -``` - -Observed output: - -```text -Failed to initialize relaycast client: Workspace key required (rk_live_...) -``` - -## Half-Started Broker With Missing Metadata - -Run with Relaycast environment variables unset so messaging commands must rely -on local broker metadata: - -```bash -CLI=/Users/khaliqgant/Projects/AgentWorkforce/relay/dist/src/cli/index.js -RUN=(env -u RELAY_API_KEY -u RELAY_AGENT_TOKEN -u RELAY_WORKSPACES_JSON -u RELAY_DEFAULT_WORKSPACE -u RELAY_WORKSPACE_ID -u RELAY_BASE_URL -u RELAY_BROKER_URL -u RELAY_BROKER_API_KEY -u RELAY_AGENT_NAME -u RELAY_AGENT_TYPE -u RELAY_STRICT_AGENT_NAME node "$CLI") -TMP=$(mktemp -d /tmp/relay-repro-half2-XXXXXX) -cd "$TMP" -"${RUN[@]}" up --no-dashboard --port 49600 -rm .agent-relay/connection.json -ps -p 15596 -o pid=,comm= -"${RUN[@]}" status -"${RUN[@]}" history -"${RUN[@]}" replies WorkerA --json -"${RUN[@]}" up --no-dashboard --port 49700 -``` - -Observed output: - -```text -Broker started. -Broker PID: 15596 -Stop with: agent-relay down - -15596 /Users/khaliqgant/Projects/AgentWorkforce/relay/target/release/agent-relay-broker - -Status: STOPPED - -Failed to initialize relaycast client: Failed to read broker connection metadata. Start the broker with `agent-relay up` or set RELAY_API_KEY. - -Failed to initialize relaycast client: Failed to read broker connection metadata. Start the broker with `agent-relay up` or set RELAY_API_KEY. - -Broker background start did not become ready within 10s (pid: 16245). -Run `agent-relay status --wait-for=10` for details, or `agent-relay down --force` to clean up. -``` diff --git a/package-lock.json b/package-lock.json index cb7aeb6b4..83082b6d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1235,6 +1235,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -6702,9 +6703,9 @@ "link": true }, "node_modules/agent-trajectories": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/agent-trajectories/-/agent-trajectories-0.5.8.tgz", - "integrity": "sha512-Cu/+uyxAy+eNSlpzuOhk62kM/i0BdlfG8Z4avyzfbHbQ3I9EQLqiUikl3WcG75m3v+4MwTbJq9e6YTG8/ykKPw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/agent-trajectories/-/agent-trajectories-0.6.0.tgz", + "integrity": "sha512-wCOHGO7gWw6lA/4e794+YMOLWpfRnlCZiLN5i5lHj3oNHzFYEBlSEEBJXLM3moDM9x0UvLEAEsz+bYrlxW80mQ==", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", @@ -16232,7 +16233,7 @@ "@relayfile/local-mount": "^0.2.2", "@relayfile/sdk": "^0.6.0", "@slack/web-api": "^7.16.0", - "agent-trajectories": "^0.5.8", + "agent-trajectories": "^0.6.0", "commander": "^12.1.0", "dotenv": "^17.2.3", "esbuild": "^0.27.2", @@ -17202,7 +17203,7 @@ "@relaycast/sdk": "^1.1.0", "@relayfile/sdk": ">=0.1.2 <1", "@sinclair/typebox": "^0.34.48", - "agent-trajectories": "^0.5.4", + "agent-trajectories": "^0.6.0", "chalk": "^4.1.2", "ignore": "^7.0.5", "listr2": "^10.2.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 5fec1bac9..04eb89075 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -63,7 +63,7 @@ "@relayfile/local-mount": "^0.2.2", "@relayfile/sdk": "^0.6.0", "@slack/web-api": "^7.16.0", - "agent-trajectories": "^0.5.8", + "agent-trajectories": "^0.6.0", "commander": "^12.1.0", "dotenv": "^17.2.3", "esbuild": "^0.27.2", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 7a2173ebf..380fc60c1 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -179,7 +179,7 @@ "@relaycast/sdk": "^1.1.0", "@relayfile/sdk": ">=0.1.2 <1", "@sinclair/typebox": "^0.34.48", - "agent-trajectories": "^0.5.4", + "agent-trajectories": "^0.6.0", "chalk": "^4.1.2", "ignore": "^7.0.5", "listr2": "^10.2.1", diff --git a/packages/sdk/src/__tests__/harness.test.ts b/packages/sdk/src/__tests__/harness.test.ts new file mode 100644 index 000000000..cca521f2c --- /dev/null +++ b/packages/sdk/src/__tests__/harness.test.ts @@ -0,0 +1,125 @@ +import { homedir } from 'node:os'; +import path from 'node:path'; + +import { describe, expect, it, vi } from 'vitest'; + +import { AgentRelayClient } from '../client.js'; +import { harnessLookupKeys, resolveStaticHarnessConfig } from '../harness.js'; + +describe('harness configs', () => { + it('resolves static PTY harnesses to broker-executable configs', () => { + const config = resolveStaticHarnessConfig({ + name: 'QwenReviewer', + cli: 'qwen', + definition: { + runtime: 'pty', + command: 'qwen', + args: ['run', '{modelArgs}', '{args}'], + modelArgs: ['-m', '{model}'], + env: { QWEN_MODE: 'code' }, + }, + args: ['--verbose'], + model: 'qwen3-coder', + cwd: '/workspace', + }); + + expect(config).toEqual({ + runtime: 'pty', + command: 'qwen', + args: ['run', '-m', 'qwen3-coder', '--verbose'], + cwd: '/workspace', + env: { QWEN_MODE: 'code' }, + }); + }); + + it('resolves static headless app-server harnesses without process args', () => { + const config = resolveStaticHarnessConfig({ + name: 'OpenCodeServerWorker', + cli: 'opencode-server', + definition: { + runtime: 'headless', + protocol: 'opencode', + endpoint: 'http://127.0.0.1:4096', + sessionId: 'ses_123', + release: 'abort', + }, + }); + + expect(config).toEqual({ + runtime: 'headless', + driver: 'app_server', + protocol: 'opencode', + endpoint: 'http://127.0.0.1:4096', + sessionId: 'ses_123', + release: 'abort', + }); + }); + + it('looks up harnesses by full cli, executable token, and model suffix base', () => { + expect(harnessLookupKeys('qwen:coder --fast')).toEqual(['qwen:coder --fast', 'qwen:coder', 'qwen']); + }); + + it('expands home directories in direct command paths', () => { + const config = resolveStaticHarnessConfig({ + name: 'ClaudeReviewer', + cli: 'claude', + definition: { + runtime: 'pty', + command: '~/bin/claude', + }, + }); + + expect(config.command).toBe(path.join(homedir(), 'bin/claude')); + }); + + it('expands Windows-style home-relative command paths', () => { + const config = resolveStaticHarnessConfig({ + name: 'ClaudeReviewer', + cli: 'claude', + definition: { + runtime: 'pty', + command: '~\\bin\\claude', + }, + }); + + expect(config.command).toBe(path.join(homedir(), 'bin\\claude')); + }); + + it('serializes resolved harness configs on spawn requests', async () => { + const captures: unknown[] = []; + const fetchFn = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => { + captures.push(JSON.parse(String(init?.body ?? '{}'))); + return new Response( + JSON.stringify({ name: 'QwenReviewer', runtime: 'pty', sessionId: 'ses_1', pid: 123 }), + { + status: 200, + headers: { 'Content-Type': 'application/json' }, + } + ); + }); + + const client = new AgentRelayClient({ baseUrl: 'http://broker.test', apiKey: 'k', fetch: fetchFn }); + const result = await client.spawnPty({ + name: 'QwenReviewer', + cli: 'qwen', + harnessConfig: { + runtime: 'pty', + command: 'qwen', + args: ['run'], + sessionId: 'ses_1', + }, + }); + + expect(result).toEqual({ name: 'QwenReviewer', runtime: 'pty', sessionId: 'ses_1', pid: 123 }); + expect(captures[0]).toMatchObject({ + name: 'QwenReviewer', + cli: 'qwen', + harnessConfig: { + runtime: 'pty', + command: 'qwen', + args: ['run'], + sessionId: 'ses_1', + }, + }); + }); +}); diff --git a/packages/sdk/src/__tests__/lifecycle-hooks.test.ts b/packages/sdk/src/__tests__/lifecycle-hooks.test.ts index 7657306bf..7aaf4447a 100644 --- a/packages/sdk/src/__tests__/lifecycle-hooks.test.ts +++ b/packages/sdk/src/__tests__/lifecycle-hooks.test.ts @@ -87,6 +87,15 @@ describe('AgentRelayClient lifecycle hooks', () => { expect(result.sessionId).toBeUndefined(); }); + it('normalizes null spawn pid to undefined', async () => { + const { fetchFn } = makeMockFetch([() => ({ name: 'agent-null-pid', runtime: 'pty', pid: null })]); + const client = makeClient(fetchFn); + + const result = await client.spawnPty({ name: 'agent-null-pid', cli: 'claude' }); + + expect(result.pid).toBeUndefined(); + }); + it('folds beforeAgentSpawn patches into resolvedInput before POST', async () => { const { fetchFn, captures } = makeMockFetch(); const client = makeClient(fetchFn); @@ -217,6 +226,37 @@ describe('AgentRelayClient lifecycle hooks', () => { expect((after.mock.calls[0][0] as AfterAgentSpawnContext).kind).toBe('provider'); }); + it('recomputes provider transport after beforeAgentSpawn patches add a harness config', async () => { + const { fetchFn, captures } = makeMockFetch(); + const client = makeClient(fetchFn); + + client.addListener('beforeAgentSpawn', () => ({ + harnessConfig: { + runtime: 'headless', + driver: 'app_server', + protocol: 'opencode', + endpoint: 'http://127.0.0.1:4096', + sessionId: 'ses_hook', + }, + })); + + await client.spawnProvider({ + name: 'patched-headless', + provider: 'custom-provider', + }); + + expect(captures[0].body).toMatchObject({ + name: 'patched-headless', + cli: 'custom-provider', + transport: 'headless', + harnessConfig: { + runtime: 'headless', + driver: 'app_server', + sessionId: 'ses_hook', + }, + }); + }); + it('release fires beforeAgentRelease then afterAgentRelease', async () => { const { fetchFn } = makeMockFetch(); const client = makeClient(fetchFn); diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index b7c4b9464..a1fea3766 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -35,6 +35,7 @@ import type { } from './protocol.js'; import type { AgentTransport, + SpawnAgentResult, SpawnHeadlessInput, SpawnPtyInput, SpawnProviderInput, @@ -117,6 +118,7 @@ export interface AgentRelaySpawnOptions { } const optionalString = z.preprocess((value) => (value === null ? undefined : value), z.string().optional()); +const optionalNumber = z.preprocess((value) => (value === null ? undefined : value), z.number().optional()); export const SpawnAgentResultSchema = z .object({ @@ -124,15 +126,13 @@ export const SpawnAgentResultSchema = z name: z.string(), runtime: z.enum(['pty', 'headless']), model: z.string().nullable().optional(), - pid: z.number().optional(), + pid: optionalNumber, pre_registered: z.boolean().optional(), warning: z.string().nullable().optional(), sessionId: optionalString, }) .passthrough(); -export type SpawnAgentResult = z.infer; - export interface SessionInfo { broker_version: string; protocol_version: number; @@ -169,7 +169,9 @@ function isHeadlessProvider(value: string): value is HeadlessProvider { } function resolveSpawnTransport(input: SpawnProviderInput): AgentTransport { - return input.transport ?? (input.provider === 'opencode' ? 'headless' : 'pty'); + if (input.transport) return input.transport; + if (input.harnessConfig) return input.harnessConfig.runtime; + return input.provider === 'opencode' ? 'headless' : 'pty'; } /** @@ -191,6 +193,7 @@ function buildSpawnPtyBody(input: SpawnPtyInput): Record { ...(input.shadowOf !== undefined ? { shadowOf: input.shadowOf } : {}), ...(input.shadowMode !== undefined ? { shadowMode: input.shadowMode } : {}), ...(input.continueFrom !== undefined ? { continueFrom: input.continueFrom } : {}), + ...(input.harnessConfig !== undefined ? { harnessConfig: input.harnessConfig } : {}), ...(input.idleThresholdSecs !== undefined ? { idleThresholdSecs: input.idleThresholdSecs } : {}), ...(input.restartPolicy !== undefined ? { restartPolicy: input.restartPolicy } : {}), ...(input.skipRelayPrompt !== undefined ? { skipRelayPrompt: input.skipRelayPrompt } : {}), @@ -215,6 +218,7 @@ function buildSpawnProviderBody( ...(input.shadowOf !== undefined ? { shadowOf: input.shadowOf } : {}), ...(input.shadowMode !== undefined ? { shadowMode: input.shadowMode } : {}), ...(input.continueFrom !== undefined ? { continueFrom: input.continueFrom } : {}), + ...(input.harnessConfig !== undefined ? { harnessConfig: input.harnessConfig } : {}), ...(input.idleThresholdSecs !== undefined ? { idleThresholdSecs: input.idleThresholdSecs } : {}), ...(input.restartPolicy !== undefined ? { restartPolicy: input.restartPolicy } : {}), ...(input.skipRelayPrompt !== undefined ? { skipRelayPrompt: input.skipRelayPrompt } : {}), @@ -626,13 +630,6 @@ export class AgentRelayClient { } async spawnProvider(input: SpawnProviderInput): Promise { - const transport = resolveSpawnTransport(input); - if (transport === 'headless' && !isHeadlessProvider(input.provider)) { - throw new Error( - `provider '${input.provider}' does not support headless transport (supported: claude, opencode)` - ); - } - const beforeCtx: BeforeAgentSpawnContext = { kind: 'provider', input, @@ -642,6 +639,17 @@ export class AgentRelayClient { }; const t0 = Date.now(); const resolvedInput = (await this.runBeforeSpawn(beforeCtx)) as SpawnProviderInput; + const transport = resolveSpawnTransport(resolvedInput); + if ( + transport === 'headless' && + !isHeadlessProvider(resolvedInput.provider) && + !resolvedInput.harnessConfig + ) { + throw new Error( + `provider '${resolvedInput.provider}' does not support headless transport (supported: claude, opencode)` + ); + } + try { const rawResult = await this.transport.request('/api/spawn', { method: 'POST', diff --git a/packages/sdk/src/harness.ts b/packages/sdk/src/harness.ts new file mode 100644 index 000000000..33295ffd9 --- /dev/null +++ b/packages/sdk/src/harness.ts @@ -0,0 +1,199 @@ +import { homedir } from 'node:os'; +import path from 'node:path'; + +import type { AgentRuntime } from './protocol.js'; + +export type HarnessRuntime = Extract; +export type HarnessReleasePolicy = 'abort' | 'detach' | 'delete'; +export type HeadlessHarnessDriver = 'app_server'; + +export interface PtyHarnessDelivery { + mode?: 'pty-injection'; + format?: 'relay-block'; +} + +export interface PtyHarnessConfig { + runtime: 'pty'; + command: string; + args: string[]; + cwd?: string; + env?: Record; + sessionId?: string; + delivery?: PtyHarnessDelivery; + metadata?: Record; +} + +export interface AppServerHarnessAuth { + type: 'bearer' | 'basic' | 'none'; + token?: string; + username?: string; + password?: string; +} + +export interface AppServerHarnessHost { + /** + * `broker-owned` is reserved for a future broker-supervised app-server mode. + * Current broker releases accept attached hosts only. + */ + ownership?: 'broker-owned' | 'attached'; + /** Local app-server host PID to report as the harness PID when known. */ + pid?: number; +} + +export interface HeadlessAppServerHarnessConfig { + runtime: 'headless'; + driver?: HeadlessHarnessDriver; + protocol: 'opencode' | string; + endpoint: string; + sessionId: string; + auth?: AppServerHarnessAuth; + host?: AppServerHarnessHost; + release?: HarnessReleasePolicy; + metadata?: Record; +} + +export type ResolvedHarnessConfig = PtyHarnessConfig | HeadlessAppServerHarnessConfig; + +export interface StaticPtyHarnessDefinition { + runtime: 'pty'; + command: string; + args?: string[]; + modelArgs?: string[]; + env?: Record; + cwd?: string; + sessionId?: string; + delivery?: PtyHarnessDelivery; + metadata?: Record; +} + +export interface StaticHeadlessAppServerHarnessDefinition { + runtime: 'headless'; + driver?: HeadlessHarnessDriver; + protocol: 'opencode' | string; + endpoint: string; + sessionId: string; + auth?: AppServerHarnessAuth; + host?: AppServerHarnessHost; + release?: HarnessReleasePolicy; + metadata?: Record; +} + +export type StaticHarnessDefinition = StaticPtyHarnessDefinition | StaticHeadlessAppServerHarnessDefinition; +export type HarnessDefinition = StaticHarnessDefinition; + +export interface ResolveStaticHarnessInput { + name: string; + cli: string; + definition: StaticHarnessDefinition; + args?: string[]; + task?: string; + model?: string; + cwd?: string; + env?: Record; +} + +const DEFAULT_PTY_ARGS = ['{args}'] as const; +const DEFAULT_MODEL_ARGS = ['--model', '{model}'] as const; + +export function resolveStaticHarnessConfig(input: ResolveStaticHarnessInput): ResolvedHarnessConfig { + const { definition } = input; + if (definition.runtime === 'headless') { + return { + runtime: 'headless', + driver: definition.driver ?? 'app_server', + protocol: definition.protocol, + endpoint: definition.endpoint, + sessionId: definition.sessionId, + ...(definition.auth ? { auth: { ...definition.auth } } : {}), + ...(definition.host ? { host: { ...definition.host } } : {}), + ...(definition.release ? { release: definition.release } : {}), + ...(definition.metadata ? { metadata: { ...definition.metadata } } : {}), + }; + } + + const context = { + args: input.args ?? [], + task: input.task, + model: input.model, + modelArgs: input.model + ? renderTemplate(definition.modelArgs ?? DEFAULT_MODEL_ARGS, { + args: [], + task: input.task, + model: input.model, + modelArgs: [], + }) + : [], + }; + + const configEnv = { + ...(definition.env ?? {}), + ...(input.env ?? {}), + }; + + return { + runtime: 'pty', + command: expandHome(definition.command), + args: renderTemplate(definition.args ?? DEFAULT_PTY_ARGS, context), + ...((input.cwd ?? definition.cwd) ? { cwd: input.cwd ?? definition.cwd } : {}), + ...(Object.keys(configEnv).length > 0 ? { env: configEnv } : {}), + ...(definition.sessionId ? { sessionId: definition.sessionId } : {}), + ...(definition.delivery ? { delivery: { ...definition.delivery } } : {}), + ...(definition.metadata ? { metadata: { ...definition.metadata } } : {}), + }; +} + +export function harnessLookupKeys(cli: string): string[] { + const trimmed = cli.trim(); + if (!trimmed) return []; + const firstToken = trimmed.split(/\s+/)[0] ?? trimmed; + const withoutModel = firstToken.split(':')[0] ?? firstToken; + return Array.from(new Set([trimmed, firstToken, withoutModel].filter(Boolean))); +} + +function renderTemplate( + template: readonly string[], + context: { + args: string[]; + modelArgs: string[]; + task?: string; + model?: string; + } +): string[] { + const rendered: string[] = []; + for (const entry of template) { + if (isExactPlaceholder(entry, 'args')) { + rendered.push(...context.args); + continue; + } + if (isExactPlaceholder(entry, 'modelArgs')) { + rendered.push(...context.modelArgs); + continue; + } + if (isExactPlaceholder(entry, 'task')) { + if (context.task) rendered.push(context.task); + continue; + } + if (isExactPlaceholder(entry, 'model')) { + if (context.model) rendered.push(context.model); + continue; + } + + const value = entry + .replace(/\{\{\s*task\s*\}\}|\{task\}/g, context.task ?? '') + .replace(/\{\{\s*model\s*\}\}|\{model\}/g, context.model ?? ''); + if (value !== '') { + rendered.push(value); + } + } + return rendered; +} + +function isExactPlaceholder(value: string, name: string): boolean { + return value === `{${name}}` || value === `{{${name}}}`; +} + +function expandHome(value: string): string { + if (value === '~') return homedir(); + if (value.startsWith('~/') || value.startsWith('~\\')) return path.join(homedir(), value.slice(2)); + return value; +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 788021a5c..3ac0bcaa0 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -17,7 +17,6 @@ export { type AgentRelaySpawnOptions, type SetInboundDeliveryModeResult, type SessionInfo, - type SpawnAgentResult, type WorkerStreamSubscriptionOptions, } from './client.js'; export { EventBus, type EventHandler, type EventMap } from './event-bus.js'; @@ -44,6 +43,7 @@ export * from './broker-logs.js'; export * from './consensus.js'; export * from './shadow.js'; export * from './relay-adapter.js'; +export * from './harness.js'; export * from './workflows/index.js'; export * from './spawn-from-env.js'; export * from './cli-registry.js'; diff --git a/packages/sdk/src/lifecycle-hooks.ts b/packages/sdk/src/lifecycle-hooks.ts index 698801e62..b21e4fabc 100644 --- a/packages/sdk/src/lifecycle-hooks.ts +++ b/packages/sdk/src/lifecycle-hooks.ts @@ -26,9 +26,9 @@ * (`relay.ts` → `event-bus.ts` → no further). */ -import type { AgentRuntime, BrokerEvent } from './protocol.js'; +import type { BrokerEvent } from './protocol.js'; import type { Agent, AgentActivityChange, AgentResult, Message } from './relay.js'; -import type { SpawnPtyInput, SpawnProviderInput } from './types.js'; +import type { SpawnAgentResult, SpawnPtyInput, SpawnProviderInput } from './types.js'; // ── SpawnPatch ───────────────────────────────────────────────────────────── @@ -52,7 +52,10 @@ import type { SpawnPtyInput, SpawnProviderInput } from './types.js'; * the same key. */ export type SpawnPatch = Partial< - Pick + Pick< + SpawnPtyInput & SpawnProviderInput, + 'args' | 'channels' | 'task' | 'model' | 'team' | 'agentToken' | 'harnessConfig' + > >; // ── Call-site contexts ───────────────────────────────────────────────────── @@ -78,7 +81,7 @@ export interface AfterAgentSpawnContext extends BeforeAgentSpawnContext { /** Final input that was sent to the broker — original input merged with every handler's patch. */ resolvedInput: SpawnPtyInput | SpawnProviderInput; /** Broker reply on success. */ - result?: { name: string; runtime: AgentRuntime; sessionId?: string }; + result?: SpawnAgentResult; /** Set when the broker call rejected. Mutually exclusive with `result`. */ error?: Error; /** Wall-clock duration from `beforeAgentSpawn` start to here. */ diff --git a/packages/sdk/src/protocol.ts b/packages/sdk/src/protocol.ts index f70d59977..f6be68f71 100644 --- a/packages/sdk/src/protocol.ts +++ b/packages/sdk/src/protocol.ts @@ -17,11 +17,12 @@ export interface AgentSpec { runtime: AgentRuntime; provider?: HeadlessProvider; cli?: string; + session_id?: string; + harness_config?: import('./harness.js').ResolvedHarnessConfig; args?: string[]; channels?: string[]; model?: string; cwd?: string; - session_id?: string; team?: string; shadow_of?: string; shadow_mode?: string; @@ -400,6 +401,7 @@ export type BrokerEvent = cli?: string; model?: string; sessionId?: string; + pid?: number; } | { kind: 'worker_error'; @@ -507,7 +509,7 @@ export type BrokerToWorker = export type WorkerToBroker = | { type: 'worker_ready'; - payload: { name: string; runtime: AgentRuntime; provider?: HeadlessProvider }; + payload: { name: string; runtime: AgentRuntime; provider?: HeadlessProvider; sessionId?: string }; } | { type: 'delivery_ack'; diff --git a/packages/sdk/src/relay.ts b/packages/sdk/src/relay.ts index 6c07e5fdb..c7792a39a 100644 --- a/packages/sdk/src/relay.ts +++ b/packages/sdk/src/relay.ts @@ -31,13 +31,14 @@ import { RelayCast } from '@relaycast/sdk'; import { zodToJsonSchema } from 'zod-to-json-schema'; import type { ZodTypeAny } from 'zod'; -import { - AgentRelayClient, - type AgentRelayBrokerInitArgs, - type AgentRelaySpawnOptions, - type SpawnAgentResult, -} from './client.js'; +import { AgentRelayClient, type AgentRelayBrokerInitArgs, type AgentRelaySpawnOptions } from './client.js'; import { EventBus } from './event-bus.js'; +import { + harnessLookupKeys, + resolveStaticHarnessConfig, + type HarnessDefinition, + type ResolvedHarnessConfig, +} from './harness.js'; import type { AgentRelayEvents, BeforeAgentSpawnHandler } from './lifecycle-hooks.js'; import { buildPersonaSpawnPlan, @@ -53,7 +54,7 @@ import { type PersonaSpec, } from './personas.js'; import { AgentRelayProtocolError } from './transport.js'; -import type { JsonSchema, SendMessageInput, SpawnPtyInput } from './types.js'; +import type { JsonSchema, SendMessageInput, SpawnAgentResult, SpawnPtyInput } from './types.js'; import type { AgentRuntime, BrokerEvent, @@ -291,6 +292,7 @@ export interface SpawnOptions extends SpawnLifecycleHook shadowMode?: string; idleThresholdSecs?: number; restartPolicy?: RestartPolicy; + harnessConfig?: ResolvedHarnessConfig; /** Optional pre-minted relaycast agent token (`at_live_`, from * `registerAgent(workspaceKey, name)` in `@agent-relay/sdk/http`). The * broker plumbs this as `RELAY_AGENT_TOKEN`, which the relaycast MCP @@ -363,8 +365,11 @@ type AgentOutputCallback = ((chunk: string) => void) | ((data: AgentOutputPayloa export interface Agent { readonly name: string; readonly runtime: AgentRuntime; - readonly sessionId?: string; readonly channels: string[]; + /** Native harness session id, when the runtime exposes one. */ + readonly sessionId?: string; + /** Broker worker process id, when known. */ + readonly pid?: number; /** Current lifecycle status of the agent. */ readonly status: AgentStatus; /** Set when the agent exits. Available once the `agentExited` event fires. */ @@ -429,6 +434,7 @@ export interface SpawnerSpawnOptions extends SpawnLifecy model?: string; cwd?: string; idleThresholdSecs?: number; + harnessConfig?: ResolvedHarnessConfig; /** Optional pre-minted relaycast agent token (`at_live_`, from * `registerAgent(workspaceKey, name)` in `@agent-relay/sdk/http`). The * broker plumbs this as `RELAY_AGENT_TOKEN`, which the relaycast MCP @@ -482,6 +488,15 @@ export interface AgentRelayOptions { * `searchDirs` on `spawnPersona` still overrides this. */ personaDirs?: string[]; + /** + * Named harness definitions. Definitions are SDK-side shortcuts that render + * to broker-executable JSON configs before each spawn. + */ + harnesses?: Record; + /** Broker lifetime. Ephemeral attached brokers shut down when the SDK lease expires. */ + broker?: { + lifetime?: 'attached' | 'detached'; + }; } type OutputListener = { @@ -492,7 +507,8 @@ type OutputListener = { type InternalAgent = Agent & { _setChannels: (channels: string[]) => void; - _setSessionId: (sessionId: string) => void; + _setMetadata: (metadata?: { sessionId?: string; pid?: number }) => void; + _setRuntime: (runtime: AgentRuntime) => void; }; type InternalAgentResultContract = { @@ -599,6 +615,7 @@ export class AgentRelay { private readonly workspaceName?: string; private readonly relaycastBaseUrl?: string; private readonly defaultPersonaDirs?: string[]; + private readonly harnesses: Record; private relayApiKey?: string; private resolvedWorkspaceId?: string; private client?: AgentRelayClient; @@ -641,6 +658,7 @@ export class AgentRelay { } this.relaycastBaseUrl = options.relaycastBaseUrl; if (options.personaDirs) this.defaultPersonaDirs = [...options.personaDirs]; + this.harnesses = { ...(options.harnesses ?? {}) }; this.clientOptions = { binaryPath: options.binaryPath, binaryArgs: options.binaryArgs, @@ -740,6 +758,46 @@ export class AgentRelay { ); } + registerHarness(name: string, definition: HarnessDefinition): void { + const key = name.trim(); + if (!key) { + throw new Error('registerHarness() expects a non-empty harness name'); + } + this.harnesses[key] = definition; + } + + private findHarnessDefinition(cli: string): HarnessDefinition | undefined { + for (const key of harnessLookupKeys(cli)) { + const definition = this.harnesses[key]; + if (definition) return definition; + } + return undefined; + } + + private resolveHarnessConfig(input: { + name: string; + cli: string; + args?: string[]; + task?: string; + model?: string; + cwd?: string; + harnessConfig?: ResolvedHarnessConfig; + }): ResolvedHarnessConfig | undefined { + if (input.harnessConfig) return input.harnessConfig; + const definition = this.findHarnessDefinition(input.cli); + if (!definition) return undefined; + + return resolveStaticHarnessConfig({ + name: input.name, + cli: input.cli, + definition, + args: input.args ?? [], + task: input.task, + model: input.model, + cwd: input.cwd ?? this.clientOptions.cwd ?? process.cwd(), + }); + } + private applyWorkspaceEnv(workspaceId: string, apiKey: string): void { // `workspaceId` here is the **relaycast** workspace id resolved by this // SDK (auto-created or caller-supplied via `new AgentRelay({ workspaceId })`). @@ -812,6 +870,7 @@ export class AgentRelay { this.resultContracts.set(input.name, resultContract as InternalAgentResultContract); } try { + const harnessConfig = this.resolveHarnessConfig(input); result = await client.spawnPty({ name: input.name, cli: input.cli, @@ -826,6 +885,7 @@ export class AgentRelay { shadowMode: input.shadowMode, idleThresholdSecs: input.idleThresholdSecs, restartPolicy: input.restartPolicy, + harnessConfig, skipRelayPrompt: input.skipRelayPrompt, agentResultSchema: resultContract?.jsonSchema, }); @@ -848,13 +908,12 @@ export class AgentRelay { this.resultContracts.delete(input.name); this.resultContracts.set(result.name, resultContract as InternalAgentResultContract); } - const agent = this.makeAgent( + const agent = this.ensureAgentHandle( result.name, result.runtime, channels, - result.sessionId + result ) as Agent; - this.knownAgents.set(agent.name, agent); await this.invokeLifecycleHook( input.onSuccess, { @@ -888,6 +947,7 @@ export class AgentRelay { shadowMode: options?.shadowMode, idleThresholdSecs: options?.idleThresholdSecs, restartPolicy: options?.restartPolicy, + harnessConfig: options?.harnessConfig, skipRelayPrompt: options?.skipRelayPrompt, result: options?.result, onStart: options?.onStart, @@ -996,6 +1056,7 @@ export class AgentRelay { shadowMode: options.shadowMode, idleThresholdSecs: options.idleThresholdSecs, restartPolicy: options.restartPolicy, + harnessConfig: options.harnessConfig, skipRelayPrompt: options.skipRelayPrompt, result: options.result, onStart: options.onStart, @@ -1183,9 +1244,18 @@ export class AgentRelay { async listAgents(): Promise { const client = await this.ensureStarted(); const list = await client.listAgents(); - return list.map((entry) => - this.ensureAgentHandle(entry.name, entry.runtime, entry.channels, entry.sessionId) - ); + return list.map((entry) => { + const existing = this.knownAgents.get(entry.name); + if (existing) { + this.updateAgentHandle(existing, entry.runtime, entry.channels, entry, { + replaceChannels: true, + }); + return existing; + } + const agent = this.makeAgent(entry.name, entry.runtime, entry.channels, entry); + this.knownAgents.set(agent.name, agent); + return agent; + }); } /** Pre-register a batch of agents with Relaycast before steps execute. */ @@ -1338,7 +1408,10 @@ export class AgentRelay { if (event.kind !== 'worker_ready' || event.name !== name) { return; } - const agent = this.ensureAgentHandle(event.name, event.runtime); + const agent = this.ensureAgentHandle(event.name, event.runtime, [], { + sessionId: event.sessionId, + pid: event.pid, + }); this.readyAgents.add(event.name); this.exitedAgents.delete(event.name); resolveWith(agent); @@ -1472,20 +1545,35 @@ export class AgentRelay { name: string, runtime: AgentRuntime = 'pty', channels: string[] = [], - sessionId?: string + metadata?: { sessionId?: string; pid?: number } ): Agent { const existing = this.knownAgents.get(name); if (existing) { - if (sessionId) { - (existing as InternalAgent)._setSessionId(sessionId); - } + this.updateAgentHandle(existing, runtime, channels, metadata); return existing; } - const agent = this.makeAgent(name, runtime, channels, sessionId); + const agent = this.makeAgent(name, runtime, channels, metadata); this.knownAgents.set(name, agent); return agent; } + private updateAgentHandle( + agent: Agent, + runtime: AgentRuntime, + channels: string[], + metadata?: { sessionId?: string; pid?: number }, + options?: { replaceChannels?: boolean } + ): void { + const internal = agent as InternalAgent; + internal._setRuntime(runtime); + if (options?.replaceChannels) { + internal._setChannels([...new Set(channels)]); + } else if (channels.length > 0) { + internal._setChannels([...new Set([...agent.channels, ...channels])]); + } + internal._setMetadata(metadata); + } + private updateDeliveryState( eventId: string, to: string, @@ -1712,7 +1800,7 @@ export class AgentRelay { }, }) ) - .then((c) => { + .then(async (c) => { // Use the workspace key the broker actually connected with. // This ensures SDK and workers are always on the same workspace. if (c.workspaceKey) { @@ -1764,7 +1852,10 @@ export class AgentRelay { break; } case 'agent_spawned': { - const agent = this.ensureAgentHandle(event.name, event.runtime, [], event.sessionId); + const agent = this.ensureAgentHandle(event.name, event.runtime, [], { + sessionId: event.sessionId, + pid: event.pid, + }); this.readyAgents.delete(event.name); this.messageReadyAgents.delete(event.name); this.exitedAgents.delete(event.name); @@ -1825,7 +1916,10 @@ export class AgentRelay { break; } case 'worker_ready': { - const agent = this.ensureAgentHandle(event.name, event.runtime, [], event.sessionId); + const agent = this.ensureAgentHandle(event.name, event.runtime, [], { + sessionId: event.sessionId, + pid: event.pid, + }); this.readyAgents.add(event.name); this.exitedAgents.delete(event.name); this.idleAgents.delete(event.name); @@ -2070,18 +2164,25 @@ export class AgentRelay { name: string, runtime: AgentRuntime, channels: string[], - sessionId?: string + metadata?: { sessionId?: string; pid?: number } ): Agent { // eslint-disable-next-line @typescript-eslint/no-this-alias const relay = this; let agentChannels = [...channels]; - let agentSessionId = sessionId; + let agentRuntime = runtime; + let agentSessionId = metadata?.sessionId; + let agentPid = metadata?.pid; const agent: InternalAgent = { name, - runtime, + get runtime() { + return agentRuntime; + }, get sessionId() { return agentSessionId; }, + get pid() { + return agentPid; + }, get channels() { return [...agentChannels]; }, @@ -2291,8 +2392,16 @@ export class AgentRelay { _setChannels(nextChannels: string[]) { agentChannels = [...nextChannels]; }, - _setSessionId(nextSessionId: string) { - agentSessionId = nextSessionId; + _setMetadata(nextMetadata) { + if (nextMetadata?.sessionId !== undefined) { + agentSessionId = nextMetadata.sessionId; + } + if (nextMetadata?.pid !== undefined) { + agentPid = nextMetadata.pid; + } + }, + _setRuntime(nextRuntime: AgentRuntime) { + agentRuntime = nextRuntime; }, }; return agent; @@ -2316,6 +2425,7 @@ export class AgentRelay { model: options?.model, cwd: options?.cwd, idleThresholdSecs: options?.idleThresholdSecs, + harnessConfig: options?.harnessConfig, agentToken: options?.agentToken, skipRelayPrompt: options?.skipRelayPrompt, result: options?.result, @@ -2339,16 +2449,26 @@ export class AgentRelay { this.resultContracts.set(name, resultContract as InternalAgentResultContract); } try { + const harnessConfig = this.resolveHarnessConfig({ + name, + cli, + args, + task, + model: options?.model, + cwd: options?.cwd, + harnessConfig: options?.harnessConfig, + }); result = await client.spawnProvider({ name, provider: cli as HeadlessProvider, - transport: 'headless', + transport: harnessConfig?.runtime ?? 'headless', args, channels, task, model: options?.model, cwd: options?.cwd, idleThresholdSecs: options?.idleThresholdSecs, + harnessConfig, agentToken: options?.agentToken, skipRelayPrompt: options?.skipRelayPrompt, agentResultSchema: resultContract?.jsonSchema, @@ -2373,13 +2493,12 @@ export class AgentRelay { this.resultContracts.delete(name); this.resultContracts.set(result.name, resultContract as InternalAgentResultContract); } - const agent = this.makeAgent( + const agent = this.ensureAgentHandle( result.name, result.runtime, channels, - result.sessionId + result ) as Agent; - this.knownAgents.set(agent.name, agent); await this.invokeLifecycleHook( options?.onSuccess, { diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 89aad3692..37c4fc451 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -9,6 +9,7 @@ import type { MessageInjectionMode, RestartPolicy, } from './protocol.js'; +import type { ResolvedHarnessConfig } from './harness.js'; export type JsonSchema = Record | boolean; @@ -26,6 +27,7 @@ export interface SpawnPtyInput { idleThresholdSecs?: number; restartPolicy?: RestartPolicy; continueFrom?: string; + harnessConfig?: ResolvedHarnessConfig; skipRelayPrompt?: boolean; agentResultSchema?: JsonSchema; /** Optional pre-minted relaycast agent token (`at_live_`, from @@ -58,6 +60,13 @@ export interface SpawnHeadlessInput { export type AgentTransport = 'pty' | 'headless'; +export interface SpawnAgentResult { + name: string; + runtime: AgentRuntime; + sessionId?: string; + pid?: number; +} + export interface SpawnProviderInput { name: string; provider: string; @@ -73,6 +82,7 @@ export interface SpawnProviderInput { idleThresholdSecs?: number; restartPolicy?: RestartPolicy; continueFrom?: string; + harnessConfig?: ResolvedHarnessConfig; skipRelayPrompt?: boolean; agentResultSchema?: JsonSchema; /** Optional pre-minted relaycast agent token (`at_live_`, from diff --git a/web/content/docs/doctor-orchestration-repros.mdx b/web/content/docs/doctor-orchestration-repros.mdx new file mode 100644 index 000000000..5a4d723c4 --- /dev/null +++ b/web/content/docs/doctor-orchestration-repros.mdx @@ -0,0 +1,184 @@ +--- +title: Doctor orchestration repros +description: Local repro steps for broker connection and orchestration diagnostics. +--- + +Use these local repros when `agent-relay status`, `agent-relay who`, or +messaging commands disagree about broker state. They intentionally create broken +broker metadata so you can verify diagnostics without comparing command output +by hand. + +Credential values shown by your local commands should be treated as secrets. +Do not paste unredacted workspace keys, broker API keys, or agent tokens into +issues or shared logs. + +## Stale Broker Connection + +This repro simulates a connection file that points at an old broker URL while +recording the PID of a newer broker process. + +```bash +TMP=$(mktemp -d "${TMPDIR:-/tmp}/relay-repro-stale-XXXXXX") +cd "$TMP" + +agent-relay up --no-dashboard --port 49200 +CONN1=$(cat .agent-relay/connection.json) +PID1=$(node -e 'console.log(JSON.parse(process.argv[1]).pid)' "$CONN1") + +kill -9 "$PID1" + +agent-relay up --no-dashboard --port 49300 +CONN2=$(cat .agent-relay/connection.json) + +node -e ' +const fs = require("node:fs"); +const oldConn = JSON.parse(process.argv[1]); +const nextConn = JSON.parse(process.argv[2]); +oldConn.pid = nextConn.pid; +fs.writeFileSync(".agent-relay/connection.json", JSON.stringify(oldConn, null, 2)); +' "$CONN1" "$CONN2" + +agent-relay status +agent-relay status --wait-for=1 +agent-relay who --json +``` + +Expected result: + +```text +Status: STARTING +Broker process is running, but the API did not become ready before timeout. + +[] +``` + +The broker process exists, but the recorded API URL is stale. Diagnostic +commands should report the mismatch instead of treating the workspace as a +healthy broker session. + +## Unresolved API Key Template + +This repro checks that a literal unresolved environment template fails before a +messaging command attempts a Relaycast read. + +First confirm that the correctly resolved local broker context reaches +Relaycast: + +```bash +agent-relay replies WorkerA --json +``` + +Expected result when there is no conversation: + +```text +No DM conversation with WorkerA. +``` + +Then run the same command with a literal unresolved template: + +```bash +RELAY_API_KEY='${RELAY_API_KEY}' agent-relay replies WorkerA --json +``` + +Expected result: + +```text +Failed to initialize relaycast client: Workspace key required (rk_live_...) +``` + +The command should reject the unresolved template as invalid credential input +instead of treating it as a real workspace key. + +## Half-Started Broker Metadata + +This repro simulates a broker process that is still running after its local +connection metadata has disappeared. + +```bash +TMP=$(mktemp -d "${TMPDIR:-/tmp}/relay-repro-half-started-XXXXXX") +cd "$TMP" + +env \ + -u RELAY_API_KEY \ + -u RELAY_AGENT_TOKEN \ + -u RELAY_WORKSPACES_JSON \ + -u RELAY_DEFAULT_WORKSPACE \ + -u RELAY_WORKSPACE_ID \ + -u RELAY_BASE_URL \ + -u RELAY_BROKER_URL \ + -u RELAY_BROKER_API_KEY \ + -u RELAY_AGENT_NAME \ + -u RELAY_AGENT_TYPE \ + -u RELAY_STRICT_AGENT_NAME \ + agent-relay up --no-dashboard --port 49600 + +CONN=$(cat .agent-relay/connection.json) +PID=$(node -e 'console.log(JSON.parse(process.argv[1]).pid)' "$CONN") +rm .agent-relay/connection.json + +ps -p "$PID" -o pid=,comm= + +env \ + -u RELAY_API_KEY \ + -u RELAY_AGENT_TOKEN \ + -u RELAY_WORKSPACES_JSON \ + -u RELAY_DEFAULT_WORKSPACE \ + -u RELAY_WORKSPACE_ID \ + -u RELAY_BASE_URL \ + -u RELAY_BROKER_URL \ + -u RELAY_BROKER_API_KEY \ + -u RELAY_AGENT_NAME \ + -u RELAY_AGENT_TYPE \ + -u RELAY_STRICT_AGENT_NAME \ + agent-relay status + +env \ + -u RELAY_API_KEY \ + -u RELAY_AGENT_TOKEN \ + -u RELAY_WORKSPACES_JSON \ + -u RELAY_DEFAULT_WORKSPACE \ + -u RELAY_WORKSPACE_ID \ + -u RELAY_BASE_URL \ + -u RELAY_BROKER_URL \ + -u RELAY_BROKER_API_KEY \ + -u RELAY_AGENT_NAME \ + -u RELAY_AGENT_TYPE \ + -u RELAY_STRICT_AGENT_NAME \ + agent-relay history + +env \ + -u RELAY_API_KEY \ + -u RELAY_AGENT_TOKEN \ + -u RELAY_WORKSPACES_JSON \ + -u RELAY_DEFAULT_WORKSPACE \ + -u RELAY_WORKSPACE_ID \ + -u RELAY_BASE_URL \ + -u RELAY_BROKER_URL \ + -u RELAY_BROKER_API_KEY \ + -u RELAY_AGENT_NAME \ + -u RELAY_AGENT_TYPE \ + -u RELAY_STRICT_AGENT_NAME \ + agent-relay replies WorkerA --json +``` + +Expected result: + +```text +Status: STOPPED + +Failed to initialize relaycast client: Failed to read broker connection metadata. Start the broker with `agent-relay up` or set RELAY_API_KEY. +``` + +The process table can still contain the broker PID, but the workspace is missing +the metadata needed for broker-backed messaging. Diagnostic commands should +surface that missing metadata directly. + +## Cleanup + +Each repro creates a temporary project directory and may leave a broker process +behind. Clean up with: + +```bash +agent-relay down --force --all +``` + diff --git a/web/content/docs/harness-runtime-config.mdx b/web/content/docs/harness-runtime-config.mdx new file mode 100644 index 000000000..e8b91aee8 --- /dev/null +++ b/web/content/docs/harness-runtime-config.mdx @@ -0,0 +1,188 @@ +--- +title: Harness runtime config +description: Reference for the concrete HarnessConfig objects that SDK adapters send to the broker. +--- + +Use this page when you need the exact `HarnessConfig` shape. For a shorter +guide with Codex, Claude, and OpenCode examples, start with +[Harnesses](/docs/harnesses). + +The broker only executes serializable config data. SDK adapters can help you +build that data, but the broker does not call SDK functions after spawn and does +not keep a named harness registry. + +## Runtime Categories + +| Runtime | Use for | Broker capabilities | +| --- | --- | --- | +| `pty` | Terminal-backed CLIs such as Codex or Claude Code | process spawn, PTY stream, input, resize, snapshot, delivery, release | +| `headless` | Non-terminal sessions such as OpenCode app-server | session delivery, release | + +When `runtime` is `headless`, `driver` defaults to `app_server`. + +## Terms + +| Term | Meaning | +| --- | --- | +| Harness config | Concrete `pty` or `headless` JSON the broker can validate and run | +| Harness adapter | SDK or userland helper that returns a harness config | +| Named harness | SDK-side shortcut in `new AgentRelay({ harnesses })` | +| `harnessConfig` | Spawn field carrying the concrete config to the broker | + +Named harnesses are local SDK ergonomics. The SDK resolves them to an inline +`harnessConfig` before the spawn request reaches the broker. + +## PTY Config + +Use `pty` for terminal-backed coding harnesses: + +```typescript +type PtyHarnessConfig = { + runtime: 'pty'; + command: string; + args: string[]; + cwd?: string; + env?: Record; + sessionId?: string; + delivery?: { + mode?: 'pty-injection'; + format?: 'relay-block'; + }; + metadata?: Record; +}; +``` + +Example: + +```typescript +const harnessConfig = { + runtime: 'pty', + command: 'codex', + args: ['resume', sessionId], + cwd, + env: { + PATH: process.env.PATH ?? '', + CODEX_HOME: process.env.CODEX_HOME ?? '', + }, + sessionId, +} satisfies ResolvedHarnessConfig; +``` + +The broker owns the spawned process, PTY stream, raw input, resize, snapshots, +message injection, and release behavior for this runtime. + +## Headless App-Server Config + +Use `headless` for a non-terminal agent session controlled through an app +server. OpenCode is the first supported protocol: + +```typescript +type HeadlessAppServerHarnessConfig = { + runtime: 'headless'; + driver?: 'app_server'; + protocol: 'opencode' | string; + endpoint: string; + sessionId: string; + auth?: { + type: 'bearer' | 'basic' | 'none'; + token?: string; + username?: string; + password?: string; + }; + host?: { + ownership?: 'broker-owned' | 'attached'; + pid?: number; + }; + release?: 'abort' | 'detach' | 'delete'; + metadata?: Record; +}; +``` + +Example: + +```typescript +const harnessConfig = { + runtime: 'headless', + protocol: 'opencode', + endpoint: 'http://127.0.0.1:4096', + sessionId: 'ses_123', + host: { ownership: 'attached', pid: 34567 }, + release: 'abort', +} satisfies ResolvedHarnessConfig; +``` + +For now, app-server configs are attach-only. `host.ownership: 'broker-owned'` +is reserved until the broker owns app-server lifecycle supervision. If +`host.pid` is provided, the broker reports that PID as the harness process ID. + +## Adapter Pattern + +An adapter should return config data: + +```typescript +function companyClaude(): ResolvedHarnessConfig { + return { + runtime: 'pty', + command: 'claude', + args: [ + '--dangerously-skip-permissions', + '--append-system-prompt', + 'Follow the company review rubric.', + ], + }; +} +``` + +Register stable configs by name: + +```typescript +const relay = new AgentRelay({ + harnesses: { + 'company-claude': companyClaude(), + }, +}); +``` + +Use an inline `harnessConfig` when setup changes per spawn, such as creating a +Codex session: + +```typescript +const sessionId = await createCodexSession({ cwd, task }); + +await relay.spawn('CodexReviewer', 'codex', task, { + harnessConfig: { + runtime: 'pty', + command: 'codex', + args: ['resume', sessionId], + cwd, + sessionId, + }, +}); +``` + +Do not copy the whole process environment into `env`, and do not put secrets in +`metadata`. `env` and `auth` are visible to the broker, so pass explicit +allowlists. + +## Spawn Payloads + +`POST /api/spawn` accepts `harnessConfig`: + +```json +{ + "name": "CodexReviewer", + "cli": "codex", + "task": "Review the current diff.", + "harnessConfig": { + "runtime": "pty", + "command": "codex", + "args": ["resume", "session_123"], + "sessionId": "session_123" + } +} +``` + +The broker rejects `harnessId`. Relaycast spawns that need custom behavior +should also send a full inline `harnessConfig`, which keeps each spawn +self-contained across local, remote, and multi-broker deployments. + diff --git a/web/content/docs/harnesses.mdx b/web/content/docs/harnesses.mdx new file mode 100644 index 000000000..cf18bafb6 --- /dev/null +++ b/web/content/docs/harnesses.mdx @@ -0,0 +1,172 @@ +--- +title: Harnesses +description: Define custom PTY and headless harness configs in the SDK. +--- + +A harness is the thing Relay controls for an agent. The broker only executes +serializable `HarnessConfig` data. SDK "adapters" are helpers that return those +configs before a spawn request is sent. + +Relay supports two runtime categories: + +| Runtime | Use for | Broker capabilities | +| --- | --- | --- | +| `pty` | Terminal-backed CLIs such as Codex or Claude Code | stream, input, resize, snapshot, delivery, release | +| `headless` | Non-terminal sessions such as OpenCode app-server | delivery, release | + +When `runtime` is `headless`, `driver` defaults to `app_server`. + +## Naming + +| Name | Meaning | +| --- | --- | +| Harness config | Concrete `pty` or `headless` JSON the broker can validate and run | +| Harness adapter | SDK helper that returns a harness config | +| Named harness | SDK-side shortcut in `new AgentRelay({ harnesses })` | +| `harnessConfig` | Spawn field carrying the concrete config to the broker | + +The broker boundary is always explicit config data. Named harnesses are resolved +by the SDK before spawn; Relaycast and multi-broker spawns should send +`harnessConfig` directly. + +## Claude PTY Config + +Use a named harness when the command is stable across spawns: + +```typescript file="claude-harness.ts" +import { AgentRelay } from '@agent-relay/sdk'; + +const relay = new AgentRelay({ + harnesses: { + 'company-claude': { + runtime: 'pty', + command: 'claude', + args: [ + '--dangerously-skip-permissions', + '--append-system-prompt', + 'Follow the company review rubric.', + '{modelArgs}', + '{args}', + ], + modelArgs: ['--model', '{model}'], + env: { + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1', + }, + }, + }, +}); + +await relay.spawn('ClaudeReviewer', 'company-claude', 'Review the current diff.', { + model: 'opus', + args: ['--verbose'], +}); +``` + +The SDK turns `company-claude` into an inline `harnessConfig` for that spawn. +The broker does not keep a named harness registry. + +## Codex Per-Spawn Config + +Use direct `harnessConfig` when setup produces a different config for each +spawn, such as creating or resuming a Codex session: + +```typescript file="codex-harness.ts" +import { AgentRelay, type ResolvedHarnessConfig } from '@agent-relay/sdk'; + +function codexResume(sessionId: string, cwd: string): ResolvedHarnessConfig { + return { + runtime: 'pty', + command: 'codex', + args: ['resume', sessionId], + cwd, + env: { + PATH: process.env.PATH ?? '', + CODEX_HOME: process.env.CODEX_HOME ?? '', + }, + sessionId, + }; +} + +const relay = new AgentRelay(); +const cwd = process.cwd(); +const task = 'Review the current diff.'; +const sessionId = await createCodexSession({ cwd, task }); + +await relay.spawn('CodexReviewer', 'codex', task, { + cwd, + harnessConfig: codexResume(sessionId, cwd), +}); +``` + +Do not copy the whole process environment into `env`. Pass only the keys the +harness needs. + +## OpenCode Headless Config + +Use `headless` for an agent that already exists inside an app-server session. +OpenCode `serve` is the first supported protocol: + +```typescript file="opencode-harness.ts" +import { AgentRelay, type ResolvedHarnessConfig } from '@agent-relay/sdk'; + +function opencodeSession(input: { + endpoint: string; + sessionId: string; + pid?: number; +}): ResolvedHarnessConfig { + return { + runtime: 'headless', + protocol: 'opencode', + endpoint: input.endpoint, + sessionId: input.sessionId, + host: { ownership: 'attached', pid: input.pid }, + release: 'abort', + }; +} + +const relay = new AgentRelay(); + +await relay.spawn('OpenCodeWorker', 'opencode', 'Inspect the repo.', { + harnessConfig: opencodeSession({ + endpoint: 'http://127.0.0.1:4096', + sessionId: 'ses_123', + pid: 34567, + }), +}); +``` + +For OpenCode, Relay delivers messages to `POST /session/:id/prompt_async`. +For now, app-server configs must use `protocol: 'opencode'`, an `http` or +`https` endpoint, a non-empty `sessionId`, and attached host ownership. +`broker-owned` app-server hosts are reserved for later broker supervision. + +## Relaycast Spawns + +Agent-crafted spawns should send the full config: + +```json +{ + "agent": { + "name": "CodexReviewer", + "cli": "codex", + "task": "Review the current diff.", + "harnessConfig": { + "runtime": "pty", + "command": "codex", + "args": ["resume", "session_123"], + "sessionId": "session_123" + } + } +} +``` + +Relay intentionally avoids a broker-local harness registry for now. A spawn +request should be self-contained so behavior does not depend on hidden runtime +state or which broker receives it. + +## See Also + +- [Harness runtime config](/docs/harness-runtime-config) - Exact config shapes +- [Spawning an agent](/docs/spawning-an-agent) - High-level spawn APIs +- [Event handlers](/docs/event-handlers) - SDK event subscriptions +- [Broker HTTP / WS API](/docs/reference-broker-api) - Broker API routes diff --git a/web/content/docs/reference-broker-api.mdx b/web/content/docs/reference-broker-api.mdx index da1c69bff..89cf18fde 100644 --- a/web/content/docs/reference-broker-api.mdx +++ b/web/content/docs/reference-broker-api.mdx @@ -91,13 +91,15 @@ Body (fields accept both camelCase and snake_case): "idleThresholdSecs": 0, "skipRelayPrompt": false, "restartPolicy": null, + "harnessConfig": null, "agentToken": null } ``` -`name` and `cli` are required. Returns `{ "success": true, ... }` on -success or `{ "success": false, "error": "..." }` with a non-2xx -status on failure. +`name` and `cli` are required. `harnessConfig` sends a concrete broker-executable +config for a custom PTY or headless harness. Returns `{ "success": true, ... }` +on success or `{ "success": false, "error": "..." }` with a non-2xx status on +failure. ### PTY interaction diff --git a/web/content/docs/typescript-sdk.mdx b/web/content/docs/typescript-sdk.mdx index daacf6923..c4ce2808a 100644 --- a/web/content/docs/typescript-sdk.mdx +++ b/web/content/docs/typescript-sdk.mdx @@ -29,6 +29,7 @@ const relay = new AgentRelay(options?: AgentRelayOptions); | `channels` | `string[]` | Default channels agents are joined to on spawn | `['general']` | | `cwd` | `string` | Working directory for the broker and spawned agents | `process.cwd()` | | `env` | `NodeJS.ProcessEnv` | Environment variables passed to the broker | Inherited | +| `harnesses` | `Record` | Named harness configs registered with the broker | `{}` | | `workspaceName` | `string` | Name for the auto-created Relaycast workspace | Random | | `relaycastBaseUrl` | `string` | Base URL for the Relaycast API | `https://api.relaycast.dev` | diff --git a/web/lib/docs-nav.ts b/web/lib/docs-nav.ts index 404751ff1..bd4ed913d 100644 --- a/web/lib/docs-nav.ts +++ b/web/lib/docs-nav.ts @@ -20,6 +20,7 @@ export const docsNav: NavGroup[] = [ title: 'Basics', items: [ { title: 'Spawning an agent', slug: 'spawning-an-agent' }, + { title: 'Harnesses', slug: 'harnesses' }, { title: 'Sending messages', slug: 'sending-messages' }, { title: 'Event handlers', slug: 'event-handlers' }, { title: 'Channels', slug: 'channels' }, @@ -94,6 +95,8 @@ const ALL_SLUGS = [ 'communicate-openai-agents', 'communicate-swarms', 'communicate-crewai', + 'doctor-orchestration-repros', + 'harness-runtime-config', 'local-mode', 'reference-openclaw', 'reference-workflows',