Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Ported from the Python sample [`hosted-agents/agent-framework/responses/13-found
2. **Searches for contextual memories** matching the current user message and injects them into the model context.
3. **Queues a store update** with new facts inferred from the conversation.

After each successful run, an Agent Framework middleware calls `FoundryMemoryProvider.whenUpdatesCompleted()` to wait for the queued extraction. This makes the new memories searchable before the next request without adding sample-specific polling logic or environment variables.
Memory extraction is asynchronous. Like the .NET hosted-agent sample, this agent does not block every response while a queued update is processed. Callers that immediately verify a newly taught memory should allow time for extraction to complete.

Memories are scoped per end user with `hostedUserScope()`: the hosting infrastructure injects the end-user id on every request. For local requests without that header, `MEMORY_USER_ID` supplies a stable development scope from `.env`. This is the TypeScript counterpart of the Python sample's `scope="{{$userId}}"` placeholder.
Memories are scoped per end user with `hostedUserScope()`: the hosting infrastructure injects the end-user id on every request. For local requests without that header, `MEMORY_USER_ID` supplies a stable development scope from `.env`. This is the TypeScript equivalent of .NET's `HostedFoundryMemoryProviderScopes.PerUser()`: it resolves the platform-injected end-user id to an explicit Memory scope instead of relying on the service-side `{{$userId}}` substitution used by the Python sample.

The agent uses `FoundryChatClient` and is served via `ResponsesHostServer`, which exposes a REST API compatible with the OpenAI Responses container protocol v2.0.0. `serve` binds `0.0.0.0:${PORT:-8088}` — the address the platform's readiness probe expects. See [src/main.ts](src/main.ts).

Expand Down Expand Up @@ -68,10 +68,12 @@ Invoke the local agent from another terminal:

```bash
curl -X POST localhost:8088/responses -H 'content-type: application/json' -d '{"input":"Hi, my name is Alex and I am vegetarian."}'
# Memory extraction is asynchronous; wait before verifying a newly taught memory.
sleep 30
curl -X POST localhost:8088/responses -H 'content-type: application/json' -d '{"input":"Do you remember my name and what I like to eat?"}'
```

Each successful response waits for asynchronous extraction through the Agent Framework's `whenUpdatesCompleted()` API. This mirrors the explicit `WhenUpdatesCompletedAsync()` calls in the .NET Foundry Memory sample and avoids relying on an arbitrary sleep before the next request.
`FoundryMemoryProvider.whenUpdatesCompleted()` is available for deterministic tests or one-shot workflows that must verify an update immediately, but the hosted server intentionally leaves extraction off the request's critical path.

The sample fails fast when the store cannot be read or updated instead of silently behaving like a stateless agent. For local requests, `MEMORY_USER_ID` from `.env` is used when no `x-agent-user-id` header is present.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* variable.
*/

import { Agent, agentMiddleware } from '@polymind-inc/agent-framework';
import { Agent } from '@polymind-inc/agent-framework';
import { serve } from '@polymind-inc/agent-framework/agentserver/node';
import { FoundryChatClient, FoundryMemoryProvider } from '@polymind-inc/agent-framework/foundry';
import { hostedUserScope, ResponsesHostServer } from '@polymind-inc/agent-framework/foundry/hosting';
Expand Down Expand Up @@ -57,7 +57,8 @@ function resolveMemoryScope(): string {
if (localMemoryUserId) return localMemoryUserId;
throw new Error(
'No hosted end-user id is available. Set MEMORY_USER_ID in .env for local runs, ' +
'or send x-agent-user-id with the request.',
'or, for local testing only, send x-agent-user-id directly to the local server. ' +
'In Foundry, the platform injects this header.',
{ cause: error },
);
}
Expand All @@ -72,27 +73,6 @@ const memoryProvider = new FoundryMemoryProvider({
failureMode: 'throw',
});

// Foundry extracts memories asynchronously. Match the .NET sample's explicit
// WhenUpdatesCompletedAsync calls by waiting after each successful agent run.
// Streaming runs finish later, so register the wait against their final result.
const waitForMemoryUpdates = agentMiddleware(
async (ctx, next) => {
if (ctx.stream) {
ctx.onResult(async (response) => {
await memoryProvider.whenUpdatesCompleted(ctx.signal === undefined ? {} : { signal: ctx.signal });
return response;
});
}

await next();

if (!ctx.stream) {
await memoryProvider.whenUpdatesCompleted(ctx.signal === undefined ? {} : { signal: ctx.signal });
}
},
{ name: 'waitForFoundryMemoryUpdates' },
);

const agent = new Agent({
client: new FoundryChatClient({
projectEndpoint,
Expand All @@ -104,7 +84,6 @@ const agent = new Agent({
'automatically provided to you in the system context. Use them when ' +
'answering, and acknowledge when you are relying on remembered facts.',
contextProviders: [memoryProvider],
middleware: [waitForMemoryUpdates],
// History will be managed by the hosting infrastructure, thus there
// is no need to store history by the service. Learn more at:
// https://developers.openai.com/api/reference/resources/responses/methods/create
Expand Down