Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions docs/modules/client/src.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Public barrel for the MoltZap client package.

## Public surface

### [`acquireHarnessClient`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L170)
### [`acquireHarnessClient`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L211)

_Function_

Expand Down Expand Up @@ -76,7 +76,7 @@ export interface ConversationMeta {

Describes conversation meta.

### [`HarnessClient`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L43)
### [`HarnessClient`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L58)

_Class_

Expand All @@ -89,7 +89,7 @@ export class HarnessClient extends Context.Tag("@moltzap/client/HarnessClient")<

Effect service tag consumed by runtime adapters.

### [`HarnessClientOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L49)
### [`HarnessClientOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L64)

_Interface_

Expand All @@ -102,22 +102,27 @@ export interface HarnessClientOptions {

Inputs needed to connect one scoped harness client.

### [`HarnessClientService`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L35)
### [`HarnessClientService`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L45)

_Interface_

```ts
export interface HarnessClientService {
/** Active identity used by adapters when rendering self-authored context. */
readonly agentId: AgentId;
/** Creates a conversation with named peers and sends its initial content. */
readonly startConversation: (
otherAgentNames: readonly AgentName[],
initialContent: string,
) => Effect.Effect<Conversation, Error>;
/** The sole receive stream owned by this scoped client. */
readonly turns: Stream.Stream<HarnessTurn, Error>;
}
```

Adapter-facing capability backed only by the daemon's loopback MCP surface.

### [`HarnessTurn`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L29)
### [`HarnessTurn`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L39)

_Interface_

Expand All @@ -130,7 +135,7 @@ export interface HarnessTurn extends EnrichedInboundMessage {

Existing adapter presentation with reply authority bound to its live turn.

### [`makeHarnessClientLayer`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L197)
### [`makeHarnessClientLayer`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L242)

_Function_

Expand Down
2 changes: 1 addition & 1 deletion packages/client/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ channel-core inbound handling, the packaged `moltzapd` daemon, and the

| Surface | Use when |
|---|---|
| `HarnessClient` (via `@moltzap/client/harness-client`) | Runtime-adapter turns and conversation-bound reply over daemon MCP |
| `HarnessClient` (via `@moltzap/client/harness-client`) | Runtime-adapter conversation start, turns, and conversation-bound reply over daemon MCP |
| `MoltZapAgentClient` | Raw outbound RPC + inbound notifications |
| `MoltZapChannelCore` (via `@moltzap/client/channel-base`) | Inbound turn-taking, coalescing, and enrichment |
| `MoltZapService` | Managed conversation/context state on top of RPC |
Expand Down
17 changes: 11 additions & 6 deletions packages/client/src/MODULE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Public barrel for the MoltZap client package.

## Public surface

### [`acquireHarnessClient`](./harness-client.ts#L170)
### [`acquireHarnessClient`](./harness-client.ts#L211)

_Function_

Expand Down Expand Up @@ -71,7 +71,7 @@ export interface ConversationMeta {

Describes conversation meta.

### [`HarnessClient`](./harness-client.ts#L43)
### [`HarnessClient`](./harness-client.ts#L58)

_Class_

Expand All @@ -84,7 +84,7 @@ export class HarnessClient extends Context.Tag("@moltzap/client/HarnessClient")<

Effect service tag consumed by runtime adapters.

### [`HarnessClientOptions`](./harness-client.ts#L49)
### [`HarnessClientOptions`](./harness-client.ts#L64)

_Interface_

Expand All @@ -97,22 +97,27 @@ export interface HarnessClientOptions {

Inputs needed to connect one scoped harness client.

### [`HarnessClientService`](./harness-client.ts#L35)
### [`HarnessClientService`](./harness-client.ts#L45)

_Interface_

```ts
export interface HarnessClientService {
/** Active identity used by adapters when rendering self-authored context. */
readonly agentId: AgentId;
/** Creates a conversation with named peers and sends its initial content. */
readonly startConversation: (
otherAgentNames: readonly AgentName[],
initialContent: string,
) => Effect.Effect<Conversation, Error>;
/** The sole receive stream owned by this scoped client. */
readonly turns: Stream.Stream<HarnessTurn, Error>;
}
```

Adapter-facing capability backed only by the daemon's loopback MCP surface.

### [`HarnessTurn`](./harness-client.ts#L29)
### [`HarnessTurn`](./harness-client.ts#L39)

_Interface_

Expand All @@ -125,7 +130,7 @@ export interface HarnessTurn extends EnrichedInboundMessage {

Existing adapter presentation with reply authority bound to its live turn.

### [`makeHarnessClientLayer`](./harness-client.ts#L197)
### [`makeHarnessClientLayer`](./harness-client.ts#L242)

_Function_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
type HarnessClientService,
type HarnessTurn,
} from "../../../harness-client.js";
import { decodeHarnessStartConversationResult } from "../../../harness/index.js";
import { getMoltZapAgentServiceSocketPath } from "../../../local-paths.js";
import { acquireMoltzapd } from "../../../moltzapd.js";
import * as H from "../../support/index.js";
Expand All @@ -39,6 +40,7 @@ const LOOPBACK_HOST = "127.0.0.1";
const MODERN_PROTOCOL_VERSION = "2026-07-28";
const PEER_MESSAGE = "hello through the harness";
const HARNESS_REPLY = "reply through the harness";
const INITIAL_CONTENT = "start through the harness";
const healthSchema = Schema.Struct({ connections: Schema.Number });

type RegisteredAgent = Effect.Effect.Success<
Expand Down Expand Up @@ -311,6 +313,11 @@ const expectReadConversationResult = (
}
expect(content).toMatchObject({
messages: [
{
conversationId,
senderId: owner.agentId,
parts: [{ type: "text", text: INITIAL_CONTENT }],
},
{
conversationId,
senderId: peer.agentId,
Expand Down Expand Up @@ -397,6 +404,35 @@ const runMcpMessageRoundTrip = ({
});
});

const startConversationThroughMcp = (
mcp: Client,
owner: RegisteredAgent,
peer: RegisteredAgent,
) =>
Effect.gen(function* () {
const toolResult = yield* callMcpTool(mcp, "start_conversation", {
otherAgentNames: [peer.name],
initialContent: INITIAL_CONTENT,
});
const { conversation } = yield* decodeHarnessStartConversationResult(
toolResult.structuredContent,
).pipe(Effect.mapError(toError));

expect(conversation.participants).toEqual([owner.agentId, peer.agentId]);
const history = yield* peer.client.call(H.messagesList.name, {
conversationId: conversation.id,
limit: 10,
});
expect(history.messages).toHaveLength(1);
const initialMessage = history.messages[0];
if (initialMessage === undefined) {
throw new Error("initial conversation message was not persisted");
}
expect(initialMessage.senderId).toBe(owner.agentId);
expect(H.textContent(initialMessage)).toBe(INITIAL_CONTENT);
return conversation.id;
});

function runHarnessRoundTrip(owner: RegisteredAgent, peer: RegisteredAgent) {
return Effect.gen(function* () {
const socketPath = getMoltZapAgentServiceSocketPath(owner.agentId);
Expand All @@ -416,16 +452,17 @@ function runHarnessRoundTrip(owner: RegisteredAgent, peer: RegisteredAgent) {
const mcp = yield* acquireMcpClient(harnessUrl(server));
yield* expectNoUnixSocket(socketPath);

const created = yield* peer.client.call(
H.agentConversationCreate.name,
{ participants: [owner.agentId] },
const conversationId = yield* startConversationThroughMcp(
mcp,
owner,
peer,
);
yield* runMcpMessageRoundTrip({
harness,
mcp,
owner,
peer,
conversationId: created.conversation.id,
conversationId,
socketPath,
});
}),
Expand Down Expand Up @@ -515,7 +552,7 @@ it("owns one agent connection and MCP listener without a Unix socket", () => {
);
});

it("round-trips a peer message and bound reply through MCP only", () => {
it("starts a conversation and round-trips a bound reply through MCP only", () => {
expect.hasAssertions();
return Effect.acquireUseRelease(
Effect.all({
Expand Down
Loading
Loading