glove-foundry 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -4
- package/dist/{chunk-P65RT7H5.js → chunk-25BGNRJN.js} +1452 -178
- package/dist/{chunk-CRWY7M66.js → chunk-5LDW2N2E.js} +1086 -91
- package/dist/{chunk-3GCUECPA.js → chunk-GKZLWD5M.js} +66 -2
- package/dist/cli.js +186 -40
- package/dist/{client-CLkZREDr.d.ts → client-D0-pKEZM.d.ts} +365 -12
- package/dist/client.d.ts +4 -1
- package/dist/client.js +1 -1
- package/dist/config.d.ts +15 -1
- package/dist/execution-agent.js +1 -1
- package/dist/index.d.ts +91 -5
- package/dist/index.js +428 -6
- package/docs/architecture.md +51 -0
- package/docs/building-with-foundry.md +336 -4
- package/docs/evaluation-checklist.md +10 -1
- package/docs/guidance.md +146 -0
- package/docs/inspector.md +39 -1
- package/docs/release-verification.md +74 -0
- package/package.json +15 -13
- package/templates/minimal/README.md +30 -2
- package/templates/travel-concierge/README.md +33 -5
|
@@ -4,6 +4,16 @@ Foundry uses the filesystem for code identity and imported values for code relat
|
|
|
4
4
|
|
|
5
5
|
## Create and run
|
|
6
6
|
|
|
7
|
+
Use Node 20.12 or newer; Node 22.13+ is recommended and required by optional SQLite memory adapters. In a terminal, `init` opens a guided wizard: choose a directory, standalone or Next.js integration, guided or minimal starter, package manager, and dependency installation. Review the plan before any files are written. Arrow keys select, Enter confirms, and Ctrl+C cancels without creating the project. The wizard never requests credentials.
|
|
8
|
+
|
|
9
|
+
For CI or repeatable setup, use explicit choices:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx glove-foundry init support-workforce --yes --target standalone --template travel-concierge --package-manager pnpm --no-install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`--no-interactive` also skips prompts; piped input never opens them. `--interactive` requires a terminal. `--install` opts into installation in scripts; otherwise install dependencies yourself. Existing project files are preserved; installation failures leave the generated project available for retry. Use `glove foundry init --help` for all setup and runtime flags. The [setup wizard and CLI reference](https://glove.dterminal.net/foundry/docs/getting-started) walks through the entire first run.
|
|
16
|
+
|
|
7
17
|
```bash
|
|
8
18
|
npx glove foundry init support-workforce
|
|
9
19
|
cd support-workforce
|
|
@@ -12,7 +22,21 @@ pnpm install
|
|
|
12
22
|
pnpm dev
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
`glove foundry dev` discovers the source graph, derives identities,
|
|
25
|
+
`glove foundry dev` discovers the source graph, derives identities, generates `.foundry/routes.d.ts`, and starts the runtime and inspector. Run the generated `typecheck` and `lint` scripts separately to validate types and authoring conventions.
|
|
26
|
+
|
|
27
|
+
The HTTP server keeps JSON requests at 1 MB by default. For a trusted multimodal
|
|
28
|
+
client that sends base64 images or documents, raise the explicit typed bound rather
|
|
29
|
+
than removing it:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
server: { port: 4141, messageBodyBytes: 40 * 1024 * 1024 },
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Native Glove media parts may include a human-facing `name`. A custom `run` handler can
|
|
38
|
+
persist or normalize those parts and then call `context.defaultRun(enrichedMessage)`
|
|
39
|
+
to keep the standard Glove loop and conversation semantics.
|
|
16
40
|
|
|
17
41
|
The generated project depends on the exact Glove versions the `glove-foundry` that created it was built against. That is not tidiness: a narrower range makes the package manager install a second copy of `glove-js`, and the two `JsSession` classes then fail to type-match.
|
|
18
42
|
|
|
@@ -49,7 +73,71 @@ The runtime is a separate process from `next dev`, deliberately — it holds dur
|
|
|
49
73
|
|
|
50
74
|
Two details make this work in a Next.js project specifically. A Next.js app is not `"type": "module"`, so Node would load the agents through the CommonJS resolver and fail on Foundry's ESM-only export map; the nested `foundry/package.json` scopes ESM to the agent tree, and `.mts` makes the config unambiguous whatever the root declares.
|
|
51
75
|
|
|
52
|
-
In production, set `FOUNDRY_URL` to wherever the runtime is deployed and keep it
|
|
76
|
+
In production, set `FOUNDRY_URL` to wherever the runtime is deployed and keep it
|
|
77
|
+
inside a deliberate trust boundary. Loopback is the default. Foundry refuses to bind
|
|
78
|
+
another interface unless `foundry.application.ts` supplies an Effect-native request
|
|
79
|
+
authorization adapter:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { Effect } from "effect"
|
|
83
|
+
import { defineApplication } from "glove-foundry"
|
|
84
|
+
|
|
85
|
+
export default defineApplication({
|
|
86
|
+
name: "Support workforce",
|
|
87
|
+
requestAuthorization: {
|
|
88
|
+
identifier: "company-control-auth",
|
|
89
|
+
challenge: 'Bearer realm="Support Foundry"',
|
|
90
|
+
authorize: request => Effect.tryPromise({
|
|
91
|
+
try: () => companyIdentityAdapter.authorize({
|
|
92
|
+
authorization: request.authorization,
|
|
93
|
+
cookie: request.cookie,
|
|
94
|
+
path: request.path,
|
|
95
|
+
}),
|
|
96
|
+
catch: cause => new Error("Control authorization unavailable", { cause }),
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This is verification, not credential acquisition: your adapter owns tokens, cookies,
|
|
103
|
+
OIDC/trusted-proxy identity, refresh, revocation, and rate policy. The result is only
|
|
104
|
+
a boolean; credential material does not enter Foundry state, manifests, prompts, or
|
|
105
|
+
events. Put TLS and a restrictive firewall/proxy in front of any network-visible
|
|
106
|
+
listener.
|
|
107
|
+
|
|
108
|
+
A remote typed client resolves its own headers at request time:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const foundry = createFoundryClient({
|
|
112
|
+
baseUrl: process.env.FOUNDRY_URL,
|
|
113
|
+
authorization: {
|
|
114
|
+
identifier: "company-control-client",
|
|
115
|
+
headers: () => companyIdentityAdapter.requestHeaders(),
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For a single-host deployment, persist Foundry's mutable data with the bundled atomic file adapter:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { FileFoundryDataAdapter, defineApplication } from "glove-foundry"
|
|
124
|
+
import { join } from "node:path"
|
|
125
|
+
|
|
126
|
+
const data = new FileFoundryDataAdapter({
|
|
127
|
+
file: join(process.env.AGENT_DATA_DIR ?? ".data", "foundry.json"),
|
|
128
|
+
agents: [primaryInstance],
|
|
129
|
+
conversations: [primaryConversation],
|
|
130
|
+
subscriptions: [inboundSubscription],
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
export default defineApplication({
|
|
134
|
+
name: "Support workforce",
|
|
135
|
+
data,
|
|
136
|
+
conversationStore: createConversationStore,
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`FileFoundryDataAdapter` coordinates sibling execution processes with an advisory lock and commits by atomic rename. It persists instances, subscriptions, delivery claims, activations, conversations, workspace data, VFS snapshots, inbox items, tasks, and non-secret environment data. Use a transactional database adapter when several hosts need to share the same state.
|
|
53
141
|
|
|
54
142
|
## The filesystem is the static registry
|
|
55
143
|
|
|
@@ -129,6 +217,50 @@ export const components = composeAgent(helpdesk, customerLookup, customerMemory)
|
|
|
129
217
|
|
|
130
218
|
`composeAgent` builds the agent-local catalogue. It does not install applications, MCPs, or shared tools. An instance selects those dynamically.
|
|
131
219
|
|
|
220
|
+
### HTTP and stdio MCP definitions
|
|
221
|
+
|
|
222
|
+
An MCP definition is a typed catalogue entry, not a global connection. Its instance
|
|
223
|
+
installation decides whether it is present for an agent. HTTP remains concise; stdio
|
|
224
|
+
uses an explicit transport:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const projectTools = defineMcp({
|
|
228
|
+
description: "Approved project operations",
|
|
229
|
+
entry: {
|
|
230
|
+
name: "Project tools",
|
|
231
|
+
description: "Search and update the mounted project",
|
|
232
|
+
transport: {
|
|
233
|
+
kind: "stdio",
|
|
234
|
+
command: "/opt/agents/project-mcp",
|
|
235
|
+
args: ["--stdio"],
|
|
236
|
+
},
|
|
237
|
+
includeTools: ["search_*", "read_*", "create_issue"],
|
|
238
|
+
excludeTools: ["delete_repository"],
|
|
239
|
+
resources: true,
|
|
240
|
+
prompts: false,
|
|
241
|
+
connectTimeoutMs: 15_000,
|
|
242
|
+
requestTimeoutMs: 60_000,
|
|
243
|
+
idleTimeoutMs: 15 * 60_000,
|
|
244
|
+
maxLifetimeMs: 24 * 60 * 60_000,
|
|
245
|
+
},
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
For HTTP, return `{ url: "https://mcp.example.com/mcp", ... }` or an explicit
|
|
250
|
+
`{ transport: { kind: "http", url }, ... }`. Authentication and stdio environment
|
|
251
|
+
values are connection-time adapter concerns: implement `getAuthHeaders` /
|
|
252
|
+
`getAccessToken` for HTTP and `getStdioEnvironment` for stdio. Never place their
|
|
253
|
+
resolved values in definition config, installation data, manifests, or events.
|
|
254
|
+
|
|
255
|
+
`includeTools` and `excludeTools` accept exact un-namespaced names or globs. A
|
|
256
|
+
non-empty allowlist is authoritative. `resources` and `prompts` independently control
|
|
257
|
+
capability-aware list/read and list/get utility tools. Selection and defensive result sanitization happen at the shared MCP
|
|
258
|
+
connection boundary, so boot reload, lazy activation, and scratchpad bridges see
|
|
259
|
+
the same safe capability set. Finite connection and request timeouts keep broken
|
|
260
|
+
servers from stalling a run indefinitely.
|
|
261
|
+
Stdio-only idle and lifetime limits can recycle memory-heavy children without
|
|
262
|
+
interrupting in-flight calls; adapter environment values are resolved again on reopen.
|
|
263
|
+
|
|
132
264
|
## Mount a working environment, VFS, and REPL
|
|
133
265
|
|
|
134
266
|
Foundry mounts the native Glove packages; it does not reimplement their sandboxes. A working environment supplies a persistent virtual filesystem, named scripts, checkpoints, history, artifact export, and a closed model-facing verb set. A REPL is a separate computation surface over registered functions.
|
|
@@ -172,6 +304,18 @@ export function createRepl(actor: string) {
|
|
|
172
304
|
language: "javascript",
|
|
173
305
|
session,
|
|
174
306
|
mount: { discovery: "auto" },
|
|
307
|
+
programmaticTools: {
|
|
308
|
+
maxCalls: 50,
|
|
309
|
+
select: ({ tools }) => tools
|
|
310
|
+
.filter((tool) => tool.name.startsWith("workspace_"))
|
|
311
|
+
.map((tool) => ({
|
|
312
|
+
tool,
|
|
313
|
+
name: `workspace__${tool.name.slice("workspace_".length)}`,
|
|
314
|
+
server: "workspace",
|
|
315
|
+
readOnly: ["workspace_read_file", "workspace_ls", "workspace_grep"]
|
|
316
|
+
.includes(tool.name),
|
|
317
|
+
})),
|
|
318
|
+
},
|
|
175
319
|
})
|
|
176
320
|
}
|
|
177
321
|
```
|
|
@@ -192,6 +336,23 @@ export default defineAgent({
|
|
|
192
336
|
|
|
193
337
|
`workingEnvironment` and `repl` accept the same direct-value-or-lazy-resolver shape as the other assembly fields. JavaScript, Python, and Lisp sessions are supported through one discriminated `defineRepl` API. Foundry exposes the mounted `workingEnvironment`, its guarded `vfs` handle, and the native `repl` session to layers, `configure`, calls, and `run` handlers.
|
|
194
338
|
|
|
339
|
+
`programmaticTools` turns an explicit least-privilege projection of the live
|
|
340
|
+
agent tool registry into functions inside that sandbox. The selector runs after
|
|
341
|
+
application transmissions, instance-installed tools and MCPs, calls, memory,
|
|
342
|
+
mesh, and `configure`, so it sees the actual message-specific assembly. It must
|
|
343
|
+
return those exact tool objects (or `{ tool, ...discoveryMetadata }` wrappers),
|
|
344
|
+
not copied names. Nothing is projected by default.
|
|
345
|
+
|
|
346
|
+
This is the programmatic tool-calling path for workflows with several reads,
|
|
347
|
+
loops, filters, or branches: the model writes one complete program and only its
|
|
348
|
+
last, structurally bounded value returns to conversation context. A shared
|
|
349
|
+
`maxCalls` budget defaults to 50 for the assembled run. Foundry validates Zod
|
|
350
|
+
inputs again, forwards cancellation, records safe started/completed/failed
|
|
351
|
+
events for each underlying call, and refuses to execute a tool whose current
|
|
352
|
+
input requires interactive approval. The agent must call that tool normally so
|
|
353
|
+
the approval surface remains visible. Do not select outbound or destructive
|
|
354
|
+
tools merely because a sandbox can call them.
|
|
355
|
+
|
|
195
356
|
The working environment is closed after every Foundry run. Add a persistence adapter to restore its VFS on the next run. `foundryDataEnvironmentPersistence` uses the data adapter's private snapshot seam, derives ownership from the definition and instance or conversation, and never exposes VFS contents as workspace entries. It requires a durable `FoundryDataAdapter` shared by execution workers. For high-concurrency or large trees, provide a native persistent `Vfs` such as `cachedRemote` in the environment options and let that adapter own locking and storage credentials.
|
|
196
357
|
|
|
197
358
|
For HTTP requests and file transfers, put `fetchFiles()` from `glove-env-fetch` in the environment options’ `stdlib`. Mount `secret()` from `glove-env-secret` when scripts need scoped key metadata or references, and supply the same instance-scoped host store to fetch credential aliases. Neither credentials nor the store are included in the VFS snapshot; re-supply them each run. The [HTTP and secrets guide](../../glove-working-environment/HTTP-AND-SECRETS.md) covers the complete setup, private-network opt-ins, cancellation, and persistent store boundaries.
|
|
@@ -257,6 +418,7 @@ const tickets = defineTransmission({
|
|
|
257
418
|
config: Schema.Struct({ queue: Schema.String }),
|
|
258
419
|
input: Schema.Struct({ threadId: Schema.String, body: Schema.String }),
|
|
259
420
|
output: Schema.Struct({ messageId: Schema.String }),
|
|
421
|
+
observe: ({ threadId, body }) => ({ threadId, characters: body.length }),
|
|
260
422
|
adapter: { deliver: (input) => userTicketAdapter.deliver(input) },
|
|
261
423
|
},
|
|
262
424
|
})
|
|
@@ -278,7 +440,52 @@ export default defineApp({
|
|
|
278
440
|
})
|
|
279
441
|
```
|
|
280
442
|
|
|
281
|
-
The application can own multiple inbound and outbound transmissions. Installing it mounts outbound transmissions as validated tools. Connections remain dormant until an active instance or subscription needs the installed app and playbook.
|
|
443
|
+
The application can own multiple inbound and outbound transmissions. Installing it mounts outbound transmissions as validated tools. Each generated tool awaits the parent-owned delivery adapter and returns its output after the transmission's output schema validates it. The agent subprocess never receives provider credentials or an account session. Cancellation propagates back to adapters through `context.signal`. Connections remain dormant until an active instance or subscription needs the installed app and playbook.
|
|
444
|
+
|
|
445
|
+
Outbound inputs cross the worker boundary through a private, mode-0600 command record rather than the event stream or child stdout. Foundry deletes a settled exchange. Retained observability is redacted by default; define `outbound.observe(input)` when the transmission can expose a deliberate, secret-safe projection such as a route, byte count, or digest. Never return credentials or file bodies from that projection.
|
|
446
|
+
|
|
447
|
+
Outbound adapters that select an account receive `context.withAccountSession`. Use it to enter the same user-owned, operation-scoped credential boundary used by application installers and inbound connections:
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
adapter: {
|
|
451
|
+
deliver: (input, context) => context.withAccountSession!(
|
|
452
|
+
"tickets:reply",
|
|
453
|
+
session => sendTicketReply(session, input, context.signal),
|
|
454
|
+
),
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The session value is never added to Foundry data or observability. Credential acquisition, refresh, SDK construction, and cleanup remain responsibilities of the adapter supplied on the agent definition.
|
|
459
|
+
|
|
460
|
+
Inbound connections normally isolate conversations by route and `threadKey`. A
|
|
461
|
+
trusted identity adapter can additionally provide an agent-scoped
|
|
462
|
+
`conversationKey` when two authenticated external identities represent the same
|
|
463
|
+
principal:
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
yield* context.receive({
|
|
467
|
+
route,
|
|
468
|
+
eventId: event.id,
|
|
469
|
+
threadKey: providerThread.id,
|
|
470
|
+
conversationKey: `principal:${resolvedIdentity.id}`,
|
|
471
|
+
conversationScope: "agent",
|
|
472
|
+
awaitCompletion: true,
|
|
473
|
+
raw: event,
|
|
474
|
+
})
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Foundry reuses an existing conversation whose data context has that key, or creates
|
|
478
|
+
a deterministic conversation for the agent. The transport thread remains on the
|
|
479
|
+
event and outbound route, so joining private history never changes where a reply is
|
|
480
|
+
delivered. Only use this after an adapter authenticates and explicitly links the
|
|
481
|
+
identities; route-scoped isolation remains the default.
|
|
482
|
+
|
|
483
|
+
`receive()` normally resolves once matching runs have been durably dispatched. Set
|
|
484
|
+
`awaitCompletion: true` for a stateful chat or voice transport that must not accept
|
|
485
|
+
the next turn until every subscribed run reaches a terminal state. This keeps a
|
|
486
|
+
conversation transcript ordered without changing direct requests or unrelated
|
|
487
|
+
connections. A high-throughput adapter can instead keep the default and implement
|
|
488
|
+
its own per-conversation queue.
|
|
282
489
|
|
|
283
490
|
## Config is inferred from its definition
|
|
284
491
|
|
|
@@ -423,10 +630,134 @@ glove_foundry_sleep({
|
|
|
423
630
|
|
|
424
631
|
glove_foundry_schedules({ action: "list" })
|
|
425
632
|
glove_foundry_schedules({ action: "update", activationId, timing: { kind: "every", interval: "2h" } })
|
|
633
|
+
glove_foundry_schedules({ action: "pause", activationId })
|
|
634
|
+
glove_foundry_schedules({ action: "resume", activationId })
|
|
426
635
|
glove_foundry_schedules({ action: "cancel", activationId })
|
|
427
636
|
```
|
|
428
637
|
|
|
429
|
-
Schedules are agent-local composable values; Foundry has no root schedule registry or automatically discovered schedule files. Immediate spawning, future activation, recurrence, management, and suspension are separate runtime operations. Foundry stores activation state through `FoundryDataAdapter` before arming its private execution backend, so a durable adapter can reconstruct
|
|
638
|
+
Schedules are agent-local composable values; Foundry has no root schedule registry or automatically discovered schedule files. Immediate spawning, future activation, recurrence, management, and suspension are separate runtime operations. Pausing disarms a trigger without losing its message, timing, payload, ownership, or definition provenance; edits made while paused remain paused until an explicit resume. Foundry stores activation state through `FoundryDataAdapter` before arming its private execution backend, so a durable adapter can reconstruct active work—and keep paused work disarmed—on startup. Sleep preserves the instance and conversation so the wake-up resumes with the same stored context.
|
|
639
|
+
|
|
640
|
+
Schedule management reloads the owning instance's persisted activations on each
|
|
641
|
+
tool call and overlays the current run's pending commands. A recurring run can
|
|
642
|
+
therefore inspect and cancel its own trigger even when it was inserted after the
|
|
643
|
+
assembly snapshot. Another instance's schedules do not appear in that tool view.
|
|
644
|
+
|
|
645
|
+
For an application-specific loop or goal controller, reuse these tools and store
|
|
646
|
+
the policy in `FoundryDataAdapter`; do not create another timer service. Mount native
|
|
647
|
+
`glove-memory/goals` for goal tracking. Durable coordination can use the optional
|
|
648
|
+
`compareAndSetWorkspaceEntry(entry, expectedUpdatedAt)` adapter method: `null`
|
|
649
|
+
means the key must be absent, an update compares the previously read timestamp,
|
|
650
|
+
and every accepted replacement must advance that timestamp. Validate the value
|
|
651
|
+
with the consumer's schema and retry conflicts against fresh state. Both bundled
|
|
652
|
+
adapters support it; database adapters should implement it transactionally. Every
|
|
653
|
+
writer to that coordinated key must use the same contract.
|
|
654
|
+
|
|
655
|
+
## Client and control protocols
|
|
656
|
+
|
|
657
|
+
Foundry exposes one runtime through three HTTP shapes. The native `/api` routes are
|
|
658
|
+
the typed control plane used by `createFoundryClient`. OpenAI-compatible clients can
|
|
659
|
+
use streaming or non-streaming `/v1/chat/completions` and `/v1/responses`. Automation
|
|
660
|
+
hosts can create an asynchronous run, inspect it, follow its events, and stop it:
|
|
661
|
+
|
|
662
|
+
```text
|
|
663
|
+
POST /v1/runs
|
|
664
|
+
GET /v1/runs/:runId
|
|
665
|
+
GET /v1/runs/:runId/events
|
|
666
|
+
POST /v1/runs/:runId/stop
|
|
667
|
+
POST /v1/runs/:runId/steer
|
|
668
|
+
GET /v1/capabilities
|
|
669
|
+
GET /health/detailed
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
All three paths resolve the supplied model to a persisted agent instance and write
|
|
673
|
+
into a durable conversation. Reuse `conversation_id`, `user`, or the returned
|
|
674
|
+
`x-foundry-conversation-id` header to continue the same conversation. Run-event
|
|
675
|
+
requests return JSON by default and become live server-sent events when the client
|
|
676
|
+
sends `Accept: text/event-stream`.
|
|
677
|
+
|
|
678
|
+
`/v1/capabilities` is authoritative: a client must inspect it instead of assuming a
|
|
679
|
+
control feature exists. Steering is explicitly `interrupt-and-restart`: Foundry
|
|
680
|
+
cooperatively cancels active work, waits for a terminal boundary, then starts the
|
|
681
|
+
guidance as a replacement run in the same durable conversation with lineage back to
|
|
682
|
+
the source. It never injects arbitrary text halfway through a tool side effect.
|
|
683
|
+
The typed client exposes the same boundary as `handle.steer(message)`, returning a
|
|
684
|
+
new run handle plus the source id and whether active work was interrupted.
|
|
685
|
+
|
|
686
|
+
Build chat hosts with `createFoundryClient`: resume durable conversations, read
|
|
687
|
+
`conversationTranscript`, follow correlated run events, and route new guidance
|
|
688
|
+
during active work through the typed steering operation.
|
|
689
|
+
|
|
690
|
+
## Effect approvals
|
|
691
|
+
|
|
692
|
+
A Glove tool can set `requiresPermission: true` or return a boolean from
|
|
693
|
+
`requiresPermission(input)`. In Foundry, an unset decision becomes a public,
|
|
694
|
+
expiring approval record rather than an unresolved in-process display promise.
|
|
695
|
+
The worker pauses; a trusted host lists and resolves the exact request:
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
const [approval] = await client.approvals({
|
|
699
|
+
runId: handle.id,
|
|
700
|
+
status: "pending",
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
if (approval) {
|
|
704
|
+
await client.resolveApproval(approval.id, "approve"); // or "deny"
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Approval identity includes the agent instance, conversation, run, tool name, and
|
|
709
|
+
serialized tool input. Decisions are fail-closed when the channel is unavailable,
|
|
710
|
+
the run is cancelled, or the request expires. A custom conversation store that
|
|
711
|
+
omits permission methods receives a per-run exact-input overlay; stores that
|
|
712
|
+
implement Glove permissions may persist the decision under their own policy.
|
|
713
|
+
The inspector shows pending decisions on its overview and on the blocked run, with
|
|
714
|
+
the exact payload and direct approve/deny controls.
|
|
715
|
+
|
|
716
|
+
## Voice hosts
|
|
717
|
+
|
|
718
|
+
For typed `goals`, `facts`, `forms`, and `contextProviders` fields, see
|
|
719
|
+
[Guided conversations](./guidance.md). They mount native runners and providers
|
|
720
|
+
and expose typed execution handles; no custom layer is required.
|
|
721
|
+
|
|
722
|
+
Voice is a host adapter, not a second agent-definition vocabulary. Keep realtime
|
|
723
|
+
audio, provider turn detection, interruption, telephony, and device access in the
|
|
724
|
+
host. Delegate substantive work to a persisted Foundry instance and conversation
|
|
725
|
+
through the native client or `/v1/responses`; the resulting run stays durable and
|
|
726
|
+
observable.
|
|
727
|
+
|
|
728
|
+
Use `glove-voice-s2s` for Gemini Live or OpenAI Realtime and `glove-voice` for a
|
|
729
|
+
speech-to-text / Glove / text-to-speech pipeline. A realtime host can expose a
|
|
730
|
+
delegation tool backed by the typed Foundry client, or mount `RealtimeAgent` against
|
|
731
|
+
the assembled Glove in a scoped layer. Stop the voice session in the layer's
|
|
732
|
+
cleanup. Keep provider credentials and audio device access in the host adapter.
|
|
733
|
+
|
|
734
|
+
`examples/foundry-braind-storm` demonstrates a voice lead delegating durable work
|
|
735
|
+
to Foundry agents. Phone bridges, LiveKit rooms and native audio hosts can use the
|
|
736
|
+
same instance/conversation boundary. Messenger-specific voice gateways and codecs
|
|
737
|
+
are consumer adapters, not built-in Foundry telephony services.
|
|
738
|
+
|
|
739
|
+
Core 4 runtime context is transient: goals, forms and pinned memory reach the model
|
|
740
|
+
without rewriting system instructions or persisted user turns. Native realtime
|
|
741
|
+
agents refresh that context silently at startup and after tool calls. Call
|
|
742
|
+
`await realtime.refreshContext()` after externally changing it, and forward
|
|
743
|
+
`addContextProvider` and `getRuntimeContext` from any custom runnable wrapper.
|
|
744
|
+
|
|
745
|
+
### Durable knowledge and documents
|
|
746
|
+
|
|
747
|
+
Persist conversation history, structured memory and VFS files separately. A durable
|
|
748
|
+
Foundry data adapter alone does not make an in-memory Glove store durable. For
|
|
749
|
+
single-host Node 22.13+ deployments, `glove-memory/sqlite` provides
|
|
750
|
+
`createSqliteMemoryAdapters({ file, namespace, schema })` for entity, episodic,
|
|
751
|
+
resource and pinned-context memory. Select namespaces from trusted instance
|
|
752
|
+
identity, not incoming tool input. See the [memory persistence guide](../../glove-memory/README.md).
|
|
753
|
+
|
|
754
|
+
Mount `documents()` from `glove-env-documents` through
|
|
755
|
+
`defineWorkingEnvironment({ options: { stdlib: [documents()] }, persistence })`.
|
|
756
|
+
It supplies native PDF and DOCX creation, inspection, editing and extraction within
|
|
757
|
+
the guarded VFS. Add the optional PDF extraction and rendering dependencies where
|
|
758
|
+
needed. Pass documents between agents as authorized durable artifact references;
|
|
759
|
+
do not copy entire file bodies into orchestration events. Persist the VFS before
|
|
760
|
+
run cleanup. See the [document adapter](../../glove-env-documents/README.md).
|
|
430
761
|
|
|
431
762
|
## Boundary checklist
|
|
432
763
|
|
|
@@ -440,3 +771,4 @@ Schedules are agent-local composable values; Foundry has no root schedule regist
|
|
|
440
771
|
- VFS persistence, remote storage, and locking remain adapter-owned.
|
|
441
772
|
- Transmissions own executable integration logic; playbooks remain serializable policy.
|
|
442
773
|
- Provider adapters own credential acquisition and refresh.
|
|
774
|
+
- Voice and device hosts own audio transport while Foundry owns the durable agent run.
|
|
@@ -47,6 +47,7 @@ The package tests include an inbound subscription with zero initial instances, t
|
|
|
47
47
|
|
|
48
48
|
- [ ] An app owns multiple inbound and outbound transmissions.
|
|
49
49
|
- [ ] Installing an app mounts outbound transmission tools.
|
|
50
|
+
- [ ] An outbound transmission tool receives the adapter's validated result and propagates cancellation without moving credentials into the agent process.
|
|
50
51
|
- [ ] Transmissions own authentication, normalization, classification, predicates, serialization, and delivery.
|
|
51
52
|
- [ ] Playbooks contain serializable match parameters and directives only.
|
|
52
53
|
- [ ] Credential acquisition and refresh remain in user adapters.
|
|
@@ -73,7 +74,7 @@ The package tests include an inbound subscription with zero initial instances, t
|
|
|
73
74
|
|
|
74
75
|
- [ ] Conversations, workspace entries, shared inbox items, tasks, and scoped environment values are first-class adapter data.
|
|
75
76
|
- [ ] Agent-local schedules reconcile into adapter data; agents can also create triggers dynamically.
|
|
76
|
-
- [ ] Core tools can list, update, cancel, recur, sleep, run in background, and reconvene within agent identity.
|
|
77
|
+
- [ ] Core tools can list, update, pause, resume, cancel, recur, sleep, run in background, and reconvene within agent identity.
|
|
77
78
|
- [ ] Layered agents, S2S/S2V calls, mesh, custom subscribers, custom build, and custom run/handler functions remain available.
|
|
78
79
|
- [ ] A native working environment mounts its guarded VFS and script tools with lifecycle cleanup and telemetry.
|
|
79
80
|
- [ ] JavaScript, Python, and Lisp REPLs mount through one typed, lazy agent field.
|
|
@@ -87,3 +88,11 @@ The package tests include an inbound subscription with zero initial instances, t
|
|
|
87
88
|
- [ ] The inspector makes arrival → policy → workforce → work visible.
|
|
88
89
|
- [ ] Raw trace data is available without making it the default interface.
|
|
89
90
|
- [ ] The runnable example uses the same public API described in the docs.
|
|
91
|
+
|
|
92
|
+
## Control-plane security
|
|
93
|
+
|
|
94
|
+
- [ ] Loopback is the default listener boundary.
|
|
95
|
+
- [ ] A non-loopback bind fails before listening unless the application supplies `requestAuthorization`.
|
|
96
|
+
- [ ] Inspector HTML, health, APIs, and event streams all cross the same authorization adapter.
|
|
97
|
+
- [ ] Remote typed clients resolve authorization headers per request through their own adapter.
|
|
98
|
+
- [ ] Control credentials never enter Foundry config, manifests, data, prompts, or observability.
|
package/docs/guidance.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Goals, facts, forms, and live context
|
|
2
|
+
|
|
3
|
+
Foundry exposes `goals`, `facts`, `forms`, and `contextProviders` as typed lazy
|
|
4
|
+
agent fields. Use them in `defineAgent` or as named exports in `agent.ts`.
|
|
5
|
+
Configurations may be literal values or resolvers returning values, Promises, or
|
|
6
|
+
Effects based on the current message, history, instance, and conversation.
|
|
7
|
+
|
|
8
|
+
These mount native Glove surfaces, not a second workflow engine. Programs, schemas,
|
|
9
|
+
gates, hooks, and preparation rules are code. Progress, answers, fact revisions,
|
|
10
|
+
evidence claims, and effect receipts are adapter-owned runtime data.
|
|
11
|
+
|
|
12
|
+
## Mount native surfaces
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { defineAgent, defineFacts, defineGoals, defineForms,
|
|
16
|
+
foundryGuidanceSubject, type AgentAssemblyContext } from "glove-foundry";
|
|
17
|
+
import { createSqliteMemoryAdapters } from "glove-memory/sqlite";
|
|
18
|
+
import { MemorySchema } from "glove-memory/core";
|
|
19
|
+
import { intakeGoals, intakeForms } from "./workflow.js";
|
|
20
|
+
import model from "./model.js";
|
|
21
|
+
|
|
22
|
+
const memory = (ctx: AgentAssemblyContext) => createSqliteMemoryAdapters({
|
|
23
|
+
file: "/data/agent-memory.sqlite",
|
|
24
|
+
namespace: foundryGuidanceSubject(ctx, "instance"),
|
|
25
|
+
schema: new MemorySchema(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default defineAgent({
|
|
29
|
+
description: "Conversational intake",
|
|
30
|
+
systemPrompt: "Collect information conversationally using workflow tools.",
|
|
31
|
+
model,
|
|
32
|
+
facts: (_agent, ctx) => defineFacts({ adapter: memory(ctx).facts }),
|
|
33
|
+
goals: (_agent, ctx) => defineGoals({ adapter: memory(ctx).goals, program: intakeGoals }),
|
|
34
|
+
forms: (_agent, ctx) => defineForms({ adapter: memory(ctx).forms, registry: intakeForms }),
|
|
35
|
+
contextProviders: (_agent, ctx) => [async signal => {
|
|
36
|
+
signal?.throwIfAborted();
|
|
37
|
+
return `Response preference: ${ctx.agentInstance.context.concise ? "concise" : "detailed"}`;
|
|
38
|
+
}],
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See the complete [guided-intake example](../../../examples/foundry-agent/agents/guided-intake)
|
|
43
|
+
for the native program, Zod form, direct code references, deterministic no-key
|
|
44
|
+
model, and optional OpenRouter model. Run `pnpm verify:guidance` in the example
|
|
45
|
+
project for a real Foundry worker/restart check.
|
|
46
|
+
|
|
47
|
+
One surface of each kind mounts per runnable. A program can contain many goals;
|
|
48
|
+
a registry can contain many forms. Return `undefined` to omit a surface for a run.
|
|
49
|
+
This removes its tools from that assembly, not its saved state.
|
|
50
|
+
|
|
51
|
+
## Scope and reconstruction
|
|
52
|
+
|
|
53
|
+
The default subject derives from workspace, instance, and conversation—not run id.
|
|
54
|
+
Set `scope: "instance"` to deliberately share across an instance's conversations.
|
|
55
|
+
For externally scoped business identities, supply a native goal/fact scope or
|
|
56
|
+
form `{ subject }`. Authorization and tenant isolation remain host-owned.
|
|
57
|
+
`foundryGuidanceSubject(ctx, scope)` derives the same key outside assembly.
|
|
58
|
+
Facts additionally have an evidence context; goals have a program key.
|
|
59
|
+
|
|
60
|
+
`program` seeds an absent goal scope through native idempotent `start`. Reassembly
|
|
61
|
+
never resets progress or overwrites an existing program. To adapt obligations,
|
|
62
|
+
call the runner's `revise` with the version you read and a reason. Keep native
|
|
63
|
+
goal/item keys stable; use code values such as `identityGoal.key`, not repeated
|
|
64
|
+
string references. These are native revision keys, not a new Foundry registry.
|
|
65
|
+
|
|
66
|
+
`configure`, `spawn`, and `run` receive `ctx.goals`, `ctx.facts`, and `ctx.forms`
|
|
67
|
+
as typed native handles. Goals also accept native `configure({ glove, status })`,
|
|
68
|
+
`hooks`, `tools`, and `onChange` options for progress-dependent behavior.
|
|
69
|
+
|
|
70
|
+
## Facts and opt-in preparation
|
|
71
|
+
|
|
72
|
+
Mounting facts adds native `record_fact`. The model supplies text and urgency;
|
|
73
|
+
Foundry binds message/run provenance. It cannot grant verification or choose
|
|
74
|
+
another subject. Supply `source` for transport-specific stable operation ids.
|
|
75
|
+
Host-verified evidence can be written with `ctx.facts.record(...)`.
|
|
76
|
+
|
|
77
|
+
To enable automatic preparation, give `facts.preparationAgent` a dedicated,
|
|
78
|
+
built Glove runnable with its own scope-specific store and tracing subscribers.
|
|
79
|
+
Then configure `goals.preparation` or `forms.preparation` with native `rule` and
|
|
80
|
+
optional `eligible` callbacks. Foundry constructs the shared native
|
|
81
|
+
`FactPreparation`; it never calls a model adapter directly. Preparation without
|
|
82
|
+
a dedicated agent, or with mismatched facts/workflow subjects, rejects explicitly.
|
|
83
|
+
Never reuse the conversational runnable or a preparation store across fact scopes.
|
|
84
|
+
|
|
85
|
+
Rules are host-owned allowlists; omitted requirements remain manual. Unverified
|
|
86
|
+
information needs confirmation unless explicitly permitted. Actions and outcomes
|
|
87
|
+
need verified successful evidence with the correct evidence key; approvals need
|
|
88
|
+
an authorized actor. Intentions are not actions. Corrections can require review;
|
|
89
|
+
they do not automatically overwrite answers or repeat completed effects. The same
|
|
90
|
+
evidence can support multiple workflows without being consumed globally.
|
|
91
|
+
|
|
92
|
+
## Forms and effects
|
|
93
|
+
|
|
94
|
+
Use native `defineForm`, Zod fields, gates, checkpoints and executors. Register a
|
|
95
|
+
definition using its own `.id`, not a copied reference string. The registry makes
|
|
96
|
+
forms available; it does not start every form. Native tools select, start, fill,
|
|
97
|
+
inspect, revise and abandon them. A host may call `ctx.forms.start(form.id)`;
|
|
98
|
+
check saved instances, including completed ones, before once-only initialization.
|
|
99
|
+
|
|
100
|
+
Adapters retain full answer history, pending hook batches, prepared claims,
|
|
101
|
+
checkpoint state, and dispatch receipts. Goal hooks and form effects are
|
|
102
|
+
at-least-once: downstream effects must use their idempotency keys. Native schemas
|
|
103
|
+
and gates retain authority over valid values and permitted effects.
|
|
104
|
+
|
|
105
|
+
## Custom context providers
|
|
106
|
+
|
|
107
|
+
The outer resolver selects providers once per run. Each returned native provider
|
|
108
|
+
is re-read before a model iteration and may fetch live adapter state. It returns
|
|
109
|
+
text, null, or undefined and receives an abort signal. Keep providers read-only;
|
|
110
|
+
do not run inference inside one. Foundry removes its providers during cleanup,
|
|
111
|
+
including failures.
|
|
112
|
+
|
|
113
|
+
Glove appends transient user-role context after complete tool-result pairs, without
|
|
114
|
+
rewriting system instructions or storing snapshots as conversation messages.
|
|
115
|
+
Custom runnable wrappers must forward `addContextProvider` and `getRuntimeContext`.
|
|
116
|
+
Realtime voice refreshes at startup and after tools; after external changes the
|
|
117
|
+
host calls `realtime.refreshContext()`. It does not automatically interrupt speech
|
|
118
|
+
or erase superseded context already in the provider's session.
|
|
119
|
+
|
|
120
|
+
## Persistence and inspection
|
|
121
|
+
|
|
122
|
+
`createSqliteMemoryAdapters` supplies goals, facts and forms alongside entity,
|
|
123
|
+
episodic, resource and pinned-context memory. Use Node 22.13+ and a local persistent
|
|
124
|
+
volume. Goal/form operations are transactional with native CAS and audit state.
|
|
125
|
+
Facts use a separate SQLite lock file across asynchronous scope callbacks;
|
|
126
|
+
each save commits independently and process death releases the OS-owned lock.
|
|
127
|
+
All fact scopes in one database serialize. For greater concurrency use separate
|
|
128
|
+
files or a production database adapter. Network filesystems are unsupported.
|
|
129
|
+
Same-process callers queue behind the active callback; `busyTimeoutMs` bounds
|
|
130
|
+
SQLite contention after that queue, not inference or callback duration. Bound
|
|
131
|
+
preparation-agent execution in the host and avoid recursive fact callbacks.
|
|
132
|
+
Do not delete or replace database/lock files while workers run. Back up a coherent
|
|
133
|
+
SQLite snapshot, not just a live main file without its WAL. The lock file contains
|
|
134
|
+
no application records.
|
|
135
|
+
|
|
136
|
+
The inspector's **Conversation guidance** card shows goal progress, fact
|
|
137
|
+
revision/claim counts, and form status/pending effects. It is the latest observed
|
|
138
|
+
run snapshot, not a live database query. Its `foundry.guidance.state` event omits
|
|
139
|
+
answers and fact bodies. Native runtime-context/tool traces can still contain
|
|
140
|
+
conversation content; apply normal access and retention controls. Never put
|
|
141
|
+
credentials in facts or context providers.
|
|
142
|
+
|
|
143
|
+
Foundry data, transcript storage, workflow state, and VFS persistence are separate
|
|
144
|
+
boundaries. Durable workflow adapters do not make a transient transcript durable.
|
|
145
|
+
These fields do not schedule background work; use Foundry's existing schedules,
|
|
146
|
+
sleep, and inbound activations for that.
|
package/docs/inspector.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Foundry inspector
|
|
2
2
|
|
|
3
|
+
Run detail includes a **Conversation guidance** card when goals, facts, forms or
|
|
4
|
+
custom context providers are mounted. It shows the latest observed progress,
|
|
5
|
+
fact/claim counts and pending form effects, not a live database query. Answer
|
|
6
|
+
values and fact bodies are excluded from this summary. See [guidance](./guidance.md).
|
|
7
|
+
|
|
3
8
|
The development server includes a read-oriented runtime inspector. It is organized around Foundry's actual ownership boundaries rather than presenting every event on one screen.
|
|
4
9
|
|
|
5
10
|
## Navigation
|
|
@@ -10,13 +15,43 @@ The development server includes a read-oriented runtime inspector. It is organiz
|
|
|
10
15
|
| Agents | Which definitions exist, which instances were provisioned, and how do they differ? |
|
|
11
16
|
| Agent definition | What can this code route assemble, including lazy fields, capabilities, native surfaces, schedules, and playbooks? |
|
|
12
17
|
| Agent instance | Which context, installations, playbooks, conversations, and runs belong to this persisted identity? |
|
|
18
|
+
| Chat | What has this exact instance/conversation said, and how can I continue, stop, or redirect it? |
|
|
13
19
|
| Runs | Which invocations occurred and what status, source, and attempt count did each have? |
|
|
14
20
|
| Run detail | What observable phases and events produced this outcome? |
|
|
15
21
|
| Automations | Which schedules, sleeping runs, playbook listeners, and inbound application workers exist? |
|
|
16
22
|
| Integrations | Which transmissions, safe account references, routes, and agent bindings form the external topology? |
|
|
17
23
|
| Workspaces | Which shared entries, inbox items, tasks, and non-secret environment values are available? |
|
|
18
24
|
|
|
19
|
-
Every detail view has a real URL. For example, `/agents/support-lead`, `/instances/<agent-id>`, and `/runs/<run-id>` can be bookmarked or opened directly; the Foundry server returns the inspector shell for non-API paths.
|
|
25
|
+
Every detail view has a real URL. For example, `/agents/support-lead`, `/instances/<agent-id>`, `/chat/<conversation-id>?agent=<agent-id>`, and `/runs/<run-id>` can be bookmarked or opened directly; the Foundry server returns the inspector shell for non-API paths.
|
|
26
|
+
|
|
27
|
+
The inspector and APIs share one authorization boundary. Loopback remains the
|
|
28
|
+
development default. Foundry refuses a non-loopback bind without the application's
|
|
29
|
+
`requestAuthorization` adapter; when configured, every HTML, API, health, and event
|
|
30
|
+
stream request must pass it. The adapter owns credential/session validation and the
|
|
31
|
+
browser challenge, while Foundry keeps credential values out of state and traces.
|
|
32
|
+
|
|
33
|
+
## Talking to an agent
|
|
34
|
+
|
|
35
|
+
**Chat** is a browser conversation client, not a second transcript system. Select a
|
|
36
|
+
runtime instance in the left rail, create or reopen any of its conversations, and
|
|
37
|
+
send text, images, video, or documents. The page reads native Glove `Message`
|
|
38
|
+
records from the definition or root `conversationStore`, including tool calls and
|
|
39
|
+
results. Recent history loads first; **Earlier** and **Newer** page through long
|
|
40
|
+
sessions without putting an unbounded transcript into the browser. It also displays
|
|
41
|
+
the store's turn and token counters. When no store is
|
|
42
|
+
configured the page says that history is unavailable instead of inventing history
|
|
43
|
+
from run output or observability events.
|
|
44
|
+
|
|
45
|
+
During an active turn the conversation shows safe runtime progress and links to the
|
|
46
|
+
complete run trace. Provider-approved `text_delta` events form a transient assistant
|
|
47
|
+
bubble while work is active; when the run settles, the exact stored message replaces
|
|
48
|
+
that projection. Buffered output from failed provider attempts never reaches the page.
|
|
49
|
+
**Stop** cooperatively cancels it. Sending another message while
|
|
50
|
+
it is active becomes **Redirect**: Foundry interrupts at a run boundary and creates
|
|
51
|
+
a replacement run in the same conversation with steering lineage. Session routing
|
|
52
|
+
is explicit in the URL, and every refresh reloads history from the server. New
|
|
53
|
+
conversations take the first text turn as their initial title and can be renamed
|
|
54
|
+
later without changing their stable conversation identity.
|
|
20
55
|
|
|
21
56
|
## Following a run
|
|
22
57
|
|
|
@@ -80,6 +115,9 @@ Every truncated identifier in the inspector has a copy button, so the full run,
|
|
|
80
115
|
The inspector is an API client and adds no hidden runtime state. Its primary read surfaces are:
|
|
81
116
|
|
|
82
117
|
- `/api/manifest`, `/api/agent-instances`, and `/api/conversations`
|
|
118
|
+
- `/api/conversations/:id/messages?agent=<agent-id>` for exact adapter-backed history
|
|
119
|
+
- `POST /api/conversations/:id/messages` and `/api/runs/:id/cancel|steer`
|
|
120
|
+
- `PATCH /api/conversations/:id` for an instance-owned title or context update
|
|
83
121
|
- `/api/runs`, `/api/runs/:id`, and `/api/events`
|
|
84
122
|
- `/api/activations` and `/api/playbook-subscriptions`
|
|
85
123
|
- `/api/application-connections`
|