glove-foundry 0.3.2 → 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.
@@ -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, checks types and conventions, generates `.foundry/routes.d.ts`, and starts the runtime and inspector.
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 private to your network — the inspector is a development surface.
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 pending work on startup. Sleep preserves the instance and conversation so the wake-up resumes with the same stored context.
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.
@@ -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`