Skip to content

AI Support ​

Your household data sits behind a standard REST API, so AI tools can work with it. You have four ways in:

  • homestead resources — a CLI that reads and writes your data. Best for an agent or a script.
  • Chat — a built-in assistant that manages your data in plain language.
  • An MCP server — connect Claude or another MCP client to your instance.
  • Outside MCP servers — the other direction: give the household assistant services like Home Assistant, on a shared household account.
  • Agent skills — drop-in skills that teach your coding agent to scaffold apps, edit your schema, and stand up an instance.

The built-in features that call a model directly — Chat and the photo-import features (grocery lists, HSA receipts) — share one provider you configure once. The CLI, MCP, and skills above use whatever model you already run, so they need no provider config.


Configure your AI provider ​

Homestead's built-in AI features — the chat assistant, grocery-list photo import, and HSA receipt scanning — run through a single provider you choose. Three are supported, via the Vercel AI SDK:

ProviderproviderExample model
OpenAI (Codex)'openai'gpt-4o
Anthropic (Claude)'anthropic'claude-3-5-sonnet-latest
Google (Gemini)'google'gemini-2.5-flash

Set the ai block in homestead.config.ts — the provider and model:

ts
// homestead.config.ts
const ai: HomesteadConfig['ai'] = {
  provider: 'google', //  'openai' | 'anthropic' | 'google'
  model: 'gemini-2.5-flash', // must be vision-capable for the photo features
};

const config: HomesteadConfig = {
  apps: [/* … */],
  ai,
};

Then give it a key, either way:

  • In Homestead: Superuser → Credentials lists what Homestead needs under Homestead needs. Set AI provider API key there. It's stored encrypted, nobody can read it back, and the assistant turns on at once — no restart.
  • In the environment: AI_API_KEY. It wins over a stored key, and the Credentials page says so.
bash
# .env — get a key from your chosen provider's console.
AI_API_KEY=your_ai_provider_api_key_here

