wolli 0.0.2 → 0.0.4

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.
@@ -0,0 +1,95 @@
1
+ # Introduction
2
+
3
+ How a wolli agent is laid out as files, what runs when a message arrives, and the capabilities you add as it grows.
4
+
5
+ wolli runs durable personal agents as always-on daemons; each agent is a home directory of ordinary files that you and the agent both edit.
6
+
7
+ Instead of one large configuration object, each capability gets a clear home. Identity lives in one file, workflows in one folder, integrations in another. wolli discovers that structure and turns it into an agent that remembers across sessions, reacts to events, and keeps working while your machine is on. Clients (the CLI, the TUI) attach to the daemon over HTTP.
8
+
9
+ ## An agent home at a glance
10
+
11
+ Every agent lives under `~/.wolli/agents/<name>/`:
12
+
13
+ ```
14
+ ~/.wolli/agents/<name>/
15
+ ├── SOUL.md
16
+ ├── MEMORY.md
17
+ ├── agent.json
18
+ ├── integrations/
19
+ │ └── telegram.ts
20
+ ├── workflows/
21
+ │ └── telegram-chat.ts
22
+ ├── hooks/
23
+ ├── tools/
24
+ ├── providers/
25
+ ├── skills/
26
+ ├── prompts/
27
+ ├── themes/
28
+ └── sessions/
29
+ ```
30
+
31
+ You can read most of an agent from that tree:
32
+
33
+ - `SOUL.md` and `MEMORY.md` hold identity and durable notes; the agent maintains both.
34
+ - `agent.json` holds runtime configuration.
35
+ - [integrations/](./integrations.md) connect external services (Telegram, a scheduler); transport only.
36
+ - [workflows/](./workflows.md) route events into sessions and automate everything else.
37
+ - [hooks/](./hooks.md) intercept engine events: block tool calls, rewrite input.
38
+ - [tools/](./tools.md) hold typed functions loaded into session tooling.
39
+ - [providers/](./providers.md) add model providers.
40
+ - [skills/](./skills.md) hold procedures the model loads when they apply.
41
+ - `prompts/` holds `/name` text macros, and `themes/` holds TUI color schemes.
42
+ - `sessions/` is the agent's lifetime conversation record.
43
+
44
+ A new agent needs none of the capability folders. wolli writes `SOUL.md` with you in the first conversation; add the rest when the agent needs them.
45
+
46
+ ## The files are the interface
47
+
48
+ Identity comes from the path. A workflow's name is its export binding — a `default` export takes the filename — and a file may hold several. This file defines a workflow named `inbound`:
49
+
50
+ ```ts
51
+ // ~/.wolli/agents/assistant/workflows/telegram-chat.ts
52
+ import telegram from "../integrations/telegram";
53
+
54
+ // msg is typed from the event schema
55
+ export const inbound = telegram.on("message", async (msg, ctx) => {
56
+ const [match] = await ctx.agent.findSessions({ "telegram:chat": String(msg.chatId) });
57
+ const session = await ctx.agent.openSession(match.id); // create-if-missing elided
58
+ await session.sendUserMessage(msg.text, { deliverAs: "followUp" });
59
+ });
60
+ ```
61
+
62
+ There is no registry to keep in sync. Add the file and wolli discovers it; run `/reload` to apply the change without restarting the daemon. See [Workflows](./workflows.md) for the complete API.
63
+
64
+ ## What happens when a message arrives
65
+
66
+ A Telegram message lands in the integration's transport loop, which emits a typed `message` event. A workflow bound to that event finds the chat's session by tag, or creates one, and sends the text in. The agent runs the turn, calling tools as it works. When the turn ends, a second workflow fires on `agent_end`, reads the chat tag off the session, and delivers the reply through the integration.
67
+
68
+ The session does not know which platform asked. Replies ride the session's tags, so a turn the scheduler triggers in a Telegram-tagged session still returns to that chat.
69
+
70
+ ## Sessions are durable
71
+
72
+ A session is an append-only JSONL tree, the lifetime record of one conversation. Nothing is rewritten; wolli reconstructs context deterministically from the log, and the latest leaf resumes by default. The daemon restarts, the machine reboots, and every conversation survives.
73
+
74
+ Workflow activity is recorded the same way. Each trigger firing creates a run, and everything the handler does through its context lands in that run as a step, so you can inspect what fired and what it did.
75
+
76
+ ## Grow the agent by adding capabilities
77
+
78
+ As the agent grows, each concern keeps a predictable home:
79
+
80
+ | Path | Add it when you need... |
81
+ | --- | --- |
82
+ | [providers/](./providers.md) | A model provider wolli does not ship |
83
+ | [skills/](./skills.md) | Procedures the model loads on demand |
84
+ | `prompts/` | Reusable `/name` text macros |
85
+ | `themes/` | A custom TUI color scheme |
86
+ | `store/` | Durable key-value state an integration owns |
87
+
88
+ The home is plain files, so the agent can author them itself; an agent that needs a new workflow writes one into its own folder. The directory stays readable before it runs.
89
+
90
+ ## What to read next
91
+
92
+ - [Workflows](./workflows.md): triggers, routing, and the run and step record.
93
+ - [Integrations](./integrations.md): transports, typed events, and actions.
94
+ - [Tools](./tools.md): typed functions the model calls.
95
+ - [Plugins](./plugins.md): package and install capabilities.
package/docs/plugins.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Plugins
2
2
 
