Skip to content

Commit cb7bce0

Browse files
Jacksondr5claude
andcommitted
fix(j5): a project with no agents yet lists its people instead of failing
Listing a project that exists but has no ledger yet failed with a placement error. The glossary also still said only a person steers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ee61bfb commit cb7bce0

3 files changed

Lines changed: 26 additions & 6 deletions

File tree

‎apps/server/src/j5/a2a/mcp/handlers.test.ts‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ import {
6060
} from "../HomeRegistrar.ts";
6161
import { A2ALedger } from "../LedgerService.ts";
6262
import { PeerDirectory, noneLayer as peerDirectoryNoneLayer } from "../PeerDirectory.ts";
63-
import { ParticipantPlacementService, PlacementStorageError } from "../PlacementService.ts";
63+
import {
64+
ParticipantPlacementService,
65+
PlacementProjectNotFoundError,
66+
PlacementStorageError,
67+
} from "../PlacementService.ts";
6468
import { A2AHomeMembershipStateError, A2ASendService } from "../SendService.ts";
6569
import { SpawnCompositionService } from "../SpawnCompositionService.ts";
6670
import { ThreadRegistration } from "../ThreadRegistration.ts";
@@ -682,6 +686,7 @@ it.effect("lists active and archived agent titles with one ambient shell snapsho
682686
});
683687
const callerParticipantId = ParticipantId.make("agent:j5:mcp-directory:caller");
684688
const otherLedgerProjectId = LedgerProjectId.make("ledger:j5:mcp-directory:other");
689+
const emptyLedgerProjectId = LedgerProjectId.make("ledger:j5:mcp-directory:empty");
685690
const elsewhereParticipantId = ParticipantId.make("agent:j5:mcp-directory:elsewhere");
686691
const elsewhereThreadId = ThreadId.make("thread:j5:mcp-directory:elsewhere");
687692
const rows = [
@@ -781,7 +786,11 @@ it.effect("lists active and archived agent titles with one ambient shell snapsho
781786
sendService,
782787
orchestrator,
783788
Layer.mock(ParticipantPlacementService)({
784-
listParticipants: () => Effect.succeed([]),
789+
// A project nobody has worked in yet has no ledger to place anything in.
790+
listParticipants: (projectId) =>
791+
projectId === emptyLedgerProjectId
792+
? Effect.fail(new PlacementProjectNotFoundError({ projectId }))
793+
: Effect.succeed([]),
785794
}),
786795
Layer.mock(A2ADeliveryWorker)({ notify: Effect.void }),
787796
unusedLifecycleDependencies,
@@ -926,6 +935,11 @@ it.effect("lists active and archived agent titles with one ambient shell snapsho
926935
[humanParticipantId, elsewhereParticipantId],
927936
"another project lists its own agents, and the people",
928937
);
938+
assert.deepStrictEqual(
939+
ids(yield* list({ project_id: emptyLedgerProjectId })),
940+
[humanParticipantId],
941+
"a project with no agents yet lists the people",
942+
);
929943
const gone = yield* call({ project_id: LedgerProjectId.make(missingProjectId) });
930944
assert.isTrue(gone.isFailure);
931945
assert.include(JSON.stringify(gone.encodedResult), `no project ${missingProjectId}`);

‎apps/server/src/j5/a2a/mcp/handlers.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -759,9 +759,15 @@ const handlers = {
759759
);
760760
const placements = yield* ParticipantPlacementService;
761761
const placementByParticipant = new Map(
762-
(projectId === undefined ? [] : yield* placements.listParticipants(projectId)).map(
763-
(row) => [row.participantId, row] as const,
764-
),
762+
(projectId === undefined
763+
? []
764+
: // A project nobody has worked in yet has no ledger, so nothing is placed in it.
765+
yield* placements
766+
.listParticipants(projectId)
767+
.pipe(
768+
Effect.catchTag("PlacementProjectNotFoundError", () => Effect.succeed([])),
769+
)
770+
).map((row) => [row.participantId, row] as const),
765771
);
766772
const localRows = directory
767773
.filter((row) => row.participant.kind !== "human" && row.projectId === projectId)

‎docs/j5/product/glossary.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ These entries are the distinctions the whole team must share; the glossary owns
6767
| delivery receipt | The recorded fact that a message was delivered | [a2a/index.md](a2a/index.md) |
6868
| delivery alarm | The recorded fact that a delivery failed | [a2a/index.md](a2a/index.md) |
6969
| silence notice | A platform-authored fact appended when an agent's turn ends without a reply it owed | [a2a/index.md](a2a/index.md) |
70-
| queue, steer | The two ways a message reaches an agent mid-turn: queued behind the active turn, or steered into it. Agent deliveries queue; only a person steers, explicitly | [a2a/index.md](a2a/index.md) |
70+
| queue, steer | The two ways a message reaches an agent mid-turn: queued behind the active turn, or steered into it. Agent deliveries queue unless the sender asks for a steer or a restart | [a2a/index.md](a2a/index.md) |
7171
| human node | A person as a participant in the communication graph, keyed by person id | [features/inbox.md](features/inbox.md) |
7272

7373
## Humans and attention

0 commit comments

Comments
 (0)