A config can still pass its own auth: { apiKey } (the scaffolded config reads the provider's own variable, such as OPENAI_API_KEY); that wins over both. With no key anywhere — or no ai block — the chat and photo endpoints return 503 and the rest of Homestead works as normal. homestead doctor says where the AI key comes from. The embedding key for document search works the same way: EMBEDDING_API_KEY, or Embedding API key in Credentials.

Vision models

The grocery and HSA photo features send an image to the model, so the configured model must be vision-capable. The example models above all are.


Read and write data from the CLI ​

homestead resources reads and writes your household data from the command line. Run it on the box where Homestead is installed; it authenticates itself by reading the instance's database, so you don't pass credentials.

List every resource and what you can do with it:

bash
homestead resources
Available resources — run `homestead resources <resource> <verb>`:

  account-tag   list, get, create, delete            (parent: --user)
  gift-card     list, get, create, update, delete
  transaction   list, get, create, delete            (parent: --gift-card)

Run `homestead resources <resource>` for fields and usage.

See a resource's fields and usage:

bash
homestead resources gift-card

This prints the description, every field (with type and whether it's required), file fields, and any custom methods.

Read and write:

bash
homestead resources gift-card list
homestead resources gift-card get <id>
homestead resources gift-card create --merchant "Visa" --amount 100
homestead resources gift-card update <id> --notes "Used $50"
homestead resources gift-card delete <id>

For a child resource, pass the parent id before the verb:

bash
homestead resources --gift-card <card-id> transaction list

The command discovers your schema at runtime, so it always lists the resources your apps declare. Point an agent at it and it learns your whole API by running homestead resources.

Flags ​

FlagPurpose
--token=TOKENUse an existing bearer token instead of minting one.
--email=… --password=…Authenticate with superuser credentials instead of minting.
--server-url=URLTarget a remote engine (default http://127.0.0.1:<port>/api/aep).
--@data=PATHSupply a JSON body from a file (for create or custom methods).
--upload=FIELD=PATHAttach a file to a custom method's body (repeat a field for a list); sent as multipart.
--no-waitPrint a long-running method's operation instead of waiting for its result.
--format, --file, --text, --text-fileBulk import input (--file repeats).
--validate-onlyBulk import: preview without writing; prints the preview_id.
--preview-id=ID, --select=0,2Bulk import: write exactly those rows of a preview.
--out=PATH, --filter=EXPRBulk export: where to write the file, and which rows.
--data-dir=PATHWhere the SQLite database lives (default <project>/data).

Chat with your data ​

Chat is a built-in assistant that looks up and changes your household data in plain language — "how much is left on my Visa gift card?", "add milk to the grocery list", "what events are coming up?". It's always installed, and it's the home: the first page you land on is a conversation with it (below).

Chat works with any app you've added, with no per-app setup. It acts with your permissions — it's only offered the apps you can use, and only the changes you're allowed to make — confirms before deleting, and shows you which actions it took — each tool call is a card you can open to see what it sent and what came back. Replies stream in as they're written, and render as Markdown: several records come back as a list with each name in bold, records compared side by side as a table, and the values you asked for (dates, amounts, addresses) in bold. Some values are links you can act on: an address opens in the map service you picked in your People settings, a phone number dials, and an email address starts a message. Apps can add their own (Typed values in replies).

The home ​

The home (/dashboard) is the assistant's: the conversation fills the screen, with a strip of chips along the top for the widgets that matter now.

  • Each chip is a widget's one-line summary ("Groceries · 4 items left to buy", "Today · plumber at 2, 1 more"), composed from the widgets' own data — no model call on page load, so it's instant. Tap a chip to open the widget under the strip; tap again to put it away.
  • The strip holds the widgets you've pinned in your dashboard settings, plus any whose summary says it's urgent (those go first). A pinned widget with nothing to say stays off the strip.
  • The conversation stays on the home as it grows. Its first message gives it its own address (/dashboard/c/<id>), so a reload or a shared link brings it back, while going Home or tapping New chat starts a fresh one (a greeting is its empty state).
  • The history button beside the chips opens your past conversations in a drawer: pick one to carry on with it on the home, or delete it.
  • Old Chat links (/chat, /chat/<id>, a bookmark) open the same conversation on the home.
  • For an admin, a new conversation sits under the setup checklist (invite your household, configure the assistant) until it's dismissed.

On a phone the strip sits under the top bar, the thread takes the rest of the screen, and the composer is docked at the bottom.

Without the assistant the home keeps its chips, and a note takes the conversation's place saying why: no AI provider is configured yet (see Enable Chat), or the person can't use the Chat app (its app access). Their apps are in the menu either way.

It knows your local date and time: your browser sends its timezone with each message, so "tomorrow", "this weekend" and "at 6pm" mean yours. Nothing about it is stored.

It knows who else lives here: everyone holding the Admin or Member role (through any group), and every superuser. It gets their names and user ids, so a record that says created_by: u_2 reads as "Sam added it", never the id. Emails aren't shared. A guest isn't part of the household and isn't told anyone's name. The same list is at GET /api/household for any household member, and the Share dialog uses it to offer members the people they live with.

It knows your family, too, once People knows which person you are. A person's Homestead account (in the person form) links them to the account they use; the People screen asks "Which one is you?" until your own is linked, or tell the assistant ("I'm Alex in People"). Then it's told who you are, your partner, parents, children and siblings, and anyone else in the household, each with the account they use — so "remind the kids" or "Sam's birthday" needs no follow-up question. An account belongs to one person: linking it moves it off whoever had it. A link is only a label; it grants no access.

Conversations are kept. The history drawer holds your past threads, most recent first; open one to carry on where you left off. Each conversation has its own address (/dashboard/c/<id>), so a reload — or another device — picks it back up, and a reply that's still being written on one device appears on the other as it lands. Stop ends a reply early and keeps what was written so far. Delete a conversation from the list; otherwise it's removed 60 days after its last message.

You can attach photos and PDFs to a message: tap the paperclip, paste, or drop files on the box. A message can carry up to 5 files of up to 10 MB each, and can be just files with no text ("what's in this receipt?" works, and so does a photo on its own). JPEG, PNG, WebP and GIF images and PDFs are accepted — the assistant's models read those — and a file's type is checked from its contents, not its name. The assistant sees the files of your three most recent messages that have any; older ones are noted by name, so a long thread doesn't resend every photo. Attachments are stored with the message, so they're there on a reload or another device, and they go when the conversation does.

The assistant can also file an attachment into an app: "save this as an HSA receipt", "add this gift card with its photo", "file this PDF". It copies the file into the record's file field (a receipt's scan, a gift card's front image, a document's file) as it creates or updates it. It can only use files attached in the same conversation, including older ones it no longer sees, and only fields that hold a single file. The MCP server's tools don't take files.

Under the hood the assistant sees the same one-tool-per-resource surface the MCP server serves by default: one tool per resource, with the verb — list, get, create, update, delete, or an app's :custom-method such as :process-image or :classify — as the tool's action. A list can be narrowed and sorted on the server (filter, order_by, limit), so "what's due this week" is one small read rather than a dump of the whole collection. Filters can use every record's create_time and update_time too, so "what changed since Friday" works on any resource.

Filters can also match part of a value, ignoring case: name.contains("jake"), title.startsWith("Tax"), merchant.endsWith("co"), and, for a list of strings, aliases.exists(a, a.contains("jake")). So "Jake's address" finds Jake Miller, or the friend whose nickname is Jakey, without a full name. The assistant is told to look things up this way. The same filters work over MCP and on the REST API's ?filter=.

When a question matches more than one record, the assistant answers for each of them: "Jake's address" with two Jakes gives both addresses, each labelled with its person. It asks which one you mean only before it changes something, such as setting a reminder for one of them.

Some things wait for your Approve. "Tell Sam I'm running late" sends Sam a message (household messages), so the reply stops on a card — who it's to, what it says — with Approve and Deny. Nothing goes out until you answer; then the reply carries on, and the assistant hears whether it was sent. The card is stored with the reply, so you can answer it after a reload or from another device. Send a new message instead and it counts as Deny. Which actions ask is up to each app (a custom method's confirm: true, see Asking before a model acts), not the model.

Stored conversations ​

Under the home's conversation is one endpoint, which you can call yourself. POST /api/chat runs one stored, streamed turn. Send only the new message; the server keeps the history:

http
POST /api/chat
Authorization: Bearer <token>
Content-Type: application/json

{ "conversation_id": "…", "message": { "id": "<your id>", "text": "Add milk" }, "timezone": "America/Chicago" }
  • timezone (an IANA name) is optional; the assistant reads dates and times in it, and in UTC when it's left out or not recognized.

  • Leave out conversation_id to start a new conversation; its id comes back in the X-Conversation-Id header and the stream's first event. The streamed reply carries the id it's stored under (<your id>-reply).

  • The reply is the AI SDK's UI message stream — text as it's written, and each tool call with its result.

  • The turn runs to the end on the server even if you disconnect, and is saved as it goes: a conversation with its chat-messages, readable like any other resource (and by MCP clients — the assistant itself doesn't see them).

  • One turn at a time per conversation: a second send while one runs gets 409. Stop a running turn with POST /api/aep/conversations/{id}:cancel; the reply keeps the text written before the stop.

  • message.id is yours to choose, so a retried send gets 409 instead of sending twice.

  • To attach files, send multipart/form-data instead: the same JSON as a request part, and each file as an attachments part. The text may then be empty. A file that isn't an image or PDF is 415, one over 10 MB is 413, and more than 5 is 400. The files are stored on the user's message (attachments, a list of file descriptors — see Files in lists and objects).

  • A conversation is private to whoever started it, and is deleted 60 days after its last message.

  • A reply that stopped on an approval (a tool part in approval-requested, with an approval.id) is answered with { "conversation_id": "…", "approval": { "id": "<approval id>", "approved": true } }. The same reply message carries on and streams like any turn. An answer nothing is waiting for is 409.

  • The tools are built for each person, from what they can reach: a collection they can't read is left out (with everything nested under it), and one they can read but not change offers only list and get — so the model never spends a turn on a call that could only be refused. The engine still decides every call. MCP does the same.

A resource can be left out of the assistant's tools, or out of MCP's, with modelTools on its definition — see Hiding a Resource from AI Tools.

Enable Chat ​

Chat runs on whichever model you configure as your AI provider — OpenAI, Anthropic, or Gemini. Set the ai block in homestead.config.ts and give it a key (in Credentials, or AI_API_KEY); the home then holds a conversation instead of noting that the assistant isn't set up — a superuser sees a Set the AI key button there. Without an ai block the home shows its widget chips and that note, and the rest of Homestead works as normal.


Connect an MCP client ​

Homestead ships a built-in MCP server at /api/mcp — no separate process. It exposes every resource in the instance, plus the app-declared AEP-136 custom methods (the POST /<plural>:<verb> endpoints declared in customMethods), plus document search when embeddings are configured — the same tools the built-in Chat runs on. Every action runs with the signed-in user's own permissions, and the tool list is cut to match: collections the caller can't read aren't offered, and ones they can't change are offered read-only.

Enable it. MCP clients sign in through Homestead's OAuth authorization server, so turn that on in homestead.config.ts and restart:

bash
OAUTH_SERVER_ENABLED=1
OAUTH_SERVER_ISSUER_URL=https://your-host   # the instance's public origin

The MCP server is on by default whenever the authorization server is enabled (set auth.authServer.mcpEnabled: false to run the AS without it).

Connect a client. Point your MCP client (e.g. Claude Desktop) at:

https://your-host/api/mcp

Authorization is automatic — no bearer token to copy. The client discovers the authorization server from the endpoint, registers itself, and opens Homestead's sign-in and consent screen in your browser; approve it and the client is connected. The model can then list, read, and write your household resources.

The tool surface ​

Three surfaces are available, chosen by the settings app's MCP tool surface flag (Superuser → Flag Management). It's an ordinary app flag, so a change takes effect on the client's next tools/list — no config edit, no restart, and switching back is the same two clicks.

SurfaceToolsWhen
resource (default)one per resource, ~41Almost always
typedfour per resource plus one per custom method, ~167You want the strictest per-verb schemas and your client can hold them
genericsix, resource-parameterizedYour client is on a tight context or tool budget

All three run every call with the signed-in user's own permissions, and reach the engine by exactly the same path — the two derived surfaces translate a call into the typed call it stands for before executing it, so permissions, reference checks, and custom-method dispatch can't diverge between them.

resource — one tool per resource ​

Each resource is a single tool named for it — gift_cards, recipes, transactions — and the verb is its action parameter:

jsonc
{
  "name": "gift_cards",
  "arguments": {
    "action": "create",
    "fields": { "merchant": "Target", "amount": 25 }
  }
}

The actions are list, get, create, update, and delete, plus one per declared custom method written in AEP's :verb notation — :classify, :process-image. (The colon is why a custom method named list can't shadow the CRUD action.) A custom method's request body goes in body; the tool description says what each verb needs, and an async (AEP-151) method tells the model to poll the operations resource until done is true.

Each tool carries its own resource's field schemas — types, enums, descriptions, and which resource a reference points at — so the model can see what a field is at the moment it composes the call. Nested resources take their ancestor ids as required <parent>_id params, needed for every action including list.

Because one schema serves every action, requiredness that varies by action isn't in the JSON Schema: the tool description states it (create requires: merchant, card_number, amount) and the server enforces it, naming the fields the resource accepts when it rejects. A wrong guess self-corrects in one round-trip.

This is the default because it beats typed on both counts: ~41 tools instead of ~167, and fewer schema tokens, since typed emits each resource's field set twice (once for create_x, once for update_x) where this emits it once.

typed — four tools per resource ​

The server mints one tool per resource per verb: create_book / read_book / update_book / delete_book, plus one tool per custom method named after its verb and what it addresses — classify_document for an item-target method, process_image_groceries for a collection-target one. Each takes the addressed record's id, any parent ids, and (for a custom method) the fields of its declared request schema; a method with no declared schema gets a single optional body param taking a JSON-encoded object.

The schemas are the strictest of the three — requiredness is per verb, so create_book can demand title in JSON Schema rather than in prose. The cost is ~167 tools on a stock instance: tens of thousands of tokens of schema in the client's context on every request, and past the tool cap some clients enforce.

generic — six tools ​

For clients on a tight budget, everything collapses into six tools that take the resource as a parameter, so the count stays flat however many apps you add:

ToolWhat it does
describe_resourcesNo argument → the catalog. With resource → that resource's fields, allowed values, references, parent ids, and custom methods
read_recordsid for one record, omit it to list the collection
create_recordCreate one record from a fields object
update_recordMerge-patch one record
delete_recordDelete one record
run_custom_methodInvoke an AEP-136 custom method by resource + verb

Discovery is designed in rather than traded away. The resource parameter is a real enum of every plural in the instance, so the catalog rides along in each tool's schema and the model can neither miss a resource nor invent one. The server's instructions carry a one-line description of every resource and its custom methods, and describe_resources returns the full field schema on demand. What you give up versus resource is field typing at call time: the model has to ask what a field accepts instead of reading it off the schema.

Nested resources take their ancestor ids in parent_ids, keyed by <parent>_id — {"gift_card_id": "abc"} for a transaction.

Read-only vs. read + write ​

The server advertises two scopes:

ScopeWhat it grants
homestead:readReading every resource, GET custom methods, document search
homestead:writeEverything read grants, plus create / update / delete and the rest of the custom methods

A client that requests homestead:read gets a read-only surface and cannot mutate data. How that's expressed depends on the surface: typed and generic don't register the write tools at all, while resource — where the tool is the resource and so can't be withheld — narrows each tool's action enum to the read actions. That makes the read-only resource surface self-describing: the model sees an enum with no write actions in it rather than inferring the limit from absent tools.

Custom methods count as writes unless they're declared method: 'GET'. Request homestead:write (or both) for full access. Clients that request no scope get full read + write, as before.

The synthesized :bulk-import is a tool action like any custom method, with the same body every client sends: a validate_only preview first, then the preview_id (and selected_indices) to write exactly those rows. In the chat the write asks the person first (confirm: { unless: 'validate_only' }), and the approval card lists the rows it would save; a preview never asks. A preview is labelled untrusted — it's someone else's caption or page — so instructions inside it stay data. :bulk-export stays out: it returns a file.

A method that takes files gets them by attachment id in the chat: files the person attached to the conversation, which the executor uploads (as multipart $upload parts) — "save this recipe" with a screenshot attached, or a grocery-list photo for :process-image. MCP has no attachments, so an optional file field is left out of the tool and a method that requires one isn't offered. Long-running methods are awaited (up to 90 seconds) and return their result; past that, the operation to poll.

Prefer an out-of-process option? The community aep-mcp-server can still be pointed at the engine's /api/aep prefix with a bearer token from homestead login.

Put the endpoint behind Cloudflare Access ​

By default the MCP endpoint is its own OAuth gate: it's reachable, but every request needs a valid Homestead token. If you'd rather have Cloudflare Access authenticate callers to /api/mcp (Cloudflare's "MCP server behind Access" model), give Homestead the team domain and the Access application's Audience (AUD) tag:

bash
OAUTH_SERVER_CF_ACCESS_TEAM_DOMAIN=your-team   # or your-team.cloudflareaccess.com
OAUTH_SERVER_CF_ACCESS_AUD=<application-aud-tag>

With both set, a request carrying Access's Cf-Access-Jwt-Assertion header is verified (RS256 against your team's JWKS, plus issuer/audience/expiry) and its email claim is mapped to the Homestead user of the same email — the MCP tools then run under that user's permissions. A Homestead user must already exist for each Access identity you expect to connect (an unknown email gets a 403). The endpoint's own OAuth still works, so this is additive: you can front /api/mcp with Access without breaking direct OAuth clients.

OAUTH_SERVER_CF_ACCESS_AUD takes a comma-separated list of AUD tags. If you reach the endpoint through more than one Access application — for example an MCP portal in front of the origin's own app — each forwards a token with a different aud, so list both:

bash
OAUTH_SERVER_CF_ACCESS_AUD=<origin-app-aud>,<portal-app-aud>

Because a machine MCP client can't complete an interactive Access login, this is the only way to have Access authenticate the endpoint itself — a plain Access self-hosted app in front of /api/mcp would intercept the OAuth handshake and break it.

Debugging a rejected token. If Access-fronted requests come back 401, set HOMESTEAD_LOG_LEVEL=debug and restart, then watch the logs (filter for the access-jwt scope). Each rejection logs the exact reason (issuer mismatch, audience mismatch, signature invalid, no Homestead user for <email>, or no external authenticators configured), so you can see which check failed. If you see no access-jwt line at all for an attempt, the request never reached Homestead — the failure is upstream in Cloudflare/the client, not here. Return the level to info once you're done.


Connect outside services (MCP servers) ​

The other direction: Homestead as an MCP client, so the household assistant can use an outside service — Home Assistant, a calendar — on one shared household account that members never hold the keys to. Superusers set it up under Superuser → MCP servers:

  1. Store the token under Superuser → Credentials (it's write-only: you can replace it, never read it back).
  2. Add the server: a name, its Streamable HTTP URL, and that credential. Home-network addresses (homeassistant.local, 192.168.…, a tailnet) are fine; loopback and link-local addresses — Homestead itself, the cloud metadata service — are refused. To reach a server on the same box, list its host in HOMESTEAD_MCP_TRUSTED_HOSTS (comma-separated, host or host:port).
  3. Check tools. Homestead connects and lists the server's tools with their descriptions. Every tool starts off — including any the server adds later — because a tool's description goes into the assistant's prompt, so read it before turning it on.
  4. Set each tool's policy: Use freely (look-ups), Ask first (anything that changes something — an approval card every time), or Allow without asking.
  5. Share the server with the groups who may use it. Until it's shared, nobody but superusers can see it, or use it. The share level decides which tools: View lets a group use the Use freely tools; Can edit lets it use the Ask first and Allow without asking ones too. Either way only superusers can change the server itself.

The token is attached only inside Homestead's server, as a bearer header on requests to that server; it never reaches the browser, the assistant or a log.

Servers that sign in with OAuth ​

Many MCP servers take an OAuth sign-in rather than a pasted token. Add the server with no credential, then click Sign in with OAuth on its card:

  • Most servers register Homestead themselves — leave the client blank and click Connect. You sign in on the provider's page, approve, and land back on MCP servers with the server's tools fetched.
  • A provider that doesn't (Google, for one) needs a client you register in its console. The dialog shows the redirect address to give it — this Homestead's /superuser/mcp-servers/oauth-callback — then paste the client ID and secret, and any scopes to ask for.

Homestead signs in as the household, stores the sign-in as an OAuth credential (encrypted; nobody can read its tokens back), and keeps it fresh: a token about to expire is refreshed before it's used, one refresh at a time. If the provider revokes it, the server's card says Reconnect needed and the assistant says the service is unavailable; nothing pops up mid-conversation. Disconnect forgets the sign-in.

A sign-in is tied to the server's address: change the address and the server has to be connected again — its tokens are never sent anywhere else.

Every call goes through one gate ​

Whatever makes a call — today a person through the API, next the assistant — it goes through the same broker (homestead-core/server/mcp/broker.ts, callConnectionTool), which checks, in order: the server is shared with the caller; the tool is on (a tool that's off, unknown, or has a policy Homestead doesn't recognize never runs); the share level covers the tool's policy; and an Ask first tool has the person's approval. Then it connects with the token, and hands back what the tool returned labelled as untrusted data from outside the household ({ untrusted: true, source, tool, content, truncated }), as text capped at 16,000 characters.

A person can call a tool themselves — calling it is their approval:

bash
curl -X POST "$HOMESTEAD/api/aep/mcp-servers/$ID:call-tool" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{ "tool": "get_state", "arguments": { "entity": "light.kitchen" } }'

A refusal is a 403 with the reason; the tool's own failure, or a server that can't be reached, is a 502.

The call log ​

Every call, refused ones included, leaves an mcp-call row: when, who, which server and tool, a summary of the arguments (cut at 500 characters), the conversation it came from, the outcome (ok, error, denied — the person declined — or refused), any error, and how long it took. It never holds the token or the tool's output. Rows are written by the system, so nobody can erase their own call; only superusers can read them, and they're deleted after 90 days. Recent calls on each server's card shows its last 20.

The assistant uses them ​

Each turn, the assistant is offered the tools of every server shared with the person it's talking to, as far as their share goes (View: Use freely tools; Can edit: the rest that are on too). Superusers get every server's tools.

  • What the model sees is what you reviewed. A tool's description and its arguments' schema are stored when you Check tools, and that copy is what the assistant gets — a turn never asks the server what its tools are. A server checked before this release needs one more Check tools before its tools reach the assistant.
  • "Ask first" tools show an approval card — the tool, the service, and the arguments — and run only on Approve. Deny, or sending another message instead, means it never reaches the server; the log records it as denied.
  • What a tool returns is outside data. It reaches the model labelled as coming from that service, and the assistant is told to treat it as data to report, never as instructions — so a calendar invite or a web page that says "ignore your instructions and…" doesn't steer it.
  • A server that's down costs only its own tools. The assistant says the call failed and carries on.

In the chat, a call shows as Home Assistant · Get state, with what it sent and got back behind the disclosure, like Homestead's own tools.

A ready-made setup for Google Workspace services (Calendar first) is coming next.


Teach a coding agent with skills ​

Homestead ships SKILL.md skills in the repo under .claude/skills/:

SkillWhat it does
setup-homesteadStand up a new instance — scaffold, first boot, claim the admin account, install service.
create-appScaffold a new feature app end-to-end — resources, hooks, components, config, e2e.
add-resourceAdd or modify a resource definition (fields, enums, file fields, child resources).
add-widgetAdd a dashboard widget to an existing app.

Copy them into your agent's skills directory (use ~/... for all projects, or the dotted form inside one project), then start a fresh session:

bash
cp -R /path/to/homestead/.claude/skills/*  ~/.claude/skills/    # Claude Code
cp -R /path/to/homestead/.claude/skills/*  ~/.codex/skills/     # Codex
cp -R /path/to/homestead/.claude/skills/*  ~/.gemini/skills/    # Gemini CLI