@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.
Files changed (42) hide show
  1. package/README.md +9 -0
  2. package/index.mjs +183 -0
  3. package/package.json +34 -0
  4. package/template/.dev.vars.example +11 -0
  5. package/template/AGENTS.md +67 -0
  6. package/template/CLAUDE.md +6 -0
  7. package/template/CUSTOMIZE.md +83 -0
  8. package/template/KNOWLEDGE.md +73 -0
  9. package/template/README.md +41 -0
  10. package/template/_gitignore +5 -0
  11. package/template/_package.json +33 -0
  12. package/template/_tsconfig.json +16 -0
  13. package/template/_wrangler.toml +22 -0
  14. package/template/agent.config.ts +113 -0
  15. package/template/knowledge/.gitkeep +0 -0
  16. package/template/knowledge/README.md +24 -0
  17. package/template/scripts/knowledge-ingest.mjs +86 -0
  18. package/template/src/agent-app.ts +77 -0
  19. package/template/src/worker.ts +128 -0
  20. package/template/tests/agent-app.test.ts +71 -0
  21. package/template/vitest.config.ts +7 -0
  22. package/template-chat/.dev.vars.example +18 -0
  23. package/template-chat/AGENTS.md +73 -0
  24. package/template-chat/CLAUDE.md +6 -0
  25. package/template-chat/CUSTOMIZE.md +88 -0
  26. package/template-chat/README.md +50 -0
  27. package/template-chat/_gitignore +6 -0
  28. package/template-chat/_package.json +38 -0
  29. package/template-chat/_tsconfig.json +16 -0
  30. package/template-chat/_wrangler.toml +38 -0
  31. package/template-chat/agent.config.ts +73 -0
  32. package/template-chat/declarations.d.ts +9 -0
  33. package/template-chat/migrations/0001_init.sql +109 -0
  34. package/template-chat/prompts/system.md +11 -0
  35. package/template-chat/public/index.html +236 -0
  36. package/template-chat/src/chat.ts +210 -0
  37. package/template-chat/src/db/schema.ts +70 -0
  38. package/template-chat/src/env.ts +33 -0
  39. package/template-chat/src/sandbox.ts +159 -0
  40. package/template-chat/src/worker.ts +58 -0
  41. package/template-chat/tests/chat-turn.e2e.test.ts +277 -0
  42. package/template-chat/vitest.config.ts +22 -0
