Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/desktop/src/components/chat/tool-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
BotMessageSquare,
Box,
Camera,
CircleStop,
Code2,
Eye,
FileDown,
Expand Down Expand Up @@ -406,6 +407,7 @@ const GODMODE_TOOLS = new Set([
"automation_check_result",
"runs_list",
"run_continue",
"run_cancel",
"workspaces_list",
"workspace_create",
"workspace_update",
Expand Down Expand Up @@ -547,6 +549,8 @@ function godmodeMeta(tool: string, input: Input, ctx: ToolContext): Omit<ToolMet
return { kind: "agents", icon: History, title: "Checked recent runs" };
case "run_continue":
return { kind: "agents", icon: Play, title: "Continued work a restart cut off" };
case "run_cancel":
return { kind: "agents", icon: CircleStop, title: "Stopped a run", detail: truncate(str(input.reason), 120) || undefined };
case "workspaces_list":
return { kind: "agents", icon: Layers, title: "Listed workspaces" };
case "workspace_create":
Expand Down Expand Up @@ -771,6 +775,8 @@ function easyMeta(meta: ToolMeta, input: Input, ctx: ToolContext): { title: stri
return { title: "Checked recent work" };
case "run_continue":
return { title: "Picked up where it left off" };
case "run_cancel":
return { title: "Stopped a teammate's run" };
case "routine_create":
case "routine_update":
// The detail is the cron line or the trigger's name.
Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ restarted").
* Something new in the chat since, a run already working there, a pause or an automation that already runs again
(one chat per event) leave it alone; a turn continued three times in a row by itself (one that restarts Godmode) is
left for the human, and so is what a restored backup marked interrupted (`leaveInterruptedWork`).
* Managers continue what is left (older work, setting off) with the `run_continue` tool.
* Managers continue what is left (older work, setting off) with the `run_continue` tool. They stop a run for good (a paused run that would repeat work already done, also on another computer of the group) with `run_cancel`.

### Questions and approvals

Expand Down Expand Up @@ -609,7 +609,7 @@ the core, the desktop and the phone alike.
| `browser_profile_create_own()` | Create an empty browser profile in the agent's workspace and pin it to the calling agent; only while it has none, applies from its next run |
| `agents_list()`, `agent_get({id})` | Discover peer agents: role, who they report to (`relation` marks the caller's lead and reports), and for `agent_get` who reports to it (only agents the caller could reach) |
| `agent_delegate({ agentId, task, wait })` | Hand a task to a peer agent (optionally wait for its result). The chat stores the bare task (`source: "delegation"`); the run's prompt starts with `[Delegated by <name> (<role>), your lead. Your final answer goes back to <name>.]` |
| `agent_create`, `templates_list`, `team_install`, `agent_update`, `agent_delete`, `routine_list`, `routine_create`, `routine_update`, `routine_run`, `routine_delete`, `automation_triggers_list`, `automation_events_list`, `runs_list`, `run_continue`, `workspaces_list`, `workspace_create`, `workspace_update`, `project_create`, `project_update`, `tasks_list`, `task_get`, `task_create`, `task_update`, `task_message` | Management tools — only for agents with `canManageAgents` (the built-in *Godmode* agent). `agent_create` / `agent_update` also set `role` and `reportsTo`; `agent_create({ template })` starts from a role template (unset fields come from it, its schedule comes back as `templateSchedule`), `templates_list` shows the roles and structures (seats with their paths for `skip`), and `team_install` hires a structure as the calling agent (no secret access or management rights for the new agents; refused in a run kept in a VM). `workspace_update` / `project_update` only add sources (`addSources`); removing sources, deleting, VM, browser profile and auto-merge stay human-only. A run kept in a VM sets up no workspaces or projects (their instructions reach agents on the computer), and changing a workspace's context follows the `agent_update` rules for every agent in it (reveal mode, computer use); changing a project's, for every agent working on it (its own agents, and those with a chat or ticket in it) — a new project has none. What a caller without raw secrets or computer use writes into a project is marked (`projects.fill_only` / `off_computer`): runs that get it go fill-only, agents that control the computer get it without description and instructions, until the human saves its instructions (lifting `fill_only` takes the vault passphrase) |
| `agent_create`, `templates_list`, `team_install`, `agent_update`, `agent_delete`, `routine_list`, `routine_create`, `routine_update`, `routine_run`, `routine_delete`, `automation_triggers_list`, `automation_events_list`, `runs_list`, `run_continue`, `run_cancel`, `workspaces_list`, `workspace_create`, `workspace_update`, `project_create`, `project_update`, `tasks_list`, `task_get`, `task_create`, `task_update`, `task_message` | Management tools — only for agents with `canManageAgents` (the built-in *Godmode* agent). `agent_create` / `agent_update` also set `role` and `reportsTo`; `agent_create({ template })` starts from a role template (unset fields come from it, its schedule comes back as `templateSchedule`), `templates_list` shows the roles and structures (seats with their paths for `skip`), and `team_install` hires a structure as the calling agent (no secret access or management rights for the new agents; refused in a run kept in a VM). `workspace_update` / `project_update` only add sources (`addSources`); removing sources, deleting, VM, browser profile and auto-merge stay human-only. A run kept in a VM sets up no workspaces or projects (their instructions reach agents on the computer), and changing a workspace's context follows the `agent_update` rules for every agent in it (reveal mode, computer use); changing a project's, for every agent working on it (its own agents, and those with a chat or ticket in it) — a new project has none. What a caller without raw secrets or computer use writes into a project is marked (`projects.fill_only` / `off_computer`): runs that get it go fill-only, agents that control the computer get it without description and instructions, until the human saves its instructions (lifting `fill_only` takes the vault passphrase) |
| `automation_check_result({ met, observation, summary })` | Only in condition-check runs: report whether an automation's condition holds (see Automations) |
| `task_note({ text, taskId? })` | A progress note on the ticket the run works on (managers: any ticket); on its timeline, nobody is notified |
| `ask_human({ question, context?, options? })`, `request_approval({ action, reason, affects })` | Ask the human a question or for an OK and stand still until the answer; the run continues with it (see Questions and approvals). Not in condition checks, dreams or delegated runs |
Expand Down
45 changes: 43 additions & 2 deletions packages/core/src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ import { callApiTool, METHODS, type ApiCallResult, type CallPlaces } from "../in
import { chatSources, createProject, getProject, projectOfChat, updateProject } from "../services/projects";
import { all, get } from "../db";
import { config } from "../config";
import { fixRunner, runnerExec, runnerHealth } from "../remote/runners";
import { fixRunner, link, runnerExec, runnerHealth } from "../remote/runners";
import { loginFillScope } from "../browser/fill";
import { chatFillOnly, createConversation, maskSecretInChat, sendMessage } from "../services/conversations";
import { assignVm, createVm, getVm, listVms, sharedDirOf, startVm, stopVm, suspendVm, vmInUse, vmOfRun, vmStatus } from "../vm/service";
Expand All @@ -142,7 +142,7 @@ import { getSettings } from "../services/settings";
import { agentSettingsRefusal, applyAgentSettings, humanOnlySettings, settingsSections } from "../services/agentSettings";
import { spendReport } from "../services/spend";
import { budgetOverview, budgetSentence, exhaustedBudget } from "../services/budgets";
import { getRun, listRuns, markMissingLoginReported, runBrowserProfile, runChatBrowserProfile, waitForRun, runExempt } from "../runner/runner";
import { cancelRun, getRun, listRuns, markMissingLoginReported, runBrowserProfile, runChatBrowserProfile, waitForRun, runExempt } from "../runner/runner";
import { listGoals } from "../tasks/goals";
import { addTaskNote, createTask, findTask, getTask, listTaskEvents, listTasks, reportBlocked, sendTaskMessage, taskForConversation, updateTask } from "../tasks/service";
import { describeNow } from "../runner/prompt";
Expand Down Expand Up @@ -402,6 +402,16 @@ function reachableAgents(agent: Agent): Agent[] {
return peersFor(agent);
}

/** The run and the runs that handed it its work, up to the top. */
function runsAbove(runId: string): string[] {
const chain: string[] = [];
for (let id: string | null = runId; id && !chain.includes(id); ) {
chain.push(id);
id = get<{ parent_run_id: string | null }>("SELECT parent_run_id FROM runs WHERE id = ?", id)?.parent_run_id ?? null;
}
return chain;
}

function requireReachable(agent: Agent, targetId: string): Agent {
const target = reachableAgents(agent).find((a) => a.id === targetId);
if (!target) throw new HttpError(403, `Agent ${targetId} is not one of your peers (use agents_list to see who you can work with).`);
Expand Down Expand Up @@ -2899,6 +2909,37 @@ const TOOLS: ToolDef[] = [
},
}),

defineTool({
name: "run_cancel",
description:
"Stop a run for good: one that works, waits in the queue or stands still (paused on a usage limit, a budget or a question). The work it handed to other agents stops too, and a paused run never resumes by itself. Runs of chats that work on another computer of the group are stopped there. Use it for runs that would repeat work already done or are no longer wanted; never for your own run or the ones that handed you your task.",
schema: z.object({
runId: z.string(),
reason: z.string().trim().min(1).max(200).optional().describe("Why, shown in its chat"),
}),
when: managesSetup,
run: async ({ runId, reason }, { agent, ctx }) => {
const run = getRun(runId);
if (run.status === "succeeded" || run.status === "failed" || run.status === "cancelled") return `Run ${runId} already ended (${run.status}).`;
if (runsAbove(ctx.runId).includes(runId)) return fail("That run is yours or handed you your task: stopping it would stop you too. End your turn instead.");
const target = run.agentId === agent.id ? agent : requireReachable(agent, run.agentId);
const refusal = offHostRefusal(ctx, target, "stop its work") ?? revealTargetRefusal(agent, ctx, target, "stop its work");
if (refusal) return fail(refusal);
const text = `Stopped by ${agent.name}${reason ? `: ${reason}` : ""}`;
const runnerId = get<{ runner_id: string | null }>("SELECT c.runner_id FROM runs r JOIN conversations c ON c.id = r.conversation_id WHERE r.id = ?", runId)?.runner_id ?? null;
if (runnerId) {
const l = link(runnerId);
l.assertOnline();
const path = encodeURIComponent(runId);
await l.json("POST", `/api/link/runs/${path}/stop`, { reason: text }).catch(() => l.json("POST", `/api/runs/${path}/cancel`, {}));
} else {
await cancelRun(runId, text);
}
audit(`agent:${agent.id}`, "run.cancel", runId, { agentId: run.agentId, status: run.status, ...(runnerId ? { runnerId } : {}), ...(reason ? { reason } : {}) });
return `Stopped run ${runId} of ${target.name} (it was ${run.status}${runnerId ? ", on another computer" : ""}).`;
},
}),

defineTool({
name: "workspaces_list",
description:
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/remote/deviceTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const SHARED_TOOLS: ReadonlySet<string> = new Set([
"task_note",
"spend_overview",
"runs_list",
"run_cancel",
"workspaces_list",
"workspace_create",
"workspace_update",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/runner/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ Not in this chat: its task came from another agent that may not read raw secrets

if (perms.canManageAgents) {
out.push(`### Managing agents
You are the orchestrator. You can create, update and delete agents (\`agent_create\`, \`agent_update\`, \`agent_delete\`), manage their automations (\`routine_list\`, \`routine_create\`, \`routine_update\`, \`routine_run\`, \`routine_delete\`, \`automation_events_list\`), inspect recent work with \`runs_list\` (results and errors of every agent) and pick up a run a restart of Godmode cut off with \`run_continue\`, manage and supervise the task board (\`tasks_list\`, \`task_get\`, \`task_create\`, \`task_update\`, \`task_message\`, \`task_note\` — tickets agents work on; coding tasks end in a pull request; read a ticket's result and timeline with \`task_get\`, and send feedback into it with \`task_message\` instead of filing a new task for the same work), set up workspaces and their projects (\`workspaces_list\`, \`workspace_create\`, \`workspace_update\`, \`project_create\`, \`project_update\` — a workspace groups the agents, logins and repositories of one business or client, a project is one product or initiative inside it; when the work clearly belongs to something new, create it instead of piling everything into an existing one, and put its agents and tasks there), and review \`logins_overview\` and \`missing_logins_list\`. When asked to "check on all agents", use \`runs_list\` and \`missing_logins_list\` and summarize what succeeded, what failed and what the human must do (e.g. add a login in the vault). For a common job or a whole team, start from the ready-made roles and org charts (\`templates_list\`): \`agent_create({ template })\` hires one role, \`team_install\` a whole team or company with its reporting lines (one all-in-one agent per department when the business is small, specialists where the work is big enough). When you create an agent, give it a clear description, concrete standing instructions, a character and personality that fit the job, and an automation when the job is recurring. An agent that needs a browser of its own (separate logins) gets a new, empty profile: \`browser_profile_create\`, then \`agent_update({ browser: { profileId } })\`; existing profiles (\`browser_profiles_list\`) hold ${human}'s logins and stay theirs. Give every agent a \`role\` — its job title in two or three words — and, when the team has leads, say who it reports to (\`reportsTo\`); an agent without one reports to you. Never delete an agent unless ${human} explicitly asked for it.${
You are the orchestrator. You can create, update and delete agents (\`agent_create\`, \`agent_update\`, \`agent_delete\`), manage their automations (\`routine_list\`, \`routine_create\`, \`routine_update\`, \`routine_run\`, \`routine_delete\`, \`automation_events_list\`), inspect recent work with \`runs_list\` (results and errors of every agent), pick up a run a restart of Godmode cut off with \`run_continue\` and stop one for good with \`run_cancel\` (a paused run that would repeat work already done, a run nobody needs any more), manage and supervise the task board (\`tasks_list\`, \`task_get\`, \`task_create\`, \`task_update\`, \`task_message\`, \`task_note\` — tickets agents work on; coding tasks end in a pull request; read a ticket's result and timeline with \`task_get\`, and send feedback into it with \`task_message\` instead of filing a new task for the same work), set up workspaces and their projects (\`workspaces_list\`, \`workspace_create\`, \`workspace_update\`, \`project_create\`, \`project_update\` — a workspace groups the agents, logins and repositories of one business or client, a project is one product or initiative inside it; when the work clearly belongs to something new, create it instead of piling everything into an existing one, and put its agents and tasks there), and review \`logins_overview\` and \`missing_logins_list\`. When asked to "check on all agents", use \`runs_list\` and \`missing_logins_list\` and summarize what succeeded, what failed and what the human must do (e.g. add a login in the vault). For a common job or a whole team, start from the ready-made roles and org charts (\`templates_list\`): \`agent_create({ template })\` hires one role, \`team_install\` a whole team or company with its reporting lines (one all-in-one agent per department when the business is small, specialists where the work is big enough). When you create an agent, give it a clear description, concrete standing instructions, a character and personality that fit the job, and an automation when the job is recurring. An agent that needs a browser of its own (separate logins) gets a new, empty profile: \`browser_profile_create\`, then \`agent_update({ browser: { profileId } })\`; existing profiles (\`browser_profiles_list\`) hold ${human}'s logins and stay theirs. Give every agent a \`role\` — its job title in two or three words — and, when the team has leads, say who it reports to (\`reportsTo\`); an agent without one reports to you. Never delete an agent unless ${human} explicitly asked for it.${
ctx.settingsTools
? `\n\nGodmode's own settings are yours to read and change when ${human} asks for it or a task needs it: \`settings_get\` and \`settings_update\` (default and fallback model, reasoning effort, run limits, how the browser and computer control behave, virtual machines, memory and dreaming, voice, upkeep, logs). A change applies to every agent at once, so say what you changed. What guards secrets, access from outside, what agents may do on this computer, what every agent is told or their limits stays with ${human} (\`humanOnly\` in \`settings_get\`); never hand them a task for a setting you can change yourself.`
: ""
Expand Down
Loading
Loading