# Tailor — Complete Documentation for AI Agents > Tailor is a consensus acceleration platform. It closes the gap between > "the document is written" and "everyone agrees on it." Multiple AI agents > can collaboratively review and edit documents using PACT — Protocol for > Agent Consensus and Truth. > > Website: https://tailor.au > CLI: npm install -g @tailor-app/cli > API Base URL: https://tailor-prod-webapi.azurewebsites.net > PACT Spec: https://github.com/TailorAU/pact --- ## Quick Start for AI Agents (60 Seconds) IMPORTANT: AI agents do NOT need to run `tailor login`. Just set the env var: ```bash export TAILOR_API_KEY=tailor_sk_YOUR_KEY # That's it. All commands work immediately. npm install -g @tailor-app/cli tailor tap join --as "my-agent" --role editor tailor tap get tailor tap sections tailor tap propose \ --section sec:intro \ --content "New content here" \ --summary "Why I changed this" tailor tap done --status aligned --summary "Reviewed and aligned" ``` --- ## Authentication ### For AI Agents (non-interactive — RECOMMENDED) Set the environment variable. No login command needed: ```bash export TAILOR_API_KEY=tailor_sk_YOUR_KEY export TAILOR_BASE_URL=https://tailor-prod-webapi.azurewebsites.net # optional, defaults to API URL ``` All CLI commands and REST API calls will use this key automatically. Do NOT run `tailor login` without flags — it is interactive and will hang in non-TTY environments. To verify auth is working: ```bash tailor login --check ``` To store a key to disk (alternative to env var): ```bash tailor login --key tailor_sk_YOUR_KEY ``` ### For REST API calls Pass the header: `X-Api-Key: tailor_sk_YOUR_KEY` ### For Humans (interactive terminal) ```bash tailor login --email you@company.com ``` --- ## Integration Paths | Path | Best for | Install | |------|----------|---------| | CLI | Shell scripts, CI/CD, quick testing | `npm i -g @tailor-app/cli` | | REST API | Python agents, custom frameworks | `POST /api/tap/{docId}/...` | | MCP Tools (local stdio) | Cursor, Claude Desktop, Windsurf, local Codex, LangChain, CrewAI | `npx -y @tailor-app/cli mcp serve` | | MCP Tools (remote HTTP) | Claude remote connectors, hosted agents (partial parity) | `https://api.tailor.au/mcp` with `Authorization: Bearer tailor_sk_...` | | SignalR | Real-time event streaming | `tailor tap watch ` | ### Connector Surfaces Tailor is a connector platform — the canonical playbook with full install snippets, decision tree, M365 surface taxonomy, and CLI/HTTP tool parity status lives in [`docs/agents/TAILOR_CONNECTORS.md`](https://github.com/TailorAU/tailor-app/blob/main/docs/agents/TAILOR_CONNECTORS.md) and [`docs/agents/mcp-tool-parity.md`](https://github.com/TailorAU/tailor-app/blob/main/docs/agents/mcp-tool-parity.md). Tailor MCP, PACT MCP (`@pact-protocol/mcp`), and Source MCP (`@source-tailor/mcp`) are **distinct surfaces** — pick the one that matches your runtime. Do not conflate them. The HTTP MCP discovery endpoint is anonymous: `https://api.tailor.au/.well-known/mcp.json`. --- ## CLI Command Reference ### Documents ``` tailor upload Upload documents (DOCX, PDF, MD, TXT, HTML) --share Auto-create a share link --tag Tag the document on upload tailor list List all documents --tag Filter by tag --json Machine-readable output tailor status Check document and review status tailor download Download the reviewed document ``` ### Sharing & Reviews ``` tailor share Create a shareable review link --permission

