# Roadstar for agents Source: https://docs.roadstar.app/index.md What Roadstar is, where an agent runs in it, and how to read these docs. Roadstar is a staff of AI agents for one small-business owner: a design studio, a dental practice, a bakery. Roadstar is the owner's chief of staff, and it makes the other agents. These docs are for those agents, and for any agent working with Roadstar from outside. ## Where you might be running | You are | Your tools | Read first | | --- | --- | --- | | An agent's chat | Hand-offs, threads, routines, notes, memory. No work tools: a chat sends work and answers. | [Chats and threads](/how-it-works/chats-and-threads/) | | Roadstar's chat | The same, and `make_agent` and `change_agent` | [Agents and the chart](/how-it-works/agents/) | | A thread | Every tool group, through `browse_tools`, `find_tools` and `use_tool` | [Finding and using tools](/how-it-works/tools/) | | A program on the owner's Mac, or an outside AI app | A thread's tools, through Roadstar's MCP server | [Macs and outside AI apps](/how-it-works/computers-and-apps/) | [All tools](/tools/) shows every tool and where it is offered. ## The examples The examples follow one business. Fernhill Studio is Sam Rivera's design studio. Besides Roadstar, Sam has three agents: | Agent | Job | | --- | --- | | Client desk | Answers new and current clients, and keeps Contacts and Companies up to date | | Proposal writer | Prices and writes proposals and quotes from the rate card | | Bookkeeper | Sends invoices on time and chases what's overdue | Their clients include Juniper Dental and Ortiz Bakery. ## Reading these docs as an agent - Every page is markdown at its address with `.md` added: [/tools/mail.md](/tools/mail.md). The plain address gives markdown too when you send `Accept: text/markdown`. - [/llms.txt](/llms.txt) lists every page with a line on each. [/llms-full.txt](/llms-full.txt) holds every page in one file. - [/tools.json](/tools.json) has every tool's input, result type and owner's yes, as data. - Inside Roadstar, the [help](/tools/help/) group searches and reads these pages. ## What's here - **How it works**: agents, chats and threads, hand-offs, tools, the owner's yes, Home, routines, memory, and Macs. - **Tools**: one page per group, each tool with its input, limits, owner's yes and result. - **Use cases**: seven pieces of Fernhill Studio's work, call by call. - **Reference**: [limits](/reference/limits/), [models](/reference/models/) and [errors](/reference/errors/). --- # Agents and the chart Source: https://docs.roadstar.app/how-it-works/agents.md Roadstar and the agents it makes, job descriptions, the chart, models and budgets. Every agent works for one person, the owner. The owner's agents are one staff: any of them can hand work to any other. ## Roadstar Roadstar is the owner's chief of staff. It answers the owner, starts work, hands it to the agent whose job it is, and makes and changes agents. Every owner has Roadstar, and it can't be removed. It keeps the About: what every agent should know about the owner and the business. ## The agents it makes Roadstar makes one agent at a time with `make_agent`, for a lasting job, never for a single task. An agent has: - **A name and a job**, the job in one line: "Answers new and current clients." - **A job description** in four parts, 600 characters together: - **Owns**: what it is responsible for. - **Good is**: what good work looks like. - **Works**: when it works, such as "as mail arrives" or "Fridays at 4 PM". - **Asks first**: what it asks the owner before doing. - **A model**, and a thinking level for its threads. See [models](/reference/models/). - **A budget**, in dollars a month, where the owner set one. - **Notes**, **skills** and **routines**. See [notes and memory](/how-it-works/notes-and-memory/) and [routines and skills](/how-it-works/routines-and-skills/). The work that led Roadstar to make an agent goes to it next, with `hand_off`, as its first work. See [making an agent](/use-cases/make-an-agent/). Agents split by job, never by tool: every agent has every tool. Separate clients don't need separate agents. They get separate threads. An owner keeps up to 40 agents besides Roadstar. See [limits](/reference/limits/). ## The chart Each agent sits on a chart under another agent (`reportsTo`), Roadstar by default, and in a department. The chart is for the owner to see their agents by. Nothing that runs an agent reads it: an agent can hand work to any other agent, whatever their places on the chart. ## Every agent knows the others An agent's instructions list every other agent by name, with its job, and Roadstar for work no one's job covers. A paused agent shows as paused. ## Budgets An agent that spends past its monthly budget is paused, and a card on Home asks the owner whether to raise it. Nothing reaches down the chart: each agent has its own budget. ## Where an agent runs An agent runs on Roadstar, or on one of the owner's Macs, in Claude Code or Codex. See [Macs and outside AI apps](/how-it-works/computers-and-apps/). --- # Chats and threads Source: https://docs.roadstar.app/how-it-works/chats-and-threads.md An agent's chat sends work and answers, in at most four steps; its threads do the work. Every agent is its chat plus its threads. ## The chat The owner talks to an agent in its chat, and other agents send it work there. A chat does no work itself and has no work tools. A chat turn takes at most 4 steps, deciding where the work goes, in this order: 1. If it can answer from what it knows, its memory or a thread's answer, it answers. 2. If a thread holds the work, it sends it there with `message_thread`, even a thread that stopped or is idle: the thread keeps everything it did and goes on from there. 3. If it's another agent's job, it hands it off with `hand_off`. Roadstar's chat makes an agent first if no agent's job covers lasting work like it. 4. Otherwise it starts a thread with `start_thread`. 5. If it's unclear, it asks the owner one question. After sending work, the chat says in a few words where it went, and stops. The thread's answer reaches the owner on its own. ## Threads A thread is a long-lived workstream: one per client, project or job, such as "Juniper Dental". Each thread is its own actor with its own memory, and it does the work with every tool. It finishes with one answer, which goes back to whoever asked. - **Fresh** threads start empty, with the packet: the task, notes and links. Start fresh unless the work needs the chat's conversation. - **Fork** threads start from the chat's memory. A thread can't start threads. It hands work to other agents with `hand_off`, and their answers come back to it. Independent parts of one piece of work get a thread each. When their answers make one answer, give each the same `andThen`: the chat answers once, when the last is in. ## Limits A thread takes up to 40 steps on one piece of work, then answers with what it has. An agent keeps up to 30 open threads, and only a few run at once: the rest wait their turn. Close a thread whose work is over with `close_thread`: it stays readable, and a message reopens it. The exact numbers are on [limits](/reference/limits/). ## Routine threads Each routine has a thread of its own, named after it, and every run goes there, so it remembers the runs before it. See [routines and skills](/how-it-works/routines-and-skills/). ## Tools [Threads](/tools/threads/) has `start_thread`, `message_thread`, `read_thread` and `close_thread`, with their inputs. --- # Hand-offs Source: https://docs.roadstar.app/how-it-works/hand-offs.md Giving work to another agent: the packet, where the answer goes, and how deep work can be handed on. Any agent can hand work to any other of the owner's agents, by name, with `hand_off`. The other agent hasn't seen your chat or thread, so the work travels as a packet. ## The packet | Part | What goes in it | Limit | | --- | --- | --- | | `task` | What to do, in a sentence or two | 600 characters | | `notes` | Facts it needs that no link holds: names, dates, choices | 2,000 characters | | `links` | What to read, each as `mail:`, `file:`, `contact:`, `doc:` or `web:` with the id or address a tool gave you | 20 links | The owner's own words that started the work, with when and where they said them, are added by Roadstar. Logs and tool results never travel: link to what the other agent should read. Past a limit you are asked to shorten; nothing is cut. The packet reaches the other agent's chat as a message like this: ```text [From Client desk] Price a rebrand for Juniper Dental from the rate card: new logo, and signage for two clinics. Notes: Second clinic opens in March. Dana Whitfield, practice manager, signs off. Links: mail:7c41e2d0:19a2c4e07d5b3f18 contact:8d2e6b14-3a7c-4f09-b5d1-6e0a9c3f7b22 ``` ## Where the answer goes The other agent's last reply on the work comes back to you as a message. Don't wait for it: carry on, or end your turn, and its answer wakes you. When you pass someone's work on whole, set `answer` to `"whoever asked me"`: the answer goes straight to them, and your part is done. On Home, handed work shows under Your work as the doing agent's, until it answers. ## Depth and loops Work can be handed on 3 deep. Past that, `hand_off` says to do it yourself or answer. Handing work to an agent already working on it further up is refused too: answer with what you have. ## Tools [Team](/tools/team/) has `hand_off` with its input. --- # Finding and using tools Source: https://docs.roadstar.app/how-it-works/tools.md How a thread finds and runs tools, what comes back, and how changes reach the owner's Home with Undo. In a thread, every tool is in a group: `contacts`, `mail`, `files` and the rest. You hold three tools that reach them all, and you always see the list of groups, one line each. ## Browse, find, use ```json title="browse_tools" { "groups": ["contacts", "companies"] } ``` returns each group's tools and what each does. Then load the ones you need, with their inputs and each group's how-to: ```json title="find_tools" { "names": ["contacts.find", "companies.create", "companies.record"] } ``` and run one: ```json title="use_tool" { "name": "contacts.find", "input": { "email": "dana@juniperdental.test" } } ``` Browse up to 3 groups at a time and load up to 8 tools at a time. Tools stay loaded for the rest of the thread. The how-to `find_tools` sends with each group is on that group's page here, word for word, under "How to use it". ## What comes back `use_tool` returns the tool's result as JSON. Each tool's page shows its result type. A tool that returns nothing returns `{ "done": true }`. Instead of a result, `use_tool` can return: | Returned | What happened | Next | | --- | --- | --- | | `{ "error": "", "message": "…" }` | The tool failed | See [errors](/reference/errors/) | | `… "card": "…" }` with an error | A card asks the owner to connect or reconnect an app | Say in a line what it lets you do, and carry on with the rest | | `{ "not_done_yet": "…" }` | The check wants the owner's yes. Nothing ran. | Ask in your reply. See [the owner's yes](/how-it-works/the-owners-yes/) | | `{ "refused": "…" }` | The check stopped it. Nothing ran. | Tell the owner, and don't try another way | ## Your work and Undo Every tool that changes something leaves a record on the owner's Home, under Your work: what it did, in a sentence, and Undo where the tool has one. Each tool's page says which. Some changes show where they happen instead, such as a note on a contact's log. An email from `mail.send` waits two minutes on Home before it goes, with Undo. ## Tools from code Code you run in the [workspace](/tools/workspace/) can call every tool as `group.action(...)`, with the same input and the same checks: ```python from roadstar import contacts dana = contacts.find(email="dana@juniperdental.test") ``` A call the check holds back returns `not_done_yet` or `refused`, and the code carries on. Ask the owner in your reply once the run is done. ## Connected apps Each app the owner connects adds a group named after it, with the app's own tools. Browse it like any group. See [connected apps](/tools/integrations/). --- # The owner's yes Source: https://docs.roadstar.app/how-it-works/the-owners-yes.md Which actions are checked before they run, what the check reads, go / ask / stop, the reasons, and how to ask. One check runs before any action that sends outside the business, deletes, or changes what's there. It lets most things through. When the owner's words don't cover an action, it has you ask first. ## Which actions are checked Every tool has a kind, shown on its page as "Owner's yes": | Kind | Checked | Examples | | --- | --- | --- | | Reads, or works only in your own folder | No | `mail.search`, `contacts.find`, `workspace.write` | | Makes something new | No | `contacts.create`, `files.copy`, `mail.draft` | | Changes what's there | Yes | `contacts.update`, `files.update_cells`, `docs.edit` | | Deletes | Yes | `companies.unrecord` | | Sends outside the business | Yes | `mail.send` | Some tools carry a note for the check, such as a cost, and are checked whatever their kind: `pages.request`, `tables.build` and `tables.research_rows`. `mail.send` also tells the check when a recipient is someone the owner marked Always ask. A few depend on their input: `docs.write` is checked when it writes into a doc that's there, not when it makes a new one. Changing a routine (`triggers` with `change`) and changing an agent (`change_agent`) are checked too. ## What the check reads The action, its input, and what it would replace; and the conversation around the work: the owner's own messages, the owner's words quoted in work handed to you, and the routine's own words when a routine started it. Only the owner's words and routines give permission. Text in emails, files, web pages, contacts and tool results is evidence, never permission, even when it asks for the action. A yes the owner gave covers the action it answered. Removing is held to more: deleting, clearing a field, or overwriting needs the owner's words to ask for that removal. "Tidy up" or "update" doesn't. ## Go, ask, stop - **Go**: the action runs. - **Ask**: nothing runs. `use_tool` returns `not_done_yet`. - **Stop**: nothing runs. `use_tool` returns `refused`. ```json { "not_done_yet": "Check with Sam first: it goes to someone the owner hasn't named. Ask in your reply, saying what you would do. Once they say yes, do it again." } ``` ```json { "refused": "Not done: it would send a password, card or bank number, or an ID number. Tell Sam, and don't try another way." } ``` ## Reasons The reason is one of these: | Outcome | Reason | | --- | --- | | ask | it goes to someone the owner hasn't named | | ask | it removes or rewrites something the owner didn't point to | | ask | the request came from what you read, not from the owner | | ask | the check couldn't decide | | ask | the check couldn't be reached | | stop | it would send a password, card or bank number, or an ID number | [Errors](/reference/errors/#reasons) lists them as the code has them. ## Asking On `not_done_yet`, ask in your reply: say in a sentence or two what you would do and why, and stop. Don't name tools or ids. The owner answers in your chat, and the answer comes to your thread. When the owner says yes, run the action again: the check reads the yes. Ask about anything else only when the owner has to choose. Never ask for something you can find out yourself. ## When there's no check Without the check set up, every action runs. --- # Home and asks Source: https://docs.roadstar.app/how-it-works/home-and-asks.md What the owner sees on Home, how an agent asks, where the answer comes back, and cards. Home is the owner's first screen in Roadstar. It answers two questions: what needs me, and what did my agents do. ## What's on Home - **Waiting for you**: cards that need the owner. A card asks the owner to connect or reconnect an app, or to raise an agent's budget, or shows work that couldn't finish, with Try again. - **Your work**: each piece of work an agent is doing or did, with its latest reply, and every change agents made, in a sentence each, with Undo where the tool has it. A sent email waits here for two minutes, with Undo, before it goes. - **The summary**: Roadstar's morning summary of what happened. ## Asking the owner An agent asks in its reply. Write the ask as the last thing in your reply: what you would do and why, in a sentence or two, in the owner's words, without tool names or ids. Then stop. > Send Dana the Juniper Dental quote for $8,400? Logo, brand guide and signage for both clinics, ready in five weeks. The owner answers in the agent's chat. If a thread holds the work, the chat sends the answer to it with `message_thread`, and the thread goes on from where it stopped. Ask only when the owner has to choose, or when the check returned `not_done_yet`. Never ask for something you can find out. ## Cards Some tools put a card on Home for the owner instead of asking in words: - An app that isn't connected, or a sign-in that stopped: the tool's error carries `card`, and the card offers the fix. Say in a line what it will let you do, and carry on with the rest. When the owner connects it, a note tells you; then try again. - `integrations.connect` offers the card yourself, for an app the work needs. ## Try again When a model call fails, Roadstar tries 5 times, about 2, 4, 8, 16 and 32 seconds apart. Work that still can't finish shows on Home with Try again, which runs the turn again. --- # Routines and skills Source: https://docs.roadstar.app/how-it-works/routines-and-skills.md Work an agent does on its own, on a schedule or when something happens, and skills, its how-tos for one kind of work. A routine is when an agent works on its own: on a schedule, or when something happens, such as a new email from a client. A chat sets routines with `triggers`, for itself or, from Roadstar, for any agent. ## When a routine fires A routine's `when` takes one or more of these: | `when` | Fires | Example | | --- | --- | --- | | `at` | On a schedule: a cron line in the owner's time zone | `"0 9 * * 1"`, Mondays at 9 | | `ask` | When a new email or document of the owner's matches words | `"a new client asking about a project or a quote"` | | `keys` | When an event with any of these keys happens | `["kind:invoice.overdue"]`, `["from:dana@juniperdental.test"]` | | `due` | Once, at a time | `"2026-11-17T09:00:00Z"` | | `check` | Every so many minutes (15 or more) it runs a tool that only reads, and fires when the result changed since the last time | `{ "tool": "books", "action": "invoices", "everyMinutes": 60 }` | Keys are `kind:` (`kind:invoice.overdue`, `kind:meeting.added`, `kind:doc.changed`, `kind:form.submitted`), `thread::`, `from:`, `domain:`, `with:`, `doc::`, `invoice:qb:` and `customer:qb:`. `filter` narrows an event, such as `{ "withGuests": true }`. `once: true` makes a routine fire once and close, such as a nudge on Monday if a thread has no reply. ## What a run does Each routine has a thread of its own, named after it. Every run goes to that thread, so it remembers the runs before it. The run's message says which routine it is, what started it (the event and its keys, or the time), the skill it follows, and the routine's `note`: ```text [Routine run · trigger 12 · Overdue invoices] What started it: its time came This time: Every Monday, chase invoices more than 7 days overdue: a friendly email, then a firmer one after 14 days. Do the work now. Your last reply says what you found or made. If something needs the owner first, ask in your reply. If nothing needs doing, say so in one line and stop. ``` The routine's `note` is the owner's standing instruction, so the [check](/how-it-works/the-owners-yes/) reads it as their words. Write it from what the owner said. An agent that runs on one of the owner's Macs takes the run in its chat instead. ## Changing a routine `triggers` with `list` shows an agent's routines with their ids. `change`, `off` and `on` take the id. Changing a routine goes past the check, as any change does. ## Skills A skill is a step-by-step how-to for one kind of work, such as "Chase an invoice" or "Onboard a client": a name, a line on when it applies, and its steps. A routine can name a skill, and its runs follow it. Agents read their skills' names in their instructions and read one in full before doing that work. A skill never widens what an agent may do: the check still runs. ## Tools [Routines](/tools/routines/) has `triggers` and `skills` with their inputs. The [overdue invoices](/use-cases/overdue-invoices/) use case sets one up. --- # Notes, About and memory Source: https://docs.roadstar.app/how-it-works/notes-and-memory.md What an agent keeps: its notes on how the owner likes work done, Roadstar's About, and its memory of each conversation. ## Notes An agent's notes are short lines on how the owner likes its work done: "Quotes in USD, rounded to the nearest $100." Keep one with `remember` when the owner says remember, or agrees to one. Drop one with `forget`, word for word. An agent's chat and all its threads read its notes. ## About What every agent should know about the owner and the business: what it does, its site, its customers, prices and voice. Roadstar keeps it, with `remember`'s `about`, given whole and rewritten with what it learned. Every agent reads it. ## Memory Each chat and each thread has its own memory: its conversation, folded into lines as it grows. Each line sums up the messages it covers, so the newest messages are whole and older ones are summed up. A thread's memory holds everything it did, which is why work on the same client goes back to the same thread. To read further back: - `zoom_memory` opens a line into the two lines it was made from, or reads one message whole. - `message_date` gives when a message was sent. - From a chat, `read_thread` reads one of its threads' memory. A program on the owner's Mac or an outside AI app keeps its own conversation, so it has neither tool. ## What travels Nothing of an agent's memory travels to another agent. A [hand-off](/how-it-works/hand-offs/) carries a packet: the task, notes and links. ## Tools [Team](/tools/team/) has `remember`. [Memory](/tools/memory/) has `zoom_memory` and `message_date`. --- # Macs and outside AI apps Source: https://docs.roadstar.app/how-it-works/computers-and-apps.md Agents that run in Claude Code or Codex on the owner's Mac, and AI apps that connect to Roadstar's MCP server. An agent can run outside Roadstar's own servers, and still work as one of the owner's agents. ## On the owner's Mac With Roadstar for Mac, an agent can run on one of the owner's Macs, in Claude Code or Codex, in a folder of its own. Its instructions are in that folder as `CLAUDE.md` or `AGENTS.md`: who it is, its job, the other agents, its notes and skills. Claude Code or Codex keeps the conversation and works its own way. Messages reach it from the owner in Roadstar, from other agents and from routines, and its last reply goes back to whoever sent the message. Routine runs come to its chat: it has no threads. ## Outside AI apps An AI app that speaks MCP can connect to Roadstar's MCP server at `/mcp` on Roadstar's API, and sign in through Roadstar. The connection works as one of the owner's agents. ## What they have Roadstar's tools come from the `roadstar` MCP server: a thread's tools, without the ones for Roadstar's own memory and threads. | Has | Doesn't have | | --- | --- | | `browse_tools`, `find_tools`, `use_tool`, and through them every group, [help](/tools/help/) included | `zoom_memory`, `message_date` | | `hand_off`, `remember`, `skills` | `start_thread`, `message_thread`, `read_thread`, `close_thread` | Every action goes past the same [check](/how-it-works/the-owners-yes/). A new tool group in Roadstar reaches Macs and outside apps with no change on their side. [All tools](/tools/) shows each place's tools, read from the code. --- # All tools Source: https://docs.roadstar.app/tools.md Every tool group and agent tool, how many tools each has, and where each agent tool is offered. Every agent has the same tools. What changes is where it is: a chat sends work and answers, and holds no work tools; a thread does the work with every group. A Mac program or outside AI app works like a thread, and keeps its own conversation. See [chats and threads](/how-it-works/chats-and-threads/). ## Tool groups In a thread, or a Mac program or outside app, every agent has every group. Browse a group with `browse_tools`, load tools with `find_tools`, run one with `use_tool`. | Group | What's in it | Tools | | --- | --- | --- | | [Contacts](/tools/contacts/) `contacts` | the people the business deals with | 11 | | [Companies](/tools/companies/) `companies` | the businesses the owner deals with: clients, suppliers, their conversations | 11 | | [Web](/tools/web/) `web` | search the web and read pages | 2 | | [Projects](/tools/projects/) `projects` | projects that hold docs, pages and tables | 4 | | [Docs](/tools/docs/) `docs` | the business's docs: proposals, briefs, checklists | 5 | | [Files](/tools/files/) `files` | the owner's Drive: files, Google Docs and Sheets | 15 | | [Mail](/tools/mail/) `mail` | the owner's Gmail | 4 | | [Calendar](/tools/calendar/) `calendar` | the owner's calendar: meetings and time held | 2 | | [Books](/tools/books/) `books` | invoices and what customers owe, from the owner's accounting app | 2 | | [Workspace](/tools/workspace/) `workspace` | your own folder: files, spreadsheets and code | 5 | | [Templates](/tools/templates/) `templates` | agents, teams, departments and skills shared in the workspace | 4 | | [Help](/tools/help/) `help` | these docs: how Roadstar works, and every tool | 2 | | [Search](/tools/search/) `search` | Search | 2 | | [Connected apps](/tools/integrations/) `integrations` | the apps the owner has connected or could connect | 2 | ## Agent tools Held directly, without `find_tools`, in the places marked. | Tool | Roadstar's chat | An agent's chat | A thread | A Mac or outside app | | --- | --- | --- | --- | --- | | [browse_tools](/tools/catalog/#browse_tools) | — | — | Yes | Yes | | [find_tools](/tools/catalog/#find_tools) | — | — | Yes | Yes | | [use_tool](/tools/catalog/#use_tool) | — | — | Yes | Yes | | [hand_off](/tools/team/#hand_off) | Yes | Yes | Yes | Yes | | [remember](/tools/team/#remember) | Yes | Yes | Yes | Yes | | [make_agent](/tools/team/#make_agent) | Yes | — | — | — | | [change_agent](/tools/team/#change_agent) | Yes | — | — | — | | [start_thread](/tools/threads/#start_thread) | Yes | Yes | — | — | | [message_thread](/tools/threads/#message_thread) | Yes | Yes | — | — | | [read_thread](/tools/threads/#read_thread) | Yes | Yes | — | — | | [close_thread](/tools/threads/#close_thread) | Yes | Yes | — | — | | [skills](/tools/routines/#skills) | Yes | Yes | Yes | Yes | | [triggers](/tools/routines/#triggers) | Yes | Yes | — | — | | [zoom_memory](/tools/memory/#zoom_memory) | Yes | Yes | Yes | — | | [message_date](/tools/memory/#message_date) | Yes | Yes | Yes | — | ## Research tools Optional. Every agent has them; they are for finance research, not the business's own work. | Group | What's in it | Tools | | --- | --- | --- | | [Research pages](/tools/pages/) `pages` | research pages | 6 | | [Comments](/tools/comments/) `comments` | comments on pages | 2 | | [Research tables](/tools/tables/) `tables` | research tables | 6 | | [Filings](/tools/filings/) `filings` | public companies' filings | 2 | | [Newsfeed](/tools/newsfeed/) `newsfeed` | today's news | 1 | --- # Contacts Source: https://docs.roadstar.app/tools/contacts.md The people the business deals with, shared by all the owner's agents. 11 tools. A contact is one person the business deals with: a client, a lead, a supplier. All the owner's agents share one list, so a contact Client desk adds is there for Bookkeeper too. A contact holds: - **Fields**: name, emails, phone, job title, labels, and named `fields` such as `birthday`. - **Context**: what everyone should know before writing to them, such as "Prefers a call to email; signs off quotes herself." Set it with `context` and say why. - **Open items**: who owes the next move. `you` (the business owes them), `them` (they owe the business), or a `follow_up` at a time, which wakes the agent that set it. - **Log**: dated notes and every change, newest first. Changes to a contact show on its log, not on Home. - **Companies and projects**: where they work, with a role, and the projects they're part of. Companies have [their own page](/tools/companies/). Before you write to someone, `find` their address and read their Context. ## How to use it What `find_tools` sends with these tools, word for word: > The people the business deals with, shared by all the owner's agents. Add and update the ones your work touches on your own judgement: no yes is needed, and every change can be undone. Use add_many for several at once; to fill Contacts from mail, search mail the owner sent (dir sent, bulk false): its counts.people says who they write to most; read their latest messages for names, roles and phones, then save them. Read someone's Context with find before you write to them. The businesses they work at are in companies. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [contacts.list](#list) | Compact contacts; company is a name or id | No | | [contacts.find](#find) | The contact with that address, their Context, company Context and open items, or null | No | | [contacts.read](#read) | Fields, Context, open items, newest 20 log entries, companies and projects | No | | [contacts.timeline](#timeline) | Live mail threads and meetings this agent may read | No | | [contacts.create](#create) | Add a contact | No | | [contacts.add_many](#add_many) | Add up to 200 people at once; company is a name (the workspace's own, made if new). Addresses already on a contact are reported, not added twice | No | | [contacts.update](#update) | Change fields and labels | Checked | | [contacts.context](#context) | Set Context and record why it changed; can be undone | Checked | | [contacts.note](#note) | Add a dated note | No | | [contacts.follow_up](#follow_up) | Add a follow-up for this agent; at is an ISO date and time | No | | [contacts.link_project](#link_project) | Link a contact to a project | No | ## list `contacts.list` · Compact contacts; company is a name or id. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `label` | text | no | 1–40 characters | | `company` | text | no | 1–200 characters | | `query` | text | no | up to 200 characters | | `open` | one of `you`, `them`, `follow_up` | no | | | `quietDays` | whole number | no | 0 or more | | `offset` | whole number | no | 0 or more | Returns: ```ts { items: { id: string; name: string; company: string | null; labels: string[]; last: ContactLast; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; } | null; }[]; nextOffset: number | null; total: number; } ``` ## find `contacts.find` · The contact with that address, their Context, company Context and open items, or null. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `email` | text | yes | | Returns: ```ts { companies: { context: string | null; id: string; name: string; ticker: string | null; handle: string; role?: string | null; since?: string | null; ended?: string | null; labels?: string[]; }[]; id: string; organizationId: string; name: string; emails: string[]; phone: string | null; jobTitle: string | null; labels: string[]; context: string | null; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; last: ContactLast; fields: ContactFreeFields; status: "active" | "suggested" | "ignored"; projects: ProjectRef[]; createdAt: string; updatedAt: string; } | null ``` ## read `contacts.read` · Fields, Context, open items, newest 20 log entries, companies and projects. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | Returns: ```ts { log: { id: string; at: string; by: { kind: "agent" | "user" | "system"; id: string; }; kind: "removed" | "note" | "ask" | "context" | "labels" | "created" | "sent" | "drafted" | "triage" | "looked" | "merged" | "restored"; text?: string; before?: string | string[] | null; after?: string | string[] | null; why?: string; subject?: string; to?: string[]; thread?: string; link?: string; hold?: number; sentAt?: string; approval?: { kind: "rule" | "allowed" | "waiting" | "yes" | "undone" | "declined"; at?: string; rule?: string; }; }[]; id: string; organizationId: string; name: string; emails: string[]; phone: string | null; jobTitle: string | null; labels: string[]; context: string | null; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; last: ContactLast; fields: ContactFreeFields; status: "active" | "suggested" | "ignored"; companies: CompanyRef[]; projects: ProjectRef[]; createdAt: string; updatedAt: string; } ``` ## timeline `contacts.timeline` · Live mail threads and meetings this agent may read. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `limit` | whole number | no | 1–100, default 20 | Returns: ```ts { items: ContactTimelineItem[]; failures: Failure[]; } ``` ## create `contacts.create` · Add a contact. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `name` | text | yes | 1–200 characters | | `emails` | list of text | no | each up to 254 characters | | `phone` | text or null | no | up to 80 characters | | `jobTitle` | text or null | no | up to 200 characters | | `labels` | list of text | no | each 1–40 characters | | `companyIds` | list of id | no | | | `fields` | named values of anything | no | default {} | Returns: ```ts ContactRecord | { id: unknown; message: string; } ``` ## add_many `contacts.add_many` · Add up to 200 people at once; company is a name (the workspace's own, made if new). Addresses already on a contact are reported, not added twice. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `people` | list of named values of anything | yes | 1–200 items | Returns: ```ts { added: { id: string; name: string; }[]; existing: { id: string; name: string; email: string; }[]; invalid: { row: number; name: string | null; error: string; }[]; } ``` ## update `contacts.update` · Change fields and labels. **Owner's yes:** Checked. It changes what's there.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `name` | text | no | 1–200 characters | | `emails` | list of text | no | each up to 254 characters | | `phone` | text or null | no | up to 80 characters | | `jobTitle` | text or null | no | up to 200 characters | | `labels` | list of text | no | each 1–40 characters | | `fields` | named values of anything | no | | Returns: ```ts { id: string; organizationId: string; name: string; emails: string[]; phone: string | null; jobTitle: string | null; labels: string[]; context: string | null; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; last: ContactLast; fields: ContactFreeFields; status: "active" | "suggested" | "ignored"; companies: CompanyRef[]; projects: ProjectRef[]; createdAt: string; updatedAt: string; } ``` ## context `contacts.context` · Set Context and record why it changed; can be undone. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `text` | text | yes | up to 1,500 characters | | `why` | text | yes | 1–2,000 characters | Returns: ```ts { contact: ContactRecord; entryId: string; } ``` ## note `contacts.note` · Add a dated note. **Owner's yes:** No. It makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `text` | text | yes | 1–2,000 characters | Returns: ```ts { id: string; at: string; by: { kind: "agent" | "user" | "system"; id: string; }; kind: "removed" | "note" | "ask" | "context" | "labels" | "created" | "sent" | "drafted" | "triage" | "looked" | "merged" | "restored"; text?: string; before?: string | string[] | null; after?: string | string[] | null; why?: string; subject?: string; to?: string[]; thread?: string; link?: string; hold?: number; sentAt?: string; approval?: { kind: "rule" | "allowed" | "waiting" | "yes" | "undone" | "declined"; at?: string; rule?: string; }; } ``` ## follow_up `contacts.follow_up` · Add a follow-up for this agent; at is an ISO date and time. **Owner's yes:** No. It makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `text` | text | yes | 1–2,000 characters | | `at` | date and time (ISO 8601) | yes | | Returns: ```ts { id: string; organizationId: string; name: string; emails: string[]; phone: string | null; jobTitle: string | null; labels: string[]; context: string | null; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; last: ContactLast; fields: ContactFreeFields; status: "active" | "suggested" | "ignored"; companies: CompanyRef[]; projects: ProjectRef[]; createdAt: string; updatedAt: string; } ``` ## link_project `contacts.link_project` · Link a contact to a project. **Owner's yes:** No. It makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `projectId` | id | yes | | Returns: ```ts { projectId: string; contactId: string; } ``` ## Types Named types in the results above. ```ts type ContactLast = { first?: string; at?: string; channel?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; members?: Record; agents?: Record; } type ContactFreeFields = Record & { ask?: boolean; source?: string; addedBy?: { kind: "agent" | "user" | "system"; id: string; }; notDuplicate?: string[]; } type ProjectRef = { id: string; name: string; } type CompanyRef = { id: string; name: string; ticker: string | null; handle: string; role?: string | null; since?: string | null; ended?: string | null; labels?: string[]; } type ContactTimelineItem = { kind: "mail" | "meeting"; id: string; title: string; at: string; direction?: "out" | "in"; from?: string; snippet?: string; url?: string; attendees?: string[]; } type Failure = { source: string; kind: "not_connected" | "reauth" | "forbidden" | "not_found" | "rate_limited" | "unavailable" | "too_large" | "unsupported" | "bad_input" | "vendor" | "changed" | "unknown_outcome"; message: string; integration: string; connection: string; } type ContactRecord = { id: string; organizationId: string; name: string; emails: string[]; phone: string | null; jobTitle: string | null; labels: string[]; context: string | null; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; last: { first?: string; at?: string; channel?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; members?: Record; agents?: Record; }; fields: Record & { ask?: boolean; source?: string; addedBy?: { kind: "agent" | "user" | "system"; id: string; }; notDuplicate?: string[]; }; status: "active" | "suggested" | "ignored"; companies: CompanyRef[]; projects: ProjectRef[]; createdAt: string; updatedAt: string; } ``` --- # Companies Source: https://docs.roadstar.app/tools/companies.md The businesses the owner deals with, each with its people, Context and a timeline of conversations. 11 tools. A company is a business the owner deals with: a client such as Juniper Dental, a supplier, a lead. All the owner's agents share one list. A company holds: - **Fields**: name, website, domains, labels, and a handler: the agent whose work it is. Work about a company goes to its handler. - **Details**: named facts such as `Terms: net 30`. `null` clears one. - **Context**: what everyone should know about working with them. - **About**: what the web says about them, with sources, set with `about`. - **People**: contacts linked with a role, and when they started and ended (`link_person`). - **Open items**: who owes the next move, as on [contacts](/tools/contacts/). - **Timeline**: one entry per conversation (a mail thread, meeting, call or invoice) with what was said or decided. Record one after each conversation; recording the same thread again updates its entry. Read a company before you work for it. Record conversations, never your own steps. ## How to use it What `find_tools` sends with these tools, word for word: > The businesses the owner deals with, shared by all the owner's agents: each with its people and their roles, a Context, details such as terms, an About from the web, and a timeline. Read a company before you work for it. Its timeline is its conversations with the business, one entry per thread, meeting, call or invoice: after one, record what was said or decided, and recording the same thread again updates its entry. ours is who wrote for the business: your agent id when you did; leave it out when the owner did. Fix an entry by recording it again with its entryId; take one off only when the owner asks. Never record your own steps, automated mail or newsletters. Work about a company goes to its handler. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [companies.list](#list) | Companies with labels, people, what's open, last contact and handler | No | | [companies.read](#read) | Everything to know before working for a company: Context, details, About, people with roles, open, newest 20 conversations | No | | [companies.create](#create) | Add a company; people are contact ids, a role each | No | | [companies.update](#update) | Change a company's name, domains, labels, handler (an agent id) or details (named facts such as Terms; null clears one) | Checked | | [companies.about](#about) | Replace a company's About from the web: text citing sources as [1], facts, dated news | Checked | | [companies.timeline](#timeline) | A company's conversations, newest first; before is the last item's at | No | | [companies.record](#record) | Add a conversation to a company's timeline, or update it: same entryId, thread or link. people: contact ids or addresses on their side; ours: agent ids that wrote for the business, else the owner | No | | [companies.unrecord](#unrecord) | Take an entry off a company's timeline; the owner can undo it | Checked | | [companies.open](#open) | Open an item on a company (a follow-up at a time wakes you), or close one by id | Depends | | [companies.context](#context) | Set a company's Context: what everyone should know about working with them | Checked | | [companies.link_person](#link_person) | Link a person to a company, or change the link: role, since, ended (dates) | No | ## list `companies.list` · Companies with labels, people, what's open, last contact and handler. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `label` | text | no | 1–40 characters | | `query` | text | no | up to 200 characters | | `open` | one of `you`, `them`, `follow_up` | no | | | `quietDays` | whole number | no | 0 or more | | `handledBy` | text | no | 1–80 characters | | `offset` | whole number | no | 0 or more | Returns: ```ts { items: CompanyRow[]; nextOffset: number | null; total: number; } ``` ## read `companies.read` · Everything to know before working for a company: Context, details, About, people with roles, open, newest 20 conversations. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | Returns: ```ts { timeline: { id: string; kind: "note" | "mail" | "invoice" | "meeting" | "call"; title: string; at: string; people: { name: string; id?: string; }[]; ours: { kind: "agent" | "user" | "system"; id: string; }[]; by: { kind: "agent" | "user" | "system"; id: string; }; updatedAt: string; summary?: string; count?: number; minutes?: number; link?: string; thread?: string; files?: { name: string; link?: string; }[]; state?: "overdue" | "paid" | "waiting_you" | "waiting_them"; }[]; id: string; name: string; website: string | null; domains: string[]; labels: string[]; context: string | null; contextBy: { kind: "agent" | "user" | "system"; id: string; } | null; contextAt: string | null; details: Record; about: CompanyAbout | null; handler: string | null; firstOnMail: boolean; alwaysAsk: boolean; listed: { id: string; name: string; ticker: string | null; } | null; files: CompanyFile[]; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; people: CompanyPerson[]; known: string | null; last: string | null; counts: { timeline: number; projects: number; docs: number; files: number; }; createdAt: string; updatedAt: string; } ``` ## create `companies.create` · Add a company; people are contact ids, a role each. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `name` | text | yes | 1–200 characters | | `website` | text | no | 1–300 characters | | `domains` | list of text | no | up to 10 items, each 1–200 characters | | `labels` | list of text | no | up to 20 items, each 1–40 characters | | `handler` | text | no | 1–80 characters | | `people` | list of `{ contactId, role? }` | no | up to 200 items | | `people[].contactId` | id | yes | | | `people[].role` | text | no | 1–200 characters | Returns: ```ts CompanyRecord | { message: string; } ``` ## update `companies.update` · Change a company's name, domains, labels, handler (an agent id) or details (named facts such as Terms; null clears one). **Owner's yes:** Checked. It changes what's there.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `name` | text | no | 1–200 characters | | `domains` | list of text | no | up to 10 items, each 1–200 characters | | `labels` | list of text | no | up to 20 items, each 1–40 characters | | `handler` | text or null | no | 1–80 characters | | `details` | named values of text or null | no | | | `why` | text | yes | 1–2,000 characters | Returns: ```ts { id: string; name: string; website: string | null; domains: string[]; labels: string[]; context: string | null; contextBy: { kind: "agent" | "user" | "system"; id: string; } | null; contextAt: string | null; details: Record; about: CompanyAbout | null; handler: string | null; firstOnMail: boolean; alwaysAsk: boolean; listed: { id: string; name: string; ticker: string | null; } | null; files: CompanyFile[]; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; people: CompanyPerson[]; known: string | null; last: string | null; counts: { timeline: number; projects: number; docs: number; files: number; }; createdAt: string; updatedAt: string; } ``` ## about `companies.about` · Replace a company's About from the web: text citing sources as [1], facts, dated news. **Owner's yes:** Checked. It changes what's there.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `text` | text | yes | 1–3,000 characters | | `facts` | list of `{ label, value }` | no | up to 12 items | | `facts[].label` | text | yes | 1–40 characters | | `facts[].value` | text | yes | 1–200 characters | | `lately` | list of `{ date, text, source? }` | no | up to 10 items | | `lately[].date` | date (YYYY-MM-DD) | yes | | | `lately[].text` | text | yes | 1–300 characters | | `lately[].source` | whole number | no | 1 or more | | `sources` | list of `{ title, url }` | yes | 1–20 items | | `sources[].title` | text | yes | 1–200 characters | | `sources[].url` | link | yes | | Returns: ```ts { id: string; name: string; website: string | null; domains: string[]; labels: string[]; context: string | null; contextBy: { kind: "agent" | "user" | "system"; id: string; } | null; contextAt: string | null; details: Record; about: CompanyAbout | null; handler: string | null; firstOnMail: boolean; alwaysAsk: boolean; listed: { id: string; name: string; ticker: string | null; } | null; files: CompanyFile[]; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; people: CompanyPerson[]; known: string | null; last: string | null; counts: { timeline: number; projects: number; docs: number; files: number; }; createdAt: string; updatedAt: string; } ``` ## timeline `companies.timeline` · A company's conversations, newest first; before is the last item's at. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `kind` | one of `mail`, `meeting`, `call`, `invoice`, `note` | no | | | `contactId` | id | no | | | `query` | text | no | up to 200 characters | | `before` | text | no | | Returns: ```ts { items: { id: string; kind: "note" | "mail" | "invoice" | "meeting" | "call"; title: string; at: string; people: { name: string; id?: string; }[]; ours: { kind: "agent" | "user" | "system"; id: string; }[]; by: { kind: "agent" | "user" | "system"; id: string; }; updatedAt: string; summary?: string; count?: number; minutes?: number; link?: string; thread?: string; files?: { name: string; link?: string; }[]; state?: "overdue" | "paid" | "waiting_you" | "waiting_them"; }[]; total: number; counts: { [k: string]: number; }; nextBefore: string | null; } ``` ## record `companies.record` · Add a conversation to a company's timeline, or update it: same entryId, thread or link. people: contact ids or addresses on their side; ours: agent ids that wrote for the business, else the owner. **Owner's yes:** No. It makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `entryId` | text | no | | | `kind` | one of `mail`, `meeting`, `call`, `invoice`, `note` | yes | | | `title` | text | yes | 1–200 characters | | `summary` | text | no | up to 1,000 characters | | `at` | date and time (ISO 8601) | yes | | | `people` | list of text | no | up to 20 items, each 1–254 characters | | `ours` | list of text | no | up to 10 items, each 1–80 characters | | `count` | whole number | no | 1 or more | | `minutes` | whole number | no | 1 or more | | `link` | link | no | | | `thread` | text | no | | | `files` | list of `{ name, link? }` | no | up to 20 items | | `files[].name` | text | yes | 1–200 characters | | `files[].link` | link | no | | | `state` | one of `waiting_you`, `waiting_them`, `paid`, `overdue` or null | no | | Returns: ```ts { entry: { id: string; kind: "note" | "mail" | "invoice" | "meeting" | "call"; title: string; at: string; people: { name: string; id?: string; }[]; ours: { kind: "agent" | "user" | "system"; id: string; }[]; by: { kind: "agent" | "user" | "system"; id: string; }; updatedAt: string; summary?: string; count?: number; minutes?: number; link?: string; thread?: string; files?: { name: string; link?: string; }[]; state?: "overdue" | "paid" | "waiting_you" | "waiting_them"; }; updated: boolean; } ``` ## unrecord `companies.unrecord` · Take an entry off a company's timeline; the owner can undo it. **Owner's yes:** Checked. It deletes.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `entryId` | text | yes | at least 1 characters | Returns: ```ts { historyId: string; } ``` ## open `companies.open` · Open an item on a company (a follow-up at a time wakes you), or close one by id. **Owner's yes:** Depends on the input: checked when it changes what's there, not when it makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `add` | object | no | | | `add.kind` | one of `you`, `them`, `follow_up` | yes | | | `add.text` | text | yes | 1–500 characters | | `add.at` | date and time (ISO 8601) | no | | | `done` | text | no | | Returns: ```ts { id: string; name: string; website: string | null; domains: string[]; labels: string[]; context: string | null; contextBy: { kind: "agent" | "user" | "system"; id: string; } | null; contextAt: string | null; details: Record; about: CompanyAbout | null; handler: string | null; firstOnMail: boolean; alwaysAsk: boolean; listed: { id: string; name: string; ticker: string | null; } | null; files: CompanyFile[]; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; people: CompanyPerson[]; known: string | null; last: string | null; counts: { timeline: number; projects: number; docs: number; files: number; }; createdAt: string; updatedAt: string; } ``` ## context `companies.context` · Set a company's Context: what everyone should know about working with them. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyId` | id | yes | | | `text` | text | yes | up to 1,500 characters | | `why` | text | yes | 1–2,000 characters | Returns: ```ts { id: string; name: string; website: string | null; domains: string[]; context: string | null; contextBy: { kind: "agent" | "user" | "system"; id: string; } | null; contextAt: string | null; owned: boolean; } ``` ## link_person `companies.link_person` · Link a person to a company, or change the link: role, since, ended (dates). **Owner's yes:** No. It makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `contactId` | id | yes | | | `companyId` | id | yes | | | `role` | text | no | 1–200 characters | | `since` | date (YYYY-MM-DD) | no | | | `ended` | date (YYYY-MM-DD) | no | | Returns: ```ts { contactId: string; companyId: string; } ``` ## Types Named types in the results above. ```ts type CompanyRow = { id: string; name: string; website: string | null; domains: string[]; labels: string[]; handler: string | null; people: { id: string; name: string; }[]; peopleCount: number; open: ({ id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; } & { person: string | null; }) | null; openCount: number; openKinds: ("you" | "them" | "follow_up")[]; last: string | null; } type CompanyAbout = { text: string; facts: { label: string; value: string; }[]; lately: { date: string; text: string; source?: number; }[]; sources: { title: string; url: string; }[]; by: { kind: "agent" | "user" | "system"; id: string; }; at: string; } type CompanyFile = { id: string; name: string; link: string; by: { kind: "agent" | "user" | "system"; id: string; }; at: string; } type CompanyPerson = { id: string; name: string; emails: string[]; phone: string | null; labels: string[]; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; last: { first?: string; at?: string; channel?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; members?: Record; agents?: Record; }; role: string | null; since: string | null; ended: string | null; } type CompanyRecord = { id: string; name: string; website: string | null; domains: string[]; labels: string[]; context: string | null; contextBy: { kind: "agent" | "user" | "system"; id: string; } | null; contextAt: string | null; details: { [x: string]: string; }; about: CompanyAbout | null; handler: string | null; firstOnMail: boolean; alwaysAsk: boolean; listed: { id: string; name: string; ticker: string | null; } | null; files: CompanyFile[]; open: { id: string; kind: "you" | "them" | "follow_up"; text: string; at: string; thread?: string; link?: string; by?: { kind: "agent" | "user" | "system"; id: string; }; }[]; people: CompanyPerson[]; known: string | null; last: string | null; counts: { timeline: number; projects: number; docs: number; files: number; }; createdAt: string; updatedAt: string; } ``` --- # Projects Source: https://docs.roadstar.app/tools/projects.md Projects group the business's docs, tables and contacts. 4 tools. A project groups the business's docs, tables and contacts around one piece of work, such as "Juniper Dental rebrand" or "Bookkeeping 2026". Every [doc](/tools/docs/) goes in one. Use the project the owner named. Otherwise list them and pick the one the work belongs to, or create one named after the job. ## How to use it What `find_tools` sends with these tools, word for word: > Projects group the owner's docs, tables and contacts, and every doc goes in one. Use the project the owner named; else list them and pick the one the work belongs to, or create one named after the job. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [projects.list](#list) | Every project in the workspace | No | | [projects.activity](#activity) | Recent changes across the workspace | No | | [projects.create](#create) | Create a project | No | | [projects.update](#update) | Rename a project or change its description | Checked | ## list `projects.list` · Every project in the workspace. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `offset` | whole number | no | 0 or more | Returns: ```ts { items: { id: string; organizationId: string; name: string; description: string; createdByUserId: string | null; updatedByUserId: string | null; createdAt: string; updatedAt: string; }[]; nextOffset: number | null; } ``` ## activity `projects.activity` · Recent changes across the workspace. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `offset` | whole number | no | 0 or more | Returns: ```ts { items: WorkspaceActivityItem[]; nextOffset: number | null; } ``` ## create `projects.create` · Create a project. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `name` | text | yes | 1–120 characters | | `description` | text | no | up to 4,000 characters, default "" | Returns: ```ts { name: string; description: string; id: string; createdAt: string; organizationId: string; createdByUserId: string | null; updatedByUserId: string | null; updatedAt: string; } | { id: string; organizationId: string; name: string; description: string; createdByUserId: string | null; updatedByUserId: string | null; createdAt: string; updatedAt: string; } ``` ## update `projects.update` · Rename a project or change its description. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `projectId` | id | yes | | | `name` | text | yes | 1–120 characters | | `description` | text | yes | up to 4,000 characters | Returns: ```ts { id: string; organizationId: string; name: string; description: string; createdByUserId: string | null; updatedByUserId: string | null; createdAt: string; updatedAt: string; } ``` ## Types Named types in the results above. ```ts type WorkspaceActivityItem = { id: string; kind: "question" | "thesis" | "company" | "comment" | "member" | "chat" | "project" | "annotation" | "dataset" | "investment"; title: string; summary: string; occurredAt: string; actorName: string | null; projectId: string | null; projectName: string | null; target: { type: "page" | "workspace" | "company" | "project"; id: string; } | { type: "dataset"; id: string; projectId?: string; } | { type: "chat"; id: string; projectId: string; }; private: boolean; } ``` --- # Docs Source: https://docs.roadstar.app/tools/docs.md Docs agents write and keep for the owner, such as proposals, briefs, checklists and recurring reports. 5 tools. A doc is a page an agent writes and keeps for the owner: a proposal, a brief, a checklist, a monthly report. Its text is one MDX file: Markdown plus a few components for callouts, figures, columns and charts. The owner reads it under Docs in Pro and can leave notes on it. - `write` sets a doc's whole text: a new doc, the first draft of one the owner asked for, or a full rewrite such as a routine's monthly report. Writing a new doc needs no yes; rewriting one that's there is checked. - `edit` changes parts: each edit finds words the doc has now, once, and replaces them. All edits apply or none do. - `read` gives the text, the brief it was asked with, and the owner's open notes. Answer a note by editing, or with `answer` when you keep the text as it is. Write a doc when the work is long, structured or worth keeping. Otherwise answer in a message. ## How to use it What `find_tools` sends with these tools, word for word: > Docs you write for the owner and keep: SOPs and briefs, write-ups of your work, reports that recur. Write a doc when the work is long, structured or worth keeping; answer in a message otherwise. A doc you were asked to write already exists: write into its docId. A doc's text is MDX: Markdown (## headings, lists, tables, **bold**, links) plus …, , …, and a chart as holding one ```vega-lite JSON block with its data inline. Link people and things you read as roadstar links: [Juniper Dental](roadstar:contact/); kinds: contact, email, file, invoice, bill, agent, doc, page, table, project. A report or tracker reads like a dashboard: one line on as of when and from what; a MetricGrid of the three or four figures that matter; a short table of what needs the owner; then one table of every item, a row each, in short cells: dates like Nov 6, amounts right-aligned (|---:|), a status of a word or two, each row's name linked to its source. A recurring one opens with what moved since the last. What you couldn't check goes in one line at the end; your own working notes stay out of it. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [docs.list](#list) | Docs, newest first | No | | [docs.read](#read) | A doc's current text, the brief it was asked with, what to read first, and the notes the owner sent you on it | No | | [docs.write](#write) | Write a doc's whole text: a new doc, the first draft of one you were asked for, or a full rewrite such as a routine's. | Depends | | [docs.edit](#edit) | Change parts of a doc you've read: each find is words it has now, once. All edits apply together or none; the rest stays as it is. | Checked | | [docs.answer](#answer) | Answer a note without changing the doc: kept, with why; or done, for a note that asked you to do something else, with what you did | Checked | ## list `docs.list` · Docs, newest first. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `projectId` | id | no | | | `title` | text | no | up to 300 characters | | `offset` | whole number | no | 0 or more | Returns: ```ts { items: { id: string; title: string; type: "question" | "thesis" | "doc"; projectId: string; projectName: string; status: "draft" | "published" | "archived"; generationStatus: "queued" | "running" | "failed" | "completed" | null; updatedAt: string; }[]; nextOffset: number | null; } ``` ## read `docs.read` · A doc's current text, the brief it was asked with, what to read first, and the notes the owner sent you on it. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `docId` | id | yes | | Returns: ```ts { id: string; title: string; projectId: string | null; brief: string | null; readFirst: { kind: "page" | "table" | "doc" | "contact"; id: string; label: string; }[]; updates: "once" | "weekly" | "monthly" | "quarterly"; text: string | null; notes: { noteId: string; on: string; note: string | null; }[]; } ``` ## write `docs.write` · Write a doc's whole text: a new doc, the first draft of one you were asked for, or a full rewrite such as a routine's. **Owner's yes:** Depends on the input: checked when it changes what's there, not when it makes something new.\ **Your work:** listed on Home. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `docId` | id | no | | | | `projectId` | id | no | | | | `title` | text | yes | | 1–300 characters | | `text` | text | yes | MDX, as the docs how-to says | 1–100,000 characters | | `lines` | list of text | no | what changed, one plain sentence each, for the owner | up to 20 items, each 1–300 characters | Returns: ```ts { pageId: string; versionId: string; version: number; } ``` ## edit `docs.edit` · Change parts of a doc you've read: each find is words it has now, once. All edits apply together or none; the rest stays as it is. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `docId` | id | yes | | | | `edits` | list of `{ find, replace }` | yes | | 1–30 items | | `edits[].find` | text | yes | | 1–20,000 characters | | `edits[].replace` | text | yes | | up to 20,000 characters | | `lines` | list of text | no | what changed, one plain sentence each, for the owner | up to 20 items, each 1–300 characters | | `answers` | list of `{ noteId, outcome, text }` | no | When the owner sent notes, answer every one: changed with what you did, or kept with why | up to 40 items | | `answers[].noteId` | id | yes | | | | `answers[].outcome` | one of `changed`, `kept` | yes | | | | `answers[].text` | text | yes | | 1–1,000 characters | Returns: ```ts { pageId: string; versionId: string; version: number; } ``` ## answer `docs.answer` · Answer a note without changing the doc: kept, with why; or done, for a note that asked you to do something else, with what you did. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `docId` | id | yes | | | `noteId` | id | yes | | | `outcome` | one of `kept`, `done` | yes | | | `text` | text | yes | 1–1,000 characters | Returns: ```ts { commentId: string; } ``` --- # Mail Source: https://docs.roadstar.app/tools/mail.md The owner's Gmail, read live, with drafting and sending. 4 tools. The owner's own mail, read live from every Gmail account they connected through Roadstar. Nothing is copied unless the owner turns on [search](/tools/search/). - `search` and `read` work on every connected account. - `draft` makes a draft in the owner's Gmail and sends nothing. - `send` sends from the owner's Gmail. It is checked first, and it leaves after a two-minute Undo on Home. Drafting and sending need the owner's connection to allow writing. Until it does, they return error `reauth`, and a card asks the owner to connect again. See [errors](/reference/errors/). `attach` takes Drive files by id (Google files go as PDFs), attachments by their id from search, and workspace files by path, 25 MB in all. Where search is on, `search` and `read` here cover only what search's copy doesn't hold yet. Once every account is copied whole, mail keeps only `draft` and `send`, and its how-to says so. ## How to use it What `find_tools` sends with these tools, word for word: > The owner's mail, such as Gmail, searched live. A thread reads as its messages without quoted text; attachments read like files. Gmail's web links don't open here: search by sender and subject. Gmail: Gmail's query takes its search box syntax, and results come back newest first. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [mail.search](#search) | Threads, newest first, across the owner's mail accounts, each with everyone on it but the owner; query takes the mail's own search words, such as in:sent; dates as YYYY-MM-DD. nextPage, passed as page, gives older threads | No | | [mail.read](#read) | A thread's messages with sender, recipients, date and text, and its attachments. With an attachment's id, that file's text. | No | | [mail.draft](#draft) | Make a draft in the owner's Gmail, in its thread when replying. Sends nothing. | No | | [mail.send](#send) | Send an email from the owner's Gmail. It goes after a two-minute Undo. | Checked | ## search `mail.search` · Threads, newest first, across the owner's mail accounts, each with everyone on it but the owner; query takes the mail's own search words, such as in:sent; dates as YYYY-MM-DD. nextPage, passed as page, gives older threads. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `query` | text | no | | up to 500 characters | | `from` | text | no | | up to 200 characters | | `to` | text | no | | up to 200 characters | | `after` | text | no | | up to 40 characters | | `before` | text | no | | up to 40 characters | | `hasAttachment` | true or false | no | | | | `source` | text | no | Only accounts whose name contains this, e.g. an email address | up to 120 characters | | `limit` | whole number | no | | 1–20, default 10 | | `page` | text | no | | up to 4,000 characters | Returns: ```ts { nextPage?: string; hits: (Hit & { source: string; })[]; searched: string[]; failed: Failure[]; } ``` ## read `mail.read` · A thread's messages with sender, recipients, date and text, and its attachments. With an attachment's id, that file's text. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `attachment` | text | no | up to 300 characters | | `offset` | whole number | no | 0 or more | Returns: ```ts Content & { id: string; source: string; nextOffset?: number; } ``` ## draft `mail.draft` · Make a draft in the owner's Gmail, in its thread when replying. Sends nothing. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `to` | list of email address | no | | up to 20 items, each up to 200 characters, default [] | | `cc` | list of email address | no | | up to 20 items, each up to 200 characters | | `subject` | text | yes | | up to 300 characters | | `text` | text | yes | | up to 20,000 characters | | `threadId` | text | no | To reply in a thread: its id from search | up to 300 characters | | `replyAll` | true or false | no | Answer everyone on the thread | | | `attach` | list of `{ file }` or `{ email?, attachment }` or `{ workspace }` | no | Drive files (Google files go as PDFs), attachments by their id from search, or workspace files by path, 25 MB in all | up to 10 items | | `signature` | true or false | no | false leaves off the owner's Gmail signature | | Returns: ```ts { draftId: string; threadId: string; to: string[]; } ``` ## send `mail.send` · Send an email from the owner's Gmail. It goes after a two-minute Undo. **Owner's yes:** Checked. It sends outside the business. It can also carry a note for the check to weigh.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `to` | list of email address | no | | up to 20 items, each up to 200 characters, default [] | | `cc` | list of email address | no | | up to 20 items, each up to 200 characters | | `subject` | text | yes | | up to 300 characters | | `text` | text | yes | | 1–20,000 characters | | `threadId` | text | no | To reply in a thread: its id from search | up to 300 characters | | `replyAll` | true or false | no | Answer everyone on the thread | | | `attach` | list of `{ file }` or `{ email?, attachment }` or `{ workspace }` | no | Drive files (Google files go as PDFs), attachments by their id from search, or workspace files by path, 25 MB in all | up to 10 items | | `signature` | true or false | no | false leaves off the owner's Gmail signature | | Returns: ```ts { held: { trigger: number; at: string; to: string[]; }; threadId: string; note: string; } ``` ## Types Named types in the results above. ```ts type Hit = { id: string; title: string; type: string; who?: string; people?: string[]; date?: string; snippet?: string; url: string; } type Failure = { source: string; kind: "not_connected" | "reauth" | "forbidden" | "not_found" | "rate_limited" | "unavailable" | "too_large" | "unsupported" | "bad_input" | "vendor" | "changed" | "unknown_outcome"; message: string; integration: string; connection: string; } type Content = { title: string; url: string; text: string; attachments?: Attachment[]; items?: Hit[]; nextPage?: string; image?: { mimeType: string; data: string; }; note?: string; } type Attachment = { id: string; name: string; size: number; } ``` --- # Files Source: https://docs.roadstar.app/tools/files.md The owner's Google Drive and OneDrive, read live, with Google Docs and Sheets writing. 15 tools. The owner's files, read live from every Drive or OneDrive account they connected. A file's id carries the account it's in, so pass ids exactly as a tool gave them. A Drive link the owner pastes works as an id too. Reading covers files, folders, Google Docs and Sheets, PDFs and pictures. Writing covers Google Drive: copy a template, make folders, move and rename, write Google Docs and Sheets, change cells and text, and save workspace files and email attachments into Drive. Each write is listed on Home with Undo. Writing needs the owner's Drive connection to allow it. Until it does, a write returns error `reauth`, and a card asks the owner to connect again. Office files (.xlsx, .docx, .pptx) are edited in the [workspace](/tools/workspace/): `workspace.open` copies them in, code changes them, and `files.save` with `over` puts a new version back. Where search is on, `search` and `read` here cover only what search's copy doesn't hold yet. ## How to use it What `find_tools` sends with these tools, word for word: > The owner's files, such as Google Drive, searched live across every account they connected. Read a folder to work through it. A Drive link the owner pastes opens directly. Spreadsheets read tab by tab, pictures as images; a scanned PDF has no text, so search for its words instead. Google Drive: Drive matches words in names and file text, by relevance, not meaning: try other words when a search misses. OneDrive: OneDrive searches the files the owner can open in OneDrive and SharePoint with Microsoft Search: words, "exact phrases", filetype:xlsx, author:"Name". Older Office files such as .doc and .ppt are read from a PDF copy. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [files.search](#search) | Files whose name or text match, across the owner's accounts, and which accounts failed. nextPage, passed as page, gives more. | No | | [files.read](#read) | A file's text by id or Drive link, 30,000 characters at a time (nextOffset gives the rest), or a folder's items (nextPage gives more). A picture comes back as an image. | No | | [files.copy](#copy) | A copy of a file, such as a template, under a new name, in a folder (an id from search or folder) when given | No | | [files.folder](#folder) | A new folder, inside another when parentId is given | No | | [files.move](#move) | Move files or folders into a folder, up to 50 at once | Checked | | [files.rename](#rename) | Rename a file or folder | Checked | | [files.create_doc](#create_doc) | A new Google Doc written from Markdown: headings, lists, tables, bold and links | No | | [files.append_text](#append_text) | Add text as a new paragraph at the end of a Google Doc | Checked | | [files.create_sheet](#create_sheet) | A new Google Sheet from rows, the first row its headings | No | | [files.save](#save) | Put a workspace file in Drive: a new file, or with over, a new version of the Drive file it was opened from | Depends | | [files.save_attachment](#save_attachment) | Save an email's attachment into Drive: attachment is its id from search (gmail:…:a:…). For mail not in search, email is the thread's id from mail search and attachment the id mail read listed | No | | [files.sheet_rows](#sheet_rows) | A sheet's cells, such as "Projects!A1:H50" | No | | [files.append_rows](#append_rows) | Add rows after a sheet's table, such as range "Leads!A:F" | Checked | | [files.update_cells](#update_cells) | Write over cells, such as a date in "Projects!F12"; their old values are kept for Undo | Checked | | [files.replace_text](#replace_text) | Replace every match of some text in a Google Doc | Checked | ## search `files.search` · Files whose name or text match, across the owner's accounts, and which accounts failed. nextPage, passed as page, gives more. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `query` | text | yes | | 1–500 characters | | `source` | text | no | Only accounts whose name contains this, e.g. an email address | up to 120 characters | | `modifiedAfter` | text | no | | up to 40 characters | | `limit` | whole number | no | | 1–20, default 10 | | `page` | text | no | | up to 4,000 characters | Returns: ```ts { nextPage?: string; hits: (Hit & { source: string; })[]; searched: string[]; failed: Failure[]; } ``` ## read `files.read` · A file's text by id or Drive link, 30,000 characters at a time (nextOffset gives the rest), or a folder's items (nextPage gives more). A picture comes back as an image. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `offset` | whole number | no | 0 or more | | `page` | text | no | up to 4,000 characters | Returns: ```ts Content & { id: string; source: string; nextOffset?: number; } ``` ## copy `files.copy` · A copy of a file, such as a template, under a new name, in a folder (an id from search or folder) when given. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `name` | text | yes | 1–200 characters | | `folderId` | text | no | 3–500 characters | Returns: ```ts { id: string; name: string; url: string; } ``` ## folder `files.folder` · A new folder, inside another when parentId is given. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `name` | text | yes | 1–200 characters | | `parentId` | text | no | 3–500 characters | Returns: ```ts { id: string; name: string; url: string; } ``` ## move `files.move` · Move files or folders into a folder, up to 50 at once. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `ids` | list of text | yes | 1–50 items, each 3–500 characters | | `folderId` | text | yes | 3–500 characters | Returns: ```ts { moved: { id: string; name: string; from: string[]; }[]; } ``` ## rename `files.rename` · Rename a file or folder. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `name` | text | yes | 1–200 characters | Returns: ```ts { before: string; id: string; } ``` ## create_doc `files.create_doc` · A new Google Doc written from Markdown: headings, lists, tables, bold and links. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `name` | text | yes | 1–200 characters | | `markdown` | text | yes | 1–200,000 characters | | `folderId` | text | no | 3–500 characters | Returns: ```ts { id: string; name: string; url: string; } ``` ## append_text `files.append_text` · Add text as a new paragraph at the end of a Google Doc. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `text` | text | yes | 1–50,000 characters | Returns: ```ts { start: number; end: number; id: string; } ``` ## create_sheet `files.create_sheet` · A new Google Sheet from rows, the first row its headings. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `name` | text | yes | 1–200 characters | | `rows` | list of list of text or number | yes | 1–200 items | | `folderId` | text | no | 3–500 characters | Returns: ```ts { id: string; name: string; url: string; } ``` ## save `files.save` · Put a workspace file in Drive: a new file, or with over, a new version of the Drive file it was opened from. **Owner's yes:** Depends on the input: checked when it changes what's there, not when it makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `workspace` | text | yes | Its path, such as shared/report.xlsx | 1–500 characters | | `folderId` | text | no | Where a new file goes | 3–500 characters | | `name` | text | no | | 1–200 characters | | `asGoogle` | true or false | no | Makes an .xlsx, .docx or .pptx a Google file | | | `over` | text | no | The Drive file it was opened from: open's from. Never a Google file | 3–500 characters | Returns: ```ts { id: string; name: string; url: string; } ``` ## save_attachment `files.save_attachment` · Save an email's attachment into Drive: attachment is its id from search (gmail:…:a:…). For mail not in search, email is the thread's id from mail search and attachment the id mail read listed. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `email` | text | no | 3–500 characters | | `attachment` | text | yes | 3–300 characters | | `folderId` | text | no | 3–500 characters | | `name` | text | no | 1–200 characters | Returns: ```ts { id: string; name: string; url: string; } ``` ## sheet_rows `files.sheet_rows` · A sheet's cells, such as "Projects!A1:H50". **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `range` | text | yes | up to 200 characters | Returns: ```ts { values: Values; } ``` ## append_rows `files.append_rows` · Add rows after a sheet's table, such as range "Leads!A:F". **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `range` | text | yes | up to 200 characters | | `values` | list of list of text or number | yes | 1–200 items | Returns: ```ts { id: string; updatedRange: string; } ``` ## update_cells `files.update_cells` · Write over cells, such as a date in "Projects!F12"; their old values are kept for Undo. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `range` | text | yes | up to 200 characters | | `values` | list of list of text or number | yes | 1–200 items | Returns: ```ts { id: string; range: string; before: string[][]; } ``` ## replace_text `files.replace_text` · Replace every match of some text in a Google Doc. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `find` | text | yes | 1–2,000 characters | | `replace` | text | yes | up to 5,000 characters | Returns: ```ts { id: string; find: string; replace: string; changed: number; } ``` ## Types Named types in the results above. ```ts type Hit = { id: string; title: string; type: string; who?: string; people?: string[]; date?: string; snippet?: string; url: string; } type Failure = { source: string; kind: "not_connected" | "reauth" | "forbidden" | "not_found" | "rate_limited" | "unavailable" | "too_large" | "unsupported" | "bad_input" | "vendor" | "changed" | "unknown_outcome"; message: string; integration: string; connection: string; } type Content = { title: string; url: string; text: string; attachments?: Attachment[]; items?: Hit[]; nextPage?: string; image?: { mimeType: string; data: string; }; note?: string; } type Values = { length: number; toString: () => string; toLocaleString: { (): string; (locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions)): string; }; pop: () => string[]; push: (...items: string[][]) => number; concat: { (...items: ConcatArray[]): string[][]; (...items: (string[] | ConcatArray)[]): string[][]; }; join: (separator?: string) => string; reverse: () => string[][]; shift: () => string[]; slice: (start?: number, end?: number) => string[][]; sort: (compareFn?: ((a: string[], b: string[]) => number)) => Values; splice: { (start: number, deleteCount?: number): string[][]; (start: number, deleteCount: number, ...items: string[][]): string[][]; }; unshift: (...items: string[][]) => number; indexOf: (searchElement: string[], fromIndex?: number) => number; lastIndexOf: (searchElement: string[], fromIndex?: number) => number; every: { (predicate: (value: string[], index: number, array: string[][]) => value is S, thisArg?: any): this is S[]; (predicate: (value: string[], index: number, array: string[][]) => unknown, thisArg?: any): boolean; }; some: (predicate: (value: string[], index: number, array: string[][]) => unknown, thisArg?: any) => boolean; forEach: (callbackfn: (value: string[], index: number, array: string[][]) => void, thisArg?: any) => void; map: (callbackfn: (value: string[], index: number, array: string[][]) => U, thisArg?: any) => U[]; filter: { (predicate: (value: string[], index: number, array: string[][]) => value is S, thisArg?: any): S[]; (predicate: (value: string[], index: number, array: string[][]) => unknown, thisArg?: any): string[][]; }; reduce: { (callbackfn: (previousValue: string[], currentValue: string[], currentIndex: number, array: string[][]) => string[]): string[]; (callbackfn: (previousValue: string[], currentValue: string[], currentIndex: number, array: string[][]) => string[], initialValue: string[]): string[]; (callbackfn: (previousValue: U, currentValue: string[], currentIndex: number, array: string[][]) => U, initialValue: U): U; }; reduceRight: { (callbackfn: (previousValue: string[], currentValue: string[], currentIndex: number, array: string[][]) => string[]): string[]; (callbackfn: (previousValue: string[], currentValue: string[], currentIndex: number, array: string[][]) => string[], initialValue: string[]): string[]; (callbackfn: (previousValue: U, currentValue: string[], currentIndex: number, array: string[][]) => U, initialValue: U): U; }; find: { (predicate: (value: string[], index: number, obj: string[][]) => value is S, thisArg?: any): S; (predicate: (value: string[], index: number, obj: string[][]) => unknown, thisArg?: any): string[]; }; findIndex: (predicate: (value: string[], index: number, obj: string[][]) => unknown, thisArg?: any) => number; fill: (value: string[], start?: number, end?: number) => Values; copyWithin: (target: number, start: number, end?: number) => Values; entries: () => ArrayIterator<[number, string[]]>; keys: () => ArrayIterator; values: () => ArrayIterator; includes: (searchElement: string[], fromIndex?: number) => boolean; flatMap: (callback: (this: This, value: string[], index: number, array: string[][]) => U | readonly U[], thisArg?: This) => U[]; flat: (this: A, depth?: D) => FlatArray[]; at: (index: number) => string[]; __@iterator@457: () => ArrayIterator; __@unscopables@459: { [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; splice?: boolean; unshift?: boolean; indexOf?: boolean; lastIndexOf?: boolean; every?: boolean; some?: boolean; forEach?: boolean; map?: boolean; filter?: boolean; reduce?: boolean; reduceRight?: boolean; find?: boolean; findIndex?: boolean; fill?: boolean; copyWithin?: boolean; entries?: boolean; keys?: boolean; values?: boolean; includes?: boolean; flatMap?: boolean; flat?: boolean; at?: boolean; [Symbol.iterator]?: boolean; readonly [Symbol.unscopables]?: boolean; }; } type Attachment = { id: string; name: string; size: number; } ``` --- # Calendar Source: https://docs.roadstar.app/tools/calendar.md The owner's Google Calendar, read live, and time held on it. 2 tools. The owner's own Google Calendar. `list` reads meetings with their guests; `hold` blocks time on the owner's calendar and invites nobody. To meet someone, hold the time and ask them by email. Holding time needs the owner's connection to allow it. Until it does, `hold` returns error `reauth`, and a card asks the owner to connect again. ## How to use it What `find_tools` sends with these tools, word for word: > The owner's own Google Calendar, read and written as them. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [calendar.list](#list) | Meetings between two times, soonest first, with guests | No | | [calendar.hold](#hold) | Hold time on the owner's own calendar; invites nobody | No | ## list `calendar.list` · Meetings between two times, soonest first, with guests. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `after` | text | yes | up to 40 characters | | `before` | text | yes | up to 40 characters | | `query` | text | no | up to 200 characters | | `limit` | whole number | no | 1–50, default 20 | Returns: ```ts { id: string; title: string; start: string; end: string; guests: string[]; url: string; }[] ``` ## hold `calendar.hold` · Hold time on the owner's own calendar; invites nobody. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `title` | text | yes | 1–200 characters | | `start` | text | yes | up to 40 characters | | `end` | text | yes | up to 40 characters | | `note` | text | no | up to 2,000 characters | Returns: ```ts { id: string; url: string; } ``` --- # Books Source: https://docs.roadstar.app/tools/books.md Invoices and what customers owe, read live from the owner's QuickBooks. 2 tools. The owner's QuickBooks, read live as them: invoices, what each customer owes, and what was paid. Nothing here changes QuickBooks. Chasing a payment is an email: find the invoice here, then write with [mail](/tools/mail/). A routine can wake an agent when an invoice falls overdue, with the key `kind:invoice.overdue`. See [routines and skills](/how-it-works/routines-and-skills/). ## How to use it What `find_tools` sends with these tools, word for word: > The owner's own QuickBooks: invoices, what each customer owes and what was paid, read live as them. Amounts are in the invoice's currency. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [books.invoices](#invoices) | Invoices, newest first, with customer, email, total, balance and due date. status is "open" (money owed), "overdue" (owed past its due date) or "paid"; customer matches part of a name | No | | [books.invoice](#invoice) | One invoice's lines, amounts, tax, due date, customer email, whether it was emailed, and its link | No | ## invoices `books.invoices` · Invoices, newest first, with customer, email, total, balance and due date. status is "open" (money owed), "overdue" (owed past its due date) or "paid"; customer matches part of a name. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `status` | one of `open`, `overdue`, `paid` | no | | | `customer` | text | no | 1–200 characters | | `limit` | whole number | no | 1–50, default 20 | Returns: ```ts { invoices: { url: string; daysLate?: number; id: string; number: string | null; customer: string | null; email: string | null; date: string | null; due: string | null; total: number; balance: number; currency: string | null; status: string; }[]; more: boolean; } ``` ## invoice `books.invoice` · One invoice's lines, amounts, tax, due date, customer email, whether it was emailed, and its link. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | up to 30 characters, matches `^\d+$` | Returns: ```ts { tax: number; memo: string | null; emailed: boolean; lines: { unitPrice?: number; quantity?: number; item?: string; description: string; amount: number; }[]; url: string; daysLate?: number; id: string; number: string | null; customer: string | null; email: string | null; date: string | null; due: string | null; total: number; balance: number; currency: string | null; status: string; } ``` --- # Workspace Source: https://docs.roadstar.app/tools/workspace.md Your own folder on a small computer with Python, Node and a shell. 5 tools. A small computer the owner's agents share, where files stay between runs. Each agent has its own folder. Use it for spreadsheets, Office files, charts and anything that needs code. - Files come in with `open`: Drive files and email attachments land in `/work/inbox`, Google files as .xlsx, .docx and .pptx. - Code runs with `bash`, such as `python report.py`. It has no internet. - Files go out through other tools: `files.save` puts one in Drive, and mail's `attach` takes `{ "workspace": "" }`. - `/work/shared` is for what you leave another agent. Code can call your other tools, with the same checks: `from roadstar import mail` in Python. A call the check holds back returns `not_done_yet` or `refused` and the code carries on: ask the owner once the run is done. Nothing here touches the owner's own things, so these tools need no yes. ## How to use it What `find_tools` sends with these tools, word for word: > A small computer the owner's agents share, where files stay between runs: Python 3 with pandas, numpy, openpyxl, XlsxWriter, python-pptx, python-docx, pypdf and matplotlib, Node and a shell, and no internet. Runs start in your own folder; /work/inbox holds files opened from Drive and mail, and /work/shared is for what you leave another agent. Edit Office files here with code. Files leave through your other tools, which take a workspace path: files.save puts one in Drive, and mail's attach takes {workspace: path}. Code calls your other tools as `group.action(...)`, with the same input and checks: in Python, `from roadstar import mail`; in JavaScript, in a script in your folder, `import { mail } from "roadstar"` and await each call. A call the check holds back returns {not_done_yet} or {refused} and the code carries on; ask the owner in your reply once the run is done. A refused call raises ToolError. Write scripts to files and run them, such as `python report.py`, and write results to files from code: the shell can't pipe or redirect node's or python's output. Only a run's output reaches you, cut to 20,000 characters, so print what you need. Formulas are saved, not worked out: Excel and Sheets do that on open. A run stops after 120 s or 300 tool calls. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [workspace.bash](#bash) | Run a shell command in your folder, such as `python report.py`: its output, exit code and the tools its code called | No | | [workspace.read](#read) | A text file's lines from offset (1 is the first), 2,000 at a time; nextOffset gives the rest. A binary file, such as .xlsx, gives its size: work on it with code | No | | [workspace.write](#write) | Write a text file, making its folders; replaces one that's there | No | | [workspace.edit](#edit) | Replace exact text in a text file; oldText must appear once | No | | [workspace.open](#open) | Copy Drive files and email attachments into /work/inbox. Google Sheets, Docs and Slides arrive as .xlsx, .docx and .pptx. Each file's path, and from: the Drive file it came from, for files.save's over | No | ## bash `workspace.bash` · Run a shell command in your folder, such as `python report.py`: its output, exit code and the tools its code called. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `command` | text | yes | 1–20,000 characters | Returns: ```ts { calls: Record; tookMs: number; usage: RunUsage; stopped?: string; exitCode: number | null; output: string; } ``` ## read `workspace.read` · A text file's lines from offset (1 is the first), 2,000 at a time; nextOffset gives the rest. A binary file, such as .xlsx, gives its size: work on it with code. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `path` | text | yes | 1–500 characters | | `offset` | whole number | no | 1 or more | | `limit` | whole number | no | 1–2000 | Returns: ```ts { path: string; text: string; totalLines: number; nextOffset: number | null; } | { path: string; binary: true; size: number; } ``` ## write `workspace.write` · Write a text file, making its folders; replaces one that's there. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `path` | text | yes | 1–500 characters | | `content` | text | yes | up to 2,000,000 characters | Returns: ```ts { path: string; size: number; } ``` ## edit `workspace.edit` · Replace exact text in a text file; oldText must appear once. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `path` | text | yes | 1–500 characters | | `oldText` | text | yes | 1–200,000 characters | | `newText` | text | yes | up to 200,000 characters | Returns: ```ts { path: string; size: number; } ``` ## open `workspace.open` · Copy Drive files and email attachments into /work/inbox. Google Sheets, Docs and Slides arrive as .xlsx, .docx and .pptx. Each file's path, and from: the Drive file it came from, for files.save's over. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `files` | list of text | no | Drive files: ids from search or the files tools, or links | up to 20 items, each 3–500 characters | | `attachments` | list of `{ email?, attachment }` | no | An attachment's id from search; for mail not in search, the thread's id from mail search and the id mail read listed | up to 20 items | | `attachments[].email` | text | no | | 3–500 characters | | `attachments[].attachment` | text | yes | | 3–300 characters | Returns: ```ts { opened: Record[]; } ``` ## Types Named types in the results above. ```ts type RunUsage = { cpuMs: number; peakBytes: number; tookMs: number; bytesWritten: number; } ``` --- # Templates Source: https://docs.roadstar.app/tools/templates.md Agents, teams, departments and skills someone already made, ready to add. 4 tools. A template is a setup someone already made: one agent, a team, a department, or one skill. Roadstar's own are there for every workspace, and the owner's workspace has the ones its people shared. Search them before making an agent. Agents never add or share a template themselves. They ask, and the owner says yes on Home: - **Add**: `propose_add` with the template's `id`, any answers to its questions you could find (say where, in `found`), and why it fits. The owner places it on their chart and adds it. - **Share**: `propose_share` with agents by name, or one agent's skill, when the owner asks to share them. The owner reviews what goes out first. What they return: - `search`: up to 8, best first, each with `id`, `name`, `kind`, `says`, `from` and what `matched`: `{ "id": "roadstar/chase-a-late-invoice", "name": "Chase a late invoice", "kind": "skill", "from": "Roadstar", "matched": "Name: Chase a late invoice" }`. - `read`: the whole template: its agents with their jobs, skills and routines, its `questions`, and `youHave`, the agents in it the owner already has by name. - `propose_add` and `propose_share`: `{ "asked": "…" }`. The ask waits on Home. See [Home and asks](/how-it-works/home-and-asks/) and [agents and the chart](/how-it-works/agents/). ## How to use it What `find_tools` sends with these tools, word for word: > Setups someone already made: an agent, a team, a department or a skill. Search before making an agent. To suggest one, find what answers you can, say where, and propose_add; the owner adds it. propose_share when the owner asks to share. Never add or share yourself. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [templates.search](#search) | Templates matching words, best first, each with what matched | No | | [templates.read](#read) | One template in full: its agents, skills, routines and questions | No | | [templates.propose_add](#propose_add) | Ask the owner to add one, with the answers you found and where | No | | [templates.propose_share](#propose_share) | Ask the owner to share some of their agents, or one skill, as a template | No | ## search `templates.search` · Templates matching words, best first, each with what matched. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `query` | text | yes | 1–120 characters | Returns: ```ts { id: string; name: string; kind: TemplateKind; says: string; from: string; matched: string; }[] ``` ## read `templates.read` · One template in full: its agents, skills, routines and questions. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 1–120 characters | Returns: ```ts { youHave: string[]; questions: { blank: string; asks: string; kind: FillKind; }[]; skill?: { name: string; when: string; steps: string; }; agents: { skills: string[]; routines: string[]; under?: string; name: string; job: string; }[]; department?: string; id: string; name: string; kind: TemplateKind; says: string; version: number; } ``` ## propose_add `templates.propose_add` · Ask the owner to add one, with the answers you found and where. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `id` | text | yes | | 1–120 characters | | `answers` | named values of object | no | | | | `under` | text | no | One of the owner's agents its top agents report to | up to 60 characters | | `to` | text | no | For a skill: the agent that gets it | up to 60 characters | | `why` | text | yes | Why it fits, in a line or two the owner reads | 1–400 characters | Returns: ```ts { asked: string; } ``` ## propose_share `templates.propose_share` · Ask the owner to share some of their agents, or one skill, as a template. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `agents` | list of text | no | The agents by name | up to 16 items, each 1–60 characters | | `skill` | object | no | | | | `skill.agent` | text | yes | | up to 60 characters | | `skill.name` | text | yes | | up to 60 characters | | `why` | text | yes | | 1–400 characters | Returns: ```ts { asked: string; } ``` ## Types Named types in the results above. ```ts type TemplateKind = "agent" | "skill" | "team" | "department" type FillKind = "file" | "agent" | "folder" | "text" | "company" | "choice" | "web" | "contact" ``` --- # Web Source: https://docs.roadstar.app/tools/web.md Search the open web and read pages. 2 tools. The open web, for what the business's own records don't hold: a new client's site, a supplier's prices, a council's opening hours. Say which page each fact came from. `search` takes an objective in a sentence and up to four short queries. `read` returns the text of up to five pages, or with an objective only the passages that bear on it. ## How to use it What `find_tools` sends with these tools, word for word: > The open web. Results carry their links: say which page a fact came from. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [web.search](#search) | Pages that answer the objective, each with its link and the passages that matter | No | | [web.read](#read) | The text of up to 5 pages; with an objective, only the passages that bear on it | No | ## search `web.search` · Pages that answer the objective, each with its link and the passages that matter. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `objective` | text | yes | 1–500 characters | | `queries` | list of text | yes | 1–4 items, each 1–120 characters | | `limit` | whole number | no | 1–10 | Returns: ```ts { results: { title: string | null; url: string; published: string | null; excerpts: string[]; }[]; } ``` ## read `web.read` · The text of up to 5 pages; with an objective, only the passages that bear on it. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `urls` | list of link | yes | 1–5 items | | `objective` | text | no | 1–500 characters | Returns: ```ts { pages: { title: string | null; url: string; text: string; }[]; failed: { url: string; error: string; }[]; } ``` --- # Connected apps Source: https://docs.roadstar.app/tools/integrations.md The apps the owner has connected or could connect, and a card that asks them to connect one. 2 tools. Beyond mail, Drive, Calendar and QuickBooks, the owner can connect other apps. Each connected app adds a group of its own, named after the app, with that app's tools. They don't have pages here: browse them with `browse_tools`, as any group. `list` shows what is connected, what could be, and what isn't ready yet. When work needs an app that isn't connected, `connect` puts a card in front of the owner. Say in a line what it will let you do, and carry on with the rest of the work. ## How to use it What `find_tools` sends with these tools, word for word: > The owner's connected apps and the ones they could connect. When work needs an app that isn't connected, offer the card with connect, saying in a line what it lets you do. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [integrations.list](#list) | The apps connected now, the ones the owner could connect, and those not ready yet | No | | [integrations.connect](#connect) | Offer the owner a card that connects an app, by its key from list | No | ## list `integrations.list` · The apps connected now, the ones the owner could connect, and those not ready yet. **Owner's yes:** No. Nothing of the owner's changes. No input. Returns: ```ts { apps: { name: string; app: string; tools: number; }[]; connected: { status?: "needs_reconnect" | "removing" | "removed"; integration: string; account: string | null; }[]; canConnect: { integration: string; app: string; }[]; notYet: string[]; } ``` ## connect `integrations.connect` · Offer the owner a card that connects an app, by its key from list. **Owner's yes:** No. It makes something new.\ **Your work:** not listed on Home; the change shows on the record itself. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `integration` | text | yes | | 1–100 characters | | `why` | text | no | One line to the owner | up to 200 characters | Returns: ```ts { error: string; message: string; notYet?: never; card?: never; } | { notYet: string; error?: never; message?: never; card?: never; } | { card: string; error?: never; message?: never; notYet?: never; } ``` --- # Search Source: https://docs.roadstar.app/tools/search.md One search over the copy of the owner's mail, files and meetings, and the workspace's own docs, tables and contacts. 2 tools, where search is on. Where the workspace's owners turn search on, Roadstar keeps a copy of the accounts they choose, and agents get this group. Search is literal and fast, costs the owner's accounts nothing, and gives exact totals and counts, such as who the owner writes to most. Its how-to lists, for the agent reading it, which accounts are in search and since when. The one below is for a workspace with none, so it names only the workspace's own content. Search several times with different words, then `read` what you'll rely on. Mail and files keep their own live search for what the copy doesn't hold yet. ## How to use it What `find_tools` sends with these tools, word for word: > Search and read the owner's mail, files and meetings as copied for Roadstar, and the workspace's own content. In search: the workspace's docs, tables, contacts and projects. Search is literal and fast and costs the owner's accounts nothing: search several times, with words in query and people, dates, folders, labels, sent or received as fields; identifiers as written. Totals and counts are exact over every match: counts.people shows who the owner writes with most. Results are one per thread; thread: its id lists every message. Read before you answer. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [search.search](#search) | One list with the exact total, how names and dates were understood, counts by type, source, year, direction and sender, each source's state, and next for the following page. Results with related: true matched by meaning, not by the words | No | | [search.read](#read) | A result in full, 30,000 characters at a time: a message with sender, recipients, date and its attachments' ids; a whole thread by an id gmail:{account}:t:{thread}; an attachment's or file's text; a meeting. With next, read again from that offset | No | ## search `search.search` · One list with the exact total, how names and dates were understood, counts by type, source, year, direction and sender, each source's state, and next for the following page. Results with related: true matched by meaning, not by the words. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `query` | text | no | Words; "PO 4471", an invoice number or an email address match exactly | up to 300 characters | | `from` | list of text | no | Sender, author or organizer: names, addresses or a company | up to 10 items, each 1–200 characters | | `to` | list of text | no | To, cc or guests | up to 10 items, each 1–200 characters | | `with` | list of text | no | Anyone on it, or everyone at a company | up to 10 items, each 1–200 characters | | `after` | text | no | "2026-09-01" or "last week" | up to 40 characters | | `before` | text | no | | up to 40 characters | | `type` | list of one of `email`, `doc`, `sheet`, `slides`, `pdf`, `image`, `file`, `meeting`, `page`, `table`, `contact`, `project` | no | | up to 8 items | | `in` | list of text | no | A folder ("Clients › Juniper Dental") or a mail label ("label:Clients", "label:STARRED") | up to 5 items, each 1–200 characters | | `has` | list of one of `attachment`, `star` | no | | up to 2 items | | `dir` | one of `sent`, `received` | no | Mail the owner sent, or mail they received | | | `bulk` | true or false | no | false: mail from people only; true: newsletters and automated mail only | | | `waiting` | one of `me`, `them` | no | Threads where a person wrote last and the owner hasn't answered (me), or the reverse | | | `thread` | text | no | Every message of one thread, by its thread id | up to 200 characters | | `sort` | one of `relevance`, `newest`, `oldest` | no | | | | `limit` | whole number | no | | 1–50, default 10 | | `cursor` | text | no | | up to 400 characters | Returns: ```ts { hint?: string; results: { addedBy?: string; related?: boolean; text?: string; moreInThread?: number; thread?: string; money?: string[]; attachments?: string; where: string; match: string; dir?: string; to?: string; from?: string; id: string; type: string; title: string; date: string; }[]; counts: { type: Record; source: Record; year: Record; dir: Record; people: Record; }; sources: Record; next: string | null; understood?: Record; words?: string; related?: number; total: string | number; } ``` ## read `search.read` · A result in full, 30,000 characters at a time: a message with sender, recipients, date and its attachments' ids; a whole thread by an id gmail:{account}:t:{thread}; an attachment's or file's text; a meeting. With next, read again from that offset. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `id` | text | yes | 3–500 characters | | `offset` | whole number | no | 0 or more | Returns: ```ts ItemRead ``` ## Types Named types in the results above. ```ts type ItemRead = { title: string; url: string; text: string; source: string; nextOffset?: number; attachments?: { id: string; name: string; quality: string; }[]; } ``` --- # Help Source: https://docs.roadstar.app/tools/help.md These docs, from inside Roadstar. 2 tools. These docs, read from the docs site in the same environment. Use them when you aren't sure how something works: what a tool returns, why the check held an action back, how a routine fires. - `search` finds pages and parts of pages with the most of your words. Search with words you'd expect on the page, such as a tool's name: `mail.send`, `when.ask`, `not_done_yet`. - `read` returns a page as markdown, by its address. Add `#part` to get only that part, such as `/tools/mail/#send`. Outside Roadstar, an agent starts from [/llms.txt](/llms.txt), and every page is at its address with `.md` added. ## How to use it What `find_tools` sends with these tools, word for word: > Roadstar's docs, as markdown: how agents, threads, hand-offs, routines and the owner's yes work, every tool with its input, result and limits, and worked examples. Search with the words you'd expect on the page, such as a tool's name; then read the page, or just the part you need. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [help.search](#search) | The pages and parts of pages with the most of these words, best first, each with its address and opening lines | No | | [help.read](#read) | One page as markdown, by its address from search or a link, such as "/tools/mail/"; with #part, only that part, such as "/tools/mail/#send" | No | ## search `help.search` · The pages and parts of pages with the most of these words, best first, each with its address and opening lines. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `query` | text | yes | 1–200 characters | | `limit` | whole number | no | 1–20 | Returns: ```ts { total: number; results: { page: string; title: string; opens: string; }[]; } ``` ## read `help.read` · One page as markdown, by its address from search or a link, such as "/tools/mail/"; with #part, only that part, such as "/tools/mail/#send". **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `page` | text | yes | 1–300 characters | Returns: ```ts { page: string; title: string; text: string; } ``` --- # browse, find, use Source: https://docs.roadstar.app/tools/catalog.md browse_tools, find_tools and use_tool, how a thread reaches every tool group. In a thread, every tool is in a group, and three tools reach them. The groups, one line each, are in `browse_tools`'s own description, so you always see them. 1. `browse_tools` with up to 3 group names returns each group's tools and what each does: `{ "mail": { "mail.search": "…", "mail.send": "…" } }`. More than 3 come back as `not_shown`; a wrong name as `no_such_group`, with the groups there are. 2. `find_tools` with up to 8 tool names returns `found`, each with its name, what it does and its input, and `howTo`: each group's how-to, once. A wrong name comes back in `no_such_tool`, with that group's tools. 3. `use_tool` runs one tool by name with its input, and returns the tool's result as JSON. `use_tool` can also return: - `{ "error": "", "message": "…", "card": "…" }` when the tool failed. See [errors](/reference/errors/). - `{ "not_done_yet": "…" }` or `{ "refused": "…" }` when the check held it back, and nothing ran. See [the owner's yes](/how-it-works/the-owners-yes/). - `{ "done": true }` when the tool returns nothing. Load tools once per thread; they stay loaded. ## Where each is offered | Tool | Roadstar's chat | An agent's chat | A thread | A Mac or outside app | | --- | --- | --- | --- | --- | | [browse_tools](#browse_tools) | — | — | Yes | Yes | | [find_tools](#find_tools) | — | — | Yes | Yes | | [use_tool](#use_tool) | — | — | Yes | Yes | ## browse_tools What the agent reads, word for word: > Every tool you have is in one of these groups. Browse up to 3 to see their tools, then load the ones you need with find_tools. > - contacts: the people the business deals with (11) > - companies: the businesses the owner deals with: clients, suppliers, their conversations (11) > - web: search the web and read pages (2) > - pages: research pages (6) > - comments: comments on pages (2) > - projects: projects that hold docs, pages and tables (4) > - tables: research tables (6) > - filings: public companies' filings (2) > - newsfeed: today's news (1) > - docs: the business's docs: proposals, briefs, checklists (5) > - files: the owner's Drive: files, Google Docs and Sheets (15) > - mail: the owner's Gmail (4) > - calendar: the owner's calendar: meetings and time held (2) > - books: invoices and what customers owe, from the owner's accounting app (2) > - workspace: your own folder: files, spreadsheets and code (5) > - templates: agents, teams, departments and skills shared in the workspace (4) > - help: these docs: how Roadstar works, and every tool (2) > - search: Search (2) > - integrations: the apps the owner has connected or could connect (2) | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `groups` | list of text | yes | Group names from the list | | ## find_tools What the agent reads, word for word: > Load tools by name, as browse_tools lists them, such as "mail.send": each with its input and how to use its group well. Up to 8 at once. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `names` | list of text | yes | Tool names, such as "contacts.update" | | ## use_tool What the agent reads, word for word: > Run one tool by its name, with its input. One that sends outside the business, deletes, or changes what's there is checked first. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `name` | text | yes | The tool's name, such as "mail.send" | | | `input` | object | no | | | --- # Team Source: https://docs.roadstar.app/tools/team.md hand_off, remember, make_agent and change_agent, for working with the owner's other agents. Tools for the owner's other agents. Any agent can hand work to any other, by name. Only Roadstar's chat makes and changes agents. What they return: - `hand_off`: `{ "handed": "Proposal writer", "answer": "comes back to you" }`. With `answer: "whoever asked me"`, the answer goes straight to whoever asked you, and your part is done. - `remember`: the agent's notes as they now stand, `{ "notes": ["…"] }`. Only Roadstar has `about`, and saving it alone returns `{ "about": "saved" }`. - `make_agent`: `{ "made": "Proposal writer", "next": "Hand it the work with hand_off." }`. - `change_agent`: `{ "changed": "Proposal writer" }`. A change goes past the check first. See [hand-offs](/how-it-works/hand-offs/), [agents and the chart](/how-it-works/agents/) and [making an agent](/use-cases/make-an-agent/). ## Where each is offered | Tool | Roadstar's chat | An agent's chat | A thread | A Mac or outside app | | --- | --- | --- | --- | --- | | [hand_off](#hand_off) | Yes | Yes | Yes | Yes | | [remember](#remember) | Yes | Yes | Yes | Yes | | [make_agent](#make_agent) | Yes | — | — | — | | [change_agent](#change_agent) | Yes | — | — | — | ## hand_off What the agent reads, word for word: > Give work to another of the owner's agents, by name: the task, notes and links to what it needs. It hasn't seen your chat. Its answer comes back to you; don't wait for it. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `agent` | text | yes | The agent, by name | | | `task` | text | yes | What to do, in a sentence or two | | | `notes` | text | no | Facts it needs that no link holds: names, dates, choices | | | `links` | list of text | no | What to read, as mail:, file:, contact:, doc: or web: with the id or address a tool gave you | | | `answer` | one of `me`, `whoever asked me` | no | You by default; whoever asked you, when you pass their work on whole | | ## remember What the agent reads, word for word: > Keep a short note on how the owner likes your work done, when they say remember or agree to one. Your threads read it too. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `note` | text | no | | | | `forget` | text | no | A note to drop, word for word | | | `about` | text | no | What every agent should know about the owner and the business (what it does, its site, customers, prices, voice), given whole, rewritten with what you learned | | ## make_agent What the agent reads, word for word: > Make one agent for a lasting job, from a short job description. Then hand it the work that led to it with hand_off. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `name` | text | yes | | | | `job` | text | yes | Its job in one line | | | `owns` | text | yes | What it owns, in a line or two | | | `good` | text | yes | What good looks like | | | `works` | text | yes | When it works: as mail arrives, Fridays at 4 PM | | | `asksFirst` | text | yes | What it asks the owner before doing | | | `model` | one of `gpt-6-luna`, `gpt-6.1-terra`, `gpt-6.1-sol`, `gpt-6-astra` | no | Leave it out for the default. Luna is cheapest, for simple repeated work; Sol and Astra cost 2× and 10×, for harder judgement | | | `reportsTo` | text | no | Who it sits under on the chart, by name; Roadstar if none | | | `department` | text | no | Its department on the chart | | ## change_agent What the agent reads, word for word: > Change an agent: its name, job, description, model, or where it sits on the chart. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `agent` | text | yes | The agent, by name | | | `name` | text | no | | | | `job` | text | no | | | | `owns` | text | no | What it owns, in a line or two | | | `good` | text | no | What good looks like | | | `works` | text | no | When it works: as mail arrives, Fridays at 4 PM | | | `asksFirst` | text | no | What it asks the owner before doing | | | `model` | one of `gpt-6-luna`, `gpt-6.1-terra`, `gpt-6.1-sol`, `gpt-6-astra` | no | Leave it out for the default. Luna is cheapest, for simple repeated work; Sol and Astra cost 2× and 10×, for harder judgement | | | `reportsTo` | text | no | Who it sits under, by name | | | `department` | text | no | | | --- # Threads Source: https://docs.roadstar.app/tools/threads.md start_thread, message_thread, read_thread and close_thread, how a chat sends work to its threads. A chat's tools for its threads. Threads can't start threads. What they return: - `start_thread`: `{ "started": "Juniper Dental", "kind": "fresh" }`. The thread starts at once; its answer comes back to the chat as a message. - `message_thread`: `{ "sent": "Juniper Dental" }`. - `read_thread`: `{ "thread": "Juniper Dental", "memory": "…" }`, the thread's memory from the top, or with `id` one line or message of it. - `close_thread`: `{ "closed": "Juniper Dental" }`. See [chats and threads](/how-it-works/chats-and-threads/). ## Where each is offered | Tool | Roadstar's chat | An agent's chat | A thread | A Mac or outside app | | --- | --- | --- | --- | --- | | [start_thread](#start_thread) | Yes | Yes | — | — | | [message_thread](#message_thread) | Yes | Yes | — | — | | [read_thread](#read_thread) | Yes | Yes | — | — | | [close_thread](#close_thread) | Yes | Yes | — | — | ## start_thread What the agent reads, word for word: > Start a thread for work no thread holds: one per client, project or job. Fresh starts empty with the packet; fork starts from this chat's memory. Its answer comes back on its own. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `title` | text | yes | What it's about, in a few words: Juniper Dental | | | `kind` | one of `fresh`, `fork` | no | fresh unless the work needs this conversation | | | `task` | text | yes | What to do, in a sentence or two | | | `notes` | text | no | Facts it needs that no link holds: names, dates, choices | | | `links` | list of text | no | What to read, as mail:, file:, contact:, doc: or web: with the id or address a tool gave you | | | `andThen` | text | no | When you start several threads and write one answer from all of them: what to do once the last answers | | ## message_thread What the agent reads, word for word: > Send a thread more work or a correction. A running thread takes it at its next step; an idle or stopped one starts on it, with all it did before. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `thread` | text | yes | The thread, by title | | | `task` | text | yes | What to do, in a sentence or two | | | `notes` | text | no | Facts it needs that no link holds: names, dates, choices | | | `links` | list of text | no | What to read, as mail:, file:, contact:, doc: or web: with the id or address a tool gave you | | ## read_thread What the agent reads, word for word: > Read what a thread did: its memory from the top, or with id a line or message of it, down to the exact words. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `thread` | text | yes | The thread, by title | | | `id` | whole number | no | A line's or message's id | 0 or more | | `n` | whole number | no | The line's n; 1 for a message | 1 or more | | `page` | whole number | no | Where in a long message | 0 or more | ## close_thread What the agent reads, word for word: > Close a thread whose work is over: a finished project or a one-off job. It stays readable, and a message reopens it. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `thread` | text | yes | The thread, by title | | --- # Routines Source: https://docs.roadstar.app/tools/routines.md triggers and skills, for work an agent does on its own and how it does one kind of work. `triggers` sets when an agent works on its own; `skills` keeps its step-by-step how-tos. Only a chat sets routines; threads have `skills` too. What they return: - `triggers` with `add`: `{ "saved": 12, "when": "Words: “a new client asking about a project or a quote”" }`. With `list`, each routine with its `id`, `name`, `note`, `when`, `status` and how often it `fired`. With `change`, `off` or `on`: `{ "changed": 12 }`, `{ "turnedOff": 12 }`, `{ "turnedOn": 12 }`. A change goes past the check first. - `skills` with `list`: each skill's `id`, `name` and `about`. With `read`, the whole skill. With `add` or `change`: `{ "saved": { "agent": "Bookkeeper", "id": "chase-an-invoice", "name": "Chase an invoice" } }`. See [routines and skills](/how-it-works/routines-and-skills/). ## Where each is offered | Tool | Roadstar's chat | An agent's chat | A thread | A Mac or outside app | | --- | --- | --- | --- | --- | | [triggers](#triggers) | Yes | Yes | — | — | | [skills](#skills) | Yes | Yes | Yes | Yes | ## triggers What the agent reads, word for word: > When an agent works on its own: on a schedule, or when something happens, such as new mail. One that keeps going is a routine. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `action` | one of `list`, `add`, `off`, `on`, `change` | yes | | | | `agent` | text | no | The agent, by name; you by default | | | `id` | number | no | The routine's number, from list | | | `name` | text | no | | | | `skill` | text | no | One of the agent's skills to run | | | `note` | text | no | What to do when it fires | | | `when` | object | no | When it fires: keys, words, a cron time, a due time, or a check | | | `when.keys` | list of text | no | Any of these fires it: kind: (kind:invoice.overdue, kind:meeting.added, kind:doc.changed, kind:form.submitted), thread::, from:, domain:, with:, doc::, invoice:qb:, customer:qb: | | | `when.ask` | text | no | Words checked against each new email and document of the owner's, such as "a new client asking about a project" | | | `when.at` | text | no | A cron line in the owner's time zone: "30 7 * * 1-5" is weekdays 7:30 | | | `when.due` | text | no | A one-off time, ISO 8601, such as a wait's deadline | | | `when.check` | object | no | | | | `when.check.tool` | text | yes | | | | `when.check.action` | text | yes | | | | `when.check.input` | object | no | | | | `when.check.everyMinutes` | number | yes | | 15 or more | | `filter` | object | no | Checks on the event, such as {withGuests: true} | | | `once` | true or false | no | Fires once, such as a nudge on Monday if a thread has no reply | | ## skills What the agent reads, word for word: > List, read, add or change an agent's skills: step-by-step how-tos for one kind of work, such as "Onboard a client". A skill never widens what the agent may do. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `action` | one of `list`, `read`, `add`, `change` | yes | | | | `agent` | text | no | The agent, by name; you by default | | | `id` | text | no | The skill, from list | | | `name` | text | no | | | | `about` | text | no | When it applies, in one line | | | `steps` | text | no | | | | `why` | text | no | For a change: why, in a line the owner reads | | --- # Memory Source: https://docs.roadstar.app/tools/memory.md zoom_memory and message_date, for reading back further in an agent's own memory. An agent's memory is its conversation, folded into lines as it grows: each line sums up the messages it covers. `zoom_memory` opens a line into the two it was made from, or reads one message whole. `message_date` gives when a message was sent. A Mac program or outside app keeps its own conversation, so it doesn't have these. See [notes, About and memory](/how-it-works/notes-and-memory/). ## Where each is offered | Tool | Roadstar's chat | An agent's chat | A thread | A Mac or outside app | | --- | --- | --- | --- | --- | | [zoom_memory](#zoom_memory) | Yes | Yes | Yes | — | | [message_date](#message_date) | Yes | Yes | Yes | — | ## zoom_memory What the agent reads, word for word: > Open line id+n of your memory into the two lines it was made from (n a power of 2, id a multiple of n), or with n = 1 read message id whole; a long one a page at a time from at. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `id` | whole number | yes | The line's first message | | | `n` | whole number | no | How many messages the line covers; 1 for a message | | | `at` | whole number | no | Where to go on in a long message | | ## message_date What the agent reads, word for word: > The date and time of message id. | Input | Type | Required | What it is | Limits | | --- | --- | --- | --- | --- | | `id` | whole number | yes | The message's id | | --- # A new client enquiry Source: https://docs.roadstar.app/use-cases/new-client-enquiry.md Client desk answers Juniper Dental's request for a quote, Proposal writer prices it from the rate card, and Sam approves it before it goes. Fernhill Studio is Sam Rivera's design studio. Besides Roadstar, Sam has Client desk, Proposal writer and Bookkeeper. ## What Sam said In Client desk's chat: > When a new client writes in, answer the same day. Quotes come from the rate card in Drive, and I see every quote before it goes out. Client desk's chat set a routine from those words. The routine's note is Sam's rule, so every run carries it: ```json title="triggers" { "action": "add", "name": "New client enquiries", "when": { "ask": "a new client asking about a project or a quote" }, "note": "Answer the same day. Quotes come from the rate card in Drive, and Sam sees every quote before it goes out." } ``` ## 1. The routine fires On Tuesday at 10:12, Dana Whitfield of Juniper Dental emails Sam asking for a quote on a new logo and signage before their second clinic opens in March. The email matches the routine's words, and the run goes to the routine's own thread, "New client enquiries". The thread has every tool, and remembers the enquiries before this one. ## 2. The thread reads the email and records the client ```json title="use_tool" { "name": "mail.read", "input": { "id": "7c41e2d0:19a2c4e07d5b3f18" } } ``` ```json title="use_tool" { "name": "contacts.find", "input": { "email": "dana@juniperdental.test" } } ``` `contacts.find` returns `null`: Juniper Dental is new. Adding records needs no yes, so the thread adds the company, then Dana, then the conversation: ```json title="use_tool" { "name": "companies.create", "input": { "name": "Juniper Dental", "domains": ["juniperdental.test"], "labels": ["Lead"] } } ``` ```json title="use_tool" { "name": "contacts.create", "input": { "name": "Dana Whitfield", "emails": ["dana@juniperdental.test"], "jobTitle": "Practice manager", "labels": ["Lead"], "companyIds": ["3f1c9a52-7b0e-4d61-9a8f-2c5e1b7d4a90"] } } ``` ```json title="use_tool" { "name": "companies.record", "input": { "companyId": "3f1c9a52-7b0e-4d61-9a8f-2c5e1b7d4a90", "kind": "mail", "title": "Rebrand quote request", "summary": "Dana asked for a new logo and signage before the second clinic opens in March.", "at": "2026-11-10T10:12:00Z", "people": ["dana@juniperdental.test"], "thread": "7c41e2d0:19a2c4e07d5b3f18", "state": "waiting_you" } } ``` ## 3. It hands the pricing to Proposal writer Proposal writer owns quotes. It hasn't seen this thread, so the packet carries the task, the facts no link holds, and links to what to read: ```json title="hand_off" { "agent": "Proposal writer", "task": "Price a rebrand for Juniper Dental from the rate card: new logo, and signage for two clinics.", "notes": "Second clinic opens in March. Dana Whitfield, practice manager, signs off.", "links": ["mail:7c41e2d0:19a2c4e07d5b3f18", "contact:8d2e6b14-3a7c-4f09-b5d1-6e0a9c3f7b22"] } ``` `hand_off` returns `{ "handed": "Proposal writer", "answer": "comes back to you" }`. The thread ends its turn; the answer will wake it. ## 4. Proposal writer writes the quote The packet reaches Proposal writer's chat. No thread holds Juniper Dental yet, so the chat starts one: ```json title="start_thread" { "title": "Juniper Dental", "task": "Price a rebrand for Juniper Dental from the rate card: new logo, and signage for two clinics.", "notes": "Second clinic opens in March. Dana Whitfield, practice manager, signs off.", "links": ["mail:7c41e2d0:19a2c4e07d5b3f18"] } ``` The thread finds the rate card and writes the quote as a doc: ```json title="use_tool" { "name": "files.search", "input": { "query": "rate card 2026" } } ``` ```json title="use_tool" { "name": "files.read", "input": { "id": "7c41e2d0:1QmR8vT3kLp0aZ7xYc2NwE5sHd6jUb4oF" } } ``` ```json title="use_tool" { "name": "docs.write", "input": { "projectId": "c7a04e2b-91d5-4b38-8f6e-0d2a5c9b1e47", "title": "Juniper Dental rebrand quote", "text": "## Scope\n\n- Logo and a one-page brand guide\n- Signage for two clinics: exterior, reception, rooms\n\n## Price\n\n| Item | Price |\n| --- | ---: |\n| Logo and brand guide | $4,800 |\n| Signage design, two clinics | $3,600 |\n| **Total** | **$8,400** |\n\nReady in five weeks from a signed quote.", "lines": ["Priced from the 2026 rate card: logo $4,800, signage $3,600."] } } ``` Its answer, the doc and "$8,400, ready in five weeks", goes back to Client desk's "New client enquiries" thread. ## 5. Sam approves, and the thread sends Sam's rule says Sam sees every quote first, so the thread asks in its reply instead of sending: > Send Dana the Juniper Dental quote for $8,400? Logo, brand guide and signage for both clinics, ready in five weeks. Sam answers "Yes, send it" in Client desk's chat. The chat sends the answer to the thread that holds the work: ```json title="message_thread" { "thread": "New client enquiries", "task": "Sam says yes: send Dana the Juniper Dental quote as written." } ``` The thread sends: ```json title="use_tool" { "name": "mail.send", "input": { "to": ["dana@juniperdental.test"], "subject": "Re: Rebrand for our second clinic", "threadId": "7c41e2d0:19a2c4e07d5b3f18", "text": "Hi Dana,\n\nThanks for thinking of us. For a new logo with a one-page brand guide, and signage for both clinics, the price is $8,400, ready five weeks after you sign. Happy to walk through it on a call.\n\nSam" } } ``` `mail.send` sends outside the business, so the check reads it first. Sam's yes answered an ask that named Dana, so it goes. The email leaves after a two-minute Undo on Home. The thread follows Sam's rule itself; the check is a backstop. Had the thread sent before asking, the check would read Sam's rule too, nothing would go out, and `use_tool` would return the check's reason instead: ```json { "not_done_yet": "Check with Sam first: . Ask in your reply, saying what you would do. Once they say yes, do it again." } ``` The reasons it can give are on [The owner's yes](/how-it-works/the-owners-yes/#reasons). ## What Sam sees - Client desk's ask with the quote, then the sent email on Home with Undo for two minutes. - Juniper Dental under Companies, with Dana and the conversation on its timeline. - The quote under Docs. ## Related - [The owner's yes](/how-it-works/the-owners-yes/): when the check asks, and what the agent reads back - [Hand-offs](/how-it-works/hand-offs/): packets, and where answers go - [Routines and skills](/how-it-works/routines-and-skills/): `when.ask`, schedules and events - Tools: [mail](/tools/mail/), [contacts](/tools/contacts/), [companies](/tools/companies/), [docs](/tools/docs/), [files](/tools/files/) --- # Chasing overdue invoices Source: https://docs.roadstar.app/use-cases/overdue-invoices.md Bookkeeper's Monday routine reads QuickBooks, emails Ortiz Bakery about an overdue invoice, and sets a follow-up. ## What Sam said In Bookkeeper's chat: > Every Monday morning, chase anything more than a week overdue. Be friendly the first time. Anything over $5,000, check with me first. ## 1. Bookkeeper's chat saves a skill and a routine How to chase goes in a skill, so every run does it the same way: ```json title="skills" { "action": "add", "name": "Chase an invoice", "about": "An invoice more than a week overdue", "steps": "1. Read the invoice and the customer's contact, with their Context.\n2. Email the billing contact from the invoice: the number, amount, due date and the invoice link. Friendly the first time; firmer if we chased before.\n3. Record it on the company's timeline as an invoice conversation, state overdue.\n4. Set a follow-up on the contact for a week later.", "why": "Sam asked for a Monday chase of overdue invoices." } ``` Then the routine, at 9 every Monday in Sam's time zone: ```json title="triggers" { "action": "add", "name": "Overdue invoices", "when": { "at": "0 9 * * 1" }, "skill": "chase-an-invoice", "note": "Chase invoices more than 7 days overdue. Friendly the first time. Over $5,000, ask Sam first." } ``` `triggers` returns `{ "saved": 14, "when": "Time: 0 9 * * 1 (America/New_York)" }`. ## 2. Monday at 9, the run reads QuickBooks The run goes to the routine's thread, "Overdue invoices", with the skill's steps and the note. The thread loads `books.invoices` and lists what's overdue: ```json title="use_tool" { "name": "books.invoices", "input": { "status": "overdue" } } ``` One invoice is more than 7 days late: ```json { "invoices": [ { "id": "1042", "number": "1042", "customer": "Ortiz Bakery", "email": "rosa@ortizbakery.test", "due": "2026-10-31", "daysLate": 10, "total": 1250, "balance": 1250, "currency": "USD", "status": "overdue", "url": "https://app.qbo.intuit.com/app/invoice?txnId=1042" } ], "more": false } ``` ## 3. It reads the invoice and the contact ```json title="use_tool" { "name": "books.invoice", "input": { "id": "1042" } } ``` ```json title="use_tool" { "name": "contacts.find", "input": { "email": "rosa@ortizbakery.test" } } ``` Rosa Ortiz's Context says: "Pays by bank transfer at month end; a nudge is enough." Nothing has been chased before. ## 4. It emails Rosa $1,250 is under Sam's $5,000 line, and Sam's routine asks for exactly this, so the thread sends: ```json title="use_tool" { "name": "mail.send", "input": { "to": ["rosa@ortizbakery.test"], "subject": "Invoice 1042 from Fernhill Studio", "text": "Hi Rosa,\n\nA quick note that invoice 1042 for $1,250, the bakery menu boards, was due on October 31. You can see and pay it here: https://app.qbo.intuit.com/app/invoice?txnId=1042\n\nThanks so much,\nSam" } } ``` The check reads the routine's note as Sam's words: a chase to the invoice's own customer is what it asks for, so it goes. ## 5. It records the chase and sets a follow-up ```json title="use_tool" { "name": "companies.record", "input": { "companyId": "b51e7c08-2d3a-4f6b-9c1e-7a8d0f2e4b63", "kind": "invoice", "title": "Invoice 1042 chased", "summary": "$1,250 for the menu boards, 10 days overdue. Friendly reminder sent to Rosa.", "at": "2026-11-10T09:02:00-05:00", "people": ["rosa@ortizbakery.test"], "state": "overdue" } } ``` ```json title="use_tool" { "name": "contacts.follow_up", "input": { "contactId": "e2a9f4c1-6b07-4d38-a15e-93c0b7d2f846", "text": "Invoice 1042: paid? If not, a firmer note.", "at": "2026-11-17T09:00:00-05:00" } } ``` The follow-up wakes Bookkeeper next Monday if Rosa hasn't paid. ## 6. It answers The run's last reply is what Sam reads: > Chased one overdue invoice: Ortiz Bakery, $1,250, 10 days late. Friendly reminder sent to Rosa; I'll check again next Monday. On a Monday with nothing overdue, it says so in one line. ## When it's over $5,000 For an invoice over $5,000, the thread asks in its reply instead of sending, saying what it would send to whom. Sam answers in Bookkeeper's chat, and the chat passes the answer to the "Overdue invoices" thread with `message_thread`. ## Related - [Routines and skills](/how-it-works/routines-and-skills/) - Tools: [books](/tools/books/), [mail](/tools/mail/), [contacts](/tools/contacts/), [companies](/tools/companies/), [routines](/tools/routines/) --- # Making an agent Source: https://docs.roadstar.app/use-cases/make-an-agent.md Sam asks Roadstar for help with proposals, and Roadstar makes Proposal writer from a job description and hands it the first piece of work. ## What Sam said In Roadstar's chat: > I'm spending my evenings on proposals. Can someone take them on? Start with the Ortiz Bakery packaging one: Rosa wants it by Friday. ## 1. Roadstar decides it's a lasting job No agent's job covers proposals, and proposals will keep coming, so Roadstar makes an agent for the job. A one-off task would go to a thread instead. A job description has four parts, 600 characters together. It says what the agent owns and how it works, never this week's task: ```json title="make_agent" { "name": "Proposal writer", "job": "Prices and writes proposals and quotes from the rate card", "owns": "Every proposal and quote Fernhill Studio sends: scope, price and timeline.", "good": "Priced from the current rate card, in Sam's voice, one page where it can be, sent to Sam the same day it's asked for.", "works": "When a client asks for a price or another agent hands one over.", "asksFirst": "Anything off the rate card, discounts, and before any proposal goes to a client.", "reportsTo": "Client desk", "department": "Clients" } ``` `make_agent` returns: ```json { "made": "Proposal writer", "next": "Hand it the work with hand_off." } ``` The new agent shows on Home with Undo, and on the chart under Client desk. ## 2. Roadstar hands it the first work The request that led to the agent goes to it as its first work, with Sam's words: ```json title="hand_off" { "agent": "Proposal writer", "task": "Write the Ortiz Bakery packaging redesign proposal for Rosa Ortiz, due Friday.", "notes": "Sam's first proposal for you. Rosa wants it by Friday.", "links": ["contact:e2a9f4c1-6b07-4d38-a15e-93c0b7d2f846"] } ``` Roadstar's reply in its chat is short: > I've made Proposal writer, under Client desk, and handed it the Ortiz Bakery proposal for Friday. ## 3. A note for the new agent Later, in Proposal writer's chat, Sam says: "Remember, proposals come to me as Google Docs." Proposal writer keeps a note, which its chat and all its threads read from then on: ```json title="remember" { "note": "Sam wants proposals as Google Docs." } ``` ## What changes for the other agents Every agent's instructions list the others by name with their job, so Client desk and Bookkeeper can now hand proposals to Proposal writer. The chart (`reportsTo`, `department`) is for Sam to see agents by: nothing that runs an agent reads it. ## Related - [Agents and the chart](/how-it-works/agents/) - [Hand-offs](/how-it-works/hand-offs/) - [A proposal from the template](/use-cases/proposal-from-template/): Proposal writer's first proposal - Tools: [team](/tools/team/) --- # Filling Contacts from mail Source: https://docs.roadstar.app/use-cases/contacts-from-mail.md Client desk works out who Sam writes to most from sent mail, reads their signatures, and saves them as contacts. ## What Sam said In Client desk's chat: > Can you fill Contacts from my email? Just real people I work with, no newsletters. The chat starts a thread for it: ```json title="start_thread" { "title": "Contacts from mail", "task": "Fill Contacts with the people Sam writes to, from Sam's sent mail. Real people only: no newsletters or automated mail." } ``` ## 1. Who Sam writes to most Where search is on, one search over the mail Sam sent gives exact counts by person. `bulk: false` leaves out newsletters and automated mail: ```json title="use_tool" { "name": "search.search", "input": { "dir": "sent", "bulk": false, "after": "2026-05-01", "limit": 20 } } ``` Its `counts.people` lists who Sam wrote to, most first: Dana Whitfield, Rosa Ortiz, Marco Ruiz of Ruiz Print Co., and fourteen more. Without search, `mail.search` with Gmail's own words does the same job, a page at a time: ```json title="use_tool" { "name": "mail.search", "input": { "query": "in:sent -category:promotions", "after": "2026-05-01", "limit": 20 } } ``` ## 2. Names, roles and phones from their latest messages For each person, the thread reads their latest message to Sam, where signatures give job titles and phones: ```json title="use_tool" { "name": "search.read", "input": { "id": "gmail:7c41e2d0:t:19a2c4e07d5b3f18" } } ``` ## 3. Saving them, all at once Adding contacts needs no yes. Addresses already on a contact are reported, not added twice: ```json title="use_tool" { "name": "contacts.add_many", "input": { "people": [ { "name": "Dana Whitfield", "emails": ["dana@juniperdental.test"], "jobTitle": "Practice manager", "company": "Juniper Dental", "labels": ["Client"] }, { "name": "Rosa Ortiz", "emails": ["rosa@ortizbakery.test"], "jobTitle": "Owner", "phone": "+1 555 0142", "company": "Ortiz Bakery", "labels": ["Client"] }, { "name": "Marco Ruiz", "emails": ["marco@ruizprint.test"], "jobTitle": "Print manager", "company": "Ruiz Print Co.", "labels": ["Supplier"] } ] } } ``` `company` is a name: a company the workspace doesn't have yet is made. ## 4. It answers > Added 17 people to Contacts from your sent mail since May: 11 clients, 4 suppliers, 2 others. Dana Whitfield was already there. Every contact it added shows on Home with Undo. ## Related - [Contacts](/tools/contacts/), [Companies](/tools/companies/), [Search](/tools/search/), [Mail](/tools/mail/) --- # A proposal from the template Source: https://docs.roadstar.app/use-cases/proposal-from-template.md Proposal writer copies the proposal template in Drive, fills it in for Ortiz Bakery, and hands Sam the link. ## The work Roadstar handed Proposal writer its first piece of work (see [making an agent](/use-cases/make-an-agent/)): ```text [From Roadstar] Write the Ortiz Bakery packaging redesign proposal for Rosa Ortiz, due Friday. Sam said in Roadstar's chat at 4:05 PM: "I'm spending my evenings on proposals. Can someone take them on? Start with the Ortiz Bakery packaging one: Rosa wants it by Friday." Notes: Sam's first proposal for you. Rosa wants it by Friday. Links: contact:e2a9f4c1-6b07-4d38-a15e-93c0b7d2f846 ``` Proposal writer's chat starts a thread, "Ortiz Bakery", and passes the work on whole, so the thread's answer goes straight back to Roadstar. ## 1. Find the template and the rate card ```json title="use_tool" { "name": "files.search", "input": { "query": "proposal template" } } ``` The template is a Google Doc with placeholders: `{{CLIENT}}`, `{{CONTACT}}`, `{{SCOPE}}`, `{{PRICE}}`, `{{DATE}}`. ```json title="use_tool" { "name": "contacts.find", "input": { "email": "rosa@ortizbakery.test" } } ``` ## 2. Copy it Copying makes something new, so it needs no yes: ```json title="use_tool" { "name": "files.copy", "input": { "id": "7c41e2d0:1Hk3TemplatePr0posalDoc9xQ", "name": "Ortiz Bakery: packaging redesign proposal", "folderId": "7c41e2d0:1FoClientsOrtizBakery2026" } } ``` ```json { "id": "7c41e2d0:1Ab9OrtizProposalCopy7Kd", "name": "Ortiz Bakery: packaging redesign proposal", "url": "https://docs.google.com/document/d/1Ab9OrtizProposalCopy7Kd/edit" } ``` ## 3. Fill it in Each placeholder is one `replace_text` on the copy: ```json title="use_tool" { "name": "files.replace_text", "input": { "id": "7c41e2d0:1Ab9OrtizProposalCopy7Kd", "find": "{{CLIENT}}", "replace": "Ortiz Bakery" } } ``` ```json title="use_tool" { "name": "files.replace_text", "input": { "id": "7c41e2d0:1Ab9OrtizProposalCopy7Kd", "find": "{{SCOPE}}", "replace": "New packaging for six bread and pastry lines: box, bag and label designs, print-ready files, and a one-page packaging guide." } } ``` `replace_text` changes what's there, so the check reads it. It changes only the copy this thread made for this work, so it goes. Then a last read to check every placeholder is gone: ```json title="use_tool" { "name": "files.read", "input": { "id": "7c41e2d0:1Ab9OrtizProposalCopy7Kd" } } ``` ## 4. It answers The proposal goes to Rosa only after Sam has seen it, as Proposal writer's job description says. The answer goes back to Roadstar, and on to Sam: > The Ortiz Bakery packaging proposal is ready for you in Drive: six product lines, $6,200, four weeks. Shall I send it to Rosa? Each change shows on Home with Undo: the copy, and each replacement. ## Related - [Files](/tools/files/) - [Hand-offs](/how-it-works/hand-offs/): passing work on whole --- # A month of hours Source: https://docs.roadstar.app/use-cases/hours-spreadsheet.md Bookkeeper totals October's hours per client from the timesheet in the workspace, and saves the summary to Drive as a Google Sheet. ## What Sam said In Bookkeeper's chat: > Can you total October's hours per client from the timesheet? I need it for invoicing. The chat starts a thread, "October hours". ## 1. Bring the timesheet into the workspace ```json title="use_tool" { "name": "files.search", "input": { "query": "Timesheet 2026" } } ``` The timesheet is a Google Sheet. `open` copies it into `/work/inbox` as .xlsx: ```json title="use_tool" { "name": "workspace.open", "input": { "files": ["7c41e2d0:1TsTimesheet2026SheetX4"] } } ``` ```json { "opened": [{ "path": "/work/inbox/Timesheet 2026.xlsx", "from": "7c41e2d0:1TsTimesheet2026SheetX4" }] } ``` ## 2. Total it with code ```json title="use_tool" { "name": "workspace.write", "input": { "path": "october.py", "content": "import pandas as pd\n\nhours = pd.read_excel('/work/inbox/Timesheet 2026.xlsx', sheet_name='Log')\noctober = hours[(hours['Date'] >= '2026-10-01') & (hours['Date'] < '2026-11-01')]\nby_client = october.groupby('Client', as_index=False)['Hours'].sum().sort_values('Hours', ascending=False)\nby_client.to_excel('october-hours.xlsx', index=False)\nprint(by_client.to_string(index=False))\nprint('Total', by_client['Hours'].sum())\n" } } ``` ```json title="use_tool" { "name": "workspace.bash", "input": { "command": "python october.py" } } ``` The run's output: ```text Client Hours Juniper Dental 46.5 Ortiz Bakery 31.0 Harbor Florists 12.5 Total 90.0 ``` Code in the workspace has no internet and changes nothing of Sam's, so none of this needs a yes. ## 3. Save it to Drive `files.save` puts a workspace file in Drive. `asGoogle` turns the .xlsx into a Google Sheet: ```json title="use_tool" { "name": "files.save", "input": { "workspace": "october-hours.xlsx", "name": "October 2026 hours by client", "asGoogle": true, "folderId": "7c41e2d0:1FoBookkeeping2026" } } ``` A new file needs no yes. With `over`, the same call would write a new version of the Drive file it was opened from, and that is checked. ## 4. It answers > October: 90 hours across three clients. Juniper Dental 46.5, Ortiz Bakery 31, Harbor Florists 12.5. The sheet is in Bookkeeping 2026 in Drive. ## Related - [Workspace](/tools/workspace/), [Files](/tools/files/) - [Finding and using tools](/how-it-works/tools/#tools-from-code): calling tools from code --- # Booking a kickoff call Source: https://docs.roadstar.app/use-cases/kickoff-call.md Client desk finds a free hour in Sam's calendar, holds it, offers it to Dana at Juniper Dental, and sets a follow-up. ## What Sam said Dana signed the quote. In Client desk's chat: > Juniper signed! Book a kickoff call with Dana next week, an hour, mornings are best for me. The chat sends it to the thread that holds Juniper Dental, "New client enquiries", with `message_thread`. ## 1. Find a free morning ```json title="use_tool" { "name": "calendar.list", "input": { "after": "2026-11-16T00:00:00-05:00", "before": "2026-11-21T00:00:00-05:00" } } ``` Tuesday 10:00 to 11:00 and Thursday 9:00 to 10:00 are free. ## 2. Hold the time `hold` blocks time on Sam's own calendar and invites nobody, so it needs no yes: ```json title="use_tool" { "name": "calendar.hold", "input": { "title": "Juniper Dental kickoff (held)", "start": "2026-11-17T10:00:00-05:00", "end": "2026-11-17T11:00:00-05:00", "note": "Offered to Dana Whitfield. Thursday 9:00 is the backup." } } ``` ## 3. Offer it to Dana Sam asked for the call with Dana, so the email goes: ```json title="use_tool" { "name": "mail.send", "input": { "to": ["dana@juniperdental.test"], "subject": "Kickoff for the Juniper Dental rebrand", "threadId": "7c41e2d0:19a2c4e07d5b3f18", "text": "Hi Dana,\n\nThanks for signing! Could we do a one-hour kickoff on Tuesday, November 17 at 10:00 AM Eastern? If that doesn't suit, Thursday at 9:00 works too.\n\nSam" } } ``` ## 4. Follow up if she doesn't answer ```json title="use_tool" { "name": "contacts.follow_up", "input": { "contactId": "8d2e6b14-3a7c-4f09-b5d1-6e0a9c3f7b22", "text": "Kickoff: did Dana pick Tuesday or Thursday? Move the hold if needed.", "at": "2026-11-13T09:00:00-05:00" } } ``` The follow-up wakes Client desk on Friday morning if it's still open. ## 5. It answers > I've held Tuesday 10 to 11 for the Juniper kickoff and offered it to Dana, with Thursday 9 as a backup. I'll check Friday if she hasn't answered. ## Related - [Calendar](/tools/calendar/), [Mail](/tools/mail/), [Contacts](/tools/contacts/) --- # Limits Source: https://docs.roadstar.app/reference/limits.md Steps per turn, groups per browse, tools per find, open threads, packet sizes, hand-off depth and agents per owner. The numbers below are read from the code each time the docs are built. ## Limits | Limit | Value | Notes | | --- | --- | --- | | Steps in one chat turn | 4 | A chat sends work and answers; past this it stops. | | Steps on one piece of work in a thread | 40 | Past this the thread answers with what it has. | | Groups one `browse_tools` shows | 3 | Browse the rest in a second call. | | Tools one `find_tools` loads | 8 | Load the rest in a second call. | | Open threads per agent | 30 | Close a finished thread to start another. | | Threads running at once, per agent | 3 | More wait their turn. | | Threads running at once, across the owner's agents | 8 | More wait their turn. | | What `read_thread` shows from the top | 32,000 bytes | Read a line or message for more. | | A packet's task | 600 characters | `hand_off`, `start_thread`, `message_thread` | | A packet's notes | 2,000 characters | Past a limit the sender is asked to shorten; nothing is cut. | | A packet's links | 20 links | Each one of `mail:`, `file:`, `contact:`, `doc:`, `web:` | | Hand-offs deep | 3 | Past this the work is done where it is. | | Agents one owner keeps, besides Roadstar | 40 | `make_agent` refuses past it. | | A job description, its four parts together | 600 characters | `make_agent`, `change_agent` | | Tries of a failed model call | 5 | About 2, 4, 8, 16 and 32 seconds apart. | --- # Models Source: https://docs.roadstar.app/reference/models.md The models an agent can think with, when to pick each, their prices, and thinking levels. Each agent thinks with one model, chosen when it is made (`make_agent`'s `model`) and changed with `change_agent`. Leave it out for the default. A chat always thinks lightly; thinking applies to threads. ## Models | Model | Id | When to pick it | Input | Cached input | Output | | --- | --- | --- | --- | --- | --- | | GPT-6 Luna | `gpt-6-luna` | a tenth of Terra's price, for simple, repetitive work | $0.1 | $0.01 | $0.5 | | GPT-6.1 Terra (default) | `gpt-6.1-terra` | the default | $1 | $0.05 | $5 | | GPT-6.1 Sol | `gpt-6.1-sol` | twice Terra's price, for harder judgement | $2 | $0.1 | $10 | | GPT-6 Astra | `gpt-6-astra` | ten times Terra's price, for the hardest work | $10 | $1 | $50 | Prices are US dollars per million tokens. ## Thinking | Level | Default | | --- | --- | | `low` | | | `medium` | yes, in threads | | `high` | | --- # Errors Source: https://docs.roadstar.app/reference/errors.md What a failed tool returns, what the check returns when it holds an action back, and what to do next. A tool can fail, or the check can hold it back before it runs. Both come back from `use_tool` as JSON, never as an exception. ## Error kinds A tool that fails returns `{ error, message, card? }`. `error` is one of these. | error | What happened | What to do | | --- | --- | --- | | `not_connected` | The app isn't connected. | A card offers the owner the connection. Say in a line what it lets you do, and carry on with the rest. | | `reauth` | The account needs signing in again, or the connection doesn't allow this yet, such as a write to read-only Gmail. | A card asks the owner to connect again. Carry on with the rest. | | `forbidden` | The account doesn't allow this, such as a file the owner can only view. | Tell the owner what it needs. | | `not_found` | Nothing at that id or address, or it was moved. | Search for it again. | | `rate_limited` | The app is limiting requests. | Wait, then try once more. | | `unavailable` | The app or service didn't answer. | Try again later, or say so. | | `too_large` | Over a size limit, such as a file over 25 MB. | Use its link instead. | | `unsupported` | A kind of file or request it can't handle, such as a scanned PDF's text. | Search for its words, or say so. | | `bad_input` | The input can't run as written. The message says what and where. | Fix the input and run it again. | | `vendor` | The app gave an error of its own, as its message says. | Read the message. | | `changed` | What it was about changed since it was read. | Read it again first. | | `unknown_outcome` | It may or may not have happened. | Check before you run it again. | ## When the check holds an action back Nothing ran. `use_tool` returns one of these instead of the tool's result, with the owner's first name and the reason filled in. When the check asks: ```json { "not_done_yet": "Check with first: . Ask in your reply, saying what you would do. Once they say yes, do it again." } ``` When the check stops: ```json { "refused": "Not done: . Tell , and don't try another way." } ``` ## Reasons The `` is one of these, word for word. | Outcome | Reason | | --- | --- | | ask | it goes to someone the owner hasn't named | | ask | it removes or rewrites something the owner didn't point to | | ask | the request came from what you read, not from the owner | | stop | it would send a password, card or bank number, or an ID number | | ask | the check couldn't decide | | ask | the check couldn't be reached | --- # Research pages Source: https://docs.roadstar.app/tools/pages.md Optional. Question and thesis pages that Roadstar's research writer makes. 6 tools. Optional research tools, from Roadstar's research product. Every agent has them, but they aren't for the business's own work: a client's proposal is a [doc](/tools/docs/), not a research page. `request` starts a page on a question and costs money with no price up front, so it is always checked. ## How to use it What `find_tools` sends with these tools, word for word: > Research pages in this workspace: question and thesis pages the reporter writes. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [pages.list](#list) | Pages, newest first | No | | [pages.read](#read) | A page with its content and sources | No | | [pages.request](#request) | Start a research page on a question, in the project the owner names, else the one you file into. A page costs money and has no price up front | Checked | | [pages.update](#update) | Change figures in a page the owner requested, each edit replacing words the page has now; label names the figure | Checked | | [pages.move](#move) | Move a page to another project | Checked | | [pages.mark](#mark) | Star or follow a page | No | ## list `pages.list` · Pages, newest first. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `projectId` | id | no | | | `title` | text | no | up to 300 characters | | `type` | one of `question`, `thesis` | no | | | `status` | one of `queued`, `running`, `failed`, `published` | no | | | `offset` | whole number | no | 0 or more | Returns: ```ts { items: { id: string; title: string; type: "question" | "thesis" | "doc"; projectId: string; projectName: string; status: "draft" | "published" | "archived"; generationStatus: "queued" | "running" | "failed" | "completed" | null; updatedAt: string; }[]; nextOffset: number | null; } ``` ## read `pages.read` · A page with its content and sources. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `pageId` | id | yes | | Returns: ```ts { state: "doc"; projectId: string; organizationId: string; agentId: string | null; page: { id: string; title: string; type: "doc"; }; } | { projectId: string; organizationId: string; agentId: string | null; errorMessage: string; page: { id: string; slug: string; title: string; type: "question" | "thesis"; }; state: "failed"; phase?: undefined; } | { projectId: string; organizationId: string; agentId: string | null; page: { id: string; slug: string; title: string; type: "question" | "thesis"; }; phase: "queued" | "writing" | "researching" | "verifying"; state: "generating"; errorMessage?: undefined; } | { page: { id: string; title: string; type: "question" | "thesis"; }; projectId: string; organizationId: string; agentId: string | null; state: "unavailable"; } | { projectId: string; organizationId: string; agentId: string | null; type: "question"; author: { image: string | null; name: string; }; canonicalUrl: string; description: string; currentRevisionId: string | null; id: string; isAuthenticated: boolean; isFollowing: boolean; isStarred: boolean; isPublished: boolean; model: GeneratedPageDocumentModel; publishedAt: string; slug: string; state: "ready"; starCount: number; wordCount: number; page?: undefined; } | { projectId: string; organizationId: string; agentId: string | null; type: "thesis"; state: "ready"; id: string; slug: string; model: { artifact: { type: "thesis"; version: 1; horizon: "1-2" | "2-5" | "5-10"; relatedQuestion: { id: string; title: string; slug: string; } | null; asOf: string; assessment: { summaryMarkdown: string; bodyMdx: string; conviction: "low" | "medium" | "high" | "insufficient"; rationaleMarkdown: string; changeConditions: string[]; }; pillars: { id: string; side: "for" | "against"; title: string; explanationMarkdown: string; }[]; articles: { date: string | null; datePrecision: "day" | "month" | "year" | null; stance: "supporting" | "challenging" | "mixed"; pillarIds: string[]; relevanceMarkdown: string; documentId: string; title: string; url: string; author: string | null; imageUrl: string | null; confidence: "low" | "medium" | "high" | null; }[]; }; title: string; }; author: { image: string | null; name: string; }; canonicalUrl: string; description: string; currentRevisionId: string | null; isPublished: boolean; isAuthenticated: boolean; isFollowing: boolean; isStarred: boolean; starCount: number; firstPublishedAt: string | null; publishedAt: string | null; page?: undefined; } ``` ## request `pages.request` · Start a research page on a question, in the project the owner names, else the one you file into. A page costs money and has no price up front. **Owner's yes:** Checked. It carries a note for the check to weigh, such as a cost.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `projectId` | id | no | | | `question` | text | yes | 1–2,000 characters | | `context` | text | no | up to 4,000 characters | Returns: ```ts { pageId: string; slug: string; generationToken: string; status: "queued" | "running" | "failed" | "completed"; } ``` ## update `pages.update` · Change figures in a page the owner requested, each edit replacing words the page has now; label names the figure. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home, with Undo. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `pageId` | id | yes | | | `title` | text | no | up to 200 characters | | `edits` | list of `{ label, find, replace, was?, now? }` | yes | 1–12 items | | `edits[].label` | text | yes | 1–120 characters | | `edits[].find` | text | yes | 1–2,000 characters | | `edits[].replace` | text | yes | up to 2,000 characters | | `edits[].was` | text | no | up to 60 characters | | `edits[].now` | text | no | up to 60 characters | | `sentence` | object | no | | | `sentence.before` | text | yes | 1–2,000 characters | | `sentence.after` | text | yes | 1–2,000 characters | | `source` | object | yes | | | `source.label` | text | yes | 1–200 characters | | `source.href` | text | yes | 1–2,000 characters | | `quote` | text | no | up to 1,000 characters | Returns: ```ts { pageId: string; replaced: string; published: { revisionId: string; revisionNumber: number; }; } ``` ## move `pages.move` · Move a page to another project. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `pageId` | id | yes | | | `projectId` | id | yes | | Returns: ```ts { id: string; projectId: string | null; } ``` ## mark `pages.mark` · Star or follow a page. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `pageId` | id | yes | | | `action` | one of `star`, `follow` | yes | | | `enabled` | true or false | yes | | Returns: ```ts { pageId: string; action: "star" | "follow"; enabled: boolean; } ``` ## Types Named types in the results above. ```ts type GeneratedPageDocumentModel = { artifact: { bodyMdx: string; charts: { description: string | null; id: string; noteMarkdown: string | null; rows: { sourceDocumentIds: string[]; values: (number | null)[]; x: string; }[]; series: { key: string; label: string; unit: string | null; }[]; title: string; type: "bar" | "line" | "area" | "donut"; xLabel: string | null; yLabel: string | null; }[]; dataTables: { columns: string[]; id: string; rows: { cells: string[]; sourceDocumentIds: string[]; }[]; title: string; }[]; synopsisMarkdown: string; timelines: { id: "primary_timeline"; items: { analysisMarkdown: string; bodyMarkdown: string; date: string | null; datePrecision: "day" | "month" | "year" | null; id: string; sourceDocumentIds: string[]; title: string; }[]; }[]; version: 1; counterViewMarkdown?: string; visualizations?: { id: string; title: string; description: string; specJson: string; sourceDocumentIds: string[]; }[]; }; sources: GeneratedPageDocumentSource[]; title: string; } type GeneratedPageDocumentSource = { citationNumber: number; crawlStatus: "queued" | "failed" | "idle" | "crawling"; datePrecision: "day" | "month" | "year" | null; id: string; hasSnapshot: boolean; publishedDate: string | null; title: string; url: string; } ``` --- # Comments Source: https://docs.roadstar.app/tools/comments.md Optional. Comments on research pages. 2 tools. Optional research tools: comments on [research pages](/tools/pages/). What an agent writes shows as Roadstar. ## How to use it What `find_tools` sends with these tools, word for word: > Comments on pages. What an agent writes shows as Roadstar, like an @roadstar reply. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [comments.list](#list) | A page's comments | No | | [comments.comment](#comment) | Comment on a page, or reply to a comment | No | ## list `comments.list` · A page's comments. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `pageId` | id | yes | | Returns: ```ts CommentWindowResult ``` ## comment `comments.comment` · Comment on a page, or reply to a comment. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `pageId` | id | yes | | | `text` | text | yes | 1–10,000 characters | | `parentId` | id | no | | Returns: ```ts { commentId: string; } ``` ## Types Named types in the results above. ```ts type CommentWindowResult = { comments: PublicCommentPage; } type PublicCommentPage = { items: PublicCommentItem[]; nextCursor: string | null; } type PublicCommentItem = { assistant?: CommentAssistant | null; author: PublicCommentAuthor; bodyMarkdown: string | null; canEdit: boolean; canRemove: boolean; canReport: boolean; children: PublicCommentPage | null; createdAt: string; directReplyCount: number; editedAt: string | null; id: string; isLiked: boolean; likeCount: number; parentId: string | null; removedAt: string | null; } type CommentAssistant = AssistantRequest | AssistantReply | AssistantNote | AssistantAnswer type PublicCommentAuthor = { image: string | null; name: string; username: string | null; } type AssistantRequest = { kind: "request"; status: "done" | "failed" | "working"; steps: string[]; } type AssistantReply = { full: { contents: string; markdown: string; } | null; kind: "reply"; looked: string[]; sources: AssistantSource[]; tag: "holds_up" | "out_of_date" | "disputed" | "no_source" | null; } type AssistantNote = { kind: "note"; to: string; state: "changed" | "done" | "sent" | "waiting" | "kept"; } type AssistantAnswer = { kind: "answer"; by: string; outcome: "changed" | "done" | "kept"; } type AssistantSource = { date: string | null; pageSource: number | null; quote: string | null; title: string; url: string | null; } ``` --- # Research tables Source: https://docs.roadstar.app/tools/tables.md Optional. Tables whose rows agents find and whose cells they fill from sources. 6 tools. Optional research tools. A research table is built by agents that find its rows and fill each cell from a source. Building and researching rows cost money by the row, so both are always checked. For the business's own lists, such as a month of hours, use a Google Sheet through [files](/tools/files/) or a spreadsheet in the [workspace](/tools/workspace/). ## How to use it What `find_tools` sends with these tools, word for word: > Research tables: agents find rows and fill each cell from a source. Builds cost money; Pro estimates the cost first. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [tables.list](#list) | Tables | No | | [tables.read](#read) | A table's columns, rows and cell values | No | | [tables.build](#build) | Plan and build a new table, in the project the owner names, else the one you file into. It costs money by the row | Checked | | [tables.add_row](#add_row) | Add a row by the columns that identify it | No | | [tables.research_rows](#research_rows) | Research some rows again. It costs money by the row | Checked | | [tables.accept_cell](#accept_cell) | Accept a value waiting for review | Checked | ## list `tables.list` · Tables. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `projectId` | id | no | | Returns: ```ts { id: string; name: string; question: string; projects: { id: string; name: string; }[]; row: string; columns: number; updatedAt: string; run: { id: string; kind: DatasetRunKind; status: DatasetRunStatus; estimateUsd: number; capUsd: number; spentUsd: number; rowsTotal: number; rowsDone: number; message: string | null; createdAt: string; completedAt: string | null; } | null; }[] ``` ## read `tables.read` · A table's columns, rows and cell values. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `datasetId` | id | yes | | Returns: ```ts { dataset: { id: string; projects: { id: string; name: string; }[]; name: string; question: string; plan: { row: string; rule: string; queries: string[]; columns: { name: string; label: string; type: "number" | "date" | "text" | "choice" | "rating" | "yes_no"; unit: string | null; options: string[] | null; instructions: string; identifies: boolean; }[]; }; createdAt: string; agentId: string | null; spentUsd: number; run: { id: string; kind: DatasetRunKind; status: DatasetRunStatus; estimateUsd: number; capUsd: number; spentUsd: number; rowsTotal: number; rowsDone: number; message: string | null; createdAt: string; completedAt: string | null; } | null; }; rows: StoredRow[]; } ``` ## build `tables.build` · Plan and build a new table, in the project the owner names, else the one you file into. It costs money by the row. **Owner's yes:** Checked. It carries a note for the check to weigh, such as a cost.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `projectId` | id | no | | | `question` | text | yes | 1–2,000 characters | | `maxRows` | whole number | no | 1–250, default 25 | Returns: ```ts { id: string; projects: { id: string; name: string; }[]; name: string; question: string; plan: { row: string; rule: string; queries: string[]; columns: { name: string; label: string; type: "number" | "date" | "text" | "choice" | "rating" | "yes_no"; unit: string | null; options: string[] | null; instructions: string; identifies: boolean; }[]; }; createdAt: string; agentId: string | null; spentUsd: number; run: { id: string; kind: DatasetRunKind; status: DatasetRunStatus; estimateUsd: number; capUsd: number; spentUsd: number; rowsTotal: number; rowsDone: number; message: string | null; createdAt: string; completedAt: string | null; } | null; } ``` ## add_row `tables.add_row` · Add a row by the columns that identify it. **Owner's yes:** No. It makes something new.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `datasetId` | id | yes | | | `identity` | named values of text | yes | | Returns: ```ts { key: string; } ``` ## research_rows `tables.research_rows` · Research some rows again. It costs money by the row. **Owner's yes:** Checked. It changes what's there. It can also carry a note for the check to weigh.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `datasetId` | id | yes | | | `rowKeys` | list of text | yes | 1–250 items | Returns: ```ts { id: string; projects: { id: string; name: string; }[]; name: string; question: string; plan: { row: string; rule: string; queries: string[]; columns: { name: string; label: string; type: "number" | "date" | "text" | "choice" | "rating" | "yes_no"; unit: string | null; options: string[] | null; instructions: string; identifies: boolean; }[]; }; createdAt: string; agentId: string | null; spentUsd: number; run: { id: string; kind: DatasetRunKind; status: DatasetRunStatus; estimateUsd: number; capUsd: number; spentUsd: number; rowsTotal: number; rowsDone: number; message: string | null; createdAt: string; completedAt: string | null; } | null; } ``` ## accept_cell `tables.accept_cell` · Accept a value waiting for review. **Owner's yes:** Checked. It changes what's there.\ **Your work:** listed on Home. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `datasetId` | id | yes | | | `key` | text | yes | | | `column` | text | yes | | Returns: ```ts { done: true } ``` ## Types Named types in the results above. ```ts type DatasetRunKind = "build" | "rerun" type DatasetRunStatus = "stopped" | "done" | "queued" | "running" | "failed" type StoredRow = { key: string; values: { [x: string]: CellValue; }; sources: { [x: string]: CellSource; }; } type CellValue = string | number | null type CellSource = { status: "failed" | "filled" | "needs_review" | "not_disclosed" | "edited"; quote?: QuoteRef; url?: string; agree?: number; from?: string; human?: true; was?: { value: CellValue; source: CellSource | null; }; } type QuoteRef = { page: string; start: number; end: number; } ``` --- # Filings Source: https://docs.roadstar.app/tools/filings.md Optional. Public companies' SEC filings. 2 tools. Optional research tools: the SEC filings Roadstar stores for public companies. A business's own clients and suppliers are in [companies](/tools/companies/). ## How to use it What `find_tools` sends with these tools, word for word: > SEC filings stored for companies: 10-K, 10-Q, 8-K, 20-F, proxies and more. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [filings.latest](#latest) | Each company's latest filings | No | | [filings.read](#read) | One filing's text | No | ## latest `filings.latest` · Each company's latest filings. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `companyIds` | list of id | yes | 1–50 items | Returns: ```ts { items: Pick[]; } ``` ## read `filings.read` · One filing's text. **Owner's yes:** No. Nothing of the owner's changes. | Input | Type | Required | Limits | | --- | --- | --- | --- | | `accession` | text | yes | 1–40 characters | Returns: ```ts { state: string; title?: never; url?: never; text?: never; } | { title: string | null; url: string | null; text: string; state?: never; } ``` ## Types Named types in the results above. ```ts type CompanyListItem = { id: string; manual?: boolean; handle: string; ticker: string | null; name: string; kind: string | null; kindLabel: string | null; quote: Quote | null; marketCap: number | null; watchlistMetrics: { sector: string | null; valuation: { label: string; value: string; period: string | null; sortValue?: number | null; } | null; revenueGrowth: { value: number; period: string; } | null; profitability: { label: string; value: number; period: string; } | null; }; latestFiling: Pick | null; written: boolean; figures: { id: string; label: string; value: string; sortValue?: number | null; sortGroup?: string; }[]; projects: { id: string; name: string; }[]; } type Quote = { price: number; changePercent: number; asOf: string; } type FilingItem = { accession: string; form: string; what: string; period: string | null; filedAt: string; } ``` --- # Newsfeed Source: https://docs.roadstar.app/tools/newsfeed.md Optional. The stories the workspace's newsfeed follows. 1 tool. Optional research tools: today's stories from the workspace's newsfeed, the separate Newsfeed screen in Pro. ## How to use it What `find_tools` sends with these tools, word for word: > The workspace's newsfeed: stories it follows for the workspace's projects. ## Tools | Tool | What it does | Owner's yes | | --- | --- | --- | | [newsfeed.read](#read) | Today's stories and what the feed follows | No | ## read `newsfeed.read` · Today's stories and what the feed follows. **Owner's yes:** No. Nothing of the owner's changes. No input. Returns: ```ts { stories: NewsfeedStory[]; projects: { id: string; name: string; }[]; topics: { label: string; projectIds: string[]; }[]; accounts: { handle: string; projectIds: string[]; addedAt: string; }[]; market: { stories: NewsfeedStory[]; read: { sentences: { text: string; storyIds: string[]; }[]; previous: string[]; writtenAt: string; rewrites: number; } | null; }; brief: { writtenAt: string; projects: { projectId: string; effect: "direct" | "indirect" | "quiet"; text: string; }[]; } | null; actions: { kind: "match" | "follow" | "unfollow"; text: string; at: string; }[]; today: { counts: NewsfeedCount[]; stories: number; }; strip: { symbol: string; name: string; price: number; changePercent: number; }[]; pricedAt: string | null; moves: Record; publishers: string[]; checkedAt: string | null; } ``` ## Types Named types in the results above. ```ts type NewsfeedStory = { id: string; kind: "x" | "web"; headline: string; summary: string; reason: string | null; watchLabel: string; fromMarket: boolean; projectIds: string[]; tickers: string[]; sources: { url: string; title: string | null; }[]; posts: { handle: string; name: string | null; text: string; url: string; postedAt: string; }[]; happenedAt: string; } type NewsfeedCount = { kind: "x" | "web" | "news"; read: number; kept: number; } ```