@exulu/backend 1.63.3 → 1.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CUCJNEPB.js → chunk-DQR5LQOE.js} +4069 -2292
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-RNLLWEC6.js → convert-exulu-tools-to-ai-sdk-tools-J7PNBC5K.js} +1 -1
- package/dist/index.cjs +2652 -744
- package/dist/index.d.cts +30 -26
- package/dist/index.d.ts +30 -26
- package/dist/index.js +5 -3
- package/ee/agentic-retrieval/v3/index.ts +1 -1
- package/ee/agentic-retrieval/v4/index.ts +1 -1
- package/ee/python/.hermes/.env.example +8 -0
- package/ee/python/.hermes/README.md +44 -0
- package/ee/python/.hermes/SOUL.md.example +8 -0
- package/ee/python/.hermes/config.yaml.example +55 -0
- package/ee/python/setup.sh +49 -0
- package/ee/schemas.ts +4 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -26,6 +26,17 @@ type User = {
|
|
|
26
26
|
agent_ids?: string[];
|
|
27
27
|
role: UserRole;
|
|
28
28
|
team?: ExuluTeam;
|
|
29
|
+
/**
|
|
30
|
+
* Live LiteLLM budget snapshot for the user, attached at context time when
|
|
31
|
+
* the "show user budget in chat" setting is on. Not a Postgres column.
|
|
32
|
+
*/
|
|
33
|
+
budget?: UserBudgetView | null;
|
|
34
|
+
};
|
|
35
|
+
type UserBudgetView = {
|
|
36
|
+
spend: number;
|
|
37
|
+
max_budget: number;
|
|
38
|
+
budget_duration: string | null;
|
|
39
|
+
budget_reset_at: string | null;
|
|
29
40
|
};
|
|
30
41
|
type UserRole = {
|
|
31
42
|
id: string;
|
|
@@ -35,6 +46,7 @@ type UserRole = {
|
|
|
35
46
|
workflows: "read" | "write";
|
|
36
47
|
variables: "read" | "write";
|
|
37
48
|
users: "read" | "write";
|
|
49
|
+
budget_management?: "read" | "write";
|
|
38
50
|
};
|
|
39
51
|
type ExuluTeam = {
|
|
40
52
|
id: string;
|
|
@@ -72,15 +84,6 @@ type ExuluProviderConfig = {
|
|
|
72
84
|
};
|
|
73
85
|
};
|
|
74
86
|
|
|
75
|
-
type AgentRateLimitBucket = {
|
|
76
|
-
limit: number;
|
|
77
|
-
window_seconds: number;
|
|
78
|
-
};
|
|
79
|
-
type AgentRateLimits = {
|
|
80
|
-
requests?: AgentRateLimitBucket;
|
|
81
|
-
input_tokens?: AgentRateLimitBucket;
|
|
82
|
-
output_tokens?: AgentRateLimitBucket;
|
|
83
|
-
};
|
|
84
87
|
interface ExuluAgent {
|
|
85
88
|
id: string;
|
|
86
89
|
modelName?: string;
|
|
@@ -151,7 +154,6 @@ interface ExuluAgent {
|
|
|
151
154
|
rights: 'read' | 'write';
|
|
152
155
|
}>;
|
|
153
156
|
};
|
|
154
|
-
rate_limits?: AgentRateLimits;
|
|
155
157
|
createdAt?: string;
|
|
156
158
|
updatedAt?: string;
|
|
157
159
|
}
|
|
@@ -160,14 +162,6 @@ type fileTypes$1 = '.pdf' | '.docx' | '.xlsx' | '.xls' | '.csv' | '.pptx' | '.pp
|
|
|
160
162
|
type audioTypes$1 = '.mp3' | '.wav' | '.m4a' | '.mp4' | '.mpeg';
|
|
161
163
|
type videoTypes$1 = '.mp4' | '.m4a' | '.mp3' | '.mpeg' | '.wav';
|
|
162
164
|
|
|
163
|
-
interface RateLimiterRule {
|
|
164
|
-
name?: string;
|
|
165
|
-
rate_limit: {
|
|
166
|
-
time: number;
|
|
167
|
-
limit: number;
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
165
|
interface Item {
|
|
172
166
|
id?: string;
|
|
173
167
|
name?: string;
|
|
@@ -190,13 +184,16 @@ interface Item {
|
|
|
190
184
|
[key: string]: any;
|
|
191
185
|
}
|
|
192
186
|
|
|
187
|
+
declare const PUBLIC_TOOL_TYPES: readonly ["function", "web_search", "skill"];
|
|
188
|
+
type PublicToolType = (typeof PUBLIC_TOOL_TYPES)[number];
|
|
189
|
+
type ToolType = PublicToolType | "agent" | "context";
|
|
193
190
|
declare class ExuluTool {
|
|
194
191
|
id: string;
|
|
195
192
|
name: string;
|
|
196
193
|
description: string;
|
|
197
194
|
category: string;
|
|
198
195
|
inputSchema?: z.ZodType;
|
|
199
|
-
type:
|
|
196
|
+
type: ToolType;
|
|
200
197
|
tool: Tool;
|
|
201
198
|
needsApproval: boolean;
|
|
202
199
|
config: {
|
|
@@ -211,7 +208,7 @@ declare class ExuluTool {
|
|
|
211
208
|
description: string;
|
|
212
209
|
category?: string;
|
|
213
210
|
inputSchema?: z.ZodType;
|
|
214
|
-
type:
|
|
211
|
+
type: PublicToolType;
|
|
215
212
|
config: {
|
|
216
213
|
name: string;
|
|
217
214
|
description: string;
|
|
@@ -229,6 +226,17 @@ declare class ExuluTool {
|
|
|
229
226
|
items?: Item[];
|
|
230
227
|
}>;
|
|
231
228
|
});
|
|
229
|
+
/**
|
|
230
|
+
* Framework-only factory for tools whose `type` is managed by Exulu itself —
|
|
231
|
+
* "agent" (agent-as-tool instrumentation) and "context" (internal retrieval
|
|
232
|
+
* tools). NOT part of the public API: package consumers must use
|
|
233
|
+
* `new ExuluTool(...)`, which only accepts a {@link PublicToolType}. Building
|
|
234
|
+
* the tool as a "function" and then setting the managed type bypasses the
|
|
235
|
+
* constructor guard without weakening it for consumers.
|
|
236
|
+
*/
|
|
237
|
+
static internal(params: Omit<ConstructorParameters<typeof ExuluTool>[0], "type"> & {
|
|
238
|
+
type: ToolType;
|
|
239
|
+
}): ExuluTool;
|
|
232
240
|
execute: ({ agent: agentId, config, user, inputs, project, items, }: {
|
|
233
241
|
agent: string;
|
|
234
242
|
config: ExuluConfig;
|
|
@@ -530,7 +538,6 @@ declare class ExuluContext {
|
|
|
530
538
|
active: boolean;
|
|
531
539
|
fields: ExuluContextFieldDefinition[];
|
|
532
540
|
processor?: ExuluContextProcessor;
|
|
533
|
-
rateLimit?: RateLimiterRule;
|
|
534
541
|
description: string;
|
|
535
542
|
embedder?: ExuluEmbedder;
|
|
536
543
|
queryRewriter?: (query: string) => Promise<string>;
|
|
@@ -573,7 +580,7 @@ declare class ExuluContext {
|
|
|
573
580
|
languages?: ("german" | "english")[];
|
|
574
581
|
};
|
|
575
582
|
sources: ExuluContextSource[];
|
|
576
|
-
constructor({ id, name, description, embedder, processor, active,
|
|
583
|
+
constructor({ id, name, description, embedder, processor, active, fields, queryRewriter, resultReranker, configuration, sources, }: {
|
|
577
584
|
id: string;
|
|
578
585
|
name: string;
|
|
579
586
|
fields: ExuluContextFieldDefinition[];
|
|
@@ -583,7 +590,6 @@ declare class ExuluContext {
|
|
|
583
590
|
category?: string;
|
|
584
591
|
active: boolean;
|
|
585
592
|
processor?: ExuluContextProcessor;
|
|
586
|
-
rateLimit?: RateLimiterRule;
|
|
587
593
|
queryRewriter?: (query: string) => Promise<string>;
|
|
588
594
|
resultReranker?: (results: any[]) => Promise<any[]>;
|
|
589
595
|
configuration?: {
|
|
@@ -760,7 +766,6 @@ interface ExuluProviderParams {
|
|
|
760
766
|
audio: audioTypes$1[];
|
|
761
767
|
video: videoTypes$1[];
|
|
762
768
|
};
|
|
763
|
-
rateLimit?: RateLimiterRule;
|
|
764
769
|
}
|
|
765
770
|
declare class ExuluProvider {
|
|
766
771
|
id: string;
|
|
@@ -774,7 +779,6 @@ declare class ExuluProvider {
|
|
|
774
779
|
maxContextLength?: number;
|
|
775
780
|
workflows?: ExuluProviderWorkflowConfig;
|
|
776
781
|
queue?: ExuluQueueConfig;
|
|
777
|
-
rateLimit?: RateLimiterRule;
|
|
778
782
|
config?: ExuluProviderConfig | undefined;
|
|
779
783
|
model?: {
|
|
780
784
|
create: ({ apiKey, user, role, project, agent }: {
|
|
@@ -792,7 +796,7 @@ declare class ExuluProvider {
|
|
|
792
796
|
audio: string[];
|
|
793
797
|
video: string[];
|
|
794
798
|
};
|
|
795
|
-
constructor({ id, name, description, config,
|
|
799
|
+
constructor({ id, name, description, config, capabilities, type, maxContextLength, provider, queue, authenticationInformation, workflows, }: ExuluProviderParams);
|
|
796
800
|
get providerName(): string;
|
|
797
801
|
get modelName(): string;
|
|
798
802
|
tool: (instance: string, providers: ExuluProvider[], contexts: ExuluContext[], rerankers: ExuluReranker[]) => Promise<ExuluTool | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,17 @@ type User = {
|
|
|
26
26
|
agent_ids?: string[];
|
|
27
27
|
role: UserRole;
|
|
28
28
|
team?: ExuluTeam;
|
|
29
|
+
/**
|
|
30
|
+
* Live LiteLLM budget snapshot for the user, attached at context time when
|
|
31
|
+
* the "show user budget in chat" setting is on. Not a Postgres column.
|
|
32
|
+
*/
|
|
33
|
+
budget?: UserBudgetView | null;
|
|
34
|
+
};
|
|
35
|
+
type UserBudgetView = {
|
|
36
|
+
spend: number;
|
|
37
|
+
max_budget: number;
|
|
38
|
+
budget_duration: string | null;
|
|
39
|
+
budget_reset_at: string | null;
|
|
29
40
|
};
|
|
30
41
|
type UserRole = {
|
|
31
42
|
id: string;
|
|
@@ -35,6 +46,7 @@ type UserRole = {
|
|
|
35
46
|
workflows: "read" | "write";
|
|
36
47
|
variables: "read" | "write";
|
|
37
48
|
users: "read" | "write";
|
|
49
|
+
budget_management?: "read" | "write";
|
|
38
50
|
};
|
|
39
51
|
type ExuluTeam = {
|
|
40
52
|
id: string;
|
|
@@ -72,15 +84,6 @@ type ExuluProviderConfig = {
|
|
|
72
84
|
};
|
|
73
85
|
};
|
|
74
86
|
|
|
75
|
-
type AgentRateLimitBucket = {
|
|
76
|
-
limit: number;
|
|
77
|
-
window_seconds: number;
|
|
78
|
-
};
|
|
79
|
-
type AgentRateLimits = {
|
|
80
|
-
requests?: AgentRateLimitBucket;
|
|
81
|
-
input_tokens?: AgentRateLimitBucket;
|
|
82
|
-
output_tokens?: AgentRateLimitBucket;
|
|
83
|
-
};
|
|
84
87
|
interface ExuluAgent {
|
|
85
88
|
id: string;
|
|
86
89
|
modelName?: string;
|
|
@@ -151,7 +154,6 @@ interface ExuluAgent {
|
|
|
151
154
|
rights: 'read' | 'write';
|
|
152
155
|
}>;
|
|
153
156
|
};
|
|
154
|
-
rate_limits?: AgentRateLimits;
|
|
155
157
|
createdAt?: string;
|
|
156
158
|
updatedAt?: string;
|
|
157
159
|
}
|
|
@@ -160,14 +162,6 @@ type fileTypes$1 = '.pdf' | '.docx' | '.xlsx' | '.xls' | '.csv' | '.pptx' | '.pp
|
|
|
160
162
|
type audioTypes$1 = '.mp3' | '.wav' | '.m4a' | '.mp4' | '.mpeg';
|
|
161
163
|
type videoTypes$1 = '.mp4' | '.m4a' | '.mp3' | '.mpeg' | '.wav';
|
|
162
164
|
|
|
163
|
-
interface RateLimiterRule {
|
|
164
|
-
name?: string;
|
|
165
|
-
rate_limit: {
|
|
166
|
-
time: number;
|
|
167
|
-
limit: number;
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
165
|
interface Item {
|
|
172
166
|
id?: string;
|
|
173
167
|
name?: string;
|
|
@@ -190,13 +184,16 @@ interface Item {
|
|
|
190
184
|
[key: string]: any;
|
|
191
185
|
}
|
|
192
186
|
|
|
187
|
+
declare const PUBLIC_TOOL_TYPES: readonly ["function", "web_search", "skill"];
|
|
188
|
+
type PublicToolType = (typeof PUBLIC_TOOL_TYPES)[number];
|
|
189
|
+
type ToolType = PublicToolType | "agent" | "context";
|
|
193
190
|
declare class ExuluTool {
|
|
194
191
|
id: string;
|
|
195
192
|
name: string;
|
|
196
193
|
description: string;
|
|
197
194
|
category: string;
|
|
198
195
|
inputSchema?: z.ZodType;
|
|
199
|
-
type:
|
|
196
|
+
type: ToolType;
|
|
200
197
|
tool: Tool;
|
|
201
198
|
needsApproval: boolean;
|
|
202
199
|
config: {
|
|
@@ -211,7 +208,7 @@ declare class ExuluTool {
|
|
|
211
208
|
description: string;
|
|
212
209
|
category?: string;
|
|
213
210
|
inputSchema?: z.ZodType;
|
|
214
|
-
type:
|
|
211
|
+
type: PublicToolType;
|
|
215
212
|
config: {
|
|
216
213
|
name: string;
|
|
217
214
|
description: string;
|
|
@@ -229,6 +226,17 @@ declare class ExuluTool {
|
|
|
229
226
|
items?: Item[];
|
|
230
227
|
}>;
|
|
231
228
|
});
|
|
229
|
+
/**
|
|
230
|
+
* Framework-only factory for tools whose `type` is managed by Exulu itself —
|
|
231
|
+
* "agent" (agent-as-tool instrumentation) and "context" (internal retrieval
|
|
232
|
+
* tools). NOT part of the public API: package consumers must use
|
|
233
|
+
* `new ExuluTool(...)`, which only accepts a {@link PublicToolType}. Building
|
|
234
|
+
* the tool as a "function" and then setting the managed type bypasses the
|
|
235
|
+
* constructor guard without weakening it for consumers.
|
|
236
|
+
*/
|
|
237
|
+
static internal(params: Omit<ConstructorParameters<typeof ExuluTool>[0], "type"> & {
|
|
238
|
+
type: ToolType;
|
|
239
|
+
}): ExuluTool;
|
|
232
240
|
execute: ({ agent: agentId, config, user, inputs, project, items, }: {
|
|
233
241
|
agent: string;
|
|
234
242
|
config: ExuluConfig;
|
|
@@ -530,7 +538,6 @@ declare class ExuluContext {
|
|
|
530
538
|
active: boolean;
|
|
531
539
|
fields: ExuluContextFieldDefinition[];
|
|
532
540
|
processor?: ExuluContextProcessor;
|
|
533
|
-
rateLimit?: RateLimiterRule;
|
|
534
541
|
description: string;
|
|
535
542
|
embedder?: ExuluEmbedder;
|
|
536
543
|
queryRewriter?: (query: string) => Promise<string>;
|
|
@@ -573,7 +580,7 @@ declare class ExuluContext {
|
|
|
573
580
|
languages?: ("german" | "english")[];
|
|
574
581
|
};
|
|
575
582
|
sources: ExuluContextSource[];
|
|
576
|
-
constructor({ id, name, description, embedder, processor, active,
|
|
583
|
+
constructor({ id, name, description, embedder, processor, active, fields, queryRewriter, resultReranker, configuration, sources, }: {
|
|
577
584
|
id: string;
|
|
578
585
|
name: string;
|
|
579
586
|
fields: ExuluContextFieldDefinition[];
|
|
@@ -583,7 +590,6 @@ declare class ExuluContext {
|
|
|
583
590
|
category?: string;
|
|
584
591
|
active: boolean;
|
|
585
592
|
processor?: ExuluContextProcessor;
|
|
586
|
-
rateLimit?: RateLimiterRule;
|
|
587
593
|
queryRewriter?: (query: string) => Promise<string>;
|
|
588
594
|
resultReranker?: (results: any[]) => Promise<any[]>;
|
|
589
595
|
configuration?: {
|
|
@@ -760,7 +766,6 @@ interface ExuluProviderParams {
|
|
|
760
766
|
audio: audioTypes$1[];
|
|
761
767
|
video: videoTypes$1[];
|
|
762
768
|
};
|
|
763
|
-
rateLimit?: RateLimiterRule;
|
|
764
769
|
}
|
|
765
770
|
declare class ExuluProvider {
|
|
766
771
|
id: string;
|
|
@@ -774,7 +779,6 @@ declare class ExuluProvider {
|
|
|
774
779
|
maxContextLength?: number;
|
|
775
780
|
workflows?: ExuluProviderWorkflowConfig;
|
|
776
781
|
queue?: ExuluQueueConfig;
|
|
777
|
-
rateLimit?: RateLimiterRule;
|
|
778
782
|
config?: ExuluProviderConfig | undefined;
|
|
779
783
|
model?: {
|
|
780
784
|
create: ({ apiKey, user, role, project, agent }: {
|
|
@@ -792,7 +796,7 @@ declare class ExuluProvider {
|
|
|
792
796
|
audio: string[];
|
|
793
797
|
video: string[];
|
|
794
798
|
};
|
|
795
|
-
constructor({ id, name, description, config,
|
|
799
|
+
constructor({ id, name, description, config, capabilities, type, maxContextLength, provider, queue, authenticationInformation, workflows, }: ExuluProviderParams);
|
|
796
800
|
get providerName(): string;
|
|
797
801
|
get modelName(): string;
|
|
798
802
|
tool: (instance: string, providers: ExuluProvider[], contexts: ExuluContext[], rerankers: ExuluReranker[]) => Promise<ExuluTool | null>;
|
package/dist/index.js
CHANGED
|
@@ -51,8 +51,9 @@ import {
|
|
|
51
51
|
transcriptionService,
|
|
52
52
|
updateStatistic,
|
|
53
53
|
uploadFile,
|
|
54
|
+
validateHermesAtBoot,
|
|
54
55
|
withRetry
|
|
55
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-DQR5LQOE.js";
|
|
56
57
|
import "./chunk-YCE44CMU.js";
|
|
57
58
|
|
|
58
59
|
// src/index.ts
|
|
@@ -2738,6 +2739,7 @@ var ExuluApp = class {
|
|
|
2738
2739
|
);
|
|
2739
2740
|
}
|
|
2740
2741
|
}
|
|
2742
|
+
validateHermesAtBoot();
|
|
2741
2743
|
if (process.env.TRANSCRIPTION_SERVER) {
|
|
2742
2744
|
try {
|
|
2743
2745
|
const health = await transcriptionClient.health();
|
|
@@ -3031,7 +3033,6 @@ var ExuluApp = class {
|
|
|
3031
3033
|
this._config,
|
|
3032
3034
|
this._evals,
|
|
3033
3035
|
tracer,
|
|
3034
|
-
this._queues,
|
|
3035
3036
|
this._rerankers
|
|
3036
3037
|
);
|
|
3037
3038
|
if (this._config?.MCP.enabled) {
|
|
@@ -4747,7 +4748,8 @@ var execute = async ({ contexts }) => {
|
|
|
4747
4748
|
workflows: "write",
|
|
4748
4749
|
variables: "write",
|
|
4749
4750
|
users: "write",
|
|
4750
|
-
evals: "write"
|
|
4751
|
+
evals: "write",
|
|
4752
|
+
budget_management: "write"
|
|
4751
4753
|
}).returning("id");
|
|
4752
4754
|
adminRoleId = role[0].id;
|
|
4753
4755
|
} else {
|
|
@@ -213,7 +213,7 @@ export function createAgenticRetrievalToolV3({
|
|
|
213
213
|
|
|
214
214
|
const contextNames = contexts.map((c) => c.id).join(", ");
|
|
215
215
|
|
|
216
|
-
return
|
|
216
|
+
return ExuluTool.internal({
|
|
217
217
|
id: "agentic_context_search",
|
|
218
218
|
name: "Context Search",
|
|
219
219
|
description: `Intelligent context search with query classification, strategy-based retrieval, and virtual filesystem filtering. Searches: ${contextNames}`,
|
|
@@ -152,7 +152,7 @@ export function createAgenticRetrievalToolV4({
|
|
|
152
152
|
|
|
153
153
|
const contextNames = contexts.map((c) => c.id).join(", ");
|
|
154
154
|
|
|
155
|
-
return
|
|
155
|
+
return ExuluTool.internal({
|
|
156
156
|
id: "agentic_context_search",
|
|
157
157
|
name: "Context Search",
|
|
158
158
|
description: `Intelligent context search with query classification, strategy-based retrieval, and virtual filesystem filtering. Searches: ${contextNames}`,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# EXAMPLE ONLY — Exulu generates the real .env per profile at runtime (mode 0600).
|
|
2
|
+
# Profile-local secrets referenced by ${VAR} in config.yaml.
|
|
3
|
+
#
|
|
4
|
+
# Runtime API-server params (API_SERVER_ENABLED / HOST / PORT / KEY) are NOT
|
|
5
|
+
# stored here — the supervisor injects them via the child process environment so
|
|
6
|
+
# port and key allocation stay owned by Exulu and a profile dir is portable.
|
|
7
|
+
|
|
8
|
+
LITELLM_MASTER_KEY=replace-with-your-litellm-master-key
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Hermes Agent profiles (advanced agent mode)
|
|
2
|
+
|
|
3
|
+
This directory documents the per-profile files Exulu generates for the
|
|
4
|
+
[Hermes Agent](https://hermes-agent.nousresearch.com) harness when an Exulu
|
|
5
|
+
agent has **advanced mode** enabled. You do **not** edit anything here — Exulu's
|
|
6
|
+
provisioner writes the real files at runtime under `${HERMES_HOME}/profiles/<id>/`.
|
|
7
|
+
|
|
8
|
+
## How it fits together
|
|
9
|
+
|
|
10
|
+
- One Hermes **profile** per Exulu agent (`<agentId>`), or per agent/user
|
|
11
|
+
(`<agentId>/<userId>`) when the agent's
|
|
12
|
+
`advanced_agent_profile_scope` is `private`.
|
|
13
|
+
- Each in-use profile runs its own `hermes gateway` process on its own port,
|
|
14
|
+
supervised by `src/exulu/hermes/supervisor.ts` (lazy start + idle eviction).
|
|
15
|
+
- Every model call still flows through the LiteLLM proxy — Hermes' `model`
|
|
16
|
+
block points `base_url` at LiteLLM.
|
|
17
|
+
|
|
18
|
+
## Enabling
|
|
19
|
+
|
|
20
|
+
1. `ENABLE_HERMES_AGENT=true` (gates install + the whole code path).
|
|
21
|
+
2. Run `npm run python:setup` — installs the `hermes` binary when the flag is on.
|
|
22
|
+
3. Toggle **advanced mode** on an individual agent in the agent form.
|
|
23
|
+
|
|
24
|
+
## Env vars
|
|
25
|
+
|
|
26
|
+
| Var | Default | Purpose |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `ENABLE_HERMES_AGENT` | (unset) | Global gate for advanced mode. |
|
|
29
|
+
| `HERMES_HOME` | `~/.hermes` | Root for profile directories. |
|
|
30
|
+
| `HERMES_BIN` | (auto) | Override path to the `hermes` binary. |
|
|
31
|
+
| `HERMES_PORT_RANGE` | `8642-8700` | Gateway port pool. |
|
|
32
|
+
| `HERMES_MAX_GATEWAYS` | `20` | LRU cap on concurrent gateways. |
|
|
33
|
+
| `HERMES_IDLE_TIMEOUT_MS` | `900000` | Idle eviction threshold (15 min). |
|
|
34
|
+
| `HERMES_APPROVALS_MODE` | `smart` | Tool-approval policy written to config.yaml. |
|
|
35
|
+
| `HERMES_TERMINAL_BACKEND` | `docker` | Backend that runs native shell/file tools (`docker` isolates without host user namespaces; `local`/`ssh`/`modal`/`daytona`/`singularity` also selectable). Docker must be available to the host process. |
|
|
36
|
+
| `HERMES_DOCKER_IMAGE` | `nikolaik/python-nodejs:python3.11-nodejs20` | Image for the docker backend (needs python + node). |
|
|
37
|
+
| `BACKEND` | `http://127.0.0.1:<PORT>` | URL a gateway uses to reach Exulu's `/mcp/:agentId` (set this if the host app's port isn't `PORT`/`EXULU_PORT`). |
|
|
38
|
+
| `EXULU_MCP_KEY` | `LITELLM_MASTER_KEY` | Bearer token guarding the ExuluTools MCP endpoint. |
|
|
39
|
+
|
|
40
|
+
ExuluTools reach the agent over HTTP MCP at `/mcp/<agentId>` and **add to** Hermes'
|
|
41
|
+
native tools (bash, filesystem, …) rather than replacing them.
|
|
42
|
+
|
|
43
|
+
See `config.yaml.example`, `.env.example`, and `SOUL.md.example` in this folder
|
|
44
|
+
for the shape of the generated files.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- EXAMPLE ONLY — Exulu generates the real SOUL.md per profile from the agent's
|
|
2
|
+
`instructions`. SOUL.md is slot #1 of the Hermes system prompt and defines
|
|
3
|
+
who the agent is. Exulu owns this file and rewrites it whenever the agent's
|
|
4
|
+
instructions change (Hermes never overwrites an existing SOUL.md). -->
|
|
5
|
+
|
|
6
|
+
You are Acme Corp's research assistant. You are precise, cite your sources, and
|
|
7
|
+
prefer primary documents over summaries. When unsure, you say so rather than
|
|
8
|
+
guessing.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# EXAMPLE ONLY — Exulu generates the real config.yaml per profile at runtime.
|
|
2
|
+
# Documentation: https://hermes-agent.nousresearch.com/docs/user-guide/configuration
|
|
3
|
+
#
|
|
4
|
+
# The model block points Hermes at the LiteLLM proxy so every model call still
|
|
5
|
+
# flows through the single model gateway. NOTE: the model-name key is `default`,
|
|
6
|
+
# not `model`. When base_url is set, Hermes calls it directly using api_key.
|
|
7
|
+
|
|
8
|
+
model:
|
|
9
|
+
default: "claude-haiku" # a LiteLLM model_name from config.litellm.yaml
|
|
10
|
+
provider: custom
|
|
11
|
+
base_url: "http://127.0.0.1:4000/v1"
|
|
12
|
+
api_key: "${LITELLM_MASTER_KEY}" # resolved from the profile .env / process env
|
|
13
|
+
api_mode: chat_completions
|
|
14
|
+
|
|
15
|
+
# Tool-approval policy: `smart` auto-approves low-risk actions and emits an
|
|
16
|
+
# approval event (requiring a decision) before destructive ones.
|
|
17
|
+
approvals:
|
|
18
|
+
mode: smart
|
|
19
|
+
|
|
20
|
+
# Native shell/file tools run via this backend. Default is `docker`: a hardened,
|
|
21
|
+
# Hermes-managed container (cap-drop ALL, no-new-privileges) that isolates the
|
|
22
|
+
# tools from the host WITHOUT needing user namespaces — so it behaves the same
|
|
23
|
+
# on macOS (Docker Desktop) and Linux. Volumes are bind-mounted host->same path
|
|
24
|
+
# so the absolute cwd / skills.external_dirs resolve inside the container;
|
|
25
|
+
# secrets are not mounted. Set HERMES_TERMINAL_BACKEND=local to disable.
|
|
26
|
+
terminal:
|
|
27
|
+
backend: docker
|
|
28
|
+
# No bind mount: the Files panel talks to the container's /root directly via
|
|
29
|
+
# docker exec/cp. We stamp a deterministic label so Exulu can find the
|
|
30
|
+
# container (docker ps --filter label=exulu-profile=<profileId>), and keep it
|
|
31
|
+
# persistent so files survive between runs.
|
|
32
|
+
docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
|
33
|
+
container_persistent: true
|
|
34
|
+
lifetime_seconds: 86400
|
|
35
|
+
# Run as root (home /root) so the agent's working dir is a predictable /root
|
|
36
|
+
# the Files panel reads — NOT the host user's home (e.g. /Users/<you>), which
|
|
37
|
+
# is what docker_run_as_host_user (default) would replicate.
|
|
38
|
+
docker_run_as_host_user: false
|
|
39
|
+
docker_mount_cwd_to_workspace: false
|
|
40
|
+
docker_extra_args: ["--label", "exulu-profile=<profileId>"]
|
|
41
|
+
- "/abs/.../profiles/<profileId>/exulu-skills:/abs/.../profiles/<profileId>/exulu-skills:ro"
|
|
42
|
+
|
|
43
|
+
# Added in Phase 3 — ExuluTools exposed over HTTP MCP at /mcp/<agentId>:
|
|
44
|
+
# mcp_servers:
|
|
45
|
+
# exulu:
|
|
46
|
+
# url: "http://127.0.0.1:<exulu-port>/mcp/<agentId>"
|
|
47
|
+
# headers:
|
|
48
|
+
# Authorization: "Bearer ${EXULU_MCP_KEY}"
|
|
49
|
+
|
|
50
|
+
# Enabled Exulu skills, synced from S3 into the profile (Anthropic Agent Skills
|
|
51
|
+
# format). ADDS to Hermes' own skills home (learned/bundled skills); only
|
|
52
|
+
# written when the agent has skills enabled.
|
|
53
|
+
# skills:
|
|
54
|
+
# external_dirs:
|
|
55
|
+
# - "/abs/path/to/${HERMES_HOME}/profiles/<profileId>/exulu-skills"
|
package/ee/python/setup.sh
CHANGED
|
@@ -253,6 +253,46 @@ if [ -n "$LITELLM_PROXY_DIR" ] && [ -f "$LITELLM_PROXY_DIR/schema.prisma" ]; the
|
|
|
253
253
|
|| print_warning "Prisma generate failed; LiteLLM database mode (database_url in config.litellm.yaml) may not work until you run 'cd $LITELLM_PROXY_DIR && PATH=$VENV_DIR/bin:\$PATH $VENV_DIR/bin/prisma generate'"
|
|
254
254
|
fi
|
|
255
255
|
|
|
256
|
+
# Step 6.6: Install the Hermes Agent harness (advanced agent mode).
|
|
257
|
+
# Opt-in via ENABLE_HERMES_AGENT=true. Hermes is NOT a pip package — it ships
|
|
258
|
+
# as a standalone binary via Nous Research's official installer (lands in
|
|
259
|
+
# ~/.local/bin/hermes). We only install if it's not already present so re-runs
|
|
260
|
+
# are fast, and we never fail the whole setup if the install fails (advanced
|
|
261
|
+
# mode is optional; the operator can install it manually and retry).
|
|
262
|
+
if [ "${ENABLE_HERMES_AGENT}" = "true" ]; then
|
|
263
|
+
echo ""
|
|
264
|
+
echo "Step 6.6: Installing Hermes Agent harness (ENABLE_HERMES_AGENT=true)..."
|
|
265
|
+
if command -v hermes &> /dev/null || [ -x "$HOME/.local/bin/hermes" ]; then
|
|
266
|
+
HERMES_VERSION=$( (command -v hermes &> /dev/null && hermes --version 2>/dev/null) || "$HOME/.local/bin/hermes" --version 2>/dev/null || echo "unknown")
|
|
267
|
+
print_success "Hermes already installed ($HERMES_VERSION) — skipping installer"
|
|
268
|
+
else
|
|
269
|
+
print_info "Running Hermes official installer..."
|
|
270
|
+
if curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash; then
|
|
271
|
+
print_success "Hermes Agent installed (binary at ~/.local/bin/hermes)"
|
|
272
|
+
else
|
|
273
|
+
print_warning "Hermes installer failed. Advanced agent mode will be unavailable until 'hermes' is on PATH. Install manually: https://hermes-agent.nousresearch.com/docs/getting-started/installation"
|
|
274
|
+
fi
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Pre-pull the docker terminal-backend image so the first agent request
|
|
278
|
+
# isn't blocked on a cold image pull (~minute). Only when the backend is
|
|
279
|
+
# docker (the default) and docker is available; non-fatal otherwise.
|
|
280
|
+
HERMES_BACKEND="${HERMES_TERMINAL_BACKEND:-docker}"
|
|
281
|
+
if [ "${HERMES_BACKEND}" = "docker" ]; then
|
|
282
|
+
HERMES_IMG="${HERMES_DOCKER_IMAGE:-nikolaik/python-nodejs:python3.11-nodejs20}"
|
|
283
|
+
if command -v docker &> /dev/null; then
|
|
284
|
+
print_info "Pre-pulling Hermes docker backend image: ${HERMES_IMG}..."
|
|
285
|
+
if docker pull "${HERMES_IMG}" > /dev/null 2>&1; then
|
|
286
|
+
print_success "Docker backend image ready (${HERMES_IMG})"
|
|
287
|
+
else
|
|
288
|
+
print_warning "Could not pre-pull ${HERMES_IMG}; the first advanced-mode request will pull it (slower)."
|
|
289
|
+
fi
|
|
290
|
+
else
|
|
291
|
+
print_warning "Docker not found, but HERMES_TERMINAL_BACKEND=docker. Install Docker, or set HERMES_TERMINAL_BACKEND=local (unsandboxed)."
|
|
292
|
+
fi
|
|
293
|
+
fi
|
|
294
|
+
fi
|
|
295
|
+
|
|
256
296
|
# Step 7: Validate installation
|
|
257
297
|
echo ""
|
|
258
298
|
echo "Step 7: Validating installation..."
|
|
@@ -269,6 +309,15 @@ $PYTHON_CMD -c "import whisperx" 2>/dev/null && print_success "whisperx imported
|
|
|
269
309
|
$PYTHON_CMD -c "import pyannote.audio" 2>/dev/null && print_success "pyannote.audio imported successfully" || print_warning "pyannote.audio not importable (diarization will be disabled even with HF_AUTH_TOKEN)"
|
|
270
310
|
$PYTHON_CMD -c "import fastapi, uvicorn" 2>/dev/null && print_success "fastapi/uvicorn imported successfully" || print_warning "fastapi/uvicorn not importable (transcription server will not start)"
|
|
271
311
|
|
|
312
|
+
# Hermes Agent binary check (advanced agent mode) — only when opted in.
|
|
313
|
+
if [ "${ENABLE_HERMES_AGENT}" = "true" ]; then
|
|
314
|
+
if command -v hermes &> /dev/null || [ -x "$HOME/.local/bin/hermes" ]; then
|
|
315
|
+
print_success "hermes binary available (advanced agent mode ready)"
|
|
316
|
+
else
|
|
317
|
+
print_warning "hermes binary not found (advanced agent mode will be unavailable)"
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
|
|
272
321
|
# Step 8: Display summary
|
|
273
322
|
echo ""
|
|
274
323
|
echo -e "${GREEN}========================================${NC}"
|
package/ee/schemas.ts
CHANGED