glove-foundry 0.0.0 → 0.2.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.
Files changed (35) hide show
  1. package/dist/{chunk-UNHGCCSA.js → chunk-P65RT7H5.js} +486 -49
  2. package/dist/cli.js +536 -352
  3. package/dist/index.js +1 -1
  4. package/docs/building-with-foundry.md +39 -2
  5. package/docs/inspector.md +35 -2
  6. package/package.json +7 -6
  7. package/templates/minimal/README.md +58 -0
  8. package/templates/minimal/agents/assistant/agent.ts +28 -0
  9. package/templates/minimal/agents/assistant/tools/current-time.tool.ts +17 -0
  10. package/templates/minimal/env.example +2 -0
  11. package/templates/minimal/foundry.application.ts +12 -0
  12. package/templates/minimal/foundry.config.ts +6 -0
  13. package/templates/minimal/gitignore +3 -0
  14. package/templates/travel-concierge/README.md +203 -0
  15. package/templates/travel-concierge/agents/concierge/actions/reply.action.ts +6 -0
  16. package/templates/travel-concierge/agents/concierge/agent.ts +106 -0
  17. package/templates/travel-concierge/agents/concierge/apps/calendar.app.ts +67 -0
  18. package/templates/travel-concierge/agents/concierge/composition.ts +18 -0
  19. package/templates/travel-concierge/agents/concierge/events/message-received.event.ts +7 -0
  20. package/templates/travel-concierge/agents/concierge/events/message-sent.event.ts +7 -0
  21. package/templates/travel-concierge/agents/concierge/layers/trip-context.layer.ts +23 -0
  22. package/templates/travel-concierge/agents/concierge/memory/traveller.memory.ts +19 -0
  23. package/templates/travel-concierge/agents/concierge/predicates/mentions-trip.predicate.ts +17 -0
  24. package/templates/travel-concierge/agents/concierge/schedules/trip-countdown.ts +16 -0
  25. package/templates/travel-concierge/agents/concierge/subscribers/usage.subscriber.ts +16 -0
  26. package/templates/travel-concierge/agents/concierge/tools/find-flights.tool.ts +49 -0
  27. package/templates/travel-concierge/agents/concierge/topology.ts +35 -0
  28. package/templates/travel-concierge/agents/concierge/transmissions/messaging.transmission.ts +57 -0
  29. package/templates/travel-concierge/agents/concierge/workbench.ts +26 -0
  30. package/templates/travel-concierge/env.example +3 -0
  31. package/templates/travel-concierge/foundry.application.ts +18 -0
  32. package/templates/travel-concierge/foundry.config.ts +9 -0
  33. package/templates/travel-concierge/gitignore +3 -0
  34. package/templates/travel-concierge/lib/demo-model.ts +72 -0
  35. package/templates/travel-concierge/src/client.ts +22 -0
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ import {
45
45
  memoryTopologyStore,
46
46
  serializeInboundTransmissionXml,
47
47
  writeGeneratedTypes
48
- } from "./chunk-UNHGCCSA.js";
48
+ } from "./chunk-P65RT7H5.js";
49
49
  import {
50
50
  FoundryClient,
51
51
  FoundryRunHandle,
@@ -5,15 +5,52 @@ Foundry uses the filesystem for code identity and imported values for code relat
5
5
  ## Create and run
6
6
 
7
7
  ```bash
8
- npx glove foundry support-workforce
8
+ npx glove foundry init support-workforce
9
9
  cd support-workforce
10
- pnpm install
11
10
  cp .env.example .env.local
11
+ pnpm install
12
12
  pnpm dev
13
13
  ```
14
14
 
15
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.
16
16
 
17
+ 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
+
19
+ ### Templates
20
+
21
+ | `--template` | What you get |
22
+ | --- | --- |
23
+ | `travel-concierge` (default) | A travel agent that searches flights, installs a calendar application, replies over a chat transmission, remembers the traveller, wakes on a schedule, and owns a sandboxed VFS and REPL. One example per convention. |
24
+ | `minimal` | One agent and one tool. |
25
+
26
+ The travel concierge runs before you configure a provider — `lib/demo-model.ts` answers deterministically until `OPENROUTER_API_KEY` is set, so the first run produces a real trace with real tool calls.
27
+
28
+ ### Adding Foundry to an existing Next.js app
29
+
30
+ ```bash
31
+ cd my-next-app
32
+ npx glove foundry init . --target nextjs # or just: npx glove foundry init .
33
+ ```
34
+
35
+ A directory holding a Next.js app is detected, so `--target` is usually unnecessary. Foundry then joins the project rather than taking it over:
36
+
37
+ | Path | What lands there |
38
+ | --- | --- |
39
+ | `foundry/agents/**` | The agents |
40
+ | `foundry/foundry.application.ts` | Data adapter, accounts, routes |
41
+ | `foundry/package.json` | `{"type":"module"}`, scoping ESM to the agents |
42
+ | `foundry.config.mts` | Points the runtime at `foundry/agents` |
43
+ | `lib/foundry.ts` | A typed client your app imports |
44
+ | `app/api/<agent>/route.ts` | An example route handler |
45
+
46
+ Your `package.json` keeps its name, scripts, and dependencies; it gains `foundry:dev`, `foundry:start`, and the Glove packages. Nothing it already owns is overwritten.
47
+
48
+ The runtime is a separate process from `next dev`, deliberately — it holds durable state and should not restart when a component changes. The app reaches it over HTTP through `lib/foundry.ts`, so no part of the agent graph is bundled into your app, and `FoundryRoutes` stays a type-only import.
49
+
50
+ 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
+
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.
53
+
17
54
  ## The filesystem is the static registry
18
55
 
19
56
  ```text
package/docs/inspector.md CHANGED
@@ -27,7 +27,9 @@ Open **Runs**, then choose one invocation. The run detail starts with a four-pha
27
27
  3. Agent work: observable model and tool work occurred.
28
28
  4. Completed, failed, cancelled, or still in progress.
29
29
 
30
- The event trace below the spine is collapsed by default. Expand an event when you need its adapter payload. The inspector shows observable intent, actions, and outcomes; it does not expose a model's private hidden chain-of-thought.
30
+ The event trace below the spine is collapsed by default. Each row carries its wall-clock time and its offset from the start of the run, so a slow phase is visible without arithmetic. Filter the trace by event category, expand an event when you need its adapter payload, and copy a payload straight from the expanded row. The inspector shows observable intent, actions, and outcomes; it does not expose a model's private hidden chain-of-thought.
31
+
32
+ A failed run states its error above the spine rather than only inside the recorded output. A run that is still going says so and keeps a running duration. **Run again** reopens the run drawer with the same instance and message.
31
33
 
32
34
  ## Starting work
33
35
 
@@ -38,9 +40,40 @@ Use **New run** from any page. Select a definition and either:
38
40
 
39
41
  The inspector reuses that instance's latest conversation or creates its first conversation, sends the message, then navigates directly to the new run.
40
42
 
43
+ ## Filtering runs
44
+
45
+ The **Runs** page keeps its filters in the query string, so any view is a link you can send or bookmark:
46
+
47
+ | Parameter | Values |
48
+ | --- | --- |
49
+ | `status` | `running`, `completed`, `failed`, or `cancelled`. `running` also covers pending runs. |
50
+ | `source` | Any recorded source kind, such as `direct`, `transmission`, `activation`, or `spawn`. |
51
+ | `q` | Free text matched against the agent, run id, instance id, and recorded input. |
52
+
53
+ `/runs?status=failed&q=invoice` opens directly on the failed runs mentioning an invoice. The status tabs carry live counts, and each row shows the run's duration alongside a relative start time.
54
+
41
55
  ## Live updates and search
42
56
 
43
- The inspector subscribes to `/api/events` with server-sent events and also performs a low-frequency reconciliation. Press `Command-K` or `Control-K` to search pages, definitions, instances, and retained runs.
57
+ The inspector subscribes to `/api/events` with server-sent events and also performs a low-frequency reconciliation. A busy run emits many events, so they are coalesced into one refresh rather than one repaint each.
58
+
59
+ A refresh preserves what you are doing: scroll position, an expanded event, an open `Output` panel, and the text and caret in a filter box all survive it. Relative timestamps and the duration of an in-flight run tick every second without a repaint.
60
+
61
+ Press `Command-K` or `Control-K` to search pages, definitions, instances, and retained runs.
62
+
63
+ ## Keyboard
64
+
65
+ | Key | Action |
66
+ | --- | --- |
67
+ | `j` / `k` | Move down and up the current list |
68
+ | `Enter` or `o` | Open the highlighted row |
69
+ | `/` | Focus the run filter, or open search elsewhere |
70
+ | `c` | Start a run |
71
+ | `r` | Refresh runtime data |
72
+ | `Command-K` / `Control-K` | Search |
73
+ | `Escape` | Close the drawer or search |
74
+ | `Command-Enter` | Submit the run drawer |
75
+
76
+ Every truncated identifier in the inspector has a copy button, so the full run, instance, or conversation id is always retrievable.
44
77
 
45
78
  ## Operator API used by the inspector
46
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glove-foundry",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "description": "An Effect-native, file-routed framework for typed, observable Glove agent applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "files": [
41
41
  "dist",
42
42
  "docs",
43
+ "templates",
43
44
  "THIRD_PARTY_NOTICES.md"
44
45
  ],
45
46
  "keywords": [
@@ -62,14 +63,14 @@
62
63
  "station-schedules": "^2.2.0",
63
64
  "tsx": "^4.21.0",
64
65
  "zod": "^4.3.6",
65
- "glove-core": "3.6.0",
66
- "glove-js": "0.4.0",
67
66
  "glove-mcp": "1.1.0",
67
+ "glove-js": "0.4.0",
68
+ "glove-memory": "1.1.0",
69
+ "glove-core": "3.6.0",
68
70
  "glove-lisp": "0.4.0",
69
71
  "glove-mesh": "0.1.1",
70
- "glove-memory": "1.1.0",
71
- "glove-working-environment": "0.6.0",
72
- "glove-python": "0.3.0"
72
+ "glove-python": "0.3.0",
73
+ "glove-working-environment": "0.6.0"
73
74
  },
74
75
  "devDependencies": {
75
76
  "@types/node": "^25.2.3",
@@ -0,0 +1,58 @@
1
+ # {{projectName}}
2
+
3
+ A [Glove Foundry](https://github.com/porkytheblack/glove/tree/main/packages/glove-foundry) application.
4
+
5
+ ```bash
6
+ cp .env.example .env.local # add your OPENROUTER_API_KEY
7
+ {{installCommand}}
8
+ {{devCommand}}
9
+ ```
10
+
11
+ Then open **http://127.0.0.1:4141** and press **Start a run**.
12
+
13
+ ## The one idea to understand first
14
+
15
+ Foundry separates a **definition** (code — what an agent can assemble, at `agents/assistant/agent.ts`) from an **instance** (data — one persisted identity with its own context, installed apps, and conversations). One definition serves many instances.
16
+
17
+ **The filesystem is the registry.** `agents/assistant/agent.ts` *is* the agent `assistant`. There are no string ids to keep in sync.
18
+
19
+ ## Adding capabilities
20
+
21
+ Any file matching these names under an agent folder is discovered automatically. Each default-exports one definition, and the **filename becomes its id**.
22
+
23
+ | File | Defines | Helper |
24
+ | --- | --- | --- |
25
+ | `agent.ts` | The agent | `defineAgent` |
26
+ | `tools/*.tool.ts` | A tool any agent can mount | `defineSharedTool` |
27
+ | `apps/*.app.ts` | An installable capability bundle | `defineApp` |
28
+ | `transmissions/*.transmission.ts` | An external transport shape | `defineTransmission` |
29
+ | `events/*.event.ts` | A transmission event | `defineTransmissionEvent` |
30
+ | `predicates/*.predicate.ts` | An inbound match rule | `defineTransmissionPredicate` |
31
+ | `mcp/*.mcp.ts` | An MCP server entry | `defineMcp` |
32
+ | `memory/*.memory.ts` | A memory profile | `defineMemory` |
33
+ | `layers/*.layer.ts` | Native Glove setup | `defineLayer` |
34
+ | `subscribers/*.subscriber.ts` | An observer | `defineSubscriber` |
35
+ | `schedules/*.ts` | Recurring or future work | `defineSchedule` |
36
+
37
+ Create the file, add it to `composeAgent(...)`, and the dev server picks it up and regenerates types.
38
+
39
+ Want a worked example with a calendar application, a chat transport, memory, a schedule, and a sandboxed REPL? Scaffold the travel concierge:
40
+
41
+ ```bash
42
+ npx glove-foundry init my-concierge --template travel-concierge
43
+ ```
44
+
45
+ ## Commands
46
+
47
+ | Command | What it does |
48
+ | --- | --- |
49
+ | `{{devCommand}}` | Discover agents, typecheck, generate routes, serve the runtime and inspector |
50
+ | `{{startCommand}}` | Run without file watching |
51
+ | `{{lintCommand}}` | Lint, including the Foundry file-routing rules |
52
+ | `{{typecheckCommand}}` | `tsc --noEmit` |
53
+
54
+ ## Documentation
55
+
56
+ - [Building with Foundry](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/building-with-foundry.md)
57
+ - [Architecture](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/architecture.md)
58
+ - [The inspector](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/inspector.md)
@@ -0,0 +1,28 @@
1
+ import { MemoryStore } from "glove-core";
2
+ import { createAdapter } from "glove-core/models/providers";
3
+ import { composeAgent, defineAgent } from "glove-foundry";
4
+ import currentTime from "./tools/current-time.tool.js";
5
+
6
+ /**
7
+ * The file path is the identity: this is the agent `assistant`.
8
+ *
9
+ * Every field may be a value or a function; a function is called per run with
10
+ * the request context. Add capabilities as sibling files — `tools/*.tool.ts`,
11
+ * `apps/*.app.ts`, `memory/*.memory.ts` — and compose them below.
12
+ */
13
+ export default defineAgent({
14
+ description: "A general-purpose Glove assistant",
15
+ components: composeAgent(currentTime),
16
+ store: ({ conversationId }) => new MemoryStore(`assistant:${conversationId}`),
17
+ model: () => createAdapter({
18
+ provider: "openrouter",
19
+ model: process.env.OPENROUTER_MODEL ?? "openai/gpt-4.1-mini",
20
+ stream: true,
21
+ }),
22
+ systemPrompt: (_agent, { message, history }) => [
23
+ "You are a precise, practical assistant.",
24
+ `Current request: ${message.text}`,
25
+ `Prior messages: ${history.length}`,
26
+ ].join("\n"),
27
+ compactionInstructions: () => "Preserve decisions, open work, and important context.",
28
+ });
@@ -0,0 +1,17 @@
1
+ import { defineSharedTool } from "glove-foundry";
2
+ import { z } from "zod";
3
+
4
+ /** One file, one default export. The filename is the id: `current-time`. */
5
+ const currentTime = defineSharedTool({
6
+ description: "Return the current ISO timestamp",
7
+ tool: {
8
+ name: "current_time",
9
+ description: "Return the current ISO timestamp",
10
+ inputSchema: z.object({}),
11
+ async do() {
12
+ return { status: "success" as const, data: new Date().toISOString() };
13
+ },
14
+ },
15
+ });
16
+
17
+ export default currentTime;
@@ -0,0 +1,2 @@
1
+ OPENROUTER_API_KEY=
2
+ OPENROUTER_MODEL=openai/gpt-4.1-mini
@@ -0,0 +1,12 @@
1
+ import { MemoryFoundryDataAdapter, defineApplication } from "glove-foundry";
2
+
3
+ /** Swap this adapter for your own to persist instances across restarts. */
4
+ export const data = new MemoryFoundryDataAdapter();
5
+
6
+ export default defineApplication({
7
+ name: "{{projectName}}",
8
+ data,
9
+ accounts: [],
10
+ routes: [],
11
+ bindings: [],
12
+ });
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "glove-foundry/config";
2
+
3
+ export default defineConfig({
4
+ server: { port: 4141 },
5
+ execution: { pollIntervalMs: 100, maxConcurrent: 4 },
6
+ });
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ .env.local
3
+ .foundry/manifest.json
@@ -0,0 +1,203 @@
1
+ # {{projectName}}
2
+
3
+ A [Glove Foundry](https://github.com/porkytheblack/glove/tree/main/packages/glove-foundry) application. The example agent is a **travel concierge**: it searches flights, checks a calendar, remembers the traveller, replies over a chat transport, and wakes itself on a schedule.
4
+
5
+ It runs before you configure anything — there is a built-in demo model, so you get real runs and a real event trace with no API key.
6
+
7
+ ```bash
8
+ cp .env.example .env.local # optional: add OPENROUTER_API_KEY for real answers
9
+ {{installCommand}}
10
+ {{devCommand}}
11
+ ```
12
+
13
+ Then open **http://127.0.0.1:4141** and press **Start a run**.
14
+
15
+ ---
16
+
17
+ ## The one idea to understand first
18
+
19
+ Foundry separates two things that most frameworks merge:
20
+
21
+ | | What it is | Where it lives | Changes when |
22
+ | --- | --- | --- | --- |
23
+ | **Definition** | Code. What an agent *can* assemble. | `agents/concierge/agent.ts` | You edit a file |
24
+ | **Instance** | Data. One persisted identity, with its own context, installed apps, and conversations. | Your data adapter | You call the API or use the inspector |
25
+
26
+ One definition serves many instances. Two travellers can run the same concierge with different calendars, budgets, and chat accounts — without a branch in your code.
27
+
28
+ **The filesystem is the registry.** `agents/concierge/agent.ts` *is* the agent `concierge`. There are no string ids to keep in sync, and renaming a file breaks the import at compile time rather than at 3am.
29
+
30
+ ---
31
+
32
+ ## What is in this project
33
+
34
+ ```
35
+ agents/concierge/
36
+ agent.ts the agent. start here
37
+ composition.ts composeAgent(...) — what it is built from
38
+ tools/find-flights.tool.ts a shared tool
39
+ apps/calendar.app.ts an application an instance installs
40
+ transmissions/messaging.transmission.ts Telegram/WhatsApp-shaped transport
41
+ events/message-received.event.ts an inbound event
42
+ events/message-sent.event.ts an outbound event
43
+ predicates/mentions-trip.predicate.ts routing logic, kept out of the agent
44
+ memory/traveller.memory.ts ambient context across conversations
45
+ schedules/trip-countdown.ts recurring work
46
+ layers/trip-context.layer.ts direct access to the native Glove runtime
47
+ subscribers/usage.subscriber.ts observation without behaviour change
48
+ workbench.ts the agent's VFS + JavaScript REPL
49
+ topology.ts accounts and routes (runtime data)
50
+
51
+ foundry.application.ts data adapter, accounts, routes
52
+ foundry.config.ts port, execution policy
53
+ lib/demo-model.ts keyless model — delete once you have a key
54
+ src/client.ts a typed client for these agents
55
+ .foundry/routes.d.ts generated. do not edit
56
+ ```
57
+
58
+ ### Conventions
59
+
60
+ Any file matching these names under an agent folder is discovered automatically. Each one default-exports a single definition, and its **filename becomes its id**. Nested folders nest the id: `tools/calendar/today.tool.ts` is `calendar/today`.
61
+
62
+ | File | Defines | Helper |
63
+ | --- | --- | --- |
64
+ | `agent.ts` | The agent | `defineAgent` |
65
+ | `tools/*.tool.ts` | A tool any agent can mount | `defineSharedTool` |
66
+ | `apps/*.app.ts` | An installable capability bundle | `defineApp` |
67
+ | `transmissions/*.transmission.ts` | An external transport shape | `defineTransmission` |
68
+ | `events/*.event.ts` | A transmission event | `defineTransmissionEvent` |
69
+ | `predicates/*.predicate.ts` | An inbound match rule | `defineTransmissionPredicate` |
70
+ | `mcp/*.mcp.ts` | An MCP server entry | `defineMcp` |
71
+ | `memory/*.memory.ts` | A memory profile | `defineMemory` |
72
+ | `layers/*.layer.ts` | Native Glove setup | `defineLayer` |
73
+ | `subscribers/*.subscriber.ts` | An observer | `defineSubscriber` |
74
+ | `schedules/*.ts` | Recurring or future work | `defineSchedule` |
75
+ | `connections/*.connection.ts` | A long-lived inbound worker | `defineConnection` |
76
+ | `actions/*.action.ts` | A playbook action | `definePlaybookAction` |
77
+
78
+ Every field of `defineAgent` accepts **a value or a function**. A function runs per request with the full context — message, history, instance, installations — which is how one definition adapts without branching inside a prompt.
79
+
80
+ ---
81
+
82
+ ## Things you will want to do
83
+
84
+ ### Add a tool
85
+
86
+ Create `agents/concierge/tools/weather.tool.ts`, default-export `defineSharedTool({...})`, then add it to `composition.ts`. That is the whole loop — the dev server picks it up and regenerates types.
87
+
88
+ ### Add a second agent
89
+
90
+ Create `agents/<name>/agent.ts`. It is immediately routable, appears in the inspector, and is added to `FoundryRoutes` for the typed client.
91
+
92
+ ### Give an instance a capability
93
+
94
+ Applications and MCP servers stay inert until an instance installs one. From the inspector, open the instance and install `calendar`; or over the API:
95
+
96
+ ```bash
97
+ curl -X PUT http://127.0.0.1:4141/api/installations \
98
+ -H 'content-type: application/json' \
99
+ -d '{"agentId":"<instance-id>","kind":"application","id":"calendar","config":{"calendarId":"primary"}}'
100
+ ```
101
+
102
+ ### Connect a real chat app
103
+
104
+ `transmissions/messaging.transmission.ts` has the shape; only `deliver` needs a real call.
105
+
106
+ ```ts
107
+ function deliver(input: { threadId: string; text: string }, provider: string) {
108
+ return Effect.tryPromise(async () => {
109
+ const response = await fetch(
110
+ `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`,
111
+ {
112
+ method: "POST",
113
+ headers: { "content-type": "application/json" },
114
+ body: JSON.stringify({ chat_id: input.threadId, text: input.text }),
115
+ },
116
+ );
117
+ const body = await response.json() as { result: { message_id: number } };
118
+ return { externalMessageId: String(body.result.message_id) };
119
+ });
120
+ }
121
+ ```
122
+
123
+ For inbound, point the provider's webhook at your own HTTP handler and call `dispatchInbound` with the route id. Foundry stores only the safe metadata on the account — the token stays in your environment, behind `accessRef`.
124
+
125
+ ### Schedule work
126
+
127
+ Agents never call `setTimeout`. Add a `schedules/*.ts` definition, or let the agent create one at runtime through Foundry's scheduling tools. Either way it becomes a persisted activation you can see under **Automations**.
128
+
129
+ ### Call agents from your own code
130
+
131
+ ```ts
132
+ import { createFoundryClient } from "glove-foundry/client";
133
+ import type { FoundryRoutes } from "../.foundry/routes.js";
134
+
135
+ const foundry = createFoundryClient<FoundryRoutes>({ baseUrl: "http://127.0.0.1:4141" });
136
+ const agent = await foundry.agent("concierge").create({ workspaceId: "demo" });
137
+ const conversation = await foundry.createConversation(agent.id);
138
+ const run = await foundry.send(agent.id, conversation.id, "Find me a flight to Nairobi");
139
+ console.log((await run.wait()).output);
140
+ ```
141
+
142
+ `src/client.ts` is a runnable version of this.
143
+
144
+ ---
145
+
146
+ ## The inspector
147
+
148
+ `{{devCommand}}` serves a runtime inspector at **http://127.0.0.1:4141**.
149
+
150
+ | Page | Answers |
151
+ | --- | --- |
152
+ | Overview | Is the runtime healthy, what is running, what needs attention |
153
+ | Agents | Which definitions exist and which instances were provisioned |
154
+ | Runs | Every invocation, with status, duration, and source |
155
+ | Run detail | The phase spine and the full observable event trace |
156
+ | Automations | Schedules, sleeping runs, playbook listeners, inbound workers |
157
+ | Integrations | Transmissions, accounts, routes, and bindings |
158
+ | Workspaces | Shared entries, inbox, tasks, and non-secret environment values |
159
+
160
+ Filters live in the URL, so `/runs?status=failed` is a link you can send. Press `⌘K` to search, `j`/`k` to move through a list, `c` to start a run.
161
+
162
+ ---
163
+
164
+ ## Going to production
165
+
166
+ 1. **Replace the data adapter.** `MemoryFoundryDataAdapter` in `foundry.application.ts` loses everything on restart. Implement `FoundryDataAdapter` against your database.
167
+ 2. **Delete `lib/demo-model.ts`** and the fallback in `agent.ts` once `OPENROUTER_API_KEY` is set.
168
+ 3. **Own your credentials.** Foundry stores account *references*, never secrets. Keep tokens in your own adapter or secret manager.
169
+ 4. **Run `{{startCommand}}`** rather than `dev` — no file watching, no restart-on-change.
170
+ 5. **Keep the ESLint preset.** `glove-foundry/eslint` rejects patterns that break file routing, such as a hand-written `id` on a file-routed definition.
171
+
172
+ ---
173
+
174
+ ## The Glove packages
175
+
176
+ | Package | What it gives you |
177
+ | --- | --- |
178
+ | [`glove-foundry`](https://www.npmjs.com/package/glove-foundry) | This framework: routing, runtime, inspector, client |
179
+ | [`glove-core`](https://www.npmjs.com/package/glove-core) | The agent runtime, model adapters, stores, tools |
180
+ | [`glove-js`](https://www.npmjs.com/package/glove-js) | The JavaScript REPL session used in `workbench.ts` |
181
+ | [`glove-python`](https://www.npmjs.com/package/glove-python) | A Python REPL, same shape |
182
+ | [`glove-lisp`](https://www.npmjs.com/package/glove-lisp) | A Lisp REPL, same shape |
183
+ | [`glove-working-environment`](https://www.npmjs.com/package/glove-working-environment) | The sandboxed VFS behind `defineWorkingEnvironment` |
184
+ | [`glove-memory`](https://www.npmjs.com/package/glove-memory) | Memory schemas and adapters |
185
+ | [`glove-mcp`](https://www.npmjs.com/package/glove-mcp) | MCP client and server support |
186
+ | [`glove-mesh`](https://www.npmjs.com/package/glove-mesh) | Multi-agent messaging |
187
+
188
+ Add an environment package when you need it — `glove-env-documents`, `glove-env-spreadsheets`, `glove-env-images`, `glove-env-slides`, `glove-env-render`, `glove-env-ocr`, `glove-env-media`, `glove-env-email`, `glove-env-zip`, `glove-env-motion` — and mount it in `workbench.ts`.
189
+
190
+ ## Commands
191
+
192
+ | Command | What it does |
193
+ | --- | --- |
194
+ | `{{devCommand}}` | Discover agents, typecheck, generate routes, serve the runtime and inspector |
195
+ | `{{startCommand}}` | Run without file watching |
196
+ | `{{lintCommand}}` | Lint, including the Foundry file-routing rules |
197
+ | `{{typecheckCommand}}` | `tsc --noEmit` |
198
+
199
+ ## Documentation
200
+
201
+ - [Building with Foundry](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/building-with-foundry.md)
202
+ - [Architecture](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/architecture.md)
203
+ - [The inspector](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/inspector.md)
@@ -0,0 +1,6 @@
1
+ import { definePlaybookAction } from "glove-foundry";
2
+
3
+ /** What the playbook asks the agent to do when a traveller message matches. */
4
+ export default definePlaybookAction({
5
+ description: "Draft a reply to the traveller's message.",
6
+ });
@@ -0,0 +1,106 @@
1
+ import { MemoryStore } from "glove-core";
2
+ import { createAdapter } from "glove-core/models/providers";
3
+ import { composePlaybook, defineAgent } from "glove-foundry";
4
+ import { DemoModel } from "../../lib/demo-model.js";
5
+ import reply from "./actions/reply.action.js";
6
+ import calendar from "./apps/calendar.app.js";
7
+ import { conciergeComponents } from "./composition.js";
8
+ import messageReceived from "./events/message-received.event.js";
9
+ import messageSent from "./events/message-sent.event.js";
10
+ import tripContext from "./layers/trip-context.layer.js";
11
+ import travellerMemory from "./memory/traveller.memory.js";
12
+ import mentionsTrip from "./predicates/mentions-trip.predicate.js";
13
+ import tripCountdown from "./schedules/trip-countdown.js";
14
+ import usage from "./subscribers/usage.subscriber.js";
15
+ import { travellerInbound, travellerOutbound, travellerAccount } from "./topology.js";
16
+ import messaging from "./transmissions/messaging.transmission.js";
17
+ import { conciergeRepl, conciergeWorkspace } from "./workbench.js";
18
+
19
+ /**
20
+ * The file path is the identity: `agents/concierge/agent.ts` is the agent
21
+ * `concierge`. There is no id to keep in sync.
22
+ *
23
+ * Every field below may be a value or a function. A function is called per
24
+ * run with the request context, which is how one definition serves many
25
+ * instances without branching inside the prompt.
26
+ */
27
+
28
+ /** Runs with no API key so `pnpm dev` works before you have configured one. */
29
+ function model() {
30
+ if (!process.env.OPENROUTER_API_KEY) return new DemoModel();
31
+ return createAdapter({
32
+ provider: "openrouter",
33
+ model: process.env.OPENROUTER_MODEL ?? "openai/gpt-4.1-mini",
34
+ stream: true,
35
+ });
36
+ }
37
+
38
+ /**
39
+ * When a chat message arrives on the inbound route and mentions a trip, answer
40
+ * it and deliver the reply back to the same thread.
41
+ */
42
+ const travellerChat = composePlaybook({
43
+ name: "traveller-chat",
44
+ transmission: messaging,
45
+ match: {
46
+ event: messageReceived,
47
+ routes: [travellerInbound],
48
+ predicate: { definition: mentionsTrip, parameters: { any: ["trip", "flight", "hotel"] } },
49
+ },
50
+ directives: [{
51
+ action: reply,
52
+ instruction: "Answer the traveller's message. Keep it short enough to read on a phone.",
53
+ }],
54
+ applications: [calendar],
55
+ outbound: [{
56
+ route: travellerOutbound,
57
+ application: calendar,
58
+ account: travellerAccount,
59
+ applicationAccount: travellerAccount,
60
+ event: messageSent,
61
+ instruction: "Send the reply back to the traveller's thread.",
62
+ }],
63
+ });
64
+
65
+ export default defineAgent({
66
+ description: "Plans trips: finds flights, checks the calendar, and keeps the traveller updated",
67
+ tags: ["travel", "example"],
68
+ components: conciergeComponents,
69
+
70
+ // Persisted context, per conversation.
71
+ memory: [travellerMemory],
72
+ store: ({ conversationId }) => new MemoryStore(`concierge:${conversationId}`),
73
+ model,
74
+
75
+ // The prompt is built per run, so it can name what is actually mounted.
76
+ systemPrompt: (_agent, { message, history, installations }) => [
77
+ "You are a travel concierge. You are practical and you never invent bookings.",
78
+ "Use find_flights for availability, and the calendar tools before proposing dates.",
79
+ `Traveller asked: ${message.text}`,
80
+ `Prior messages: ${history.length}.`,
81
+ `Installed for this instance: ${installations.map((item) => `${item.kind}:${item.id}`).join(", ") || "nothing yet"}.`,
82
+ ].join("\n"),
83
+
84
+ // The sandbox. `budgetUsd` is read off the instance so two travellers differ.
85
+ workingEnvironment: conciergeWorkspace,
86
+ repl: (_agent, { agentId, agentInstance }) =>
87
+ conciergeRepl(agentId, Number(agentInstance.context.budgetUsd ?? 2_000)),
88
+
89
+ // Recurring work. An instance can opt out through its own context.
90
+ schedules: (_agent, { agentInstance }) =>
91
+ agentInstance.context.muteCountdown === true ? [] : [tripCountdown],
92
+
93
+ // A playbook can only bind a transmission that an installed application
94
+ // owns, so this one exists only once `calendar` is installed on the
95
+ // instance. That is lazy assembly in one line: the same code adapts to
96
+ // whatever each instance actually has.
97
+ playbooks: (_agent, { installations }) =>
98
+ installations.some((item) => item.kind === "application" && item.id === "calendar")
99
+ ? [travellerChat]
100
+ : [],
101
+
102
+ compactionInstructions: () =>
103
+ "Keep the destination, dates, budget, confirmed bookings, and open decisions.",
104
+ layers: [tripContext],
105
+ subscribers: [usage],
106
+ });