@zibby/skills 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/jira.d.ts +26 -1
- package/dist/jira.js +8 -8
- package/dist/package.json +4 -4
- package/dist/trackers/index.js +1 -1
- package/dist/trackers/jira-adapter.js +5 -5
- package/docs/templates/catalog-metadata.md +101 -0
- package/docs/templates/composition.md +97 -0
- package/docs/templates/deploy-time-config.md +228 -0
- package/docs/templates/index.md +138 -0
- package/docs/templates/models.md +157 -0
- package/docs/templates/node-declarations.md +149 -0
- package/docs/templates/remote-mcp.md +105 -0
- package/docs/templates/requires.md +183 -0
- package/docs/templates/sidecars.md +163 -0
- package/docs/templates/stores.md +113 -0
- package/docs/templates/surfaces.md +184 -0
- package/docs/templates/update-behavior.md +108 -0
- package/package.json +4 -4
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
title: models
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `models`
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
At deploy the platform has to decide two things: which model credentials to ask
|
|
11
|
+
you for, and whether a missing one should block the install. `models` is the
|
|
12
|
+
declaration those decisions are computed from.
|
|
13
|
+
|
|
14
|
+
Each block says **what kind** of model, **which node** it belongs to, **where**
|
|
15
|
+
the key is consumed, and **whether you supply it**. The deploy dialog and the
|
|
16
|
+
model disc on the agent's graph are generic renderings of this array — there is
|
|
17
|
+
no per-agent special case anywhere.
|
|
18
|
+
|
|
19
|
+
## Syntax
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
spec: {
|
|
23
|
+
models: [
|
|
24
|
+
{
|
|
25
|
+
kind: 'llm', // 'llm' | 'embedding'
|
|
26
|
+
target: 'agent', // 'agent' | 'sidecar'
|
|
27
|
+
role: 'review', // the graph NODE this block belongs to
|
|
28
|
+
label: 'Review', // the row heading in the deploy dialog
|
|
29
|
+
byok: true, // true → a real picker; false → a read-only row
|
|
30
|
+
options: ['claude', 'codex', 'gemini'],
|
|
31
|
+
required: true, // does a missing key block the deploy?
|
|
32
|
+
note: 'Reads the diff and writes the review.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
kind: 'llm', target: 'agent', role: 'triage', label: 'Triage',
|
|
36
|
+
byok: false, required: true,
|
|
37
|
+
followsVendorOf: 'review', // tracks another block's vendor
|
|
38
|
+
fixedModelByVendor: { // …and shows what it then runs
|
|
39
|
+
claude: 'haiku-4.5',
|
|
40
|
+
codex: 'gpt-4o-mini',
|
|
41
|
+
gemini: 'gemini-3-flash',
|
|
42
|
+
},
|
|
43
|
+
note: 'Fixed — a cheap fast-tier model decides how much review depth the change needs.',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Properties
|
|
50
|
+
|
|
51
|
+
| Name | Type | Required | Allowed values | Default | Update behavior |
|
|
52
|
+
|---|---|---|---|---|---|
|
|
53
|
+
| `kind` | string | No | `llm`, `embedding` | `llm` | Re-applied on the next deploy |
|
|
54
|
+
| `target` | string | No | `agent` (the run container), `sidecar` (a service reads it per request) | `agent` | Re-applied |
|
|
55
|
+
| `role` | string | No | a node id in this agent's graph | none | Re-applied |
|
|
56
|
+
| `label` | string | No | any | the vendor name | Card only |
|
|
57
|
+
| `byok` | boolean | No | `true`, `false` | `true` | Re-applied |
|
|
58
|
+
| `provider` | string | No | e.g. `openai` | none | Re-applied |
|
|
59
|
+
| `options` | string[] | No | vendor ids for `kind: 'llm'`; **model ids** for `kind: 'embedding'` | `[]` | Re-applied |
|
|
60
|
+
| `default` | string | No | one of `options` | none | Re-applied |
|
|
61
|
+
| `required` | boolean | No | `true`, `false` | `true` | Re-applied — **this is the deploy gate** |
|
|
62
|
+
| `followsVendorOf` | string | No | another block's `role` | none | Re-applied |
|
|
63
|
+
| `fixedModelByVendor` | object | No | `{ vendor: modelId }` | none | Re-applied |
|
|
64
|
+
| `note` | string | No | one honest sentence | none | Card only |
|
|
65
|
+
|
|
66
|
+
## The three decisions
|
|
67
|
+
|
|
68
|
+
**`target` — where the key is consumed.**
|
|
69
|
+
`agent` puts it in the run container's environment. `sidecar` routes it to a
|
|
70
|
+
service container that reads it **per request**, from the declaring agent's own
|
|
71
|
+
encrypted Env bag. One shared service, many isolated tenants.
|
|
72
|
+
|
|
73
|
+
**`byok` — what the dialog renders.**
|
|
74
|
+
`true` gives you a real picker that collects a key. `false` gives you an honest,
|
|
75
|
+
read-only line describing what actually runs. It is never a text box that does
|
|
76
|
+
nothing.
|
|
77
|
+
|
|
78
|
+
**`required` — whether a missing key blocks the install.**
|
|
79
|
+
`required: false` is how an agent says "I need no AI key". Use it for a block
|
|
80
|
+
that names an identity rather than adding a dependency — a deterministic node
|
|
81
|
+
that never invokes a model.
|
|
82
|
+
|
|
83
|
+
## Use cases
|
|
84
|
+
|
|
85
|
+
| You want | Declare |
|
|
86
|
+
|---|---|
|
|
87
|
+
| An ordinary bring-your-own-key agent | nothing — it is synthesised for you |
|
|
88
|
+
| An agent that runs no model at all | `models: []` |
|
|
89
|
+
| A different model per node, chosen by the operator | one block per model-running node, each with its own `role` |
|
|
90
|
+
| A cheap fixed tier behind an operator-chosen one | `byok: false` + `followsVendorOf` + `fixedModelByVendor` |
|
|
91
|
+
| An embedding model used by a knowledge-base service | `kind: 'embedding', target: 'sidecar', provider: 'openai', options: [<model ids>]` |
|
|
92
|
+
| A locked identity badge with no key | `byok: false, required: false` |
|
|
93
|
+
|
|
94
|
+
### Example — one block per model-running node
|
|
95
|
+
|
|
96
|
+
The shipped **Product Owner** agent runs three separate judgements, each on its
|
|
97
|
+
own node, each with its own picker:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
models: [
|
|
101
|
+
{ kind: 'llm', target: 'agent', role: 'plan', label: 'Planner',
|
|
102
|
+
byok: true, options: ['claude','codex','gemini'], required: true,
|
|
103
|
+
note: 'Reads the PRD and the repository, then writes the tickets.' },
|
|
104
|
+
{ kind: 'llm', target: 'agent', role: 'review', label: 'Plan reviewer',
|
|
105
|
+
byok: true, options: ['claude','codex','gemini'], required: true,
|
|
106
|
+
note: 'The second opinion that fails a weak plan back to the planner.' },
|
|
107
|
+
{ kind: 'llm', target: 'agent', role: 'judge', label: 'Acceptance judge',
|
|
108
|
+
byok: true, options: ['claude','codex','gemini'], required: true,
|
|
109
|
+
note: 'Judges a pull request against the ticket\'s acceptance criteria.' },
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Example — an embedding model consumed by a service
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
models: [{
|
|
117
|
+
kind: 'embedding',
|
|
118
|
+
role: 'kb',
|
|
119
|
+
target: 'sidecar',
|
|
120
|
+
byok: true,
|
|
121
|
+
provider: 'openai',
|
|
122
|
+
options: ['text-embedding-3-small', 'text-embedding-3-large'],
|
|
123
|
+
default: 'text-embedding-3-small',
|
|
124
|
+
required: true,
|
|
125
|
+
}]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Gotchas
|
|
129
|
+
|
|
130
|
+
**`options` means different things per `kind`.** For `kind: 'llm'` they are
|
|
131
|
+
vendor ids. For `kind: 'embedding'` they are model ids, and the picker offers
|
|
132
|
+
them directly.
|
|
133
|
+
|
|
134
|
+
**`role` must match a node id exactly.** A block whose `role` names no node
|
|
135
|
+
binds to nothing: the pinned tier stops applying, and the node renders a free
|
|
136
|
+
picker whose choice the node's own code then ignores. If your node ids live in
|
|
137
|
+
a constant, import it — do not retype the string.
|
|
138
|
+
|
|
139
|
+
**A composite agent collects its children's keys too.** If your agent dispatches
|
|
140
|
+
another agent, that child's model blocks are appended to yours automatically,
|
|
141
|
+
marked non-blocking and labelled with whose they are. You do not declare them.
|
|
142
|
+
|
|
143
|
+
**Probe a model id before you declare it.** An id that does not exist makes the
|
|
144
|
+
vendor's API reject the call, and the turn comes back empty rather than with an
|
|
145
|
+
error.
|
|
146
|
+
|
|
147
|
+
**Credentials are one per vendor.** The key in the "Codex · OpenAI" slot is the
|
|
148
|
+
same key OpenAI embeddings use. At run time the order is: a key supplied for
|
|
149
|
+
this agent, then the project's credential for that vendor, then (self-hosted
|
|
150
|
+
only) the box's own environment. Nobody should ever paste the same key twice.
|
|
151
|
+
|
|
152
|
+
## See also
|
|
153
|
+
|
|
154
|
+
- [Node declarations](./node-declarations.md) — a node's `agent:` pin is what
|
|
155
|
+
*locks* a vendor; `models` describes it.
|
|
156
|
+
- [Sidecars](./sidecars.md) — `target: 'sidecar'` needs a service container.
|
|
157
|
+
- [Update behavior](./update-behavior.md)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 10
|
|
3
|
+
title: Node declarations
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Node declarations
|
|
7
|
+
|
|
8
|
+
Some declarations live on a **graph node**, not in the `spec` block,
|
|
9
|
+
because they are about what that node needs.
|
|
10
|
+
|
|
11
|
+
| Field | Declares |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `skills` | the tools this node gets, and the integrations the agent requires |
|
|
14
|
+
| `optionalSkills` | integrations that improve the node but never block a deploy |
|
|
15
|
+
| `agent` | a vendor this node is locked to |
|
|
16
|
+
| `stores` | durable storage — see [`stores`](./stores.md) |
|
|
17
|
+
| `dispatchesWorkflow` | a member agent — see [Composition](./composition.md) |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## `skills` and `optionalSkills`
|
|
22
|
+
|
|
23
|
+
### What it is
|
|
24
|
+
|
|
25
|
+
A node's single "what this node needs" list. It drives four things at once:
|
|
26
|
+
|
|
27
|
+
1. the MCP tools the model gets **at that node**;
|
|
28
|
+
2. the skill's prompt fragment, appended to the node's prompt;
|
|
29
|
+
3. the required/optional integrations shown on the card and enforced at deploy;
|
|
30
|
+
4. for a no-connection skill, the on/off switch in the agent's Settings.
|
|
31
|
+
|
|
32
|
+
### Syntax
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import { SKILLS } from '@zibby/core';
|
|
36
|
+
|
|
37
|
+
graph.addNode('review', {
|
|
38
|
+
prompt: …,
|
|
39
|
+
outputSchema: Review,
|
|
40
|
+
skills: [SKILLS.GITHUB, SKILLS.ARTIFACT],
|
|
41
|
+
optionalSkills: [SKILLS.SENTRY],
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Required vs optional
|
|
46
|
+
|
|
47
|
+
| Declared in | Surfaces as | Blocks Deploy? |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `skills`, and the skill needs a connection | **required** | **Yes** — the Deploy button is blocked until it is connected |
|
|
50
|
+
| `skills`, and the skill is marked optional | optional | No |
|
|
51
|
+
| `optionalSkills` | optional | **Never** |
|
|
52
|
+
| the same skill in **both** arrays on one node | optional **for that node only** | No |
|
|
53
|
+
|
|
54
|
+
That last row is the useful one. A skill can be required by one node and merely
|
|
55
|
+
nice-to-have on another; listing it in a node's `optionalSkills` demotes it for
|
|
56
|
+
**that node** and cannot cancel a different node's requirement.
|
|
57
|
+
|
|
58
|
+
The card shows required and optional in separate groups — "Connect" versus
|
|
59
|
+
"Connect (optional)" — and only the required group can stop an install.
|
|
60
|
+
|
|
61
|
+
### One-of groups
|
|
62
|
+
|
|
63
|
+
Some entries in the array are not skills at all: they are markers that resolve
|
|
64
|
+
to *any one of* a set of providers.
|
|
65
|
+
|
|
66
|
+
| Marker | Satisfied by |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `board_tracker` | Jira **or** Vikunja |
|
|
69
|
+
| `doc_source` | Google Docs **or** Notion **or** Lark Docs |
|
|
70
|
+
| `chat_notify` | Slack **or** Lark |
|
|
71
|
+
|
|
72
|
+
Connect any member and the requirement is met. Entries the platform does not
|
|
73
|
+
recognise are skipped with a warning, so an unknown id costs you nothing.
|
|
74
|
+
|
|
75
|
+
### Toggleable skills
|
|
76
|
+
|
|
77
|
+
A few skills need no connection at all — codebase memory, code scanning,
|
|
78
|
+
artifacts. They render as a simple on/off switch, default on, and the operator
|
|
79
|
+
can turn them off per agent.
|
|
80
|
+
|
|
81
|
+
:::danger Bind a toggleable skill on `skills`, not `optionalSkills`
|
|
82
|
+
The switch will appear either way — but **the tools will not**. Skills reach the
|
|
83
|
+
model only through a node's `skills`; `optionalSkills` is an integration-surface
|
|
84
|
+
declaration that the engine never reads. Put a toggleable skill in
|
|
85
|
+
`optionalSkills` and you ship a switch that controls nothing.
|
|
86
|
+
:::
|
|
87
|
+
|
|
88
|
+
### Gotchas
|
|
89
|
+
|
|
90
|
+
**`optionalSkills` is not part of the serialised graph.** It is read from your
|
|
91
|
+
source when the catalog is built, so it is visible on the card — but anything
|
|
92
|
+
that re-derives from a stored graph will not see it. Treat it as a card-level
|
|
93
|
+
declaration, never as a runtime one.
|
|
94
|
+
|
|
95
|
+
**A store skill is a precondition for [`stores`](./stores.md).** Without it, a
|
|
96
|
+
`stores` block on that node provisions nothing, silently.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## `agent` — pinning a node to a vendor
|
|
101
|
+
|
|
102
|
+
### What it is
|
|
103
|
+
|
|
104
|
+
Locks one node to one coding-agent vendor, regardless of what the operator picks
|
|
105
|
+
elsewhere.
|
|
106
|
+
|
|
107
|
+
### Syntax
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
graph.addNode('plan', { prompt, outputSchema: Plan, agent: 'claude' });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Properties
|
|
114
|
+
|
|
115
|
+
| Name | Type | Required | Allowed values | Default | Update behavior |
|
|
116
|
+
|---|---|---|---|---|---|
|
|
117
|
+
| `agent` | string | No | `claude`, `codex`, `gemini` | unpinned — the operator's choice applies | Frozen into the published graph; the credential slots are re-applied |
|
|
118
|
+
|
|
119
|
+
### What it does at deploy
|
|
120
|
+
|
|
121
|
+
The distinct set of pins across your graph becomes the agent's vendor
|
|
122
|
+
requirement:
|
|
123
|
+
|
|
124
|
+
| Pins found | Deploy asks for |
|
|
125
|
+
|---|---|
|
|
126
|
+
| none | any vendor — the picker offers all of them |
|
|
127
|
+
| one | that vendor only |
|
|
128
|
+
| several | one credential per vendor |
|
|
129
|
+
|
|
130
|
+
Children you dispatch contribute their pins too, so a parent on Claude that
|
|
131
|
+
dispatches a Codex child collects both keys.
|
|
132
|
+
|
|
133
|
+
### Gotchas
|
|
134
|
+
|
|
135
|
+
**A pinned node is invisible to "apply this model to every node".** It is
|
|
136
|
+
*locked* — that is the point — so it never receives an operator's model choice.
|
|
137
|
+
If every model-running node is locked, there is nothing left for the operator to
|
|
138
|
+
pick.
|
|
139
|
+
|
|
140
|
+
**There is no node-level `model:` declaration.** A model comes from one of three
|
|
141
|
+
places: the operator's per-node pick, a literal in your node's own code, or —
|
|
142
|
+
best — [`models[].fixedModelByVendor`](./models.md) joined to the node by
|
|
143
|
+
`role`. Only the third is visible on the card and the canvas. Prefer it.
|
|
144
|
+
|
|
145
|
+
## See also
|
|
146
|
+
|
|
147
|
+
- [`models`](./models.md), [`stores`](./stores.md),
|
|
148
|
+
[Composition](./composition.md)
|
|
149
|
+
- [Skills](../concepts/skills.md)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 8
|
|
3
|
+
title: remoteMcp
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `remoteMcp`
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
The hosted, third-party MCP servers this agent is **for**. Declaring one means a
|
|
11
|
+
customer who installs the card never has to know an API call exists: deploy
|
|
12
|
+
binds the server onto the agent, the owner authorises it once from the agent's
|
|
13
|
+
page, and the platform holds the grant encrypted from then on.
|
|
14
|
+
|
|
15
|
+
The declaration is pure data — a name, the server's own public URL, and which
|
|
16
|
+
authentication **the server** uses. It can never carry a credential.
|
|
17
|
+
|
|
18
|
+
## Syntax
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
spec: {
|
|
22
|
+
remoteMcp: [{
|
|
23
|
+
name: 'figma',
|
|
24
|
+
url: 'https://mcp.figma.com/mcp',
|
|
25
|
+
auth: 'oauth',
|
|
26
|
+
}],
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Properties
|
|
31
|
+
|
|
32
|
+
| Name | Type | Required | Allowed values | Default | Update behavior |
|
|
33
|
+
|---|---|---|---|---|---|
|
|
34
|
+
| `name` | string | **Yes** | any — this is the handle `entryPoints.connect.server` refers to | — | Re-applied (matched by URL) |
|
|
35
|
+
| `url` | string | **Yes** | must parse; credential-shaped query parameters are stripped | — | Re-applied |
|
|
36
|
+
| `auth` | string | No | `oauth`, `bearer` — **anything else drops the whole entry** | absent | Re-applied |
|
|
37
|
+
|
|
38
|
+
`bearer` and "no `auth`" produce the same stored entry, on purpose: a template
|
|
39
|
+
carries no token, so there is nothing to distinguish.
|
|
40
|
+
|
|
41
|
+
## What deploy does
|
|
42
|
+
|
|
43
|
+
**Without `auth: 'oauth'`** — the server is bound onto the agent immediately.
|
|
44
|
+
|
|
45
|
+
**With `auth: 'oauth'`** — deploy binds **nothing**. The agent carries the
|
|
46
|
+
declaration; the server entry is created **when the sign-in completes**. A
|
|
47
|
+
freshly deployed agent therefore shows no server under its custom MCP list,
|
|
48
|
+
because nothing is connected yet — that list holds exactly what is connected.
|
|
49
|
+
|
|
50
|
+
Binding is **idempotent by URL and additive**. Re-deploying keeps the existing
|
|
51
|
+
entry byte-for-byte, which is what keeps the OAuth grant intact. Removing the
|
|
52
|
+
declaration never removes a server somebody connected — that stays an explicit
|
|
53
|
+
action.
|
|
54
|
+
|
|
55
|
+
## Pairing it with a Connect card
|
|
56
|
+
|
|
57
|
+
`remoteMcp` says *what the server is*. [`entryPoints.connect`](./surfaces.md)
|
|
58
|
+
says *what the sign-in looks like*, and refers to the server by **name**:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
spec: {
|
|
62
|
+
remoteMcp: [{ name: 'figma', url: 'https://mcp.figma.com/mcp', auth: 'oauth' }],
|
|
63
|
+
surfaces: ['mcp', 'connect'],
|
|
64
|
+
entryPoints: {
|
|
65
|
+
connect: {
|
|
66
|
+
kind: 'oauth',
|
|
67
|
+
server: 'figma',
|
|
68
|
+
label: 'Connect your Figma account',
|
|
69
|
+
button: 'Connect',
|
|
70
|
+
buttonDisconnect: 'Disconnect',
|
|
71
|
+
connectedLine: 'Connected to Figma as {account}.',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Because the card comes from the declaration rather than from a live server row,
|
|
78
|
+
the Connect button is on the page from the moment you deploy and stays there
|
|
79
|
+
whatever happens to the connection.
|
|
80
|
+
|
|
81
|
+
## Gotchas
|
|
82
|
+
|
|
83
|
+
:::warning The identity is the owner's
|
|
84
|
+
An agent runs with its own role and resolves nothing about whoever is talking to
|
|
85
|
+
it. Anyone who can reach an authorised agent's chat surface acts with the
|
|
86
|
+
owner's third-party account. Say so in your card copy, and do not put an
|
|
87
|
+
authorised agent in a shared channel.
|
|
88
|
+
:::
|
|
89
|
+
|
|
90
|
+
**Never give one agent two MCP addresses.** An agent that both declares a remote
|
|
91
|
+
server *and* hosts its own MCP sidecar will have the sidecar win the address and
|
|
92
|
+
answer every call with its own "missing token", while the OAuth grant sits
|
|
93
|
+
unused on the other one. One card, one address, one sign-in.
|
|
94
|
+
|
|
95
|
+
**Credential-shaped query parameters are stripped from the URL** before it is
|
|
96
|
+
stored. A template must never carry a credential — and the strip is also what
|
|
97
|
+
stops a duplicate server being attached on every re-deploy.
|
|
98
|
+
|
|
99
|
+
**Servers are matched by URL, not by name**, because the owner may rename an
|
|
100
|
+
entry on their own page.
|
|
101
|
+
|
|
102
|
+
## See also
|
|
103
|
+
|
|
104
|
+
- [`surfaces` and `entryPoints`](./surfaces.md)
|
|
105
|
+
- [`requires`](./requires.md) — the same shape, for one of your own agents.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 4
|
|
3
|
+
title: requires
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `requires`
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
One agent needs a capability another agent publishes — a browser, a knowledge
|
|
11
|
+
base, a bridge to an internal API. `requires` declares that need by **name**.
|
|
12
|
+
|
|
13
|
+
At deploy the platform installs the provider as one of **this agent's own
|
|
14
|
+
members** — the same cascade, the same binding record and the same delete
|
|
15
|
+
cascade a sub-graph child gets — and then attaches the provider's endpoint to
|
|
16
|
+
your agent as a managed MCP server.
|
|
17
|
+
|
|
18
|
+
Nothing concrete is written in the template: no id, no URL, no credential.
|
|
19
|
+
|
|
20
|
+
## There is no field to put a token in
|
|
21
|
+
|
|
22
|
+
This is the part people look for and do not find, so it is worth stating plainly:
|
|
23
|
+
|
|
24
|
+
- **A consumer declares WHO it uses.** Board Autopilot declares
|
|
25
|
+
`requires: [{ ref: 'endpoint:gbrain-kb/mcp', as: 'kb' }]` — and nothing else.
|
|
26
|
+
- **A provider declares WHAT IT NEEDS.** The knowledge base declares its own
|
|
27
|
+
embedding model, in its own template.
|
|
28
|
+
- **The credential that lets one call the other is in neither declaration.** The
|
|
29
|
+
platform mints it, encrypts it into the consumer's own environment, and never
|
|
30
|
+
returns it, logs it, or shows it to a model.
|
|
31
|
+
|
|
32
|
+
So there is no token field, no shared secret to distribute and nothing for you
|
|
33
|
+
to rotate. If you find yourself wanting to write a credential into a template,
|
|
34
|
+
the design has gone wrong somewhere — a declaration is public data that travels
|
|
35
|
+
with the card.
|
|
36
|
+
|
|
37
|
+
:::info Your agent installs its own members
|
|
38
|
+
A consumer never borrows a provider that happens to already exist in the
|
|
39
|
+
project. It installs its own, always. The heavy engine underneath is shared per
|
|
40
|
+
box anyway, so the extra agent row is just configuration.
|
|
41
|
+
:::
|
|
42
|
+
|
|
43
|
+
## Syntax
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
spec: {
|
|
47
|
+
requires: [{ ref: 'endpoint:browser/mcp', as: 'browser' }],
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A bare string works too — `as` then defaults to the provider's slug:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
requires: ['endpoint:browser/mcp'],
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Properties
|
|
58
|
+
|
|
59
|
+
| Name | Type | Required | Allowed values | Default | Update behavior |
|
|
60
|
+
|---|---|---|---|---|---|
|
|
61
|
+
| `ref` | string | **Yes** | `<kind>:<provider-slug>/<entry-name>`. All lowercase, letters/digits/hyphens; each part must start with a letter or digit. **`endpoint` is the only kind today** | — | Re-applied. The member is installed and the endpoint attached, idempotently |
|
|
62
|
+
| `as` | string | No | letters, digits, `-`, `_`; must start with a letter or digit | the provider slug | Re-applied. This is the handle your code looks the link up by |
|
|
63
|
+
|
|
64
|
+
`<entry-name>` is a surface the provider **publishes** — `mcp` for an agent that
|
|
65
|
+
serves an MCP endpoint.
|
|
66
|
+
|
|
67
|
+
## What deploy does
|
|
68
|
+
|
|
69
|
+
1. Adds the provider to this agent's member list, alongside any sub-graph
|
|
70
|
+
children.
|
|
71
|
+
2. Installs it (recursively — its own members come too).
|
|
72
|
+
3. Records the binding on your agent.
|
|
73
|
+
4. Attaches the bound provider's endpoint as a managed MCP server, tagged with
|
|
74
|
+
your alias.
|
|
75
|
+
|
|
76
|
+
Re-deploying is safe. An existing link is kept as-is; a stale one is replaced;
|
|
77
|
+
a link you added by hand to the same provider is adopted rather than duplicated.
|
|
78
|
+
|
|
79
|
+
## The two ways to consume it
|
|
80
|
+
|
|
81
|
+
This is the part worth reading twice. Both shipped examples are mechanically
|
|
82
|
+
identical to the platform — the attached endpoint is reachable from every node's
|
|
83
|
+
model either way. **What differs is the choice your template makes.**
|
|
84
|
+
|
|
85
|
+
| | **A. Model-driven** | **B. Code-driven** |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Who calls the endpoint | the model, by calling its tools | your node's own code |
|
|
88
|
+
| What the node does to resolve it | nothing | looks the link up by its alias |
|
|
89
|
+
| What the model sees | tools in its tool list | nothing — just text in the prompt |
|
|
90
|
+
| When it is missing | the model is told to skip and say why | one log line, an empty block |
|
|
91
|
+
| Reach for it when | the work is open-ended | the retrieval must happen every run |
|
|
92
|
+
|
|
93
|
+
### A. Model-driven — the model calls the tools
|
|
94
|
+
|
|
95
|
+
The **Frontend Specialist** agent requires a browser. Its QA node does not
|
|
96
|
+
resolve anything: it simply prompts, and the model discovers `browser_navigate`,
|
|
97
|
+
`browser_take_screenshot` and the rest in its tool list.
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
// the declaration
|
|
101
|
+
requires: [{ ref: 'endpoint:browser/mcp', as: 'browser' }],
|
|
102
|
+
|
|
103
|
+
// the node — no MCP wiring at all
|
|
104
|
+
const out = await invokeAgent(prompt, { state });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The prompt is written to degrade honestly:
|
|
108
|
+
|
|
109
|
+
> If you do **not** have browser tools, or the preview is unreachable: do not
|
|
110
|
+
> fabricate anything and do not mark checks passed. Set `skippedReason`
|
|
111
|
+
> explaining exactly what was missing.
|
|
112
|
+
|
|
113
|
+
So with no browser attached the node returns `qaRan: false` plus an honest
|
|
114
|
+
reason, and the run still completes.
|
|
115
|
+
|
|
116
|
+
**Choose this** when the work is open-ended and the model should decide how many
|
|
117
|
+
calls to make.
|
|
118
|
+
|
|
119
|
+
### B. Code-driven — your code calls it, the model never sees a tool
|
|
120
|
+
|
|
121
|
+
The **Board Autopilot** manager requires a knowledge base. Its `observe` node
|
|
122
|
+
retrieves the top few relevant records *in code* and splices the text into the
|
|
123
|
+
briefing. The model is never told a KB exists.
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
const KB_REQUIRE_AS = 'kb'; // must equal the `as` above
|
|
127
|
+
const KB_MANAGED_BY = `requires:${KB_REQUIRE_AS}`;
|
|
128
|
+
|
|
129
|
+
// 1. read this agent's own row
|
|
130
|
+
// GET {api}/projects/{PROJECT_ID}/workflows/{WORKFLOW_TYPE}
|
|
131
|
+
// 2. find the link the platform attached under our alias
|
|
132
|
+
const entry = (row.customMcp || [])
|
|
133
|
+
.find((e) => e && e.managedBy === KB_MANAGED_BY && e.id && e.url);
|
|
134
|
+
|
|
135
|
+
// 3. call it through the broker
|
|
136
|
+
// POST {api}/mcp/broker/{WORKFLOW_UUID}/{entry.id} → tools/call
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If the link is missing, the node logs once, returns an empty block, and the tick
|
|
140
|
+
carries on.
|
|
141
|
+
|
|
142
|
+
**Choose this** when the retrieval is deterministic, must happen on every run,
|
|
143
|
+
and the model must not be able to skip it or spend a turn deciding to.
|
|
144
|
+
|
|
145
|
+
:::tip Pin the alias with a test
|
|
146
|
+
A code-driven consumer looks the link up by the literal
|
|
147
|
+
`requires:<alias>`. Assert in a test that the constant in your node and the `as`
|
|
148
|
+
in your `spec` block are the same string — nothing else checks it.
|
|
149
|
+
:::
|
|
150
|
+
|
|
151
|
+
## Gotchas
|
|
152
|
+
|
|
153
|
+
**A malformed `ref` is dropped silently.** A wrong kind, a capital letter or an
|
|
154
|
+
underscore inside a segment makes the entry vanish at normalisation, with no
|
|
155
|
+
diagnostic anywhere — the agent deploys without its capability. Copy a working
|
|
156
|
+
ref; do not retype one.
|
|
157
|
+
|
|
158
|
+
**A bad `as` degrades to the provider slug** rather than failing. That then
|
|
159
|
+
breaks a code-driven consumer looking for a different alias.
|
|
160
|
+
|
|
161
|
+
**The provider has to publish the entry you name.** If it does not, the deploy
|
|
162
|
+
fails with a message naming the ref.
|
|
163
|
+
|
|
164
|
+
**Dangling refs fail the deploy loudly.** Provider not bound, endpoint not
|
|
165
|
+
published, link not buildable — each returns an error naming the ref and telling
|
|
166
|
+
you to fix it and re-deploy. The cascade is idempotent, so re-deploying
|
|
167
|
+
re-attaches.
|
|
168
|
+
|
|
169
|
+
**At run time nothing throws.** Both patterns degrade: an honest skip, or an
|
|
170
|
+
empty block. That is deliberate — a missing capability should not lose the run.
|
|
171
|
+
|
|
172
|
+
:::caution Self-hosted only, today
|
|
173
|
+
`requires` attaches one agent to another over the per-agent MCP endpoint, and
|
|
174
|
+
that endpoint exists on self-hosted boxes only. A template with a `requires`
|
|
175
|
+
block cannot finish deploying on Zibby Cloud yet.
|
|
176
|
+
:::
|
|
177
|
+
|
|
178
|
+
## See also
|
|
179
|
+
|
|
180
|
+
- [`surfaces` and `entryPoints`](./surfaces.md) — what a provider must publish.
|
|
181
|
+
- [Composition](./composition.md) — the other kind of member; the two share one
|
|
182
|
+
member list.
|
|
183
|
+
- [`remoteMcp`](./remote-mcp.md) — the same idea for a third-party server.
|