@zibby/skills 0.1.96 → 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.
- package/README.md +1 -1
- package/dist/artifact.d.ts +1 -115
- package/dist/browser.d.ts +10 -19
- package/dist/chartRender.d.ts +46 -57
- package/dist/chartRender.js +1 -1
- package/dist/chat-memory.d.ts +26 -330
- package/dist/chat-notify.d.ts +30 -409
- package/dist/chat-notify.js +3 -3
- package/dist/chatProgress.d.ts +28 -47
- package/dist/code-scan.d.ts +52 -58
- package/dist/codeStats.d.ts +36 -136
- package/dist/codeStats.js +1 -1
- package/dist/codebaseMemory.d.ts +52 -32
- package/dist/codebaseMemory.js +2 -2
- package/dist/core-tools.d.ts +10 -131
- package/dist/datasetStore.d.ts +52 -326
- package/dist/discord.d.ts +32 -68
- package/dist/figma.d.ts +5 -408
- package/dist/function-skill.d.ts +23 -135
- package/dist/function-skill.js +1 -1
- package/dist/gbrain.d.ts +46 -114
- package/dist/git-write.d.ts +45 -137
- package/dist/git-write.js +6 -6
- package/dist/git.d.ts +2 -73
- package/dist/github.d.ts +2 -1202
- package/dist/gitlab.d.ts +55 -1059
- package/dist/gitlab.js +2 -2
- package/dist/googleDocs.d.ts +39 -175
- package/dist/hubspot.d.ts +1 -381
- package/dist/index.d.ts +19 -12
- package/dist/index.js +135 -135
- package/dist/integrations.d.ts +2 -2
- package/dist/jira.d.ts +2 -532
- package/dist/jira.js +4 -4
- package/dist/kvMemory.d.ts +47 -78
- package/dist/lark.d.ts +2 -161
- package/dist/lark.js +1 -1
- package/dist/larkDocs.d.ts +35 -236
- package/dist/linear.d.ts +36 -343
- package/dist/linkedin.d.ts +10 -120
- package/dist/llm-billing.d.ts +92 -180
- package/dist/llm-billing.js +1 -1
- package/dist/memory.d.ts +11 -137
- package/dist/notion.d.ts +28 -276
- package/dist/notion.js +4 -4
- package/dist/opendesign.d.ts +24 -202
- package/dist/opendesign.js +2 -2
- package/dist/package.json +3 -2
- package/dist/plane.d.ts +40 -24
- package/dist/report.d.ts +153 -110
- package/dist/review-dedup.d.ts +8 -8
- package/dist/review.d.ts +12 -2
- package/dist/reviewMemoryIo.d.ts +37 -3
- package/dist/reviewRecord.d.ts +41 -47
- package/dist/sentry.d.ts +25 -22
- package/dist/skill-installer.d.ts +11 -86
- package/dist/slack.d.ts +1 -284
- package/dist/socialCard.d.ts +35 -89
- package/dist/test-runner.d.ts +1 -220
- package/dist/trackers/github-adapter.d.ts +39 -94
- package/dist/trackers/index.d.ts +25 -18
- package/dist/trackers/jira-adapter.d.ts +20 -88
- package/dist/trackers/linear-adapter.d.ts +24 -87
- package/dist/trackers/plane-adapter.d.ts +29 -85
- package/dist/trackers/plane-adapter.js +1 -1
- package/dist/trackers/types.d.ts +1 -186
- package/dist/triggerAgent.d.ts +26 -42
- package/dist/triggerAgent.js +1 -1
- package/dist/workflow-builder.d.ts +11 -245
- package/docs/concepts/designing-agents.md +201 -0
- package/docs/self-host/backup-restore.md +52 -0
- package/docs/self-host/index.md +75 -0
- package/docs/self-host/storage.md +50 -0
- package/docs/self-host/troubleshooting.md +48 -0
- package/docs/self-host/upgrade.md +81 -0
- package/package.json +3 -2
|
@@ -1,245 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
name: {
|
|
13
|
-
type: string;
|
|
14
|
-
description: string;
|
|
15
|
-
};
|
|
16
|
-
description: {
|
|
17
|
-
type: string;
|
|
18
|
-
description: string;
|
|
19
|
-
};
|
|
20
|
-
nodes: {
|
|
21
|
-
type: string;
|
|
22
|
-
items: {
|
|
23
|
-
type: string;
|
|
24
|
-
properties: {
|
|
25
|
-
name: {
|
|
26
|
-
type: string;
|
|
27
|
-
description: string;
|
|
28
|
-
};
|
|
29
|
-
description: {
|
|
30
|
-
type: string;
|
|
31
|
-
description: string;
|
|
32
|
-
};
|
|
33
|
-
inputFields: {
|
|
34
|
-
type: string;
|
|
35
|
-
items: {
|
|
36
|
-
type: string;
|
|
37
|
-
};
|
|
38
|
-
description: string;
|
|
39
|
-
};
|
|
40
|
-
outputFields: {
|
|
41
|
-
type: string;
|
|
42
|
-
items: {
|
|
43
|
-
type: string;
|
|
44
|
-
};
|
|
45
|
-
description: string;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
required: string[];
|
|
49
|
-
};
|
|
50
|
-
description: string;
|
|
51
|
-
};
|
|
52
|
-
edges: {
|
|
53
|
-
type: string;
|
|
54
|
-
items: {
|
|
55
|
-
type: string;
|
|
56
|
-
properties: {
|
|
57
|
-
from: {
|
|
58
|
-
type: string;
|
|
59
|
-
description: string;
|
|
60
|
-
};
|
|
61
|
-
to: {
|
|
62
|
-
type: string;
|
|
63
|
-
description: string;
|
|
64
|
-
};
|
|
65
|
-
condition: {
|
|
66
|
-
type: string;
|
|
67
|
-
description: string;
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
required: string[];
|
|
71
|
-
};
|
|
72
|
-
description: string;
|
|
73
|
-
};
|
|
74
|
-
spec?: undefined;
|
|
75
|
-
workflowName?: undefined;
|
|
76
|
-
nodeName?: undefined;
|
|
77
|
-
inputFields?: undefined;
|
|
78
|
-
outputFields?: undefined;
|
|
79
|
-
projectId?: undefined;
|
|
80
|
-
topic?: undefined;
|
|
81
|
-
};
|
|
82
|
-
required: string[];
|
|
83
|
-
};
|
|
84
|
-
} | {
|
|
85
|
-
name: string;
|
|
86
|
-
description: string;
|
|
87
|
-
input_schema: {
|
|
88
|
-
type: string;
|
|
89
|
-
properties: {
|
|
90
|
-
name: {
|
|
91
|
-
type: string;
|
|
92
|
-
description: string;
|
|
93
|
-
};
|
|
94
|
-
spec: {
|
|
95
|
-
type: string;
|
|
96
|
-
description: string;
|
|
97
|
-
properties: {
|
|
98
|
-
name: {
|
|
99
|
-
type: string;
|
|
100
|
-
};
|
|
101
|
-
description: {
|
|
102
|
-
type: string;
|
|
103
|
-
};
|
|
104
|
-
nodes: {
|
|
105
|
-
type: string;
|
|
106
|
-
items: {
|
|
107
|
-
type: string;
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
edges: {
|
|
111
|
-
type: string;
|
|
112
|
-
items: {
|
|
113
|
-
type: string;
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
};
|
|
118
|
-
description?: undefined;
|
|
119
|
-
nodes?: undefined;
|
|
120
|
-
edges?: undefined;
|
|
121
|
-
workflowName?: undefined;
|
|
122
|
-
nodeName?: undefined;
|
|
123
|
-
inputFields?: undefined;
|
|
124
|
-
outputFields?: undefined;
|
|
125
|
-
projectId?: undefined;
|
|
126
|
-
topic?: undefined;
|
|
127
|
-
};
|
|
128
|
-
required: string[];
|
|
129
|
-
};
|
|
130
|
-
} | {
|
|
131
|
-
name: string;
|
|
132
|
-
description: string;
|
|
133
|
-
input_schema: {
|
|
134
|
-
type: string;
|
|
135
|
-
properties: {
|
|
136
|
-
workflowName: {
|
|
137
|
-
type: string;
|
|
138
|
-
description: string;
|
|
139
|
-
};
|
|
140
|
-
nodeName: {
|
|
141
|
-
type: string;
|
|
142
|
-
description: string;
|
|
143
|
-
};
|
|
144
|
-
description: {
|
|
145
|
-
type: string;
|
|
146
|
-
description: string;
|
|
147
|
-
};
|
|
148
|
-
inputFields: {
|
|
149
|
-
type: string;
|
|
150
|
-
items: {
|
|
151
|
-
type: string;
|
|
152
|
-
};
|
|
153
|
-
description: string;
|
|
154
|
-
};
|
|
155
|
-
outputFields: {
|
|
156
|
-
type: string;
|
|
157
|
-
items: {
|
|
158
|
-
type: string;
|
|
159
|
-
};
|
|
160
|
-
description: string;
|
|
161
|
-
};
|
|
162
|
-
name?: undefined;
|
|
163
|
-
nodes?: undefined;
|
|
164
|
-
edges?: undefined;
|
|
165
|
-
spec?: undefined;
|
|
166
|
-
projectId?: undefined;
|
|
167
|
-
topic?: undefined;
|
|
168
|
-
};
|
|
169
|
-
required: string[];
|
|
170
|
-
};
|
|
171
|
-
} | {
|
|
172
|
-
name: string;
|
|
173
|
-
description: string;
|
|
174
|
-
input_schema: {
|
|
175
|
-
type: string;
|
|
176
|
-
properties: {
|
|
177
|
-
name: {
|
|
178
|
-
type: string;
|
|
179
|
-
description: string;
|
|
180
|
-
};
|
|
181
|
-
projectId: {
|
|
182
|
-
type: string;
|
|
183
|
-
description: string;
|
|
184
|
-
};
|
|
185
|
-
description?: undefined;
|
|
186
|
-
nodes?: undefined;
|
|
187
|
-
edges?: undefined;
|
|
188
|
-
spec?: undefined;
|
|
189
|
-
workflowName?: undefined;
|
|
190
|
-
nodeName?: undefined;
|
|
191
|
-
inputFields?: undefined;
|
|
192
|
-
outputFields?: undefined;
|
|
193
|
-
topic?: undefined;
|
|
194
|
-
};
|
|
195
|
-
required: string[];
|
|
196
|
-
};
|
|
197
|
-
} | {
|
|
198
|
-
name: string;
|
|
199
|
-
description: string;
|
|
200
|
-
input_schema: {
|
|
201
|
-
type: string;
|
|
202
|
-
properties: {
|
|
203
|
-
name?: undefined;
|
|
204
|
-
description?: undefined;
|
|
205
|
-
nodes?: undefined;
|
|
206
|
-
edges?: undefined;
|
|
207
|
-
spec?: undefined;
|
|
208
|
-
workflowName?: undefined;
|
|
209
|
-
nodeName?: undefined;
|
|
210
|
-
inputFields?: undefined;
|
|
211
|
-
outputFields?: undefined;
|
|
212
|
-
projectId?: undefined;
|
|
213
|
-
topic?: undefined;
|
|
214
|
-
};
|
|
215
|
-
required?: undefined;
|
|
216
|
-
};
|
|
217
|
-
} | {
|
|
218
|
-
name: string;
|
|
219
|
-
description: string;
|
|
220
|
-
input_schema: {
|
|
221
|
-
type: string;
|
|
222
|
-
properties: {
|
|
223
|
-
topic: {
|
|
224
|
-
type: string;
|
|
225
|
-
description: string;
|
|
226
|
-
};
|
|
227
|
-
name?: undefined;
|
|
228
|
-
description?: undefined;
|
|
229
|
-
nodes?: undefined;
|
|
230
|
-
edges?: undefined;
|
|
231
|
-
spec?: undefined;
|
|
232
|
-
workflowName?: undefined;
|
|
233
|
-
nodeName?: undefined;
|
|
234
|
-
inputFields?: undefined;
|
|
235
|
-
outputFields?: undefined;
|
|
236
|
-
projectId?: undefined;
|
|
237
|
-
};
|
|
238
|
-
required?: undefined;
|
|
239
|
-
};
|
|
240
|
-
})[];
|
|
241
|
-
export function handleToolCall(name: any, args: any, context: any): Promise<string>;
|
|
242
|
-
export function resolve(): any;
|
|
243
|
-
}
|
|
244
|
-
declare const PROMPT_FRAGMENT: "## Workflow Builder\n\nYou can help users build custom AI workflows using the Zibby workflow framework.\n\n### What makes Zibby workflows different\nEach node invokes a **real AI agent** (Cursor, Claude, Codex, or Gemini) \u2014 not a thin LLM API wrapper.\nThat means every node has full agent capabilities: tool use, MCP servers (browser, GitHub, Jira, Slack),\nmulti-turn reasoning, and structured output validation via Zod schemas.\n\nKey differentiators:\n- **Agent-powered nodes** \u2014 each step runs a full AI agent (cursor-agent, claude, codex, gemini CLI) with tool access and MCP skills, not a simple chat completion call.\n- **Structured output** \u2014 every node declares a Zod schema; the framework validates and parses the agent's response automatically.\n- **Conditional routing** \u2014 edges can branch on agent-produced fields (e.g., `state.triage.priority === 'critical'`), enabling intelligent decision graphs.\n- **MCP skill injection** \u2014 nodes declare `skills: [SKILLS.BROWSER, SKILLS.GITHUB]` and the framework spins up the right MCP servers automatically.\n- **Deploy anywhere** \u2014 `zibby deploy` pushes to Zibby Cloud with an API trigger; or self-host with `zibby start`.\n- **State accumulation** \u2014 each node's validated output is stored under its name in `state` (e.g., `state.classify_ticket`), so downstream nodes can reference upstream results.\n\n### What is a workflow?\nA directed graph of nodes (AI agent steps) connected by edges. Each node has:\n- `name` \u2014 unique identifier (snake_case)\n- `prompt` \u2014 function that receives state and returns the prompt string sent to the agent\n- `outputSchema` \u2014 Zod schema defining the structured output the agent must return\n- `skills` (optional) \u2014 array of MCP skill IDs the node needs (e.g., `SKILLS.BROWSER`, `SKILLS.GITHUB`)\n- `timeout` (optional) \u2014 max execution time in ms (default: 300000)\n- `model` (optional) \u2014 override the model for this node (e.g., `'claude-opus-4'`)\n\n### File structure\n```\n.zibby/workflows/<name>/\n\u251C\u2500\u2500 graph.mjs \u2014 WorkflowAgent subclass with buildGraph()\n\u251C\u2500\u2500 nodes/\n\u2502 \u251C\u2500\u2500 index.mjs \u2014 barrel export for all nodes\n\u2502 \u2514\u2500\u2500 <node>.mjs \u2014 one file per node\n\u2514\u2500\u2500 workflow.json \u2014 manifest (name, description, triggers)\n```\n\n### Node pattern\n```javascript\nimport { z, SKILLS } from '@zibby/core';\n\nconst OutputSchema = z.object({\n summary: z.string().describe('Brief summary'),\n items: z.array(z.string()).describe('List of extracted items'),\n needsReview: z.boolean().describe('Whether a human should review this'),\n});\n\nexport const myNode = {\n name: 'my_node',\n skills: [SKILLS.GITHUB], // optional \u2014 framework injects MCP servers\n timeout: 120000, // optional \u2014 2 min timeout\n prompt: (state) => \\`You are analyzing a pull request.\n\nInput:\n\\${JSON.stringify(state.input || {}, null, 2)}\n\nReturn a JSON object matching the schema.\\`,\n outputSchema: OutputSchema,\n};\n```\n\n### Graph pattern\n```javascript\nimport { WorkflowAgent, WorkflowGraph } from '@zibby/core';\nimport { classifyNode, routeNode } from './nodes/index.mjs';\n\nexport class MyWorkflow extends WorkflowAgent {\n buildGraph() {\n const graph = new WorkflowGraph();\n graph.addNode('classify', classifyNode);\n graph.addNode('route', routeNode);\n graph.setEntryPoint('classify');\n graph.addEdge('classify', 'route');\n graph.addEdge('route', 'END');\n return graph;\n }\n\n async onComplete(result) {\n // Post-execution hook \u2014 save artifacts, notify, etc.\n console.log('Workflow complete:', result.success);\n }\n}\n```\n\nConditional edges: `graph.addConditionalEdges('node', (state) => state.node.priority === 'high' ? 'escalate' : 'notify')`\n\n### Available SKILLS constants\nImport from `@zibby/core`: `SKILLS.BROWSER`, `SKILLS.MEMORY`, `SKILLS.GITHUB`, `SKILLS.JIRA`, `SKILLS.SLACK`, `SKILLS.RUNNER`\n\n### Deep documentation\nCall `explore_framework_docs` to read detailed framework docs on demand. Use it for:\n- Advanced patterns (middleware, parallel nodes, state schemas)\n- Deployment & cloud triggers\n- CLI commands reference\n- Integration details (Jira, GitHub, etc.)\nCall with no arguments to see all available topics.\n\n### How to use the builder tools\n1. For complex workflows, call `explore_framework_docs(\"custom-workflows\")` first to learn advanced patterns.\n2. Ask the user what their workflow should do, what input it receives, and what steps are needed.\n3. Call `design_workflow` with the structured spec for the user to review.\n4. Once approved, call `build_workflow` to generate real code on disk (uses the configured agent for high-quality code generation).\n5. Remind the user: `zibby start <name>` to test locally, `zibby deploy <name> --project <id>` to deploy to cloud, `zibby logs --workflow <name>` to tail logs.\n\n### Important\n- Each node prompt should be detailed and specific \u2014 tell the AI agent exactly what to do and what format to return.\n- Zod schemas MUST use .describe() on every field so the agent knows what each field means.\n- Node names must be snake_case (e.g., classify_ticket, generate_report).\n- Workflow names must be kebab-case (e.g., ticket-triage, pr-review).\n- State flows through: each node's validated output is stored under its name in state (e.g., state.classify_ticket).\n- Downstream nodes reference upstream outputs in their prompt function (e.g., \\`\\${JSON.stringify(state.classify_ticket, null, 2)}\\`).\n- Nodes can declare skills to get MCP tool access \u2014 the framework handles server lifecycle automatically.";
|
|
245
|
-
export {};
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Builder Skill
|
|
3
|
+
*
|
|
4
|
+
* Guides users through designing and building custom AI workflows
|
|
5
|
+
* via conversation. The chat agent (assistant) handles the dialog;
|
|
6
|
+
* this skill provides tools that generate real workflow code by
|
|
7
|
+
* delegating to the user's configured agent (cursor/claude/codex/gemini).
|
|
8
|
+
*
|
|
9
|
+
* No MCP server needed — all handlers run locally.
|
|
10
|
+
*/
|
|
11
|
+
export declare const workflowBuilderSkill: any;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 7
|
|
3
|
+
title: Designing agents
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Designing agents
|
|
7
|
+
|
|
8
|
+
Before you build an agent, learn the three patterns that make Zibby agents
|
|
9
|
+
powerful. Almost every good agent is some combination of them:
|
|
10
|
+
|
|
11
|
+
1. **One agent, many entry points** — a single deployed agent that behaves
|
|
12
|
+
differently depending on *how* it was triggered (cron vs. a human vs. a
|
|
13
|
+
webhook), selected by a code **router** node.
|
|
14
|
+
2. **Zero-LLM code nodes that read & write Stores** — deterministic work
|
|
15
|
+
(fetch, ETL, aggregate, persist) runs as plain code, no model call, and can
|
|
16
|
+
read/write a Store (SQLite / file / dataset) directly.
|
|
17
|
+
3. **Event-driven agents** — one agent subscribes to several events (PR opened,
|
|
18
|
+
comment, `@mention`) and routes each to the right branch.
|
|
19
|
+
|
|
20
|
+
These are not exotic. The shipped **sentry-triage** and **engineering-insights**
|
|
21
|
+
agents use all three at once. Study them — they are the canonical reference for
|
|
22
|
+
"how an agent should be designed."
|
|
23
|
+
|
|
24
|
+
:::tip Design rule
|
|
25
|
+
**Don't reach for an LLM node when plain code will do.** A model call costs
|
|
26
|
+
tokens, adds latency, and is non-deterministic. Fetching data, transforming
|
|
27
|
+
JSON, running a query, writing to a Store, calling an API — all of that belongs
|
|
28
|
+
in a **code node**. Reserve LLM nodes for judgment: classify, summarize, decide.
|
|
29
|
+
:::
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Pattern 1 — one agent, many entry points
|
|
34
|
+
|
|
35
|
+
The trap beginners fall into is building *two* agents: a "collector" that runs on
|
|
36
|
+
a schedule and a "reporter" a human triggers. Don't. Build **one** agent whose
|
|
37
|
+
**first node is a code router** that inspects the trigger and sends the run down
|
|
38
|
+
the right branch. One bundle, one deployment, one set of env/Store bindings.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { WorkflowGraph } from '@zibby/agent-workflow';
|
|
42
|
+
import { z } from '@zibby/core';
|
|
43
|
+
|
|
44
|
+
// The router is PURE CODE — no model call. It reads the trigger input off state
|
|
45
|
+
// and returns the name of the next node.
|
|
46
|
+
function route(state) {
|
|
47
|
+
// state.trigger / state.mode / state.action come from the trigger payload,
|
|
48
|
+
// spread onto the initial state by the runner.
|
|
49
|
+
return state?.trigger === 'fix' ? 'fix_intake' : 'fetch_issues';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const graph = new WorkflowGraph()
|
|
53
|
+
.addNode('route', {
|
|
54
|
+
_isCustomCode: true,
|
|
55
|
+
description: 'Routes on the trigger: "fix" runs the single-issue auto-fix branch, anything else runs the scheduled triage branch.',
|
|
56
|
+
outputSchema: z.object({}).passthrough(),
|
|
57
|
+
execute: async (ctx) => ctx.state.getAll(), // router only decides an edge; it forwards state
|
|
58
|
+
})
|
|
59
|
+
// …branch nodes…
|
|
60
|
+
.setEntryPoint('route')
|
|
61
|
+
// A conditional edge: run `route`, then take the edge its return value names.
|
|
62
|
+
.addConditionalEdges('route', route, {
|
|
63
|
+
labels: { fix_intake: 'auto-fix one issue', fetch_issues: 'scheduled triage' },
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The **same agent** then gets three trigger sources, all landing on `route`:
|
|
68
|
+
|
|
69
|
+
| Trigger | Payload | Branch it takes |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Cron (nightly) | `{ mode: 'collect' }` | collect → persist to Store |
|
|
72
|
+
| Human `zibby agent trigger` | `{ action: 'report' }` | read Store → generate → deliver |
|
|
73
|
+
| Webhook / Lark `@bot` | `{ trigger: 'fix', issueId }` | single-item fix branch |
|
|
74
|
+
|
|
75
|
+
> **Real reference:** `sentry-triage` routes `state.trigger === 'fix'` to an
|
|
76
|
+
> auto-fix branch and everything else (incl. an absent trigger — the nightly
|
|
77
|
+
> cron) to scheduled triage. `engineering-insights` routes `state.action`
|
|
78
|
+
> between `collect` (meter commits → SQLite) and `report` (aggregate → charts).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Pattern 2 — code nodes read & write Stores (no LLM)
|
|
83
|
+
|
|
84
|
+
A **code node** (`_isCustomCode: true` + an `execute` function) is a first-class
|
|
85
|
+
citizen. It runs in the same container as the rest of the graph and has the same
|
|
86
|
+
environment — including any **Store** bound to the node. So a deterministic node
|
|
87
|
+
can fetch from a Store, transform, and write results back, entirely without a
|
|
88
|
+
model.
|
|
89
|
+
|
|
90
|
+
### How a node reaches a Store
|
|
91
|
+
|
|
92
|
+
When you bind a Store to a node (via `zibby_set_node_stores`, the deploy modal,
|
|
93
|
+
or the CLI), the runtime injects three env vars into that node's container:
|
|
94
|
+
|
|
95
|
+
| Env var | What it is |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `ZIBBY_STORE__<name>` | the **storeId** of the Store you bound as `<name>` |
|
|
98
|
+
| `ZIBBY_ACCOUNT_API_URL` | the base URL of the datasets API |
|
|
99
|
+
| `PROJECT_API_TOKEN` | a project-scoped bearer token |
|
|
100
|
+
|
|
101
|
+
The node just `fetch`es the datasets API. No SDK required.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
// A pure-code node: read from a SQLite Store, write to a file Store. Zero LLM.
|
|
105
|
+
const BASE = process.env.ZIBBY_ACCOUNT_API_URL;
|
|
106
|
+
const TOKEN = process.env.PROJECT_API_TOKEN;
|
|
107
|
+
|
|
108
|
+
async function ds(storeId, action, body) {
|
|
109
|
+
const r = await fetch(`${BASE}/datasets/stores/${encodeURIComponent(storeId)}/${action}`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${TOKEN}` },
|
|
112
|
+
body: JSON.stringify(body),
|
|
113
|
+
});
|
|
114
|
+
if (!r.ok) throw new Error(`${action} ${r.status}: ${await r.text()}`);
|
|
115
|
+
return r.json();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
graph.addNode('build_report', {
|
|
119
|
+
_isCustomCode: true,
|
|
120
|
+
description: 'Reads metrics from a SQLite Store, writes the rendered report to a file Store. Pure code — no model call.',
|
|
121
|
+
outputSchema: z.object({ topDevs: z.array(z.any()), wrote: z.boolean() }),
|
|
122
|
+
execute: async (ctx) => {
|
|
123
|
+
const state = ctx.state.getAll();
|
|
124
|
+
const metricsDb = process.env.ZIBBY_STORE__metrics_db; // a sqlite Store
|
|
125
|
+
const reportFile = process.env.ZIBBY_STORE__report_files; // a file Store
|
|
126
|
+
|
|
127
|
+
// ── SQLite READ ── (readOnly:true → read-only guard)
|
|
128
|
+
const q = await ds(metricsDb, 'sql', {
|
|
129
|
+
sql: 'SELECT dev, added FROM commits WHERE added > ? ORDER BY added DESC LIMIT 10',
|
|
130
|
+
params: [100],
|
|
131
|
+
readOnly: true,
|
|
132
|
+
});
|
|
133
|
+
// q = { columns: ['dev','added'], rows: [['alice', 420], …] }
|
|
134
|
+
|
|
135
|
+
// ── SQLite WRITE ── (bound params; a brand-new table is created STRICT)
|
|
136
|
+
await ds(metricsDb, 'sql', {
|
|
137
|
+
sql: 'INSERT INTO runs (ran_at, top_dev) VALUES (?, ?)',
|
|
138
|
+
params: [state.ranAt, q.rows[0]?.[0] ?? null],
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ── file WRITE / READ ──
|
|
142
|
+
await ds(reportFile, 'put', { path: 'report.html', content: state.html });
|
|
143
|
+
const tpl = await ds(reportFile, 'get', { path: 'template.html' });
|
|
144
|
+
|
|
145
|
+
return { topDevs: q.rows, wrote: true };
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### The Store data actions
|
|
151
|
+
|
|
152
|
+
| Store type | Actions | Notes |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `sqlite` | `sql` `{ sql, params?, readOnly? }` | a real relational DB — tables, joins, UPDATEs. `readOnly: true` refuses any write. New tables are created **STRICT** so a column's declared type is enforced. |
|
|
155
|
+
| `file` | `put` / `get` / `list` / `delete` `{ path, content? }` | arbitrary blobs by relative path — good for whole-JSON inputs and rendered outputs. |
|
|
156
|
+
| `dataset` | `append` `{ record }` / `query` `{ select?, where?, … }` | append-only records + SQL-style aggregation — good for raw event streams you later `count`/`sum`/`group`. |
|
|
157
|
+
|
|
158
|
+
:::tip Choosing a Store type by data shape
|
|
159
|
+
- **Whole-blob** (a JSON file a script reads with `readFileSync`, a rendered
|
|
160
|
+
report) → **file**.
|
|
161
|
+
- **Rows you dedupe / update / join** (git commits keyed by SHA) → **sqlite**.
|
|
162
|
+
- **Append-only events you aggregate later** (MRs, worklogs) → **dataset**.
|
|
163
|
+
- **A tiny cursor** ("where did I get to last time") → **kv-memory** (built into
|
|
164
|
+
every agent, not a registry Store).
|
|
165
|
+
:::
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Pattern 3 — event-driven agents
|
|
170
|
+
|
|
171
|
+
A single agent can subscribe to **several** events and handle each differently.
|
|
172
|
+
The shipped **github-code-review** / **gitlab-code-review** agents are one agent
|
|
173
|
+
that reacts to *PR opened*, *new commits pushed*, *a comment reply*, and an
|
|
174
|
+
*`@mention`* — the entry router branches on which event fired. The owner even
|
|
175
|
+
chooses **which** events trigger a review (cost control), because the whole thing
|
|
176
|
+
is just an event-subscription subset feeding the same router.
|
|
177
|
+
|
|
178
|
+
The takeaway: **you rarely need a second agent for a new trigger.** Add an event
|
|
179
|
+
to the subscription, add a branch, extend the router.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Putting it together — a design checklist
|
|
184
|
+
|
|
185
|
+
When you design a new agent, ask:
|
|
186
|
+
|
|
187
|
+
1. **How many ways will this be triggered?** More than one → **Pattern 1**: a
|
|
188
|
+
code `route` node at the entry, one agent, branches per scenario.
|
|
189
|
+
2. **What here is deterministic?** Fetch / transform / query / persist → **code
|
|
190
|
+
nodes** (Pattern 2), not LLM nodes. Bind the Stores those nodes need.
|
|
191
|
+
3. **What actually needs judgment?** Only *those* steps get an LLM node
|
|
192
|
+
(`agent: 'claude' | 'codex' | …` + a `prompt` + an `outputSchema`).
|
|
193
|
+
4. **Where does the data live?** Pick a Store type per data shape (blob → file,
|
|
194
|
+
relational → sqlite, append-only → dataset; cursor → kv-memory).
|
|
195
|
+
5. **Is the split write-side vs. read-side?** Keep it **one agent** — a nightly
|
|
196
|
+
`collect` branch that writes the Store and an on-demand `report` branch that
|
|
197
|
+
reads it, selected by the router. Not two agents.
|
|
198
|
+
|
|
199
|
+
Then read the source of **sentry-triage** and **engineering-insights** and map
|
|
200
|
+
each pattern onto what you see. That is the fastest way to learn what Zibby can
|
|
201
|
+
do.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
title: Backup & restore
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Backup & restore
|
|
7
|
+
|
|
8
|
+
`backup.sh` and `restore.sh` ship next to the compose file in your install directory (the installer drops them there). Together they let a dead VM — or a fat-fingered `docker compose down -v` — never mean "start over and reconnect 50 repos".
|
|
9
|
+
|
|
10
|
+
## What a backup contains
|
|
11
|
+
|
|
12
|
+
One timestamped tarball holds the three things that together **are** your install:
|
|
13
|
+
|
|
14
|
+
1. **ScyllaDB volume** (`scylla-data`) — all metadata: integrations + their encrypted connect tokens, deployed agents (graph/env/schedule/prompts), repo→agent links, projects, members, access tokens, review-memory, execution history, stores.
|
|
15
|
+
2. **Object-store volume** — `seaweed-data` (SeaweedFS, the default) or `minio-data` on a legacy box. Run artifacts + per-node logs, datasets, marketplace icons. The active store is detected from `.env` (`ZIBBY_OBJECT_STORE`) / volume presence; `restore.sh` handles either.
|
|
16
|
+
3. **`.env`** — **critical**. It holds `ENCRYPTION_KEY`, without which every encrypted token/secret in ScyllaDB is unrecoverable, plus `JWT_SECRET`, the object-store root creds, and integration tokens. A ScyllaDB restore without the matching `ENCRYPTION_KEY` is useless, so `.env` travels **inside** the backup — which is also why the backup is highly sensitive.
|
|
17
|
+
|
|
18
|
+
## Take a backup
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd zibby-selfhosted
|
|
22
|
+
./backup.sh # → ./backups/zibby-selfhosted-<timestamp>.tar.gz
|
|
23
|
+
./backup.sh --out /mnt/backups # write elsewhere
|
|
24
|
+
./backup.sh --encrypt # encrypt at rest with age or gpg (prompts for a passphrase)
|
|
25
|
+
./backup.sh --s3 s3://mybucket/zibby # also upload with YOUR aws creds (nothing goes to Zibby)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Consistency + downtime: for a single-VM box the safe, simple approach wins — the script briefly **stops the writers** (control-plane + datastores), `nodetool flush`es Scylla so the commitlog is on disk, tars the volumes read-only from a throwaway helper container, then restarts everything. Downtime is typically seconds to a few minutes (plus Scylla's ~1–2 min cold-boot on restart). A trap guarantees the stack restarts even if the backup errors out. Volume names are compose-project-prefixed and **discovered at runtime**, so it works regardless of your install-dir name.
|
|
29
|
+
|
|
30
|
+
Copy the tarball **off the box** — it is your only recovery path, and it contains the encryption key.
|
|
31
|
+
|
|
32
|
+
:::tip
|
|
33
|
+
An upgrade takes its own safety snapshot into `<install-dir>/backups/upgrade-<id>/` before touching anything, and there is an `auto-backup.sh` you can cron for scheduled snapshots.
|
|
34
|
+
:::
|
|
35
|
+
|
|
36
|
+
## Restore
|
|
37
|
+
|
|
38
|
+
Onto the same box after data loss, or onto a brand-new VM:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
./restore.sh zibby-selfhosted-<timestamp>.tar.gz # interactive confirm
|
|
42
|
+
./restore.sh backup.tar.gz --dir /opt/zibby --force # scripted
|
|
43
|
+
./restore.sh backup.age # decrypts first (needs age/gpg)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`restore.sh` restores **both** data volumes and the `.env` (so the restored, encrypted data is readable). Volume names are discovered at runtime, or created if this is a fresh box.
|
|
47
|
+
|
|
48
|
+
:::danger Restore wipes current data
|
|
49
|
+
Restoring **overwrites** this install's volumes. The script refuses to run without an explicit confirmation (interactive `y/N`, or `--force`).
|
|
50
|
+
:::
|
|
51
|
+
|
|
52
|
+
A MinIO-era bundle restored onto a current (post-SeaweedFS-flip) release comes up on the legacy MinIO override; the next installer run auto-migrates it to SeaweedFS — see [Storage](./storage).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
title: Self-host overview
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Operating a self-hosted box
|
|
7
|
+
|
|
8
|
+
Run the entire Zibby agent platform on **one host**, inside your own VPC, with `docker compose`. This section is the **operator runbook** — for the person who holds the box's owner/admin token and is responsible for install, upgrade, backup, and storage. It is not the agent-authoring guide (that's the rest of these docs, and `zibby init` in your repo).
|
|
9
|
+
|
|
10
|
+
A self-hosted box is the **same codebase as cloud**, assembled differently: the control plane is `backend/server.js` (the Express server that wraps every Lambda handler, including the MCP server), the run executor is the host Docker engine instead of Fargate, and the datastore / object store are ScyllaDB Alternator + SeaweedFS instead of DynamoDB + S3.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
One command, on a Linux/macOS host with **Docker Engine + Compose v2**:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
curl -fsSL https://dl.zibby.app/selfhosted/latest/install.sh | bash
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The installer downloads a signed release (a `docker save` image tarball + compose file + the marketplace-template bundle), **verifies it** (an Ed25519 signature over `SHA256SUMS`, then a per-file checksum — it fails closed on any mismatch and never runs unverified bytes), `docker load`s the images, and `docker compose up -d`s the stack. It is idempotent — re-running it is how you upgrade in place, and cached downloads are checksum-verified.
|
|
21
|
+
|
|
22
|
+
:::warning Compose v2 is required
|
|
23
|
+
`apt install docker.io` alone gives you the engine but **not** Compose v2. Install both — the installer aborts with a link if `docker compose version` fails. On a headless box also make sure `openssl` is present (the installer needs it to verify the release signature).
|
|
24
|
+
:::
|
|
25
|
+
|
|
26
|
+
### What comes up
|
|
27
|
+
|
|
28
|
+
The stack is a handful of containers on the `zibby-selfhosted` docker network:
|
|
29
|
+
|
|
30
|
+
| Container | Role |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `zibby-control-plane` | the long-lived API + `/mcp` + `/hooks/*` server + the dashboard; drives the host Docker engine as the run executor over the mounted `docker.sock` |
|
|
33
|
+
| `zibby-scylla` | **ScyllaDB Alternator** (`:8000`) — speaks the DynamoDB API, so the backend is unchanged. All metadata: integrations + encrypted tokens, deployed agents, projects, members, executions, stores. |
|
|
34
|
+
| `zibby-minio` | the **object store** (`:9000`) for run sources + artifacts + logs. As of 0.1.126 this is **SeaweedFS** (the service keeps the historical name `minio` so every `S3_ENDPOINT` consumer is unchanged). Legacy boxes still run MinIO until they auto-migrate — see [Storage](./storage). |
|
|
35
|
+
| `zibby-bootstrap` | one-shot: provisions tables + buckets, syncs all marketplace templates, seeds the default project + token, then exits. |
|
|
36
|
+
| run containers | one ephemeral `zibby-agent` container per run, destroyed on exit — the same per-run isolation Fargate gives cloud. |
|
|
37
|
+
|
|
38
|
+
The control plane mounts `/var/run/docker.sock` — that socket **is** the run executor (no docker-in-docker).
|
|
39
|
+
|
|
40
|
+
### After install
|
|
41
|
+
|
|
42
|
+
The installer prints the access info and the seeded credential:
|
|
43
|
+
|
|
44
|
+
- **Dashboard** — `http://<host>:3001/` (and `/ui`). The self-host dashboard is a single vanilla HTML page served by the control plane; the cloud React app is not shipped on-prem.
|
|
45
|
+
- **API + CLI** — `http://<host>:3001`. Set `ZIBBY_API_URL` + `ZIBBY_API_KEY=<apiToken>` (the seeded `zby_…` project token printed at the end, or recover it with `docker logs zibby-bootstrap`).
|
|
46
|
+
- **MCP** — `http://<host>:3001/mcp`, `Authorization: Bearer <PAT>`. MCP needs a **user PAT** (`zby_pat_…`), minted with `zibby self-host token` — not the project token.
|
|
47
|
+
- **Health** — `http://<host>:3001/health`.
|
|
48
|
+
|
|
49
|
+
AI model keys are **per-project**, set in the dashboard (Settings → AI); the deploy modal collects a missing one. The box comes up keyless — there is deliberately no box-global model credential.
|
|
50
|
+
|
|
51
|
+
## Install knobs
|
|
52
|
+
|
|
53
|
+
Set these as environment variables or flags on the install command (they persist into `.env`, which is the runtime source of truth and is **reused untouched on upgrade**):
|
|
54
|
+
|
|
55
|
+
| Knob | Default | Effect |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `--dir <path>` / `ZIBBY_DIR` | `./zibby-selfhosted` | install + data directory |
|
|
58
|
+
| `--url <u>` / `ZIBBY_DIST_URL` | `https://dl.zibby.app/selfhosted/latest` | release base URL |
|
|
59
|
+
| `--reconfigure` | — | re-run the external-URL / domain prompt |
|
|
60
|
+
| `CONTROL_PLANE_PORT` (`.env`) | `3001` | host port the control plane binds |
|
|
61
|
+
| `CONTROL_PLANE_BIND` (`.env`) | `0.0.0.0` | set to `127.0.0.1` to restrict the dashboard/API to loopback and front it with the bundled Caddy TLS proxy |
|
|
62
|
+
| `ZIBBY_EXTERNAL_URL` (`.env`/prompt) | unset | a domain → automatic Let's Encrypt HTTPS; an IP → self-signed HTTPS; `http://…` → plain HTTP via Caddy. Blank → plain HTTP on `CONTROL_PLANE_PORT`. |
|
|
63
|
+
| `MAX_CONCURRENT_RUNS` (`.env`) | `5` | run containers executing in parallel; the rest queue |
|
|
64
|
+
| `ZIBBY_SKIP_OBJECT_STORE_MIGRATION` | `0` | `1` opts a legacy MinIO box out of the auto-migration to SeaweedFS |
|
|
65
|
+
|
|
66
|
+
:::warning Plain-HTTP default binds in cleartext
|
|
67
|
+
With no external URL the dashboard/API bind to `0.0.0.0:3001` unencrypted — the operator token and login passcode cross the network in the clear. Either set `ZIBBY_EXTERNAL_URL` (→ auto-TLS via Caddy), firewall the port to trusted IPs, or set `CONTROL_PLANE_BIND=127.0.0.1`.
|
|
68
|
+
:::
|
|
69
|
+
|
|
70
|
+
## The runbook
|
|
71
|
+
|
|
72
|
+
- **[Upgrade & rollback](./upgrade)** — check the running version, the owner-only remote upgrade, and how auto-rollback works.
|
|
73
|
+
- **[Backup & restore](./backup-restore)** — snapshot the whole install to one tarball; restore onto the same or a fresh box.
|
|
74
|
+
- **[Storage](./storage)** — the object store, and the automatic MinIO → SeaweedFS migration.
|
|
75
|
+
- **[Troubleshooting](./troubleshooting)** — reading logs and the failure signatures worth recognizing.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 4
|
|
3
|
+
title: Storage
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Storage
|
|
7
|
+
|
|
8
|
+
A self-hosted box keeps two stateful stores, both as single-box containers on named docker volumes:
|
|
9
|
+
|
|
10
|
+
- **ScyllaDB Alternator** (`zibby-scylla`, volume `scylla-data`) — the DynamoDB-compatible metadata store. All accounts, projects, agents, integrations, executions, and stores live here.
|
|
11
|
+
- **Object store** (`zibby-minio`, volume `seaweed-data` or legacy `minio-data`) — run sources, artifacts, per-node logs, datasets, and marketplace icons, over the S3 API.
|
|
12
|
+
|
|
13
|
+
Both survive `docker compose restart` / `down` (they're on named volumes) but **not** `down -v`. Back them up — see [Backup & restore](./backup-restore).
|
|
14
|
+
|
|
15
|
+
## MinIO → SeaweedFS
|
|
16
|
+
|
|
17
|
+
MinIO's upstream was archived in 2026-04, so as of **0.1.126** the object store is **SeaweedFS**. The service is still *named* `minio` in compose and DNS so every `S3_ENDPOINT` consumer is unchanged — only the engine behind it changed. (Full evaluation: `strategy/object-store-replacement.md` in the root repo.)
|
|
18
|
+
|
|
19
|
+
- **New installs** come up on SeaweedFS (volume `seaweed-data`). Nothing to do.
|
|
20
|
+
- **Upgrades from a MinIO box auto-migrate.** When the installer upgrades a legacy (pre-flip, `minio-data`) box, it keeps MinIO running for that `up`, then — once the upgraded stack is healthy — runs the migration automatically.
|
|
21
|
+
|
|
22
|
+
### How the auto-migration works
|
|
23
|
+
|
|
24
|
+
It is designed to **auto-attempt, never auto-brick** — MinIO's data is never modified, so every failure path rolls back to a working box:
|
|
25
|
+
|
|
26
|
+
1. **backup** — a full `backup.sh` snapshot first; it refuses to migrate without one.
|
|
27
|
+
2. **quiesce** — stop the S3 writers (control-plane + copilot-runtime) so the copy is consistent.
|
|
28
|
+
3. **copy + verify** — `migrate-object-store.sh` mirrors every bucket into a throwaway SeaweedFS container writing to the `seaweed-data` volume, then verifies per-bucket **object count + total bytes must match exactly**, plus a sha256 comparison of sampled objects (always including the largest). It never deletes anything.
|
|
29
|
+
4. **flip** — record `ZIBBY_OBJECT_STORE=seaweedfs` in `.env` and bring up the default (SeaweedFS) stack.
|
|
30
|
+
5. **health-gate** — the new control plane must become healthy **and** pass an authenticated S3 list against the migrated store.
|
|
31
|
+
6. **rollback** — any failure at any stage rolls back to MinIO (whose data was never touched) with a loud status line.
|
|
32
|
+
|
|
33
|
+
The **old `minio-data` volume is retained** as a rollback safety net. After a few days of verified operation you can reclaim it:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
docker volume rm <project>_minio-data # irreversible — only after you trust SeaweedFS
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Controlling / retrying the migration
|
|
40
|
+
|
|
41
|
+
- **Opt out** (stay on MinIO for now): run the installer with `ZIBBY_SKIP_OBJECT_STORE_MIGRATION=1`.
|
|
42
|
+
- **Retry** after a failed auto-migration: re-run the installer, or manually —
|
|
43
|
+
```bash
|
|
44
|
+
cd zibby-selfhosted
|
|
45
|
+
docker compose stop control-plane copilot-runtime
|
|
46
|
+
bash migrate-object-store.sh --yes
|
|
47
|
+
# then flip: set ZIBBY_OBJECT_STORE=seaweedfs + COMPOSE_FILE=docker-compose.yml in .env
|
|
48
|
+
docker compose -f docker-compose.yml up -d
|
|
49
|
+
```
|
|
50
|
+
- **MinIO emergency fallback image** (if a legacy box lost its local MinIO image): the pinned tarball is at `https://dl.zibby.app/selfhosted/infra/` — verify the `.sha256`, then `docker load -i`. The box only ever pulls images from our own CDN, never a third-party registry.
|