139: "Who can I ask?" — discover teammate agents by skill and engage them; publish cards for hosted personas - #178
Conversation
|
End-to-end merge gate: this PR is not production-ready yet. Validated locally at
Real integration blockers:
Unblock/merge criteria:
Marking draft so the passing mocked checks cannot be mistaken for full ecosystem proof. |
|
Addressed the branch-integration finding from the merge-gate review on the current PR head.
The PR remains draft. The external merge gates are still blocked exactly as identified: Relaycast #294 is not deployed, Workforce #296 has not published |
Stale-PR triage: REVIVE — the blocker that stalled this has since clearedTriage sweep of the 6 open PRs, evaluated against #139's closure is a false positive — this work is not done#139 shows Factory has no PR #2891 — its numbering is in the ~270s. PR #2891 is in Nothing on main implements thisNo The question that stalled it has been answered by eventsThis PR stopped on 2026-07-22 with an open human-input request:
That was never answered in-thread, but upstream resolved it two days later.
Rebase cost: moderate, mostly mechanicalTested in an isolated detached worktree (never the shared checkout, no force-push). 7 files conflict, one block each: The lockfile and CI. Still a draft — it would need the dependency bump, mapper deletion, and un-drafting before review. Recommendation only — I do not close or merge. Posted by an automated triage lane. |
Triage: REBASE-AND-FINISH — unlanded, blocker cleared, and the conflicts are mostly mechanicalStale-PR triage pass. Recommendation only; no ruling has been made yet. Evidence against This is the healthiest of the four stale Factory PRs and the one I'd revive first. 1. Is the work already on main by another route? — No.Checked
None of this PR's 300-line teammate directory or 298-line persona-card publisher exists on main. 2. Does the parent issue still describe something we want? — Yes. And #139 is wrongly closed; it should be reopened.
The second closure is not the same defect, and #278's fix would not have prevented it. #278 is a real merged PR in this repository, so repo-qualification passes. But #278's file list is exactly (I have not established whether the running Factory had picked up #278's build by 23:27 — merged is not released is not deployed — so this may be the old binary re-firing rather than a surviving hole in the new logic. Either way the closure is invalid and worth a separate look. Flagging, not filing, pending your ruling.) Recommendation: reopen #139. I have not done so — that is outside my brief. 3. Is the original blocker cleared? — Yes. Verified independently against the registry, not against upstream issue state.#139's open human-input request from 2026-07-22 was: "Which published @agentworkforce/persona-kit version containing I unpacked the published tarball rather than trusting a version claim:
4. How big is the conflict? — 7 files, 82 behind — but 4 of the 7 are mechanical.
Seven conflicts sounds worse than it is. Inspected individually:
So the substantive merge work is roughly 60 additive lines across two fleet clients. Rebase, don't redo — the 300-line RecommendationREBASE-AND-FINISH. Concretely, on a ruling:
Holding for a ruling — not rebasing, reopening, or closing anything on my own. |
d93aaa5 to
996b7bc
Compare
Rebase-and-finish updateRebased this PR onto current The July blocker in Workforce is resolved in code:
Verification at the rebased content:
The PR remains draft for one external reason that the rebase cannot fix: AgentWorkforce/relaycast#294 is still open and an unauthenticated probe of the required directory route still returns HTTP 404. Therefore the required real deployed discover → ask → reply → persona publish/directory proof is not available yet. The checked-in mock round trip is green, but I am not presenting it as production proof. No merge performed. |
|
Hosted CI is green at exact head The PR intentionally remains draft only for the live Relaycast directory proof recorded above. |
…y title (#327) Refs #313. Orphan recovery demanded the scope label alone while isInFactoryScope accepts title prefix OR label, so title-scoped orphans were never recovered. Issue reference deliberately omitted: the PR title cited (#139), which is unrelated teammate-discovery work implemented in PR #178 and must not be auto-closed by this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
996b7bc to
5a53da6
Compare
|
Taken out of draft and rebased onto current Why this sat still. It went quiet on 2026-08-17, not because it was abandoned but because nobody owned it. It was checked for supersession against #327, which also referenced #139 — the two share zero files ( Rebase. All 6 commits replayed onto Local verification at
Dependency change, called out explicitly: @coderabbitai review @codex review Requesting both explicitly rather than waiting for an automatic pass. CodeRabbit is an incremental reviewer and does not re-review already-reviewed commits on its own, and cubic is currently returning |
|
|
|
Warning Review limit reachedNext included review available in 50 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (25)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a53da6192
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Addresses both codex findings on #178. P1 -- askTeammate could never resolve on RelayFleetClient. That backend cannot represent `SendInput.from` (documented at its own sendMessage) and stamps every inbound message's `target` with the authenticated identity. The listener required `target === from`, the caller's worker name, so a valid reply was discarded and the ask timed out. Add the optional `FleetClient.effectiveSender()`, implement it on RelayFleetClient, and match replies against it, falling back to `from` on a backend that carries the requested sender faithfully. P2 -- the same backend drops `SendInput.data`, so `requestId` never reaches the teammate and cannot be echoed back. Two open questions to one teammate were therefore indistinguishable, and either reply could resolve either waiter -- including a late reply from a question that had already timed out. Refuse the overlap on backends that cannot correlate, and leave faithful backends unrestricted. Verified by mutation, not by colour: restoring `target === from` fails both new tests on a 1s timeout, which is the reported bug; removing the overlap guard fails the P2 test alone. The pre-existing tests stay green under the first mutant, which is why this was not caught before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
|
New head
Mutation-verified: restoring Local at @coderabbitai review @codex review CodeRabbit — your previous attempt on this PR returned "Review rate limited" with a green commit status and no review, so this head has had no CodeRabbit pass at all. Please treat the whole diff as new. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== PR metadata ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,baseRefOid,files \
--jq '{headRefOid, baseRefOid, files: [.files[].path]}'
echo '=== Changed-file status ==='
git diff --stat origin/main...HEAD
git diff --name-status origin/main...HEAD
echo '=== TypeScript outlines for changed source and test files ==='
fd -e ts -e json . src test .agentworkforce | sort | while read -r f; do
case "$f" in
src/fleet/teammates.ts|src/fleet/relay-fleet-client.ts|src/fleet/internal-fleet-client.ts|src/ports/fleet.ts|src/node/factory-persona-card.ts|src/node/factory-node.ts|src/index.ts|test/e2e/ask-a-teammate.test.ts|*.test.ts)
echo "--- $f"
if [[ "$f" == *.ts ]]; then ast-grep outline "$f" --items all || true; fi
;;
esac
done
echo '=== Full PR diff ==='
git diff --find-renames --find-copies origin/main...HEAD -- \
package.json package-lock.json \
src/ports/fleet.ts src/fleet/teammates.ts src/fleet/relay-fleet-client.ts src/fleet/internal-fleet-client.ts \
src/node/factory-persona-card.ts src/node/factory-node.ts src/index.ts \
test/e2e/ask-a-teammate.test.ts
echo '=== Relevant symbol definitions/usages ==='
rg -n -C 4 --glob '*.ts' \
'effectiveSender|askTeammate|discoverTeammates|sendMessage\s*\(|waitForInjected\s*\(|onAgentMessage\s*\(|deriveAgentCard|RelaycastAgentCardPublisher|startFactoryNode' \
src test
echo '=== Dependency and test config context ==='
sed -n '1,220p' package.json
if [ -f vitest.config.ts ]; then sed -n '1,220p' vitest.config.ts; fiLength of output: 139677 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== Changed paths and diff summary ==='
git diff --name-status origin/main...HEAD
git diff --stat origin/main...HEAD
echo '=== askTeammate implementation with line numbers ==='
nl -ba src/fleet/teammates.ts | sed -n '105,215p'
echo '=== askTeammate tests with line numbers ==='
nl -ba src/fleet/teammates.test.ts | sed -n '55,255p'
echo '=== Relay sender, send, and inbound-message mapping ==='
nl -ba src/fleet/relay-fleet-client.ts | sed -n '500,575p'
rg -n -C 8 '`#send`\(|`#emitAgentMessage`|authenticatedAgentName|data:' src/fleet/relay-fleet-client.ts
echo '=== Node runtime and card-publication tests ==='
nl -ba src/node/factory-node-runtime.ts | sed -n '1,180p'
nl -ba src/node/factory-persona-card.test.ts | sed -n '1,260p'
echo '=== Directory tests ==='
nl -ba src/fleet/teammates.test.ts | sed -n '1,65p'
rg -n -C 5 'discoverTeammates|effectiveSender' src/fleet/relay-fleet-client.test.ts src/fleet/internal-fleet-client.test.ts
echo '=== Exports and package public paths ==='
git diff origin/main...HEAD -- src/index.ts package.jsonLength of output: 3175 🏁 Script executed: #!/bin/bash
set -euo pipefail
numbered() {
awk -v start="$2" -v end="$3" 'NR >= start && NR <= end { printf "%6d\t%s\n", NR, $0 }' "$1"
}
echo '=== askTeammate implementation ==='
numbered src/fleet/teammates.ts 105 215
echo '=== askTeammate tests ==='
numbered src/fleet/teammates.test.ts 55 255
echo '=== Relay sender, send, and inbound mapping ==='
numbered src/fleet/relay-fleet-client.ts 500 575
rg -n -C 8 '`#send`\(|`#emitAgentMessage`|authenticatedAgentName|data:' src/fleet/relay-fleet-client.ts
echo '=== Factory node runtime ==='
numbered src/node/factory-node-runtime.ts 1 180
echo '=== Persona-card tests ==='
numbered src/node/factory-persona-card.test.ts 1 260
echo '=== Directory tests and client coverage ==='
numbered src/fleet/teammates.test.ts 1 65
rg -n -C 5 'discoverTeammates|effectiveSender' src/fleet/relay-fleet-client.test.ts src/fleet/internal-fleet-client.test.ts
echo '=== Supporting port and fake changes ==='
git diff origin/main...HEAD -- src/ports/index.ts src/testing/fakes.tsLength of output: 36917
P1 — Guard discovered targets after resolution On Resolve the teammate before creating the guard key, then guard by the resolved teammate address for every uncorrelated backend. Add a regression test that uses You are interacting with an AI system. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc9436c516
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Second-pass codex review found three defects in the first fix. All three were correct. P1 -- `effectiveSender()` returned `#authenticatedAgentName`, which holds the CONFIGURED `agentName` until something calls `agents.me()`. An injected `messaging` or an existing `agentToken` can authenticate as a different name, so the synchronous accessor handed out a pre-auth guess and replies were matched against a name the server never used. Make the port method async and resolve the identity for real before returning. P2a -- the guard exempted "correlatable" backends, defined as those where `replyTarget === from`. That reasoning was wrong: `AgentMessage` echoes no request field on ANY backend, so the same requester asking one teammate twice is ambiguous even where `from` is carried faithfully. The exemption is removed; the claim is unconditional. P2b -- the claim was keyed on `input.teammate` and skipped entirely when the caller passed a skill/tag/query, so two discovery-based asks resolving to the same teammate both armed listeners. The comment claimed the discovery leg was covered; it was not. The claim is now taken after discovery, against the resolved teammate. Mutation-verified per finding: dropping the await fails 4 tests including the async-identity case; restoring the correlatable exemption fails exactly the same-requester and discovery cases; keying on the caller-supplied teammate fails only the discovery-contention case. The previous concurrency test used two DIFFERENT requesters, which is precisely the shape that dodged P2a. It is kept, renamed for what it actually proves, and joined by a same-requester test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Session-Id: ded1b209-9eee-4893-9e62-15b85847e24f
|
@codex review |
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6d5b7cbd5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
a6d5b7c to
ac4eb0a
Compare
|
@coderabbitai review Requested for exact head |
|
@codex review\n\nPlease review exact current head |
|
@coderabbitai review\n\nPlease review exact current head |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='ac4eb0a61069c01b2c9c0e3b961b258920ef97a3'
show() {
local file="$1" start="$2" end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
echo '=== Commit and PR identity ==='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${requested_head}^{commit}"
printf 'requested='; git rev-parse "$requested_head"
if [ "$(git rev-parse HEAD)" = "$requested_head" ]; then
echo 'exact_head_checked_out=true'
else
echo 'exact_head_checked_out=false'
fi
gh pr view 178 --repo AgentWorkforce/factory \
--json headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus,statusCheckRollup \
--jq '{headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'
echo '=== Requested commit and aggregate diff ==='
git show --format=fuller --stat "$requested_head"
git diff --name-status origin/main..."$requested_head"
git diff --stat origin/main..."$requested_head"
git diff --unified=60 "${requested_head}^" "$requested_head"
echo '=== Outline changed TypeScript files ==='
git diff --name-only "${requested_head}^" "$requested_head" -- '*.ts' | sort | while read -r file; do
echo "--- $file"
ast-grep outline "$file" --items all || true
done
echo '=== Stream identity, rebind, and node termination references ==='
rg -n -C 12 --glob '*.ts' \
'\b(messageStreamIdentity|messageStreamScope|rebind|rebinds?|stream|askTeammate|inFlightAsks|onAgentMessage|whenMessagesObservable|terminate|termination|onRegistered|startFactoryNode|dispose|close)\b' \
src test
echo '=== Relevant implementation files ==='
for file in \
src/fleet/teammates.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/relay-fleet-client.ts \
src/ports/fleet.ts \
src/node/factory-node-runtime.ts \
src/node/factory-node.ts \
src/node/factory-persona-card.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.test.ts \
src/node/factory-node-runtime.test.ts \
src/node/factory-persona-card.test.ts \
test/e2e/ask-a-teammate.test.ts
do
if [ -f "$file" ]; then
echo "--- $file"
ast-grep outline "$file" --items all || true
fi
doneLength of output: 198546 🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='ac4eb0a61069c01b2c9c0e3b961b258920ef97a3'
show() {
local file="$1" start="$2" end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
echo '=== Exact latest commit ==='
git show --format='%H%n%P%n%s%n%b' --stat "$requested_head"
git diff --unified=45 "${requested_head}^" "$requested_head" -- \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.reconnect.test.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/teammates.ts \
src/ports/fleet.ts
echo '=== Reconnect implementation ==='
rg -n -C 22 \
'`#callBroker`|`#reconnectIfBrokerChanged`|`#readConnectionFile`|`#connectionFilePid`|messageStreamIdentity|dispose\(\)|`#ensureEventSubscription`|`#connectedBroker`|`#disposed`' \
src/fleet/internal-fleet-client.ts
echo '=== Exact implementation slices ==='
show src/fleet/internal-fleet-client.ts 130 285
show src/fleet/internal-fleet-client.ts 580 810
show src/fleet/internal-fleet-client.ts 940 1120
show src/fleet/internal-fleet-client.ts 1120 1335
echo '=== Reconnect test file ==='
show src/fleet/internal-fleet-client.reconnect.test.ts 1 520
echo '=== Node termination regression tests ==='
rg -n -C 20 \
'markAgentTerminal|terminal|dispose\(\)|owned broker|spawned agents to exit|release.*terminal' \
src/fleet/internal-fleet-client.test.ts
echo '=== Exact-head checks ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'Length of output: 71756 🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='ac4eb0a61069c01b2c9c0e3b961b258920ef97a3'
show() {
file="$1"; start="$2"; end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
awk -v start="$start" -v end="$end" \
'NR >= start && NR <= end { printf "%6d %s\n", NR, $0 }' "$file"
}
echo '=== Node runtime latest delta ==='
git diff --unified=55 "${requested_head}^" "$requested_head" -- \
src/node/factory-node-runtime.ts \
src/node/factory-persona-card.test.ts
echo '=== Node runtime implementation and all call sites ==='
show src/node/factory-node-runtime.ts 1 220
rg -n -C 14 --glob '*.ts' \
'\b(startFactoryNode|onRegistered|onTerminated|onTerminate|cardPublished|publishAgentCard|RunningFactoryNode|dispose)\b' \
src test
echo '=== Persona runtime regressions ==='
show src/node/factory-persona-card.test.ts 1 360
echo '=== Internal disposal and rebind implementations ==='
show src/fleet/internal-fleet-client.ts 530 890
show src/fleet/internal-fleet-client.ts 1170 1325
echo '=== Rebind and stream-identity regression sections ==='
show src/fleet/internal-fleet-client.reconnect.test.ts 300 470
show src/fleet/internal-fleet-client.test.ts 1650 1785
echo '=== Exact-head checks ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'Length of output: 167588 🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='ac4eb0a61069c01b2c9c0e3b961b258920ef97a3'
show() {
file="$1"; start="$2"; end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
awk -v start="$start" -v end="$end" \
'NR >= start && NR <= end { printf "%6d %s\n", NR, $0 }' "$file"
}
echo '=== Complete node runtime ==='
show src/node/factory-node-runtime.ts 1 220
echo '=== Exact node-runtime delta ==='
git diff --unified=75 "${requested_head}^" "$requested_head" -- src/node/factory-node-runtime.ts
echo '=== Node runtime test cases ==='
rg -n -A75 -B12 \
'rejects card publication when node startup terminates|rejects card publication on a pre-registration stop|derives a shared-schema card|retries a transient card-publication' \
src/node/factory-persona-card.test.ts
echo '=== RunningNode and startServeNode type contract ==='
rg -n -C 8 --glob '*.ts' \
'\binterface RunningNode\b|\btype RunningNode\b|\bstartServeNode\b|\bdone:\b|\bstop:\b' \
src/node src --glob '!src/node/factory-persona-card.test.ts'
echo '=== Current status check summary ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion}]}'Length of output: 28896
P1 — Reject In If Reject Add a regression where The stream-rebind change is otherwise sound. The connection-file scope remains stable across broker replacement, and the stale-call retry guard stops broker activity after The exact-head You are interacting with an AI system. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac4eb0a610
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ac4eb0a to
d6c3605
Compare
|
@coderabbitai review Requested for exact head |
|
@codex review\n\nPlease review exact current head |
|
@coderabbitai review\n\nPlease review exact current head |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='d6c3605e11bfc2c5d23be7f0e4970149bf62edf1'
show() {
local file="$1" start="$2" end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
echo '=== Commit and PR identity ==='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${requested_head}^{commit}"
printf 'requested='; git rev-parse "$requested_head"
if [ "$(git rev-parse HEAD)" = "$requested_head" ]; then
echo 'exact_head_checked_out=true'
else
echo 'exact_head_checked_out=false'
fi
gh pr view 178 --repo AgentWorkforce/factory \
--json headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus \
--jq '{headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus}'
echo '=== Requested commit and aggregate diff ==='
git show --format=fuller --stat "$requested_head"
git diff --name-status origin/main..."$requested_head"
git diff --stat origin/main..."$requested_head"
git diff --unified=60 "${requested_head}^" "$requested_head" -- \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/relay-fleet-client.ts \
src/fleet/relay-fleet-client.test.ts \
src/ports/fleet.ts \
src/testing/fakes.ts \
test/e2e/ask-a-teammate.test.ts
echo '=== Target file outlines ==='
for file in \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/relay-fleet-client.ts \
src/ports/fleet.ts \
src/testing/fakes.ts \
test/e2e/ask-a-teammate.test.ts
do
if [ -f "$file" ]; then
echo "--- $file"
ast-grep outline "$file" --items all || true
fi
done
echo '=== Claim scope, delivery wait, and failure lifecycle ==='
rg -n -C 14 --glob '*.ts' \
'\b(askTeammate|messageStreamIdentity|messageStreamScope|inFlightAsks|claim|pending|waitForInjected|onDeliveryFailed|FleetDeliveryRejectedError|delivery_failed|message_delivery_failed|sendMessage|onAgentMessage|whenMessagesObservable)\b' \
src test
echo '=== Focused implementations ==='
show src/fleet/teammates.ts 80 380
show src/fleet/internal-fleet-client.ts 600 780
show src/ports/fleet.ts 100 280
echo '=== Relevant regression tests ==='
rg -n -C 22 --glob '*.test.ts' \
'\b(explicit.*scope|scope.*precedence|pending.*delivery|delivery.*pending|claim.*retention|messageStreamIdentity|waitForInjected|concurrent|reject)\b' \
src/fleet src test
echo '=== Exact-head checks ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'Length of output: 203386 🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='d6c3605e11bfc2c5d23be7f0e4970149bf62edf1'
echo '=== Identity ==='
printf 'checked_out='; git rev-parse HEAD
printf 'requested='; git rev-parse "$requested_head"
if [ "$(git rev-parse HEAD)" = "$requested_head" ]; then
echo 'exact_head_checked_out=true'
else
echo 'exact_head_checked_out=false'
fi
gh pr view 178 --repo AgentWorkforce/factory \
--json headRefOid,mergeable,mergeStateStatus,isDraft \
--jq '{headRefOid,mergeable,mergeStateStatus,isDraft}'
echo '=== Exact latest delta ==='
git show --format='%H%n%P%n%s%n%b' --stat "$requested_head"
git diff --unified=45 "${requested_head}^" "$requested_head" -- \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/ports/fleet.ts \
src/testing/fakes.ts
echo '=== Target outlines ==='
for file in src/fleet/teammates.ts src/fleet/teammates.test.ts src/fleet/internal-fleet-client.ts src/ports/fleet.ts; do
echo "--- $file"
ast-grep outline "$file" --items all || true
done
echo '=== askTeammate implementation ==='
sed -n '90,330p' src/fleet/teammates.ts
echo '=== Claim scope helpers ==='
sed -n '330,460p' src/fleet/teammates.ts
echo '=== Internal client delivery and stream identity ==='
rg -n -C 14 \
'messageStreamIdentity\s*\(|messageStreamScope|waitForInjected\s*\(|FleetDeliveryRejectedError|delivery_failed|message_delivery_failed|pendingInjected' \
src/fleet/internal-fleet-client.ts
sed -n '620,760p' src/fleet/internal-fleet-client.ts
echo '=== FleetClient contract ==='
sed -n '120,260p' src/ports/fleet.ts
echo '=== Targeted teammate regressions ==='
rg -n -C 20 \
'explicit.*scope|scope.*precedence|pending.*delivery|delivery.*pending|claim.*retain|claim.*release|waitForInjected|concurrent|second ask' \
src/fleet/teammates.test.ts
echo '=== Targeted internal-client regressions ==='
rg -n -C 18 \
'messageStreamIdentity|messageStreamScope|pending.*delivery|delivery.*pending|waitForInjected' \
src/fleet/internal-fleet-client.test.ts
echo '=== Exact-head checks ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'Length of output: 148979
P1 — Keep the claim after an early reply if delivery confirmation fails ambiguously
A Quarantine the pair when an early reply has settled the ask but the delivery operation ends with any non- Add a regression with this sequence:
The explicit You are interacting with an AI system. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6c3605e11
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
d6c3605 to
0e8801a
Compare
|
@coderabbitai review Requested for exact head |
0e8801a to
5ed5157
Compare
|
@coderabbitai review Requested for exact head |
|
@coderabbitai review Please review exact current head 5ed5157, including the in-flight readiness-resend settlement fix and regression. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='5ed51578a8304f9c9bf7b7899d96b98e4be4c480'
show() {
local file="$1" start="$2" end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
echo '=== Commit and pull request identity ==='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${requested_head}^{commit}"
printf 'requested='; git rev-parse "$requested_head"
if [ "$(git rev-parse HEAD)" = "$requested_head" ]; then
echo 'exact_head_checked_out=true'
else
echo 'exact_head_checked_out=false'
fi
gh pr view 178 --repo AgentWorkforce/factory \
--json headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus \
--jq '{headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus}'
echo '=== Requested commit delta ==='
git show --format=fuller --stat "$requested_head"
git diff --unified=70 "${requested_head}^" "$requested_head" -- \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/ports/fleet.ts \
src/testing/fakes.ts \
test/e2e/ask-a-teammate.test.ts
echo '=== Relevant source structure ==='
for file in \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/ports/fleet.ts \
src/testing/fakes.ts \
test/e2e/ask-a-teammate.test.ts
do
if [ -f "$file" ]; then
echo "--- $file"
ast-grep outline "$file" --items all || true
fi
done
echo '=== askTeammate implementation and helpers ==='
show src/fleet/teammates.ts 80 440
echo '=== Resend, readiness, delivery, and claim references ==='
rg -n -C 16 --glob '*.ts' \
'\b(askTeammate|waitForInjected|whenMessagesObservable|sendMessage|readiness|resend|delivery|FleetDeliveryRejectedError|claim|quarantine|settle|settled|onAgentMessage|messageStreamIdentity)\b' \
src/fleet src/ports src/testing test
echo '=== Focused regression tests ==='
rg -n -C 28 --glob '*.test.ts' \
'\b(readiness|resend|in.flight|in-flight|early reply|delivery.*timeout|timeout.*delivery|claim.*retain|second ask|concurrent)\b' \
src/fleet/teammates.test.ts src/fleet/internal-fleet-client.test.ts test/e2e/ask-a-teammate.test.ts
echo '=== Exact-head checks ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'Length of output: 92329 🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='5ed51578a8304f9c9bf7b7899d96b98e4be4c480'
show() {
local file="$1" start="$2" end="$3"
printf '=== %s:%s-%s ===\n' "$file" "$start" "$end"
awk -v start="$start" -v end="$end" \
'NR >= start && NR <= end { printf "%6d %s\n", NR, $0 }' "$file"
}
echo '=== Exact current delta ==='
git diff --unified=45 "${requested_head}^" "$requested_head" -- \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts
echo '=== waitForInjected creation and resend implementation ==='
rg -n -C 18 \
'async waitForInjected|`#triggerReadyResend`|async `#resendPendingInjected`|`#resolvePendingInjected`|`#settlePendingInjected`|`#rejectPendingInjected`|`#failPendingInjectedEvent`|`#markAgentReady`' \
src/fleet/internal-fleet-client.ts
echo '=== Full delivery waiter region ==='
show src/fleet/internal-fleet-client.ts 600 780
show src/fleet/internal-fleet-client.ts 920 1075
echo '=== askTeammate settlement and claim lifecycle ==='
show src/fleet/teammates.ts 155 320
echo '=== Regression tests for a blocked resend and claim retention ==='
rg -n -C 35 \
'keeps a confirmed injection pending until its readiness re-send returns|retains the pair claim after an early reply until delivery retries have stopped|disposes a logical waiter while its readiness re-send response is blocked|keeps waiting when the first event fails' \
src/fleet/internal-fleet-client.test.ts src/fleet/teammates.test.ts
echo '=== All readiness-resend test names ==='
rg -nP '^\s*it\(' src/fleet/internal-fleet-client.test.ts | rg -i -C2 'resend|injection|delivery'
echo '=== Exact-head GitHub checks ==='
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'Length of output: 119408
I found no new blocking issue in the in-flight readiness-resend settlement change.
The new regressions cover both conditions:
The If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! You are interacting with an AI system. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ed51578a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
5ed5157 to
0075364
Compare
|
@coderabbitai review Requested for exact head |
|
@coderabbitai review Please review exact current head 0075364, especially permanent claim quarantine after a possible duplicate resend and canonical broker URL stream identities. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='0075364ec248a2c0a5db5ebdc96297a949e854e2'
show() {
local file="$1" start="$2" end="$3"
printf '\n=== %s:%s-%s ===\n' "$file" "$start" "$end"
nl -ba "$file" | sed -n "${start},${end}p"
}
echo '=== Commit and PR identity ==='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${requested_head}^{commit}"
printf 'requested='; git rev-parse "$requested_head"
if [ "$(git rev-parse HEAD)" = "$requested_head" ]; then
echo 'exact_head_checked_out=true'
else
echo 'exact_head_checked_out=false'
fi
gh pr view 178 --repo AgentWorkforce/factory \
--json headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus,statusCheckRollup \
--jq '{headRefOid,baseRefOid,isDraft,mergeable,mergeStateStatus,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'
echo '=== Requested commit and latest delta ==='
git show --format=fuller --stat "$requested_head"
git diff --unified=65 "${requested_head}^" "$requested_head" -- \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/internal-fleet-client.reconnect.test.ts \
src/fleet/relay-fleet-client.ts \
src/fleet/relay-fleet-client.test.ts \
src/ports/fleet.ts \
src/testing/fakes.ts \
test/e2e/ask-a-teammate.test.ts
echo '=== Relevant source outlines ==='
for file in \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/internal-fleet-client.reconnect.test.ts \
src/fleet/relay-fleet-client.ts \
src/fleet/relay-fleet-client.test.ts \
src/ports/fleet.ts \
src/testing/fakes.ts \
test/e2e/ask-a-teammate.test.ts
do
if [ -f "$file" ]; then
echo "--- $file"
ast-grep outline "$file" --items all || true
fi
done
echo '=== Claim, resend, and stream-scope references ==='
rg -n -C 14 --glob '*.ts' \
'\b(askTeammate|inFlightAsks|claim|quarantine|permanent|timedOut|resend|readiness|waitForInjected|delivery|FleetDeliveryRejectedError|messageStreamIdentity|messageStreamScope|brokerUrl|canonical|URL|onAgentMessage|whenMessagesObservable)\b' \
src/fleet src/ports src/testing test
echo '=== Focused implementations ==='
show src/fleet/teammates.ts 80 460
show src/fleet/internal-fleet-client.ts 1 360
show src/fleet/internal-fleet-client.ts 580 1110
show src/fleet/relay-fleet-client.ts 1 260
show src/ports/fleet.ts 100 290
echo '=== Focused regression tests ==='
rg -n -C 26 --glob '*.test.ts' \
'\b(duplicate|resend|readiness|permanent|quarantine|claim|canonical|broker.*URL|URL.*broker|messageStreamIdentity|same.stream|distinct.stream)\b' \
src/fleet src testLength of output: 198236 🏁 Script executed: #!/bin/bash
set -euo pipefail
requested_head='0075364ec248a2c0a5db5ebdc96297a949e854e2'
show() {
local file="$1" start="$2" end="$3"
printf '\n=== %s:%s-%s ===\n' "$file" "$start" "$end"
awk -v start="$start" -v end="$end" \
'NR >= start && NR <= end { printf "%6d %s\n", NR, $0 }' "$file"
}
echo '=== Exact identity and checks ==='
printf 'checked_out='; git rev-parse HEAD
printf 'requested='; git rev-parse "$requested_head"
gh pr view 178 --repo AgentWorkforce/factory --json headRefOid,statusCheckRollup \
--jq '{headRefOid,checks:[.statusCheckRollup[]|{name,status,conclusion,detailsUrl}]}'
echo '=== Exact commit delta ==='
git diff --unified=75 "${requested_head}^" "$requested_head" -- \
src/fleet/teammates.ts \
src/fleet/teammates.test.ts \
src/fleet/internal-fleet-client.ts \
src/fleet/internal-fleet-client.test.ts \
src/fleet/relay-fleet-client.ts \
src/fleet/relay-fleet-client.test.ts
echo '=== askTeammate claim and delivery lifecycle ==='
show src/fleet/teammates.ts 163 332
echo '=== Internal broker stream identity implementation ==='
rg -n -C 35 \
'messageStreamIdentity\s*\(|canonical.*[Bb]roker|[Bb]roker.*[Uu][Rr][Ll]|baseUrl|connection.*fingerprint|createHash' \
src/fleet/internal-fleet-client.ts
echo '=== URL canonicalization implementations ==='
rg -n -C 16 \
'function canonical|canonical.*[Uu][Rr][Ll]|new URL\(|toString\(\).*replace|baseUrl' \
src/fleet/internal-fleet-client.ts src/fleet/relay-fleet-client.ts
echo '=== New and nearby claim regressions ==='
show src/fleet/teammates.test.ts 450 555
show src/fleet/internal-fleet-client.test.ts 1760 1855
echo '=== URL identity test inventory ==='
rg -n -C 12 \
'broker URL|broker.*url|canonical|trailing|default port|localhost|127\.0\.0\.1|messageStreamIdentity' \
src/fleet/internal-fleet-client.test.ts src/fleet/relay-fleet-client.test.tsLength of output: 231480
I found no new blocking issue.
The exact-head You are interacting with an AI system. |
Part of the cross-repo epic #2785. This is the consumer issue — the one that makes the ecosystem come alive. Depends on the directory from relaycast#294 and the shared card contract from workforce#296.
Why
The Factory turns issues into reviewed PRs by spawning worker agents. Today, when a worker hits a question it can't answer, it has nowhere to turn — Factory only discovers fleet nodes/capabilities for placement (
FleetClient.roster()), not agents by skill, and there's no path to actually ask another agent. Once every proactive agent publishes an A2A card and registers with the Relaycast directory, a Factory worker should be able to: "who can answer X?" → pick an agent by its card skills → send it the question and get a reply.What
1. Directory-backed peer discovery
Add a card-aware lookup alongside
roster()inFleetClient(src/ports/fleet.ts), implemented inRelayFleetClient(src/fleet/relay-fleet-client.ts) — query Relaycast'sGET /v1/a2a/directoryfiltered by skill/tag, returning{ name, skills[], url, kind }per candidate. Optionally cache via an in-package index likeRelayflowPolicyRegistry(src/dispatch/relayflow-registry.ts).2. "Ask a teammate" capability
Give a running worker a way to engage a discovered peer: resolve target via #1, then send over Relaycast — A2A
POST /a2a/rpcmessage/sendor the broker seam (FleetClient.sendMessage/waitForInjected/onAgentMessage). Reuse the existing DM/relay path; don't invent a new transport. Surface it as a discrete capability/tool the worker invokes mid-task, with a bounded wait for the reply.3. Publish cards for factory-hosted personas
Generate an A2A card for factory-hosted personas (e.g.
.agentworkforce/agents/factory-feature-guardian/persona.json) via@agentworkforce/persona-kitderiveAgentCard([workforce#296]) and publish it when the persona-backed node comes online —createFactoryNodeDefinition(src/node/factory-node.ts) already assembles capabilities/tags/metadata and callsdefineNode. Export the new surface fromsrc/index.ts.Acceptance
FleetClientmethod returns teammate agents matching a skill/tag from the Relaycast directory (with how to reach each).deriveAgentCard-derived card when their node comes online; they appear in the directory.src/index.ts; card schema consumed from@relaycast/a2a(no hand-rolled shape).✅ End-to-end verification — Factory success criteria (REQUIRED)
This issue IS the "does the ecosystem come alive?" proof. "Compiles" is nowhere near done — the Factory must run a real discover → ask → reply round trip and observe the reply arrive, then check the harness in. Use
agent-relay's in-process broker so this runs locally with no external services (src/fleet/internal-fleet-client.ts,src/fleet/ensure-relay-broker.ts).Run it
The E2E (
test/e2e/ask-a-teammate.test.ts, runnable vianpm test) must:/v1/a2a/directoryHTTP response, or seed the in-process broker) with two agents whose cards declare distinct skills (e.g.["infra-watch"],["code-review"]).skill: "infra-watch"→ assert it returns only the infra agent, with an address to reach it.InternalFleetClient+ensureRelayBroker) that replies to an inbound question with a canned answer.createFactoryNodeDefinition, then assert itsderiveAgentCard-derived card was published/registered and shows up in the directory lookup from step 2.Observe (must pass)
[]for an unknown skill.Deliverable
test/e2e/ask-a-teammate.test.tsrunning the full discover→ask→reply round trip against the in-process broker + mock directory, one command (npm test).Anchor files
src/ports/fleet.ts(FleetClient,RosterEntry,sendMessage/waitForInjected/onAgentMessage)src/fleet/relay-fleet-client.ts,src/fleet/internal-fleet-client.ts,src/fleet/ensure-relay-broker.tssrc/dispatch/relayflow-registry.ts(in-package registry precedent)src/node/factory-node.ts(createFactoryNodeDefinition),src/index.ts(public barrel).agentworkforce/agents/factory-feature-guardian/persona.json(proto card / template)planning/linear-issue-cloud-proactive-runtime-fleet-unification.md,planning/factory-unified-node-architecture-linear-issue.mdFixes #139
Summary by cubic
Workers can discover teammate agents by skill and ask them; Factory‑hosted personas publish canonical A2A cards. Previously there was no skill discovery or persona publishing; now asks are bounded, correlate replies to the real sender, and report definitive delivery rejections.
Discovery and ask:
FleetClient.discoverTeammates({ skill | tag | q })queries a Relaycast directory viaRelaycastTeammateDirectory.askTeammate(...)awaits transport readiness, blocks overlapping asks to the same teammate from the same requester, drains late replies, quarantines timed‑out asks, matches replies using the backend’seffectiveSender(), and throwsFleetDeliveryRejectedErroron rejection. AddsdiscoverTeammates,effectiveSender(), andwhenMessagesObservable()toFleetClient; exportsaskTeammateandRelaycastTeammateDirectory.Teammate tools via MCP: packaged entrypoint
bin/factory.mjs teammate-mcpextends the injected Agent Relay MCP server so workers keep existing Relay tools and identity while gaining discover/ask.InternalFleetClientresolves this command by default.Persona cards: derive with
@agentworkforce/persona-kit’s canonical mapper, validate with@relaycast/a2a, and publish viaRelaycastAgentCardPublisherwith retry and 409 handling.startFactoryNode({ cardPublisher })publishes on node registration and exposescardPublished. The default@agent-relay/factory/nodehosts no persona; cloud personas are unchanged.Relay workspace scoping: identify message streams by Relay workspace ID and fail closed on unknown streams; preserve identity across rebinds.
createFleetacceptsmessageStreamScopeand forwards it; passRELAY_WORKSPACE_KEYas bothRELAY_WORKSPACE_KEYandRELAY_API_KEY.Verification: end‑to‑end
test/e2e/ask-a-teammate.test.tsexercises discover → ask → reply and persona card publication; unit tests cover MCP tools, directory filtering, concurrency claims, and relay/internal backends.Migration
RELAY_WORKSPACE_KEYin worker environments (and use it asRELAY_API_KEY) when using the Relay backend.Written for commit 0075364. Summary will update on new commits.