Table of contents

Openclaw (draft)

Prepared for: Mark · Saad Mohsin · Hasaan Jamil Date: May 2026 Status: Living document — updated as decisions are made

A company knowledge fabric — a governed library of research, datasheets, manuals, contact records and executive memory — that our agents can query safely. Each user gets a personal agent reachable from Cliq and email. All personal agents, plus dedicated task agents, share one library and one shared set of rules. Nothing important gets said twice, nothing sensitive leaks, and every retrieval has a clean audit trail.

User (Cliq / Email) │ ▼ [Personal Agent] ← One per user, auto-created, OAuth2 as that user Thin orchestrator Handles conversation + identity only │ ├── Zoho operation needed? │ └── calls Warden → gets access token │ ├── Complex task? │ └── sessions_spawn(specialist sub-agent) │ linkedin-agent / docs-agent / research-agent / crm-agent │ └── Library query needed? └── calls Policy Broker (n8n) │ ├──► Librarian: Public & Marketing ├──► Librarian: Product & Support ├──► Librarian: Commercial & Research ├──► Librarian: Executive & Board └──► Librarian: Compliance & Contacts │ ▼ Qdrant + WorkDrive + Zoho CRM
  • ✅ Done: One personal agent per user, auto-created
  • Thin orchestrator — handles conversation, routes tasks to sub-agents or librarians
  • Authenticates to Zoho via OAuth2 through Warden
  • Can only access what the user can access in WorkDrive and CRM
  • Personal memory, workspace, and preferences fully isolated per user
  • When user's Zoho account is disabled, their token is revoked immediately via the UI

Warden is a standalone Next.js + shadcn/ui application running on Ionos. It is the single source of truth for all OAuth tokens, user permissions, and connection state in the OpenClaw system. Named deliberately — Warden controls who gets access to what, and nothing passes through without its approval.

Warden does four things:

  • Syncs users from Zoho Directory so only known, active employees can authorise agents
  • Restricts scopes per user — new users get a safe default set of restricted scopes, and admins can grant exceptions individually
  • Owns all tokens — refresh tokens and cached access tokens live in Warden's encrypted database, never in agent workspace files or environment variables
  • Serves agents — OpenClaw agents call Warden's internal API on every Zoho operation; Warden returns a cached or freshly refreshed access token, or sends back an OAuth URL if the user hasn't authorised yet

No token ever lives in a workspace file, an environment variable, or an agent's memory beyond the duration of one task. Warden handles everything.

User in Cliq: "Show me my WorkDrive files" │ ▼ Personal Agent calls UI: GET /api/internal/token?user=mark@tellus&service=workdrive Authorization: Bearer {AGENT_SECRET} │ ├── Token exists and valid │ → UI returns access token │ → Agent makes Zoho API call directly │ → Returns result to user │ └── No token / expired / not authorised → UI returns { error: "not_connected", authUrl: "/connect?..." } → Agent sends user in Cliq: "I need WorkDrive access. Click here to authorise: [URL]" │ ▼ User clicks → lands on UI /connect page Sees: service name + exact scopes to be granted Clicks Authorise │ ▼ Zoho OAuth consent screen │ ▼ Zoho redirects to UI /api/oauth/callback UI exchanges auth code → receives access_token + refresh_token UI stores both in encrypted Postgres columns User sees: "✅ Connected — return to Cliq" │ ▼ Agent retries original request → now succeeds

Token typeWhere storedRuleRefresh tokenUI Postgres DB (encrypted column)Never leaves the UI. Only UI backend reads/writes it.Access tokenUI Postgres DB (encrypted column)Refreshed inline before returning to agent.Agent receivesAccess token in API response onlyHeld in agent memory for session only. Never written to disk or workspace files.Sub-agent receivesAccess token as env var from parentSingle task use. Discarded on completion. Sub-agents never call UI directly.

Admin: Scope pre-approval

Before any user connects, admin configures the maximum scopes each user's agent may request. The OAuth URL the agent generates will only include pre-approved scopes — the user cannot grant more than the admin allows.

User Service Allowed Scopes ────────────────────────────────────────────────────── mark@tellus WorkDrive files.READ, files.CREATE mark@tellus CRM contacts.READ, leads.READ nina@tellus WorkDrive files.READ nina@tellus CRM contacts.READ

Admin: Connections dashboard

Main view — all users × all services, live status:

User Service Status Scopes Granted Last Used Requests ───────────────────────────────────────────────────────────────────────────── Mark WorkDrive ✅ Active files.READ, files.CREATE 2 min ago 1,247 Mark CRM ✅ Active contacts.READ 1 hr ago 89 Nina WorkDrive ✅ Active files.READ 3 hrs ago 412 Nina CRM ❌ Expired — 2 days ago — Hasaan WorkDrive ⏳ Pending — Never —

Admin: Per-connection detail

Full scope list, token expiry countdown, request log (timestamp / agent / endpoint / outcome), revoke button, re-authorise button.

Admin: Revoke

One click. Warden calls Zoho OAuth revoke endpoint, deletes tokens from database, marks connection revoked. Agent gets not_connected on its next request immediately.

Admin: Request log

Every agent token request logged — timestamp, user, agent ID, service, endpoint, outcome. Filterable by any field. Full audit trail.

User: /connect page (public)

The page users land on when their agent sends them an auth URL. Shows the service being connected and the exact scopes that will be granted. One Authorise button. After callback completes, user sees a success page and returns to Cliq.

EndpointCalled byPurposeGET /api/internal/tokenOpenClaw agentsGet access token for a user+service. Returns token or auth URL.POST /api/internal/log-requestOpenClaw agentsLog a Zoho API call outcome.GET /api/oauth/callbackZoho (redirect)Exchange auth code for tokens, store in DB.POST /api/oauth/revokeAdmin UIRevoke a connection.

The UI runs a Node.js cron job (same process) every 45 minutes. Loops through all active connections, refreshes any token expiring within 90 minutes, logs outcome. On failure, marks connection expired — agent gets not_connected on next call.

Personal agents and task agents are thin orchestrators. When a task needs specialist capability, they spawn an isolated sub-agent that loads only what it needs.

Personal Agent (~2,000 token bootstrap) │ ├── LinkedIn request → sessions_spawn(linkedin-agent) │ TOOLS_LINKEDIN.md + LinkdAPI skill only (~3,500 tokens) │ ├── Document needed → sessions_spawn(docs-agent) │ TOOLS_DOCS.md + pdf/docx skills (~3,000 tokens) │ ├── Web research → sessions_spawn(research-agent) │ TOOLS_RESEARCH.md + Tavily + Apollo (~3,500 tokens) │ ├── Send email → sessions_spawn(email-agent) │ TOOLS_EMAIL.md + imap-smtp skill (~2,500 tokens) │ └── CRM lookup → sessions_spawn(crm-agent) TOOLS_CRM.md + Zoho MCP CRM only (~2,000 tokens)

Each sub-agent has its own TOOLS_[DOMAIN].md and MEMORY_[DOMAIN].md. It knows nothing about other domains, users, or credentials outside its task. When the parent passes a Zoho access token to a sub-agent, the sub-agent uses it for the task and discards it — no path to refresh or re-request.

AgentPurposeAgent 1 — Market IntelligenceDaily briefings, web research, email to distribution listAgent 2 — Product KnowledgeProduct Q&A via Cliq + email + SalesIQAgent 3 — Website / Support / Sales TriageSalesIQ chatbot, Zoho Desk, inbound sales filterAgent 4 — Lead Generation ResearchProspect lists, writes CSV to staging, no outreachAgent 5 — Social Media & ContentDraft-only, writes to review folder, human approves

Based on OpenClaw Memory Architecture Spec v1.0, May 2026.

The core principle: an agent should not re-read the entire history of its relationship with a user on every turn. It remembers the broad strokes (identity, user profile), the recent session (digest from today), and looks up specific prior context on demand — never loading everything just in case.

