speexor 0.1.0 → 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.
Files changed (46) hide show
  1. package/API-REFERENCE.md +96 -1
  2. package/ARCHITECTURE.md +84 -33
  3. package/BENCHMARKS.md +52 -0
  4. package/CHANGELOG.md +35 -4
  5. package/CODE-OF-CONDUCT.md +83 -83
  6. package/CONTRIBUTING.md +98 -98
  7. package/FAQ.md +105 -105
  8. package/GLOSSARY.md +33 -0
  9. package/LICENSE.md +21 -21
  10. package/PUBLISH.md +77 -77
  11. package/README.md +222 -8
  12. package/REFACTOR-LOG.md +40 -40
  13. package/ROADMAP.md +37 -15
  14. package/SECURITY-DEFAULTS.md +118 -0
  15. package/SECURITY.md +79 -79
  16. package/SUMMARY.md +31 -8
  17. package/TESTING.md +140 -140
  18. package/dist/{agent-5D3BVWNK.js → agent-D4BRWEOZ.js} +4 -4
  19. package/dist/agent-D4BRWEOZ.js.map +1 -0
  20. package/dist/{chunk-2F66BZYJ.js → chunk-2DX54KIM.js} +2 -2
  21. package/dist/chunk-2DX54KIM.js.map +1 -0
  22. package/dist/{chunk-B7WLHC4W.js → chunk-7VZHDGRQ.js} +2 -2
  23. package/dist/chunk-7VZHDGRQ.js.map +1 -0
  24. package/dist/{chunk-SXALZEOJ.js → chunk-AOFWQZWY.js} +2 -2
  25. package/dist/chunk-AOFWQZWY.js.map +1 -0
  26. package/dist/cli/index.js +4 -4
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/core/index.js +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/plugins/index.js +1 -1
  32. package/docs/SETUP.md +94 -94
  33. package/docs/TROUBLESHOOTING.md +113 -113
  34. package/docs/adr/0001-record-architecture-decisions.md +44 -0
  35. package/docs/adr/0002-plugin-architecture.md +53 -0
  36. package/docs/adr/0003-recursive-task-decomposition.md +57 -0
  37. package/docs/adr/0004-local-first-security.md +58 -0
  38. package/docs/adr/0005-data-directory-layout.md +69 -0
  39. package/examples/basic.yaml +61 -61
  40. package/package.json +103 -102
  41. package/schema/config.schema.json +119 -119
  42. package/speexor.config.yaml.example +30 -30
  43. package/dist/agent-5D3BVWNK.js.map +0 -1
  44. package/dist/chunk-2F66BZYJ.js.map +0 -1
  45. package/dist/chunk-B7WLHC4W.js.map +0 -1
  46. package/dist/chunk-SXALZEOJ.js.map +0 -1
