@coinrithm/mcp-trading 0.7.12 → 0.7.14
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/CHANGELOG.md +613 -546
- package/README.md +366 -344
- package/dist/agent/act.d.ts +7 -2
- package/dist/agent/act.js +47 -4
- package/dist/agent/cli.d.ts +1 -0
- package/dist/agent/cli.js +25 -4
- package/dist/agent/client.d.ts +4 -0
- package/dist/agent/decision.d.ts +12 -12
- package/dist/agent/decisionReceipt.js +43 -0
- package/dist/agent/decisionValidator.js +74 -0
- package/dist/agent/definitionSnapshot.d.ts +22 -0
- package/dist/agent/definitionSnapshot.js +28 -0
- package/dist/agent/engine.d.ts +2 -1
- package/dist/agent/engine.js +2 -1
- package/dist/agent/mergeRules.d.ts +1 -1
- package/dist/agent/mergeRules.js +17 -2
- package/dist/agent/observe.js +98 -7
- package/dist/agent/prompt.js +11 -1
- package/dist/agent/providers.d.ts +4 -0
- package/dist/agent/providers.js +16 -10
- package/dist/agent/resolve.js +8 -1
- package/dist/agent/runner.js +42 -8
- package/dist/agent/skill.js +9 -0
- package/dist/agent/skillValidator.js +11 -2
- package/dist/agent/strictLint.js +1 -0
- package/dist/agent/templates.js +30 -30
- package/dist/agent/types.d.ts +35 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +43 -0
- package/dist/httpCompletion.d.ts +1 -1
- package/dist/httpCompletion.js +2 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +208 -17
- package/package.json +97 -97
package/dist/agent/act.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { CoinRithmClient, ProvenanceReport } from "./client.js";
|
|
2
2
|
import { ProposedAction, AgentTrace, ApiResult, Observation, QuoteEvidence } from "./types.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/** Configured policy values the server enforces independently of the model's
|
|
4
|
+
* action fields (2026-09-24: the PM entry floor). */
|
|
5
|
+
export interface ExecutionPolicy {
|
|
6
|
+
pmMinEntryProbabilityPct?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function fetchQuote(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace?: AgentTrace, policy?: ExecutionPolicy): Promise<QuoteEvidence | undefined>;
|
|
9
|
+
export declare function executeAction(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace: AgentTrace, idempotencyKey: string, provenance?: ProvenanceReport, policy?: ExecutionPolicy): Promise<ApiResult>;
|
package/dist/agent/act.js
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
// Act phase: fetch the quote evidence for an open (the runner does this, never
|
|
2
2
|
// the model) and execute a validated action (futures / spot / PM) with an
|
|
3
3
|
// idempotency key.
|
|
4
|
-
import { asObj, asNum } from "./extract.js";
|
|
4
|
+
import { asObj, asNum, asStr } from "./extract.js";
|
|
5
5
|
import { freshnessOf } from "./pmContext.js";
|
|
6
|
+
function fundingQuoteEvidence(value) {
|
|
7
|
+
if (value === null)
|
|
8
|
+
return null;
|
|
9
|
+
if (typeof value !== "object" || value === null)
|
|
10
|
+
return undefined;
|
|
11
|
+
const f = asObj(value);
|
|
12
|
+
const estimatedPerIntervalMusd = Object.prototype.hasOwnProperty.call(f, "estimatedPerIntervalMusd")
|
|
13
|
+
? (asNum(f.estimatedPerIntervalMusd) ??
|
|
14
|
+
(f.estimatedPerIntervalMusd === null ? null : undefined))
|
|
15
|
+
: undefined;
|
|
16
|
+
return {
|
|
17
|
+
...(asStr(f.venue) !== undefined ? { venue: asStr(f.venue) } : {}),
|
|
18
|
+
...(asStr(f.symbol) !== undefined ? { symbol: asStr(f.symbol) } : {}),
|
|
19
|
+
...(asNum(f.rate) !== undefined ? { rate: asNum(f.rate) } : {}),
|
|
20
|
+
...(asNum(f.intervalHours) !== undefined
|
|
21
|
+
? { intervalHours: asNum(f.intervalHours) }
|
|
22
|
+
: {}),
|
|
23
|
+
...(asStr(f.nextFundingTime) !== undefined
|
|
24
|
+
? { nextFundingTime: asStr(f.nextFundingTime) }
|
|
25
|
+
: {}),
|
|
26
|
+
...(asStr(f.asOf) !== undefined ? { asOf: asStr(f.asOf) } : {}),
|
|
27
|
+
...(estimatedPerIntervalMusd !== undefined
|
|
28
|
+
? { estimatedPerIntervalMusd }
|
|
29
|
+
: {}),
|
|
30
|
+
...(asNum(f.annualizedRate) !== undefined
|
|
31
|
+
? { annualizedRate: asNum(f.annualizedRate) }
|
|
32
|
+
: {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
6
35
|
function coinIdFor(observation, symbol) {
|
|
7
36
|
return (observation.watch.find((w) => w.symbol.toUpperCase() === symbol.toUpperCase())?.coinId ?? undefined);
|
|
8
37
|
}
|
|
9
|
-
|
|
10
|
-
|
|
38
|
+
const pmFloorBody = (policy) => typeof policy?.pmMinEntryProbabilityPct === "number" &&
|
|
39
|
+
Number.isFinite(policy.pmMinEntryProbabilityPct)
|
|
40
|
+
? { minEntryProbabilityPct: policy.pmMinEntryProbabilityPct }
|
|
41
|
+
: {};
|
|
42
|
+
export async function fetchQuote(client, action, observation, trace, policy) {
|
|
11
43
|
let r;
|
|
12
44
|
if (action.type === "futures_open") {
|
|
13
45
|
const coinId = coinIdFor(observation, action.symbol);
|
|
@@ -32,6 +64,10 @@ export async function fetchQuote(client, action, observation, trace) {
|
|
|
32
64
|
slug: action.slug,
|
|
33
65
|
outcomeExternalMarketId: action.outcomeExternalMarketId,
|
|
34
66
|
stakeMusd: action.stakeMusd,
|
|
67
|
+
...(action.forecastProbability != null
|
|
68
|
+
? { forecastProbability: action.forecastProbability }
|
|
69
|
+
: {}),
|
|
70
|
+
...pmFloorBody(policy),
|
|
35
71
|
}, trace);
|
|
36
72
|
}
|
|
37
73
|
else {
|
|
@@ -62,6 +98,9 @@ export async function fetchQuote(client, action, observation, trace) {
|
|
|
62
98
|
futuresFeeBps: asNum(asObj(d.executionModel).feeBps),
|
|
63
99
|
estimatedEntryFeeMusd: asNum(asObj(d.executionModel).estimatedEntryFeeMusd),
|
|
64
100
|
cashRequiredMusd: asNum(d.cashRequiredMusd),
|
|
101
|
+
...(Object.prototype.hasOwnProperty.call(d, "funding")
|
|
102
|
+
? { funding: fundingQuoteEvidence(d.funding) }
|
|
103
|
+
: {}),
|
|
65
104
|
}
|
|
66
105
|
: {}),
|
|
67
106
|
// Freshness lives in the response's `observation` block (anti-look-ahead).
|
|
@@ -76,7 +115,7 @@ export async function executeAction(client, action, observation, trace, idempote
|
|
|
76
115
|
// Optional SELF-REPORTED runner provenance, attached to a pm_open only (the one
|
|
77
116
|
// durable-artifact write path here). Absent => the request is byte-identical to
|
|
78
117
|
// pre-provenance behavior.
|
|
79
|
-
provenance) {
|
|
118
|
+
provenance, policy) {
|
|
80
119
|
if (action.type === "futures_open") {
|
|
81
120
|
const coinId = coinIdFor(observation, action.symbol);
|
|
82
121
|
if (!coinId)
|
|
@@ -146,6 +185,10 @@ provenance) {
|
|
|
146
185
|
...(action.forecastProbability != null
|
|
147
186
|
? { forecastProbability: action.forecastProbability }
|
|
148
187
|
: {}),
|
|
188
|
+
...(action.thesis?.summary ? { thesis: action.thesis.summary } : {}),
|
|
189
|
+
// The CONFIGURED entry floor rides along so the server re-checks it inside
|
|
190
|
+
// its locked open transaction (absent => byte-identical request).
|
|
191
|
+
...pmFloorBody(policy),
|
|
149
192
|
// Attach runner provenance only when present (byte-identical to before when absent).
|
|
150
193
|
...(provenance ? { provenance } : {}),
|
|
151
194
|
agentTrace: trace,
|
package/dist/agent/cli.d.ts
CHANGED
package/dist/agent/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { resolve as resolvePath, dirname, join, basename } from "node:path";
|
|
|
10
10
|
import { parse as parseYaml } from "yaml";
|
|
11
11
|
import { resolveAgent, ResolveError, mergeProseParts, isSkillProseSource, hostedProseBudget, HOSTED_PROSE_MAX_CHARS, } from "./resolve.js";
|
|
12
12
|
import { buildSpec, loadAgent } from "./skill.js";
|
|
13
|
+
import { buildAgentDefinitionSnapshot } from "./definitionSnapshot.js";
|
|
13
14
|
import { validateSkill } from "./skillValidator.js";
|
|
14
15
|
import { strictLint } from "./strictLint.js";
|
|
15
16
|
import { checkCapabilityDrift } from "./capabilityGuard.js";
|
|
@@ -227,6 +228,9 @@ export function cmdInspect(path, json = false) {
|
|
|
227
228
|
resolvedConfig: resolved.rawFrontmatter,
|
|
228
229
|
provenance: resolved.provenance,
|
|
229
230
|
contentHashes: resolved.contentHashes,
|
|
231
|
+
// The local runtime consumes these exact compiled inputs. In particular,
|
|
232
|
+
// skill ablation changes prose, not the source manifest or hard caps.
|
|
233
|
+
compiledDefinition: buildAgentDefinitionSnapshot(spec, runtimeProse(resolved)),
|
|
230
234
|
validation: { valid: v.valid, issues: v.issues, lint },
|
|
231
235
|
};
|
|
232
236
|
if (json) {
|
|
@@ -244,11 +248,17 @@ export function cmdInspect(path, json = false) {
|
|
|
244
248
|
`model: ${spec.model ? `${spec.model.provider}/${spec.model.name}` : "(host free-tier)"}`,
|
|
245
249
|
`risk: maxLeverage=${spec.risk.maxLeverage} perTradeMargin=${spec.risk.perTradeMarginMusd} requireStopLoss=${spec.risk.requireStopLoss}`,
|
|
246
250
|
`sources: ${Object.keys(resolved.contentHashes).length} file(s)`,
|
|
251
|
+
`definition: ${output.compiledDefinition.definitionHash}`,
|
|
247
252
|
`validation: ${v.valid ? "valid" : "INVALID"}${lint.length ? ` (+${lint.length} lint note(s))` : ""}`,
|
|
248
253
|
...output.warnings.map((warning) => `⚠ ${warning}`),
|
|
249
254
|
];
|
|
250
255
|
return { ok: v.valid, code: 0, lines, data: output };
|
|
251
256
|
}
|
|
257
|
+
function runtimeProse(resolved) {
|
|
258
|
+
return envFlag(process.env.COINRITHM_AGENT_DISABLE_SKILLS)
|
|
259
|
+
? mergeProseParts(resolved.proseParts.filter((part) => !isSkillProseSource(part.source)))
|
|
260
|
+
: resolved.mergedProse;
|
|
261
|
+
}
|
|
252
262
|
// Is a process still alive? signal 0 probes without sending — ESRCH means it's
|
|
253
263
|
// gone, EPERM means it exists but we can't signal it (still alive). Unknown PIDs
|
|
254
264
|
// (NaN / non-positive) are treated as alive so we never reclaim a malformed lock.
|
|
@@ -333,6 +343,16 @@ export async function cmdRun(path, opts = {}) {
|
|
|
333
343
|
return issuesResult(e.issues, "resolve failed");
|
|
334
344
|
throw e;
|
|
335
345
|
}
|
|
346
|
+
const mergedProse = runtimeProse(loaded.resolved);
|
|
347
|
+
const definition = buildAgentDefinitionSnapshot(loaded.spec, mergedProse);
|
|
348
|
+
if (opts.expectDefinition !== undefined &&
|
|
349
|
+
opts.expectDefinition !== definition.definitionHash) {
|
|
350
|
+
return fail([
|
|
351
|
+
"compiled agent definition does not match --expect-definition; no model or account call was made",
|
|
352
|
+
`current definition: ${definition.definitionHash}`,
|
|
353
|
+
"inspect the change before choosing a new baseline",
|
|
354
|
+
]);
|
|
355
|
+
}
|
|
336
356
|
const apiKey = process.env.COINRITHM_API_KEY;
|
|
337
357
|
if (!apiKey)
|
|
338
358
|
return fail([
|
|
@@ -386,14 +406,12 @@ export async function cmdRun(path, opts = {}) {
|
|
|
386
406
|
const live = !!opts.live;
|
|
387
407
|
const lines = [
|
|
388
408
|
`run ${live ? "LIVE (paper trades WILL be placed)" : "DRY-RUN (no writes; set --live or LIVE=1)"} — ${loaded.spec.name}`,
|
|
409
|
+
`definition: ${definition.definitionHash}`,
|
|
389
410
|
];
|
|
390
411
|
// Skills ablation kill-switch: drop tactic-skill prose from the prompt for
|
|
391
412
|
// token-cost control or A/B testing. Affects ONLY the run-time prompt — the
|
|
392
413
|
// resolver, manifest, and caps are untouched (the spec is still enforced).
|
|
393
414
|
const disableSkills = envFlag(process.env.COINRITHM_AGENT_DISABLE_SKILLS);
|
|
394
|
-
const mergedProse = disableSkills
|
|
395
|
-
? mergeProseParts(loaded.resolved.proseParts.filter((p) => !isSkillProseSource(p.source)))
|
|
396
|
-
: loaded.body;
|
|
397
415
|
if (disableSkills) {
|
|
398
416
|
const dropped = loaded.resolved.proseParts.filter((p) => isSkillProseSource(p.source)).length;
|
|
399
417
|
lines.push(`skills DISABLED via COINRITHM_AGENT_DISABLE_SKILLS — ${dropped} tactic skill(s) dropped from the prompt (caps unchanged)`);
|
|
@@ -442,6 +460,8 @@ function parseFlags(args) {
|
|
|
442
460
|
out.preset = args[++i];
|
|
443
461
|
else if (a === "--state")
|
|
444
462
|
out.state = args[++i];
|
|
463
|
+
else if (a === "--expect-definition")
|
|
464
|
+
out.expectDefinition = args[++i] ?? "";
|
|
445
465
|
else
|
|
446
466
|
out._.push(a);
|
|
447
467
|
}
|
|
@@ -455,7 +475,7 @@ function usageLines() {
|
|
|
455
475
|
" inspect <path> [--json]",
|
|
456
476
|
" eject <agent.md | dir>",
|
|
457
477
|
" lock <path>",
|
|
458
|
-
" run <path> [--once] [--live] [--dry-run] [--state <file>] (dry-run by default)",
|
|
478
|
+
" run <path> [--once] [--live] [--dry-run] [--state <file>] [--expect-definition sha256:...] (dry-run by default)",
|
|
459
479
|
];
|
|
460
480
|
}
|
|
461
481
|
export async function main(argv) {
|
|
@@ -489,6 +509,7 @@ export async function main(argv) {
|
|
|
489
509
|
once: flags.once,
|
|
490
510
|
live,
|
|
491
511
|
stateFile: flags.state,
|
|
512
|
+
expectDefinition: flags.expectDefinition,
|
|
492
513
|
});
|
|
493
514
|
break;
|
|
494
515
|
}
|
package/dist/agent/client.d.ts
CHANGED
|
@@ -91,6 +91,9 @@ export declare class CoinRithmClient {
|
|
|
91
91
|
slug: string;
|
|
92
92
|
outcomeExternalMarketId: string;
|
|
93
93
|
stakeMusd: number;
|
|
94
|
+
side?: "yes" | "no";
|
|
95
|
+
forecastProbability?: number;
|
|
96
|
+
bankrollMusd?: number;
|
|
94
97
|
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
95
98
|
openFutures(body: {
|
|
96
99
|
coinId: string;
|
|
@@ -133,6 +136,7 @@ export declare class CoinRithmClient {
|
|
|
133
136
|
stakeMusd: number;
|
|
134
137
|
idempotencyKey: string;
|
|
135
138
|
forecastProbability?: number;
|
|
139
|
+
thesis?: string;
|
|
136
140
|
provenance?: ProvenanceReport;
|
|
137
141
|
agentTrace?: AgentTrace;
|
|
138
142
|
}): Promise<ApiResult>;
|
package/dist/agent/decision.d.ts
CHANGED
|
@@ -19,9 +19,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
19
19
|
marginMusd?: any;
|
|
20
20
|
stopLossPrice?: any;
|
|
21
21
|
takeProfitPrice?: any;
|
|
22
|
+
thesis?: import("./types.js").Thesis | undefined;
|
|
22
23
|
confidence?: any;
|
|
23
24
|
rationaleSummary?: string | undefined;
|
|
24
|
-
thesis?: import("./types.js").Thesis | undefined;
|
|
25
25
|
}, {
|
|
26
26
|
symbol: string;
|
|
27
27
|
side: "long" | "short";
|
|
@@ -30,9 +30,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
30
30
|
marginMusd?: unknown;
|
|
31
31
|
stopLossPrice?: unknown;
|
|
32
32
|
takeProfitPrice?: unknown;
|
|
33
|
+
thesis?: any;
|
|
33
34
|
confidence?: unknown;
|
|
34
35
|
rationaleSummary?: string | undefined;
|
|
35
|
-
thesis?: any;
|
|
36
36
|
}>, z.ZodObject<{
|
|
37
37
|
type: z.ZodLiteral<"futures_close">;
|
|
38
38
|
positionId: z.ZodEffects<z.ZodTypeAny, any, unknown>;
|
|
@@ -44,16 +44,16 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
44
44
|
type: "futures_close";
|
|
45
45
|
positionId?: any;
|
|
46
46
|
fraction?: any;
|
|
47
|
+
thesis?: import("./types.js").Thesis | undefined;
|
|
47
48
|
confidence?: any;
|
|
48
49
|
rationaleSummary?: string | undefined;
|
|
49
|
-
thesis?: import("./types.js").Thesis | undefined;
|
|
50
50
|
}, {
|
|
51
51
|
type: "futures_close";
|
|
52
52
|
positionId?: unknown;
|
|
53
53
|
fraction?: unknown;
|
|
54
|
+
thesis?: any;
|
|
54
55
|
confidence?: unknown;
|
|
55
56
|
rationaleSummary?: string | undefined;
|
|
56
|
-
thesis?: any;
|
|
57
57
|
}>, z.ZodObject<{
|
|
58
58
|
type: z.ZodLiteral<"futures_set_sltp">;
|
|
59
59
|
positionId: z.ZodEffects<z.ZodTypeAny, any, unknown>;
|
|
@@ -67,17 +67,17 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
67
67
|
stopLossPrice?: any;
|
|
68
68
|
takeProfitPrice?: any;
|
|
69
69
|
positionId?: any;
|
|
70
|
+
thesis?: import("./types.js").Thesis | undefined;
|
|
70
71
|
confidence?: any;
|
|
71
72
|
rationaleSummary?: string | undefined;
|
|
72
|
-
thesis?: import("./types.js").Thesis | undefined;
|
|
73
73
|
}, {
|
|
74
74
|
type: "futures_set_sltp";
|
|
75
75
|
stopLossPrice?: unknown;
|
|
76
76
|
takeProfitPrice?: unknown;
|
|
77
77
|
positionId?: unknown;
|
|
78
|
+
thesis?: any;
|
|
78
79
|
confidence?: unknown;
|
|
79
80
|
rationaleSummary?: string | undefined;
|
|
80
|
-
thesis?: any;
|
|
81
81
|
}>, z.ZodObject<{
|
|
82
82
|
type: z.ZodLiteral<"spot_order">;
|
|
83
83
|
symbol: z.ZodString;
|
|
@@ -97,9 +97,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
97
97
|
quantity?: any;
|
|
98
98
|
limitPrice?: any;
|
|
99
99
|
stopPrice?: any;
|
|
100
|
+
thesis?: import("./types.js").Thesis | undefined;
|
|
100
101
|
confidence?: any;
|
|
101
102
|
rationaleSummary?: string | undefined;
|
|
102
|
-
thesis?: import("./types.js").Thesis | undefined;
|
|
103
103
|
}, {
|
|
104
104
|
symbol: string;
|
|
105
105
|
side: "buy" | "sell";
|
|
@@ -108,9 +108,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
108
108
|
quantity?: unknown;
|
|
109
109
|
limitPrice?: unknown;
|
|
110
110
|
stopPrice?: unknown;
|
|
111
|
+
thesis?: any;
|
|
111
112
|
confidence?: unknown;
|
|
112
113
|
rationaleSummary?: string | undefined;
|
|
113
|
-
thesis?: any;
|
|
114
114
|
}>, z.ZodObject<{
|
|
115
115
|
type: z.ZodLiteral<"spot_cancel">;
|
|
116
116
|
orderId: z.ZodEffects<z.ZodTypeAny, any, unknown>;
|
|
@@ -119,16 +119,16 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
119
119
|
thesis: z.ZodOptional<z.ZodEffects<z.ZodAny, import("./types.js").Thesis | undefined, any>>;
|
|
120
120
|
}, "strict", z.ZodTypeAny, {
|
|
121
121
|
type: "spot_cancel";
|
|
122
|
+
thesis?: import("./types.js").Thesis | undefined;
|
|
122
123
|
confidence?: any;
|
|
123
124
|
rationaleSummary?: string | undefined;
|
|
124
125
|
orderId?: any;
|
|
125
|
-
thesis?: import("./types.js").Thesis | undefined;
|
|
126
126
|
}, {
|
|
127
127
|
type: "spot_cancel";
|
|
128
|
+
thesis?: any;
|
|
128
129
|
confidence?: unknown;
|
|
129
130
|
rationaleSummary?: string | undefined;
|
|
130
131
|
orderId?: unknown;
|
|
131
|
-
thesis?: any;
|
|
132
132
|
}>, z.ZodObject<{
|
|
133
133
|
type: z.ZodLiteral<"pm_open">;
|
|
134
134
|
ref: z.ZodOptional<z.ZodString>;
|
|
@@ -147,9 +147,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
147
147
|
outcomeExternalMarketId?: string | undefined;
|
|
148
148
|
stakeMusd?: any;
|
|
149
149
|
forecastProbability?: number | undefined;
|
|
150
|
+
thesis?: import("./types.js").Thesis | undefined;
|
|
150
151
|
confidence?: any;
|
|
151
152
|
rationaleSummary?: string | undefined;
|
|
152
|
-
thesis?: import("./types.js").Thesis | undefined;
|
|
153
153
|
ref?: string | undefined;
|
|
154
154
|
}, {
|
|
155
155
|
type: "pm_open";
|
|
@@ -158,9 +158,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
|
|
|
158
158
|
outcomeExternalMarketId?: string | undefined;
|
|
159
159
|
stakeMusd?: unknown;
|
|
160
160
|
forecastProbability?: any;
|
|
161
|
+
thesis?: any;
|
|
161
162
|
confidence?: unknown;
|
|
162
163
|
rationaleSummary?: string | undefined;
|
|
163
|
-
thesis?: any;
|
|
164
164
|
ref?: string | undefined;
|
|
165
165
|
}>]>;
|
|
166
166
|
export declare const DECISION_JSON_SCHEMA: {
|
|
@@ -138,6 +138,31 @@ export function buildDecisionInputRecord(input) {
|
|
|
138
138
|
"sentimentBullishPct",
|
|
139
139
|
]),
|
|
140
140
|
freshness: freshness(r.freshness),
|
|
141
|
+
...(r.indicatorContext
|
|
142
|
+
? {
|
|
143
|
+
indicatorContext: {
|
|
144
|
+
range: code(obj(r.indicatorContext).range, ["1D"]),
|
|
145
|
+
asOf: sourceTimestamp(obj(r.indicatorContext).asOf) ?? null,
|
|
146
|
+
intervalStatus: code(obj(r.indicatorContext).intervalStatus, [
|
|
147
|
+
"regular",
|
|
148
|
+
"irregular",
|
|
149
|
+
"unknown",
|
|
150
|
+
]),
|
|
151
|
+
...numeric(obj(r.indicatorContext), [
|
|
152
|
+
"nominalIntervalSeconds",
|
|
153
|
+
"barCount",
|
|
154
|
+
"timestampedBarCount",
|
|
155
|
+
"checkedIntervalCount",
|
|
156
|
+
"irregularIntervalCount",
|
|
157
|
+
"maxGapSeconds",
|
|
158
|
+
]),
|
|
159
|
+
recent15: {
|
|
160
|
+
...numeric(obj(obj(r.indicatorContext).recent15), ["barCount"]),
|
|
161
|
+
intervalStatus: code(obj(obj(r.indicatorContext).recent15).intervalStatus, ["regular", "irregular", "unknown"]),
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
: {}),
|
|
141
166
|
indicators: {
|
|
142
167
|
...numeric(obj(r.indicators), [
|
|
143
168
|
"asOfClose",
|
|
@@ -334,6 +359,7 @@ const LIST_KEYS = {
|
|
|
334
359
|
"sentimentBullishPct",
|
|
335
360
|
"freshness",
|
|
336
361
|
"indicators",
|
|
362
|
+
"indicatorContext",
|
|
337
363
|
"fundamentals",
|
|
338
364
|
],
|
|
339
365
|
futuresPositions: [
|
|
@@ -379,6 +405,19 @@ const LIST_KEYS = {
|
|
|
379
405
|
universeMovers: ["symbol", "change24hPct", "priceUsd"],
|
|
380
406
|
};
|
|
381
407
|
const NESTED_KEYS = {
|
|
408
|
+
indicatorContext: [
|
|
409
|
+
"range",
|
|
410
|
+
"asOf",
|
|
411
|
+
"nominalIntervalSeconds",
|
|
412
|
+
"barCount",
|
|
413
|
+
"timestampedBarCount",
|
|
414
|
+
"checkedIntervalCount",
|
|
415
|
+
"irregularIntervalCount",
|
|
416
|
+
"maxGapSeconds",
|
|
417
|
+
"intervalStatus",
|
|
418
|
+
"recent15",
|
|
419
|
+
],
|
|
420
|
+
recent15: ["barCount", "intervalStatus"],
|
|
382
421
|
freshness: ["status", "ageSeconds", "asOf", "basis"],
|
|
383
422
|
quality: [
|
|
384
423
|
"decisionEligible",
|
|
@@ -432,6 +471,10 @@ function validRow(value, keys) {
|
|
|
432
471
|
return sourceTimestamp(v) === v;
|
|
433
472
|
if (key === "basis")
|
|
434
473
|
return code(v, FRESHNESS_BASES) !== null;
|
|
474
|
+
if (key === "range")
|
|
475
|
+
return code(v, ["1D"]) !== null;
|
|
476
|
+
if (key === "intervalStatus")
|
|
477
|
+
return code(v, ["regular", "irregular", "unknown"]) !== null;
|
|
435
478
|
if (key === "policyVersion")
|
|
436
479
|
return typeof v === "string" && /^pm-quality-\d{1,3}$/.test(v);
|
|
437
480
|
if (key === "warningReasons" || key === "blockReasons") {
|
|
@@ -17,6 +17,44 @@ const PM_MIN_FORECAST_EDGE_POINTS = (() => {
|
|
|
17
17
|
return Number.isFinite(raw) && raw >= 0 ? raw : 2;
|
|
18
18
|
})();
|
|
19
19
|
const PM_MIN_STAKE_MUSD = 10; // server minimum prediction-market stake
|
|
20
|
+
// Match backend-v2 validateSlTpPrices, including its boundary tolerance. This
|
|
21
|
+
// preflight uses observed evidence only; the API still checks a fresh mark and
|
|
22
|
+
// locked position at execution. Older observations can omit these prices.
|
|
23
|
+
const TRIGGER_PRICE_EPS = 1e-8;
|
|
24
|
+
function validateTriggerUpdate(action, pos) {
|
|
25
|
+
// executeAction omits null as well as undefined; neither clears a trigger in
|
|
26
|
+
// this runner. Check the resulting state, including retained protection.
|
|
27
|
+
const sl = action.stopLossPrice ?? pos.stopLossPrice;
|
|
28
|
+
const tp = action.takeProfitPrice ?? pos.takeProfitPrice;
|
|
29
|
+
const positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
30
|
+
if (sl != null && !positive(sl))
|
|
31
|
+
return fail("invalid_stop_loss_price", `position ${pos.id}: stop loss must be finite and positive`);
|
|
32
|
+
if (tp != null && !positive(tp))
|
|
33
|
+
return fail("invalid_take_profit_price", `position ${pos.id}: take profit must be finite and positive`);
|
|
34
|
+
if (pos.side !== "long" && pos.side !== "short")
|
|
35
|
+
return ok();
|
|
36
|
+
const long = pos.side === "long";
|
|
37
|
+
const mark = pos.markPrice;
|
|
38
|
+
const liq = pos.liquidationPrice;
|
|
39
|
+
if (sl != null) {
|
|
40
|
+
if (positive(mark) &&
|
|
41
|
+
(long ? sl >= mark - TRIGGER_PRICE_EPS : sl <= mark + TRIGGER_PRICE_EPS)) {
|
|
42
|
+
return fail(long ? "stop_loss_not_below_mark" : "stop_loss_not_above_mark", `position ${pos.id}: ${pos.side} effective stop ${sl} must be ${long ? "below" : "above"} observed mark ${mark}; omitted triggers keep their existing values`);
|
|
43
|
+
}
|
|
44
|
+
if (positive(liq) &&
|
|
45
|
+
(long ? sl <= liq + TRIGGER_PRICE_EPS : sl >= liq - TRIGGER_PRICE_EPS)) {
|
|
46
|
+
return fail(long
|
|
47
|
+
? "stop_loss_not_above_liquidation"
|
|
48
|
+
: "stop_loss_not_below_liquidation", `position ${pos.id}: ${pos.side} effective stop ${sl} must be ${long ? "above" : "below"} observed liquidation ${liq}; omitted triggers keep their existing values`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (tp != null &&
|
|
52
|
+
positive(mark) &&
|
|
53
|
+
(long ? tp <= mark + TRIGGER_PRICE_EPS : tp >= mark - TRIGGER_PRICE_EPS)) {
|
|
54
|
+
return fail(long ? "take_profit_not_above_mark" : "take_profit_not_below_mark", `position ${pos.id}: ${pos.side} effective take profit ${tp} must be ${long ? "above" : "below"} observed mark ${mark}; omitted triggers keep their existing values`);
|
|
55
|
+
}
|
|
56
|
+
return ok();
|
|
57
|
+
}
|
|
20
58
|
// Entry budgets are exposure budgets, not emergency-action budgets. Closing a
|
|
21
59
|
// futures position, updating its protection, cancelling an order, or selling
|
|
22
60
|
// spot reduces/contains risk and must remain available after an entry cap.
|
|
@@ -219,6 +257,7 @@ export function validateAction(action, ctx) {
|
|
|
219
257
|
if (!hasTrigger) {
|
|
220
258
|
return fail("sltp_no_op", "futures_set_sltp must set at least one positive stopLossPrice or takeProfitPrice");
|
|
221
259
|
}
|
|
260
|
+
return validateTriggerUpdate(action, pos);
|
|
222
261
|
}
|
|
223
262
|
return ok();
|
|
224
263
|
}
|
|
@@ -316,6 +355,41 @@ export function validateAction(action, ctx) {
|
|
|
316
355
|
if (!ctx.quote.freshness || ctx.quote.freshness.status !== "fresh") {
|
|
317
356
|
return fail("stale_quote", `quote freshness ${ctx.quote.freshness?.status ?? "missing"} (need fresh)`);
|
|
318
357
|
}
|
|
358
|
+
// Hard entry-price floor (opt-in, 2026-09-24): compares the CHOSEN
|
|
359
|
+
// outcome's raw market probability at entry (the quote's entryProbability,
|
|
360
|
+
// points) with risk.pmMinEntryProbabilityPct. Fees are deliberately left
|
|
361
|
+
// out: a 19-point outcome fails a 20-point floor even when fees push its
|
|
362
|
+
// all-in cost above 20 (fees belong to the edge check below). No
|
|
363
|
+
// discovery-price fallback: with a floor set and no finite quoted
|
|
364
|
+
// probability the open is rejected, never waved through.
|
|
365
|
+
const entryFloor = spec.risk.pmMinEntryProbabilityPct;
|
|
366
|
+
if (entryFloor !== undefined) {
|
|
367
|
+
// A present-but-invalid floor never silently means "no floor".
|
|
368
|
+
if (typeof entryFloor !== "number" ||
|
|
369
|
+
!Number.isFinite(entryFloor) ||
|
|
370
|
+
entryFloor < 0 ||
|
|
371
|
+
entryFloor > 100) {
|
|
372
|
+
return fail("pm_entry_floor_invalid", `risk.pmMinEntryProbabilityPct ${JSON.stringify(entryFloor)} is not a number between 0 and 100`);
|
|
373
|
+
}
|
|
374
|
+
const market = ctx.quote.entryProbability;
|
|
375
|
+
if (typeof market !== "number" ||
|
|
376
|
+
!Number.isFinite(market) ||
|
|
377
|
+
market < 0 ||
|
|
378
|
+
market > 100) {
|
|
379
|
+
return fail("pm_entry_price_unavailable", `entry floor ${entryFloor}pt is set but the quote carries no usable market probability for the chosen outcome (${JSON.stringify(market ?? null)})`);
|
|
380
|
+
}
|
|
381
|
+
if (market + 1e-9 < entryFloor) {
|
|
382
|
+
const stake = ctx.quote.stakeMusd;
|
|
383
|
+
const shares = ctx.quote.sharesEstimate;
|
|
384
|
+
const cost = typeof stake === "number" &&
|
|
385
|
+
typeof shares === "number" &&
|
|
386
|
+
stake > 0 &&
|
|
387
|
+
shares > 0
|
|
388
|
+
? ` (fee-inclusive cost ${((stake / shares) * 100).toFixed(1)}pt)`
|
|
389
|
+
: "";
|
|
390
|
+
return fail("pm_entry_below_floor", `market ${market.toFixed(1)}pt is below the ${entryFloor}pt entry floor for the chosen outcome${cost}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
319
393
|
// Forecast consistency. By prompt contract forecastProbability is the
|
|
320
394
|
// model's own probability (1-99) that the outcome IT IS BACKING wins, so
|
|
321
395
|
// buying that outcome only makes sense when the forecast clears what the
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentSpec } from "./types.js";
|
|
2
|
+
/** Private, portable definition; deliberately excludes mutable account state. */
|
|
3
|
+
export interface AgentDefinitionSnapshot {
|
|
4
|
+
schema: "coinrithm.agent-definition.v1";
|
|
5
|
+
engine: {
|
|
6
|
+
packageVersion: string;
|
|
7
|
+
resolverVersion: string;
|
|
8
|
+
indicatorVersion: string;
|
|
9
|
+
apiContractVersion: string;
|
|
10
|
+
};
|
|
11
|
+
/** Pass the actual post-overlay spec, without rebuilding it from frontmatter. */
|
|
12
|
+
spec: AgentSpec;
|
|
13
|
+
/** Exact strategy prose supplied to runCycle, after any skills ablation. */
|
|
14
|
+
mergedProse: string;
|
|
15
|
+
definitionHash: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Binds the compiled definition and declared engine versions, not the original
|
|
19
|
+
* source folder alone. This is an integrity/comparison hash, not a signature,
|
|
20
|
+
* complete replay record, or attestation of a particular deployed binary.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildAgentDefinitionSnapshot(spec: AgentSpec, mergedProse: string): AgentDefinitionSnapshot;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { INDICATOR_VERSION } from "./indicators.js";
|
|
2
|
+
import { COINRITHM_API, RESOLVER_VERSION } from "./version.js";
|
|
3
|
+
import { sha256, stableStringify } from "./util.js";
|
|
4
|
+
/**
|
|
5
|
+
* Binds the compiled definition and declared engine versions, not the original
|
|
6
|
+
* source folder alone. This is an integrity/comparison hash, not a signature,
|
|
7
|
+
* complete replay record, or attestation of a particular deployed binary.
|
|
8
|
+
*/
|
|
9
|
+
export function buildAgentDefinitionSnapshot(spec, mergedProse) {
|
|
10
|
+
const definition = {
|
|
11
|
+
schema: "coinrithm.agent-definition.v1",
|
|
12
|
+
engine: {
|
|
13
|
+
packageVersion: COINRITHM_API.mcpVersion,
|
|
14
|
+
resolverVersion: RESOLVER_VERSION,
|
|
15
|
+
indicatorVersion: INDICATOR_VERSION,
|
|
16
|
+
apiContractVersion: COINRITHM_API.openapiVersion,
|
|
17
|
+
},
|
|
18
|
+
spec,
|
|
19
|
+
mergedProse,
|
|
20
|
+
};
|
|
21
|
+
// Detach from caller-owned spec: later mutations must not invalidate a saved
|
|
22
|
+
// artifact while leaving its old hash attached to it.
|
|
23
|
+
const serialized = stableStringify(definition);
|
|
24
|
+
return {
|
|
25
|
+
...JSON.parse(serialized),
|
|
26
|
+
definitionHash: sha256(serialized),
|
|
27
|
+
};
|
|
28
|
+
}
|
package/dist/agent/engine.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { runCycle, type RunnerDeps } from "./runner.js";
|
|
2
|
+
export { buildAgentDefinitionSnapshot, type AgentDefinitionSnapshot, } from "./definitionSnapshot.js";
|
|
2
3
|
export { DECISION_INPUT_MAX_BYTES, sanitizeDecisionInputRecord, type DecisionInputRecord, } from "./decisionReceipt.js";
|
|
3
|
-
export { selectProvider, providerForRoute, type ProviderEnv, type Provider, type DecideInput, type DecideResult, type DecideRouteAttempt, type DecideRouteMeta, } from "./providers.js";
|
|
4
|
+
export { selectProvider, providerForRoute, classifyProviderFailure, type ProviderEnv, type Provider, type DecideInput, type DecideResult, type DecideRouteAttempt, type DecideRouteMeta, } from "./providers.js";
|
|
4
5
|
export { parseDecision } from "./decision.js";
|
|
5
6
|
export { chatShapeFor, buildChatBody, type ChatShape, } from "./providerCapabilities.js";
|
|
6
7
|
export { probeDecisionContract, type ProbeRoute, type ProbeDecisionResult, } from "./decisionProbe.js";
|
package/dist/agent/engine.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// This barrel is the ONE import a host scheduler needs; it re-exports only the
|
|
7
7
|
// stable engine pieces, never the CLI.
|
|
8
8
|
export { runCycle } from "./runner.js";
|
|
9
|
+
export { buildAgentDefinitionSnapshot, } from "./definitionSnapshot.js";
|
|
9
10
|
export { DECISION_INPUT_MAX_BYTES, sanitizeDecisionInputRecord, } from "./decisionReceipt.js";
|
|
10
|
-
export { selectProvider, providerForRoute, } from "./providers.js";
|
|
11
|
+
export { selectProvider, providerForRoute, classifyProviderFailure, } from "./providers.js";
|
|
11
12
|
export { parseDecision } from "./decision.js";
|
|
12
13
|
// Reliability slice A: the declarative request-capability table and the
|
|
13
14
|
// representative decision probe (route eligibility = a REAL parsed decision,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ResolveIssue } from "./types.js";
|
|
2
|
-
export type CapDirection = "lower" | "higher" | "true";
|
|
2
|
+
export type CapDirection = "lower" | "lower_zero_off" | "higher" | "true";
|
|
3
3
|
export declare const RISK_CAPS: Record<string, CapDirection>;
|
|
4
4
|
export declare const LIMIT_CAPS: Record<string, CapDirection>;
|
|
5
5
|
export declare function isAtLeastAsRestrictive(dir: CapDirection, candidate: unknown, base: unknown): boolean;
|
package/dist/agent/mergeRules.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Normal (non-cap) config uses simple precedence (inline > $ref > defaults),
|
|
10
10
|
// handled in the resolver; this module owns only the cap arithmetic.
|
|
11
|
+
// 0 is "no cap" for lower_zero_off; every other value compares as itself.
|
|
12
|
+
const zeroOffLimit = (n) => (n === 0 ? Infinity : n);
|
|
13
|
+
const isZeroOffValue = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
11
14
|
// risk.* hard caps a tactic module may tighten.
|
|
12
15
|
export const RISK_CAPS = {
|
|
13
16
|
maxLeverage: "lower",
|
|
@@ -17,9 +20,9 @@ export const RISK_CAPS = {
|
|
|
17
20
|
};
|
|
18
21
|
// limits.* throughput/spend caps a tactic module may tighten.
|
|
19
22
|
export const LIMIT_CAPS = {
|
|
20
|
-
maxTradesPerDay: "
|
|
23
|
+
maxTradesPerDay: "lower_zero_off",
|
|
21
24
|
maxWritesPerCycle: "lower",
|
|
22
|
-
maxDailyLossMusd: "
|
|
25
|
+
maxDailyLossMusd: "lower_zero_off",
|
|
23
26
|
maxOpenMarginMusd: "lower",
|
|
24
27
|
};
|
|
25
28
|
// Is `candidate` at least as restrictive as `base` (i.e. a legal tightening)?
|
|
@@ -29,6 +32,11 @@ export function isAtLeastAsRestrictive(dir, candidate, base) {
|
|
|
29
32
|
// legal: base=false→candidate any; base=true→candidate must be true.
|
|
30
33
|
return base === true ? candidate === true : true;
|
|
31
34
|
}
|
|
35
|
+
if (dir === "lower_zero_off") {
|
|
36
|
+
if (!isZeroOffValue(candidate) || !isZeroOffValue(base))
|
|
37
|
+
return false;
|
|
38
|
+
return zeroOffLimit(candidate) <= zeroOffLimit(base);
|
|
39
|
+
}
|
|
32
40
|
if (typeof candidate !== "number" || typeof base !== "number") {
|
|
33
41
|
// a non-numeric cap candidate where a number is expected is not a legal
|
|
34
42
|
// tightening (caller will surface a type issue separately).
|
|
@@ -40,6 +48,13 @@ export function isAtLeastAsRestrictive(dir, candidate, base) {
|
|
|
40
48
|
export function mostRestrictive(dir, a, b) {
|
|
41
49
|
if (dir === "true")
|
|
42
50
|
return a === true || b === true ? true : false;
|
|
51
|
+
if (dir === "lower_zero_off") {
|
|
52
|
+
if (!isZeroOffValue(a))
|
|
53
|
+
return b;
|
|
54
|
+
if (!isZeroOffValue(b))
|
|
55
|
+
return a;
|
|
56
|
+
return zeroOffLimit(a) <= zeroOffLimit(b) ? a : b;
|
|
57
|
+
}
|
|
43
58
|
if (typeof a !== "number")
|
|
44
59
|
return b;
|
|
45
60
|
if (typeof b !== "number")
|