@agentshouse/skills 0.1.0-cd1cc903cbd3b → 0.1.0-cd3fcd75c5224

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +4 -2
  2. package/agent-instructions.json +11 -4
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +2 -1
  5. package/guide/agent-functions/work-on-ticket.md +1 -1
  6. package/guide/apps/build.md +1 -0
  7. package/guide/apps/chat.md +6 -2
  8. package/guide/apps/content-safety.md +3 -2
  9. package/guide/apps/design-functions.md +10 -0
  10. package/guide/apps/design-load.md +14 -0
  11. package/guide/apps/design-look.md +11 -0
  12. package/guide/apps/design-schema.md +12 -0
  13. package/guide/apps/design.md +12 -0
  14. package/guide/apps/example-function.md +1 -1
  15. package/guide/apps/example-tasks-2.md +21 -21
  16. package/guide/apps/example-tasks-3.md +2 -2
  17. package/guide/apps/example-tasks.md +6 -6
  18. package/guide/apps/function-times.md +10 -0
  19. package/guide/apps/functions.md +7 -7
  20. package/guide/apps/page.md +4 -4
  21. package/guide/apps/runtime-conversations.md +10 -0
  22. package/guide/apps/runtime-refusals.md +4 -2
  23. package/guide/apps/search.md +1 -1
  24. package/guide/apps/start.md +3 -0
  25. package/guide/index.md +2 -4
  26. package/guide/owner-rules.md +3 -5
  27. package/guide/reference/access-level.md +1 -1
  28. package/guide/reference/agent-function.md +1 -1
  29. package/guide/reference/app-manifest.md +3 -1
  30. package/guide/reference/group-post.md +7 -0
  31. package/guide/reference/inbox-types.md +7 -0
  32. package/guide/reference/schema.md +1 -1
  33. package/guide/reference/telegram-link.md +11 -0
  34. package/guide/reference/triggers.md +5 -3
  35. package/guide/reference/wayfinding-types.md +7 -0
  36. package/guide/sites/entry-data-contract.md +1 -1
  37. package/guide/skills/house-onboarding.md +1 -1
  38. package/owner-rules-earlier.json +2 -1
  39. package/package.json +1 -1
  40. package/upstream.json +2 -2
package/README.md CHANGED
@@ -14,7 +14,8 @@ The Skills agents.house serves its agents, and the texts its agents read.
14
14
  `agent-functions/` holds the Agent functions agents.house writes into every Private Room, frontmatter and
15
15
  all. `owner-rules` is the Owner rules' text, and `owner-rules-earlier.json` the SHA-256 of every
16
16
  earlier text.
17
- - `agent-instructions.json` is the lines that open an agent's first prompt.
17
+ - `agent-instructions.json` is the lines that open an agent's first prompt; its `routing` lines are
18
+ the ones agents.house's MCP server instructions carry too.
18
19
 
19
20
  ```ts
20
21
  import { OWNER_RULES, listSkills, readSkill } from '@agentshouse/skills';
@@ -23,7 +24,8 @@ for (const { name } of listSkills()) console.log(name, readSkill(name).length);
23
24
  console.log(OWNER_RULES);
24
25
  ```
25
26
 
26
- `agentInstructions()` returns the first prompt's lines, which name no Local copy,
27
+ `agentInstructions()` returns the first prompt's lines, which name no Local copy, `ROUTING_INSTRUCTIONS`
28
+ its routing lines,
27
29
  `GUIDE` every Guide page with its path, title, first line and text and
28
30
  `EARLIER_OWNER_RULES_SHA256` the earlier texts' digests.
29
31
 