@@ -0,0 +1,69 @@
1
+ # ADR-0005: User-Home Data Directory Layout (~/.speexor/)
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Speexor persists several kinds of local state: session data, task graphs, checkpoints, the decision log, extension caches, and secret references. Earlier drafts left the storage location implicit. A formal decision is needed on:
10
+
11
+ 1. **Location:** Should data live in the project directory (`.speexor/`) or the user home directory (`~/.speexor/`)?
12
+ 2. **Structure:** How should different data types (SQLite, JSON, binary checkpoints, vault references) be organized?
13
+ 3. **Isolation:** How do we prevent state leakage between different projects managed by Speexor?
14
+
15
+ ## Decision
16
+
17
+ ### Location: ~/.speexor/ (User Home)
18
+
19
+ All persistent state lives under `~/.speexor/` in the user's home directory, not project-local. Each managed project gets a subdirectory keyed by a content-hash of the repository URL combined with a short project ID:
20
+
21
+ ```
22
+ ~/.speexor/
23
+ ├── 1a2b3c4d-my-project/ # Per-project directory (hash-projectId)
24
+ │ ├── task-graph.sqlite # Task graph DAG store (SQLite)
25
+ │ ├── decision-log.sqlite # Decision Log (SQLite, read-only history)
26
+ │ ├── checkpoints/ # Session checkpoint snapshots (JSON)
27
+ │ │ ├── checkpoint-abc.json
28
+ │ │ └── checkpoint-def.json
29
+ │ ├── extensions/ # Downloaded/cached extension manifests
30
+ │ └── vault-refs.json # References into OS keychain (never secrets)
31
+ ├── registry-cache/ # Marketplace registry index cache
32
+ ├── logs/ # Global orchestrator logs
33
+ └── lock # Process lock file (prevents concurrent instances)
34
+ ```
35
+
36
+ Rationale for `~/.speexor/` over project-local:
37
+
38
+ - Data survives project directory deletion (user can delete a clone without losing the decision log).
39
+ - Multiple clones of the same repo share a single project directory (hash-based dedup).
40
+ - Cleaner user experience — no hidden directories scattered across the file system.
41
+ - Consistent with established patterns (`.aws/`, `.config/`, `.npm/`, etc.).
42
+
43
+ ### Storage Formats
44
+
45
+ | Data Type | Format | Rationale |
46
+ |------------------|---------|------------------------------------------------|
47
+ | Task graph | SQLite | Relational queries on DAG nodes/edges |
48
+ | Decision log | SQLite | Searchable, filterable history |
49
+ | Checkpoints | JSON | Human-readable, debuggable snapshots |
50
+ | Extension cache | JSON | Simple manifest storage, no query pattern |
51
+ | Vault refs | JSON | Lightweight reference store (values in keychain)|
52
+ | Session state | JSON | Synchronous read/write for simplicity |
53
+
54
+ ### Secrets Management
55
+
56
+ The `vault-refs.json` file contains only **references** (key names/paths) into the OS keychain. Actual secret values (API tokens, encryption keys) are stored via the OS keychain (macOS Keychain, Windows Credential Manager, Linux libsecret) and never written to disk in plaintext. The `secretsBackend` config option controls which backend is used.
57
+
58
+ ### Process Lock
59
+
60
+ A `lock` file at `~/.speexor/lock` prevents multiple Speexor processes from operating on the same data directory simultaneously. The lock is advisory — it contains the PID and is cleaned up on graceful shutdown. Stale locks from crashes are automatically detected and cleared on startup.
61
+
62
+ ## Consequences
63
+
64
+ - **Positive:** Data persists across project directory deletions; clean separation between projects.
65
+ - **Positive:** Hash-based project IDs prevent collisions and enable deduplication.
66
+ - **Positive:** SQLite for graph and decision log enables efficient queries vs. JSON scanning.
67
+ - **Negative:** User-home directory may not be writable in some locked-down enterprise environments (mitigation: `SPEEXOR_DATA_DIR` env var override).
68
+ - **Negative:** OS keychain dependency adds a setup step for headless/server environments.
69
+ - **Neutral:** Consistent with `speexor migrate-from-konduktor` migration path (FR-84).
@@ -1,61 +1,61 @@
1
- # Speexor Configuration — Basic Example
2
- # Agent Orchestrator for multi-AI coding agent orchestration
3
- #
4
- # Usage:
5
- # 1. Save this file as speexor.config.yaml in your project root
6
- # 2. Run `speexor start` to initialize and open the dashboard
7
- # 3. Run `speexor agent spawn --task <issue-id>` to spawn an agent
8
-
9
- version: "1"
10
-
11
- projects:
12
- # --- Single project, single agent ---
13
- - name: brainclash
14
- repository: https://github.com/superdevids/brainclash
15
- provider:
16
- primary: opencode
17
- fallback:
18
- - claude-code
19
- - aider
20
- concurrentLimit: 3
21
-
22
- # Automated reactions to CI and PR events
23
- reactions:
24
- ci-failed:
25
- auto: true
26
- action: fix
27
- retries: 3
28
- escalateAfter: 30
29
- changes-requested:
30
- auto: true
31
- action: fix
32
- retries: 2
33
- escalateAfter: 60
34
- approved-and-green:
35
- auto: false
36
- action: notify
37
- retries: 0
38
- escalateAfter: 0
39
-
40
- # --- Second project with different provider ---
41
- - name: kata-netizen
42
- repository: https://github.com/superdevids/kata-netizen
43
- provider:
44
- primary: claude-code
45
- concurrentLimit: 2
46
- reactions:
47
- ci-failed:
48
- auto: true
49
- action: fix
50
- retries: 3
51
- escalateAfter: 30
52
- changes-requested:
53
- auto: true
54
- action: fix
55
- retries: 2
56
- escalateAfter: 45
57
- approved-and-green:
58
- auto: false
59
- action: notify
60
- retries: 0
61
- escalateAfter: 0
1
+ # Speexor Configuration — Basic Example
2
+ # Agent Orchestrator for multi-AI coding agent orchestration
3
+ #
4
+ # Usage:
5
+ # 1. Save this file as speexor.config.yaml in your project root
6
+ # 2. Run `speexor start` to initialize and open the dashboard
7
+ # 3. Run `speexor agent spawn --task <issue-id>` to spawn an agent
8
+
9
+ version: "1"
10
+
11
+ projects:
12
+ # --- Single project, single agent ---
13
+ - name: brainclash
14
+ repository: https://github.com/superdevids/brainclash
15
+ provider:
16
+ primary: opencode
17
+ fallback:
18
+ - claude-code
19
+ - aider
20
+ concurrentLimit: 3
21
+
22
+ # Automated reactions to CI and PR events
23
+ reactions:
24
+ ci-failed:
25
+ auto: true
26
+ action: fix
27
+ retries: 3
28
+ escalateAfter: 30
29
+ changes-requested:
30
+ auto: true
31
+ action: fix
32
+ retries: 2
33
+ escalateAfter: 60
34
+ approved-and-green:
35
+ auto: false
36
+ action: notify
37
+ retries: 0
38
+ escalateAfter: 0
39
+
40
+ # --- Second project with different provider ---
41
+ - name: kata-netizen
42
+ repository: https://github.com/superdevids/kata-netizen
43
+ provider:
44
+ primary: claude-code
45
+ concurrentLimit: 2
46
+ reactions:
47
+ ci-failed:
48
+ auto: true
49
+ action: fix
50
+ retries: 3
51
+ escalateAfter: 30
52
+ changes-requested:
53
+ auto: true
54
+ action: fix
55
+ retries: 2
56
+ escalateAfter: 45
57
+ approved-and-green:
58
+ auto: false
59
+ action: notify
60
+ retries: 0
61
+ escalateAfter: 0
package/package.json CHANGED
@@ -1,102 +1,103 @@
1
- {
2
- "name": "speexor",
3
- "version": "0.1.0",
4
- "description": "Speexor — Agent Orchestrator for multi-AI coding agent orchestration across repositories",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "bin": {
10
- "speexor": "dist/cli/index.js"
11
- },
12
- "exports": {
13
- ".": {
14
- "import": "./dist/index.js",
15
- "types": "./dist/index.d.ts"
16
- },
17
- "./core": {
18
- "import": "./dist/core/index.js",
19
- "types": "./dist/core/index.d.ts"
20
- },
21
- "./cli": {
22
- "import": "./dist/cli/index.js",
23
- "types": "./dist/cli/index.d.ts"
24
- },
25
- "./plugins": {
26
- "import": "./dist/plugins/index.js",
27
- "types": "./dist/plugins/index.d.ts"
28
- }
29
- },
30
- "files": [
31
- "dist",
32
- "schema",
33
- "docs",
34
- "*.md",
35
- "examples",
36
- "speexor.config.yaml.example"
37
- ],
38
- "scripts": {
39
- "build": "tsup",
40
- "dev": "tsup --watch",
41
- "test": "vitest run",
42
- "test:watch": "vitest",
43
- "test:coverage": "vitest run --coverage",
44
- "typecheck": "tsc --noEmit",
45
- "lint": "biome check src/",
46
- "lint:fix": "biome check --write src/",
47
- "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
48
- },
49
- "dependencies": {
50
- "commander": "^12.0.0",
51
- "chalk": "^5.3.0",
52
- "conf": "^12.0.0",
53
- "dayjs": "^1.11.0",
54
- "debug": "^4.3.0",
55
- "eventemitter3": "^5.0.0",
56
- "execa": "^9.0.0",
57
- "globby": "^14.0.0",
58
- "node-emoji": "^2.1.0",
59
- "ora": "^8.0.0",
60
- "yaml": "^2.5.0",
61
- "zod": "^3.23.0"
62
- },
63
- "devDependencies": {
64
- "@biomejs/biome": "^2.5.1",
65
- "@types/debug": "^4.1.0",
66
- "@types/node": "^26.0.1",
67
- "@vitest/coverage-v8": "^2.1.9",
68
- "tsup": "^8.3.0",
69
- "typescript": "^5.7.0",
70
- "vite": "^8.1.0",
71
- "vitest": "^2.1.0"
72
- },
73
- "keywords": [
74
- "orchestrator",
75
- "ai-agent",
76
- "agent-orchestration",
77
- "coding-agent",
78
- "claude-code",
79
- "opencode",
80
- "aider",
81
- "codex",
82
- "multi-agent",
83
- "speexor"
84
- ],
85
- "publishConfig": {
86
- "access": "public"
87
- },
88
- "preferGlobal": true,
89
- "repository": {
90
- "type": "git",
91
- "url": "git+https://github.com/superdevids/speexjs.git",
92
- "directory": "packages/speexor"
93
- },
94
- "bugs": {
95
- "url": "https://github.com/superdevids/speexjs/issues"
96
- },
97
- "homepage": "https://github.com/superdevids/speexjs/tree/main/packages/speexor#readme",
98
- "license": "MIT",
99
- "engines": {
100
- "node": ">=18.0.0"
101
- }
102
- }
1
+ {
2
+ "name": "speexor",
3
+ "version": "0.2.0",
4
+ "description": "Speexor — Agent Orchestrator for multi-AI coding agent orchestration across repositories",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "speexor": "dist/cli/index.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./core": {
18
+ "import": "./dist/core/index.js",
19
+ "types": "./dist/core/index.d.ts"
20
+ },
21
+ "./cli": {
22
+ "import": "./dist/cli/index.js",
23
+ "types": "./dist/cli/index.d.ts"
24
+ },
25
+ "./plugins": {
26
+ "import": "./dist/plugins/index.js",
27
+ "types": "./dist/plugins/index.d.ts"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "schema",
33
+ "docs",
34
+ "*.md",
35
+ "examples",
36
+ "speexor.config.yaml.example"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "test:coverage": "vitest run --coverage",
44
+ "typecheck": "tsc --noEmit",
45
+ "lint": "biome check src/",
46
+ "lint:fix": "biome check --write src/",
47
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
48
+ },
49
+ "dependencies": {
50
+ "better-sqlite3": "^11.10.0",
51
+ "chalk": "^5.3.0",
52
+ "commander": "^12.0.0",
53
+ "conf": "^12.0.0",
54
+ "dayjs": "^1.11.0",
55
+ "debug": "^4.3.0",
56
+ "eventemitter3": "^5.0.0",
57
+ "execa": "^9.0.0",
58
+ "globby": "^14.0.0",
59
+ "node-emoji": "^2.1.0",
60
+ "ora": "^8.0.0",
61
+ "yaml": "^2.5.0",
62
+ "zod": "^3.23.0"
63
+ },
64
+ "devDependencies": {
65
+ "@biomejs/biome": "^2.5.1",
66
+ "@types/better-sqlite3": "^7.6.13",
67
+ "@types/debug": "^4.1.0",
68
+ "@types/node": "^26.0.1",
69
+ "@vitest/coverage-v8": "^2.1.9",
70
+ "tsup": "^8.3.0",
71
+ "typescript": "^5.7.0",
72
+ "vite": "^8.1.0",
73
+ "vitest": "^2.1.0"
74
+ },
75
+ "keywords": [
76
+ "orchestrator",
77
+ "ai-agent",
78
+ "agent-orchestration",
79
+ "coding-agent",
80
+ "claude-code",
81
+ "opencode",
82
+ "aider",
83
+ "codex",
84
+ "multi-agent",
85
+ "speexor"
86
+ ],
87
+ "publishConfig": {
88
+ "access": "public"
89
+ },
90
+ "preferGlobal": true,
91
+ "repository": {
92
+ "type": "git",
93
+ "url": "git+https://github.com/superdevids/speexor.git"
94
+ },
95
+ "bugs": {
96
+ "url": "https://github.com/superdevids/speexor/issues"
97
+ },
98
+ "homepage": "https://github.com/superdevids/speexor#readme",
99
+ "license": "MIT",
100
+ "engines": {
101
+ "node": ">=18.0.0"
102
+ }
103
+ }
@@ -1,119 +1,119 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://speexjs.dev/schemas/speexor-config.json",
4
- "title": "Speexor Configuration",
5
- "description": "Configuration schema for Speexor — Agent Orchestrator",
6
- "type": "object",
7
- "required": ["version", "projects"],
8
- "properties": {
9
- "version": {
10
- "type": "string",
11
- "enum": ["1"],
12
- "description": "Config schema version"
13
- },
14
- "projects": {
15
- "type": "array",
16
- "minItems": 1,
17
- "items": {
18
- "type": "object",
19
- "required": ["name", "repository", "provider"],
20
- "properties": {
21
- "name": {
22
- "type": "string",
23
- "description": "Project display name"
24
- },
25
- "repository": {
26
- "type": "string",
27
- "description": "Git repository URL or local path"
28
- },
29
- "path": {
30
- "type": "string",
31
- "description": "Local path override for repository"
32
- },
33
- "branch": {
34
- "type": "string",
35
- "description": "Default base branch",
36
- "default": "main"
37
- },
38
- "provider": {
39
- "type": "object",
40
- "required": ["primary"],
41
- "properties": {
42
- "primary": {
43
- "type": "string",
44
- "enum": ["opencode", "claude-code", "aider", "codex"],
45
- "description": "Primary AI coding agent"
46
- },
47
- "fallback": {
48
- "type": "array",
49
- "items": {
50
- "type": "string",
51
- "enum": ["opencode", "claude-code", "aider", "codex"]
52
- },
53
- "description": "Fallback agents in priority order"
54
- },
55
- "concurrentLimit": {
56
- "type": "integer",
57
- "minimum": 1,
58
- "maximum": 20,
59
- "description": "Maximum parallel agents for this project"
60
- },
61
- "costLimit": {
62
- "type": "integer",
63
- "minimum": 0,
64
- "description": "Cost limit in cents per session"
65
- }
66
- }
67
- },
68
- "reactions": {
69
- "type": "object",
70
- "properties": {
71
- "ci-failed": { "$ref": "#/definitions/reactionRule" },
72
- "changes-requested": { "$ref": "#/definitions/reactionRule" },
73
- "approved-and-green": { "$ref": "#/definitions/reactionRule" }
74
- },
75
- "description": "Reaction rules for CI/PR events"
76
- },
77
- "plugins": {
78
- "type": "object",
79
- "properties": {
80
- "tracker": { "type": "string" },
81
- "scm": { "type": "string" },
82
- "runtime": { "type": "string" },
83
- "notifier": { "type": "string" }
84
- }
85
- }
86
- }
87
- }
88
- }
89
- },
90
- "definitions": {
91
- "reactionRule": {
92
- "type": "object",
93
- "required": ["auto", "action"],
94
- "properties": {
95
- "auto": {
96
- "type": "boolean",
97
- "description": "Auto-trigger reaction when event occurs"
98
- },
99
- "action": {
100
- "type": "string",
101
- "enum": ["fix", "notify", "escalate", "skip"],
102
- "description": "Action to take when reaction triggers"
103
- },
104
- "retries": {
105
- "type": "integer",
106
- "minimum": 0,
107
- "maximum": 10,
108
- "description": "Number of retry attempts"
109
- },
110
- "escalateAfter": {
111
- "type": "integer",
112
- "minimum": 1,
113
- "maximum": 1440,
114
- "description": "Minutes before escalating to human"
115
- }
116
- }
117
- }
118
- }
119
- }
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://speexjs.dev/schemas/speexor-config.json",
4
+ "title": "Speexor Configuration",
5
+ "description": "Configuration schema for Speexor — Agent Orchestrator",
6
+ "type": "object",
7
+ "required": ["version", "projects"],
8
+ "properties": {
9
+ "version": {
10
+ "type": "string",
11
+ "enum": ["1"],
12
+ "description": "Config schema version"
13
+ },
14
+ "projects": {
15
+ "type": "array",
16
+ "minItems": 1,
17
+ "items": {
18
+ "type": "object",
19
+ "required": ["name", "repository", "provider"],
20
+ "properties": {
21
+ "name": {
22
+ "type": "string",
23
+ "description": "Project display name"
24
+ },
25
+ "repository": {
26
+ "type": "string",
27
+ "description": "Git repository URL or local path"
28
+ },
29
+ "path": {
30
+ "type": "string",
31
+ "description": "Local path override for repository"
32
+ },
33
+ "branch": {
34
+ "type": "string",
35
+ "description": "Default base branch",
36
+ "default": "main"
37
+ },
38
+ "provider": {
39
+ "type": "object",
40
+ "required": ["primary"],
41
+ "properties": {
42
+ "primary": {
43
+ "type": "string",
44
+ "enum": ["opencode", "claude-code", "aider", "codex"],
45
+ "description": "Primary AI coding agent"
46
+ },
47
+ "fallback": {
48
+ "type": "array",
49
+ "items": {
50
+ "type": "string",
51
+ "enum": ["opencode", "claude-code", "aider", "codex"]
52
+ },
53
+ "description": "Fallback agents in priority order"
54
+ },
55
+ "concurrentLimit": {
56
+ "type": "integer",
57
+ "minimum": 1,
58
+ "maximum": 20,
59
+ "description": "Maximum parallel agents for this project"
60
+ },
61
+ "costLimit": {
62
+ "type": "integer",
63
+ "minimum": 0,
64
+ "description": "Cost limit in cents per session"
65
+ }
66
+ }
67
+ },
68
+ "reactions": {
69
+ "type": "object",
70
+ "properties": {
71
+ "ci-failed": { "$ref": "#/definitions/reactionRule" },
72
+ "changes-requested": { "$ref": "#/definitions/reactionRule" },
73
+ "approved-and-green": { "$ref": "#/definitions/reactionRule" }
74
+ },
75
+ "description": "Reaction rules for CI/PR events"
76
+ },
77
+ "plugins": {
78
+ "type": "object",
79
+ "properties": {
80
+ "tracker": { "type": "string" },
81
+ "scm": { "type": "string" },
82
+ "runtime": { "type": "string" },
83
+ "notifier": { "type": "string" }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ },
90
+ "definitions": {
91
+ "reactionRule": {
92
+ "type": "object",
93
+ "required": ["auto", "action"],
94
+ "properties": {
95
+ "auto": {
96
+ "type": "boolean",
97
+ "description": "Auto-trigger reaction when event occurs"
98
+ },
99
+ "action": {
100
+ "type": "string",
101
+ "enum": ["fix", "notify", "escalate", "skip"],
102
+ "description": "Action to take when reaction triggers"
103
+ },
104
+ "retries": {
105
+ "type": "integer",
106
+ "minimum": 0,
107
+ "maximum": 10,
108
+ "description": "Number of retry attempts"
109
+ },
110
+ "escalateAfter": {
111
+ "type": "integer",
112
+ "minimum": 1,
113
+ "maximum": 1440,
114
+ "description": "Minutes before escalating to human"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }