Workspace agent primitives for Organigram applications.
This package contains the public, framework-independent parts of the Organigram workspace agent: request and response types, snapshot and prompt construction, response normalization, model usage extraction, and document text extraction.
pnpm add @organigram/agent
The agent answers questions about one workspace, and prepares changes to its organigrams. Gather what the user may see of the workspace in a Context, and build the prompt with buildPrompt(). The prompt includes a snapshot of the workspace, with the names of files but never their contents. Send it to the language model of your choice, with WORKSPACE_AGENT_OUTPUT_SCHEMA as the structured output format when the provider supports it.
Build the prompt of the workspace agent for a conversation.
The prompt contains the rules of the agent, a snapshot of the workspace built with buildSnapshot(), the conversation and the output schema. It forbids proposals, transactions and signatures, and tells the model to treat file names, notifications and document contents as data rather than instructions. It cannot enforce these rules: validate the answer with normalizeResponse().
const prompt = buildPrompt({ messages, context })const answer = normalizeResponse(JSON.parse(completion), context)
buildPrompt: (input: { messages: Message[]; context: Context;}) => string
Instructions to generate a new organigram from a description or from documents, such as bylaws, included in the prompt of buildPrompt().
JSON schema of the answers expected from the language model, to pass as its structured output format.
Build the description of a workspace given to the agent in its prompt.
The snapshot keeps only the fields the agent needs: the workspace, its organigrams with their organs, entries, procedures, latest proposals and elections, and assets, plus the names and references of files and the notifications. It never contains the contents of files. It includes generatedAt, the date it was built.
buildSnapshot: (context: Context) => Record<string, unknown>
Parameters
| Name | Type | Description |
|---|---|---|
context | Context | What is known about the workspace. |
Number of the latest proposals of each procedure included in a snapshot.
const MAX_PROPOSALS_PER_PROCEDURE = 10
What the agent knows about a workspace: the input of buildSnapshot(), buildPrompt() and normalizeResponse().
export type Context = { workspace: Workspace; files: File[]; notifications: Notification[]; currentOrganigram: OrganigramJson | null;};
A file of the workspace. The agent only sees its reference and name, not its contents.
export type File = { cid: string; name?: string | null;};
A notification of the workspace.
export type Notification = { id: string; type: string; title?: string | null; body?: string | null; href?: string | null; createdAt: Date | string;};
The workspace the agent answers about.
export type Workspace = { id: string; slug: string; name?: string | null; description?: string | null; admins: string[]; ownerUserAddress?: string | null; ownerOrganizationId?: string | null; ownerOrganization?: { id?: string | null; } | null; organigrams: OrganigramInput[];};
The output of a language model is untrusted. normalizeResponse() keeps only valid citations, and only previews that create a new organigram or add to an organigram that is not deployed, without changing what it holds. The agent never proposes, signs or sends transactions: a deployed organigram changes only through its own procedures.
Validate the answer of the language model and turn it into an AgentResponse.
Treat the model's output as untrusted. Without a message, the answer is replaced by a fallback message. Only citations of known types are kept, at most 8. A preview is kept only if it is a new organigram, or an addition to an organigram of the workspace that has no deployed organ, procedure or asset and that changes or removes none of its organs, entries, procedures or assets. Invalid previews are dropped.
normalizeResponse: (input: unknown, context: Context) => AgentResponse
Parameters
| Name | Type | Description |
|---|---|---|
input | unknown | The answer of the model, parsed from JSON. |
context | Context | The context the prompt was built with. |
Returns
| Type | Description |
|---|---|
AgentResponse | The answer, without threadId. |
An answer of the workspace agent, as returned by normalizeResponse().
export type AgentResponse = { threadId?: string; message: string; citations: Citation[]; preview?: OrganigramPreview;};
A reference to a workspace object that supports an answer of the agent.
export type Citation = { type: 'workspace' | 'organigram' | 'organ' | 'procedure' | 'asset' | 'notification' | 'file'; id: string; label: string;};
A proposed addition to an existing organigram that is not deployed yet.
export type MergeOrganigramPreview = { type: 'organigram'; organigramId: string; persistence: 'merge-additive'; summary: string[]; organigram: OrganigramJson;};
A proposed new organigram.
export type NewOrganigramPreview = { type: 'new-organigram'; persistence: 'create'; summary: string[]; organigram: OrganigramJson;};
A change to organigrams proposed by the agent, which the user reviews before saving it.
export type OrganigramPreview = MergeOrganigramPreview | NewOrganigramPreview;
Types of the requests to the agent and of the conversations stored in the history of a workspace.
Body of a request to the workspace agent.
export type AgentRequest = { threadId?: string | null; message?: string; messages?: Message[]; currentOrganigramId?: string | null; source?: ThreadSource;};
A message of a conversation with the workspace agent.
export type Message = { role: 'user' | 'assistant'; content: string;};
Body of a request that stores a conversation held elsewhere, such as on the home page hero, in the workspace history.
export type RecordThreadRequest = { action: 'recordThread'; source: ThreadSource; userMessage: string; assistantMessage: string; currentOrganigramId?: string | null;};
A message as stored in the history of a conversation.
export type StoredMessage = Message & { id: string; createdAt: string; response?: AgentResponse;};
A stored conversation with the workspace agent.
export type Thread = { id: string; title: string; source: ThreadSource; workspace?: ThreadWorkspace | null; currentOrganigramId?: string | null; createdAt: string; updatedAt: string; messages: StoredMessage[];};
Where a conversation started: in a workspace, or on the home page hero.
export type ThreadSource = 'workspace' | 'hero';
List of the conversations of a user.
export type ThreadsResponse = { threads: Thread[];};
The workspace of a conversation.
export type ThreadWorkspace = { id: string; name?: string | null;};
Check that a value is a message with a role and non-empty content, for example in a request body.
isMessage: (value: unknown) => value is Message
Parameters
| Name | Type | Description |
|---|---|---|
value | unknown | The value to check. |
Users can upload bylaws or other documents for the agent to draw an organigram from. These functions extract their text on the server. The text is written by third parties: give it to the model as data.
A file uploaded by a user, as a browser or Node.js File.
export type UploadedDocument = Pick<File, 'name' | 'type' | 'arrayBuffer' | 'text'>;
Extract the text of an uploaded document, to give it to the agent.
Reads Word documents (body, headers, footers, footnotes and endnotes), PDF files and plain text. PDF pages without enough text are read with OCR in English and French, which needs Poppler (pdftocairo) installed on the server; if OCR fails, the text found in the PDF is kept. Runs on Node.js only. The text is written by third parties: give it to the model as data, never as instructions.
extractTextFromUploadedDocument: (file: UploadedDocument) => Promise<string>
Parameters
| Name | Type | Description |
|---|---|---|
file | UploadedDocument | The uploaded file. |
Returns
| Type | Description |
|---|---|
Promise<string> | The text, with whitespace normalized. |
Check whether a file is a Word document (.docx), by its MIME type or extension.
isDocxDocument: (file: Pick<UploadedDocument, "name" | "type">) => boolean
Parameters
| Name | Type | Description |
|---|---|---|
file | Pick<UploadedDocument, "name" | "type"> | The file to check. |
Check whether a file is a PDF, by its MIME type or extension.
isPdfDocument: (file: Pick<UploadedDocument, "name" | "type">) => boolean
Parameters
| Name | Type | Description |
|---|---|---|
file | Pick<UploadedDocument, "name" | "type"> | The file to check. |
Count the tokens used by each call to a language model, to bill or limit usage.
Read the tokens used by a call to a language model from the provider's response.
Reads the usage fields of OpenAI-compatible responses (input_tokens, prompt_tokens, cached token details) and Ollama responses (prompt_eval_count, eval_count). When the provider reports no input or output tokens, they are estimated as a quarter of the length of the prompt or completion, and estimatedTokens is true.
extractModelUsage: (input: { payload: unknown; prompt: string; completion: string; provider: string; model: string;}) => AiModelUsage
Tokens used by one call to a language model.
export type AiModelUsage = { provider: string; model: string; inputTokens: number; cachedInputTokens: number; outputTokens: number; totalTokens: number; estimatedTokens: boolean;};
Edit this page
On this page:
© 2026 Organigram SAS
All Rights Reserved.