Skip to content

139: "Who can I ask?" — discover teammate agents by skill and engage them; publish cards for hosted personas - #178

Draft
agent-relay-code[bot] wants to merge 6 commits into
mainfrom
factory/139-agentworkforce-factory-49b6fd48
Draft

139: "Who can I ask?" — discover teammate agents by skill and engage them; publish cards for hosted personas#178
agent-relay-code[bot] wants to merge 6 commits into
mainfrom
factory/139-agentworkforce-factory-49b6fd48

Conversation

@agent-relay-code

@agent-relay-code agent-relay-code Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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() in FleetClient (src/ports/fleet.ts), implemented in RelayFleetClient (src/fleet/relay-fleet-client.ts) — query Relaycast's GET /v1/a2a/directory filtered by skill/tag, returning { name, skills[], url, kind } per candidate. Optionally cache via an in-package index like RelayflowPolicyRegistry (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/rpc message/send or 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-kit deriveAgentCard ([workforce#296]) and publish it when the persona-backed node comes online — createFactoryNodeDefinition (src/node/factory-node.ts) already assembles capabilities/tags/metadata and calls defineNode. Export the new surface from src/index.ts.

Acceptance

  • A FleetClient method returns teammate agents matching a skill/tag from the Relaycast directory (with how to reach each).
  • A worker can, mid-task, ask a discovered teammate a question and receive a reply (bounded wait).
  • Factory-hosted personas publish a deriveAgentCard-derived card when their node comes online; they appear in the directory.
  • New surface exported from 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

npm run build
npm test                                   # vitest run (add the E2E below)

The E2E (test/e2e/ask-a-teammate.test.ts, runnable via npm test) must:

  1. Stand up a local directory (mock the Relaycast /v1/a2a/directory HTTP response, or seed the in-process broker) with two agents whose cards declare distinct skills (e.g. ["infra-watch"], ["code-review"]).
  2. Call the new Factory discovery method with skill: "infra-watch" → assert it returns only the infra agent, with an address to reach it.
  3. Stand up a stub teammate agent on the in-process broker (InternalFleetClient + ensureRelayBroker) that replies to an inbound question with a canned answer.
  4. From a simulated worker, invoke the new "ask a teammate" capability against the discovered agent → assert the question is delivered and the canned reply is received within the bounded wait.
  5. For persona cards: bring a factory-hosted persona node online via createFactoryNodeDefinition, then assert its deriveAgentCard-derived card was published/registered and shows up in the directory lookup from step 2.

Observe (must pass)

  • Discovery returns the right agent for a skill and [] for an unknown skill.
  • The worker actually receives the teammate's reply over the relay path (the round trip completes, not just "message sent").
  • A factory-hosted persona is discoverable by its skill after its node comes online.

Deliverable

  • test/e2e/ask-a-teammate.test.ts running the full discover→ask→reply round trip against the in-process broker + mock directory, one command (npm test).
  • The E2E fails if discovery returns everything unfiltered, if the reply never arrives, or if persona-card publishing is removed (no false green).

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.ts
  • src/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.md

Fixes #139


Summary by cubic

Lets workers discover teammate agents by skill and ask them questions over Relaycast; factory-hosted personas publish A2A cards so they appear in the Relaycast directory. Previously there was no skill-based discovery or reply path.

  • Discovery: FleetClient.discoverTeammates(TeammateQuery) via a RelaycastTeammateDirectory (GET /v1/a2a/directory with skill/tag/q). Applies exact skill/tag filters client‑side, dedupes by kind+address, preserves server alias matches for q, and times out cleanly. Works in RelayFleetClient (workspace key or agent token; optional directoryFetch/directoryTimeoutMs/teammateDirectory) and InternalFleetClient (inject a TeammateDirectory or use a workspace key).

  • Ask a teammate: askTeammate(fleet, { from, question, ... }) resolves a target, arms the listener before send, accepts only that agent’s reply addressed to the requester, uses waitForInjected when available, and fails with a bounded timeout.

  • Persona cards: derive with @agentworkforce/persona-kit’s canonical mapper and validate with @relaycast/a2a; publish via RelaycastAgentCardPublisher (POST /v1/a2a/register). On 409, verify an exact card match with GET /v1/a2a/agents and return alreadyPublished (and certification when present). createFactoryNodeDefinition(...) attaches agentCard; startFactoryNode(...) publishes on node-online and exposes a cardPublished promise.

  • Public API: exports discovery (TeammateQuery, TeammateAgent, RelaycastTeammateDirectory, askTeammate), persona tooling (deriveFactoryPersonaCard, RelaycastAgentCardPublisher, startFactoryNode), and defaults (DEFAULT_RELAYCAST_BASE_URL, DEFAULT_TEAMMATE_DIRECTORY_TIMEOUT_MS, DEFAULT_ASK_TEAMMATE_TIMEOUT_MS). Adds A2aSkill to types. E2E covers discover→ask→reply and card publishing.

  • Migration

    • Use fleet.discoverTeammates({ skill | tag | q }) or askTeammate(...) to consult a peer mid-task.
    • For discovery: in RelayFleetClient, configure a workspace key or agent token; in InternalFleetClient, pass a teammateDirectory or a workspace key.
    • To publish cards: pass persona to createFactoryNodeDefinition(...) and provide a RelaycastAgentCardPublisher to startFactoryNode(...) when an agentCard is present.

Written for commit 996b7bc. Summary will update on new commits.

Review in cubic

@kjgbot
kjgbot marked this pull request as draft July 21, 2026 14:16
@kjgbot

kjgbot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

End-to-end merge gate: this PR is not production-ready yet.

Validated locally at 3e12fc676c6389b254310cd6e979a35d39cacedb:

  • focused discovery, ask/reply, and persona-card tests: 3 files / 5 tests pass
  • build, feature map, and 9 packed-install checks pass
  • all five hosted Factory E2Es pass

Real integration blockers:

Unblock/merge criteria:

  1. publish/deploy the Relaycast directory and canonical persona-kit deriveAgentCard
  2. remove the compatibility mapper and consume the published shared API
  3. integrate current Factory main
  4. run a real deployed discover → ask → reply → persona publish/directory lookup, in addition to the checked-in mock E2E
  5. rerun all local and five hosted E2E gates and clear review threads

Marking draft so the passing mocked checks cannot be mistaken for full ecosystem proof.

Copy link
Copy Markdown
Member

Addressed the branch-integration finding from the merge-gate review on the current PR head.

  • Merged current origin/main at 0c70c64 (Factory 0.1.52) into this branch in commit 90b4837; the branch is conflict-free and GitHub reports it mergeable.
  • Kept the reply-matching, timeout, and idempotent card-registration hardening in 66a1fa2.
  • Exact-head local validation passed: npm run build; focused teammate/persona tests (3 files / 6 tests); full npm test (72 files / 1,313 tests); feature-map check; CLI help; and the tier-2 fixture cycle.
  • Fresh GitHub Actions CI run #456 passed all five jobs on 90b48377e7b24f0dc2d748a8777a92f1973e671c: package, verification-gate-e2e, load-e2e, verification-stack-e2e, and kubernetes-provider-e2e.

The PR remains draft. The external merge gates are still blocked exactly as identified: Relaycast #294 is not deployed, Workforce #296 has not published deriveAgentCard, the compatibility mapper therefore remains, and the required real deployed discover → ask → reply → publish/directory proof cannot yet be run. I am not treating the mocked/in-process E2E as production-readiness evidence.

@khaliqgant

Copy link
Copy Markdown
Member

Stale-PR triage: REVIVE — the blocker that stalled this has since cleared

Triage sweep of the 6 open PRs, evaluated against origin/main @ f13600f.

#139's closure is a false positive — this work is not done

#139 shows CLOSED / COMPLETED, which would normally end the discussion. It should not be trusted here:

$ gh api repos/AgentWorkforce/factory/issues/139/timeline
commented 2026-08-13 "Factory observed pull request #2891 merge and completed this issue."
closed    2026-08-13

Factory has no PR #2891 — its numbering is in the ~270s. PR #2891 is in AgentWorkforce/cloud: "fix(relayauth): add dormant emergency source mint gate", which has nothing to do with teammate discovery. This is a cross-repo issue/PR number collision in Factory's reconciliation (#146 was closed the same way by cloud#2895). I am reporting that defect separately.

