runbios-mcp 0.2.18 → 0.2.19-dev.277
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/config.d.ts +17 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -2
- package/dist/config.js.map +1 -1
- package/dist/http/assistant.js +3 -3
- package/dist/http/assistant.js.map +1 -1
- package/dist/http/metadata.js +1 -1
- package/dist/http/metadata.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -17
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -22,7 +22,7 @@ const RAW_FAILURE_TEXT_FIELDS = [
|
|
|
22
22
|
"last_error",
|
|
23
23
|
"stage_detail",
|
|
24
24
|
];
|
|
25
|
-
import { LOOP_TOOL_PREFIX, resolveLaunchGates } from "./config.js";
|
|
25
|
+
import { LOOP_TOOL_PREFIX, resolveLaunchGates, withoutGatedLoopIntrospection } from "./config.js";
|
|
26
26
|
import { VERSION } from "./version.js";
|
|
27
27
|
export { VERSION };
|
|
28
28
|
/**
|
|
@@ -1541,7 +1541,9 @@ create (1-5 choices), and the queue itself stays opt-in.`,
|
|
|
1541
1541
|
/* ══════════════════════════════════════════════════════════════════════════ */
|
|
1542
1542
|
server.tool("introspect_api_key", "Discover what this API key can do. Returns the key's permissions (scopes), the org and workspace it's bound to, allowed MCP tools and SDK methods, rate limits, and expiration. Call this first to understand your access level and available capabilities.", {}, async () => {
|
|
1543
1543
|
const data = await client.api("/api/api-keys/introspect");
|
|
1544
|
-
|
|
1544
|
+
// Where the loop is gated, IAM's loop tools and descriptions are removed:
|
|
1545
|
+
// the answer must not advertise what tools/list hides (src/config.ts).
|
|
1546
|
+
return json(withoutGatedLoopIntrospection(data, launchGates));
|
|
1545
1547
|
});
|
|
1546
1548
|
/* ══════════════════════════════════════════════════════════════════════════ */
|
|
1547
1549
|
/* TOOL: get_wallet_balance */
|
|
@@ -2918,7 +2920,7 @@ create (1-5 choices), and the queue itself stays opt-in.`,
|
|
|
2918
2920
|
});
|
|
2919
2921
|
return json(data);
|
|
2920
2922
|
});
|
|
2921
|
-
server.tool("loop_import_rows", "Bring data the user ALREADY HAS into the loop — an export from another provider, a spreadsheet of past answers, a set of preference pairs. This does NOT create a training set: it creates conversations, in the same place captured ones live, subject to the same review, tags and pipelines. A row becomes trainable when something says it is good, never because it arrived in a file. Each row is read for what it is: a prompt/chosen/rejected triple becomes a
|
|
2923
|
+
server.tool("loop_import_rows", "Bring data the user ALREADY HAS into the loop — an export from another provider, a spreadsheet of past answers, a set of preference pairs. This does NOT create a training set: it creates conversations, in the same place captured ones live, subject to the same review, tags and pipelines. A row becomes trainable when something says it is good, never because it arrived in a file. Each row is read for what it is: a prompt/chosen/rejected triple becomes a correction (chosen is learned from); an answer plus a yes-or-no becomes a thumbs verdict; a question and an answer waits for review; a question with no answer waits for an answer; a paragraph of prose is refused, because it is not a conversation. A verdict that arrives with the file is kept, but recorded as having come from the user's earlier process rather than from a reviewer here. WHAT COMES BACK, and what to tell the user: imported is what this call created and trace_ids names the conversations from the file that are now in the loop; already_present is rows an earlier import of the same file already had, so they were not stored twice; by_shape counts the shapes this call CREATED, not what was merely recognised and not rows that were already here, so a file that was silently the wrong shape shows up there rather than merely importing fewer rows than expected, and reviewed and needs_review likewise count only what this call wrote; refused with refused_why is rows whose shape could not be read, which the user fixes by changing the file; not_saved with not_saved_rows is rows that were read and understood and then could not be stored, which the file cannot fix; verdicts_not_saved with verdicts_not_saved_rows is rows whose conversation stored and whose VERDICT did not, so those conversations are in the loop waiting for review rather than reviewed. A call with any not_saved or verdicts_not_saved rows fails with code IMPORT_INCOMPLETE and carries all of the same counts. HOW TO RETRY SAFELY: every response carries import_id, the token that call was filed under. Send the SAME rows again with that import_id and anything already imported comes back under already_present instead of being stored a second time, while a verdict that failed to write is attempted again. Do NOT retry without repeating import_id: a call that repeats no token is its own import and stores everything again, deliberately, because two calls carrying the same rows are as likely to be two pages of one export as one call sent twice. SENDING MORE THAN 5000 ROWS: split the file and either give every row its own row_ids entry, after which chunking and ordering stop mattering, or send one import_id with the row_offset each page starts at. Without row_ids, do not send back only the rows not_saved_rows names: a shorter list moves every row after the gap and imports it again.", {
|
|
2922
2924
|
source: z.string().describe("Where this came from, e.g. 'zendesk-2026' or 'gpt4-history'. Required, and becomes the source every imported conversation is filed under — do not use a generic word like 'import', because that produces a corpus nobody can slice later."),
|
|
2923
2925
|
rows: z.array(z.record(z.string(), z.any())).describe("The rows themselves, already parsed. At most 5000 per call: the call is synchronous. Pass each row in whatever shape it already has rather than reshaping it — the shape is how the server decides whether it carries a verdict."),
|
|
2924
2926
|
model: z.string().optional().describe("Which model produced these answers, when the rows do not say per-row"),
|
|
@@ -2934,7 +2936,7 @@ create (1-5 choices), and the queue itself stays opt-in.`,
|
|
|
2934
2936
|
});
|
|
2935
2937
|
return json(data);
|
|
2936
2938
|
});
|
|
2937
|
-
server.tool("loop_list_traces", "List recorded conversations (samples) for this workspace, newest first. To read a pipeline's data by feedback, pass pipeline with signal ('good', 'bad' or 'corrected': the verdict that decides the sample) or unsignalled true (what still needs review). A bad sample is never trained on. from/to narrow by when a conversation was recorded.", {
|
|
2939
|
+
server.tool("loop_list_traces", "List recorded conversations (samples) for this workspace, newest first. To read a pipeline's data by feedback, pass pipeline with signal ('good', 'bad' or 'corrected': the verdict that decides the sample) or unsignalled true (what still needs review). A bad sample is never trained on. from/to narrow by when a conversation was recorded. A conversation kept in the workspace's own storage carries payload_stored_externally; when its text could not be read just now it also carries payload_unavailable, with messages null and completion empty. That conversation is NOT empty: say its text could not be read right now, do not review it, and read it again later.", {
|
|
2938
2940
|
source: z.string().optional().describe("Only conversations from this source"),
|
|
2939
2941
|
model: z.string().optional().describe("Only what THIS model answered, e.g. 'kimi-k3'. The filter distillation is made of — every other filter selects by what was asked or what somebody said about the answer, never by who produced it."),
|
|
2940
2942
|
origin: z.enum(["captured", "imported"]).optional().describe("captured = the workspace's own model behaviour; imported = a file somebody brought in. Pooling them makes 'train only on what we actually served' unanswerable."),
|
|
@@ -2975,28 +2977,25 @@ create (1-5 choices), and the queue itself stays opt-in.`,
|
|
|
2975
2977
|
});
|
|
2976
2978
|
return json(data);
|
|
2977
2979
|
});
|
|
2978
|
-
server.tool("loop_get_trace", "Read one recorded conversation in full, with every verdict anyone has recorded on it. Anything that looked like a credential or a personal detail was removed before it was stored, and redaction_report says what.", { trace_id: z.string().describe("The trace id returned by loop_capture_trace or loop_list_traces") }, async ({ trace_id }) => {
|
|
2980
|
+
server.tool("loop_get_trace", "Read one recorded conversation in full, with every verdict and tag anyone has recorded on it. Anything that looked like a credential or a personal detail was removed before it was stored, and redaction_report says what. payload_unavailable true (messages null, completion empty) means its text is kept in the workspace's own storage and could not be read just now, not that it is empty: say so, do not review it, and read it again later.", { trace_id: z.string().describe("The trace id returned by loop_capture_trace or loop_list_traces") }, async ({ trace_id }) => {
|
|
2979
2981
|
const data = await client.api(`/api/loop/traces/${encodeURIComponent(trace_id)}`);
|
|
2980
2982
|
return json(data);
|
|
2981
2983
|
});
|
|
2982
2984
|
server.tool("loop_delete_trace", "Delete one recorded conversation, with its feedback and tags, for good. It stops counting toward every pipeline that selected it. A version already trained on it keeps what it learned. Confirm with the user first: this cannot be undone.", { trace_id: z.string() }, async ({ trace_id }) => json(await client.api(`/api/loop/traces/${encodeURIComponent(trace_id)}`, { method: "DELETE" })));
|
|
2983
|
-
server.tool("loop_add_signal", "Record a verdict on a conversation: was the answer right? Feedback is APPEND-ONLY — a second verdict does not replace the first, because two reviewers disagreeing is information worth keeping.\n\
|
|
2985
|
+
server.tool("loop_add_signal", "Record a verdict on a conversation: was the answer right? Feedback is APPEND-ONLY — a second verdict does not replace the first, because two reviewers disagreeing is information worth keeping.\n\nWHAT EACH VERDICT DOES (every pipeline fine-tunes on examples):\n• accepted (a thumbs up) — the answer was good. It becomes an example as it stands.\n• rejected (a thumbs down) — it was wrong and you have nothing better. It is LEFT OUT of training and nothing is learned from it; prefer edited whenever you know the better answer.\n• edited — the model was WRONG and here is what it should have said. The most valuable single action: your answer is trained on in place of the model's. Nothing is learned from the answer it replaced.\n• gold — the reference answer for this question, recorded whatever the model happened to say. It trains exactly like edited; it only makes no claim that the model was wrong.", {
|
|
2984
2986
|
trace_id: z.string(),
|
|
2985
|
-
verdict: z.enum(["accepted", "rejected", "edited", "
|
|
2986
|
-
source: z.enum(["human", "verifier", "judge", "behavioural"]).optional().describe("Who judged. A human outranks a verifier, a verifier outranks a judge, a judge outranks a behavioural hint. Default human."),
|
|
2987
|
+
verdict: z.enum(["accepted", "rejected", "edited", "gold"]).describe("accepted = thumbs up; rejected = thumbs down with nothing better to offer; edited = the model was wrong and you wrote the better answer; gold = the reference answer for this question regardless of what the model said"),
|
|
2987
2988
|
correction: z.string().optional().describe("REQUIRED when verdict is 'edited' (the answer it should have given) or 'gold' (the reference answer for this question)"),
|
|
2988
|
-
score: z.number().optional().describe("REQUIRED when verdict is 'scored'"),
|
|
2989
|
-
ground_truth: z.string().optional().describe("The value or fact the answer can be checked against"),
|
|
2990
2989
|
reason: z.string().optional().describe("Why, in your own words"),
|
|
2991
2990
|
author: z.string().optional(),
|
|
2992
2991
|
labels: z.array(z.string()).optional().describe("Which pipeline this feedback feeds, named HERE rather than in a second call: a pipeline trains on every conversation carrying any of its tags, so a tag is the pipeline a conversation goes down. Sent with the verdict it is written in one transaction: either both land or neither does. A separate tagging call is the one that gets skipped, and what it leaves is a reviewed conversation in no pipeline — counted in every 'reviewed' total and selected by nothing."),
|
|
2993
|
-
attributes: z.record(z.string(), z.string()).optional().describe("The same, by named dimension: {\"category\": \"billing\"}.
|
|
2992
|
+
attributes: z.record(z.string(), z.string()).optional().describe("The same, by named dimension: {\"category\": \"billing\"}."),
|
|
2994
2993
|
parent: z.string().optional().describe("The master group the bare labels belong to, so selecting the group picks them up without anybody maintaining a list. Leave it out and a label the conversation already carries keeps the group it has; send an empty string to take the labels out of their group."),
|
|
2995
|
-
}, async ({ trace_id, verdict,
|
|
2994
|
+
}, async ({ trace_id, verdict, correction, reason, author, labels, attributes, parent }) => {
|
|
2996
2995
|
const data = await client.api(`/api/loop/traces/${encodeURIComponent(trace_id)}/signals`, {
|
|
2997
2996
|
method: "POST",
|
|
2998
2997
|
body: {
|
|
2999
|
-
verdict, source:
|
|
2998
|
+
verdict, source: "human", correction, reason, author,
|
|
3000
2999
|
labels, attributes, parent,
|
|
3001
3000
|
},
|
|
3002
3001
|
});
|
|
@@ -3089,7 +3088,7 @@ create (1-5 choices), and the queue itself stays opt-in.`,
|
|
|
3089
3088
|
body,
|
|
3090
3089
|
})));
|
|
3091
3090
|
server.tool("loop_delete_pipeline", "Delete a pipeline for good. An attempt still running is cancelled; versions it made, their checkpoints and whatever serves the app now are untouched, and its samples and tags are kept. Recording from its own tag's source stops: an app still sending there gets captured:false. THIS CANNOT BE UNDONE and the name is spent: the response's retained_name can never be used by another pipeline in this workspace. To stop it for now, loop_update_pipeline with enabled false. Confirm with the user first.", { pipeline_id: z.string() }, async ({ pipeline_id }) => json(await client.api(`/api/loop/pipelines/${encodeURIComponent(pipeline_id)}`, { method: "DELETE" })));
|
|
3092
|
-
server.tool("loop_run_pipeline", "Train now: start an attempt without waiting for the schedule. SPEND CONSENT: it trains on real GPUs charged to the wallet, so say so and get a yes. It never skips the minimum: 422 NOT_ENOUGH_SAMPLES carries have and need — read both back. 409 RUN_ACTIVE: an attempt is already running. 409 VERSION_LIMIT_REACHED: raise or remove max_versions. 409 RULE_PAUSED: it is switched off or paused (paused_reason says why). 409 PIPELINE_NEEDS_SAVE: its settings changed without a pipeline save; save it with loop_update_pipeline, then train again. 409 MONTHLY_LIMIT_REACHED: the platform's runaway guard for this month; read resumes_at back. 409 SCORING_CAPPED: the comparison's key is at its monthly cap, so what it trained could not be scored; read resumes_at back. 503 AGENT_COULD_NOT_START or 409 AGENT_OFF: the key every attempt is scored with could not be renewed just then, so nothing was started; train now again in a minute (saving the pipeline does not help). Do not retry any of the others unchanged.", { pipeline_id: z.string() }, async ({ pipeline_id }) => json(await client.api(`/api/loop/pipelines/${encodeURIComponent(pipeline_id)}/run`, {
|
|
3091
|
+
server.tool("loop_run_pipeline", "Train now: start an attempt without waiting for the schedule. SPEND CONSENT: it trains on real GPUs charged to the wallet, so say so and get a yes. It never skips the minimum: 422 NOT_ENOUGH_SAMPLES carries have and need — read both back. 409 RUN_ACTIVE: an attempt is already running. 409 VERSION_LIMIT_REACHED: raise or remove max_versions. 409 RULE_PAUSED: it is switched off or paused (paused_reason says why). 409 PIPELINE_NEEDS_SAVE: its settings changed without a pipeline save; save it with loop_update_pipeline, then train again. 409 MONTHLY_LIMIT_REACHED: the platform's runaway guard for this month; read resumes_at back. 409 SCORING_CAPPED: the comparison's key is at its monthly cap, so what it trained could not be scored; read resumes_at back (the pipeline's scoring_capped_until says the same before you try). 409 LIVE_VERSION_STOPPED: the deployment the live version answers from is stopped, paused or deleted, so an attempt could not be compared against it; the user resumes it in Deployments or rolls the version back. 503 AGENT_COULD_NOT_START or 409 AGENT_OFF: the key every attempt is scored with could not be renewed just then, so nothing was started; train now again in a minute (saving the pipeline does not help). Do not retry any of the others unchanged.", { pipeline_id: z.string() }, async ({ pipeline_id }) => json(await client.api(`/api/loop/pipelines/${encodeURIComponent(pipeline_id)}/run`, {
|
|
3093
3092
|
method: "POST",
|
|
3094
3093
|
body: {},
|
|
3095
3094
|
})));
|
|
@@ -3116,11 +3115,11 @@ create (1-5 choices), and the queue itself stays opt-in.`,
|
|
|
3116
3115
|
}, async ({ rule_id, state, limit, offset }) => json(await client.api("/api/loop/training-runs", {
|
|
3117
3116
|
params: { rule_id, state, limit: limit?.toString(), offset: offset?.toString() },
|
|
3118
3117
|
})));
|
|
3119
|
-
server.tool("loop_get_training_run", "One run in full,
|
|
3120
|
-
server.tool("loop_list_pipelines", "Every pipeline in the workspace: the series of versions it has produced (versions are attempts that were put live; attempts[] lists every attempt, winner or not), which one serves the app today (live_version, the same as champion_version, null when what serves is not one of its versions), how each version did against the champion of its day and on the standing benchmark, versions_made against max_versions, the run in flight and which version it will become, and status. Read-only. When the user asks 'is this getting better' or 'what is it doing', answer from this, and read status back honestly: needs_review and needs_funds are waiting on THEM (a decision, a top-up), paused names why in paused_reason
|
|
3118
|
+
server.tool("loop_get_training_run", "One run in full: the base model it trained (base_model_id, model_family) and the recipe, the rows it trained on and held back, its GPU time and what it was billed, the timeline of every state it passed through and why, links to the job, the candidate and the comparison, and available_actions — which says what THIS reader may actually do next. Use available_actions rather than guessing from the state: a run can look promotable and not be, because the consent moved or the candidate is already gone.", { run_id: z.string() }, async ({ run_id }) => json(await client.api(`/api/loop/training-runs/${encodeURIComponent(run_id)}`)));
|
|
3119
|
+
server.tool("loop_list_pipelines", "Every pipeline in the workspace: the series of versions it has produced (versions are attempts that were put live; attempts[] lists every attempt, winner or not), which one serves the app today (live_version, the same as champion_version, null when what serves is not one of its versions), how each version did against the champion of its day and on the standing benchmark, versions_made against max_versions, the run in flight and which version it will become, and status. Read-only. When the user asks 'is this getting better' or 'what is it doing', answer from this, and read status back honestly: needs_review and needs_funds are waiting on THEM (a decision, a top-up), paused names why in paused_reason (consent_invalid means its settings changed since it was last saved: saving it again starts it), complete means the version limit is reached, and running is the platform working. next_reason is the pipeline's own sentence about what it is waiting for. scoring_capped_until, when set, is when the workspace's scoring key comes off its monthly cap: until then nothing trains and Train now answers SCORING_CAPPED, so say so instead of offering Train now. month_spent_cents against monthly_ceiling_cents is what decides whether the next run may start; month_spent_cents is what runs started this UTC month have cost at most (a run still in progress counts at its ceilings), so say 'up to', never 'spent'. A pipeline trains only on new samples: it never retrains the conversations an attempt already had with a recipe changed. The list is a page of 100, newest first: total is how many pipelines the workspace has and truncated is true when more exist after this page. When truncated, say it is the newest of total, and pass offset (this page's offset plus its length) for the next page.", { offset: z.number().int().min(0).optional().describe("How many of the newest pipelines to skip") }, async ({ offset }) => json(await client.api("/api/loop/pipelines", {
|
|
3121
3120
|
params: { offset: offset ? offset.toString() : undefined },
|
|
3122
3121
|
})));
|
|
3123
|
-
server.tool("loop_get_pipeline", "One pipeline in full, by its id: every version with its verdict, the decision a person made, its win rate and benchmark score, what it cost and
|
|
3122
|
+
server.tool("loop_get_pipeline", "One pipeline in full, by its id: every version with its verdict, the decision a person made, its win rate and benchmark score, what it cost and why it was made (trigger, in the same words as attempts[]), plus the run in flight, and what this month's runs have cost at most (month_spent_cents, an upper bound, not an exact spend) against the monthly limit. Read-only: promote, reject and roll back stay on the run tools, and its settings on loop_update_pipeline. minimums says how many samples it has against what the next attempt needs. scoring_capped_until, when set, is when the workspace's scoring key comes off its monthly cap: until then nothing trains and Train now answers SCORING_CAPPED with that time as resumes_at. Only compare benchmark scores whose benchmark_comparable is true; the others were measured on a different yardstick. versions[] are WINNERS ONLY -- attempts that were put live, numbered v1, v2 ... in the order they went live, each with its attempt, base_model_id and model_family -- and live_version is the one serving now; attempts[] is every training run the pipeline made, winner or not, with its outcome (became_version, not_better, inconclusive, stopped, failed, running, waiting_for_review) and trigger (data, schedule, manual, or challenger for the second model trained on the same data). Nothing in flight is a version yet.", { rule_id: z.string().describe("The pipeline's id, from loop_list_pipelines") }, async ({ rule_id }) => json(await client.api(`/api/loop/pipelines/${encodeURIComponent(rule_id)}`)));
|
|
3124
3123
|
server.tool("loop_list_pipeline_benchmarks", "The benchmarks a pipeline is measured on, in priority order (1 is the primary), then the ones it stopped using, and its promotion policy. Every attempt is replayed on each attached benchmark; the policy decides which of those measurements make it a version.", { pipeline_id: z.string() }, async ({ pipeline_id }) => json(await client.api(`/api/loop/pipelines/${encodeURIComponent(pipeline_id)}/benchmarks`)));
|
|
3125
3124
|
server.tool("loop_attach_pipeline_benchmark", "Measure a pipeline on one more benchmark (from loop_list_benchmarks or loop_create_benchmark), last unless priority is given; priority 1 makes it the primary. Applies from the next attempt; older versions are not re-measured and show not_measured for it. 409 BENCHMARK_ALREADY_ATTACHED or BENCHMARK_RETIRED.", {
|
|
3126
3125
|
pipeline_id: z.string(),
|