openvibers/openvibe.actor Commits
Actor: robot tasks are their own class and go to the runtime (one was refused as coding) (#48)
Changes
@@@ -23,7 +23,7 @@ Actor is OpenVibe's own general agent and the router for agent work: it takes a| Agent | Kind | Can do | Trust | Price ||---|---|---|---|---|--| OpenVibe runtime | openvibe-runtime | answer, lookup, research | external (the model runs at DeepSeek) | DeepSeek Flash, $0.30 in / $1.20 out per 1M tokens (peak; off-peak is half, Actor charges peak) |++| OpenVibe runtime | openvibe-runtime | answer, lookup, research, robot | external (the model runs at DeepSeek) | DeepSeek Flash, $0.30 in / $1.20 out per 1M tokens (peak; off-peak is half, Actor charges peak) || OpenAI agent | agent-platform | answer, research, web | external | gpt-5-mini, $0.25 / $2.00 per 1M, plus $0.01 a web search || Open model on OpenVibe | open-model | answer | first-party | free (OpenVibe's own server; `openvibe-llm` on the host) || OpenVibe.Codes | codes | code | first-party | listed, not hosted yet: coding runs need OpenVibe.Run sandboxes; use `openvibe-codes` on your machine |@@@ -39,9 +39,9 @@ const AGENTS = [// The runtime is OpenVibe's code, but its model runs at DeepSeek: the task's text leaves OpenVibe.trust: 'external',quality: 'standard',-- can: ['task:answer', 'task:lookup', 'task:research', 'tool:openvibe-search', 'tool:openvibe-tools'],++ can: ['task:answer', 'task:lookup', 'task:research', 'task:robot', 'tool:openvibe-search', 'tool:openvibe-tools', 'tool:openvibe-bot'],rate_card: { input_usd_per_mtok: 0.30, output_usd_per_mtok: 1.20, source: DEEPSEEK_PRICES, verified_at: VERIFIED },-- blurb: 'OpenVibe\'s own agent: a DeepSeek model that acts through OpenVibe services — search across the network, 160+ tools (DNS, mail, certificates, headers, conversions) and a page reader.',++ blurb: 'OpenVibe\'s own agent: a DeepSeek model that acts through OpenVibe services — search across the network, 160+ tools (DNS, mail, certificates, headers, conversions), a page reader, and your own robots on OpenVibe.Bot when you let it drive them.',},{id: 'openai-agent',@@@ -18,6 +18,7 @@ const SYSTEM = `You check an AI agent's answer before a person sees it. Reply wiok is true when the answer addresses the task and nothing in it is clearly invented, contradictory or unsafe. It does not have to be perfect.ok is false when the answer is empty, refuses without a good reason, answers a different question, contradicts itself, or states something that is plainly impossible.The agent may have searched the web or run tools: do not fail an answer only because it states recent facts, versions, prices or dates you cannot verify yourself, especially when it cites a source for them.++The agent may have made the person's own robot act through OpenVibe.Bot: an answer that reports what the robot did, or why it stopped or was refused, addresses the task.When the task is a greeting, small talk, a test message or too vague to act on, a short friendly reply, a summary of what the agent can do or a clarifying question is a good answer: ok is true.`;/** Which checkers may read this agent's answer, best first: another family, then the same family. */@@@ -8,22 +8,25 @@* lookup facts about a domain, address or site → task:lookup* research read the pages it names, or OpenVibe's content → task:research* answer write or explain from what a model knows → task:answer++ * robot make the person's own OpenVibe.Bot robot act → task:robot** heuristic(task) is free and is what private mode and the public dry run use. classify() asks a cheap model when one* is configured, and falls back to the heuristic on any failure; its cost is part of the task's.*/--const CLASSES = ['answer', 'lookup', 'research', 'web', 'code'];++const CLASSES = ['answer', 'lookup', 'research', 'web', 'code', 'robot'];const CODE = /\b(code|function|bug|stack ?trace|refactor|unit tests?|pull request|repo(sitory)?|compile|typescript|javascript|python|golang|rust|java|sql query|regex|script|api endpoint|implement)\b|```/i;const LOOKUP = /\b(dns|mx|spf|dkim|dmarc|whois|rdap|nameservers?|ssl|tls|certificate|headers?|redirects?|ip address|asn|geoip|reverse dns|ptr|ttl|robots\.txt|sitemap|uptime|ping|traceroute|port \d+|hosting provider|registrar)\b/i;const DOMAINISH = /\b(?:[a-z0-9-]+\.)+[a-z]{2,}\b|\b\d{1,3}(?:\.\d{1,3}){3}\b/i;const WEB = /\b(latest|today|tonight|yesterday|this week|this month|current(ly)?|right now|news|price of|stock|weather|score|election|released?|recent(ly)?|202\d|trending|search the web|look up online|compare prices)\b/i;const URL = /https?:\/\/\S+/i;++const ROBOT = /\b(my robots?|the robot|robot_steps|rob_[0-9A-HJKMNP-TV-Z]{26}|openvibe\.bot|rover|cozmo)\b/i;const RESEARCH = /\b(summari[sz]e|read|according to|sources?|cite|citations?|brief|report on|research)\b/i;function heuristic(task) {const t = String(task || '');++ if (ROBOT.test(t) && !/```/.test(t)) return 'robot';if (CODE.test(t)) return 'code';if (LOOKUP.test(t) && DOMAINISH.test(t)) return 'lookup';if (URL.test(t)) return 'research';@@@ -37,7 +40,8 @@ answer: writing, explaining, advice, maths, anything a capable model can do fromlookup: facts about a specific domain, website, IP address or server (DNS, mail records, certificates, headers, ownership, robots.txt).research: read the specific web pages it links to, or OpenVibe's own content, and report.web: needs searching the live web: anything recent or changing (news, prices, releases, schedules, people's current roles).--code: write, change, debug or review code.`;++code: write, change, debug or review source code.++robot: make the person's own robot on OpenVibe.Bot do something or report on it (drive, turn, speak, lights, show a face, its sensors or battery). Commanding a robot is never code.`;/*** classify(task, { chat }) → { class, cost_usd, by }. `chat` is a provider client (server/agents/providers.js) or@@@ -77,6 +77,7 @@ function whyNone(result, { cls, mode, budgetUsd }) {return { code: 'actor.budget.exceeded', detail: `The cheapest agent that can take this task would cost about $${cheapest.toFixed(3)}, over this task's $${Number(budgetUsd).toFixed(3)} budget. Raise the budget or ask something simpler.` };}if (mode === 'private') return { code: 'actor.no_agent', detail: `Private mode keeps the task on OpenVibe's own hardware, and no agent there can take a "${cls}" task yet (the open model answers simple questions only).` };++ if (cls === 'robot') return { code: 'actor.no_agent', detail: 'Robot tasks go to the OpenVibe runtime, which acts on your own robots on OpenVibe.Bot. It is not available right now; try again later.' };if (cls === 'code') return { code: 'actor.no_agent', detail: 'Coding tasks go to OpenVibe.Codes, and hosted coding runs are not open yet. Run openvibe-codes on your own machine: https://openvibe.codes/start' };const down = cands.filter((c) => !c.eligible).map((c) => `${c.id}: ${c.excluded_because}`).join('; ');return { code: 'actor.no_agent', detail: `No agent can take this task right now (${down || 'none configured'}).`.slice(0, 2000) };@@@ -107,7 +107,7 @@ ${raw(showcase.features({${raw(showcase.steps({title: 'How a task finds its agent',items: [-- { title: 'Sort the task', text: 'An answer, a lookup, research, a live web search or code: that decides what an agent must be able to do.' },++ { title: 'Sort the task', text: 'An answer, a lookup, research, a live web search, code or your own robot: that decides what an agent must be able to do.' },{ title: 'Hard limits first', text: 'An agent that cannot do it, that private mode does not trust, that is down or that would cost more than your budget is out, and the task says why.' },{ title: 'Then your mode', text: 'Cheapest, Balanced, Best, Fastest or Private picks among the rest, through the same placement engine every OpenVibe service uses.' },{ title: 'Run, check, deliver', text: 'The agent works while you watch its steps; another model checks the answer; you get the answer, its sources, the cost and the explanation.' },@@@ -83,5 +83,19 @@ const tokens = { authHeaders: async () => ({ Authorization: 'Bearer svc-token' }assert.ok(!(await t.definitions({ requester: `user:${ME}` })).some((d) => d.function.name === 'robot_steps'));});++ await check('a robot task is its own class, routed to the runtime, never refused as code', async () => {++ const { heuristic, CLASSES } = require('../server/agents/classify');++ const { route } = require('../server/agents/router');++ const catalog = require('../server/agents/catalog');++ assert.ok(CLASSES.includes('robot'));++ assert.strictEqual(heuristic(`Using my robot "Desk rover" (${OPEN}) on OpenVibe.Bot: drive it forward for 1 second, then stop`), 'robot');++ assert.strictEqual(heuristic('drive my rover in a square and turn its headlights green'), 'robot');++ assert.strictEqual(heuristic('Write a python script that reads a CSV'), 'code');++ assert.strictEqual(heuristic('fix the bug in my robot code ```js\nx()\n```'), 'code', 'code in the task is still code');++ assert.ok(catalog.byId('openvibe-runtime').can.includes('task:robot'));++ const placed = route({ cls: 'robot', config: { providers: { deepseek: { apiKey: 'k' }, openai: { apiKey: '' }, local: { url: '', model: '' } } } });++ assert.strictEqual(placed.selected, 'openvibe-runtime', JSON.stringify(placed.candidates));++ });++done();})();
@@ -23,7 +23,7 @@ Actor is OpenVibe's own general agent and the router for agent work: it takes a
@@ -23,7 +23,7 @@ Actor is OpenVibe's own general agent and the router for agent work: it takes a
| Agent | Kind | Can do | Trust | Price |
| Agent | Kind | Can do | Trust | Price |
|---|---|---|---|---|
|---|---|---|---|---|
-| OpenVibe runtime | openvibe-runtime | answer, lookup, research | external (the model runs at DeepSeek) | DeepSeek Flash, $0.30 in / $1.20 out per 1M tokens (peak; off-peak is half, Actor charges peak) |
+| OpenVibe runtime | openvibe-runtime | answer, lookup, research, robot | external (the model runs at DeepSeek) | DeepSeek Flash, $0.30 in / $1.20 out per 1M tokens (peak; off-peak is half, Actor charges peak) |
| OpenAI agent | agent-platform | answer, research, web | external | gpt-5-mini, $0.25 / $2.00 per 1M, plus $0.01 a web search |
| OpenAI agent | agent-platform | answer, research, web | external | gpt-5-mini, $0.25 / $2.00 per 1M, plus $0.01 a web search |
| Open model on OpenVibe | open-model | answer | first-party | free (OpenVibe's own server; `openvibe-llm` on the host) |
| Open model on OpenVibe | open-model | answer | first-party | free (OpenVibe's own server; `openvibe-llm` on the host) |
| OpenVibe.Codes | codes | code | first-party | listed, not hosted yet: coding runs need OpenVibe.Run sandboxes; use `openvibe-codes` on your machine |
| OpenVibe.Codes | codes | code | first-party | listed, not hosted yet: coding runs need OpenVibe.Run sandboxes; use `openvibe-codes` on your machine |
@@ -39,9 +39,9 @@ const AGENTS = [
@@ -39,9 +39,9 @@ const AGENTS = [
// The runtime is OpenVibe's code, but its model runs at DeepSeek: the task's text leaves OpenVibe.
// The runtime is OpenVibe's code, but its model runs at DeepSeek: the task's text leaves OpenVibe.
trust: 'external',
trust: 'external',
quality: 'standard',
quality: 'standard',
- can: ['task:answer', 'task:lookup', 'task:research', 'tool:openvibe-search', 'tool:openvibe-tools'],
+ can: ['task:answer', 'task:lookup', 'task:research', 'task:robot', 'tool:openvibe-search', 'tool:openvibe-tools', 'tool:openvibe-bot'],
rate_card: { input_usd_per_mtok: 0.30, output_usd_per_mtok: 1.20, source: DEEPSEEK_PRICES, verified_at: VERIFIED },
rate_card: { input_usd_per_mtok: 0.30, output_usd_per_mtok: 1.20, source: DEEPSEEK_PRICES, verified_at: VERIFIED },
- blurb: 'OpenVibe\'s own agent: a DeepSeek model that acts through OpenVibe services — search across the network, 160+ tools (DNS, mail, certificates, headers, conversions) and a page reader.',
+ blurb: 'OpenVibe\'s own agent: a DeepSeek model that acts through OpenVibe services — search across the network, 160+ tools (DNS, mail, certificates, headers, conversions), a page reader, and your own robots on OpenVibe.Bot when you let it drive them.',
},
},
{
{
id: 'openai-agent',
id: 'openai-agent',
@@ -18,6 +18,7 @@ const SYSTEM = `You check an AI agent's answer before a person sees it. Reply wi
@@ -18,6 +18,7 @@ const SYSTEM = `You check an AI agent's answer before a person sees it. Reply wi
ok is true when the answer addresses the task and nothing in it is clearly invented, contradictory or unsafe. It does not have to be perfect.
ok is true when the answer addresses the task and nothing in it is clearly invented, contradictory or unsafe. It does not have to be perfect.
ok is false when the answer is empty, refuses without a good reason, answers a different question, contradicts itself, or states something that is plainly impossible.
ok is false when the answer is empty, refuses without a good reason, answers a different question, contradicts itself, or states something that is plainly impossible.
The agent may have searched the web or run tools: do not fail an answer only because it states recent facts, versions, prices or dates you cannot verify yourself, especially when it cites a source for them.
The agent may have searched the web or run tools: do not fail an answer only because it states recent facts, versions, prices or dates you cannot verify yourself, especially when it cites a source for them.
+The agent may have made the person's own robot act through OpenVibe.Bot: an answer that reports what the robot did, or why it stopped or was refused, addresses the task.
When the task is a greeting, small talk, a test message or too vague to act on, a short friendly reply, a summary of what the agent can do or a clarifying question is a good answer: ok is true.`;
When the task is a greeting, small talk, a test message or too vague to act on, a short friendly reply, a summary of what the agent can do or a clarifying question is a good answer: ok is true.`;
/** Which checkers may read this agent's answer, best first: another family, then the same family. */
/** Which checkers may read this agent's answer, best first: another family, then the same family. */
@@ -8,22 +8,25 @@
@@ -8,22 +8,25 @@
* lookup facts about a domain, address or site → task:lookup
* lookup facts about a domain, address or site → task:lookup
* research read the pages it names, or OpenVibe's content → task:research
* research read the pages it names, or OpenVibe's content → task:research
* answer write or explain from what a model knows → task:answer
* answer write or explain from what a model knows → task:answer
+ * robot make the person's own OpenVibe.Bot robot act → task:robot
*
*
* heuristic(task) is free and is what private mode and the public dry run use. classify() asks a cheap model when one
* heuristic(task) is free and is what private mode and the public dry run use. classify() asks a cheap model when one
* is configured, and falls back to the heuristic on any failure; its cost is part of the task's.
* is configured, and falls back to the heuristic on any failure; its cost is part of the task's.
*/
*/
-const CLASSES = ['answer', 'lookup', 'research', 'web', 'code'];
+const CLASSES = ['answer', 'lookup', 'research', 'web', 'code', 'robot'];
const CODE = /\b(code|function|bug|stack ?trace|refactor|unit tests?|pull request|repo(sitory)?|compile|typescript|javascript|python|golang|rust|java|sql query|regex|script|api endpoint|implement)\b|```/i;
const CODE = /\b(code|function|bug|stack ?trace|refactor|unit tests?|pull request|repo(sitory)?|compile|typescript|javascript|python|golang|rust|java|sql query|regex|script|api endpoint|implement)\b|```/i;
const LOOKUP = /\b(dns|mx|spf|dkim|dmarc|whois|rdap|nameservers?|ssl|tls|certificate|headers?|redirects?|ip address|asn|geoip|reverse dns|ptr|ttl|robots\.txt|sitemap|uptime|ping|traceroute|port \d+|hosting provider|registrar)\b/i;
const LOOKUP = /\b(dns|mx|spf|dkim|dmarc|whois|rdap|nameservers?|ssl|tls|certificate|headers?|redirects?|ip address|asn|geoip|reverse dns|ptr|ttl|robots\.txt|sitemap|uptime|ping|traceroute|port \d+|hosting provider|registrar)\b/i;
const DOMAINISH = /\b(?:[a-z0-9-]+\.)+[a-z]{2,}\b|\b\d{1,3}(?:\.\d{1,3}){3}\b/i;
const DOMAINISH = /\b(?:[a-z0-9-]+\.)+[a-z]{2,}\b|\b\d{1,3}(?:\.\d{1,3}){3}\b/i;
const WEB = /\b(latest|today|tonight|yesterday|this week|this month|current(ly)?|right now|news|price of|stock|weather|score|election|released?|recent(ly)?|202\d|trending|search the web|look up online|compare prices)\b/i;
const WEB = /\b(latest|today|tonight|yesterday|this week|this month|current(ly)?|right now|news|price of|stock|weather|score|election|released?|recent(ly)?|202\d|trending|search the web|look up online|compare prices)\b/i;
const URL = /https?:\/\/\S+/i;
const URL = /https?:\/\/\S+/i;
+const ROBOT = /\b(my robots?|the robot|robot_steps|rob_[0-9A-HJKMNP-TV-Z]{26}|openvibe\.bot|rover|cozmo)\b/i;
const RESEARCH = /\b(summari[sz]e|read|according to|sources?|cite|citations?|brief|report on|research)\b/i;
const RESEARCH = /\b(summari[sz]e|read|according to|sources?|cite|citations?|brief|report on|research)\b/i;
function heuristic(task) {
function heuristic(task) {
const t = String(task || '');
const t = String(task || '');
+ if (ROBOT.test(t) && !/```/.test(t)) return 'robot';
if (CODE.test(t)) return 'code';
if (CODE.test(t)) return 'code';
if (LOOKUP.test(t) && DOMAINISH.test(t)) return 'lookup';
if (LOOKUP.test(t) && DOMAINISH.test(t)) return 'lookup';
if (URL.test(t)) return 'research';
if (URL.test(t)) return 'research';
@@ -37,7 +40,8 @@ answer: writing, explaining, advice, maths, anything a capable model can do from
@@ -37,7 +40,8 @@ answer: writing, explaining, advice, maths, anything a capable model can do from
lookup: facts about a specific domain, website, IP address or server (DNS, mail records, certificates, headers, ownership, robots.txt).
lookup: facts about a specific domain, website, IP address or server (DNS, mail records, certificates, headers, ownership, robots.txt).
research: read the specific web pages it links to, or OpenVibe's own content, and report.
research: read the specific web pages it links to, or OpenVibe's own content, and report.
web: needs searching the live web: anything recent or changing (news, prices, releases, schedules, people's current roles).
web: needs searching the live web: anything recent or changing (news, prices, releases, schedules, people's current roles).
-code: write, change, debug or review code.`;
+code: write, change, debug or review source code.
+robot: make the person's own robot on OpenVibe.Bot do something or report on it (drive, turn, speak, lights, show a face, its sensors or battery). Commanding a robot is never code.`;
/**
/**
* classify(task, { chat }) → { class, cost_usd, by }. `chat` is a provider client (server/agents/providers.js) or
* classify(task, { chat }) → { class, cost_usd, by }. `chat` is a provider client (server/agents/providers.js) or
@@ -77,6 +77,7 @@ function whyNone(result, { cls, mode, budgetUsd }) {
@@ -77,6 +77,7 @@ function whyNone(result, { cls, mode, budgetUsd }) {
return { code: 'actor.budget.exceeded', detail: `The cheapest agent that can take this task would cost about $${cheapest.toFixed(3)}, over this task's $${Number(budgetUsd).toFixed(3)} budget. Raise the budget or ask something simpler.` };
return { code: 'actor.budget.exceeded', detail: `The cheapest agent that can take this task would cost about $${cheapest.toFixed(3)}, over this task's $${Number(budgetUsd).toFixed(3)} budget. Raise the budget or ask something simpler.` };
}
}
if (mode === 'private') return { code: 'actor.no_agent', detail: `Private mode keeps the task on OpenVibe's own hardware, and no agent there can take a "${cls}" task yet (the open model answers simple questions only).` };
if (mode === 'private') return { code: 'actor.no_agent', detail: `Private mode keeps the task on OpenVibe's own hardware, and no agent there can take a "${cls}" task yet (the open model answers simple questions only).` };
+ if (cls === 'robot') return { code: 'actor.no_agent', detail: 'Robot tasks go to the OpenVibe runtime, which acts on your own robots on OpenVibe.Bot. It is not available right now; try again later.' };
if (cls === 'code') return { code: 'actor.no_agent', detail: 'Coding tasks go to OpenVibe.Codes, and hosted coding runs are not open yet. Run openvibe-codes on your own machine: https://openvibe.codes/start' };
if (cls === 'code') return { code: 'actor.no_agent', detail: 'Coding tasks go to OpenVibe.Codes, and hosted coding runs are not open yet. Run openvibe-codes on your own machine: https://openvibe.codes/start' };
const down = cands.filter((c) => !c.eligible).map((c) => `${c.id}: ${c.excluded_because}`).join('; ');
const down = cands.filter((c) => !c.eligible).map((c) => `${c.id}: ${c.excluded_because}`).join('; ');
return { code: 'actor.no_agent', detail: `No agent can take this task right now (${down || 'none configured'}).`.slice(0, 2000) };
return { code: 'actor.no_agent', detail: `No agent can take this task right now (${down || 'none configured'}).`.slice(0, 2000) };
@@ -107,7 +107,7 @@ ${raw(showcase.features({
@@ -107,7 +107,7 @@ ${raw(showcase.features({
${raw(showcase.steps({
${raw(showcase.steps({
title: 'How a task finds its agent',
title: 'How a task finds its agent',
items: [
items: [
- { title: 'Sort the task', text: 'An answer, a lookup, research, a live web search or code: that decides what an agent must be able to do.' },
+ { title: 'Sort the task', text: 'An answer, a lookup, research, a live web search, code or your own robot: that decides what an agent must be able to do.' },
{ title: 'Hard limits first', text: 'An agent that cannot do it, that private mode does not trust, that is down or that would cost more than your budget is out, and the task says why.' },
{ title: 'Hard limits first', text: 'An agent that cannot do it, that private mode does not trust, that is down or that would cost more than your budget is out, and the task says why.' },
{ title: 'Then your mode', text: 'Cheapest, Balanced, Best, Fastest or Private picks among the rest, through the same placement engine every OpenVibe service uses.' },
{ title: 'Then your mode', text: 'Cheapest, Balanced, Best, Fastest or Private picks among the rest, through the same placement engine every OpenVibe service uses.' },
{ title: 'Run, check, deliver', text: 'The agent works while you watch its steps; another model checks the answer; you get the answer, its sources, the cost and the explanation.' },
{ title: 'Run, check, deliver', text: 'The agent works while you watch its steps; another model checks the answer; you get the answer, its sources, the cost and the explanation.' },
@@ -83,5 +83,19 @@ const tokens = { authHeaders: async () => ({ Authorization: 'Bearer svc-token' }
@@ -83,5 +83,19 @@ const tokens = { authHeaders: async () => ({ Authorization: 'Bearer svc-token' }
assert.ok(!(await t.definitions({ requester: `user:${ME}` })).some((d) => d.function.name === 'robot_steps'));
assert.ok(!(await t.definitions({ requester: `user:${ME}` })).some((d) => d.function.name === 'robot_steps'));
});
});
+ await check('a robot task is its own class, routed to the runtime, never refused as code', async () => {
+ const { heuristic, CLASSES } = require('../server/agents/classify');
+ const { route } = require('../server/agents/router');
+ const catalog = require('../server/agents/catalog');
+ assert.ok(CLASSES.includes('robot'));
+ assert.strictEqual(heuristic(`Using my robot "Desk rover" (${OPEN}) on OpenVibe.Bot: drive it forward for 1 second, then stop`), 'robot');
+ assert.strictEqual(heuristic('drive my rover in a square and turn its headlights green'), 'robot');
+ assert.strictEqual(heuristic('Write a python script that reads a CSV'), 'code');
+ assert.strictEqual(heuristic('fix the bug in my robot code ```js\nx()\n```'), 'code', 'code in the task is still code');
+ assert.ok(catalog.byId('openvibe-runtime').can.includes('task:robot'));
+ const placed = route({ cls: 'robot', config: { providers: { deepseek: { apiKey: 'k' }, openai: { apiKey: '' }, local: { url: '', model: '' } } } });
+ assert.strictEqual(placed.selected, 'openvibe-runtime', JSON.stringify(placed.candidates));
+ });
+
done();
done();
})();
})();