Nothing on main implements this

$ git grep -i -l "teammate\|personaCard\|deriveAgentCard\|discoverBySkill" origin/main -- src
(no matches)

No src/fleet/teammates.ts, no src/node/factory-persona-card.ts, no equivalent under another name. The capability is absent from main, so there is nothing that superseded this PR.

The question that stalled it has been answered by events

This PR stopped on 2026-07-22 with an open human-input request:

Which published @agentworkforce/persona-kit version containing deriveAgentCard should PR #178 target so I can remove the compatibility mapper and complete the shared-contract acceptance gate?

That was never answered in-thread, but upstream resolved it two days later. AgentWorkforce/workforce#296"deriveAgentCard(personaSpec): canonical persona → A2A agent card in persona-kit" — closed COMPLETED on 2026-07-24. I verified it actually shipped rather than trusting the issue state, by unpacking the published tarball:

$ npm pack @agentworkforce/persona-kit@4.1.43 && tar xzf *.tgz
$ grep -rl deriveAgentCard package/
package/dist/spec.d.ts
package/dist/agent-card.d.ts
package/dist/agent-card.js
package/dist/index.d.ts
...

deriveAgentCard is present in the published 4.1.43. This branch pins ^4.1.34 and carries an in-tree compatibility mapper with the comment "workforce#296 owns the canonical mapper; persona-kit 4.1.34 predates that". Reviving this should bump the dependency and delete that mapper — the revival is a net scope reduction, not just a rebase.

Rebase cost: moderate, mostly mechanical

Tested in an isolated detached worktree (never the shared checkout, no force-push). 7 files conflict, one block each:

package-lock.json                  <- regenerate, not hand-merge
package.json                       <- dependency bump anyway
vitest.config.ts                   <- 16 lines
src/index.ts                       <- export barrel
src/fleet/internal-fleet-client.ts
src/fleet/relay-fleet-client.ts
.agentworkforce/agents/factory-feature-guardian/persona.json

The lockfile and package.json blocks resolve by regeneration, and the persona-kit bump has to happen regardless. The two src/fleet/*-client.ts blocks are the only ones needing real judgment. mergeable: CONFLICTING overstates this.

CI. gh run list --branch <this-branch> shows CI success across all 8 head shas including the current d93aaa5. CI is the only workflow on this branch.

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.

@khaliqgant

Copy link
Copy Markdown
Member

Triage: REBASE-AND-FINISH — unlanded, blocker cleared, and the conflicts are mostly mechanical

Stale-PR triage pass. Recommendation only; no ruling has been made yet. Evidence against origin/main at f6050e0, fetched today.

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 origin/main directly rather than trusting issue state:

  • origin/main:src/fleet/ contains create-fleet.*, ensure-relay-broker.*, internal-fleet-client.*, relay-fleet-client.*, relay-workspace-key.ts. There is no teammates.ts.
  • origin/main:src/node/ contains factory-node.*, factory.node.ts, preview-process.*, tailscale-preview.*. There is no factory-persona-card.ts and no factory-node-runtime.ts.

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.

⚠️ Correction to the triage brief, which listed #139 as open: it is CLOSED, as of 2026-08-16T23:27:42Z, stateReason: COMPLETED. That closure is invalid, and it is the second invalid closure of this same issue:

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 src/orchestrator/factory.ts and src/orchestrator/factory.test.ts — it has nothing whatever to do with "Who can I ask?" teammate discovery. So a same-repo PR with no connection to the issue still advanced it to Done. Repo-qualification was necessary but is not sufficient; the record-less fallback still resolves merged PRs to unrelated issues within the same repo.

(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 deriveAgentCard should PR #178 target so I can remove the compatibility mapper?"

I unpacked the published tarball rather than trusting a version claim:

npm pack @agentworkforce/persona-kit@4.1.43
→ package/dist/agent-card.js, package/dist/agent-card.d.ts   (deriveAgentCard present)

4.1.43 is also the current latest. This PR pins "@agentworkforce/persona-kit": "^4.1.34", which resolves to 4.1.43. The blocker is genuinely cleared and the compatibility mapper can come out. This independently confirms the earlier REVIVE verdict recorded on #139 on 2026-08-16.

4. How big is the conflict? — 7 files, 82 behind — but 4 of the 7 are mechanical.

git merge-tree --write-tree origin/main origin/factory/139-agentworkforce-factory-49b6fd48 → exit 1, conflicting:

.agentworkforce/agents/factory-feature-guardian/persona.json
package.json
package-lock.json
src/fleet/internal-fleet-client.ts
src/fleet/relay-fleet-client.ts
src/index.ts
vitest.config.ts

rev-list --left-right --count82 behind, 9 ahead.

Seven conflicts sounds worse than it is. Inspected individually:

file nature cost
src/index.ts pure export-list append (two blocks) trivial — take both sides
vitest.config.ts one line: adds 'test/e2e/**/*.test.ts' to include trivial
package.json dependency block alphabetical reorder + 2 additions trivial
package-lock.json regenerate from resolved package.json mechanical
.agentworkforce/.../persona.json +10 / −0 small
internal-fleet-client.ts + relay-fleet-client.ts +59 lines total, purely additive small, real

