-
Notifications
You must be signed in to change notification settings - Fork 3
functions_and_calling
Status: design, 2026-09-25. Not built. Builds on
crew-calls.md, which covers functions, ask, callers and
timeouts as they exist today.
A person, a Crew, an agent over MCP, or Slack all want the same two things from a Crew or a workflow: ask it something, or run one of its functions with some inputs. Today each path looks different:
-
Over MCP, Crews and workflows use different tool sets. Crews have
get_crewandask_crew. Workflows haveget_workflow, noask_workflow, and two ways to ask:call_workflow_functionwithfunction: "ask", and the olderchatplusrun_status. The connection's instructions describe workflow functions as typed runs only ("never a free-text task"), so agents don't offer to ask a workflow at all. (Observed 2026-09-25: an MCP client offeredget_crew/ask_crewfor Crews, but only "plan, files, runs, knowledge" for workflows.) - Functions, webhooks and schedules are three separate things. Each has its own screen, its own settings and its own way to call it, although each one really means "run this route with these inputs".
-
Running a function takes typing every input. The owner and repo are
repeated on every
review_prcall, and the UI has no Run button. - A refused call returns only an error, not the function's inputs or an example of a correct call.
- A function is the unit. A function is a route or task plus typed inputs. Everything that starts it is a caller: a person in the UI, a Crew, an MCP connection, Slack, a webhook, a schedule.
- Crews and workflows have one surface. The same verbs apply to both; the kind is a detail of the target.
-
askis the front door; functions are the fast lane. Anyone can ask in plain words and the assistant picks the function and inputs. Callers that know the function call it directly. -
The server already does most of this. Crew and workflow calls share
startCrewFunctionCall, the per-caller conversations, activity-based timeouts and late answers. The work is mostly surface: tool definitions, the UI, instruction text.
The MCP connection today exposes exactly two tools, get_api_spec and
call_tool, and every operation goes through them. Most sessions need only
four operations, so those become real MCP tools the client lists and can
approve one by one. Everything else stays behind the gateway.
| Tool | Role |
|---|---|
list_agents |
Who is there, what each can do, who is busy |
ask |
Free-text question; one conversation per caller per target; about: call_id reaches running work |
call_function |
Typed run, inputs checked first; a refusal teaches the inputs |
get_call |
Progress and result of either, including late and interrupted calls |
get_api_spec / call_tool
|
Everything else: runs, files, plans, knowledge, chat, per-function tools |
A token without run permission doesn't see ask or call_function.
There is no get_agent: list_agents carries everything needed to call, and
call_function's refusal carries the full input details.
Agents. Crews and workflows are both agents. The kind is known from the
ID (workflow IDs start with wf_; Crew IDs are UUIDs) or from the name.
Every target argument accepts a name or an ID. Names match ignoring case
and punctuation. A name matching several agents is refused, with the matches
and their IDs listed.
Kept minimal, around 40–60 tokens per agent:
{
"agents": [
{
"name": "rts-pr-reviweer",
"id": "wf_fc1adcb0",
"kind": "workflow",
"about": "Reviews pull requests on runloop-works/app and posts findings.",
"functions": ["review_pr(PR_NUMBER: int, GROUP?: staging|prod)"],
"busy": "review_pr for PR 151"
},
{
"name": "RTS Flow Tester",
"id": "14374cfd-a624-5dd3-91b3-eff300ec5d5c",
"kind": "crew",
"about": "Runs live Playwright checks of RTS flows and reports with video evidence."
}
],
"next": "ask(name, message) for anything; call_function(name, function, args) to run one."
}-
Signatures list only what a caller must or may pass. Inputs with a
default are left out.
?marks optional inputs;a|blists the choices. -
Fields shown only when they apply:
-
functions, when the agent has any; -
busy, while it is running or mid-turn; -
can: "read only", when this connection can't ask or call it.
-
- Never included: paths, plans, steps, variables, files, prompts.
-
Continuing conversation. One conversation per caller per target,
continued across asks.
new_conversation: truestarts fresh. The conversation shows in the target's Chats as "Asked by ", never in its main chat. - Workflow targets are answered by the workflow's Run-mode assistant. It answers, can start the right function and report back, and files suggestions for the owner. Crew targets: the answer is the Crew's final reply in that conversation.
-
about: call_iddelivers the message into that running call's turn (whatask_function_updatedoes today). The answer arrives throughget_call. Withoutabout, a second ask queues behind the running one, in order. -
Waiting.
wait_secondsdefaults to 20, max 25.
Responses:
{status: "answered", answer, call_id}{status: "working", call_id, progress, queued_behind, next}-
{status: "refused", reason}: unknown or ambiguous target, no permission, empty message. Nothing started.
Responses use the same statuses:
-
completed, withresult, plusrunfor a workflow; -
working; -
refused, which carries the details the list leaves out:
{
"status": "refused",
"problems": ["missing required input PR_NUMBER", "GROUP must be one of: staging, prod (got \"dev\")"],
"function": {
"signature": "review_pr(PR_NUMBER: int, GROUP?: staging|prod)",
"inputs": [
{"name": "PR_NUMBER", "type": "integer", "required": true, "description": "Pull request number"},
{"name": "GITHUB_OWNER", "type": "string", "default": "runloop-works"},
{"name": "GROUP", "type": "string", "enum": ["staging", "prod"]}
]
},
"try": {"function": "review_pr", "args": {"PR_NUMBER": 149, "GROUP": "staging"}}
}Rules:
- Defaults apply before validation.
-
Type conversion only where it's lossless:
"149"becomes an integer,"true"a boolean. - Unknown inputs are refused, never silently dropped: a typo must not fall back to a saved value.
- All problems are reported at once. An unknown function returns the target's signatures.
-
Callers need run access, plus the function's own
allowed_callersif it has one. -
Crew calls run where the owners say (user decision 2026-09-29, issue
#213 C1). When a Crew calls a Crew with the same owner, the turn runs in
the called Crew's own chat, queued behind anything running there. When the
owners differ, it runs in a conversation of its own for the calling
person (one per caller and person), never in the owner's chat. Workflow and
external callers keep their continuing per-caller conversation, the same
one
askuses. Workflow functions start a run. -
No fallback to
ask: a typed call runs exactly as given or is refused.
-
Long-polls: waits up to
wait_secondsfor completion or new progress. -
Statuses:
working,answered/completed,failed(withpartialwhen the target produced something),late(the answer arrived after the caller's wait timed out),interrupted(the server restarted; includeslast_progress). -
Also returns:
- the last 5 progress lines, with times;
-
activity: what the target is doing right now, from its session; -
since; - a
nextline that states the last sign of life.
- Visibility: only the caller and the target can read a call. Anyone else gets not found.
-
No cancel. To stop a call, the caller can use
ask(target, "stop", about: call_id).
The old tools keep working as aliases through call_tool, with the same
handlers and responses: list_crews, list_workflows, get_crew,
get_workflow, ask_crew, call_crew_function, call_workflow_function,
ask_function_update, get_crew_function_call and
get_workflow_function_call. They are left out of the instructions. Old names
are removed after one release with no recorded use.
Crews and workflows are both agents.
list_agentsshows them. Ask anything withask; run a specific function withcall_function. If you get acall_id, check it withget_calland tell the user the progress. Useget_api_specfor anything else.
MCP over HTTP is the connection path (see
agentworks-cli-mcp.md); the
agentworks CLI is kept only for existing scripts. It gets no new
commands. Its existing crews and functions groups keep calling the old
tool names, which stay as aliases (see Compatibility above).
Crew chats already get one tool per function of each attached Crew or
workflow (for example rts_pr_reviewer__review_pr). They keep them. Over
MCP, the same generated names are listed by get_api_spec under a
functions section. call_tool("rts_pr_reviewer__review_pr", {...}) then
works directly, with no list-then-call step. MCP still exposes only its two
fixed tools; the generated names are call targets, not new MCP tools.
Automation → Functions shows one card per function, for Crews and
workflows. The built-in ask is always listed first.
| Section | What it does |
|---|---|
| Run now | A form built from the input schema: text fields, number fields, dropdowns for enum, checkboxes for booleans, defaults filled in. Run starts the call with the viewer as the caller. The result, progress and a link to the run show inline. |
| Defaults | A saved default value per input (Part 2.2). |
| Schedule | Run on a schedule with fixed inputs. Several schedules per function. |
| Webhook | A URL for outside systems, with a mapping from payload fields to inputs (e.g. pull_request.number → PR_NUMBER) and the existing auth modes. |
| Slack | Channels or apps that may start it, e.g. a message such as "review PR 149" in a routed channel. |
| Callers | Crews, people and connections allowed to call it (allowed_callers), plus each caller's continuing conversation. |
| Copy call | The same call as an MCP call_function, a curl command against the external API, or a Slack message. |
Who can use Run now: people with run access to the workflow, and the owner and editors of a Crew. Read-only viewers see the card but not the Run button.
WorkflowFunctionInput and a Crew function's input_schema properties get an
optional default:
{"name": "GITHUB_OWNER", "type": "string", "required": true, "default": "runloop-works"}- A required input with a default can be left out by any caller. The default is applied before validation, so every existing check still holds.
- The Run now form and the refusal message show defaults;
list_agentssignatures leave defaulted inputs out. - Per-caller last values: the UI pre-fills the viewer's last inputs for that function, kept in browser storage only. The server does not remember per-caller values, so a call never depends on hidden state.
With defaults for owner and repo, review_pr needs only PR_NUMBER.
Data model: a function trigger (kind: "function") is the function. A
webhook or schedule that targets it stores:
-
function(the trigger ID); -
inputs(fixed values, for schedules); -
input_mapping(payload path to input, for webhooks).
It no longer stores its own route selection and allowed variables. A delivery
goes through the same dispatchWorkflowFunction path, so inputs are checked
the same way for every caller.
Migration:
- An existing webhook or schedule with a route selection and allowed variables keeps working as a standalone trigger. The Webhooks view shows it as today, with a "Turn into a function" action. That action creates a function from its route and variables and re-points the trigger at it.
- Nothing is converted automatically.
Crews: schedules on a Crew already deliver a prompt. A Crew function
schedule delivers call_function with fixed inputs, into the schedule's own
caller conversation.
A workflow's assistant already starts runs from free text. Two changes make that the norm:
- The assistant's
askprompt lists the workflow's functions with inputs and defaults. It prefers starting a function over a raw run, so the inputs are checked the same way as a direct call. - When a required input is missing, the assistant asks for exactly that input in one question, instead of guessing or failing.
A Crew's ask already sees its functions in its own chat; no change.
| # | Piece | Size | Notes |
|---|---|---|---|
| 1 |
default on inputs, applied before validation |
S | Server only. Unblocks the short calls. |
| 2 | Helpful refusals: schema, defaults, corrected call | S | Server only; all callers benefit. |
| 3 | MCP: list_agents, ask, call_function, get_call as real MCP tools; name resolution; aliases; new instructions |
M | Mostly wrappers over existing handlers. |
| 4 | Function card: Run now form, defaults, copy call | M | Frontend plus one "run as viewer" endpoint that reuses startCrewFunctionCall. |
| 5 | Workflow ask prompt lists functions and asks for missing inputs |
S | Prompt text plus a test. |
| 6 | Generated per-function names in get_api_spec
|
S | |
| 7 | Webhooks and schedules as function callers, with "Turn into a function" | L | Data model change and migration UI. Do last. |
Steps 1–3 give MCP users the consistent experience. Step 4 is the biggest gain for people in the UI.
-
MCP (e2e against a local server):
-
list_agentsreturns both kinds. -
askby name reaches a Crew and a workflow, and continues the same conversation. -
call_functionwith a missing input returns the schema and a corrected call. -
get_callreturns progress, then the result. - The old names still work.
-
- Defaults: a required input with a default can be omitted; an explicit value wins; an invalid default is refused when the function is saved.
- Run now: a reader sees no Run button; an editor's run is stamped with their caller identity, and the result appears inline.
- Webhook-to-function migration: a converted trigger delivers the same run as before; an unconverted one is untouched.
-
Agentic check (per the E2E rule for LLM code): a live MCP client given
only "ask the PR reviewer about PR 149" picks
askwith the name and relays progress. It signs off in JSON.
-
Name resolution scope. Should
ask("latency")match a Crew and a workflow both named "latency", or prefer one kind? Proposed: refuse with both matches. Being explicit is cheap. - Run now for Crews. Crew functions run in the viewer's own caller conversation with the Crew. Proposed: yes, the same as MCP. The run shows up in the Crew's Chats as "Called by ".
-
Should
chatbe removed eventually? Proposed: keep it; it's the only way to run several parallel sessions on one workflow.
Auto-synced from docs/ on main. Edit there, not here.