matimo-examples 0.1.0-alpha.12 → 0.1.0-alpha.13
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/QUICK_COMMANDS.md +316 -0
- package/README.md +200 -57
- package/agents/langchain-agent.ts +33 -3
- package/agents/langchain-skills-policy-agent.ts +384 -0
- package/credentials/README.md +241 -0
- package/credentials/credentials-example.ts +225 -0
- package/meta-flow/README.md +319 -0
- package/meta-flow/meta-tools-integration.ts +520 -0
- package/package.json +21 -10
- package/policy/README.md +373 -0
- package/policy/policy-demo.ts +1096 -0
- package/skills/README.md +179 -0
- package/skills/skills-demo.ts +552 -0
- package/validate-implementation.ts +211 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* PER-EXECUTION CREDENTIAL OVERRIDE — MULTI-TENANT EXAMPLE
|
|
5
|
+
* ============================================================================
|
|
6
|
+
*
|
|
7
|
+
* PATTERN: Per-call credentials (options.credentials)
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* Demonstrates how to supply credentials per `execute()` call instead of
|
|
10
|
+
* relying on environment variables. This is the right pattern for
|
|
11
|
+
* multi-tenant platforms where each user/tenant has their own API keys.
|
|
12
|
+
*
|
|
13
|
+
* Use this pattern when:
|
|
14
|
+
* ✅ Serving multiple tenants from a single process
|
|
15
|
+
* ✅ Credentials come from a database / secrets manager / vault
|
|
16
|
+
* ✅ You must NOT store per-tenant tokens in process.env
|
|
17
|
+
* ✅ You want strict per-call credential isolation
|
|
18
|
+
*
|
|
19
|
+
* Contrast with single-tenant pattern (env vars):
|
|
20
|
+
* SLACK_BOT_TOKEN=xoxb-xxx matimo execute slack-send-message ...
|
|
21
|
+
* → works fine for one account, breaks for ten tenants
|
|
22
|
+
*
|
|
23
|
+
* SETUP:
|
|
24
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
25
|
+
* No .env token needed — this example uses placeholder tenant tokens to
|
|
26
|
+
* show the API shape. Real requests will fail (expected). To see real calls
|
|
27
|
+
* succeed, replace the placeholder tokens with real Slack bot tokens.
|
|
28
|
+
*
|
|
29
|
+
* USAGE:
|
|
30
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
31
|
+
* pnpm credentials:example
|
|
32
|
+
*
|
|
33
|
+
* KEY CONCEPTS DEMONSTRATED:
|
|
34
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
35
|
+
* 1. getRequiredCredentials(toolName) — discover what keys a tool needs
|
|
36
|
+
* 2. execute(name, params, { credentials }) — per-call credential injection
|
|
37
|
+
* 3. Tenant isolation — two tenants, same process, different tokens
|
|
38
|
+
* 4. Graceful partial credentials — credential + env-var fallback strategy
|
|
39
|
+
* 5. Credential manifest — build a map of all tools → required keys at startup
|
|
40
|
+
*
|
|
41
|
+
* ============================================================================
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import 'dotenv/config';
|
|
45
|
+
import { MatimoInstance } from 'matimo';
|
|
46
|
+
|
|
47
|
+
// ─── Simulated tenant "database" ─────────────────────────────────────────────
|
|
48
|
+
// In a real platform these would come from your DB / vault / secrets manager.
|
|
49
|
+
const TENANTS = {
|
|
50
|
+
'tenant-acme': {
|
|
51
|
+
name: 'Acme Corp',
|
|
52
|
+
secrets: {
|
|
53
|
+
// Replace with a real token to see live Slack calls
|
|
54
|
+
SLACK_BOT_TOKEN: process.env.ACME_SLACK_BOT_TOKEN ?? 'xoxb-acme-placeholder-token',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
'tenant-globex': {
|
|
58
|
+
name: 'Globex Inc',
|
|
59
|
+
secrets: {
|
|
60
|
+
SLACK_BOT_TOKEN: process.env.GLOBEX_SLACK_BOT_TOKEN ?? 'xoxb-globex-placeholder-token',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type TenantId = keyof typeof TENANTS;
|
|
66
|
+
type Tenant = (typeof TENANTS)[TenantId];
|
|
67
|
+
|
|
68
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
console.info('\n╔════════════════════════════════════════════════════════╗');
|
|
72
|
+
console.info('║ Per-Execution Credential Override — Multi-Tenant ║');
|
|
73
|
+
console.info('╚════════════════════════════════════════════════════════╝\n');
|
|
74
|
+
|
|
75
|
+
// ── 1. Initialize once — no per-tenant init needed ──────────────────────
|
|
76
|
+
console.info('🚀 Initializing Matimo (once for all tenants)…');
|
|
77
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
78
|
+
console.info(`✅ Loaded ${matimo.listTools().length} tools\n`);
|
|
79
|
+
|
|
80
|
+
// ── 2. Discover required credential keys at startup ──────────────────────
|
|
81
|
+
// getRequiredCredentials() tells you EXACTLY what keys to put in `credentials`
|
|
82
|
+
// for a given tool — no need to read the YAML.
|
|
83
|
+
console.info('🔑 Building credential manifest for all tools…');
|
|
84
|
+
const credentialManifest: Record<string, string[]> = {};
|
|
85
|
+
for (const tool of matimo.listTools()) {
|
|
86
|
+
const keys = matimo.getRequiredCredentials(tool.name);
|
|
87
|
+
if (keys.length > 0) {
|
|
88
|
+
credentialManifest[tool.name] = keys;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const toolsWithAuth = Object.keys(credentialManifest).length;
|
|
93
|
+
const toolsNoAuth = matimo.listTools().length - toolsWithAuth;
|
|
94
|
+
console.info(` ${toolsWithAuth} tools need credentials, ${toolsNoAuth} are public`);
|
|
95
|
+
console.info(' Sample manifest entries:');
|
|
96
|
+
for (const [tool, keys] of Object.entries(credentialManifest).slice(0, 5)) {
|
|
97
|
+
console.info(` ${tool}: [${keys.join(', ')}]`);
|
|
98
|
+
}
|
|
99
|
+
console.info();
|
|
100
|
+
|
|
101
|
+
// ── 3. Per-tenant execution helper ───────────────────────────────────────
|
|
102
|
+
// Collect only the keys the tool needs from the tenant's secrets store.
|
|
103
|
+
async function executeForTenant(
|
|
104
|
+
tenantId: TenantId,
|
|
105
|
+
toolName: string,
|
|
106
|
+
params: Record<string, unknown>
|
|
107
|
+
) {
|
|
108
|
+
const tenant: Tenant = TENANTS[tenantId];
|
|
109
|
+
const requiredKeys = matimo.getRequiredCredentials(toolName);
|
|
110
|
+
|
|
111
|
+
// Build credentials map — only the keys this specific tool needs
|
|
112
|
+
const credentials: Record<string, string> = {};
|
|
113
|
+
const missing: string[] = [];
|
|
114
|
+
|
|
115
|
+
for (const key of requiredKeys) {
|
|
116
|
+
const value = tenant.secrets[key as keyof typeof tenant.secrets];
|
|
117
|
+
if (value) {
|
|
118
|
+
credentials[key] = value;
|
|
119
|
+
} else {
|
|
120
|
+
missing.push(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (missing.length > 0) {
|
|
125
|
+
console.warn(
|
|
126
|
+
` ⚠️ [${tenant.name}] Missing ${missing.length} credential key(s) for '${toolName}'.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.info(
|
|
131
|
+
` 🏢 [${tenant.name}] Executing '${toolName}' with ${Object.keys(credentials).length} credential(s)…`
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await matimo.execute(toolName, params, { credentials });
|
|
136
|
+
return { tenantId, toolName, success: true, result };
|
|
137
|
+
} catch (err) {
|
|
138
|
+
// Expected for placeholder tokens — real tokens would succeed
|
|
139
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
140
|
+
return { tenantId, toolName, success: false, error: message };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── 4. Demo: same tool, two tenants, fully isolated ──────────────────────
|
|
145
|
+
console.info('════════════════════════════════════════════════════════════');
|
|
146
|
+
console.info('Demo 1: Same tool, two tenants, isolated credentials');
|
|
147
|
+
console.info('════════════════════════════════════════════════════════════\n');
|
|
148
|
+
|
|
149
|
+
const channel = process.env.SLACK_CHANNEL_ID ?? 'C0000000000';
|
|
150
|
+
const params = { channel, text: `Hello from multi-tenant demo at ${new Date().toISOString()}` };
|
|
151
|
+
|
|
152
|
+
const [acmeResult, globexResult] = await Promise.all([
|
|
153
|
+
executeForTenant('tenant-acme', 'slack-send-message', params),
|
|
154
|
+
executeForTenant('tenant-globex', 'slack-send-message', params),
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
for (const r of [acmeResult, globexResult]) {
|
|
158
|
+
const icon = r.success ? '✅' : '⚠️ ';
|
|
159
|
+
const tenant = TENANTS[r.tenantId as TenantId].name;
|
|
160
|
+
console.info(
|
|
161
|
+
` ${icon} [${tenant}] ${r.success ? 'Succeeded' : `Failed (expected with placeholder token): ${r.error?.slice(0, 80)}`}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── 5. Verify process.env was NOT modified ────────────────────────────────
|
|
166
|
+
console.info('\n════════════════════════════════════════════════════════════');
|
|
167
|
+
console.info('Demo 2: process.env isolation check');
|
|
168
|
+
console.info('════════════════════════════════════════════════════════════\n');
|
|
169
|
+
|
|
170
|
+
const envBefore = process.env.SLACK_BOT_TOKEN;
|
|
171
|
+
await executeForTenant('tenant-acme', 'slack-send-message', params);
|
|
172
|
+
const envAfter = process.env.SLACK_BOT_TOKEN;
|
|
173
|
+
|
|
174
|
+
if (envBefore === envAfter) {
|
|
175
|
+
console.info(' ✅ process.env.SLACK_BOT_TOKEN unchanged — credentials are call-scoped');
|
|
176
|
+
} else {
|
|
177
|
+
console.error(' ❌ UNEXPECTED: process.env was mutated!');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── 6. Fallback to env vars when credentials not provided ─────────────────
|
|
181
|
+
console.info('\n════════════════════════════════════════════════════════════');
|
|
182
|
+
console.info('Demo 3: Backward compatibility — no credentials → env var fallback');
|
|
183
|
+
console.info('════════════════════════════════════════════════════════════\n');
|
|
184
|
+
|
|
185
|
+
// Temporarily set env var to simulate single-tenant / legacy usage
|
|
186
|
+
const wasSet = !!process.env.SLACK_BOT_TOKEN;
|
|
187
|
+
process.env.SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN ?? 'xoxb-env-fallback-token';
|
|
188
|
+
|
|
189
|
+
console.info(' Calling execute() without credentials — falls back to process.env…');
|
|
190
|
+
try {
|
|
191
|
+
await matimo.execute('slack-send-message', params);
|
|
192
|
+
console.info(' ✅ Succeeded (env var token was valid)');
|
|
193
|
+
} catch {
|
|
194
|
+
console.info(' ⚠️ Failed at API level (env token is a placeholder — expected)');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!wasSet) delete process.env.SLACK_BOT_TOKEN;
|
|
198
|
+
|
|
199
|
+
// ── 7. Credential key lookup reference ───────────────────────────────────
|
|
200
|
+
console.info('\n════════════════════════════════════════════════════════════');
|
|
201
|
+
console.info('Reference: credential keys for Slack tools');
|
|
202
|
+
console.info('════════════════════════════════════════════════════════════\n');
|
|
203
|
+
|
|
204
|
+
const slackTools = matimo.listTools().filter((t) => t.name.startsWith('slack'));
|
|
205
|
+
for (const tool of slackTools.slice(0, 5)) {
|
|
206
|
+
const keys = matimo.getRequiredCredentials(tool.name);
|
|
207
|
+
console.info(` ${tool.name}`);
|
|
208
|
+
console.info(
|
|
209
|
+
` credentials required: ${keys.length ? `${keys.length} key(s)` : '(none required)'}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (slackTools.length > 5) {
|
|
213
|
+
console.info(` … and ${slackTools.length - 5} more Slack tools`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.info('\n✅ Example complete.\n');
|
|
217
|
+
console.info('To use real credentials, set per-tenant env vars:');
|
|
218
|
+
console.info(' ACME_SLACK_BOT_TOKEN=xoxb-acme-real-token');
|
|
219
|
+
console.info(' GLOBEX_SLACK_BOT_TOKEN=xoxb-globex-real-token\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main().catch((err) => {
|
|
223
|
+
console.error('Fatal error:', err);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Meta-Tools Integration Flow
|
|
2
|
+
|
|
3
|
+
**The most comprehensive example showing tool creation → policy validation → human approval → execution.**
|
|
4
|
+
|
|
5
|
+
A real LangChain ReAct agent demonstrates the complete tool lifecycle without being told which metadata tools to use.
|
|
6
|
+
|
|
7
|
+
## What It Shows
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Agent receives high-level goals (not tool names):
|
|
11
|
+
├─ "Create a safe HTTP GET tool"
|
|
12
|
+
├─ "Attempt a shell command tool" (will be blocked)
|
|
13
|
+
├─ "Attempt a file reader tool" (will be blocked)
|
|
14
|
+
├─ "Create safe tools that pass policy"
|
|
15
|
+
└─ "List and use the tools we created"
|
|
16
|
+
|
|
17
|
+
Agent autonomously discovers and uses:
|
|
18
|
+
├─ matimo_doctor → Validate YAML against policies
|
|
19
|
+
├─ matimo_create_tool → Create draft tools on disk
|
|
20
|
+
├─ matimo_review → Get human approval (terminal prompt)
|
|
21
|
+
├─ matimo_reload_tools → Reload registry after approval
|
|
22
|
+
├─ matimo_list_user_tools → List created tools
|
|
23
|
+
└─ [Tool execution] → Use the approved tools
|
|
24
|
+
|
|
25
|
+
Policy engine enforces:
|
|
26
|
+
├─ ✅ Only allowed domains (safe)
|
|
27
|
+
├─ ✅ Only HTTP methods GET/POST
|
|
28
|
+
├─ ❌ No shell commands
|
|
29
|
+
├─ ❌ No SSRF attacks
|
|
30
|
+
└─ ❌ No namespace hijacking
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Running It
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# From examples/tools/
|
|
37
|
+
pnpm meta:flow
|
|
38
|
+
|
|
39
|
+
# Or with auto-approval (for CI/testing):
|
|
40
|
+
printf "y\ny\ny\ny\ny\ny\n" | npx tsx meta-flow/meta-tools-integration.ts
|
|
41
|
+
|
|
42
|
+
# Run just meta-tools validation from anywhere:
|
|
43
|
+
cd examples/tools
|
|
44
|
+
pnpm validate:meta
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
When prompted: type `y` to approve tools, `n` to reject them. Agent learns from rejections.
|
|
48
|
+
|
|
49
|
+
## Key Differences from Other Examples
|
|
50
|
+
|
|
51
|
+
| Aspect | Policy Demo | Skills Demo | **Meta-Tools** |
|
|
52
|
+
|--------|-------------|-------------|----------------|
|
|
53
|
+
| Focus | Policy engine | Skills system | **Complete lifecycle** |
|
|
54
|
+
| Agent task | Validates tools | Creates skills | **Creates tools → approves → uses** |
|
|
55
|
+
| Policy demo | ✓ | - | ✓ |
|
|
56
|
+
| Skills demo | - | ✓ | - |
|
|
57
|
+
| Human approval | ✓ | ✓ | **✓ (most interactive)** |
|
|
58
|
+
| Tool creation | - | ✓ | **✓** |
|
|
59
|
+
| Tool execution | - | - | **✓** |
|
|
60
|
+
| Missions | 10 | 6 | **5 progressive** |
|
|
61
|
+
| Duration | ~90s | ~60s | **~120s** |
|
|
62
|
+
|
|
63
|
+
## Mission Breakdown
|
|
64
|
+
|
|
65
|
+
### Mission 1: Safe HTTP Tool
|
|
66
|
+
```
|
|
67
|
+
Agent: "Create a weather tool that calls a safe API"
|
|
68
|
+
↓
|
|
69
|
+
Agent thinks: "I need to validate, create, get approval, reload, then test"
|
|
70
|
+
↓
|
|
71
|
+
Agent: matimo_doctor(weather_fetch yaml)
|
|
72
|
+
→ ✅ "Valid: api.weatherapi.com approved, GET method allowed"
|
|
73
|
+
↓
|
|
74
|
+
Agent: matimo_create_tool('weather_fetch', yaml, toolsDir)
|
|
75
|
+
→ ✅ "Created draft: weather_fetch"
|
|
76
|
+
↓
|
|
77
|
+
Agent: matimo_review('weather_fetch', toolsDir)
|
|
78
|
+
→ Terminal: "Approve weather_fetch? (y/n): "
|
|
79
|
+
→ Human types: y
|
|
80
|
+
→ ✅ "Approved by human operator"
|
|
81
|
+
↓
|
|
82
|
+
Agent: matimo_reload_tools(toolsDir)
|
|
83
|
+
→ ✅ "Reloaded: weather_fetch available"
|
|
84
|
+
↓
|
|
85
|
+
Agent: weather_fetch({city: 'London'})
|
|
86
|
+
→ ✅ "API response: temp, condition, etc."
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Result**: ✅ Agent successfully created, approved, and executed a safe tool
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### Mission 2: Attempt Shell Command (Will Fail)
|
|
94
|
+
```
|
|
95
|
+
Agent: "Create a tool that executes shell commands"
|
|
96
|
+
↓
|
|
97
|
+
Agent generates: name: shell_exec, type: command, command: bash
|
|
98
|
+
↓
|
|
99
|
+
Agent: matimo_doctor(yaml)
|
|
100
|
+
→ ❌ "Command tools are blocked by policy (allowCommandTools=false)"
|
|
101
|
+
↓
|
|
102
|
+
Agent learns: "I cannot create command-type tools"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Result**: ⚠️ Policy enforced, Agent learns constraints
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### Mission 3: Attempt File Reader (Will Fail)
|
|
110
|
+
```
|
|
111
|
+
Agent: "Create a tool to read files"
|
|
112
|
+
↓
|
|
113
|
+
Agent generates: name: file_reader, command: cat {path}
|
|
114
|
+
↓
|
|
115
|
+
Agent: matimo_doctor(yaml)
|
|
116
|
+
→ ❌ "Command tool type is blocked"
|
|
117
|
+
↓
|
|
118
|
+
Agent learns: "Command execution is not allowed"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Result**: ⚠️ Policy blocks dangerous operation type
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### Mission 4: Create Safe Tools (Learning from Failures)
|
|
126
|
+
```
|
|
127
|
+
Agent notices previous commands were blocked.
|
|
128
|
+
Agent now creates ONLY HTTP tools:
|
|
129
|
+
├─ Tool 1: user_lookup (HTTP GET from jsonplaceholder)
|
|
130
|
+
│ ├─ doctor: ✅ Valid
|
|
131
|
+
│ ├─ create: ✅ Created
|
|
132
|
+
│ ├─ review: 🛡️ Human approves
|
|
133
|
+
│ └─ reload: ✅ Available
|
|
134
|
+
│
|
|
135
|
+
└─ Tool 2: github_stars (HTTP GET from api.github.com)
|
|
136
|
+
├─ doctor: ✅ Valid
|
|
137
|
+
├─ create: ✅ Created
|
|
138
|
+
├─ review: 🛡️ Human approves
|
|
139
|
+
└─ reload: ✅ Available
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Result**: ✅ Multiple tools created, approved, and reloaded
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### Mission 5: List and Execute Tools
|
|
147
|
+
```
|
|
148
|
+
Agent: "Show all tools we created and test one"
|
|
149
|
+
↓
|
|
150
|
+
Agent: matimo_list_user_tools(toolsDir)
|
|
151
|
+
→ Returns: [user_lookup, github_stars]
|
|
152
|
+
↓
|
|
153
|
+
Agent picks user_lookup and executes:
|
|
154
|
+
Agent: user_lookup({id: 5})
|
|
155
|
+
→ Returns: {name: 'Chelsey', city: 'Roscoe', ...}
|
|
156
|
+
↓
|
|
157
|
+
Agent reports: "Both tools work correctly"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Result**: ✅ Tools are listed and executable
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Output You'll See
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
╔════════════════════════════════════════════════════════════════════╗
|
|
168
|
+
║ Matimo Meta-Tools Integration Flow ║
|
|
169
|
+
║ Tool Creation → Policy Validation → Human Approval → Usage ║
|
|
170
|
+
╚════════════════════════════════════════════════════════════════════╝
|
|
171
|
+
|
|
172
|
+
═══════════════════════════════════════════════════════════════════════
|
|
173
|
+
PHASE 1: Setup
|
|
174
|
+
═══════════════════════════════════════════════════════════════════════
|
|
175
|
+
|
|
176
|
+
ℹ Matimo meta-tools loaded: 9 tools
|
|
177
|
+
✓ PASS Meta-tools available: matimo_doctor, matimo_create_tool, ...
|
|
178
|
+
✓ PASS LangChain agent initialized: gpt-4o-mini with meta-tools
|
|
179
|
+
|
|
180
|
+
ℹ Tools directory: /tmp/matimo-meta-flow-xxx/tools
|
|
181
|
+
ℹ When prompted, type 'y' to approve tools
|
|
182
|
+
|
|
183
|
+
═══════════════════════════════════════════════════════════════════════
|
|
184
|
+
PHASE 2: Missions (Agent-Driven Tool Lifecycle)
|
|
185
|
+
═══════════════════════════════════════════════════════════════════════
|
|
186
|
+
|
|
187
|
+
── Mission 1: Create a safe HTTP GET tool ─────────────────────────
|
|
188
|
+
🎯 Agent Goal: "Create a weather tool that calls a safe API"
|
|
189
|
+
|
|
190
|
+
🔧 Agent calls: matimo_doctor("name: weather_fetch,...")
|
|
191
|
+
📋 Result: Valid: safe domain (api.weatherapi.com), GET method allowed
|
|
192
|
+
|
|
193
|
+
🔧 Agent calls: matimo_create_tool("weather_fetch", yaml, ...)
|
|
194
|
+
📋 Result: Created draft: weather_fetch
|
|
195
|
+
|
|
196
|
+
🔧 Agent calls: matimo_review("weather_fetch", ...)
|
|
197
|
+
|
|
198
|
+
╔══════════════════════════════════════════════════════╗
|
|
199
|
+
║ 🛡️ HUMAN APPROVAL REQUIRED ║
|
|
200
|
+
║ Tool: weather_fetch ║
|
|
201
|
+
║ Desc: Fetch current weather for a city ║
|
|
202
|
+
╚══════════════════════════════════════════════════════╝
|
|
203
|
+
❓ Approve? (y/n): y
|
|
204
|
+
✓ PASS Approved by human operator.
|
|
205
|
+
|
|
206
|
+
🔧 Agent calls: matimo_reload_tools(...)
|
|
207
|
+
📋 Result: Reloaded: weather_fetch now available
|
|
208
|
+
|
|
209
|
+
💬 Agent: I have successfully created and approved a weather tool.
|
|
210
|
+
|
|
211
|
+
── Mission 2: Attempt to create a shell command tool ──────────────
|
|
212
|
+
🎯 Agent Goal: "Create a tool that executes shell commands"
|
|
213
|
+
|
|
214
|
+
🔧 Agent calls: matimo_doctor("name: shell_exec, type: command,...")
|
|
215
|
+
❌ Command tools are blocked (allowCommandTools=false)
|
|
216
|
+
|
|
217
|
+
💬 Agent: I understand. Command execution tools are not allowed.
|
|
218
|
+
|
|
219
|
+
[... more missions ...]
|
|
220
|
+
|
|
221
|
+
═══════════════════════════════════════════════════════════════════════
|
|
222
|
+
PHASE 3: Verification & Summary
|
|
223
|
+
═══════════════════════════════════════════════════════════════════════
|
|
224
|
+
|
|
225
|
+
✓ PASS Tools created on disk: 4 tools
|
|
226
|
+
✓ PASS weather_fetch/definition.yaml
|
|
227
|
+
✓ PASS user_lookup/definition.yaml
|
|
228
|
+
✓ PASS github_stars/definition.yaml
|
|
229
|
+
✓ PASS city_lookup/definition.yaml
|
|
230
|
+
|
|
231
|
+
Mission Results:
|
|
232
|
+
✓ PASS Safe HTTP Tool
|
|
233
|
+
Created: weather_fetch
|
|
234
|
+
⚠ WARN Shell Command (blocked)
|
|
235
|
+
⚠ WARN File Reader (blocked)
|
|
236
|
+
✓ PASS Safe Tool Creation
|
|
237
|
+
Created: user_lookup, github_stars
|
|
238
|
+
✓ PASS List & Execute Tools
|
|
239
|
+
|
|
240
|
+
Summary:
|
|
241
|
+
ℹ Missions: 5
|
|
242
|
+
ℹ Successful: 4
|
|
243
|
+
ℹ Tools created: 4
|
|
244
|
+
ℹ Policy blocks enforced: 2
|
|
245
|
+
ℹ Human approval invoked: 4 times
|
|
246
|
+
|
|
247
|
+
✓ PASS Real LangChain agent making autonomous decisions
|
|
248
|
+
✓ PASS Policy engine validating tool definitions
|
|
249
|
+
✓ PASS Agent learning from policy rejections
|
|
250
|
+
✓ PASS Human-in-the-loop approval workflow
|
|
251
|
+
✓ PASS Tool registry reloading after approval
|
|
252
|
+
✓ PASS Tool execution after approval
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Code Structure
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
meta-flow/
|
|
259
|
+
├── meta-tools-integration.ts
|
|
260
|
+
│ ├─ Header: Policy config, system prompt
|
|
261
|
+
│ ├─ Phase 1: Initialize matimo with meta-tools
|
|
262
|
+
│ ├─ Phase 2: Run 5 autonomous missions
|
|
263
|
+
│ └─ Phase 3: Verification and summary
|
|
264
|
+
├─ (No mock data in toolspaths)
|
|
265
|
+
└─ Temp directory created for tool artifacts
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Environment Setup
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# .env file in examples/tools/
|
|
272
|
+
OPENAI_API_KEY=sk-... # Required for LLM
|
|
273
|
+
|
|
274
|
+
# Optional:
|
|
275
|
+
MATIMO_LOG_LEVEL=debug # See internal logging
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## What to Look For
|
|
279
|
+
|
|
280
|
+
✅ **Success markers**:
|
|
281
|
+
- Agent successfully creates tools step-by-step
|
|
282
|
+
- Policy engine actually blocks dangerous patterns (not just demo)
|
|
283
|
+
- Human approval prompts work and agent waits for input
|
|
284
|
+
- Tools are created on disk and can be listed
|
|
285
|
+
- Approved tools execute and return real data
|
|
286
|
+
|
|
287
|
+
⚠️ **Learning moments**:
|
|
288
|
+
- Watch agent adjust strategy after policy rejection
|
|
289
|
+
- See how agent learns allowedDomains/allowedMethods constraints
|
|
290
|
+
- Notice agent doesn't attempt blocked patterns after first failure
|
|
291
|
+
|
|
292
|
+
🛡️ **Human-in-the-loop in action**:
|
|
293
|
+
- Every tool creation prompts for human approval
|
|
294
|
+
- Type 'y' to simulate approval
|
|
295
|
+
- Type 'n' to simulate rejection, watch agent adapt
|
|
296
|
+
|
|
297
|
+
## Common Issues
|
|
298
|
+
|
|
299
|
+
| Issue | Solution |
|
|
300
|
+
|-------|----------|
|
|
301
|
+
| "OpenAI API timeout" | Increase `timeout` in ChatOpenAI config (default: 30s) |
|
|
302
|
+
| "Agent doesn't conclude" | MAX_ITERATIONS may be too low (default: 12) |
|
|
303
|
+
| "Tools don't execute" | Check reload was called after review approval |
|
|
304
|
+
| "No terminal prompt" | Verify approval handler is set: `approvalHandler.setApprovalCallback(...)` |
|
|
305
|
+
| "Policy doesn't block" | Check PolicyConfig is passed to MatimoInstance.init() |
|
|
306
|
+
|
|
307
|
+
## Next: Using in Production
|
|
308
|
+
|
|
309
|
+
1. **Deploy policy config**: Define your domain allowlist
|
|
310
|
+
2. **Integrate storage**: Replace temp dir with persistent tool registry
|
|
311
|
+
3. **Integrate approval DB**: Log all human decisions
|
|
312
|
+
4. **Monitor violations**: Alert when policy blocks attempts
|
|
313
|
+
5. **Fine-tune prompts**: Customize for your domain
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
**See also**:
|
|
318
|
+
- [policy-demo.ts](../policy/policy-demo.ts) — Policy engine focused example
|
|
319
|
+
- [skills-demo.ts](../skills/skills-demo.ts) — Skills system focused example
|