@@ -1,9 +1,16 @@
1
1
  {
2
- "lines": [
2
+ "opening": [
3
3
  "From House:",
4
- "Work with House through the `house` CLI: run `house --help`, and `house run_command '{\"command\":\"guide/open\"}'` opens `index`, the root page of House's guides and formats.",
4
+ "Work with House through the `house` CLI, `house <tool> '<arguments as JSON>'`: `house --help` lists the Tools and `house help <tool>` names a Tool's arguments."
5
+ ],
6
+ "routing": [
7
+ "Read with `shell` and `inspect`. Write with `files_batch` and `records_append`.",
8
+ "For anything else, `find_command`, then `run_command`.",
9
+ "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`."
10
+ ],
11
+ "closing": [
12
+ "`house run_command '{\"command\":\"guide/open\"}'` opens `index`, the root page of House's guides and formats.",
5
13
  "A file reaches the User as a Room Path written with `house upload` and linked in the answer. House serves an App, a Site or a website built by the Guide from `index` (`guide/open`), with no server of your own.",
6
- "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.",
7
- "For another Agent's help, `agent-functions/send` naming one of the Room's Agent function cards under `house/agent-functions/`, or the Agent and what you ask, as its Trust level there allows; its mail comes back into this conversation."
14
+ "For another Agent's help, `agent-functions/send` naming one of the Room's Agent function cards under `house/agent-functions/`, or the Agent and what you ask, as its Standing answers there allow; its mail comes back into this conversation."
8
15
  ]
9
16
  }
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export declare const GUIDE_ROOT = "index";
16
16
  export declare const GUIDE: readonly GuidePage[];
17
17
  export declare const OWNER_RULES: string;
18
18
  export declare const EARLIER_OWNER_RULES_SHA256: readonly string[];
19
+ export declare const ROUTING_INSTRUCTIONS: readonly string[];
19
20
  export declare function agentInstructions(): string[];
20
21
  export declare function listSkills(): readonly Skill[];
21
22
  export declare function readSkill(name: string): readonly SkillFile[];
package/dist/index.js CHANGED
@@ -35,8 +35,9 @@ export const GUIDE = readdirSync(TREE, {
35
35
  export const OWNER_RULES = GUIDE.find((page) => page.path === OWNER_RULES_PAGE).text;
36
36
  export const EARLIER_OWNER_RULES_SHA256 = JSON.parse(packaged('owner-rules-earlier.json'));
37
37
  const INSTRUCTIONS = JSON.parse(packaged('agent-instructions.json'));
38
+ export const ROUTING_INSTRUCTIONS = INSTRUCTIONS.routing;
38
39
  export function agentInstructions() {
39
- return [...INSTRUCTIONS.lines];
40
+ return [...INSTRUCTIONS.opening, ...INSTRUCTIONS.routing, ...INSTRUCTIONS.closing];
40
41
  }
41
42
  export function listSkills() {
42
43
  return readdirSync(SET, { withFileTypes: true })
@@ -7,7 +7,7 @@ The Agent function an Agent runs when a person sets a Wayfinding ticket in progr
7
7
 
8
8
  Work the one ticket this request names, then stop:
9
9
 
10
- 1. Open the `wayfinding-ticket` the request names and set its `conversation` to the id of this conversation, which House's first message names.
10
+ 1. Open the `wayfinding-ticket` the request names and set its `conversation` to the id of this conversation, the one `conversations/active/list` marks `this`.
11
11
  2. Work it by its `kind`: for `research`, find out and say where you found it; for `prototype`, build the smallest thing that answers its Question; for `grilling`, ask your User until its Question is settled; for `task`, do what it asks.
12
12
  3. Write what you found or made as its Answer and set its `status` to `done`.
13
13
  4. Start no other ticket, not even one this ticket unblocks.
@@ -4,6 +4,7 @@ How to build an App over your User's Rooms and publish it, one page per step.
4
4
 
5
5
  An App is a web page over your User's Rooms, for your User and whom they share it with (`shares/create`). House serves the built files and gives the page the App runtime, which holds every bound Room in the browser and syncs it with House. There is no server, backend or House API of yours to call.
6
6
 
7
+ - [Design an App](apps/design)
7
8
  - [Start an App](apps/start)
8
9
  - [An App's data](apps/data)
9
10
  - [Local first](apps/local-first)
@@ -10,7 +10,8 @@ object House hands the page as `window.houseBridge`, which `houseBridge()` of
10
10
  resolves to a handle: `chat.update(options)` replaces its options and `chat.unmount()`
11
11
  removes it. It fills the element and lays itself out by the width it gives: the list of the
12
12
  User's Agents and their chats or one chat on a narrow element, the list beside the chat on a
13
- wider one, and the chat's details beside both on a wide one. With no chat open it shows what
13
+ wider one once the User shows it or the element passes 1000 px, and the chat's details beside
14
+ both on a wide one. With no chat open it shows what
14
15
  asks the User and what works now. `conversation` opens one chat, and with it `null` the Chat
15
16
  opens a new chat with `agent`, the Agent it goes to, or the list with neither. `onOpen` hears
16
17
  each chat it opens as `{ conversation, agent }`, and both `null` when the User goes back to the
@@ -19,7 +20,10 @@ Or mount it as the Chat button, which takes no element:
19
20
  `const button = await houseBridge.chat.mountButton(options)` draws House's dot at the
20
21
  window's bottom-right corner, which counts what asks the User or, while nothing does, the chats
21
22
  that replied since the User opened them, and breathes while a turn runs; pressing it opens the
22
- Chat in a sheet above the page, and pressing it again hides it. The dot stays in view over the
23
+ Chat in a sheet above the page, and pressing it again hides it. With `agent` among the options
24
+ the dot's first press opens it on a new chat with that Agent instead of a chat kept from an
25
+ earlier visit, and later presses reopen the chat it last showed; the sheet stays hidden until
26
+ the first press. The dot stays in view over the
23
27
  sheet, which the User moves, resizes up to the window's edges and lets fill the window, and the
24
28
  page under it never changes, so leave nothing of the page to make room for it.
25
29
  `button.open(target)` opens the sheet, on `{ conversation }` or on a new chat with
@@ -10,8 +10,9 @@ images and other resources only from origins the Provider's policy allows. An ag
10
10
  never read the authorization, load code from a Room, call the Provider directly, or rebase
11
11
  a refused Change Batch.
12
12
 
13
- MD UI uses semantic HTML, is fully operable from a keyboard with labelled controls and
14
- the browser's own focus mark, and lays out from a 360 pixel wide viewport to a desktop.
13
+ MD UI uses semantic HTML, is fully operable from a keyboard with labelled controls, styles
14
+ no focus, removes the browser's own focus ring and takes no focus by itself, and lays out
15
+ from a 360 pixel wide viewport to a desktop.
15
16
  Its strings are English only, dates and numbers follow the browser locale, and content is
16
17
  never translated. Light and dark themes come from CSS custom properties, defaulting to the
17
18
  browser's preference and respecting an App's override. What an agent adds carries none of
@@ -0,0 +1,10 @@
1
+ # Which function does what
2
+
3
+ Which part of House does each kind of an App's work, and what each run costs.
4
+
5
+ - **The page** reads and writes the bound Rooms on the device, offline too, and runs only while someone has it open ([Local first](apps/local-first)).
6
+ - **An App function** runs on House's servers when a Trigger of its Room fires, with no page open and no model. It reads and writes that Room and calls the services `external_origins` lists, within <function-time> and <function-memory> a run. It does what a script can: computing, filing, copying, counting, scheduling, calling a service ([App functions](apps/functions)).
7
+ - **An Environment function** is a script your User kept for one of their computers, which Kit runs there as them. It does work that needs that computer, its files or its logins. A Trigger of their Private Room, their Run now or `environments/functions/run` starts one; a Trigger of any other Room never does ([wizard](skills/wizard)).
8
+ - **An Agent function** starts a conversation of one of your User's Agents, which reads, judges and writes in minutes rather than milliseconds. Every run spends your User's own provider budget, and nothing bounds how many a Room's Triggers start. It does what needs judgment: reading a message, answering it, deciding what a document means ([Agent function](reference/agent-function)).
9
+ - **A job that needs judgment in one step** splits: an App function does the rest, and an Agent function only that step, writing its answer into the Room, where a Trigger hands it on to the App function ([Agent function](reference/agent-function)).
10
+ - **With no page open**, work happens only through a Trigger whose action notifies someone or runs one of these ([Triggers](reference/triggers)).
@@ -0,0 +1,14 @@
1
+ # Designs that load House
2
+
3
+ What an App's saves, Triggers and functions cost House's servers and every device holding its Rooms.
4
+
5
+ - **A save** stores the document's whole new text as a revision, tests it against every Trigger of its Room inside the write, streams the whole text to every device holding the Room and counts once against the saver's Write limit, whatever its size. A log or a state kept in one growing document costs its whole size on every change: keep one small document per thing.
6
+ - **Saving as someone types**, or on a timer, writes each time; the Room Draft keeps what they type until they save ([Room Drafts](apps/room-drafts)).
7
+ - **Every device** holding a Room at the Keep or Open level holds all of its text ([Access level](reference/access-level)), so records written by the thousand fill every holder's device; a file goes in as a file ([files](apps/runtime-files)), never as text in a document.
8
+ - **A Trigger** whose change test is any edit, `changes: "text"`, acts on every save of a document it matches, while a field's change, or the default firing as a document starts to match, acts once per change that matters ([Triggers](reference/triggers)).
9
+ - **A function's writes** fire Triggers, its own included: one whose write matches its own Trigger runs again on each run until that Trigger's <trigger-events-per-hour> Events in the hour are spent, so write what it writes so that its Trigger does not match it ([App functions](apps/functions)).
10
+ - **A function run** reads, on every Event, every document of the types, folders and paths its query names from House's database, within <function-memory>, and one Room owner's runs take at most <function-owner-slots> slots on a server at once: name the narrowest folder or paths, or read a summary document the function keeps ([App functions](apps/functions)).
11
+ - **One Trigger's Events** run one after another, so a busy Trigger builds a queue ([App functions](apps/functions)).
12
+ - **An Agent function run** spends your User's provider budget each time ([Agent function](reference/agent-function)).
13
+ - **Changing a field's `title`** relabels every document of its type in one write ([Schema](reference/schema)).
14
+ - **A query naming no `types`, `pathPrefix` or `paths`** reads every document of its Rooms on the device: name the narrowest and select only what a screen shows ([Local Query](apps/local-query)).
@@ -0,0 +1,11 @@
1
+ # An App's look
2
+
3
+ What House draws around an App's page, and what the App sets for its look.
4
+
5
+ - **The page is the App's own**, drawn and run by the App ([An App's page](apps/page)). Over it House draws only its wait until the page shows its place, and the dialog asking a User who does not own the App to allow a browser feature or House act the App uses for the first time.
6
+ - **The Chat**, where the page mounts it, draws in the App's frame: the Chat button's dot in House's look, and the Chat in its own, which the `--chat-*` properties theme ([The Chat](apps/chat)).
7
+ - **The design system's tokens**: mdui's `style.css` holds House's design tokens as CSS custom properties, light and dark: its greys, type sizes, spacing, radii and motion. An App built on them looks like House; changing them gives it a look of its own. The design system itself is not published, so mdui's tokens are how an App takes it.
8
+ - **Light and dark**: House's screens around the page, such as its wait until the page shows its place, follow the device's light or dark setting, as `prefers-color-scheme` does.
9
+ - **The top colour**: declare the page's top colour as `theme_colour` in `app.json`, and `theme_colour_dark` where the dark one differs ([App manifest](reference/app-manifest)). An installed App's title bar and a phone's top strip take it, and House's white or near-black ground without it.
10
+ - **The icon** is the owner's setting, never the release's: one image in the browser tab, the panel and on a home screen. It is the App cube in the App's colour until one is set, then a generated one from one to four colours or the owner's own square PNG or SVG, through `apps/tab-icon/set`; `bridge.setTab({ title, icon })` changes the open tab's title and icon until the next load.
11
+ - **No focus mark**: House's own screens draw none, and [An App's page](apps/page) says how a page removes the browser's ring.
@@ -0,0 +1,12 @@
1
+ # Schemas that read well
2
+
3
+ How an App's types make documents that read well as Markdown, to people and agents alike.
4
+
5
+ A typed document writes its values as Markdown, so its schema decides how it reads; [Schema](reference/schema) holds the format.
6
+
7
+ - A field named `name` is the document's `#` line, and a type with none writes documents with no title line.
8
+ - `title` on a field is the label its line shows, and a field with none shows its key.
9
+ - Each field takes one JSON type. A short value is one line, a yes or no a checkbox line and a `reference` a link; Markdown text (`contentMediaType: text/markdown`) and a value that may hold a line break take a heading of their own; a value of no single type is written as a fenced `json` block.
10
+ - Rows of short values read as a table, and a list of groups that is no table takes one heading per item, named by its `name`.
11
+ - Nest values at most five levels deep, each list's items counting as a level. A value that takes a heading goes one heading level deeper per level, from `##` to `######`, so a schema that places one deeper is refused `heading-depth`, and a deeper plain string that holds a line break is refused `malformed-value` when it is written.
12
+ - One document is one thing, such as a task, a lead or an order: link others by `reference` rather than copying or nesting them, and keep a growing list of records as a folder of documents.
@@ -0,0 +1,12 @@
1
+ # Design an App
2
+
3
+ What an App's design settles before its code, one page per question House answers.
4
+
5
+ - [Which function does what](apps/design-functions): which part of House does each piece of the App's work, and what it costs.
6
+ - [Schemas that read well](apps/design-schema): how its types make documents that people and agents read.
7
+ - [Designs that load House](apps/design-load): what its saves, Triggers and functions cost, and the designs that cost less.
8
+ - [An App's look](apps/design-look): what House draws around its page, and what the App sets.
9
+
10
+ Keep a Room for the App where you write how it is developed: its decisions, its map and tickets, and what its Triggers and functions do. Name that Room in the App's README, so the next agent working on the App finds it. House reads nothing there.
11
+
12
+ [Example: tasks](apps/example-tasks) follows these pages.
@@ -2,7 +2,7 @@
2
2
 
3
3
  How a function hands each new lead to the manager on shift with the fewest leads since their shift began, records its decision and calls the CRM.
4
4
 
5
- A CRM posts each new lead to the Room's push address (`capture/push-addresses/create`) as `{"event": "lead.created", "card": {"id": 101}}`. The Room holds three types: `staff/<key>.md` with `manager_id`, the manager's id at the CRM; `shifts/<key>.md` with `staff`, the key, and `starts` and `ends` as ISO instants; and `decisions/<card>.md` with `card`, `staff` and `decided_at`. The App's `external_origins` lists the CRM's origin, the Room holds a Service key for it, and a Trigger runs the function for each lead:
5
+ A CRM posts each new lead to the Room's push address (`capture/push-addresses/create`) as `{"event": "lead.created", "card": {"id": 101}}`. Name where the CRM puts its event id as the address's `event_id`, when you create it or later with `capture/push-addresses/edit`, so a lead it sends again writes nothing; an address naming none takes a `webhook-id` header as the id. The Room holds three types: `staff/<key>.md` with `manager_id`, the manager's id at the CRM; `shifts/<key>.md` with `staff`, the key, and `starts` and `ends` as ISO instants; and `decisions/<card>.md` with `card`, `staff` and `decided_at`. The App's `external_origins` lists the CRM's origin, the Room holds a Service key for it, and a Trigger runs the function for each lead:
6
6
 
7
7
  ```json
8
8
  {
@@ -12,8 +12,8 @@ function taskCreation(draft: Draft) {
12
12
  : undefined;
13
13
  }
14
14
 
15
- function titleOf(draft: Draft): unknown {
16
- return draft?.parts.fields?.title?.value ?? taskCreation(draft)?.fields.title;
15
+ function nameOf(draft: Draft): unknown {
16
+ return draft?.parts.fields?.name?.value ?? taskCreation(draft)?.fields.name;
17
17
  }
18
18
 
19
19
  function el<K extends keyof HTMLElementTagNameMap>(
@@ -26,29 +26,29 @@ function el<K extends keyof HTMLElementTagNameMap>(
26
26
  return node;
27
27
  }
28
28
 
29
- function titleForm(
29
+ function nameForm(
30
30
  action: string,
31
31
  room: string,
32
32
  path: () => string,
33
33
  shown: unknown,
34
- parts: (title: string, held: Draft) => Parts,
35
- done: (title: HTMLInputElement) => void,
34
+ parts: (name: string, held: Draft) => Parts,
35
+ done: (name: HTMLInputElement) => void,
36
36
  ) {
37
37
  let held = drafts.current().find((draft) => draft.room === room && draft.path === path()) ?? null;
38
- const title = el('input', {
39
- value: String(titleOf(held) ?? shown),
38
+ const name = el('input', {
39
+ value: String(nameOf(held) ?? shown),
40
40
  required: true,
41
- ariaLabel: 'Title',
41
+ ariaLabel: 'Name',
42
42
  });
43
43
  const keep = () =>
44
44
  queued(async () => {
45
- const written = await write(room, path(), held, parts(title.value, held));
45
+ const written = await write(room, path(), held, parts(name.value, held));
46
46
  held = written.draft;
47
- if (written.stale) title.value = String(titleOf(held) ?? shown);
47
+ if (written.stale) name.value = String(nameOf(held) ?? shown);
48
48
  return written.stale;
49
49
  });
50
- title.oninput = () => void keep();
51
- const form = el('form', {}, title, el('button', {}, action));
50
+ name.oninput = () => void keep();
51
+ const form = el('form', {}, name, el('button', {}, action));
52
52
  form.onsubmit = async (event) => {
53
53
  event.preventDefault();
54
54
  const stale = await keep();
@@ -56,16 +56,16 @@ function titleForm(
56
56
  if (stale) return void (note.textContent = 'Not saved: stale.');
57
57
  if (!(await save(room, held))) return;
58
58
  held = null;
59
- done(title);
59
+ done(name);
60
60
  };
61
61
  return form;
62
62
  }
63
63
 
64
64
  function editForm(room: string, path: string, shown: unknown) {
65
- const parts = (title: string): Parts => ({
66
- fields: { title: { value: title, base: { value: shown } } },
65
+ const parts = (name: string): Parts => ({
66
+ fields: { name: { value: name, base: { value: shown } } },
67
67
  });
68
- return titleForm(
68
+ return nameForm(
69
69
  'Save',
70
70
  room,
71
71
  () => path,
@@ -76,22 +76,22 @@ function editForm(room: string, path: string, shown: unknown) {
76
76
  }
77
77
 
78
78
  function addForm(room: string) {
79
- const parts = (title: string, held: Draft): Parts => {
79
+ const parts = (name: string, held: Draft): Parts => {
80
80
  const kept = taskCreation(held) ?? { type: 'task', fields: { done: false }, sections: {} };
81
- return { creation: { ...kept, fields: { ...kept.fields, title } } };
81
+ return { creation: { ...kept, fields: { ...kept.fields, name } } };
82
82
  };
83
83
  const adding = drafts
84
84
  .current()
85
85
  .find((draft) => draft.room === room && draft.path.startsWith('tasks/') && taskCreation(draft));
86
86
  let path = adding?.path ?? `tasks/${crypto.randomUUID()}.md`;
87
- return titleForm(
87
+ return nameForm(
88
88
  'Add',
89
89
  room,
90
90
  () => path,
91
91
  '',
92
92
  parts,
93
- (title) => {
94
- title.value = '';
93
+ (name) => {
94
+ name.value = '';
95
95
  path = `tasks/${crypto.randomUUID()}.md`;
96
96
  },
97
97
  );
@@ -23,7 +23,7 @@ function draw(): void {
23
23
  note.textContent = '';
24
24
  if (room === undefined) main.replaceChildren('This App has no Room yet.');
25
25
  else if (open !== undefined)
26
- main.replaceChildren(editForm(open.room, open.path, open.fields!.title), note);
26
+ main.replaceChildren(editForm(open.room, open.path, open.fields!.name), note);
27
27
  else main.replaceChildren(list, addForm(room.locator), note);
28
28
  }
29
29
  if (!('pending' in answer)) shown();
@@ -35,7 +35,7 @@ function draw(): void {
35
35
  const done = task.fields!.done === true;
36
36
  const box = el('input', { type: 'checkbox', checked: done, ariaLabel: 'Done' });
37
37
  box.onchange = () => void queued(() => toggle(task.room, task.path, done));
38
- const link = el('a', { href: task.path }, String(task.fields!.title));
38
+ const link = el('a', { href: task.path }, String(task.fields!.name));
39
39
  link.onclick = (event) => {
40
40
  event.preventDefault();
41
41
  void bridge.push(`/${task.path}`);
@@ -2,14 +2,14 @@
2
2
 
3
3
  A whole tasks App over one Room, keeping every rule of the App pages; its `main.ts` goes on over two more pages.
4
4
 
5
- `schema.yaml`, also written into the Room's `settings/schema.yaml`:
5
+ `schema.yaml`, also written into the Room's `settings/schema.yaml`, so a task reads as its `# Buy milk` line over a `- [ ] Done` line:
6
6
 
7
7
  ```yaml
8
8
  task:
9
9
  properties:
10
- title: { type: string }
11
- done: { type: boolean }
12
- required: [title, done]
10
+ name: { type: string }
11
+ done: { type: boolean, title: Done }
12
+ required: [name, done]
13
13
  ```
14
14
 
15
15
  `app.json`, then `apps/bindings/set` with slot `tasks`:
@@ -34,8 +34,8 @@ const sync = await runtime.observe('sync-status');
34
34
  const drafts = await runtime.observe('room-drafts');
35
35
  const tasks = await runtime.observe('query', {
36
36
  pathPrefix: 'tasks',
37
- select: { fields: ['/title', '/done'] },
38
- orderBy: [{ by: { field: '/title' } }],
37
+ select: { fields: ['/name', '/done'] },
38
+ orderBy: [{ by: { field: '/name' } }],
39
39
  });
40
40
  const main = document.querySelector('main')!;
41
41
  const list = document.createElement('div');
@@ -0,0 +1,10 @@
1
+ # Times in an App function
2
+
3
+ How a function reads its Room's local time and turns a date and time there into a moment and back, with no time-zone library.
4
+
5
+ - **The event.** Beside `moment`, the UTC instant `Date` reads, the event carries the Room's `time_zone`, such as `Europe/Kyiv`, and the moment's `date` (`YYYY-MM-DD`) and `time` (`HH:MM:SS`) there, so the same day is the Room's day: `order.fields.delivery === event.date`.
6
+ - **To a moment.** `await house.toMoment({ date: '2026-11-10', time: '13:00' })` answers the ISO instant of a date and an `HH:MM` or `HH:MM:SS` time in the Room's zone, `'2026-11-10T11:00:00.000Z'` in Kyiv. At daylight saving, a time the clocks skip takes the moment just after the gap, `04:00` for `03:30` on the day Kyiv's clocks jump from `03:00` to `04:00`, and a time they repeat takes its earlier moment.
7
+ - **To the Room's time.** `await house.toLocal({ moment: event.moment })` answers the `time_zone`, `date` and `time` in the Room's zone of a date-time with its offset, written as a document's `date-time` field takes it.
8
+ - **Each is a call to House** that counts as no external call and rejects what it refuses with its `code`: `invalid-time` for what is no date and time, or no date-time with its offset, and `unknown-room` for a `room_ref` naming any Room but the run's own.
9
+
10
+ Next: [App functions](apps/functions)
@@ -2,12 +2,12 @@
2
2
 
3
3
  How an App declares a function that House runs on its servers when a Trigger fires, with no page open and no model.
4
4
 
5
- - **Declare.** `app.json` lists `functions`, each `{"name": "assign lead", "entry": "functions/assign.js"}` ([App manifest](reference/app-manifest)). The entry is one ES module whose default export is the function and which imports nothing, so your build bundles each function, with all it imports, into `dist/`, such as `esbuild src/assign.ts --bundle --format=esm --outfile=dist/functions/assign.js`. A publish is refused whole for an entry that is missing, imports, has no default export or is over <function-size>, for a name used twice, and for more than <release-functions> functions.
6
- - **Run.** A Trigger's `function` action names the App and one of its functions ([Triggers](reference/triggers)). House runs the App's latest release when the Event's turn comes, one Event of a Trigger after another, so a run reads what the run before it wrote. Runs share a fixed number of slots on House's servers, and a run waits, its action `waiting`, until one is free.
7
- - **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment` and what matched: `path` and `reference`, a document change's `transition`, an arriving item's `item` with its `headers` and raw `body` text, a dated document's `document`, a delivered notification's `notification`, the facts of its web document, or a message's `message`, its `sender` and its request's `agent_function`, never its words, with the document that request was started for as `path` and `reference`. `Date` reads `moment`. The return value is ignored, so write what you keep into the Room.
8
- - **The App API.** `house` is the page's App API over the Trigger's Room alone: `query(q)` ([Local Query](apps/local-query)), which reads only the documents of the `types`, folders (`folder`, `pathPrefix`) and `paths` it names and answers over them alone, so the rest of a large Room is never read: name the `types` or `pathPrefix` of its `hop` and `backlinks` too, and select its parts, since a query or `hop` or `backlinks` naming none, or selecting `backlinks` or `'all'`, is refused `invalid-query`, `saveParts(room, parts)`, `call(name, input)` for the Room acts, each by its House act name, such as `rooms/members/list` and `triggers/fired/list`, and `callExternal(request)`. `query` and `callExternal` reject a refusal with its `code` for the function to catch, while `call` answers it as a result whose `kind` is `rejected` and `saveParts` as `outcome: 'refused'`, as on the page, so check what they answer. Its writes are the App's, by `App <App> (function <function>)`, fire Triggers, its own included, and count against the Room owner's Write limit.
9
- - **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`: keep times as ISO instants, or convert a local time by its UTC offset yourself. `callExternal` takes the request `bridge.callExternal` takes and is the one way out, to an origin the release's `external_origins` lists; naming its Room, `room_ref: event.room`, carries that Room's Service key, which the function never sees, or is refused `not_found` naming the `origin`. Its calls count against the Room owner's `external_calls` limit.
10
- - **Never repeated.** A run that started is never run again, even when it failed after a call reached the service. Send `event.id` as the service's idempotency key where it takes one, and name what you write by what the event carries, so a second delivery of the same thing finds it.
11
- - **Failure and limits.** A throw settles the run `failed` with `function_failed` and its text, which `triggers/fired/list` shows and the panel shows the Room's owner, with no notification; the next Event still runs. A run ends `failed` at <function-time>, at <function-memory> or past <function-calls> external calls: `time_cap_reached`, `memory_cap_reached` or `external_call_cap_reached`.
5
+ - **Declare.** `app.json` lists `functions`, each `{"name": "assign lead", "entry": "functions/assign.js"}` ([App manifest](reference/app-manifest)). The entry is one ES module that imports nothing and default-exports the function, so your build bundles each function with its imports into `dist/`, such as `esbuild src/assign.ts --bundle --format=esm --outfile=dist/functions/assign.js`. A publish is refused whole for an entry that is missing, imports, has no default export or is over <function-size>, for a name used twice, and for more than <release-functions> functions.
6
+ - **Run.** A Trigger's `function` action names the App and one of its functions ([Triggers](reference/triggers)). House runs the App's latest release when the Event's turn comes, one Event of a Trigger at a time, so a run reads what the one before wrote. Runs share a fixed number of slots on each server, one Room owner's at most <function-owner-slots>, and a run waits, its action `waiting`, until it may take one.
7
+ - **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment`, the Room's `time_zone`, `date` and `time` ([Times](apps/function-times)) and what matched: `path` and `reference`, a document change's `transition`, an arriving item's `item` with its `headers` and raw `body` text, a dated document's `document`, a delivered notification's `notification`, the facts of its web document, or a message's `message`, its `sender` and its request's `agent_function`, never its words, with the document that request was started for as `path` and `reference`. `Date` reads `moment`. The return value is ignored: write what you keep into the Room.
8
+ - **The App API.** `house` is the page's App API over the Trigger's Room alone: `query(q)` ([Local Query](apps/local-query)), which reads only the documents of the `types`, folders (`folder`, `pathPrefix`) and `paths` it names: name the `types` or `pathPrefix` of its `hop` and `backlinks` too and select its parts, since naming none, or selecting `backlinks` or `'all'`, is refused `invalid-query`, `saveParts(room, parts)`, `call(name, input)` for the Room acts by House act name and `callExternal(request)`. `call` answers a refusal as `kind: 'rejected'` and `saveParts` as `outcome: 'refused'`, as on the page; the others reject with its `code`. Its writes are the App's, by `App <App> (function <function>)`, fire Triggers, its own included, and count against the Room owner's Write limit.
9
+ - **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`: convert times by `house.toMoment` and `house.toLocal`, which a page lacks. `callExternal` takes the request `bridge.callExternal` takes and is the one way out; naming its Room, `room_ref: event.room`, carries that Room's Service key, which the function never sees; calls count against the Room owner's `external_calls` limit. Its webhook signing secret goes nowhere: `call('service-keys/signatures/check', { room_ref, origin, text, signature })` answers `matched`, whether `signature` is `text`'s HMAC-SHA256 under it, lowercase hex or base64. Both take only an origin the release's `external_origins` lists and are refused `not_found` naming the `origin` where the Room holds none.
10
+ - **Never repeated.** A run that started is never run again, even when it failed after a call reached the service. Send `event.id` as the service's idempotency key where it takes one, and name what you write by what the event carries, so a second delivery finds it.
11
+ - **Failure and limits.** A throw settles the run `failed` with `function_failed` and its text, which `triggers/fired/list` and the Room owner's panel show, with no notification; the next Event still runs. A run ends `failed` at <function-time>, at <function-memory> or past <function-calls> external calls: `time_cap_reached`, `memory_cap_reached` or `external_call_cap_reached`.
12
12
 
13
13
  Next: [Example: assign a lead](apps/example-function)
@@ -2,13 +2,13 @@
2
2
 
3
3
  What an App's page draws and how it behaves in the browser.
4
4
 
5
- - The App draws everything; House draws nothing around it. Set the tab's title with `bridge.setTab({ title })`.
6
- - The address bar is the shell's, not your page's: read your place from `bridge.address()` and `bridge.onAddress`, and move with `bridge.push` or `bridge.replace`, so Back, Forward, reload and a shared link keep it; a push to the address already shown replaces it, so a double click adds no history entry. After an act that ends what the page shows, and when a dialog closes, `bridge.replace` the next address; a replace with the address this page was opened from goes back to it, so Back returns to where the person came from. Keep the open item, tab, filter and search in the address. Addresses under `/render` and `/info` are House's.
5
+ - The App draws everything on its page; [An App's look](apps/design-look) says what House draws around it. Set the tab's title with `bridge.setTab({ title })`.
6
+ - The address bar is the shell's, not your page's: read your place from `bridge.address()` and `bridge.onAddress`, and move with `bridge.push` or `bridge.replace`, so Back, Forward, reload and a shared link keep it; a push to the address already shown replaces it, so a double click adds no history entry. After an act that ends what the page shows, and when a dialog closes, `bridge.replace` the next address; a replace with the address this page was opened from goes back to it, so Back returns to where the person came from. Keep the open item, tab, filter and search in the address. Addresses under `/render`, `/info` and `/home-screen` are House's.
7
7
  - Files: take one in with `binaryFile.attach` the moment it is chosen and keep the Path it answers, hand one out with `binaryFile.download`, and show an image or video in `<room-media>` naming its width and height, so nothing waits for media and nothing moves when it arrives; never read a file whole to show or download it ([files](apps/runtime-files)).
8
8
  - Mobile first: lay out from 360 pixels wide up, with no action behind hover alone.
9
- - Every control works from the keyboard and has a label; give it no focus style of your own and never remove the browser's focus mark.
9
+ - Every control works from the keyboard and has a label. Draw no focus mark: give nothing a focus style, and remove the browser's own ring with `* { outline: none; }`, the reset House's base carries, since that base does not reach your page. Nothing takes focus by itself: no `autofocus`, and no `focus()` that runs because a page or dialog opened.
10
10
  - An empty list says what it will hold and invites the first document.
11
11
  - Long names truncate with an ellipsis and show in full on hover.
12
12
  - The element is the link: a row, card or name opens its item; no "Open" buttons.
13
- - Offer installation yourself: `bridge.install()` from a click shows the browser's prompt, or answers `manual` where you show how.
13
+ - Offer installation yourself: `bridge.install()` from a click starts the install or shows the browser's prompt, or answers `manual` where you show how.
14
14
  - Offer a way out where your User expects one: `bridge.ceremony('/sign-out')` signs them out of House and opens sign-in.
@@ -12,6 +12,16 @@ option and `conversation.retry(conversation, message, operation)` sends a refuse
12
12
  again. A release that declares no conversations is refused each with `action-undeclared`
13
13
  naming its act.
14
14
 
15
+ `conversation.attach(file)` attaches a `File` to a message, whatever Rooms the App reaches:
16
+ House writes it into the User's Private Room at `attachments/<YYYY-MM-DD>/<name>` and it
17
+ answers `{ kind: 'attached', link }`, the file's link with its key, which opens that one
18
+ file to whoever holds the link, so write it into the chat draft or the text you send; or
19
+ `{ kind: 'offline' }`, writing nothing, or a refusal or failure.
20
+ `conversation.attachment(link)` answers such a link's file as `{ kind: 'link', url, name,
21
+ mediaType }`, its short-lived address, `rejected` `not_found` for a key that opens nothing,
22
+ or a failure. Without conversations the attach is refused `action-undeclared` and the read
23
+ answers `absent`.
24
+
15
25
  `conversation.pin(figures)` keeps the Chat's pinned figures, which every conversation's
16
26
  `pins` shows, `conversation.openTurn(conversation, turn)` each chat's last opened turn,
17
27
  which its row's `opened` shows beside `answered`, the turn its Agent answered last, and
@@ -1,6 +1,6 @@
1
1
  # Refusals, deployment facts and the member
2
2
 
3
- How the MD Runtime API refuses and fails, and how App code reads the deployment facts and who uses it.
3
+ How the MD Runtime API refuses and fails, and how App code reads the deployment facts, who uses it and their time zone.
4
4
 
5
5
  A refusal or failure carries `code` and `retryable`: each Call and History answer other
6
6
  than its result, a refused Save, a returned Room Draft's reason, a Room's refusal and every
@@ -21,4 +21,6 @@ named; signing out answers `null`, then ends the observation with the authorizat
21
21
  `observe('authority-changes')` answers how many times House told the runtime that what its
22
22
  User may see changed, a Room's members, a Share or an offer among them, and hears each next
23
23
  one, never a reconnection, so a view that read such a fact through Call reads it again when
24
- the count moves.
24
+ the count moves. `observe('time-zone')` answers that User's IANA time zone, `null` until the
25
+ Provider names one and where they hold none, kept and heard like the member; the Chat shows its
26
+ times in it, and a page may too.
@@ -2,7 +2,7 @@
2
2
 
3
3
  How `search` finds Documents by words through House, and how a page finds them offline.
4
4
 
5
- `search({ text, rooms })` asks House's search, the one Agents use, once and online: there
5
+ `search({ text, rooms })` asks House's search once and online: there
6
6
  is no observed search and the device keeps no full-text index. `rooms` names Room locators
7
7
  and defaults to every Room the page binds; the search never leaves those Rooms, and finds
8
8
  words in every Document there.
@@ -2,11 +2,14 @@
2
2
 
3
3
  How to get an App's repository, build it and publish every change.
4
4
 
5
+ Before these steps, design a new App, or a change to an App's types, functions or look, with [Design an App](apps/design).
6
+
5
7
  - An App with an `a_` reference already exists, even with no release yet, as every App your User makes in the panel does. Clone its `.git` address, `<origin>/app/<app_ref>.git` or its `source_url` in `apps/list`, with `git clone -c user.name=<agent-git-name> -c user.email=<agent-git-email> <address>`, so Git commits as you in every later step too. Run `npm ci --ignore-scripts`, then publish with `npm run publish`, never with `--name`.
6
8
  - Git signs in as you with a credential your User makes for you on `<origin>/connections`, given as its password.
7
9
  - When the Kit runs you, add `-c 'credential.helper=!kit git-credential'` to the clone instead, so the Kit signs Git in.
8
10
  - Only when no App exists yet, clone `<origin>/app/new.git` the same way: it gives mdui, a minimal App in plain TypeScript with its test and `npm run publish`. Run `npm ci --ignore-scripts`, then `npm run publish -- --name "<display name>"`, which creates the App and prints its address and its `.git` address. Then `git remote set-url origin <that .git address>`; every later `npm run publish` goes there, without `--name`.
9
11
  - Use any framework and build tool. `npm run build` leaves the whole release in `dist/`: the entry HTML, every file it loads, `app.json` and `schema.yaml`. The entry names files by relative paths (Vite: `base: './'`), carries no inline script and loads nothing from outside the release, so bundle fonts and libraries. A release holds at most <release-files> files and <release-size>.
10
12
  - Your User sees a change only once it is published, so an ask to change an App is an ask to publish it: end every change with a commit and `npm run publish`, which builds, pushes `main` and the build, and prints `published` or the reason House refused. A `git push` of `main` alone publishes nothing and says so. A refused push changes nothing and the previous release keeps running.
13
+ - When a House update removes or narrows something your App's code uses, House commits a fix to its `main`, as `House`, and publishes it. A push refused with `fetch first` means someone committed after your clone: `git pull`, then publish again.
11
14
  - Without Git, zip the project folder after the build, without `node_modules` and `.env`, `upload` the ZIP to a Room and publish it with `apps/publish-zip`.
12
15
  - A page whose first draw is not yet the place it opened on, as one that draws its Room's documents once `observe` answers, registers `bridge.cooperate({ placed })` in its entry script before its first draw, where `placed` resolves once that place shows what it holds, as [the example](apps/example-tasks) does: House keeps its preparation surface over the page until then, and shows a page that registers none at its first draw. An open page keeps the release it loaded and runs the next one at its next load, so it needs no update code; `node_modules/@agentshouse/app-bridge/docs/families.md` describes each bridge member.
package/guide/index.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # House
2
2
 
3
- Where House's guides and formats start: `guide/open` opens a page by its `path` or finds pages by `words`.
3
+ Where House's guides and formats start: `guide/open` opens a page by its `path` or finds pages by a `query`.
4
4
 
5
5
  House keeps Markdown files in Rooms, which your User shares with other people and their agents.
6
6
 
7
- While your User owns no Room but the Private Room, as `rooms_list` shows, open [House Onboarding](skills/house-onboarding) first.
8
-
9
- - `apps/`, building an App over your User's Rooms: [Build an App](apps/build), and the functions House runs for it when a Trigger fires: [App functions](apps/functions).
7
+ - `apps/`, building an App over your User's Rooms: [Design an App](apps/design) first, then [Build an App](apps/build), and the functions House runs for it when a Trigger fires: [App functions](apps/functions).
10
8
  - `reference/`, the formats House reads: [Access level](reference/access-level), [App manifest](reference/app-manifest), [Schema](reference/schema) and [Triggers](reference/triggers).
11
9
  - `agent-functions/`, the Agent function an Agent takes a plan's tickets by, written for the plan's Room and naming that Agent as [Agent function](reference/agent-function) says: [Work on ticket](agent-functions/work-on-ticket).
12
10
  - `skills/`, House's Skills: [Prototype an App](skills/prototype-app).
@@ -26,11 +26,9 @@ The skills are already set up; never run `setup-matt-pocock-skills`.
26
26
  with `type: wayfinding-map` in its frontmatter, and its tickets,
27
27
  `wayfinding-ticket` documents in order as `projects/<name>/map/NN-<slug>.md`
28
28
  with `type: wayfinding-ticket`, since House knows a map or a ticket only by
29
- its `type`: a ticket carries its `name` as its `#` line, its
30
- `map` (a link to the map), its `kind` (its type) and `blocked_by` (its
31
- blockers); a claim sets its `status` to `in-progress` and its
32
- `assignee`, and a resolution writes its Answer and sets `done`, or
33
- `out-of-scope`
29
+ its `type`, whose fields the Guide's `reference/wayfinding-types` lists; a
30
+ claim sets its `status` to `in-progress` and its `assignee`, and a
31
+ resolution writes its Answer and sets `done`, or `out-of-scope`
34
32
  - Research: `research/<slug>.md`
35
33
  - Architecture reports (improve-codebase-architecture): `architecture/<slug>.html`
36
34
  - Handoffs: `handoffs/<slug>.md`
@@ -2,7 +2,7 @@
2
2
 
3
3
  How much of a Room a grant or a Share lets its holder do with its text, and where.
4
4
 
5
- Access level, `level` on a Room grant or a Share’s Room item, each level including the one before it: `search`, the Search level, reaches the Room through `search`, `shell` and `inspect` alone, each reply at most <mcp-read-size> of the Room’s text, with no copy anywhere; `open`, the Open level, also opens the Room in browser Apps while the browser is online, and acts as `search` everywhere else; `keep`, the Keep level, also copies the Room to the devices that keep it, offline included, and is the one `export` needs. Apps need `open` or `keep`.
5
+ Access level, `level` on a Room grant or a Share’s Room item, each level including the one before it: `search`, the Search level, reaches the Room through `shell` and `inspect` alone, each reply at most <mcp-read-size> of the Room’s text, with no copy anywhere; `open`, the Open level, also opens the Room in browser Apps while the browser is online, and acts as `search` everywhere else; `keep`, the Keep level, also copies the Room to the devices that keep it, offline included, and is the one `export` needs. Apps need `open` or `keep`.
6
6
 
7
7
  ```json
8
8
  { "room_ref": "r_ab12.1", "level": "open", "operations": ["write"] }
@@ -2,7 +2,7 @@
2
2
 
3
3
  How an Agent function document in the Private Room is written, one for each Room it serves.
4
4
 
5
- Agent function, `/rooms/private/rooms/<room-handle>/agent-functions/<name>.md`, serving the Room `<room-handle>` names: `description`; `input`, a text saying what to send, empty if nothing; `agent`, the identifier of the one of your User's current Agents it starts; and `afk`, `true` for a run nobody watches, off when absent, under which Kit answers every question and permission prompt of the conversation a request starts by itself, for that conversation's life. The body is the instruction, which never leaves the Private Room. Writing it needs your User's write in that Room. House writes its card, the description, input and Agent, at `/rooms/<room-handle>/house/agent-functions/<name>.md`, where every member reads it, and refuses a name a card there already holds as `name_taken`. An Agent function a second Room needs is a copy under that Room's handle. While its document is not written in this form, every request for it is refused `agent_function_unreadable`. Anyone who writes that Room asks it with `agent-functions/send` naming the card, or names the `agent` with their own words, as the Agent's Trust level in that Room allows: under Any request, the default, either starts at once; under Agent functions only the card starts at once and own words are refused `own_text_not_admitted`; under Approve each either waits for the Agent's owner to approve it. Its owner sets the level on the Room's House page; the owner, their own agents and their Private Room's Triggers start it at once at every level, a Trigger of any other Room asks as that Room's writers do, and `inspect` on a Room root names the Agents you may ask there with their levels. An Agent function request's input is text the Agent reads, and House checks no shape. Mail your conversation sends with `agent-functions/mail/send` and a request it sent reaches the conversation that request started at once under Any request; below it, `agent-functions/mail/send` answers `waiting: true` and the mail waits for that Agent's owner, and a declined one comes back to you as House's message saying so. That conversation's mail always comes back to yours. A request's run is the first turn of the conversation it starts: during that turn the Agent names the document it wrote in the request's Room as the request's result with `agent-functions/result/set`, and the turn's end finishes the request. The conversation stays active until it is Done, which `conversations/mark-done` sets at the end of a turn and which nothing undoes, so mail reaches it after its run.
5
+ Agent function, `/rooms/private/rooms/<room-handle>/agent-functions/<name>.md`, serving the Room `<room-handle>` names: `description`; `input`, a text saying what to send, empty if nothing; `agent`, the identifier of the one of your User's current Agents it starts; and `afk`, `true` for a run nobody watches, off when absent, under which Kit answers every question and permission prompt of the conversation a request starts by itself, for that conversation's life. The body is the instruction, which never leaves the Private Room. Writing it needs your User's write in that Room. House writes its card, the description, input, Agent and `answer`, at `/rooms/<room-handle>/house/agent-functions/<name>.md`, where every member reads it, and refuses a name a card there already holds as `name_taken`. An Agent function a second Room needs is a copy under that Room's handle. While its document is not written in this form, every request for it is refused `agent_function_unreadable`. Anyone who writes that Room asks it with `agent-functions/send` naming the card, or names the `agent` with their own words, as the Standing answer there allows: the card's `answer`, or the Agent's for own words. `at_once`, the default, starts it; `ask_me` waits for the owner's approval; `off` refuses it, `agent_function_not_admitted` or `own_text_not_admitted`. Only the owner sets an answer, on the Room's House page; no field of this document holds one. The owner, their agents and their Private Room's Triggers start at once under every answer, a Trigger of another Room asks as its writers do, and `inspect` on a Room root names the Agents you may ask there with their answers. An Agent function request's input is text the Agent reads, and House checks no shape. Mail your conversation sends with `agent-functions/mail/send` and a request it sent reaches the conversation that request started at once where that Agent's own-words answer there is `at_once`; otherwise `agent-functions/mail/send` answers `waiting: true` and the mail waits for that Agent's owner, and a declined one comes back to you as House's message saying so. That conversation's mail always comes back to yours. A request's run is the first turn of the conversation it starts: during that turn the Agent names the document it wrote in the request's Room as the request's result with `agent-functions/result/set`, and the turn's end finishes the request. The conversation stays active until it is Done, which `conversations/mark-done` sets at the end of a turn and which nothing undoes, so mail reaches it after its run.
6
6
 
7
7
  A Trigger's `message` action puts its `text`, a link to what matched and its files into the active conversation, one not Done, its Agent function started from that Room for what the Event is about: the chat of a customer's `chat-message`, the matched document or item, or the Trigger for a `cron` clock. Its Event shows `continued` with that `request`; with none, it starts the Agent function as a `agent_function` action does, `requested`. A conversation waiting for approval refuses it, `refused` with `conversation_waiting`. The Agent reads a customer's message as a customer's words, one from a Trigger of its owner's Private Room as the owner's instruction, and any other as what matched, never an instruction. A `agent_function` action starts a new request on every Event. A `done` action marks Done every active conversation a request of its Trigger's Room started for the Event's document, whoever owns its Agent, but one waiting for approval, which stays for its owner to approve or decline; its Event shows `marked` with their number, `conversations`, 0 when there are none, as on a `cron` clock, whose Event names no document.
8
8
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  What an App's `app.json` declares.
4
4
 
5
- App manifest, `app.json` at an App’s root: `entry`, its HTML page, and `room_slots`, each with a `name`, `access` `read` or `write`, and `types` from the `schema.yaml` beside it, written like a Room schema; `apps/bindings/set` fills a slot with Rooms. Where needed it adds `external_origins`, the https origins its page may reach; `conversations: true` to read your User’s conversations; `inboxes: true` to show customer chats, naming `chat` and `chat-message` in `room_slots`, and answer them through `chats/reply`, since only House writes a chat's documents; `house_acts`, the House acts it calls by their act names, such as `rooms/list` for your User’s Rooms or `rooms/rename`, never one only a person takes or one that changes what an App reaches, except that a Self-hosted Building's App may declare its administrators' acts, `building/people/list`, `building/people/add`, `building/people/remove`, `building/administrators/name`, `building/administrators/take`, `building/factor/require`, `building/factor/stop-requiring` and `building/factor/reset`, which its page calls in an administrator's browser once it has asked them to confirm each; `functions`, each a `name` and the `entry` file a Trigger runs on House's servers ([App functions](apps/functions)); and `theme_colour`, `#rrggbb`. A User who does not own the App is asked nothing when it opens and once to allow each House act it declares, at the App’s first call of that act, which waits on the answer; a declined one is refused. Nobody is asked about an act they have no right to use, and it is refused them unasked, the App's owner included: the administrators' acts `not_building_administrator` to anyone who is no administrator, and `rooms/create`, the acts that change another member's access, `rooms/access/entries/set`, `rooms/access/schema-roles/set` and `rooms/access/revoke`, and the owner acts the Private Room never admits, `rooms/archive`, `rooms/unarchive`, `rooms/public/set`, `rooms/rename`, `rooms/space/set`, `rooms/delete` and `rooms/ownership/cancel`, `operation_denied` to anyone who manages no Room but their Private Room. [Build an App](apps/build) shows how its page works.
5
+ App manifest, `app.json` at an App’s root: `entry`, its HTML page, and `room_slots`, each with a `name`, `access` `read` or `write`, and `types` from the `schema.yaml` beside it, written like a Room schema; `apps/bindings/set` fills a slot with Rooms. Where needed it adds `external_origins`, the https origins its page may reach; `conversations: true` to read your User’s conversations; `inboxes: true` to show customer chats, naming `chat` and `chat-message` in `room_slots`, and answer them through `chats/reply`, since only House writes a chat's documents; `house_acts`, the House acts it calls by their act names, such as `rooms/list` for your User’s Rooms or `rooms/rename`, never one only a person takes or one that changes what an App reaches, except that a Self-hosted Building's App may declare its administrators' acts, `building/people/list`, `building/people/add`, `building/people/remove`, `building/administrators/name`, `building/administrators/take`, `building/factor/require`, `building/factor/stop-requiring` and `building/factor/reset`, which its page calls in an administrator's browser once it has asked them to confirm each; `functions`, each a `name` and the `entry` file a Trigger runs on House's servers ([App functions](apps/functions)); and `theme_colour`, `#rrggbb`: declare the colour of your page's top, which the installed App's title bar and a phone's top strip take, with `theme_colour_dark` where your dark mode's top differs. A User who does not own the App is asked nothing when it opens and once to allow each House act it declares, at the App’s first call of that act, which waits on the answer; a declined one is refused. Nobody is asked about an act they have no right to use, and it is refused them unasked, the App's owner included: the administrators' acts `not_building_administrator` to anyone who is no administrator, and `rooms/create`, the acts that change another member's access, `rooms/access/entries/set`, `rooms/access/schema-roles/set` and `rooms/access/revoke`, and the owner acts the Private Room never admits, `rooms/archive`, `rooms/unarchive`, `rooms/public/set`, `rooms/rename`, `rooms/space/set`, `rooms/delete` and `rooms/ownership/cancel`, `operation_denied` to anyone who manages no Room but their Private Room. [Build an App](apps/build) shows how its page works.
6
+
7
+ Where the Building runs its own bot, the administrators' acts include a person's [one-time link](reference/telegram-link).
6
8
 
7
9
  ```json
8
10
  {
@@ -0,0 +1,7 @@
1
+ # Group post
2
+
3
+ How a Trigger's `notify` action posts in its Room's Telegram group, the one `rooms/telegram/attach` attaches, in place of each User's own notices.
4
+
5
+ With `in_group: true`, e.g. `{kind: "notify", title: "Booking today", text: "A same-day booking came in.", to: {roles: ["admin"]}, in_group: true}`, the action posts once in the group's General topic: its title and text, an Open in House button to what matched, and each User it names who reads the Room, as many as one message holds, mentioned through Telegram, or by display name where they have not linked it. Nobody needs to agree to it, nobody is told on their own channels, and the post is not kept in the Room, so no Trigger fires on it.
6
+
7
+ `triggers/fired/list` shows it `posted` with how many it `mentioned`; `no_target` with `no_group` while the Room has no group or its group is paused, at the firing or the send, and with `no_recipient` when it names no reader; or `failed` with `post_not_confirmed` when Telegram refused it, after one try more past a wait it named, or gave no answer. That post is never sent again, and the Room's owner and the Trigger's creator hear that it needs fixing.
@@ -0,0 +1,7 @@
1
+ # Inbox types
2
+
3
+ The `chat` and `chat-message` types every Room knows without declaring them, as House declares them: an Inbox chat's card and its messages, which only House writes. Each value is written in the document's body as [Schema](reference/schema) says.
4
+
5
+ ```yaml
6
+ <inbox-types>
7
+ ```
@@ -4,7 +4,7 @@ How a Room's `settings/schema.yaml` declares its document types.
4
4
 
5
5
  Schema, `/rooms/<room>/settings/schema.yaml`: one JSON Schema in YAML. Each top-level key is an MD type holding the schema of its documents' values; `$defs` holds shapes reused through `$ref: '#/$defs/<name>'`. An object setting neither `additionalProperties` nor `unevaluatedProperties` refuses other keys. House adds `reference` on a string at any depth, naming the types, or `attachment`, its value links to, and `cross_room: true` beside it, letting that be another Room's document of those types; a link or reference written as its `/rooms/<room>/` path is stored relative to the document; `title` on a field, the label a document shows for it, its key when it has none; `contentMediaType: text/markdown` on a string, Markdown text under its own heading; and `member_document: true` on one type, whose documents name their member with `member`, a User Identifier from `rooms/members/list`. On a type, `create_roles` lists the Schema Roles that may create its documents and `delete_roles` those that may delete them, and changing a document's `type` needs both; on a field of a type's own `properties`, `change_roles` lists those that may change it, `initial_roles` those that may set it on a new document, and `transitions` the `{from, to, roles}` changes it may make, `null` standing for absent; `owner` and `member` are built in, `[]` allows nobody, and only someone who manages the Room's access changes them. `files_batch` creates or replaces the whole text, and may rewrite the documents a change breaks in the same call.
6
6
 
7
- A Room in a Space shows its Space's schema at that path and keeps none of its own: its owner and a member who can write in the Space change it there, a rule keyword only someone who manages the Space, in an edit holding that change alone, checked against every document of every Room of the Space; a change that would break any document is refused naming it, so change that document in its own edit first. A Room taken out of its Space keeps the last version as its own.
7
+ A Room in a Space shows its Space's schema at that path and keeps none of its own: its owner and a member who can write in the Space change it there, a rule keyword only someone who manages the Space, in an edit holding that change alone, checked against every document of every Room of the Space; a change that would break any document is refused naming it, so change that document in its own edit first. A Room taken out of its Space keeps the last version as its own. Every Room also knows the [Inbox types](reference/inbox-types) and the [Wayfinding types](reference/wayfinding-types) without declaring them.
8
8
 
9
9
  A typed document keeps only `type`, `member`, `submitted` and House's other keys in its frontmatter and writes its values in its body: `name` as its `#` line; under it each short value as one `- **Label**: value` line, yes or no as `- [x] Label` or `- [ ] Label`, a reference as a link, a group of short values as an indented list under its label and a list of plain values as indented bullets; a Markdown text, or a value holding a line break, under its own `##` heading, one level deeper per level of nesting; rows of short values as a table whose column headings are labels; any other list as one heading per item, named by the item's `name`; and a value of no single JSON type as one fenced `json` block under its heading. Values come in any order and in any spelling GitHub Markdown reads alike. The list directly under the `#` line is the value list; a heading naming no field, the text around the values and everything after a `---` line is free text. A refusal names the line: `repeated-value` for a value written twice, `unknown-label` for a value line naming no field, `malformed-value` for a value its type's form does not read. Changing a `title` relabels every document of the type.
10
10
 
@@ -0,0 +1,11 @@
1
+ # One-time link
2
+
3
+ How a Self-hosted Building's App gives a person a one-time link to the Building's own bot, and undoes one taken wrongly.
4
+
5
+ An App of a Self-hosted Building that runs its own bot may declare `building/people/telegram-link` and `building/people/telegram-link/undo` beside its administrators' other acts, which its page calls in an administrator's browser once it has asked them to confirm each, and which anyone who is no administrator is refused unasked, `not_building_administrator`.
6
+
7
+ `building/people/telegram-link` takes `{person}`, the id `building/people/list` answers, and answers `{link}`, the bot's start address with a code bound to that person, which the administrator sends them in Telegram. It is given a person added by name who has not registered, whose account its first Join registers with that Telegram account, no email and the name they were added under, or a person who holds an account and an email, to whom it adds that Telegram account; anyone else is refused `telegram_link_refused`. The link works once, lapses after a day, and a fresh copy replaces the one before. A Telegram account another person holds is refused in the bot.
8
+
9
+ `building/people/list` answers beside each person their link's `telegram_link`, `waiting`, `lapsed` or `taken`, and `telegram_name`, the name of the Telegram account that took it.
10
+
11
+ `building/people/telegram-link/undo` takes `{person}`. Where the link registered the account, it removes that account as `building/people/remove` does, the administrator who undoes it as successor, and the person is listed again as added, under their id, so a fresh link can be copied and that Telegram account may take it. Where it added Telegram to an account the person held, it unlinks that Telegram account alone.
@@ -2,14 +2,16 @@
2
2
 
3
3
  How a Trigger's `condition` and `actions` are written for `triggers/create` and `triggers/edit`; House writes a Room's Triggers as read-only `house-trigger` documents at `house/triggers/<trigger>.md`.
4
4
 
5
- `condition`: When it fires: `on`, a `query` in the [Local Query](apps/local-query) language, at most one of `types`, `pathPrefix` or `paths` (one document, followed when it moves) and a `where`, naming a member by their member document's path, then the change tests. A task becoming ready: `{on: "documents", query: {types: ["task"], where: {"/status": "review"}}}`, firing as it starts to match, a new one only with `new_documents: true`. A field moving: `changes: {field: "/status", from: ["open"], to: ["done"]}`, or `from: "new"`, `to: "deleted"`; any edit `"text"`. `by: {"/door": "browser", "/user": "<User id>"}` tests who changed it and how; `moved_between_rooms: true` only a move between Rooms. Items: `{on: "items", query: {where: {"/address": "<address>", "/json/ref": "refs/heads/main"}}}`, also `/source`, `/delivery`, `/headers/<name>`. A clock is `{on: "clock", cron: "0 9 * * 1", time_zone: "Europe/Berlin"}`, or a day before each task is due: `{on: "clock", query: {types: ["task"], where: …}, date: "/due", days: -1, at: "09:00", time_zone: "Europe/Berlin"}`. In your Private Room alone, a notification to you: `{on: "notifications", query: {where: {"/kind": "share-offer"}}}` over its `house-notification` document; such a Trigger holds no `notify` action. A refusal names the test and what it takes.
5
+ `condition`: When it fires: `on`, a `query` in the [Local Query](apps/local-query) language, at most one of `types`, `pathPrefix` or `paths` (one document, followed when it moves) and a `where`, naming a member by their member document's path, then the change tests. A task becoming ready: `{on: "documents", query: {types: ["task"], where: {"/status": "review"}}}`, firing as it starts to match, a new one only with `new_documents: true`. A field moving: `changes: {field: "/status", from: ["open"], to: ["done"]}`, or `from: "new"`, `to: "deleted"`; any edit `"text"`. `by: {"/door": "browser", "/user": "<User id>"}` tests who changed it and how; `moved_between_rooms: true` only a move between Rooms. Items: `{on: "items", query: {where: {"/address": "<address>", "/json/ref": "refs/heads/main"}}}`, also `/source`, `/delivery`, `/headers/<name>`. A clock is `{on: "clock", cron: "0 9 * * 1"}`, or a day before each task is due: `{on: "clock", query: {types: ["task"]}, date: "/due", days: -1, at: "09:00"}`, in the Room's time zone unless `time_zone: "UTC"` names another. In your Private Room alone, a notification to you: `{on: "notifications", query: {where: {"/kind": "share-offer"}}}` over its `house-notification` document; such a Trigger holds no `notify` action. A refusal names the test and what it takes.
6
6
 
7
7
  A message into a conversation the Room's [Agent function request](reference/agent-function) started: `{on: "messages", query: {where: {"/sender": "requester"}}}`, testing `/sender`, `/agent_function` and `/path`, the request's document, never its words.
8
8
 
9
- `actions`: a `notify` action names whom it reaches, e.g. `{kind: "notify", title: "Ready", text: "A task waits.", to: {users: ["<User id>"], fields: ["/members/assignee"], roles: ["owner"]}}`, Users by id, the member a field of the matched document names, and every holder of a Schema Role; a `function` action names an App and one function its latest release declares, e.g. `{kind: "function", app: "a_…", function: "assign lead"}`, which you may add only for an App you hold with this Room in a write slot of your bindings; [App functions](apps/functions) says how House runs it. `triggers/fired/list` shows each run `ran` with its `release`, or `failed` (`function_failed`, its `error`), never run again, or `function_not_started` while House tries, `final` after its last try.
9
+ `actions`: a `notify` action names whom it reaches, e.g. `{kind: "notify", title: "Ready", text: "A task waits.", to: {users: ["<User id>"], fields: ["/members/assignee"], roles: ["owner"]}}`: Users by id, the member a matched document's field names and each Schema Role holder; a `function` action names an App and a function its latest release declares, e.g. `{kind: "function", app: "a_…", function: "assign lead"}`, only for an App you hold with this Room in a write slot of your bindings and run as [App functions](apps/functions) says; `triggers/fired/list` shows each run `ran` with its `release`, or `failed` (`function_failed`, its `error`), never run again, or `function_not_started` while House tries, `final` after its last try.
10
+
11
+ A `notify` action with `in_group` is a [Group post](reference/group-post).
10
12
 
11
13
  In your Private Room alone, a `function` action naming no `app` runs one of your Environment functions ([wizard](skills/wizard)), e.g. `{kind: "function", function: "nightly backup"}`; elsewhere, or for one you do not hold, it is refused `not_found`. Kit runs its script with no terminal, the Event as JSON in the file `HOUSE_INPUT` names, one Event at a time. `triggers/fired/list` shows each run `ran` or `failed` (`function_failed`, its `error`), never run again, with its `run`, `exit_code` and `output`; a Kit not connected at its turn reads `no_target` with `environment_offline`, never run later.
12
14
 
13
- A Trigger acts on at most <trigger-events-per-hour> Events in each UTC hour, across its versions, skipped and failed ones not counted; `triggers/fired/list` shows the first past the number `missed: "skipped"` with `code: "cap_reached"`, which starts nothing, and none later that hour; the Trigger shows as failing. A Trigger whose own actions make what it fires on stops there, so narrow its condition.
15
+ A Trigger acts on at most <trigger-events-per-hour> Events in each UTC hour, across its versions, skipped ones not counted; `triggers/fired/list` shows the first past the number `missed: "skipped"` with `code: "cap_reached"`, which starts nothing, and none later that hour; the Trigger shows as failing; narrow the condition of one whose own actions make what it fires on.
14
16
 
15
17
  `{kind: "agent_function", card: "<name>", payload: "<input>"}` starts an [Agent function](reference/agent-function) and `{kind: "message", card: "<name>", text: "<text>"}` messages it; no such card is refused `not_found`, a retired one `agent_function_retired`, and one whose document misses its type's form fails `agent_function_unreadable` for good. `{kind: "done"}` marks Done each active conversation the Room's Agent function requests started for the Event's document, `marked` with their number.
@@ -0,0 +1,7 @@
1
+ # Wayfinding types
2
+
3
+ The `wayfinding-map` and `wayfinding-ticket` types every Room knows without declaring them, as House declares them: a Wayfinding map and its tickets. Each value is written in the document's body as [Schema](reference/schema) says.
4
+
5
+ ```yaml
6
+ <wayfinding-types>
7
+ ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  How a Site App's `entry_data` has House serve each route as HTML from Rooms.
4
4
 
5
- Entry Data Contract, `entry_data` in a Site App’s `app.json`: House serves each route as HTML from your Rooms before the App starts, running none of your code. Each of `routes` has a `path` (`/`, `/posts/:slug`), optional `query` (functional `?name` parameters with defaults), `queries` (Local Query objects with `types` and a `pathPrefix` at each scope, no `rooms` or `fileIds`; `{"$param": "slug"}` takes an argument, `"as": "integer"` converts it; `boundQuery` from `@agentshouse/mdruntime/entry` fills the same query in your page), `interaction` (the queries its first interaction needs), `title`, `description` and `body`. A body node is text, `{text: v}`, `{markdown: v}` of a document, `{tag, attrs, children}`, `{each: v, as, children}` or `{if: v, then, else}`; a value `v` is a literal, `{param}`, `{at, pointer}` into a query result or `each` item, `{join: [v]}`, `{count: v}` or `{stem: v}`. An `img`, a document's images among them, loads lazily in the box its `width` and `height` name, 16:9 without them. `styles` lists release CSS files. A route that fails starts the App as usual, and the Site document names why.
5
+ Entry Data Contract, `entry_data` in a Site App’s `app.json`: House serves each route as HTML from your Rooms before the App starts, running none of your code. Each of `routes` has a `path` (`/`, `/posts/:slug`), optional `query` (functional `?name` parameters with defaults), `queries` (Local Query objects with `types` and a `pathPrefix` at each scope, no `rooms` or `fileIds`; `{"$param": "slug"}` takes an argument, `"as": "integer"` converts it; `boundQuery` from `@agentshouse/mdruntime/entry` fills the same query in your page), `interaction` (the queries its first interaction needs), `title`, `description` and `body`. A body node is text, `{text: v}`, `{markdown: v}` of a document, `{tag, attrs, children}`, `{each: v, as, children}` or `{if: v, then, else}`; a value `v` is a literal, `{param}`, `{at, pointer}` into a query result or `each` item, `{join: [v]}`, `{count: v}` or `{stem: v}`. An `img` keeps the `loading` and `fetchpriority` it names and loads lazily where it names no `loading`; a document's images always load lazily. Each takes the box its `width` and `height` name, 16:9 without them. `styles` lists release CSS files. A route that fails starts the App as usual, and the Site document names why.
6
6
 
7
7
  ```json
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: house-onboarding
3
- description: Find out what a new User wants from House and keep their plan as a Wayfinding map and tickets in a Room you make for it; run it while your User owns no Room but the Private Room.
3
+ description: Find out what a new User wants from House and keep their plan as a Wayfinding map and tickets in a Room you make for it.
4
4
  ---
5
5
 
6
6
  # House Onboarding
@@ -9,5 +9,6 @@
9
9
  "e612bbaa90e41d60b9bcd0367a074b9735b9239c287e56fa1becc8364562a422",
10
10
  "1148f2eec3f500c66c4678a8ce9ab215c095692f505af1934574da6072f4f1a4",
11
11
  "42b31e675bfb35457a6cc94a54a58b180e4e646a83cb480701ee2e6259a84ee7",
12
- "ca9cf3104780486a72aac353a27ddc0f4479145de197c6ce28e4d0b5672de11b"
12
+ "ca9cf3104780486a72aac353a27ddc0f4479145de197c6ce28e4d0b5672de11b",
13
+ "2dec7ab02503f62a30605f0f976c36a8f4ed5da1b5de4750ac6550c511cddafe"
13
14
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/skills",
3
- "version": "0.1.0-cd1cc903cbd3b",
3
+ "version": "0.1.0-cd3fcd75c5224",
4
4
  "description": "The upstream Skills agents.house serves its agents, verbatim, and the texts it writes for them: the Guide, its own Skills among the pages, and the Agent instructions",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/upstream.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "repository": "https://github.com/mattpocock/skills",
3
- "commit": "4ae2317ddb22b046e26455e4f9c9b3aaaf62212d",
4
- "date": "2026-10-31",
3
+ "commit": "5863806dd4e15c63174bee4e7515a4256792ecfa",
4
+ "date": "2026-11-12",
5
5
  "skills": [
6
6
  "ask-matt",
7
7
  "code-review",