So the substantive merge work is roughly 60 additive lines across two fleet clients. Rebase, don't redo — the 300-line teammates.ts, the 298-line persona-card publisher, and the 306-line E2E all apply cleanly and are worth well more than they'd cost to rewrite.

Recommendation

REBASE-AND-FINISH. Concretely, on a ruling:

  1. Reopen [factory] "Who can I ask?" — discover teammate agents by skill and engage them; publish cards for hosted personas #139 (its closure is invalid, twice over).
  2. Cut a worktree, rebase factory/139-...49b6fd48 onto origin/main, taking both sides on the four mechanical conflicts and merging the two fleet clients by hand.
  3. Bump the pin to @agentworkforce/persona-kit@^4.1.43 and remove the compatibility mapper — the thing the PR was blocked on since 2026-07-22.
  4. Regenerate package-lock.json, run the suite, take it out of draft.
  5. Keep factory-ready off [factory] "Who can I ask?" — discover teammate agents by skill and engage them; publish cards for hosted personas #139 while this is in flight, so Factory doesn't dispatch a duplicate agent against a PR that already implements it (this exact duplication was caught and prevented on 2026-08-16).

Holding for a ruling — not rebasing, reopening, or closing anything on my own.

@khaliqgant
khaliqgant force-pushed the factory/139-agentworkforce-factory-49b6fd48 branch from d93aaa5 to 996b7bc Compare August 17, 2026 14:05
@khaliqgant

Copy link
Copy Markdown
Member

Rebase-and-finish update

Rebased this PR onto current origin/main (c79d564) and force-updated the existing branch with an exact-old-SHA lease. Current head: 996b7bca0665aaf050613f343fee758b39274510.

The July blocker in Workforce is resolved in code:

  • moved @agentworkforce/persona-kit to the production dependency set at ^4.1.43;
  • verified the root install resolves @agentworkforce/persona-kit@4.1.43;
  • removed the in-tree compatibility mapper;
  • now imports the published canonical deriveAgentCard from @agentworkforce/persona-kit/spec and still validates the result through A2aAgentCardSchema.

Verification at the rebased content:

  • npm run build — exit 0
  • focused teammate/persona/round-trip E2E: 3 files, 8 tests — exit 0
  • full npm test: 96 files passed / 1 skipped; 1,730 tests passed / 1 skipped — exit 0
  • npm run featuremap:check: 319 features, no advisories — exit 0
  • cost-accounting E2E: 6 tests — exit 0
  • packed consumer E2E under Node 22.22.2: 11 checks bound to exact head 996b7bc — exit 0
  • git diff --check — exit 0

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.

@khaliqgant

Copy link
Copy Markdown
Member

Hosted CI is green at exact head 996b7bc: run 32037774187 completed with all five jobs successful (package, kubernetes-provider-e2e, verification-gate-e2e, verification-stack-e2e, load-e2e). The package job included the full test suite, feature-map validation, packed lifecycle verification, attestation upload, and package-content verification.

The PR intentionally remains draft only for the live Relaycast directory proof recorded above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[factory] "Who can I ask?" — discover teammate agents by skill and engage them; publish cards for hosted personas

2 participants