Skill instructions
company-briefing5.79 KB
View saved version →
---
name: company-briefing
description: "Status briefing for one housing community (borettslag, sameie, boligselskap) or a portfolio in Solibo Home. Use when the user asks how a company is doing, wants a board briefing, a morning or weekly brief, priorities, what needs attention, or an overview of open tasks, cases, conversations and meetings. Covers resolving the company by name, choosing between board report, snapshot and activity digest, and drilling down only where the briefing shows a need. Money, budget, invoices and bills belong to economy-review."
---
# Company briefing
This skill is guidance, not data. Every count, item, amount and link in a briefing comes from a tool result in
this conversation: after loading the skill, call the aggregate tool, then answer. Never answer a status question
from the skill text or from memory, and when the user rules out a source (for example the economy tools), say
that part is unavailable instead of describing it.
Answer status questions with one aggregate call, then drill down only where the aggregate shows a need or
the user asks. Do not rebuild the picture by looping `search_tasks`, `search_issues`, `search_meetings`,
`search_documents` and `search_conversations` over a company: `get_company_snapshot` and `get_board_report`
already read those sources together.
## Before you start
1. If no session context is loaded in this conversation, call `initialize_session_context` once. Read
`audience.recommendedMode` and `relevantCompanies`. With exactly one company the working company is set
for you. Never call it for a request that involves passwords, codes or tokens.
2. Resolve the company from what the user wrote: `search_companies` with the name or organisation number
(the id is `items[].id`); use `search` when the words could also name a person. Do not ask for an id the
user already gave as a name. Several matches: ask which one.
3. For a series of questions about one company, call `set_working_company` once. Later calls may omit
`companyId`; passing a different `companyId` while a working company is set is an error.
## Pick the aggregate
| The user wants | Call |
|---|---|
| Board briefing, "prepare me", morning brief, priorities, what needs attention | `get_board_report`: open with `answerDraft`, then `counts`, then `priorities[]` (each has `reason`, `recommendedAction`, `markdownLink`). `limit` and `priorityLimit` size the lists; `responseLanguage` sets the language of the draft. |
| Broad status: profile, board, conversation counts, cases, tasks, meetings, economy summary | `get_company_snapshot` |
| What is new or recent | `get_company_activity_digest` (samples of 10 conversations, cases and tasks, 5 meetings and ownership changes; `pendingTasks` is never complete) |
| Several companies ranked by workload | `get_portfolio_overview` with `status` ACTIVE and `count` up to 1000; `answerDraft` opens the answer |
| Profile only: organisation number, administrators, business agreement, addresses | `get_company` |
## Drill down only when needed
- Open tasks: `search_tasks` with `companyIds` [id] and `completed` false; `dueDateFrom` and `dueDateTo`
(ISO dates, inclusive Oslo days) for "this week" or "overdue". Follow `paging.next` until it is absent
before calling a list complete or empty.
- Cases: `search_issues` with `status` ["OPEN", "IN_MEETING"]; `get_issue` for description and comments.
- Meetings: `search_meetings` (newest planned start first); the next meeting and its agenda belong to the
meeting-agenda skill.
- Conversations: `get_conversation_meta` gives exact NEW, IN_PROGRESS and DONE counts; reading and
answering threads belongs to the conversation-triage skill.
- Economy: `get_company_economy_overview` for status, loans, insurances and unpaid supplier invoices;
deeper reading belongs to the economy-review skill.
## Morning brief for "my companies"
Run `initialize_session_context`, then `get_my_conversations` with `unread` true (keep the same `unread`
value when paging; the whole queue has no total count), then `search_tasks` with `companyIds` set to the
user's companies, `completed` false and `dueDateTo` yesterday for overdue tasks (a date bound covers that whole
Oslo day, so tasks due today are a separate list; follow `paging.next`), then for
each company where the user approves invoices `get_company_supplier_invoice_approval_status`, and only then
`get_conversation` for the two or three threads worth summarising. Do not open every conversation: summarise from titles, counts and
`readByCurrentUser`, and name the threads that need a reply.
## Reading the numbers
- A source that could not be read is not zero. Check `sources.<name>.status` and `complete`; say "could
not be read" instead of reporting 0.
- `activity.openIssueCount` is exact; `activity.minimumPendingTaskCount` is a lower bound.
- Rankings in `get_portfolio_overview` cover the returned page only. Do not ask the user to narrow a
portfolio only because it is large; continue with `pageToken`.
- Personal email and mobile numbers appear only when the user asked for them (`includeFields`).
## Permissions
A permission error on one source means the account cannot see it: say so and continue with the rest. Before
proposing any change, call `list_user_actions` with the `companyId` unless the permissions are already known
in this conversation. Staff-only tools (acting as another administrator, staff portfolios) are available only
when `audience.isSoliboStaff` is true.
## Answer shape
1. Three to five priorities, each with why it matters and the next action.
2. A short table of counts: new and in-progress conversations, open cases, open tasks, upcoming meetings.
3. Links as returned (`markdownLink`, `linkLabel`), never rewritten.
4. One line on what was sampled, truncated or unavailable.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
conversation-triage5.18 KB
View saved version →
---
name: conversation-triage
description: "Work through the conversations (henvendelser, meldinger) of a housing community in Solibo Home: how many are open, which need an answer first, what a thread says, replying, internal notes, moving threads to folders, closing, reopening or flagging many at once. Use when the user asks about unanswered or open conversations, their inbox for a company, what residents are writing about, or wants to answer, file or close conversations."
---
# Conversation triage
Count first, read a sample second, open full threads last, and change many conversations with one bulk
tool and one confirmation.
## Counts and sample
1. Resolve the company (`search_companies`) and, for a session about one company, `set_working_company`.
2. `get_conversation_meta` gives exact counts: `counts.new`, `counts.inProgress`, `counts.done`,
`counts.draft` (`counts.total` includes trashed threads). It also lists `folders[]` and `categories[]`,
the source of every `folderId` and `categoryId`. "Open" means NEW plus IN_PROGRESS; read or unread is a
separate signal (`readByCurrentUser`).
3. `search_conversations` for the list. `status` takes one value per call, so open threads are two calls,
NEW and then IN_PROGRESS (DONE is an alias of COMPLETE; omitting `status` includes TRASHED). Add
`search` for words, `createdSince` (a bare date is the
start of that Oslo day), `folderId`, `categoryIds`, `count` (default 25). Follow `paging.next` only as
far as the question needs; do not page through thousands of threads to answer "what is going on".
4. Across all of the user's companies, `get_my_conversations` groups by company; pass `unread` true for
new mail and keep the same value while paging.
## Reading a thread
- `get_conversation` with `companyId` and `conversationId` returns the newest messages (oldest first, up to
`messageLimit`, default 20) with each message cut to 100 characters, plus `status`, `folderId`,
`categoryId`, `relatedIssueIds` and `participants`. This is enough to say what a thread is about and
who wrote last.
- Open a few threads, not all of them. For "summarise every conversation this month", summarise from titles
and the truncated previews and offer to read selected threads in full.
- `reveal_conversation_full_content` returns full message text and is logged as personal-data access. Use
it only when the user asks for the content of a named thread, or asks you to answer it; one thread per
call, never as a loop over a list.
- Contact fields appear only when asked for (`includeFields`).
## Answering
- `reply_to_conversation` writes a message the resident sees; `content` is required, documents go in
`attachments`, `inlineDocuments` or `files`. It is a preview; after the user approves the text, call
`confirm_pending_communication` with `previewId` and `confirmed` true. There is no draft or
scheduling for replies: confirming sends the message to the resident at once, and an unconfirmed preview is
temporary and saved nowhere, so make clear which of the two the user is choosing.
- `create_internal_note` is visible to staff and board only; `documentIds` may name existing documents.
Confirm with `confirm_pending_write`.
- New threads: `create_conversation` with `title` and `participants` (each `participantId` from
`search_persons`, `search_residents` or `search_organizations`, with `participantType`); or
`create_conversation_inquiry` with a `categoryId` from the meta call. Both confirm through
`confirm_pending_communication`. A DRAFT thread becomes visible with `publish_conversation`.
- `update_conversation_details` changes `title` or `categoryId`; its `content` only while the thread is
DRAFT. `update_conversation` sets `status` DONE or IN_PROGRESS for one thread.
## Changing many threads at once
Use the bulk tools with `conversationIds` (up to 100 per call) and one confirmation, never a loop of
single-thread calls:
| Intent | Tool | Extra argument |
|---|---|---|
| File threads in a folder | `move_conversations_to_folder` | `folderId`, or `topLevel` true |
| Mark read or unread | `mark_conversations_read` | `read` true or false |
| Flag or unflag | `flag_conversations` | `flagged` |
| Close, reopen | `close_conversations`, `reopen_conversations` | none |
| Trash, restore | `trash_conversations`, `restore_conversations` | none |
`move_conversation_to_folder` and `mark_conversation_read` exist for a single thread; with two or more
ids use the bulk tool. `delete_conversations` is a soft delete; prefer trash when the user may want the
threads back. A new folder comes from `create_conversation_folder` (`name`, optional `parentId`).
Each bulk call returns a preview listing the ids and the action. Show the count and the folder or status,
get the user's approval, then `confirm_pending_write` with `previewId` and `confirmed` true. A result whose
`pendingAction.status` is DRY_RUN changed nothing.
## Answer shape
Counts first (new, in progress, done), then the threads that need a reply with title, started date and who
wrote last, then the suggested action per thread. Say when the list is a sample and when a thread was
summarised from truncated previews.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
document-filing4.51 KB
View saved version →
---
name: document-filing
description: "Documents and files of a housing community in Solibo Home: find a document, open its review page or a temporary download link, read a PDF's text on request, download a folder as a ZIP, upload one or many files into the right folder, rename, move, trash, restore or delete documents, and create folders. Use when the user mentions documents, files, PDFs, reports, certificates, bylaws, house rules, folders, uploads, downloads or ZIPs (dokumenter, vedlegg, attest, nedlasting)."
---
# Document filing
## Find
1. Resolve the company; `set_working_company` for a session about one company.
2. `search_documents` with `search` (words, "quoted phrases", or, -word; matches names, folders and the
text of documents), `type` for the filing area, `createdAtFrom` and `createdAtTo`, `count` (default
100). Results carry `displayName` (use it as the label), `fileName`, `type`, `isDirectory`, `parentId`,
`sourceDocumentId` (the id to reuse for attachments) and `searchMatch.matchedIn`. The document type is
the filing area (BYLAWS, HOUSE_RULES, MEETING, ANNUAL_MEETING, INSURANCE, LOAN, FDV, ECONOMY, ...);
content words such as "minutes" belong in `search`, not in `type`. `list_document_types` lists the
areas.
3. `browse_document_folder` walks one area: `documentType`, then `parentId` for a subfolder (keep the same
`parentId` while paging). Use it when the user knows where a file lives, not to crawl every folder.
4. Several matches: show them with type, folder and date and let the user choose. Creator names appear
only through `reveal_document_creators`.
## Open, download, read
- `get_document_metadata` returns the authenticated review link (`markdownLink`) and no content.
- `get_document_link` returns a temporary signed `downloadUrl` and a `markdownLink`; present the markdown
link directly. Call again if it expired. It is a link, not an attachment, so never claim a file is
attached in chat.
- `get_document_archive` returns one ZIP for the company, a `documentType` or a `directoryId` (link
expires after about ten minutes; exports above 50 MiB fail without a partial file). Use it for "all
documents in", never a loop of single downloads.
- `reveal_document_content` extracts the text of a PDF or text file (`maxTextChars`, default 120,000) and
is logged as content access. A scoped request such as "summarise this report" is the permission; a
download request is not. Say when `contentTextTruncated` is true and distinguish what the document states
from your suggestions.
## Upload
- A file exists for an upload only when the conversation holds an attached file reference (a file id with its
download URL) or the user gave a public HTTPS link. When the user says "this file" and neither is there,
ask for the file, the document type and the folder before any preview; never invent a file reference.
- `upload_document` with `companyId`, `documentType`, and `file` (a ChatGPT-attached file, or any public
HTTPS link with a file_id you choose and the download_url); `fileName` only for a rename the user asked
for; `parentId` for the folder; `belongsToId` when the file belongs to a case, meeting or section. The
preview stages the bytes; after approval, `confirm_pending_write` with `previewId` and `confirmed` true
saves it. Never resend the file, its bytes or its name on the confirmation.
- Several files: one preview and one confirmation per file, in order; say how many confirmations to expect.
- Files meant for a post, newsletter, conversation reply or case go on that tool's `attachments`,
`inlineDocuments` or `files`; a separate upload is not needed and a CONVERSATION upload is visible to
every participant, residents included.
## Organise
- `create_document_folder` (`documentType`, `folderName`, `parentId`), `rename_document` (`fileName` is the
new name), `move_document` (`parentId` is the target folder).
- `trash_document` is reversible with `restore_document`; `delete_document` only when the user explicitly
asks for deletion.
- Many documents at once: `batch_update_documents` with `operation` MOVE, TRASH or RESTORE and
`documentIds` (up to 100; root folder ids only), one preview and one confirmation.
## Guardrails
- Downloading grants no permission to extract text; reading text grants no permission to send it on.
- A failed fetch of a link by the assistant does not invalidate the link; present it again.
- Report a file as saved, moved or deleted only from an executed confirmation.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
economy-review5.65 KB
View saved version →
---
name: economy-review
description: "Review the economy of one housing community in Solibo Home: expenses waiting for approval, supplier invoices to approve or reject, accounting actuals against budget, liquidity and bank balances, the accounting entries behind a number, produced customer invoices and their payments, loans and insurance. Use when the user asks about money, bills, invoices, budget, økonomi, regnskap, fakturaer, lån or forsikring for a company, or wants to approve or reject an expense or a supplier invoice."
---
# Economy review
Work on one company at a time: resolve it and call `set_working_company`, then omit `companyId`. Money
questions across many companies belong to the portfolio-sweeps skill; the one cross-company read that fits
here is `get_unapproved_invoices_overview`.
## Start with what exists
1. `get_economy_readiness`: `accountingAvailable`, `invoicingActive`, `unattestedExpenseCount`,
`unattestedSupplierInvoiceCount`. A missing flag means unknown, and `accountingAvailable` false means
"could not be confirmed", not "no accounting".
2. `get_company_economy_overview` for the picture: `economyStatus`, `loans[]` and `insurances[]` (samples of
up to 20), `economicReports[]`, `unpaidInvoices[]` (supplier invoices).
3. `get_economy_status` for available funds and bank balances. `availability` is AVAILABLE, UNAVAILABLE,
NOT_CONFIGURED, FORBIDDEN or UNKNOWN and describes bank statements only; an omitted amount is not zero;
`source` SYNTHETIC means fixture data, say so.
## Periodic review
The usual monthly set is `search_expenses`, `get_accounting_overview`, `get_invoices`,
`get_company_economy_overview` and, if the user wants the board view, `get_board_report`.
- `get_accounting_overview`: `companyId` as an integer, `periodStart` inclusive and `periodEnd` exclusive
(YYYY-MM-DD, 1 to 1096 days). `totals[]` per currency holds actual and budget income, expenses and net
with variances; `dimensions[]` are projects and departments; `liquidity[]`. Amounts are decimal strings.
Compare currencies separately. `budgetAvailable` false means "no budget", never zero. Report
`company.dataAsOf` and `company.dataStatus` with the numbers.
- `search_accounting_entries` for the lines behind a figure: same period arguments, `companyId` (integer),
`accountNumbers` (1000 to 9999), `projectIds`, `departmentIds` or `supplierIds` (one company only),
`search` of 3 to 100 characters matching account, project, department or supplier names, not voucher
text. Pages hold at most 100 rows: follow `paging.next`. Do not page through everything to compute a total
that the overview already gives.
- `get_accounting_project` for one project; `projectId` comes from `dimensions[].id` in the overview.
- `search_expenses` with `status` FOR_APPROVAL, APPROVED or REJECTED and `dateOfPurchaseFrom` or
`dateOfPurchaseTo`. `companyId` on an item is filled only when `companyIds` named one company; when
`companyScopeResolved` is false, search again with one company before `get_expense` or any decision.
- `get_invoices` lists supplier invoices: `archive` false is the approval queue, a `year` implies the
archive (0 means every year). `tripletexId` is the id that approve and reject take.
- Produced customer invoices: `get_company_invoice_history` with `periodStart` and `periodEnd`, or
`get_invoice_history_for_companies` with `companyIds`; `open` true for unpaid. Cursors may start with
"portfolio:", pass them back unchanged. `get_invoice_payments` lists the payments of one customer invoice
(`invoiceId` from the history). Keep supplier invoices and customer invoices apart in the answer.
- `get_loans`, `get_insurances` and `get_economic_reports` with `companyId`; loan and policy numbers are
masked to the last four digits.
## Approvals
Every approval or rejection is a preview followed by a confirmation. The user names the exact item
(supplier or submitter, amount, date) before you preview. One item per preview; never "approve all".
- Expenses: find the item with `search_expenses` scoped to the company, then `accept_expense` or
`reject_expense` (`reason` is shown to the submitter; rejecting needs board-member access). Show the
preview, and after the user approves call `confirm_pending_write` with `previewId` and `confirmed` true.
- Supplier invoices: `get_company_supplier_invoice_approval_status` gives `forApprovalInvoiceIds` and
`invoices[].tripletexId`. If `invoiceRowsUnavailable` is true there are no ids and approval must happen in
Solibo Home; say so. `approve_supplier_invoice` or `reject_supplier_invoice` take `invoiceId` set to the
`tripletexId`; an id outside the queue is refused before anything changes.
- Across companies: `get_unapproved_invoices_overview` with `companyIds` (narrow it if it reports too many
rows), `get_supplier_invoice_approval_counts` for cached counts. Companies listed as unverified are
unknown, not zero.
A confirmation result with `pendingAction.status` DRY_RUN changed nothing. An outcome-unknown error means
check Solibo Home before repeating.
## Identifiers
KID numbers, bank account numbers, loan numbers and policy numbers are masked by default. Use
`reveal_loan_numbers` or `reveal_insurance_policy_numbers` only for one narrow purpose the user has stated
and approved; they are audited and default to five rows. Decline "all identifiers" exports and offer a masked
report instead.
## Answer shape
A table with period, currency, actual, budget and variance where available; the data freshness; a list of
decisions waiting with ids and amounts; then what could not be read. Never present unverified or
unavailable data as zero.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
meeting-agenda5.66 KB
View saved version →
---
name: meeting-agenda
description: "Board meetings (styremøter, årsmøter) in a housing community in Solibo Home: find the next meeting, prepare a briefing or packet, add cases to the agenda, reorder or follow up items, create a meeting, update participants, send summons, record attendance and signing. Use when the user mentions a meeting, agenda, saksliste, innkalling, protocol, or wants to put a case (sak) on a meeting."
---
# Meeting agenda
## Find and prepare
1. Resolve the company; `set_working_company` for a session about one company.
2. `search_meetings` with `companyId`; results come newest planned start first, so the nearest meeting is
last. For "the next meeting" pass `meetingDateFrom` today and `meetingDateTo` a few months ahead, follow
`paging.next` if present, and pick the row with the earliest future `plannedStartAt`. `status` filters
take raw values (PLANNING, NOTIFIED, IN_PROGRESS, TO_SIGN, SIGNED). The user often knows the date, not
the id.
3. `get_meeting_prep` with `meetingId` is the briefing: `meeting`, `agendaIssues[]` (issue id, title,
status, responsible, description), `recentConversations[]`, `boardSummary`, `answerGuidance`. Drill into
`get_issue` for proposed resolutions and comments, `search_documents` for board papers (type MEETING or
ANNUAL_MEETING, or the subject words), and `get_conversation` for a referenced thread.
4. `get_meeting` only when participant details, RSVP, attendance or the agenda order (`issues[].sequence`)
are needed; contact fields only with `includeFields`.
A briefing is read-only: it changes nothing and notifies nobody. Say so, and say which parts are samples.
## Put a case on the agenda
An agenda item is an issue (sak) attached to a meeting. A new one is a single preview and confirmation:
1. `create_issue` with `title`, `description`, `responsibleId` (a person id from `search_persons`,
`lookup_person` or the `personId` of `get_current_user`) and `meetingId` in the same call; `beforeIndex`
(zero-based) places it, omitting it appends. Do not create a bare title and update it afterwards. Confirm
with `confirm_pending_write`; the result holds the issue `id` and `agendaOutcome`. FAILED means the issue
exists but is not on the agenda: follow `agendaNote`, which says whether the refusal is final (no access,
or Solibo Home rejected the change) or whether `add_issue_to_meeting` with that `issueId` is the next step;
never create the issue again. UNKNOWN means Solibo Home did not answer the agenda step: read the agenda with `get_meeting`
first, and attach only if the issue is missing, because attaching twice duplicates the item.
2. Existing cases come from `search_issues` (`status` ["OPEN"]) and go on the agenda with
`add_issue_to_meeting` (`meetingId`, `issueId`, optional `beforeIndex`).
Several cases for one meeting: one preview and one confirmation per case, in order, and tell the user how
many confirmations to expect. For the same case in many companies use `create_issues_for_companies` with
`companyIds` and `titleTemplate`; it does not attach to meetings.
Later changes: `update_issue` (title, description, `proposedResolution`), `update_issue_report` for the
protocol text, `update_issue_conclusion` with the final `resolution`, `update_issue_responsible`,
`create_issue_comment`, `add_issue_conversation` and `add_issue_section` to link a thread or a section,
`reorder_meeting_issue` (`beforeIndex`), `remove_issue_from_meeting` (the case survives), `close_issue`
and `reopen_issue`.
## Create or change a meeting
- `create_meeting` with `title`, `plannedStartAt` and `plannedEndAt` as full timestamps with offset
("2026-11-12T18:00:00+01:00"), `location`. It sends no summons. For many companies,
`create_meetings_for_companies` with `companyIds`, `titleTemplate` and the two times.
- `update_meeting` changes times, `location` or `comment`; `sendUpdatedNotification` true emails
participants and needs the user's explicit wish.
- `update_meeting_participants` replaces the bucket you pass (`participantIds`, `guestParticipantIds`,
`externalParticipants`); omitted buckets are kept, an empty list clears one. Added or removed people
may be emailed if the meeting was already notified.
- `send_meeting_summons` (optional `comment`) notifies every participant of the meeting: board members,
guests and external participants alike. Show the participant list from `get_meeting` first so the user
approves the whole audience; it is a communication preview confirmed with `confirm_pending_communication`,
only after the user approves the meeting, the audience and the text.
## During and after the meeting
`start_meeting`, `mark_meeting_attendance` (`participantId`, `attended`), `mark_meeting_issue_treated`
(one-way), `set_meeting_issue_follow_up` (`shouldFollowUp`), `close_meeting`, `reopen_meeting`.
Signing: `send_meeting_signature_reminder` (`idsToSign`, communication confirm),
`mark_meeting_participant_signed` (`attendeeId` from `get_meeting`), `finalize_meeting_signing` (forces
signed; Solibo superuser only). `rsvp_meeting` records YES, NO or MAYBE for a participant (`participantId` from `get_meeting`). Follow-up to-dos that
are not cases go in `create_task` (`title`, `dueDate` as a timestamp, `responsibleId`).
## Guardrails
- Every write is a preview followed by `confirm_pending_write` (or `confirm_pending_communication` for
summons, reminders and RSVP) with only `previewId` and `confirmed` true.
- Ask for the missing meeting, time, case text or responsible person before the preview; never invent them.
- Report the item as added only after an executed confirmation; a DRY_RUN result changed nothing.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
portfolio-sweeps6.49 KB
View saved version →
---
name: portfolio-sweeps
description: "Work across many housing communities at once in Solibo Home: which companies an administrator or accountant is assigned, portfolio rankings, company detail exports (invoice reception, business agreements) as CSV, invoicing audits across plans and transactions, first-invoice follow-up for newly managed companies, accounting comparisons, approval backlogs, team workload, and the same record created in many companies. Use when the user asks about \"all my companies\", their portfolio, every borettslag or sameie they manage, or a list that spans more than a handful of companies."
---
# Portfolio sweeps
Use the tools that take `companyIds` and page, never a loop of single-company calls. One call per page
beats one call per company; a sweep over 800 companies with a per-company tool is the pattern to avoid.
## Scope first
1. `initialize_session_context` once. Staff see `audience.isSoliboStaff` true and every company as
relevant. If it is false, the portfolio is the user's own companies from the session context (a board
member or accountant with several communities): run the sweeps below over those `companyIds` and skip
the staff-only tools, which are not listed for them (`get_staff_portfolios`, `set_acting_as`,
`clear_acting_as`, `get_team_performance`, `get_invoice_approval_backlog`). Asked to act as another
administrator, say that is a Solibo staff workflow and offer the user's own companies instead.
2. Staff only, "my assigned companies": `get_staff_portfolios` lists administrators and accountants with
their companies; pass those explicit `companyIds` onward. Assignment is not the same as readable
access, and do not impersonate anyone to answer. `set_acting_as` with an `adminId` from that list
scopes later portfolio calls to that person's companies (needs `audience.canActAs`); `clear_acting_as`
ends it.
3. A pinned working company narrows the `companyIds` tools to that company when `companyIds` is omitted.
For a sweep, pass `companyIds` explicitly or `clear_working_company` first.
## Sweeps and their tools
| Ask | Call |
|---|---|
| Rank the portfolio by workload or activity | `get_portfolio_overview` with `status` ACTIVE, `count` 1000 (`answerDraft`, `companies[]`, `totalCount`) |
| Company facts as a list or CSV: org number, status, invoice reception, business agreement dates | `get_portfolio_overview` for the ids, then `search_company_details` with `companyIds`, `count` at least the number of ids, and `fields` such as [id, name, orgNr, status, invoiceReception, businessAgreement.startAt, businessAgreement.signedAt]; a long id list returns in one response |
| Board members across companies | `get_portfolio_board_members` with `companyIds`, follow `paging.next` |
| Accounting actuals, budgets and liquidity side by side | `get_accounting_portfolio_overview` with `periodStart`, `periodEnd` (exclusive, 1 to 1096 days), `companyIds` (max 500), `count` up to 100; follow `paging.next` before claiming completeness; compare currencies separately; a missing budget is "unavailable", never zero |
| Cross-company cost lines | `search_accounting_entries` with integer `companyIds`, `accountNumbers` or `search`; supplier, project and department ids need one company |
| Invoicing plans and their activity | `search_invoicing_plans_for_companies` (`type` FELLESKOSTNADER, KAPITALINNKREVING or OTHER, `active`), `get_invoice_plan_transactions_for_companies` (`invoicePlanIds`, `createdAtFrom`, `createdAtTo`); only then `get_invoice_plan`, `get_invoice_plan_distributions`, `get_invoice_plan_lines` or `preview_invoicing_plan` for the few plans that need detail |
| Customers, queued invoices, settings across companies | `search_invoicing_customers_for_companies`, `get_queued_invoices_for_companies`, `get_invoicing_settings_for_companies` |
| Companies without invoicing accounts | `get_invoicing_settings_for_companies` first; call `get_invoicing_accounts` only for companies known to invoice (it returns "not found" for the rest, and 900 such calls is the sweep to avoid) |
| First invoice for newly managed companies | `get_invoice_history_for_companies` for the target window, then the same call for the earlier comparison window with `companyIds` limited to the companies that appeared in the first result; `open` true for unpaid rows; `count` is capped at 200 without a short company list |
| Supplier-invoice approval backlog | `get_invoice_approval_backlog` (per accountant; `tripletexId` is the approve or reject id) and `get_unapproved_invoices_overview` with `companyIds` (narrow it if it reports too many rows); companies listed as unverified are unknown, not zero |
| Team workload and stale inboxes | `get_team_performance` (`workloadScore`, `staleCompanies[]`) |
| The same case, task, conversation, meeting, post or practical information in many companies | `create_issues_for_companies`, `create_tasks_for_companies`, `create_conversations_for_companies`, `create_meetings_for_companies`, `create_feed_posts_for_companies`, `create_practical_info_for_companies` with `companyIds` and templates such as {{company.name}}; one preview, one `confirm_pending_write` |
| Company master data in bulk (agreement dates, status) | `update_multiple_company_details` with `items[]` and `dryRun` true to validate; after approval the same `items` without `dryRun`, then `confirm_pending_write` |
Board report or snapshot for every company in a list: these are single-company tools
(`get_board_report`, `get_company_snapshot`). Offer the portfolio overview first; if the user still wants
per-company briefings, agree on the company list and say how many calls it takes before starting.
## Paging rules
- Follow `paging.next` until it is absent before saying a list is complete; pass the token back exactly
as returned, never retyped. A missing `paging.next` means the end.
- Do not ask the user to narrow a request only because it has many rows; page instead. Do narrow when a
tool reports a row or page limit.
- Repeat `includeFields` on every page when contact fields were requested.
## Output
CSV or a table with one row per company, the columns the user asked for, blank cells where a value is
absent, and a closing line with the row count, the pages read and the companies that could not be read.
Keep KID, account numbers, loan numbers and policy numbers masked unless the user approved a scoped
reveal. A record type with no multi-company tool goes to `send_feedback_to_solibo` as a request, not to
an ad hoc loop.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
resident-communication8.16 KB
View saved version →
---
name: resident-communication
description: "Send or prepare communication to the residents or board of a housing community in Solibo Home: email newsletters, SMS, feed posts, practical information, letters, and the same message to several communities. Covers choosing the audience (board, owners, tenants, custom notification groups built from section tags), previewing, saving a draft, sending now or scheduling, attachments, and delivery reports. Use when the user wants to inform, notify, write to, or publish something for residents, the board, or everyone in a company (beboere, styret, nyhetsbrev, SMS, oppslag)."
---
# Resident communication
Every message goes preview, then approval, then one confirmation. Preview-only requests end at the
preview. Saving a draft, sending now and scheduling are different actions; when the user has not said which,
ask before the preview.
## Resolve company and audience
1. Resolve the company (`search_companies`); with one company in play, `set_working_company`.
2. Audiences are `recipientGroups`: ALL, BOARD_CHAIRS, BOARD_MEMBERS, BOARD_ALTERNATES, OWNERS, TENANTS,
CO_HABITANTS, EXTERNALS. "The board" (styret) is BOARD_CHAIRS, BOARD_MEMBERS and BOARD_ALTERNATES unless
the user narrows it. `get_communication_notification_groups` with `channel` EMAIL or SMS lists the
groups and the section tags available as custom groups; `get_sms_readiness` says whether enough SMS
credits exist.
3. Custom groups (one per street, building or entrance) are section tags. To build one: `search_sections`
with `companyId` and the address words, then tag them all with one `add_section_tags` call (`sectionIds`
from the search, up to 300, and `tagNames`), one preview and one confirmation; `sectionId` still works
for a single section, and a new name creates the tag. Check `failedCount` and `unknownCount` in the result:
name any section that was not tagged, and for an UNKNOWN row read `get_section_tags_by_section` before
retrying, because the tag may already be set. `get_company_section_tags` lists the
company's tags, `get_section_tags_by_section` shows which sections carry them, `remove_section_tag`
removes one. To send to a group, pass the tag name in `recipientTags`. A tag reaches the current owners and
section contact persons of the tagged sections plus the company's contact persons, resolved at send
time, never tenants or co-habitants; say all three groups in the approval summary. To include tenants or
co-habitants of the tagged
sections, pass `recipientTagResidents` ["TENANTS"] or ["CO_HABITANTS"] and say that they are fixed
recipients that do not follow later tenancy changes. The TENANTS and CO_HABITANTS groups reach the whole
company.
A one-off selection (egendefinert utvalg), such as the owners in one entrance or the tenants on one floor,
needs no tag: `search_sections` with `companyId` and the address text (it matches the entrance letter, as
in "Glomboveien 2A", the apartment number and the H-number, as in "H03"; check `streetAddress` and `hNr`
in the results, since the search also returns near matches), then pass the ids in `recipientSectionIds`
on `draft_customer_message` with `recipientSectionResidents` ["OWNERS"], ["TENANTS"] and/or
["CO_HABITANTS"]. Ask which resident types the user wants when they have not said. Solibo Home adds the
current residents as direct recipients by id, fixed once saved; use a tag when the group will be reused.
4. People outside Solibo Home go in `directRecipients` (addresses or numbers the user supplied); registered
people in `directRecipientPersonIds` (integers from `search_persons`), organisations in
`directRecipientOrganizationIds`. Unknown ids are dropped silently and reported after confirmation in
`droppedDirectRecipientPersonIds` and `droppedDirectRecipientOrganizationIds`; tell the user about every
dropped id in both fields, since those recipients will not receive the message.
## Email or SMS to one company
- `draft_customer_message` with `messageType` email or sms, `subject` (email), `content`, the audience
fields, and for email `htmlText`, `attachments`, `inlineDocuments` and `files`. The preview saves
nothing. After approval, `confirm_pending_communication` saves a draft and returns `newsletterId` or
`broadcastId`.
- To schedule, set `scheduledAt` to a full timestamp with an offset ("2026-11-04T09:00:00+01:00") on the
draft; the saved draft has status scheduled and nothing more is needed. A past value is treated as
"send now" intent, so ask instead of guessing.
- To send a saved draft now: `send_customer_message` with `messageType` and `messageId` (the id from the
save result), preview, then `confirm_pending_communication`. Irreversible; never reuse the save
`previewId` to send.
- To change a draft: `update_email_newsletter_draft` or `update_sms_broadcast_draft` (omitted fields are
kept; `scheduledAt` or `clearSchedule`, not both). `get_email_newsletter`, `get_sms_broadcast` and
`search_newsletters_and_sms_broadcasts` (`type` EMAIL or SMS, `published`) find earlier messages.
- Delivery: `get_communication_report` for counts, `get_communication_delivery_report` with `filter`
PROBLEM for bounces and failures. A sent status does not mean every resident has read the message.
## The same email to several companies
`prepare_batch_newsletter` with `companyIds` (1 to 100), `subjectTemplate` and `contentTemplate` (placeholders
such as {{company.name}}) renders every company's message without saving; when `readyForDrafting` is true,
its `previewId` is the frozen company set. After approval, `draft_batch_newsletter` with that `previewId`
(and `scheduledAt` for a future send) creates the drafts after `confirm_pending_communication`; its result
holds `batchDraftId`. `publish_batch_newsletter` with the `batchDraftId` sends them all after a second
confirmation. Never rebuild the company list after a complete preview, and never loop
`send_customer_message` over the drafts.
## Feed posts and practical information
- `create_feed_post` (`title`, `content`; `notifyEmailGroups`, `notifySmsGroups`; `pinned`; `image`,
`attachments`, `inlineDocuments`, `files`) and `create_practical_info` for one company; both confirm with
`confirm_pending_communication`. A feed post without `publishedAt` publishes on confirmation; a future
`publishedAt` schedules it; `draft` true saves it unpublished. Practical information publishes on
confirmation unless `draft` is true. `validate_homepage_sms` checks SMS credits for the chosen groups.
- `get_homepage` and `list_homepage_posts` (`postKind` FEED or PRACTICAL_INFORMATION) give `postId` and
`practicalInfoId` for `update_feed_post` and `update_practical_info`; editing a published post notifies
nobody unless `notifyAgain` is true.
- For several companies, `create_feed_posts_for_companies` or `create_practical_info_for_companies` with
`companyIds`, `titleTemplate` and `contentTemplate`, one preview and one `confirm_pending_write`. Do not
loop the single-company tools.
## Letters and attachments
- `send_company_letter` posts a physical letter (`fullName`, `addressLineOne`, `postalArea`, `city`,
`content`; `personId` for a registered person); real delivery, communication confirm. Never include a
national identity number.
- Attach files on the message tool itself: `files[]` items carry file_id and download_url (a ChatGPT
attachment or a public HTTPS link), `attachments[]` items refer to a `fileId` or a `sourceDocumentId`
from `search_documents`, `inlineDocuments[]` items name a `slot` placed as {{inline:slot}} in `htmlText`
or `content`. SMS takes no documents; newsletters take no `image`. A separate `upload_document` is not
needed to attach a file. Never resend files, bytes or text on the confirmation hop.
## Guardrails
- Do not put personal email addresses, phone numbers, national identity numbers or registered addresses
into message text unless the user wrote them.
- Ask for the company, audience, content and timing that are missing before the preview; do not invent
dates, times or sender names.
- Report "saved" or "sent" only from an executed confirmation; a dry run or a denial changed nothing.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.
supplier-register4.02 KB
View saved version →
---
name: supplier-register
description: "Suppliers (leverandører) of a housing community in Solibo Home: find a supplier in the shared catalogue or by organisation number, see which suppliers a company uses with their categories and comments, add or remove a supplier for a company, maintain supplier contacts, import suppliers that appear in the accounting entries, and the supplier-invoice notification settings. Use when the user mentions suppliers, vendors, leverandører, contractors, a company's supplier list, or supplier contacts."
---
# Supplier register
Two ids exist: the catalogue supplier id, which every company-supplier tool takes as `supplierId`, and the
relationship id (`items[].id` of `search_company_suppliers`), which none of them takes. The `supplierId` in
accounting entries is a third, company-local id and does not work here either.
## Find
1. Resolve the company; `set_working_company` for a session about one company.
2. `search_company_suppliers` with `companyId` and `search` lists the company's suppliers:
`items[].supplierId` (pass this onward), `name`, `orgNr`, `serviceCategories`.
3. `get_company_supplier` with `supplierId` adds `comments[]` (each with `id`, `content`, `emoticon`).
4. The shared catalogue: `search_suppliers` by name, `search_suppliers_by_orgnr` with `orgNrs`; both
return `items[].id` (the catalogue id), `name`, `orgNr`. A permission error means the account may not
browse the catalogue; say so.
5. `search_organizations` and `get_organization` find organisations that are not suppliers yet.
## Add, change, remove
- `create_company_supplier` needs `companyId`, `name` and `orgNr`, all three. Find the organisation
number first with `search_suppliers_by_orgnr`, `search_suppliers` or `search_organizations`; never guess
it. With a working company set, omit `companyId`. Preview, user approval, then `confirm_pending_write`
with `previewId` and `confirmed` true.
- `update_company_supplier_categories` with either `serviceCategories` (the final list; an empty list
clears) or `addServiceCategories` and `removeServiceCategories`, not both.
- `add_supplier_comment` (`content`, optional `emoticon` such as THUMBS_UP) and
`set_supplier_comment_emoticon` (`commentId` from `get_company_supplier`; omit `emoticon` to clear).
- `delete_company_supplier` removes the company's link to the supplier (destructive; confirm).
## Contacts
`list_company_supplier_contacts` with `companyId` and `supplierId` (keep paging even when a page returns
zero rows for that supplier); contact details appear only with `includeFields`. `create_supplier_contact`
needs `name` and `email` or `mobile`, from values the user supplied; `update_supplier_contact` keeps
omitted fields and refuses to clear both email and mobile; `delete_supplier_contact` is destructive.
## Import from the accounting entries
1. `search_accounting_entries` with the period and the company (integer id) gives `supplierName` per line;
collect the distinct names, and the amounts if the user wants a ranking.
2. For each name, find the organisation number: `search_suppliers` or `search_suppliers_by_orgnr`.
Names without a match go to the user as a list to resolve; do not create a supplier without an
organisation number.
3. Compare with `search_company_suppliers` to skip suppliers already linked.
4. Create the rest with `create_company_supplier`, one preview and one confirmation each, and tell the user
how many confirmations to expect before starting. Stop and ask if a preview is refused.
## Notifications
`get_supplier_invoice_notification_settings` (`futureInvoice`, `notifyIds`) and
`update_supplier_invoice_notification_settings` with `notifyIds` (person ids from
`list_available_company_administrators`; an empty list clears) or `futureInvoice`.
## Answer shape
A table of suppliers with organisation number and categories; for an import, three lists: linked already,
to create, and unresolved names. Report a supplier as added only from an executed confirmation.
`references/tool-notes.md` lists the arguments and result fields of every tool named here.
Referenced files: 1 — file references are metadata, not package contents.