TierWhat it isSizeWhen loaded1 — IdentitySOUL.md, IDENTITY.md, TOOLS.md~1–2 KBAlways. Prompt-cached.2 — User profileUSER.md per user, preferences~1–2 KBAt session start, cached per user.3 — Working memoryCurrent conversation, raw, in context windowGrows during sessionLive during session only. Compacted at ~40% of model window (older messages summarised, most recent 10 kept verbatim).4 — Session digestsStructured summary written at session end~500–1,000 tokensToday's digest loaded at session start. Older digests queried on demand only.5 — Long-term semanticEvery digest, artifact, decision — semantically searchableUnboundedNever by default. Queried on demand via memory_search.

Default context at session start: ~5 KB. Identity + user profile + today's digest only.

At session start:

  • Load Tier 1 (identity) — cached, cheap
  • Load Tier 2 (user profile) — cached per user, cheap
  • Load Tier 4 (today's digest if it exists) — gives same-day continuity
  • Nothing else. No older history, no prior-day conversations.

During the session:

  • User references prior context ("we discussed X last week") → agent calls memory_search
  • Agent judges it should check proactively → calls memory_search
  • Only the relevant snippet (~0.5–2 KB) is injected — not the whole digest
  • Working memory grows. At ~40% of model window → compaction kicks in

At session end:

  • Agent generates a structured digest (Haiku call — cheap)
  • Digest written to WorkDrive at 90_Agent_Memory/digests/{agent}/{user_id}/YYYY-MM-DD.md
  • Catalog row added to Postgres
  • Digest embedded and written to Qdrant per-user collection
  • Raw working memory discarded

Persistent footprint per session: ~0.5–1 KB of structured data.

One digest per session per user. Target size: 500–1,000 tokens. Structured fields, not prose.

FieldContentsTopicsShort bulleted list of subjects discussed. One line each.DecisionsWhat was decided, why, who decided, when.Open itemsOutstanding items — what, owner, deadline, status.EntitiesPeople, companies, projects, events, locations mentioned. CRM/catalog links where they exist.FilesPointers to artifacts produced (WorkDrive links, not content).SourcesExternal URLs and internal WorkDrive IDs cited.

Example digest (abbreviated):

# 2026-05-23 — Mark / austin-research ## Topics - EV charging events in Madrid 2026 - November flagship: MATELEC GENERA ## Decisions - (none — fact-finding session) ## Open items - Mark may want speaker/exhibitor details for VEM Forum, Euro EV Show, and Global Mobility Call (no commitment yet) ## Entities - Events: Aurora Energy Transition Summit, VEM Forum 11th Ed, MATELEC GENERA 2026 - Venues: IFEMA Madrid, La Nave - Organisations: Aedive, IFEMA, FEDLEC ## Sources - ifema.es/en/matelec - aurorae.com/events

A week later when Mark says "remind me about that November Madrid event" — the agent searches digests, retrieves this, answers in one short response. Cost: a few hundred tokens. Not 264K tokens of replayed history.

Every agent gets one new tool for accessing long-term memory.

memory_search( query: str, # natural language query user_id: str, # whose memory to search agent_name: str, # which agent's memory since: date | None, # optional date filter until: date | None, max_results: int = 3 # snippets to return ) -> List[MemorySnippet]

Under the hood:

  1. Broker validates: this agent is allowed to read this user's memory. Logs the call.
  2. Catalog (Postgres) queried first — cheap structured filter on topics/entities to narrow
  3. Qdrant queried against the user's per-user collection — semantic match
  4. Matching snippets returned with metadata (date, source digest, confidence)
  5. Agent injects only the 1–2 most relevant snippets into context

Total cost of a search: a few hundred tokens of embedding + a few hundred tokens of returned snippet. Not 87K of raw history.

When the agent should call it:

  • User references prior context — "we discussed", "you said", "last week", "the document I sent"
  • Topic comes up the agent thinks it may have seen before
  • Before answering a question that depends on a prior decision

WorkDrive — content

Holds the actual files: identity files, user profiles, daily digests, artifacts, attachments.

90_Agent_Memory/ ├── identity/ │ └── {agent_name}/ │ ├── SOUL.md │ ├── IDENTITY.md │ └── TOOLS.md ├── users/ │ └── {user_id}/ │ ├── USER.md │ └── preferences.md ├── digests/ │ └── {agent_name}/ │ └── {user_id}/ │ ├── 2026-05-23.md │ └── 2026-05-22.md ├── attachments/ │ └── {user_id}/ │ ├── inbound/ │ └── outbound/ └── shared/ └── research/

Per-user folders inherit WorkDrive ACLs. Mark's digests are not readable by Saad's agent because Mark's folder is not accessible to that service account. Permissions enforced by WorkDrive, not application code.

Postgres (catalog) — metadata

One row per memory item. Agent queries catalog first to decide what to load.

CREATE TABLE memory_items ( id UUID PRIMARY KEY, item_type TEXT NOT NULL, -- digest | artifact | decision | attachment agent_name TEXT NOT NULL, user_id TEXT NOT NULL, session_id TEXT, occurred_at TIMESTAMPTZ NOT NULL, topics TEXT[] NOT NULL DEFAULT '{}', entities JSONB NOT NULL DEFAULT '{}', decisions JSONB NOT NULL DEFAULT '[]', workdrive_id TEXT, -- pointer into WorkDrive qdrant_id TEXT, -- pointer into Qdrant collection confidentiality TEXT NOT NULL DEFAULT 'internal', contains_pii BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_memory_user_agent ON memory_items (user_id, agent_name); CREATE INDEX idx_memory_topics ON memory_items USING GIN (topics); CREATE INDEX idx_memory_entities ON memory_items USING GIN (entities);

Qdrant — semantic search

Embeddings of digest and artifact text. Per-user collections enforce isolation.

memory_{user_id} # per-user memory (Mark's digests, his artifacts) memory_shared_research # agent-discovered shared resources (no user scoping) # Existing librarian collections unchanged: col_public_marketing col_product_support col_commercial_research col_executive_board col_compliance

The biggest risk in this design is memory leakage between users. Defence is layered:

  1. WorkDrive ACLs — each user's folder accessible only to that user's service account. First line of defence at the OS level.
  2. Per-user Qdrant collections — memory_{mark_id} is a separate collection from memory_{saad_id}. No cross-user query can return cross-user results — credentials are collection-scoped.
  3. Broker enforcement — every memory_search call passes through the broker, which validates user_id matches the active user before any store is touched.
  4. Audit — every memory access logged with caller, subject, query, and what was returned. Cross-user attempts surface as alerts.

Target: cross-user retrieval count = 0. Any non-zero result is an incident.

MetricTargetDefault context at session start≤ 5 KBPer-turn context (typical)5–25 KBPer-turn context (with retrieval)≤ 40% of model windowDigest size500–1,000 tokensmemory_search latency< 500 msmemory_search false-negative rate< 10%Cross-user retrieval count0

FolderWho can readWhat goes in00_Public_KnowledgeAll staffApproved external material, datasheets, press10_Internal_OperationsAll staffInternal templates, process docs, general reference20_Commercial_IntelligenceCommercial teamMarket research, prospect dossiers, strategy30_Product_and_SupportProduct, support, customer-facingManuals, datasheets, service docs40_Executive_and_BoardExec onlyBoard reports, financing, personnel-sensitive40_Executive_and_Board/00_Highly_RestrictedTightly restrictedContracts under negotiation, live legal matters50_Compliance_and_GovernanceCompliance + adminConsent, suppression, DSARs, retention60_ArchiveInherits from source domainSuperseded versions90_Librarian_IntakeAdmin onlyStaging area for inbound material90_Agent_MemoryPer-user ACLsAgent identity files, user profiles, session digests

Applied to all governed Team Folders:

FieldTypeNotesDomainChoice (mandatory)Five values matching the scoped librariansSensitivityChoice (mandatory)Public / Internal / Confidential / Restricted / Highly_RestrictedStatusChoice (mandatory)Draft / Active / Archived / PendingDocTypeChoicedatasheet / manual / briefing / research_report / board_report / contract / process_doc / otherDocSubmittedViaChoicedirect_upload / email_intake / cliq_intake / api / ingestion_syncDocContributorEmailEmailAuto-populated from OAuth identity at submission. Audit only.DocSummaryMulti-line text≤ 300 words. Not generated for Highly_Restricted.TagsSingle-line textFree-form, comma-separatedEffective dateDateWhen document becomes canonicalReview dateDateNext scheduled reviewContains personal dataChoiceNone / Low / Medium / High

Each librarian owns one domain. They never read outside it.

LibrarianScopeQdrant collectionPublic & Marketing00_Public_Knowledgecol_public_marketingProduct & Support30_Product_and_Support + public subsetcol_product_supportCommercial & Research20_Commercial_Intelligencecol_commercial_researchExecutive & Board40_Executive_and_Board (except Highly Restricted)col_executive_boardCompliance & Contacts50_Compliance_and_Governance + CRM governance metadatacol_compliance

If a question crosses domains, the broker calls each librarian separately and merges — librarians never talk to each other directly.

Runs as an n8n workflow. Every request from a personal agent or task agent passes through it — nothing reaches a librarian or Qdrant directly.

What it checks on every request:

  1. Who is the user? (from OAuth2)
  2. Which agent is asking?
  3. What is the purpose?
  4. What is being requested?
  5. Any special flags? (personal data, bulk operation, suppressed contact)

Then decides: allow in full / allow with redaction / summary only / require human approval / deny. Every decision is logged.

Admin login (password-protected, internal tool).
Scope pre-approval config — per user per service, checkboxes per scope.
Connections dashboard — all users × services, status / scopes / last used / request count.
Per-connection detail — scope list, expiry countdown, request log, revoke, re-authorise.
/connect public page — user-facing OAuth landing.
/api/oauth/callback — exchange code, store tokens in encrypted DB.
/api/internal/token — agent token endpoint.
/api/internal/log-request — agent request logging endpoint.
Background refresh cron — every 45 min, refreshes tokens expiring within 90 min.
Revocation — calls Zoho revoke endpoint, deletes from DB.
Request log view — filterable by user / agent / service / date / outcome
Create 90_Agent_Memory folder in WorkDrive with full subfolder structure.
Apply WorkDrive ACLs: per-user subfolder restricted to that user's service account + admin. 
Create memory_items table in Postgres with schema and three indexes.
Create per-user Qdrant collections (created on first use for new users)
  1. [ ] n8n workflow "Session Digest Writer" — triggered on session end, calls Haiku to produce structured digest
  2. [ ] Workflow writes .md to WorkDrive, catalog row to Postgres, embedding to Qdrant
  3. [ ] Dead-letter queue for failed digest writes — no silent loss
  4. [ ] Error alerting if digest pipeline fails
  • [ ] n8n workflow "Session Digest Writer" — triggered on session end, calls Haiku to produce structured digest
  • [ ] Workflow writes .md to WorkDrive, catalog row to Postgres, embedding to Qdrant
  • [ ] Dead-letter queue for failed digest writes — no silent loss
  • [ ] Error alerting if digest pipeline fails
  • [ ] Implement memory_search — catalog query (Postgres) then Qdrant query, return snippets
  • [ ] Register as tool in all relevant agents
  • [ ] Broker allow rules per agent for memory access
  • [ ] Audit logging on every call
  • [ ] Update agent session-start: load Tier 1 + Tier 2 + today's digest only
  • [ ] Remove any auto-loading of older history
  • [ ] Proactive memory_search check when user references prior context at session start
  • [ ] Keep daily-reset in place during Phases A–D (safety net)
  • [ ] Once digest pipeline is reliable for 1 week → switch daily-reset off
  • [ ] Monitor: 10 sessions/week for 2 weeks — check context retrieval and token usage
  • [ ] Create all Team Folders with correct permissions
  • [ ] Apply Librarian classification Data Template to all governed folders
  • [ ] Configure Data Template schema per Section 7
  • [ ] Postgres catalog database on Ionos (document catalog schema)
  • [ ] Qdrant installed on Ionos
  • [ ] 5 librarian Qdrant collections with separate credentials
  • [ ] Embedding model chosen and locked — model name written to catalog per chunk
  • [ ] Ingestion pipeline: WorkDrive scan → text extract → classify → catalog + Qdrant write
  • [ ] Data Template field population step in ingestion worker
  • [ ] Archive intake pipeline: email/Cliq → stage in 90_Librarian_Intake → classify → review queue
  • [ ] Policy broker workflow with allow/deny rules
  • [ ] Nightly background jobs: reclassification, retention sweeps, contact freshness
  • [ ] Classification review queue
  • [ ] Archive intake review queue
  • [ ] Contact adjudication queue (15k-name dedup)
  • [ ] Outreach approval queue
  • [ ] Archive and rollback view
  • [ ] Agent activity log
  • [ ] Policy editor
  • [ ] DSAR workflow
  • [ ] Personal agent memory inspector (view/clear per user with audit record)
  • [x] OpenClaw running on Ionos ✅
  • [x] cliq-router (Haiku front-door) ✅
  • [x] Email pipeline with Haiku classifier ✅
  • [x] Per-user personal agents with OAuth2, auto-created ✅
  • [ ] Agent 1 — Market Intelligence
  • [ ] Agent 2 — Product Knowledge
  • [ ] Agent 3 — Website / Support / Sales Triage
  • [ ] Agent 4 — Lead Generation Research
  • [ ] Agent 5 — Social Media & Content
  • [ ] Librarian: Public & Marketing
  • [ ] Librarian: Product & Support
  • [ ] Librarian: Commercial & Research
  • [ ] Librarian: Executive & Board
  • [ ] Librarian: Compliance & Contacts
  • [ ] Sub-agent workspaces: linkedin-agent, docs-agent, research-agent, email-agent, crm-agent
  • [ ] Load 15,000-contact list to staging table
  • [ ] Normalise + deduplicate against Zoho CRM
  • [ ] Build decision API: may_I_contact, company lookup, capped prospect list
  • [ ] Apply suppression, lawful basis, freshness rules
  • [ ] Network policy on Ionos: broker-only path to librarians/Qdrant/WorkDrive (enforced at network level)
  • [ ] Prompt injection sanitiser in ingestion pipeline
  • [ ] Append-only audit log for all queries, policy decisions, memory access, denied requests
  • [ ] Highly Restricted subfolder — no Qdrant indexing, no embedding, catalog minimum only
  • [ ] Step A: Agent 1 live — one real daily briefing emailed to distribution list
  • [ ] Step B: Agent 2 + Product librarian + Mark's personal agent — one real support question answered from a manual
  • [ ] Step C: Memory architecture Phases A–B — digest pipeline live, session-start protocol updated
  • [ ] Step D: Warden live — agents can request Zoho access, users authorise via the connect page
  • [ ] Agent 5 + Public & Marketing librarian
  • [ ] Compliance librarian + CRM reconciliation + decision API → Agent 4
  • [ ] Commercial & Research librarian
  • [ ] Memory architecture Phases C–E — memory_search live, daily-reset retired
  • [ ] Agent 3 (external-facing, higher stakes)
  • [ ] Team agents (customer-success channel first)
  • [ ] Executive librarian (last — highest sensitivity)

DocumentRelationship to this planOpenClaw Memory Architecture Spec v1.0Section 6 of this document is the full implementation of that specToken Consumption Analysis v1.1Diagnosed conversation-history-in-context as root cause. Memory architecture is the permanent fix.Librarian Agent How-To Guide v007The catalog + Qdrant + WorkDrive three-store pattern and broker enforcement apply equally to agent memory (Section 6) and the shared library (Sections 7–9). Same mechanism, different content.