3
- A plugin is an npm-style package whose `package.json` carries a `"wolli"` manifest declaring the resources it contributes extensions, integrations, skills, prompt templates, and/or themes. One install adds all of them to an agent at once, resolved in place from the single package. Plugins are how you share a [dual-half integration](./integrations.md#the-dual-half-package) (a transport plus its mapping extension), a bundle of [extensions](./extensions.md), or any mix of resource types between agents and across machines.
3
+ A plugin is an npm-style package whose `package.json` carries a `"wolli"` manifest declaring the resources it contributes: integrations, workflows, hooks, tools, providers, skills, prompt templates, and/or themes. One install adds all of them to an agent at once, resolved in place from the single package. Plugins are how you share a channel (an [integration](./integrations.md) plus the [workflows](./workflows.md) that route it), a set of [tools](./tools.md), or any mix of resource types between agents and across machines.
4
4
 
5
5
  > **Per-agent, not global.** Wolli has no project scope. A plugin is installed for one agent and lands in that agent's own home (`~/.wolli/agents/<name>/`). The agent name precedes every verb: `wolli <agent> plugins install <source>`.
6
6
 
7
- > **Security:** Plugins run with full host access. Extensions and integrations execute arbitrary code inside the agent process, and skills can instruct the model to take any action. Review a plugin's source before installing a third-party package.
7
+ > **Security:** Plugins run with full host access. Integrations, workflows, hooks, tools, and providers execute arbitrary code inside the agent process, and skills can instruct the model to take any action. Review a plugin's source before installing a third-party package.
8
8
 
9
9
  ## Table of Contents
10
10
 
@@ -23,13 +23,16 @@ A plugin is an npm-style package whose `package.json` carries a `"wolli"` manife
23
23
 
24
24
  A plugin is a directory (or published package) with a `package.json` whose `"wolli"` field names the contribution files. Each listed path is a normal source module loaded by the agent's resource loader at launch:
25
25
 
26
- - **integrations** — transport modules registered via `wolli.registerIntegration` (see [integrations.md](./integrations.md)).
27
- - **extensions** — agent-owned behavior modules (see [extensions.md](./extensions.md)).
26
+ - **integrations** — transport modules default-exporting `defineIntegration` (see [integrations.md](./integrations.md)).
27
+ - **workflows** — routing and automation modules default-exporting `defineWorkflow` (see [workflows.md](./workflows.md)).
28
+ - **hooks** — interception modules default-exporting `defineHook` (see [hooks.md](./hooks.md)).
29
+ - **tools** — tool modules default-exporting `defineTool` (see [tools.md](./tools.md)).
30
+ - **providers** — model provider modules default-exporting `defineProvider` (see [providers.md](./providers.md)).
28
31
  - **skills** — `SKILL.md` (or top-level `.md`) instruction files (see [skills.md](./skills.md)).
29
32
  - **prompts** — `.md` prompt templates (see [prompt-templates.md](./prompt-templates.md)).
30
33
  - **themes** — `.json` theme files (see [themes.md](./themes.md)).
31
34
 
32
- A plugin may declare any subset. The common case is a single plugin that bundles both halves of an integration — a transport under `"integrations"` and its mapping extension under `"extensions"` so they install and version as one unit; for a single agent you can instead place the two files directly in its `integrations/`/`extensions/` folders without a plugin (see [integrations.md](./integrations.md#integration-locations)). In a plugin, both resolve from the one install — the extension is **not** copied anywhere; it is loaded in place from the package (see [How Resolution Works](#how-resolution-works)).
35
+ A plugin may declare any subset. The common case is a channel plugin that bundles a transport under `"integrations"` with its routing files under `"workflows"`, so they install and version as one unit; for a single agent you can instead place the files directly in its `integrations/` and `workflows/` folders without a plugin. In a plugin, everything resolves from the one install — nothing is copied into the agent's folders; the files load in place from the package (see [How Resolution Works](#how-resolution-works)).
33
36
 
34
37
  ## Where Plugins Install
35
38
 
@@ -50,7 +53,7 @@ A plugin may declare any subset. The common case is a single plugin that bundles
50
53
  - `git:` sources are cloned to `<store>/git/<host>/<user>/<repo>`; if the clone has a `package.json`, dependencies are installed there.
51
54
  - Local sources are copied (not symlinked) to `<store>/local/<basename-slug>-<sha256-prefix>`, so the install travels even if the origin moves; dependencies install in the copy.
52
55
 
53
- The agent's discovery dirs (`~/.wolli/agents/<name>/extensions/`, `integrations/`, `skills/`, etc.) are for hand-placed local resources. Installed plugins are **not** unpacked into those dirs — they stay in `.plugins/` and are resolved from there.
56
+ The agent's discovery dirs (`~/.wolli/agents/<name>/integrations/`, `workflows/`, `tools/`, etc.) are for hand-placed local resources. Installed plugins are **not** unpacked into those dirs — they stay in `.plugins/` and are resolved from there.
54
57
 
55
58
  ## The package.json `wolli` Manifest
56
59
 
@@ -59,7 +62,10 @@ The plugin manager reads `package.json` and parses exactly the `"wolli"` object.
59
62
  | Key | Loaded as | File pattern |
60
63
  |----------------|-----------------|-------------------------------|
61
64
  | `integrations` | integration modules | `.ts` / `.js` |
62
- | `extensions` | extension modules | `.ts` / `.js` |
65
+ | `workflows` | workflow modules | `.ts` / `.js` |
66
+ | `hooks` | hook modules | `.ts` / `.js` |
67
+ | `tools` | tool modules | `.ts` / `.js` |
68
+ | `providers` | provider modules | `.ts` / `.js` |
63
69
  | `skills` | skills | `SKILL.md` / `.md` |
64
70
  | `prompts` | prompt templates | `.md` |
65
71
  | `themes` | themes | `.json` |
@@ -74,14 +80,14 @@ Example manifest (modeled on the shipped Telegram plugin; see the [worked exampl
74
80
  "type": "module",
75
81
  "wolli": {
76
82
  "integrations": ["./index.ts"],
77
- "extensions": ["./telegram-chat.ts"]
83
+ "workflows": ["./telegram-chat.ts"]
78
84
  },
79
85
  "dependencies": {
80
86
  "grammy": "1.44.0",
81
87
  "@grammyjs/runner": "2.0.3"
82
88
  },
83
89
  "peerDependencies": {
84
- "@opsyhq/wolli": "*"
90
+ "wolli": "*"
85
91
  }
86
92
  }
87
93
  ```
@@ -92,7 +98,7 @@ The simplest manifest lists one plain single-file path per key — a flat packag
92
98
  {
93
99
  "wolli": {
94
100
  "integrations": ["./index.ts"],
95
- "extensions": ["./x.ts"]
101
+ "workflows": ["./x.ts"]
96
102
  }
97
103
  }
98
104
  ```
@@ -102,39 +108,40 @@ Plain single-file path entries are **first-class**; globs and override prefixes
102
108
  **Notes:**
103
109
 
104
110
  - Paths are relative to the package root and resolved against it. An entry is one of three things:
105
- - a **plain path** — a single file (`./index.ts`) loaded as-is, or a **directory**, which is then collected for that resource type. Directories collect by the type's file pattern (`.md` for skills/prompts, `.json` for themes); for `integrations`/`extensions` the directory is collected with the same package-style discovery as a convention dir (an `index.ts`/`index.js` or nested `package.json` manifest per subdir, **not** a flat sweep of every `.ts`).
106
- - a **glob** (contains `*` or `?`, e.g. `extensions/*.ts`) — expanded against the package root, then each match collected as above.
111
+ - a **plain path** — a single file (`./index.ts`) loaded as-is, or a **directory**, which is then collected for that resource type. Directories collect by the type's file pattern (`.md` for skills/prompts, `.json` for themes); for the module types (`integrations`, `workflows`, `hooks`, `tools`, `providers`) the directory is collected with the same package-style discovery as a convention dir (an `index.ts`/`index.js` or nested `package.json` manifest per subdir, **not** a flat sweep of every `.ts`).
112
+ - a **glob** (contains `*` or `?`, e.g. `workflows/*.ts`) — expanded against the package root, then each match collected as above.
107
113
  - an **override prefix** (`!exclude`, `+force-include`, `-force-exclude`) — not a source itself; it layers on top of the paths the plain/glob entries already produced. `!` removes matches, `+` adds an exact path back even if excluded, `-` removes an exact path even if force-included.
108
114
  When an entry resolves to a directory (or a glob matches one), only files matching the resource type's pattern are picked up; a plain entry pointing straight at a single file is taken as-is, so list each file under its correct key.
109
- - If no `"wolli"` manifest is present, the manager falls back to convention directories — `extensions/`, `integrations/`, `skills/`, `prompts/`, `themes/` — and auto-discovers files there. A bare file or a manifest-less directory with no convention dirs is treated as a single extension.
110
- - Third-party runtime deps (here `grammy`; `croner` in the scheduler plugin) go in `dependencies` and are installed automatically when the plugin is fetched. `"dependencies"` is **optional** and may be omitted entirely when the transport relies only on Node globals — a transport that talks to a plain HTTP endpoint can call `fetch` directly with no bundled client (see [integrations.md › Available Imports](./integrations.md#available-imports), which states a plain-HTTP transport can use `fetch` directly and needs no bundled client). Bundling a client library is only needed for richer protocols.
115
+ - If no `"wolli"` manifest is present, the manager falls back to convention directories — `integrations/`, `workflows/`, `hooks/`, `tools/`, `providers/`, `skills/`, `prompts/`, `themes/` — and auto-discovers files there.
116
+ - Third-party runtime deps (here `grammy`; `croner` in the scheduler plugin) go in `dependencies` and are installed automatically when the plugin is fetched. `"dependencies"` is **optional** and may be omitted entirely when the transport relies only on Node globals — a transport that talks to a plain HTTP endpoint can call `fetch` directly with no bundled client. Bundling a client library is only needed for richer protocols.
111
117
 
112
- ### Why peerDependencies on `@opsyhq/wolli`
118
+ ### Why peerDependencies on `wolli`
113
119
 
114
- Contribution modules import host types and APIs from `@opsyhq/wolli` (`IntegrationsAPI`, `ExtensionFactory`, etc.). The host process *provides* that package at runtime, so the plugin must not bundle its own copy. Declare it as a peer with a `"*"` range:
120
+ Contribution modules import the host's definer helpers from `wolli` (`defineIntegration`, `defineWorkflow`, `defineHook`, `defineTool`, `defineProvider`). The host process *provides* that package at runtime, so the plugin must not bundle its own copy. Declare it as a peer with a `"*"` range:
115
121
 
116
122
  ```json
117
- { "peerDependencies": { "@opsyhq/wolli": "*" } }
123
+ { "peerDependencies": { "wolli": "*" } }
118
124
  ```
119
125
 
120
126
  Managed installs are run with peer resolution disabled (`--legacy-peer-deps` and equivalents), so the package manager does not try to install or solve this host-provided peer. The agent resolves it from the host at load time instead.
121
127
 
122
128
  ## Authoring a Plugin
123
129
 
124
- Lay the package out as a normal npm package. A dual-half integration plugin looks like:
130
+ Lay the package out as a normal npm package. A channel plugin looks like:
125
131
 
126
132
  ```
127
133
  my-plugin/
128
134
  ├── package.json # name, type: "module", "wolli" manifest, deps, peerDependencies
129
135
  ├── index.ts # the integration transport (listed under "integrations")
130
- ├── my-chat.ts # the mapping extension (listed under "extensions")
136
+ ├── my-inbound.ts # routing workflow (listed under "workflows")
137
+ ├── my-reply.ts # reply workflow (listed under "workflows")
131
138
  └── README.md
132
139
  ```
133
140
 
134
- 1. **Write the contribution files.** Author the transport half per [integrations.md](./integrations.md) and the mapping/behavior half per [extensions.md](./extensions.md). This doc does not duplicate their authoring guidance; it only packages them.
141
+ 1. **Write the contribution files.** Author the transport per [integrations.md](./integrations.md) and the routing per [workflows.md](./workflows.md). This doc does not duplicate their authoring guidance; it only packages them.
135
142
  2. **Declare them in the manifest.** List each file under the matching `"wolli"` key (above).
136
143
  3. **Set `"type": "module"`** so `.ts`/`.js` modules load as ESM.
137
- 4. **Put runtime deps in `dependencies`** and **`@opsyhq/wolli` in `peerDependencies`** with `"*"`.
144
+ 4. **Put runtime deps in `dependencies`** and **`wolli` in `peerDependencies`** with `"*"`.
138
145
 
139
146
  That is the whole contract. There is no build step or registration call beyond the manifest — the agent's resource loader imports the listed files at launch.
140
147
 
@@ -227,7 +234,7 @@ A plugin entry in settings can be a bare string (load everything) or an object t
227
234
  "npm:wolli-integration-scheduler",
228
235
  {
229
236
  "source": "git:github.com/user/repo",
230
- "extensions": ["*.ts", "!legacy.ts"],
237
+ "workflows": ["*.ts", "!legacy.ts"],
231
238
  "skills": []
232
239
  }
233
240
  ]
@@ -242,13 +249,13 @@ At each launch the resource loader calls the plugin manager's `resolve()`, which
242
249
 
243
250
  1. reads the agent's `"plugins"[]` from settings,
244
251
  2. for each source, self-heals a missing install (re-fetches if the store entry is gone but the origin still exists),
245
- 3. resolves the contributions **in place** from the install — manifest paths first, then convention dirs, then the single-extension fallback,
252
+ 3. resolves the contributions **in place** from the install — manifest paths first, then convention dirs,
246
253
  4. applies any per-entry filter and name-collision precedence,
247
- 5. hands the enabled paths to the integration loader and extension loader.
254
+ 5. hands the enabled paths to the per-type resource loaders.
248
255
 
249
- Crucially, a dual-half package's integration and its paired extension both resolve from the *same* install directory in `.plugins/`. The extension is never copied into `<agent>/extensions/`. The integration arm loads first so the producer runner exists before the extension wires `getIntegration(...)`.
256
+ Crucially, a channel package's integration and its workflows all resolve from the *same* install directory in `.plugins/`. Nothing is copied into the agent's folders. The integration arm loads first so its event descriptors exist before workflows bind them.
250
257
 
251
- Because `install`/`remove`/`update` go through the daemon (the single writer), the running agent reloads itself after the change — installed contributions become active without a manual restart. Onboarding-gated mapping extensions activate once their account is configured.
258
+ Because `install`/`remove`/`update` go through the daemon (the single writer), the running agent reloads itself after the change — installed contributions become active without a manual restart. Onboarding-gated services activate once their account is configured.
252
259
 
253
260
  ## Worked Example: Packaging the Telegram Integration
254
261
 
@@ -256,14 +263,14 @@ The shipped Telegram plugin (`packages/wolli/plugins/telegram/`) is the canonica
256
263
 
257
264
  ```
258
265
  telegram/
259
- ├── package.json # "wolli": { integrations: ["./index.ts"], extensions: ["./telegram-chat.ts"] }
260
- ├── index.ts # transport: long-polls grammY, holds the bot token, emits a `message` event,
261
- # exposes sendMessage / sendChatAction / setCommands, declares onboard
262
- └── telegram-chat.ts # mapping extension: routes each message into a per-chat Wolli session,
263
- # ships the reply back through the transport
266
+ ├── package.json # "wolli": { integrations: ["./index.ts"], workflows: ["./telegram-chat.ts"] }
267
+ ├── index.ts # transport: long-polls grammY, holds the bot token, emits a `message` event,
268
+ # exposes sendMessage / startTyping / stopTyping, declares onboard
269
+ └── telegram-chat.ts # workflows: inbound routes each message into a per-chat session by tag
270
+ # (and /commands); typing on agent_start; reply on agent_end
264
271
  ```
265
272
 
266
- Its manifest (verbatim):
273
+ Its manifest:
267
274
 
268
275
  ```json
269
276
  {
@@ -273,14 +280,14 @@ Its manifest (verbatim):
273
280
  "type": "module",
274
281
  "wolli": {
275
282
  "integrations": ["./index.ts"],
276
- "extensions": ["./telegram-chat.ts"]
283
+ "workflows": ["./telegram-chat.ts"]
277
284
  },
278
285
  "dependencies": {
279
286
  "grammy": "1.44.0",
280
287
  "@grammyjs/runner": "2.0.3"
281
288
  },
282
289
  "peerDependencies": {
283
- "@opsyhq/wolli": "*"
290
+ "wolli": "*"
284
291
  }
285
292
  }
286
293
  ```
@@ -294,6 +301,6 @@ wolli my-agent plugins install ./packages/wolli/plugins/telegram
294
301
  # -> runs onboard: prompts for the bot token, writes the account to integrations.json
295
302
  ```
296
303
 
297
- Both `index.ts` and `telegram-chat.ts` resolve from that one copy. The transport starts; once the account is configured, the mapping extension activates and bidirectional chat is live. To package it for others, publish the same directory to npm (`wolli-integration-telegram`) or a git repo and have them install with `npm:` / `git:` instead of the local path.
304
+ The transport and both workflows resolve from that one copy. The transport starts; once the account is configured, the workflows bind its events and bidirectional chat is live. To package it for others, publish the same directory to npm (`wolli-integration-telegram`) or a git repo and have them install with `npm:` / `git:` instead of the local path.
298
305
 
299
- The shipped scheduler plugin (`packages/wolli/plugins/scheduler/`) has the identical shape — `"integrations": ["./index.ts"]`, `"extensions": ["./scheduler-chat.ts"]`, one runtime dep (`croner`), and the same `@opsyhq/wolli` peer — confirming the dual-half pattern is the convention, not Telegram-specific. The only manifest differences are the package name, the dependency, and the extension filename.
306
+ The shipped scheduler plugin has the same shape with one addition a transport under `"integrations"`, its `due` routing under `"workflows"`, the agent-facing `cron` tool under `"tools"`, one runtime dep (`croner`), and the same `wolli` peer — confirming the channel pattern is the convention, not Telegram-specific. The only differences are the package name, the dependency, the workflow/tool filenames, and that a scheduler ships a tool the model calls rather than typing indicators.
@@ -0,0 +1,63 @@
1
+ # Providers
2
+
3
+ Add model providers the agent can run on: a proxy, a self-hosted endpoint, or an API wolli does not ship.
4
+
5
+ A provider registers models the agent can run on, beyond the built-in catalog. Use one when the models you want live behind a proxy, a self-hosted endpoint, or a provider wolli does not ship; built-in models need no provider file. Each provider is one file under `providers/` in the agent home, default-exporting `defineProvider`. The provider name comes from the filename. wolli loads providers once at startup; there is no dynamic registration.
6
+
7
+ ## Defining a provider
8
+
9
+ A proxy that fronts the Anthropic API and serves one model:
10
+
11
+ `~/.wolli/agents/assistant/providers/my-proxy.ts`
12
+
13
+ ```ts
14
+ import { defineProvider } from "wolli";
15
+
16
+ export default defineProvider({
17
+ baseUrl: "https://proxy.example.com",
18
+ apiKey: "$PROXY_API_KEY",
19
+ api: "anthropic-messages",
20
+ models: [
21
+ {
22
+ id: "claude-sonnet-4-20250514",
23
+ name: "Claude 4 Sonnet (proxy)",
24
+ reasoning: false,
25
+ input: ["text", "image"],
26
+ // Per-token cost, used for usage tracking; 0 is fine for a proxy.
27
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
28
+ contextWindow: 200000,
29
+ maxTokens: 16384,
30
+ },
31
+ ],
32
+ });
33
+ ```
34
+
35
+ When `models` is set, it replaces any existing models for that provider name, and `baseUrl`, `apiKey`, and `api` become required (`api` at the provider or model level). Each model carries its own `contextWindow` and `maxTokens`, so wolli sizes context and output correctly even when the endpoint is yours.
36
+
37
+ ## Credentials
38
+
39
+ `apiKey` accepts three forms. `$PROXY_API_KEY` or `${PROXY_API_KEY}` reads an environment variable, a leading `!` runs a shell command and uses its output (`"!op read op://vault/proxy-key"`), and any other string passes through as a literal key. Do not put raw keys in provider files; reference the environment or a secret manager command instead.
40
+
41
+ For endpoints that expect extra request headers, set `headers`; `authHeader: true` adds an `Authorization: Bearer` header carrying the resolved key.
42
+
43
+ ## Overriding an existing provider
44
+
45
+ A file that sets only `baseUrl` redirects an existing provider instead of defining a new one. The filename names the target: `providers/anthropic.ts` overrides the `anthropic` provider, and its built-in models keep their ids, costs, and limits while requests go to the new endpoint. Use this to route traffic through a gateway or a regional mirror.
46
+
47
+ `~/.wolli/agents/assistant/providers/anthropic.ts`
48
+
49
+ ```ts
50
+ import { defineProvider } from "wolli";
51
+
52
+ export default defineProvider({ baseUrl: "https://proxy.example.com" });
53
+ ```
54
+
55
+ ## OAuth providers
56
+
57
+ A provider with an `oauth` block authenticates through a login flow instead of a static key, which is what lets you sign in to a corporate gateway with SSO rather than pasting a token. The block gives the provider a display name for the login UI and three functions: `login` runs the flow and returns credentials to persist, `refreshToken` renews them when they expire, and `getApiKey` converts the stored credentials into the key sent on each request. When `oauth` is present, `apiKey` is optional.
58
+
59
+ ## What to read next
60
+
61
+ - [Introduction](./introduction.md): the agent home and how capability folders load.
62
+ - [Workflows](./workflows.md): route events into sessions and automate the agent.
63
+ - [Tools](./tools.md): author tools the agent calls during a turn.