@@ -0,0 +1,88 @@
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
4
+ with the DISCOVERY QUESTION it answers — answer the question, then make the
5
+ edit. The whole job is filling `agent.config.ts` + `prompts/system.md` (DATA)
6
+ and wiring credentials; the chat vertical itself is already assembled and
7
+ proven by the e2e test.
8
+
9
+ When every box is checked and `pnpm typecheck && pnpm test` are green and a
10
+ message round-trips on `pnpm dev`, the app is customized.
11
+
12
+ ---
13
+
14
+ ## ① Identity — `agent.config.ts` + `prompts/system.md`
15
+
16
+ Discovery: **Whose job does this agent do, in whose voice, under what hard rules?**
17
+
18
+ - [ ] Set `name` in `agent.config.ts` to the product/agent name.
19
+ - [ ] Rewrite `prompts/system.md` as the real persona: role + voice + remit +
20
+ hard rules. State intents, never implementations (no commands, no tool
21
+ scripts — the sandboxed agent picks its own tools).
22
+ - [ ] Keep the grounding rule (never fabricate; cite a record or NOT ON FILE).
23
+
24
+ ## ② Model + harness — `agent.config.ts` → `model`, `harness`
25
+
26
+ Discovery: **Which model answers by default, at what effort, on which harness?**
27
+
28
+ - [ ] Set `model.default` to a model your Tangle Router key can reach.
29
+ - [ ] Pick `harness` (`opencode` default; vendor-locked harnesses like
30
+ `claude-code` must pair with their own provider's models).
31
+ - [ ] Leave per-turn overrides alone — the client can send `model`/`effort`
32
+ per request and `MODEL_NAME` overrides without a redeploy.
33
+
34
+ ## ③ Infrastructure — `wrangler.toml` + `.dev.vars` + `migrations/`
35
+
36
+ Discovery: **Where does this app live and what may it spend?**
37
+
38
+ - [ ] `wrangler d1 create <name>` → paste `database_id` into `wrangler.toml`.
39
+ - [ ] Copy `.dev.vars.example` → `.dev.vars`; fill `BETTER_AUTH_SECRET`,
40
+ `TANGLE_API_KEY`, `SANDBOX_API_KEY`, `SANDBOX_GATEWAY_URL`.
41
+ - [ ] `pnpm db:migrate:local` — applies `migrations/0001_init.sql` (auth +
42
+ chat + turn buffer). The e2e test executes this same file, so it cannot
43
+ silently drift from the schema.
44
+ - [ ] R2 stays commented out unless the product stores artifacts.
45
+
46
+ ## ④ Prove the loop — `pnpm dev`
47
+
48
+ Discovery: **Does a real message round-trip through a real box?**
49
+
50
+ - [ ] `pnpm dev`, open http://localhost:8787, sign up, send a message.
51
+ - [ ] Confirm: streamed text renders live, the transcript reloads with parts
52
+ and a usage receipt, and a second turn continues the same agent session.
53
+ - [ ] Kill the tab mid-turn, reopen the thread — the persisted row is intact
54
+ (the turn keeps running server-side and buffers for replay).
55
+
56
+ ## ⑤ The product UI — replace the dev page
57
+
58
+ Discovery: **What does the real surface look like?**
59
+
60
+ - [ ] `public/index.html` is the dev harness, not the product. Build the real
61
+ surface in React on `@tangle-network/agent-app/web-react`:
62
+ `ChatComposer` (`onSendParts` + `onAttach`), `ChatMessages`,
63
+ `streamChatTurn` (start + resume against `/api/chat/replay/:turnId`),
64
+ `useChatInteractions` + `InteractionQuestionCard`/`InteractionPlanCard`
65
+ for the ask channel this server already exposes.
66
+ - [ ] Keep the wire contract: `chatTurnRequestInit` from web-react serializes
67
+ exactly what `createChatTurnRoutes` parses.
68
+
69
+ ## ⑥ Extend, don't fork
70
+
71
+ Discovery: **What does this product add beyond the vertical?**
72
+
73
+ - [ ] Billing/audit/titles → `onTurnComplete` seam in `src/chat.ts`.
74
+ - [ ] PII scrubbing before persistence → `transformFinalText` +
75
+ `@tangle-network/agent-app/redact`.
76
+ - [ ] Structured agent writes (proposals, records) → the shell's `/tools`
77
+ side channel, never prose parsing.
78
+ - [ ] Multi-user teams → `@tangle-network/agent-app/teams` and pass your
79
+ workspace table to `createChatTables({ workspaceTable })` (new migration).
80
+
81
+ ## ⑦ Verify
82
+
83
+ Discovery: **Does the customized app hold its contract?**
84
+
85
+ - [ ] `pnpm typecheck` — clean.
86
+ - [ ] `pnpm test` — green (the e2e turn gate + fail-closed auth).
87
+ - [ ] `pnpm dev` — a real turn round-trips end to end.
88
+ - [ ] `pnpm deploy` + `pnpm db:migrate` when it's real.
@@ -0,0 +1,50 @@
1
+ # __PROJECT_NAME__
2
+
3
+ A multimodal chat agent product scaffolded with `create-agent-app --chat`, built
4
+ on [`@tangle-network/agent-app`](https://github.com/tangle-network/agent-app):
5
+ the whole server chat vertical — better-auth sessions, thread/message
6
+ persistence with typed parts + usage receipts, streaming turns with buffered
7
+ replay, file uploads, human-in-the-loop asks — assembled from shell factories.
8
+ The agent itself runs in a Tangle sandbox (a full harness: skills, tools, bash,
9
+ MCP); this app coordinates UI, durability, and access around it.
10
+
11
+ ## Layout
12
+
13
+ | Path | Surface | Edit when |
14
+ |---|---|---|
15
+ | `agent.config.ts` | DATA — name, model default, harness, ask kinds | defining the agent |
16
+ | `prompts/system.md` | DATA — the persona (imported as a Text module) | shaping behavior |
17
+ | `src/chat.ts` | CODE — the composer (factories → the chat vertical) | extending seams |
18
+ | `src/sandbox.ts` | CODE — the sandbox lane (boxes, credentials, profile) | provisioning changes |
19
+ | `src/worker.ts` | CODE — HTTP routing only | adding an endpoint |
20
+ | `src/db/schema.ts` | CODE — auth tables + `createChatTables()` | schema changes (+ migration) |
21
+ | `migrations/` | SQL the e2e test executes for real | schema changes |
22
+ | `public/index.html` | the dev chat page (not the product UI) | never — replace it (CUSTOMIZE ⑤) |
23
+ | `tests/` | the e2e turn gate this app ships with | extending coverage |
24
+
25
+ ## Get started
26
+
27
+ ```bash
28
+ pnpm install
29
+ pnpm typecheck && pnpm test # green before you edit anything
30
+ ```
31
+
32
+ Then walk the trail:
33
+
34
+ 1. `AGENTS.md` — the behavior contract (you are customizing a chat agent-app).
35
+ 2. `CUSTOMIZE.md` — the ordered fill-checklist, from persona to deploy.
36
+
37
+ ## Scripts
38
+
39
+ - `pnpm dev` — run the worker + dev page locally (fill `wrangler.toml` + `.dev.vars` first).
40
+ - `pnpm db:migrate:local` / `pnpm db:migrate` — apply `migrations/` to D1.
41
+ - `pnpm typecheck` — `tsc --noEmit`.
42
+ - `pnpm test` — vitest, including the end-to-end turn gate (fake sandbox
43
+ producer → streamed turn → persisted transcript → replay).
44
+ - `pnpm deploy` — `wrangler deploy`.
45
+
46
+ ## Invariants
47
+
48
+ Identity comes from the session, never a request body. Inaccessible threads
49
+ read as 404. No mock agent — missing sandbox credentials fail loud. See
50
+ `AGENTS.md` for the full contract.
@@ -0,0 +1,6 @@
1
+ node_modules/
2
+ dist/
3
+ .wrangler/
4
+ .dev.vars
5
+ *.local
6
+ .DS_Store
@@ -0,0 +1,38 @@
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
+ "db:migrate": "wrangler d1 migrations apply __PACKAGE_NAME__ --remote",
10
+ "db:migrate:local": "wrangler d1 migrations apply __PACKAGE_NAME__ --local",
11
+ "typecheck": "tsc --noEmit",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest"
14
+ },
15
+ "dependencies": {
16
+ "@tangle-network/agent-app": "__AGENT_APP_VERSION__",
17
+ "@tangle-network/agent-interface": "0.36.0",
18
+ "@tangle-network/agent-runtime": "0.109.0",
19
+ "@tangle-network/sandbox": "0.15.2",
20
+ "better-auth": "^1.6.16",
21
+ "drizzle-orm": "^0.45.2"
22
+ },
23
+ "peerDependencies": {
24
+ "@tangle-network/agent-eval": "0.135.1",
25
+ "@tangle-network/agent-integrations": ">=0.44.0"
26
+ },
27
+ "devDependencies": {
28
+ "@cloudflare/workers-types": "^4.20250620.0",
29
+ "@tangle-network/agent-eval": "0.135.1",
30
+ "@tangle-network/agent-integrations": "^0.44.0",
31
+ "@types/better-sqlite3": "^7.6.13",
32
+ "@types/node": "^25.6.0",
33
+ "better-sqlite3": "^12.10.0",
34
+ "typescript": "^5.7.0",
35
+ "vitest": "^3.0.0",
36
+ "wrangler": "^4.0.0"
37
+ }
38
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "noEmit": true,
13
+ "types": ["@cloudflare/workers-types/2023-07-01", "node"]
14
+ },
15
+ "include": ["agent.config.ts", "declarations.d.ts", "src", "tests", "vitest.config.ts"]
16
+ }
@@ -0,0 +1,38 @@
1
+ name = "__PACKAGE_NAME__"
2
+ main = "src/worker.ts"
3
+ compatibility_date = "2025-01-01"
4
+ compatibility_flags = ["nodejs_compat"]
5
+
6
+ # The dev chat page. Assets are served before the worker; /api/* falls
7
+ # through to src/worker.ts.
8
+ [assets]
9
+ directory = "public"
10
+
11
+ # D1 holds everything durable: auth, threads/messages, the turn buffer.
12
+ # Setup: `wrangler d1 create __PACKAGE_NAME__`, paste the database_id, then
13
+ # `pnpm db:migrate:local` (dev) / `pnpm db:migrate` (production).
14
+ [[d1_databases]]
15
+ binding = "DB"
16
+ database_name = "__PACKAGE_NAME__"
17
+ database_id = "REPLACE_WITH_D1_DATABASE_ID"
18
+ migrations_dir = "migrations"
19
+
20
+ # R2 is OFF by default — uncomment (and the ARTIFACTS binding in src/env.ts)
21
+ # only when the product needs artifact/object storage.
22
+ # [[r2_buckets]]
23
+ # binding = "ARTIFACTS"
24
+ # bucket_name = "__PACKAGE_NAME__-artifacts"
25
+
26
+ # prompts/system.md is imported as a Text module (see declarations.d.ts;
27
+ # vitest mirrors this via the plugin in vitest.config.ts).
28
+ [[rules]]
29
+ type = "Text"
30
+ globs = ["**/*.md"]
31
+ fallthrough = true
32
+
33
+ # Secrets live in .dev.vars locally and `wrangler secret put` in production:
34
+ # BETTER_AUTH_SECRET, TANGLE_API_KEY, SANDBOX_API_KEY, SANDBOX_GATEWAY_URL.
35
+ # See .dev.vars.example for the full list.
36
+ [vars]
37
+ BETTER_AUTH_URL = "http://localhost:8787"
38
+ # MODEL_NAME = "REPLACE_WITH_MODEL" # overrides config.model.default
@@ -0,0 +1,73 @@
1
+ /**
2
+ * agent.config.ts — the DATA surface of this chat product.
3
+ *
4
+ * This is the ONE file (plus `prompts/system.md`) you fill to define who the
5
+ * agent is and how its turns execute. It is plain data consumed by
6
+ * `@tangle-network/agent-app`'s modules through typed seams — NOT behavior.
7
+ * Control flow lives in `src/` (the assembled chat vertical). See CUSTOMIZE.md
8
+ * for the ordered fill-checklist and AGENTS.md for the layering contract.
9
+ *
10
+ * Every field below is stubbed with a placeholder. Replace the placeholders;
11
+ * keep the shape. `pnpm typecheck` proves the shape; `pnpm test` proves the
12
+ * wiring end to end (fake sandbox producer → turn → persisted transcript).
13
+ */
14
+
15
+ import type { Harness } from '@tangle-network/agent-app/harness'
16
+ // Imported as a Text module: wrangler's `[[rules]]` and the vitest plugin in
17
+ // `vitest.config.ts` both load `.md` files as strings (see declarations.d.ts).
18
+ import systemPrompt from './prompts/system.md'
19
+
20
+ export const config = {
21
+ /** Product/agent name — cookie prefix, sandbox box names, email subjects. */
22
+ name: '__PROJECT_NAME__',
23
+
24
+ /**
25
+ * The system prompt, verbatim from `prompts/system.md`. State intents and
26
+ * hard rules, never implementations — the executing agent chooses its own
27
+ * tools at execution time (see AGENTS.md "prompts state intents").
28
+ */
29
+ systemPrompt,
30
+
31
+ model: {
32
+ /**
33
+ * Model used when the client doesn't pick one and the `MODEL_NAME` env
34
+ * var is unset. Any model your Tangle Router key can reach.
35
+ */
36
+ default: 'REPLACE_WITH_MODEL',
37
+ /**
38
+ * Models to try, in order, when the chosen model's upstream is DEAD
39
+ * (quota wall, 502, provider outage). Failover is reactive — zero cost
40
+ * until the preferred model actually fails — and every fallback is
41
+ * surfaced: the persisted row and billing receipt name the model that
42
+ * actually served, and the transcript gets a visible notice. Keep this
43
+ * list cross-provider (an outage usually takes a whole provider down)
44
+ * and review it with your default: a stale ladder degrades to today's
45
+ * behavior (the turn fails), never to a silent wrong-model answer.
46
+ */
47
+ fallbacks: ['gemini-2.5-flash-lite', 'gpt-5-mini'],
48
+ /** Default reasoning effort for turns that don't specify one. */
49
+ effort: 'auto',
50
+ },
51
+
52
+ /**
53
+ * The agent harness the sandbox runs (`opencode`, `claude-code`, `codex`,
54
+ * …). Vendor-locked harnesses reject foreign-provider models — the sandbox
55
+ * lane enforces that pairing server-side.
56
+ */
57
+ harness: 'opencode',
58
+
59
+ /**
60
+ * Which sidecar ask kinds this app renders as cards. Anything the agent
61
+ * asks outside this set is auto-declined so a turn never hangs waiting on
62
+ * a card no client will show.
63
+ */
64
+ interactions: { question: true, plan: true },
65
+ } as const satisfies {
66
+ name: string
67
+ systemPrompt: string
68
+ model: { default: string; fallbacks: readonly string[]; effort: 'auto' | 'low' | 'medium' | 'high' }
69
+ harness: Harness
70
+ interactions: { question?: boolean; permission?: boolean; plan?: boolean }
71
+ }
72
+
73
+ export type Config = typeof config
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Text-module declarations. `prompts/system.md` is imported as a plain string
3
+ * by `agent.config.ts`; wrangler loads it via the `[[rules]]` Text rule in
4
+ * wrangler.toml and vitest via the inline plugin in vitest.config.ts.
5
+ */
6
+ declare module '*.md' {
7
+ const text: string
8
+ export default text
9
+ }
@@ -0,0 +1,109 @@
1
+ -- 0001_init.sql — the complete schema for the assembled chat vertical.
2
+ -- Apply with: pnpm db:migrate:local (dev) / pnpm db:migrate (production D1).
3
+ --
4
+ -- Three blocks, each mirroring a factory the app composes. The e2e test in
5
+ -- tests/ executes THIS file against a real SQLite database and then drives
6
+ -- sign-up, thread CRUD, and a full streamed turn through it — if any block
7
+ -- drifts from the factory that owns it, the test fails before you ship.
8
+
9
+ -- ── better-auth (src/db/schema.ts users/sessions/accounts/verifications) ────
10
+
11
+ CREATE TABLE IF NOT EXISTS users (
12
+ id TEXT PRIMARY KEY,
13
+ name TEXT NOT NULL,
14
+ email TEXT NOT NULL UNIQUE,
15
+ email_verified INTEGER NOT NULL,
16
+ image TEXT,
17
+ created_at INTEGER NOT NULL,
18
+ updated_at INTEGER NOT NULL
19
+ );
20
+
21
+ CREATE TABLE IF NOT EXISTS sessions (
22
+ id TEXT PRIMARY KEY,
23
+ expires_at INTEGER NOT NULL,
24
+ token TEXT NOT NULL UNIQUE,
25
+ created_at INTEGER NOT NULL,
26
+ updated_at INTEGER NOT NULL,
27
+ ip_address TEXT,
28
+ user_agent TEXT,
29
+ user_id TEXT NOT NULL
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS accounts (
33
+ id TEXT PRIMARY KEY,
34
+ account_id TEXT NOT NULL,
35
+ provider_id TEXT NOT NULL,
36
+ user_id TEXT NOT NULL,
37
+ access_token TEXT,
38
+ refresh_token TEXT,
39
+ id_token TEXT,
40
+ access_token_expires_at INTEGER,
41
+ refresh_token_expires_at INTEGER,
42
+ scope TEXT,
43
+ password TEXT,
44
+ created_at INTEGER NOT NULL,
45
+ updated_at INTEGER NOT NULL
46
+ );
47
+
48
+ CREATE TABLE IF NOT EXISTS verifications (
49
+ id TEXT PRIMARY KEY,
50
+ identifier TEXT NOT NULL,
51
+ value TEXT NOT NULL,
52
+ expires_at INTEGER NOT NULL,
53
+ created_at INTEGER,
54
+ updated_at INTEGER
55
+ );
56
+
57
+ -- ── chat threads/messages (createChatTables() in src/db/schema.ts) ──────────
58
+ -- workspace_id is a plain column: this template ships single-user workspaces
59
+ -- (workspace id = user id). Moving to real teams later adds the FK via
60
+ -- createChatTables({ workspaceTable }).
61
+
62
+ CREATE TABLE IF NOT EXISTS thread (
63
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
64
+ workspace_id TEXT NOT NULL,
65
+ title TEXT NOT NULL,
66
+ category TEXT,
67
+ is_pinned INTEGER NOT NULL DEFAULT 0,
68
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
69
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
70
+ );
71
+ CREATE INDEX IF NOT EXISTS idx_thread_workspace ON thread (workspace_id);
72
+ CREATE INDEX IF NOT EXISTS idx_thread_workspace_updated ON thread (workspace_id, updated_at);
73
+
74
+ CREATE TABLE IF NOT EXISTS message (
75
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
76
+ thread_id TEXT NOT NULL REFERENCES thread(id) ON DELETE CASCADE,
77
+ role TEXT NOT NULL,
78
+ content TEXT NOT NULL,
79
+ parts TEXT DEFAULT '[]',
80
+ tool_name TEXT,
81
+ model TEXT,
82
+ input_tokens INTEGER,
83
+ output_tokens INTEGER,
84
+ reasoning_tokens INTEGER,
85
+ cache_read_tokens INTEGER,
86
+ cache_write_tokens INTEGER,
87
+ cost_usd REAL,
88
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
89
+ );
90
+ CREATE INDEX IF NOT EXISTS idx_message_thread ON message (thread_id);
91
+ CREATE INDEX IF NOT EXISTS idx_message_thread_created ON message (thread_id, created_at);
92
+
93
+ -- ── turn buffer (TURN_EVENTS_MIGRATION_SQL from @tangle-network/agent-app/stream)
94
+ -- Byte-for-byte the constant the D1 turn-event store expects; the e2e test
95
+ -- asserts this block matches the exported SQL so it cannot drift silently.
96
+
97
+ CREATE TABLE IF NOT EXISTS turn_events (
98
+ turnId TEXT NOT NULL,
99
+ seq INTEGER NOT NULL,
100
+ event TEXT NOT NULL,
101
+ PRIMARY KEY (turnId, seq)
102
+ );
103
+ CREATE TABLE IF NOT EXISTS turn_status (
104
+ turnId TEXT PRIMARY KEY,
105
+ status TEXT NOT NULL,
106
+ scopeId TEXT,
107
+ updatedAt TEXT NOT NULL
108
+ );
109
+ CREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);
@@ -0,0 +1,11 @@
1
+ # System prompt — replace this file with the real persona
2
+
3
+ You are the __PROJECT_NAME__ assistant.
4
+ Rewrite this paragraph as the real one-paragraph role + voice + remit: whose job this agent does, in whose voice, under what hard rules.
5
+ It is the spine of every turn.
6
+
7
+ Hard rules:
8
+
9
+ - Never fabricate a figure (price, identifier, clause, date). Cite a real record or say NOT ON FILE.
10
+ - Route every regulated or client-facing action to a named human for approval; propose, don't execute.
11
+ - State what you did and what evidence backs it. No filler.