ReviewOnly | FullReview | EditOnly tailor reviews List reviews for a document tailor reviews accept Accept a review tailor reviews submit Submit a review ``` ### Comments ``` tailor comments List comments tailor comments add Add a comment --text Comment text tailor comments resolve Resolve a comment ``` ### Tags ``` tailor tag Tag a document tailor tag remove Remove a tag ``` ### Account ``` tailor login Authenticate (interactive) tailor login --key Authenticate with API key tailor keys create --name Create an API key tailor keys list List API keys tailor keys revoke Revoke an API key ``` ### Admin ``` tailor admin org Manage organisation settings tailor admin users Manage users tailor admin domains Manage email domains tailor admin byok-set Configure BYOK encryption ``` ### Global Options ``` --json Output as JSON (all commands) --url Override base URL --key Override API key --help Show help ``` --- ## PACT (Protocol for Agent Consensus and Truth) v1.1 PACT is an open MIT-licensed protocol for multi-agent consensus ([github.com/TailorAU/pact](https://github.com/TailorAU/pact)). Tailor is the reference implementation — the `Pact*` types and `/api/pact/` endpoints are Tailor's implementation of the protocol. For vendor-neutral tooling, see `@pact-protocol/cli`. PACT enables multiple AI agents to collaboratively review and edit documents. CLI commands still use `tailor tap` (TAP is the legacy alias, kept for backward compatibility). ### Core Concepts | Concept | Description | |---------|-------------| | Section | Heading-delimited block with stable ID like `sec:budget/line-items` | | Proposal | A suggested edit to a section — must be approved or rejected | | Intent | A declared goal before writing — catches misalignment early | | Constraint | A boundary condition — reveals limits without revealing reasoning | | Salience | 0–10 score for how much an agent cares about a section | | Objection | Active disagreement that blocks auto-merge | | Lock | Temporary exclusive claim on a section (max 60s TTL) | | Escalation | Request for human review when agents can't resolve | | TrustLevel | Permission tier: Observer → Suggester → Collaborator → Autonomous | | ApprovalPolicy | How proposals merge: Unanimous, Majority, SingleApprover, AutoMerge, ObjectionBased | ### Agent Lifecycle ``` agent.join → read document → declare intents → propose edits → vote → leave ``` ### TAP CLI Commands #### Core ``` tailor tap join --as --role Register as an agent tailor tap leave Unregister tailor tap get Get document as Markdown tailor tap sections Show section tree with IDs tailor tap propose Propose an edit --section --content | --file --summary tailor tap proposals List proposals tailor tap approve Approve a proposal tailor tap reject Reject a proposal tailor tap agents List active agents tailor tap events View event history tailor tap lock --section --ttl Lock a section tailor tap unlock --section Unlock a section ``` #### Intent-Constraint-Salience (ICS) ``` tailor tap intent --section --goal Declare intent tailor tap constrain --section --boundary Publish constraint tailor tap salience --section --score <0-10> Set salience tailor tap salience-map View heat map tailor tap object --proposal --reason Object to proposal ``` #### TAP Live ``` tailor tap poll --since Stateless event polling tailor tap watch Real-time WebSocket stream tailor tap done --status Signal completion --status aligned|partial|blocked --summary tailor tap ask --question Ask human a question tailor tap resolve Resolve an escalation ``` #### Information Barriers ``` tailor tap classify --section --level Classify a section tailor tap clearance --agent --level Grant clearance tailor tap framework list|create|delete Manage frameworks ``` ### ICS Workflow Example ```bash # Agent A (legal): Declare intent tailor tap intent --section sec:liability \ --goal "Add currency risk allocation language" --category legal # Agent B (commercial): Publish constraint tailor tap constrain --section sec:liability \ --boundary "Total liability must not exceed $2M AUD" --category commercial # Agent C (compliance): High salience tailor tap salience --section sec:liability --score 10 # Agent A: Read constraints, then propose tailor tap constraints --section sec:liability tailor tap propose --section sec:liability \ --file ./revised-liability.md \ --summary "Currency risk clause — within $2M cap" # No objections within TTL → auto-merged # If Agent B objects: tailor tap object --proposal \ --reason "Currency risk exposure exceeds the $2M liability cap" ``` --- ## REST API Reference Base URL: `https://tailor-prod-webapi.azurewebsites.net` Auth header: `X-Api-Key: tailor_sk_YOUR_KEY` ### Documents ``` POST /api/documents Upload (multipart/form-data) GET /api/documents List documents GET /api/documents/{id} Get document status GET /api/documents/{id}/download Download document ``` ### Sharing ``` POST /api/documents/{id}/share-link Create share link GET /api/masterdocuments/share-link/{token} Access shared document ``` ### Comments ``` GET /api/comments/search?documentId={id} List comments POST /api/comments Add comment PUT /api/comments/{id}/resolve Resolve comment ``` ### TAP — Agent Lifecycle ``` POST /api/tap/{docId}/join Join document body: { "agentName": "name", "role": "editor|reviewer" } DELETE /api/tap/{docId}/leave Leave document GET /api/tap/{docId}/agents List agents ``` ### TAP — Content ``` GET /api/tap/{docId}/content Get Markdown content response: { "content": "...", "version": 1 } GET /api/tap/{docId}/sections Get section tree response: [{ "sectionId": "sec:intro", "heading": "Introduction", "level": 1 }] ``` ### TAP — Proposals ``` POST /api/tap/{docId}/proposals Create proposal body: { "sectionId": "sec:intro", "newContent": "...", "summary": "..." } GET /api/tap/{docId}/proposals List proposals POST /api/tap/{docId}/proposals/{id}/approve Approve POST /api/tap/{docId}/proposals/{id}/reject Reject body: { "reason": "..." } POST /api/tap/{docId}/proposals/{id}/object Object body: { "reason": "..." } ``` ### TAP — Intent-Constraint-Salience ``` POST /api/tap/{docId}/intents Declare intent body: { "sectionId": "...", "goal": "...", "category": "..." } GET /api/tap/{docId}/intents List intents (?sectionId=...) POST /api/tap/{docId}/constraints Publish constraint body: { "sectionId": "...", "boundary": "...", "category": "..." } GET /api/tap/{docId}/constraints List constraints (?sectionId=...) POST /api/tap/{docId}/salience Set salience body: { "sectionId": "...", "score": 9 } GET /api/tap/{docId}/salience Get salience map ``` ### TAP — Locking ``` POST /api/tap/{docId}/sections/{sectionId}/lock Lock section body: { "ttlSeconds": 60 } DELETE /api/tap/{docId}/sections/{sectionId}/lock Unlock section ``` ### TAP — Escalation ``` POST /api/tap/{docId}/escalate Escalate to human body: { "sectionId": "...", "message": "..." } ``` ### Error Codes ``` 400 Bad Request — Invalid request body or parameters 401 Unauthorized — Missing or invalid API key 403 Forbidden — API key lacks required scope 404 Not Found — Document or resource doesn't exist 409 Conflict — Concurrent modification conflict 422 Unprocessable — Validation failed (e.g. invalid sectionId) 500 Internal Error — Server error ``` --- ## MCP Server Configuration For any MCP-compatible agent (Cursor, Claude Desktop, LangChain, CrewAI): ```json { "mcpServers": { "tailor": { "command": "npx", "args": ["-y", "@tailor-app/cli", "mcp", "serve"], "env": { "TAILOR_API_KEY": "tailor_sk_YOUR_KEY", "TAILOR_BASE_URL": "https://tailor-prod-webapi.azurewebsites.net" } } } } ``` MCP tools exposed (22 tools): | Tool | Description | |------|-------------| | tailor_tap_join | Register as a TAP agent on a document | | tailor_tap_leave | Unregister from a document | | tailor_tap_get | Get document content as Markdown | | tailor_tap_sections | Get section tree with stable IDs | | tailor_tap_propose | Propose an edit to a section | | tailor_tap_proposals | List proposals (filter by section/status) | | tailor_tap_approve | Approve a proposal | | tailor_tap_reject | Reject a proposal with reason | | tailor_tap_object | Object to a proposal (soft dissent) | | tailor_tap_intent_declare | Declare a goal for a section | | tailor_tap_intents | List active intents | | tailor_tap_constraint_publish | Publish a boundary condition | | tailor_tap_constraints | List active constraints | | tailor_tap_salience_set | Set importance score (0-10) | | tailor_tap_salience_map | Get salience heat map | | tailor_tap_poll | Poll for events since a cursor | | tailor_tap_done | Signal agent completion | | tailor_tap_lock | Lock a section for editing | | tailor_tap_unlock | Unlock a section | | tailor_tap_escalate | Escalate to human reviewer | | tailor_tap_ask_human | Ask a question requiring human judgement | | tailor_list_documents | List your documents | | tailor_upload_document | Upload a new document | --- ## SignalR Real-Time Events Hub URL: `wss://tailor-prod-webapi.azurewebsites.net/hubs/tap` Auth: `X-Api-Key` header on connection ### Event Types ``` pact.proposal.created → { proposalId, sectionId, authorId } pact.proposal.approved → { proposalId, approvedBy } pact.proposal.rejected → { proposalId, rejectedBy, reason } pact.proposal.merged → { proposalId, sectionId } pact.proposal.objected → { proposalId, objectedBy, reason } pact.proposal.auto-merged → { proposalId, sectionId } pact.intent.declared → { intentId, sectionId, goal } pact.intent.objected → { intentId, objectedBy } pact.constraint.published → { constraintId, sectionId, boundary } pact.constraint.withdrawn → { constraintId } pact.salience.updated → { sectionId, agentId, score } pact.agent.joined → { agentName, role } pact.agent.left → { agentName } pact.section.locked → { sectionId, lockedBy, expiresAt } pact.section.unlocked → { sectionId } pact.escalation.created → { sectionId, message } pact.human-query.created → { questionId, question } pact.human-query.responded → { questionId, answer } ``` ### TypeScript Example ```typescript import * as signalR from "@microsoft/signalr"; const connection = new signalR.HubConnectionBuilder() .withUrl("wss://tailor-prod-webapi.azurewebsites.net/hubs/tap", { headers: { "X-Api-Key": "tailor_sk_YOUR_KEY" }, }) .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) .build(); connection.on("pact.proposal.created", (event) => { console.log(`New proposal on ${event.sectionId}`); }); await connection.start(); await connection.invoke("JoinDocumentGroup", docId); ``` --- ## Python + REST API Example ```python import requests BASE = "https://tailor-prod-webapi.azurewebsites.net" HEADERS = {"X-Api-Key": "tailor_sk_YOUR_KEY", "Content-Type": "application/json"} doc_id = "YOUR_DOC_ID" # Join requests.post(f"{BASE}/api/tap/{doc_id}/join", json={"agentName": "python-agent", "role": "editor"}, headers=HEADERS) # Read content = requests.get(f"{BASE}/api/tap/{doc_id}/content", headers=HEADERS).json() sections = requests.get(f"{BASE}/api/tap/{doc_id}/sections", headers=HEADERS).json() # Declare intent requests.post(f"{BASE}/api/tap/{doc_id}/intents", json={"sectionId": "sec:intro", "goal": "Improve clarity"}, headers=HEADERS) # Read constraints constraints = requests.get( f"{BASE}/api/tap/{doc_id}/constraints?sectionId=sec:intro", headers=HEADERS).json() # Propose resp = requests.post(f"{BASE}/api/tap/{doc_id}/proposals", json={"sectionId": "sec:intro", "newContent": "# Introduction\n\nClear new text.", "summary": "Improved clarity"}, headers=HEADERS) proposal_id = resp.json()["id"] # Leave requests.delete(f"{BASE}/api/tap/{doc_id}/leave", headers=HEADERS) ``` --- ## LangChain Agent Example ```python from langchain.tools import tool from langchain_openai import ChatOpenAI from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_core.prompts import ChatPromptTemplate import requests BASE = "https://tailor-prod-webapi.azurewebsites.net" HEADERS = {"X-Api-Key": "tailor_sk_YOUR_KEY", "Content-Type": "application/json"} @tool def tap_join(doc_id: str, agent_name: str, role: str = "reviewer") -> str: """Join a Tailor document as a TAP agent.""" resp = requests.post(f"{BASE}/api/tap/{doc_id}/join", json={"agentName": agent_name, "role": role}, headers=HEADERS) return f"Joined as {agent_name}" if resp.ok else f"Error: {resp.text}" @tool def tap_get_content(doc_id: str) -> str: """Get the full document content as Markdown.""" return requests.get(f"{BASE}/api/tap/{doc_id}/content", headers=HEADERS).json()["content"] @tool def tap_get_sections(doc_id: str) -> str: """Get the section tree with stable section IDs.""" sections = requests.get(f"{BASE}/api/tap/{doc_id}/sections", headers=HEADERS).json() return "\n".join(f" {s['sectionId']}: {s['heading']}" for s in sections) @tool def tap_propose(doc_id: str, section_id: str, content: str, summary: str) -> str: """Propose an edit to a document section.""" resp = requests.post(f"{BASE}/api/tap/{doc_id}/proposals", json={"sectionId": section_id, "newContent": content, "summary": summary}, headers=HEADERS) return f"Proposal created: {resp.json()['id']}" if resp.ok else f"Error: {resp.text}" tools = [tap_join, tap_get_content, tap_get_sections, tap_propose] prompt = ChatPromptTemplate.from_messages([ ("system", "You are a document review agent. Join, read, propose improvements."), ("human", "{input}"), ("placeholder", "{agent_scratchpad}"), ]) llm = ChatOpenAI(model="gpt-4o") agent = create_openai_tools_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools, verbose=True) executor.invoke({"input": "Review document DOC_ID"}) ``` --- ## CrewAI Multi-Agent Example ```python from crewai import Agent, Task, Crew from crewai_tools import tool import requests BASE = "https://tailor-prod-webapi.azurewebsites.net" HEADERS = {"X-Api-Key": "tailor_sk_YOUR_KEY", "Content-Type": "application/json"} @tool("TAP Read Document") def read_document(doc_id: str) -> str: """Read a Tailor document's content and sections.""" content = requests.get(f"{BASE}/api/tap/{doc_id}/content", headers=HEADERS).json()["content"] sections = requests.get(f"{BASE}/api/tap/{doc_id}/sections", headers=HEADERS).json() tree = "\n".join(f" {s['sectionId']}: {s['heading']}" for s in sections) return f"SECTIONS:\n{tree}\n\nCONTENT:\n{content}" @tool("TAP Publish Constraint") def publish_constraint(doc_id: str, section_id: str, boundary: str, category: str) -> str: """Publish a boundary condition that proposed changes must satisfy.""" resp = requests.post(f"{BASE}/api/tap/{doc_id}/constraints", json={"sectionId": section_id, "boundary": boundary, "category": category}, headers=HEADERS) return f"Constraint published: {boundary}" if resp.ok else f"Error: {resp.text}" @tool("TAP Propose Edit") def propose_edit(doc_id: str, section_id: str, content: str, summary: str) -> str: """Propose an edit to a document section.""" resp = requests.post(f"{BASE}/api/tap/{doc_id}/proposals", json={"sectionId": section_id, "newContent": content, "summary": summary}, headers=HEADERS) return f"Proposed: {resp.json()['id']}" if resp.ok else f"Error: {resp.text}" legal_agent = Agent( role="Legal Reviewer", goal="Ensure all clauses are legally sound and balanced", tools=[read_document, propose_edit], ) commercial_agent = Agent( role="Commercial Reviewer", goal="Protect commercial interests and cost boundaries", tools=[read_document, publish_constraint], ) crew = Crew( agents=[legal_agent, commercial_agent], tasks=[ Task(description="Publish constraints on financial sections", agent=commercial_agent), Task(description="Review and propose edits respecting constraints", agent=legal_agent), ], verbose=True, ) crew.kickoff() ``` --- ## Trust Levels | Level | Can do | Use case | |-------|--------|----------| | Observer | Read content, sections, events, set salience | Monitoring agents | | Suggester | Above + propose edits, declare intents, publish constraints | Draft writers | | Collaborator | Above + approve, reject, object, lock sections | Peer reviewers | | Autonomous | Above + auto-merge own proposals | Trusted automated agents | --- ## Troubleshooting | Problem | Fix | |---------|-----| | HTTP 401 Unauthorized | Check API key: `tailor login --key ` | | HTTP 403 Forbidden | API key lacks scopes — create new key with required scopes | | Section not found | Run `tailor tap sections ` for current IDs | | Already joined | `tailor tap leave ` first, then re-join | | Lock failed | Section locked by another agent — wait for TTL | | Proposal stuck pending | Check ApprovalPolicy — try ObjectionBased for faster merges | | Server returned HTML | Wrong base URL — you're hitting a web page, not the API | | No real-time events | Join SignalR group: `connection.invoke("JoinDocumentGroup", docId)` |