@tangle-network/create-agent-app 0.44.36
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 +9 -0
- package/index.mjs +183 -0
- package/package.json +34 -0
- package/template/.dev.vars.example +11 -0
- package/template/AGENTS.md +67 -0
- package/template/CLAUDE.md +6 -0
- package/template/CUSTOMIZE.md +83 -0
- package/template/KNOWLEDGE.md +73 -0
- package/template/README.md +41 -0
- package/template/_gitignore +5 -0
- package/template/_package.json +33 -0
- package/template/_tsconfig.json +16 -0
- package/template/_wrangler.toml +22 -0
- package/template/agent.config.ts +113 -0
- package/template/knowledge/.gitkeep +0 -0
- package/template/knowledge/README.md +24 -0
- package/template/scripts/knowledge-ingest.mjs +86 -0
- package/template/src/agent-app.ts +77 -0
- package/template/src/worker.ts +128 -0
- package/template/tests/agent-app.test.ts +71 -0
- package/template/vitest.config.ts +7 -0
- package/template-chat/.dev.vars.example +18 -0
- package/template-chat/AGENTS.md +73 -0
- package/template-chat/CLAUDE.md +6 -0
- package/template-chat/CUSTOMIZE.md +88 -0
- package/template-chat/README.md +50 -0
- package/template-chat/_gitignore +6 -0
- package/template-chat/_package.json +38 -0
- package/template-chat/_tsconfig.json +16 -0
- package/template-chat/_wrangler.toml +38 -0
- package/template-chat/agent.config.ts +73 -0
- package/template-chat/declarations.d.ts +9 -0
- package/template-chat/migrations/0001_init.sql +109 -0
- package/template-chat/prompts/system.md +11 -0
- package/template-chat/public/index.html +236 -0
- package/template-chat/src/chat.ts +210 -0
- package/template-chat/src/db/schema.ts +70 -0
- package/template-chat/src/env.ts +33 -0
- package/template-chat/src/sandbox.ts +159 -0
- package/template-chat/src/worker.ts +58 -0
- package/template-chat/tests/chat-turn.e2e.test.ts +277 -0
- package/template-chat/vitest.config.ts +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @tangle-network/create-agent-app
|
|
2
|
+
|
|
3
|
+
Scaffold a new Tangle agent product on [`@tangle-network/agent-app`](https://github.com/tangle-network/agent-app):
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create @tangle-network/agent-app@latest my-agent
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Then `cd my-agent && pnpm install && pnpm typecheck && pnpm test`, and follow the generated `CUSTOMIZE.md` / `AGENTS.md`.
|
package/index.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// create-agent-app — scaffold a new Tangle agent product on @tangle-network/agent-app.
|
|
3
|
+
//
|
|
4
|
+
// Dependency-light by design: Node built-ins only. The CLI copies one template
|
|
5
|
+
// tree verbatim (`template/` by default, `template-chat/` with `--chat`),
|
|
6
|
+
// substitutes a small set of `__TOKEN__` placeholders, and renames
|
|
7
|
+
// files whose template name would otherwise interfere with tooling (a template's
|
|
8
|
+
// own `package.json` must not be read by the scaffolder's package manager; a
|
|
9
|
+
// template `gitignore` must not be applied to the scaffolder repo). The generated
|
|
10
|
+
// project's DATA surface is `agent.config.ts` + `knowledge/`; its CODE surface is
|
|
11
|
+
// `src/` (the chat route + composer). The breadcrumb docs (AGENTS.md / CLAUDE.md /
|
|
12
|
+
// CUSTOMIZE.md / KNOWLEDGE.md) ship inside the project so a coding agent that opens
|
|
13
|
+
// it walks the trail with zero external context.
|
|
14
|
+
|
|
15
|
+
import { cp, mkdir, readFile, readdir, rename, stat, writeFile } from 'node:fs/promises'
|
|
16
|
+
import { existsSync } from 'node:fs'
|
|
17
|
+
import { dirname, join, resolve } from 'node:path'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
|
|
20
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
21
|
+
// Template variants: the default tool-loop skeleton, and `--chat` — the
|
|
22
|
+
// assembled multimodal chat vertical (auth + chat-store + chat-routes +
|
|
23
|
+
// sandbox producer + uploads + replay) from `examples/chat-app.md`.
|
|
24
|
+
const TEMPLATES = {
|
|
25
|
+
default: join(HERE, 'template'),
|
|
26
|
+
chat: join(HERE, 'template-chat'),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { version: packageVersion } = JSON.parse(await readFile(join(HERE, 'package.json'), 'utf8'))
|
|
30
|
+
const AGENT_APP_RANGE = `^${packageVersion}`
|
|
31
|
+
|
|
32
|
+
// Template files renamed on materialization. A template cannot itself be named
|
|
33
|
+
// `package.json` / `.gitignore` / `tsconfig.json` without confusing the
|
|
34
|
+
// scaffolder repo's own tooling, so we prefix with `_` and restore on copy.
|
|
35
|
+
const RENAME = new Map([
|
|
36
|
+
['_package.json', 'package.json'],
|
|
37
|
+
['_gitignore', '.gitignore'],
|
|
38
|
+
['_tsconfig.json', 'tsconfig.json'],
|
|
39
|
+
['_wrangler.toml', 'wrangler.toml'],
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
const args = { _: [] }
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const a = argv[i]
|
|
46
|
+
if (a === '--name') args.name = argv[++i]
|
|
47
|
+
else if (a === '--agent-app-version') args.agentAppVersion = argv[++i]
|
|
48
|
+
else if (a === '--chat') args.template = 'chat'
|
|
49
|
+
else if (a === '--force') args.force = true
|
|
50
|
+
else if (a === '-h' || a === '--help') args.help = true
|
|
51
|
+
else if (!a.startsWith('-')) args._.push(a)
|
|
52
|
+
else throw new Error(`Unknown flag: ${a}`)
|
|
53
|
+
}
|
|
54
|
+
return args
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function usage() {
|
|
58
|
+
return [
|
|
59
|
+
'Usage: create-agent-app <target-dir> [options]',
|
|
60
|
+
'',
|
|
61
|
+
'Scaffolds a new Tangle agent product on @tangle-network/agent-app.',
|
|
62
|
+
'',
|
|
63
|
+
'Options:',
|
|
64
|
+
' --chat Scaffold the multimodal chat variant instead: the',
|
|
65
|
+
' assembled chat vertical (auth, thread/message store,',
|
|
66
|
+
' streaming turns + replay, uploads, agent asks) with',
|
|
67
|
+
' its own end-to-end test. Default: the tool-loop skeleton.',
|
|
68
|
+
' --name <name> Project name (default: the target dir basename).',
|
|
69
|
+
' --agent-app-version <range> @tangle-network/agent-app version (default: ' + AGENT_APP_RANGE + ').',
|
|
70
|
+
' --force Write into a non-empty directory.',
|
|
71
|
+
' -h, --help Show this help.',
|
|
72
|
+
'',
|
|
73
|
+
'After scaffolding:',
|
|
74
|
+
' cd <target-dir> && pnpm install',
|
|
75
|
+
' pnpm typecheck && pnpm test',
|
|
76
|
+
' # then follow CUSTOMIZE.md (each template ships its own fill-checklist)',
|
|
77
|
+
].join('\n')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Project name → a safe npm package name (lowercase, dashes, no scope chars).
|
|
81
|
+
function toPackageName(name) {
|
|
82
|
+
const cleaned = name
|
|
83
|
+
.trim()
|
|
84
|
+
.toLowerCase()
|
|
85
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
86
|
+
.replace(/^-+|-+$/g, '')
|
|
87
|
+
return cleaned || 'agent-app-product'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function applyTokens(content, tokens) {
|
|
91
|
+
let out = content
|
|
92
|
+
for (const [key, value] of Object.entries(tokens)) {
|
|
93
|
+
out = out.split(`__${key}__`).join(value)
|
|
94
|
+
}
|
|
95
|
+
return out
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Files we run token substitution on. Binary/asset files would be copied as-is;
|
|
99
|
+
// the template is text-only, but we gate on extension to stay safe.
|
|
100
|
+
const TEXT_EXT = /\.(ts|tsx|js|mjs|cjs|json|md|toml|txt|html|css|sql)$/i
|
|
101
|
+
const TEXT_BASENAMES = new Set(['_gitignore', '.gitignore', '_package.json', 'package.json'])
|
|
102
|
+
|
|
103
|
+
async function walk(dir, base = dir, out = []) {
|
|
104
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
105
|
+
const abs = join(dir, entry.name)
|
|
106
|
+
const rel = abs.slice(base.length + 1)
|
|
107
|
+
if (entry.isDirectory()) await walk(abs, base, out)
|
|
108
|
+
else out.push(rel)
|
|
109
|
+
}
|
|
110
|
+
return out
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main() {
|
|
114
|
+
const args = parseArgs(process.argv.slice(2))
|
|
115
|
+
if (args.help || (args._.length === 0 && !args.name)) {
|
|
116
|
+
process.stdout.write(usage() + '\n')
|
|
117
|
+
process.exit(args.help ? 0 : 1)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const targetDir = resolve(args._[0] ?? args.name)
|
|
121
|
+
const projectName = args.name ?? targetDir.split(/[\\/]/).pop()
|
|
122
|
+
const packageName = toPackageName(projectName)
|
|
123
|
+
const agentAppVersion = args.agentAppVersion ?? AGENT_APP_RANGE
|
|
124
|
+
const templateDir = TEMPLATES[args.template ?? 'default']
|
|
125
|
+
|
|
126
|
+
if (existsSync(targetDir)) {
|
|
127
|
+
const entries = await readdir(targetDir).catch(() => [])
|
|
128
|
+
const meaningful = entries.filter((e) => e !== '.git' && e !== '.DS_Store')
|
|
129
|
+
if (meaningful.length > 0 && !args.force) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Target directory not empty: ${targetDir}\n` +
|
|
132
|
+
`Pass --force to scaffold into it anyway (will not overwrite the .git dir).`,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
await mkdir(targetDir, { recursive: true })
|
|
137
|
+
|
|
138
|
+
const tokens = {
|
|
139
|
+
PROJECT_NAME: projectName,
|
|
140
|
+
PACKAGE_NAME: packageName,
|
|
141
|
+
AGENT_APP_VERSION: agentAppVersion,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const files = await walk(templateDir)
|
|
145
|
+
for (const rel of files) {
|
|
146
|
+
const src = join(templateDir, rel)
|
|
147
|
+
// Resolve any renamed path segments (only basenames are renamed).
|
|
148
|
+
const parts = rel.split(/[\\/]/)
|
|
149
|
+
const baseName = parts[parts.length - 1]
|
|
150
|
+
const outName = RENAME.get(baseName) ?? baseName
|
|
151
|
+
parts[parts.length - 1] = outName
|
|
152
|
+
const dest = join(targetDir, parts.join('/'))
|
|
153
|
+
|
|
154
|
+
await mkdir(dirname(dest), { recursive: true })
|
|
155
|
+
|
|
156
|
+
const isText = TEXT_EXT.test(baseName) || TEXT_BASENAMES.has(baseName)
|
|
157
|
+
if (isText) {
|
|
158
|
+
const raw = await readFile(src, 'utf8')
|
|
159
|
+
await writeFile(dest, applyTokens(raw, tokens))
|
|
160
|
+
} else {
|
|
161
|
+
await cp(src, dest)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
process.stdout.write(
|
|
166
|
+
[
|
|
167
|
+
`Scaffolded ${projectName} → ${targetDir}`,
|
|
168
|
+
'',
|
|
169
|
+
'Next:',
|
|
170
|
+
` cd ${targetDir}`,
|
|
171
|
+
' pnpm install',
|
|
172
|
+
' pnpm typecheck && pnpm test',
|
|
173
|
+
'',
|
|
174
|
+
'Then walk CUSTOMIZE.md (the fill-checklist) and AGENTS.md (the behavior contract).',
|
|
175
|
+
'',
|
|
176
|
+
].join('\n'),
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
main().catch((err) => {
|
|
181
|
+
process.stderr.write(`create-agent-app: ${err.message}\n`)
|
|
182
|
+
process.exit(1)
|
|
183
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tangle-network/create-agent-app",
|
|
3
|
+
"version": "0.44.36",
|
|
4
|
+
"description": "Scaffold a new Tangle agent product on @tangle-network/agent-app: the tool-loop skeleton (default) or, with --chat, the assembled multimodal chat vertical (auth, chat store, streaming turns with replay, uploads, agent asks) with its own end-to-end test — plus the agent-followable breadcrumb docs (AGENTS.md / CUSTOMIZE.md).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tangle",
|
|
7
|
+
"ai-agent",
|
|
8
|
+
"agent-framework",
|
|
9
|
+
"scaffold",
|
|
10
|
+
"create",
|
|
11
|
+
"cloudflare-workers"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/tangle-network/agent-app.git",
|
|
18
|
+
"directory": "create-agent-app"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"create-agent-app": "./index.mjs"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"index.mjs",
|
|
25
|
+
"template",
|
|
26
|
+
"template-chat"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copy to .dev.vars for local `wrangler dev`. Do NOT commit .dev.vars.
|
|
2
|
+
# Model resolves via resolveTangleModelConfig (see src/agent-app.ts).
|
|
3
|
+
|
|
4
|
+
# Tangle Router (OpenAI-compatible, default path):
|
|
5
|
+
TANGLE_API_KEY=
|
|
6
|
+
MODEL_NAME=
|
|
7
|
+
|
|
8
|
+
# BYOK Anthropic (set MODEL_PROVIDER=anthropic to use):
|
|
9
|
+
# MODEL_PROVIDER=anthropic
|
|
10
|
+
# ANTHROPIC_API_KEY=
|
|
11
|
+
# ANTHROPIC_BASE_URL=https://api.anthropic.com
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# AGENTS.md — you are customizing an agent-app
|
|
2
|
+
|
|
3
|
+
You are a coding agent working in a project generated by `create-agent-app`. This
|
|
4
|
+
project is a thin customization layer on top of `@tangle-network/agent-app` (the
|
|
5
|
+
shell) and the Tangle agent substrate (the engine). Walk this contract before you
|
|
6
|
+
touch anything. It is a checklist, not prose — follow it in order.
|
|
7
|
+
|
|
8
|
+
## 0. Orient
|
|
9
|
+
|
|
10
|
+
- [ ] Read this file end to end.
|
|
11
|
+
- [ ] Read `CUSTOMIZE.md` — the ordered fill-checklist. It is your task list.
|
|
12
|
+
- [ ] Read `KNOWLEDGE.md` — how the build-loop vs the act-gate work.
|
|
13
|
+
- [ ] Run `pnpm install && pnpm typecheck && pnpm test`. Confirm green BEFORE editing.
|
|
14
|
+
|
|
15
|
+
## 1. The one rule (layering)
|
|
16
|
+
|
|
17
|
+
> You change behavior by editing DATA and the COMPOSER — never by editing
|
|
18
|
+
> `@tangle-network/agent-app`.
|
|
19
|
+
|
|
20
|
+
- The shell (`@tangle-network/agent-app/*`) owns mechanism: the tool loop, the
|
|
21
|
+
agent→app tool side channel, the approval/proposal contract, the knowledge gate,
|
|
22
|
+
billing, crypto.
|
|
23
|
+
- The engine (`@tangle-network/agent-eval`, `agent-runtime`, `agent-integrations`)
|
|
24
|
+
is a peer dependency. Do not bundle it, do not fork it.
|
|
25
|
+
- You own: `agent.config.ts` (DATA) + `src/` (the COMPOSER + routes) + `knowledge/`
|
|
26
|
+
(DATA). That is the whole surface.
|
|
27
|
+
|
|
28
|
+
## 2. DATA vs CODE — know which file you're in
|
|
29
|
+
|
|
30
|
+
- [ ] DATA → `agent.config.ts`: identity, taxonomy, knowledge requirements/sources,
|
|
31
|
+
integrations, ui, model. Plain values. No control flow. No imports of engine
|
|
32
|
+
internals. If you're writing an `if`, you're in the wrong file.
|
|
33
|
+
- [ ] DATA → `knowledge/`: domain documents. Content, not code. No secrets.
|
|
34
|
+
- [ ] CODE → `src/agent-app.ts`: the COMPOSER. Wires config + bindings into the
|
|
35
|
+
shell's seams. Override ONE handler here only when the preset genuinely can't
|
|
36
|
+
express your persistence — default to the preset.
|
|
37
|
+
- [ ] CODE → `src/worker.ts`: the chat route. Extend the route; recover trusted
|
|
38
|
+
per-turn context from real auth. Do not move domain values here.
|
|
39
|
+
|
|
40
|
+
## 3. Invariants — fail-closed, never relax
|
|
41
|
+
|
|
42
|
+
- [ ] HUMAN-IN-THE-LOOP: every regulated proposal type (`config.taxonomy.regulatedTypes`)
|
|
43
|
+
is routed to a named human and CANNOT auto-execute. Keep regulated types in
|
|
44
|
+
`regulatedTypes`. Never downgrade a regulated action to an immediate tool.
|
|
45
|
+
- [ ] `regulatedTypes` ⊆ `proposalTypes`. The test enforces this; keep it green.
|
|
46
|
+
- [ ] GROUNDING: never fabricate a domain figure (price, coverage, clause, id). A
|
|
47
|
+
real record or an explicit "NOT ON FILE". The persona fragment says this — keep it.
|
|
48
|
+
- [ ] TRUSTED CONTEXT: `userId` / `workspaceId` / `threadId` come from the server
|
|
49
|
+
session, NEVER from model tool args. The model must not forge identity.
|
|
50
|
+
- [ ] SOURCES ALWAYS RECORDED, PROPOSALS GATED: in the knowledge loop, grounding is
|
|
51
|
+
never dropped; a low-confidence knowledge PROPOSAL is. Propose, don't apply.
|
|
52
|
+
|
|
53
|
+
## 4. Verify (every change ends here)
|
|
54
|
+
|
|
55
|
+
- [ ] `pnpm typecheck` — clean. Proves `agent.config.ts` matches the shell contract.
|
|
56
|
+
- [ ] `pnpm test` — green. Proves the composer wiring + invariants.
|
|
57
|
+
- [ ] `pnpm knowledge:ingest` — enumerates your docs + sources (DRY) without error.
|
|
58
|
+
- [ ] For a real deploy: `pnpm dev` (wrangler) after filling `wrangler.toml`.
|
|
59
|
+
|
|
60
|
+
If any of these is red, you are not done. Do not weaken a test to pass.
|
|
61
|
+
|
|
62
|
+
## 5. The schema floor
|
|
63
|
+
|
|
64
|
+
`agent.config.ts` is validated by the TypeScript type `AgentAppConfig` from
|
|
65
|
+
`@tangle-network/agent-app/config`. A non-TS agent can read the same shape from its
|
|
66
|
+
JSON Schema: `agentAppConfigJsonSchema` (exported from that subpath). That schema +
|
|
67
|
+
this file + `CUSTOMIZE.md` are the complete spec for what you may put where.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# CUSTOMIZE.md — fill this project, in order
|
|
2
|
+
|
|
3
|
+
This is the trail. Walk it top to bottom. Each step is a checklist item paired with
|
|
4
|
+
the DISCOVERY QUESTION it answers — answer the question, then make the edit. The
|
|
5
|
+
whole job is filling `agent.config.ts` (DATA) and seeding `knowledge/` (DATA). You
|
|
6
|
+
touch `src/` only at step ⑤ and only if the preset can't express your stack.
|
|
7
|
+
|
|
8
|
+
When every box is checked and `pnpm typecheck && pnpm test && pnpm knowledge:ingest`
|
|
9
|
+
are green, the agent is customized.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ① Identity — `agent.config.ts` → `identity`
|
|
14
|
+
|
|
15
|
+
Discovery: **Whose job does this agent do, in whose voice, under what hard rules?**
|
|
16
|
+
|
|
17
|
+
- [ ] Set `identity.name` to the product/agent name.
|
|
18
|
+
- [ ] Rewrite `identity.persona` as the real one-paragraph role + voice + remit. This
|
|
19
|
+
is the spine of the system prompt.
|
|
20
|
+
- [ ] Add hard rules + standing-workflow summaries to `systemPromptFragments`.
|
|
21
|
+
- [ ] Keep/edit the grounding rule and the `not-advice` disclaimer. Add any
|
|
22
|
+
regulatory notice your domain requires under `disclaimers`.
|
|
23
|
+
|
|
24
|
+
## ② Taxonomy — `agent.config.ts` → `taxonomy`
|
|
25
|
+
|
|
26
|
+
Discovery: **Which actions change client state or are legally gated, so a certified
|
|
27
|
+
human must approve before they execute?**
|
|
28
|
+
|
|
29
|
+
- [ ] List every action the agent can PROPOSE in `proposalTypes`.
|
|
30
|
+
- [ ] Put the regulated/state-changing subset in `regulatedTypes`. These cannot
|
|
31
|
+
auto-execute — they fail-closed to the approval queue.
|
|
32
|
+
- [ ] Confirm `regulatedTypes` ⊆ `proposalTypes` (the test checks this).
|
|
33
|
+
|
|
34
|
+
## ③ Knowledge requirements (the ACT gate) — `agent.config.ts` → `knowledge.requirements`
|
|
35
|
+
|
|
36
|
+
Discovery: **What is the minimum the agent must have GROUNDED before it's allowed to
|
|
37
|
+
propose? What facts gate the loop?**
|
|
38
|
+
|
|
39
|
+
- [ ] For each gating fact, add a `KnowledgeRequirementSpec` with a declarative
|
|
40
|
+
`satisfiedBy`:
|
|
41
|
+
- a config field is set → `{ config: 'dot.path', nonEmpty: true }`
|
|
42
|
+
- rows exist in a workspace-scoped table → `{ table: 'name', minRows: N, statusIn: [...] }`
|
|
43
|
+
- combine with `{ anyOf: [...] }` / `{ allOf: [...] }`
|
|
44
|
+
- [ ] Use a `derive` function ONLY for a rule the declarative form can't express.
|
|
45
|
+
- [ ] Pick a real `category` / `acquisitionMode` / `importance` per spec (the
|
|
46
|
+
autocomplete lists the allowed values).
|
|
47
|
+
|
|
48
|
+
## ④ Domain docs + research sources — `knowledge/` + `agent.config.ts` → `knowledge.sources`
|
|
49
|
+
|
|
50
|
+
Discovery: **What does the agent need to READ to be grounded, and where does fresh
|
|
51
|
+
knowledge come from?**
|
|
52
|
+
|
|
53
|
+
- [ ] Drop your real domain documents into `knowledge/` (md/txt/json, one topic per
|
|
54
|
+
file). See `knowledge/README.md`.
|
|
55
|
+
- [ ] List external research sources in `knowledge.sources` (URLs, regulation feeds,
|
|
56
|
+
integration refs) with a `kind`.
|
|
57
|
+
- [ ] Tune `knowledge.loop` (`goal`, `minConfidence`, `freshness`) — see KNOWLEDGE.md.
|
|
58
|
+
|
|
59
|
+
## ⑤ Integrations — `agent.config.ts` → `integrations.enabled` (+ `src/` only if needed)
|
|
60
|
+
|
|
61
|
+
Discovery: **Which CRMs / data sources / messaging channels does the workflow touch?**
|
|
62
|
+
|
|
63
|
+
- [ ] Add the `@tangle-network/agent-integrations` catalog kinds to `enabled`.
|
|
64
|
+
- [ ] Only if the house preset can't persist your data: override a single handler in
|
|
65
|
+
`src/agent-app.ts`. Default to the preset; do not fork the shell.
|
|
66
|
+
|
|
67
|
+
## ⑥ Ingest — `pnpm knowledge:ingest`
|
|
68
|
+
|
|
69
|
+
Discovery: **Did the loop pick up exactly the docs and sources I expect?**
|
|
70
|
+
|
|
71
|
+
- [ ] Run `pnpm knowledge:ingest` (DRY). Confirm the listed docs + sources match.
|
|
72
|
+
- [ ] Wire a model-backed driver + decider, then `pnpm knowledge:ingest --run` to
|
|
73
|
+
drive the acquisition loop (see KNOWLEDGE.md).
|
|
74
|
+
|
|
75
|
+
## ⑦ Verify
|
|
76
|
+
|
|
77
|
+
Discovery: **Does the customized agent hold its contract?**
|
|
78
|
+
|
|
79
|
+
- [ ] `pnpm typecheck` — clean.
|
|
80
|
+
- [ ] `pnpm test` — green (invariants + composer wiring).
|
|
81
|
+
- [ ] `pnpm knowledge:ingest` — enumerates without error.
|
|
82
|
+
- [ ] Fill `wrangler.toml` (D1 id, KV id, `MODEL_NAME`), run the preset migration,
|
|
83
|
+
then `pnpm dev` to exercise `/chat` for real.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# KNOWLEDGE.md — the build-loop and the act-gate
|
|
2
|
+
|
|
3
|
+
Two different things share the word "knowledge". Keep them straight.
|
|
4
|
+
|
|
5
|
+
## The two loops
|
|
6
|
+
|
|
7
|
+
- BUILD LOOP (acquire) — `pnpm knowledge:ingest` → `@tangle-network/agent-app/knowledge-loop`.
|
|
8
|
+
Reads `knowledge/` + `agent.config.ts` `knowledge.sources`, researches, and
|
|
9
|
+
PROPOSES grounded knowledge pages. Source-grounded, propose-don't-apply.
|
|
10
|
+
- ACT GATE (block) — `knowledge.requirements` in `agent.config.ts`, scored at runtime
|
|
11
|
+
by the Cloudflare preset's `KnowledgeStateAccessor`. Decides whether the agent
|
|
12
|
+
KNOWS enough to be allowed to propose an action.
|
|
13
|
+
|
|
14
|
+
Build fills the well; the gate decides if the well is deep enough to act. They are
|
|
15
|
+
configured separately and run at different times (ingest is offline/Node; the gate
|
|
16
|
+
is per-request).
|
|
17
|
+
|
|
18
|
+
## Build loop — how to drive it
|
|
19
|
+
|
|
20
|
+
- [ ] Put domain docs in `knowledge/` and external sources in `knowledge.sources`.
|
|
21
|
+
- [ ] `pnpm knowledge:ingest` (DRY) — confirms inputs without spending model calls.
|
|
22
|
+
- [ ] Wire a model-backed `driver` and run `--run`:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createKnowledgeLoop } from '@tangle-network/agent-app/knowledge-loop'
|
|
26
|
+
import { config } from '../agent.config'
|
|
27
|
+
|
|
28
|
+
const loop = createKnowledgeLoop(config.knowledge, {
|
|
29
|
+
root: 'knowledge', // a KB layout on disk (Node only)
|
|
30
|
+
driver: async ({ systemPrompt, userMessage }) =>
|
|
31
|
+
({ finalText: await callYourModel(systemPrompt, userMessage) }),
|
|
32
|
+
defaultMinConfidence: config.knowledge.loop?.minConfidence ?? 0.7,
|
|
33
|
+
})
|
|
34
|
+
const result = await loop.run()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Runs in Node (it touches the filesystem). NEVER on the Worker edge path — drive it
|
|
38
|
+
from `scripts/knowledge-ingest.mjs`, CI, or a sandbox/delegation context.
|
|
39
|
+
|
|
40
|
+
## Multimodal sources
|
|
41
|
+
|
|
42
|
+
The default source adapter is text. To ingest audio / video / image sources, pass a
|
|
43
|
+
`SourceAdapter` for that medium via `deps.adapters` — it is tried BEFORE text, so it
|
|
44
|
+
claims its media first:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
createKnowledgeLoop(config.knowledge, {
|
|
48
|
+
root: 'knowledge',
|
|
49
|
+
adapters: [audioSourceAdapter], // tried before the built-in text adapter
|
|
50
|
+
driver,
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Declare such sources in `agent.config.ts` with a `kind` your adapter recognizes
|
|
55
|
+
(e.g. `{ uri: 'vault://calls/2026-06.m4a', kind: 'audio' }`).
|
|
56
|
+
|
|
57
|
+
## Tuning the gate (judges / confidence / freshness)
|
|
58
|
+
|
|
59
|
+
The build loop accepts a pluggable decider — an agentic judge OR a deterministic /
|
|
60
|
+
sandbox check. Defaults to a reviewer that applies a candidate's proposal only when
|
|
61
|
+
`confidence >= minConfidence`; below it the proposal is dropped but its SOURCES are
|
|
62
|
+
still recorded.
|
|
63
|
+
|
|
64
|
+
- CONFIDENCE — raise `knowledge.loop.minConfidence` to demand stronger grounding
|
|
65
|
+
before a proposed page is accepted; lower it to accept more, weaker candidates.
|
|
66
|
+
- JUDGE — pass a custom `decide` (a `KnowledgeDecider`) to replace the default
|
|
67
|
+
reviewer with your own judge (LLM-as-judge, lint, or sandbox verification).
|
|
68
|
+
- FRESHNESS — set `knowledge.loop.freshness` (e.g. `static` / `session` / `daily`);
|
|
69
|
+
the decider receives it to decide whether cached knowledge is still valid.
|
|
70
|
+
|
|
71
|
+
Per-requirement freshness on the ACT gate is set on each
|
|
72
|
+
`KnowledgeRequirementSpec.freshness` — that controls how stale a satisfied
|
|
73
|
+
requirement may be before the gate stops crediting it.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
A Tangle agent product scaffolded with `create-agent-app`, built on
|
|
4
|
+
[`@tangle-network/agent-app`](https://github.com/tangle-network/agent-app) (the
|
|
5
|
+
application-shell framework) + the Cloudflare preset (D1 + Drizzle + KV).
|
|
6
|
+
|
|
7
|
+
## Layout
|
|
8
|
+
|
|
9
|
+
| Path | Surface | Edit when |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `agent.config.ts` | DATA — identity, taxonomy, knowledge gate, integrations | defining the agent |
|
|
12
|
+
| `knowledge/` | DATA — domain documents the agent grounds on | adding domain knowledge |
|
|
13
|
+
| `src/agent-app.ts` | CODE — the composer (config + bindings → shell seams) | overriding a handler |
|
|
14
|
+
| `src/worker.ts` | CODE — the chat route | extending the route / auth |
|
|
15
|
+
| `scripts/knowledge-ingest.mjs` | the build-loop entry (`pnpm knowledge:ingest`) | ingesting knowledge |
|
|
16
|
+
|
|
17
|
+
## Get started
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm install
|
|
21
|
+
pnpm typecheck && pnpm test
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then walk the trail:
|
|
25
|
+
|
|
26
|
+
1. `AGENTS.md` — the behavior contract (you are customizing an agent-app).
|
|
27
|
+
2. `CUSTOMIZE.md` — the ordered fill-checklist.
|
|
28
|
+
3. `KNOWLEDGE.md` — build-loop vs act-gate.
|
|
29
|
+
|
|
30
|
+
## Scripts
|
|
31
|
+
|
|
32
|
+
- `pnpm dev` — run the worker locally (fill `wrangler.toml` first).
|
|
33
|
+
- `pnpm typecheck` — `tsc --noEmit`.
|
|
34
|
+
- `pnpm test` — vitest.
|
|
35
|
+
- `pnpm knowledge:ingest` — enumerate + drive the knowledge acquisition loop.
|
|
36
|
+
- `pnpm deploy` — `wrangler deploy`.
|
|
37
|
+
|
|
38
|
+
## Invariants
|
|
39
|
+
|
|
40
|
+
Regulated proposals are human-approved and never auto-execute. Domain figures are
|
|
41
|
+
grounded in real records, never fabricated. See `AGENTS.md` for the full contract.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "wrangler dev",
|
|
8
|
+
"deploy": "wrangler deploy",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest",
|
|
12
|
+
"knowledge:ingest": "node scripts/knowledge-ingest.mjs"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@tangle-network/agent-app": "__AGENT_APP_VERSION__"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@tangle-network/agent-eval": "0.135.1",
|
|
19
|
+
"@tangle-network/agent-integrations": ">=0.44.0",
|
|
20
|
+
"@tangle-network/agent-interface": "0.36.0",
|
|
21
|
+
"@tangle-network/agent-runtime": "0.109.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@tangle-network/agent-eval": "0.135.1",
|
|
25
|
+
"@tangle-network/agent-integrations": "^0.44.0",
|
|
26
|
+
"@tangle-network/agent-interface": "0.36.0",
|
|
27
|
+
"@tangle-network/agent-runtime": "0.109.0",
|
|
28
|
+
"@types/node": "^25.6.0",
|
|
29
|
+
"typescript": "^5.7.0",
|
|
30
|
+
"vitest": "^3.0.0",
|
|
31
|
+
"wrangler": "^4.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noUncheckedIndexedAccess": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"types": ["node"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["agent.config.ts", "src", "tests"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name = "__PACKAGE_NAME__"
|
|
2
|
+
main = "src/worker.ts"
|
|
3
|
+
compatibility_date = "2025-01-01"
|
|
4
|
+
compatibility_flags = ["nodejs_compat"]
|
|
5
|
+
|
|
6
|
+
# The house Cloudflare stack the preset composes: D1 + KV.
|
|
7
|
+
# Run `wrangler d1 create __PACKAGE_NAME__` and paste the database_id; create a
|
|
8
|
+
# KV namespace and paste its id. Then run the preset migration (see CUSTOMIZE.md ⑥).
|
|
9
|
+
|
|
10
|
+
[[d1_databases]]
|
|
11
|
+
binding = "DB"
|
|
12
|
+
database_name = "__PACKAGE_NAME__"
|
|
13
|
+
database_id = "REPLACE_WITH_D1_DATABASE_ID"
|
|
14
|
+
|
|
15
|
+
[[kv_namespaces]]
|
|
16
|
+
binding = "VAULT"
|
|
17
|
+
id = "REPLACE_WITH_KV_NAMESPACE_ID"
|
|
18
|
+
|
|
19
|
+
# Model resolves from env via resolveTangleModelConfig (TANGLE_API_KEY / MODEL_NAME).
|
|
20
|
+
# Put secrets in .dev.vars locally; `wrangler secret put` for production.
|
|
21
|
+
[vars]
|
|
22
|
+
MODEL_NAME = "REPLACE_WITH_MODEL"
|