[{"data":1,"prerenderedAt":1039},["Reactive",2],{"content-query-ZPmekH9lih":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"published":10,"slug":11,"body":12,"_type":405,"_id":1034,"_source":1035,"_file":1036,"_stem":1037,"_extension":1038},"/articles/the-whole-engagement-in-one-folder","articles",false,"","The whole engagement in one folder","A consulting team's context lives in Teams threads and three people's heads. Here is how we moved it into a SharePoint library, synced it to every laptop with OneDrive, and let Claude Code keep it as an LLM wiki.","2026/6/16","the-whole-engagement-in-one-folder",{"type":13,"children":14,"toc":1025},"root",[15,23,39,48,55,60,74,80,85,94,107,113,133,154,166,365,377,385,391,402,635,640,646,651,669,695,705,723,729,734,926,971,976,982,987,1000,1019],{"type":16,"tag":17,"props":18,"children":19},"element","p",{},[20],{"type":21,"value":22},"text","Every consulting engagement I have been on has the same shape after week six. There is a statement of work nobody has reread since it was signed. There are forty decks, twelve of which are the current one. There is a Teams channel with the real decisions buried in it, and there are two or three people who actually know why things are the way they are. When someone new joins, one of those people loses a day.",{"type":16,"tag":17,"props":24,"children":25},{},[26,28,37],{"type":21,"value":27},"This post is about the setup that fixed it for us, and it is deliberately boring. No platform, no vector database, no procurement. A folder in the client's SharePoint, synced to everyone's laptop with the OneDrive client that IT already installed, and a Claude Code session opened inside it. The folder is an ",{"type":16,"tag":29,"props":30,"children":34},"a",{"href":31,"rel":32},"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f",[33],"nofollow",[35],{"type":21,"value":36},"LLM wiki",{"type":21,"value":38}," in Andrej Karpathy's sense. The sync is free. The permissions are the client's own.",{"type":16,"tag":17,"props":40,"children":41},{},[42],{"type":16,"tag":43,"props":44,"children":47},"img",{"alt":45,"src":46},"A SharePoint document library in the client tenant, synced through OneDrive to three consultant laptops, each running a Claude Code session inside the synced folder. Teams transcripts flow in on the left","/articles/engagement-wiki-architecture.svg",[],{"type":16,"tag":49,"props":50,"children":52},"h2",{"id":51},"why-sharepoint-of-all-things",[53],{"type":21,"value":54},"Why SharePoint, of all things",{"type":16,"tag":17,"props":56,"children":57},{},[58],{"type":21,"value":59},"I tried Notion first, then a git repo. Both failed on the same point. A consulting team on a client site does not get to choose its tools. The client tenant decides where documents may live, which SaaS is approved, and who can see what. SharePoint is already inside that boundary. Every engagement has a document library on day one, the client's information governance already applies to it, and every laptop in the team already has OneDrive syncing it. Nobody has to install anything or ask anyone.",{"type":16,"tag":17,"props":61,"children":62},{},[63,65,72],{"type":21,"value":64},"The second reason is that a document library is a folder. On a Mac the synced copy lives under ",{"type":16,"tag":66,"props":67,"children":69},"code",{"className":68},[],[70],{"type":21,"value":71},"~/Library/CloudStorage/OneDrive-\u003CTenantName>/",{"type":21,"value":73},", and a folder is the one thing an agent is genuinely good at working with. Claude Code does not know or care that the files are being mirrored to the cloud. It reads markdown, it edits markdown, and OneDrive does the rest.",{"type":16,"tag":49,"props":75,"children":77},{"id":76},"the-layout",[78],{"type":21,"value":79},"The layout",{"type":16,"tag":17,"props":81,"children":82},{},[83],{"type":21,"value":84},"The library is organised the way Karpathy describes, with one change for teams, which is that the log is per person.",{"type":16,"tag":86,"props":87,"children":89},"pre",{"code":88},"Engagement - Acme Data Platform/\n├── CLAUDE.md               the schema: what lives where, what may not change\n├── sources/                immutable. nothing here is ever edited\n│   ├── contract/           SoW, change requests, RAID as exported\n│   ├── decks/              every deck, dated, as PDF\n│   ├── meetings/           one folder per meeting, transcript + notes\n│   └── client-docs/        architecture docs, policies, runbooks the client gave us\n├── wiki/\n│   ├── index.md            every page, one line each\n│   ├── decisions/          one page per decision, with the meeting it came from\n│   ├── people/             one page per stakeholder, role, what they care about\n│   ├── systems/            one page per client system we touch\n│   ├── workstreams/        current state of each workstream\n│   └── open-questions.md\n├── log/\n│   ├── marco.md            append-only, one line per change I made\n│   └── priya.md\n└── inbox/                  things the wiki proposed and a human has not confirmed\n",[90],{"type":16,"tag":66,"props":91,"children":92},{"__ignoreMap":7},[93],{"type":21,"value":88},{"type":16,"tag":17,"props":95,"children":96},{},[97,99,105],{"type":21,"value":98},"The ",{"type":16,"tag":66,"props":100,"children":102},{"className":101},[],[103],{"type":21,"value":104},"sources/",{"type":21,"value":106}," rule is the one that matters. A transcript is never edited, a deck is never replaced in place. If the wiki says something wrong, the fix is to re-ingest the source or to add a newer one. That means any page can be rebuilt from scratch, and it means when two people disagree about what was said, the answer is a file with a date on it.",{"type":16,"tag":49,"props":108,"children":110},{"id":109},"getting-the-meetings-in",[111],{"type":21,"value":112},"Getting the meetings in",{"type":16,"tag":17,"props":114,"children":115},{},[116,118,123,125,131],{"type":21,"value":117},"The wiki is only as good as what goes into ",{"type":16,"tag":66,"props":119,"children":121},{"className":120},[],[122],{"type":21,"value":104},{"type":21,"value":124},", and the richest source on any engagement is the meetings. Teams already transcribes them. The problem is that the transcript lands in the organiser's personal OneDrive under ",{"type":16,"tag":66,"props":126,"children":128},{"className":127},[],[129],{"type":21,"value":130},"Recordings/",{"type":21,"value":132},", which is exactly where nobody will find it in three weeks.",{"type":16,"tag":17,"props":134,"children":135},{},[136,138,144,146,152],{"type":21,"value":137},"A Power Automate flow moves it. The trigger is \"When a file is created\" on the organiser's ",{"type":16,"tag":66,"props":139,"children":141},{"className":140},[],[142],{"type":21,"value":143},"Recordings",{"type":21,"value":145}," folder, the action is \"Copy file\" into ",{"type":16,"tag":66,"props":147,"children":149},{"className":148},[],[150],{"type":21,"value":151},"sources/meetings/",{"type":21,"value":153}," on the library, and a naming step prefixes the date. The whole flow is four boxes and took twenty minutes, most of which was finding the folder picker.",{"type":16,"tag":17,"props":155,"children":156},{},[157,158,164],{"type":21,"value":98},{"type":16,"tag":66,"props":159,"children":161},{"className":160},[],[162],{"type":21,"value":163},".vtt",{"type":21,"value":165}," that Teams produces is not pleasant for a model to read. Timestamps every few seconds, speakers repeated on every cue, and a lot of \"yeah, yeah, no, exactly\". A small script runs on ingest and collapses it into turns:",{"type":16,"tag":86,"props":167,"children":171},{"code":168,"language":169,"meta":7,"className":170,"style":7},"import re, sys\nfrom pathlib import Path\n\nCUE = re.compile(r\"\u003Cv ([^>]+)>(.*?)\u003C/v>\", re.S)\n\ndef turns(vtt: str):\n    speaker, buf = None, []\n    for who, text in CUE.findall(vtt):\n        text = \" \".join(text.split())\n        if who != speaker and buf:\n            yield speaker, \" \".join(buf)\n            buf = []\n        speaker, buf = who, buf + [text]\n    if buf:\n        yield speaker, \" \".join(buf)\n\nsrc = Path(sys.argv[1])\nout = src.with_suffix(\".md\")\nlines = [f\"# {src.stem}\\n\"]\nlines += [f\"**{who}:** {text}\\n\" for who, text in turns(src.read_text())]\nout.write_text(\"\\n\".join(lines))\n","python","language-python shiki shiki-themes github-dark",[172],{"type":16,"tag":66,"props":173,"children":174},{"__ignoreMap":7},[175,186,195,205,214,222,231,240,249,258,267,276,285,294,303,312,320,329,338,347,356],{"type":16,"tag":176,"props":177,"children":180},"span",{"class":178,"line":179},"line",1,[181],{"type":16,"tag":176,"props":182,"children":183},{},[184],{"type":21,"value":185},"import re, sys\n",{"type":16,"tag":176,"props":187,"children":189},{"class":178,"line":188},2,[190],{"type":16,"tag":176,"props":191,"children":192},{},[193],{"type":21,"value":194},"from pathlib import Path\n",{"type":16,"tag":176,"props":196,"children":198},{"class":178,"line":197},3,[199],{"type":16,"tag":176,"props":200,"children":202},{"emptyLinePlaceholder":201},true,[203],{"type":21,"value":204},"\n",{"type":16,"tag":176,"props":206,"children":208},{"class":178,"line":207},4,[209],{"type":16,"tag":176,"props":210,"children":211},{},[212],{"type":21,"value":213},"CUE = re.compile(r\"\u003Cv ([^>]+)>(.*?)\u003C/v>\", re.S)\n",{"type":16,"tag":176,"props":215,"children":217},{"class":178,"line":216},5,[218],{"type":16,"tag":176,"props":219,"children":220},{"emptyLinePlaceholder":201},[221],{"type":21,"value":204},{"type":16,"tag":176,"props":223,"children":225},{"class":178,"line":224},6,[226],{"type":16,"tag":176,"props":227,"children":228},{},[229],{"type":21,"value":230},"def turns(vtt: str):\n",{"type":16,"tag":176,"props":232,"children":234},{"class":178,"line":233},7,[235],{"type":16,"tag":176,"props":236,"children":237},{},[238],{"type":21,"value":239},"    speaker, buf = None, []\n",{"type":16,"tag":176,"props":241,"children":243},{"class":178,"line":242},8,[244],{"type":16,"tag":176,"props":245,"children":246},{},[247],{"type":21,"value":248},"    for who, text in CUE.findall(vtt):\n",{"type":16,"tag":176,"props":250,"children":252},{"class":178,"line":251},9,[253],{"type":16,"tag":176,"props":254,"children":255},{},[256],{"type":21,"value":257},"        text = \" \".join(text.split())\n",{"type":16,"tag":176,"props":259,"children":261},{"class":178,"line":260},10,[262],{"type":16,"tag":176,"props":263,"children":264},{},[265],{"type":21,"value":266},"        if who != speaker and buf:\n",{"type":16,"tag":176,"props":268,"children":270},{"class":178,"line":269},11,[271],{"type":16,"tag":176,"props":272,"children":273},{},[274],{"type":21,"value":275},"            yield speaker, \" \".join(buf)\n",{"type":16,"tag":176,"props":277,"children":279},{"class":178,"line":278},12,[280],{"type":16,"tag":176,"props":281,"children":282},{},[283],{"type":21,"value":284},"            buf = []\n",{"type":16,"tag":176,"props":286,"children":288},{"class":178,"line":287},13,[289],{"type":16,"tag":176,"props":290,"children":291},{},[292],{"type":21,"value":293},"        speaker, buf = who, buf + [text]\n",{"type":16,"tag":176,"props":295,"children":297},{"class":178,"line":296},14,[298],{"type":16,"tag":176,"props":299,"children":300},{},[301],{"type":21,"value":302},"    if buf:\n",{"type":16,"tag":176,"props":304,"children":306},{"class":178,"line":305},15,[307],{"type":16,"tag":176,"props":308,"children":309},{},[310],{"type":21,"value":311},"        yield speaker, \" \".join(buf)\n",{"type":16,"tag":176,"props":313,"children":315},{"class":178,"line":314},16,[316],{"type":16,"tag":176,"props":317,"children":318},{"emptyLinePlaceholder":201},[319],{"type":21,"value":204},{"type":16,"tag":176,"props":321,"children":323},{"class":178,"line":322},17,[324],{"type":16,"tag":176,"props":325,"children":326},{},[327],{"type":21,"value":328},"src = Path(sys.argv[1])\n",{"type":16,"tag":176,"props":330,"children":332},{"class":178,"line":331},18,[333],{"type":16,"tag":176,"props":334,"children":335},{},[336],{"type":21,"value":337},"out = src.with_suffix(\".md\")\n",{"type":16,"tag":176,"props":339,"children":341},{"class":178,"line":340},19,[342],{"type":16,"tag":176,"props":343,"children":344},{},[345],{"type":21,"value":346},"lines = [f\"# {src.stem}\\n\"]\n",{"type":16,"tag":176,"props":348,"children":350},{"class":178,"line":349},20,[351],{"type":16,"tag":176,"props":352,"children":353},{},[354],{"type":21,"value":355},"lines += [f\"**{who}:** {text}\\n\" for who, text in turns(src.read_text())]\n",{"type":16,"tag":176,"props":357,"children":359},{"class":178,"line":358},21,[360],{"type":16,"tag":176,"props":361,"children":362},{},[363],{"type":21,"value":364},"out.write_text(\"\\n\".join(lines))\n",{"type":16,"tag":17,"props":366,"children":367},{},[368,370,375],{"type":21,"value":369},"An hour-long meeting goes from about nine thousand lines of VTT to roughly two hundred lines of markdown, with the same words. The ",{"type":16,"tag":66,"props":371,"children":373},{"className":372},[],[374],{"type":21,"value":163},{"type":21,"value":376}," stays next to it, unedited, because that is the source.",{"type":16,"tag":17,"props":378,"children":379},{},[380],{"type":16,"tag":43,"props":381,"children":384},{"alt":382,"src":383},"Lifecycle of one meeting: Teams records it, Power Automate copies the transcript into sources, OneDrive syncs it down, whoever added it runs an ingest, the wiki and log update, OneDrive syncs it back up, and a colleague queries it the next morning","/articles/engagement-wiki-lifecycle.svg",[],{"type":16,"tag":49,"props":386,"children":388},{"id":387},"the-schema",[389],{"type":21,"value":390},"The schema",{"type":16,"tag":17,"props":392,"children":393},{},[394,400],{"type":16,"tag":66,"props":395,"children":397},{"className":396},[],[398],{"type":21,"value":399},"CLAUDE.md",{"type":21,"value":401}," at the root is what turns a folder of files into a wiki. Claude Code reads it at the start of every session opened inside the folder, so every team member gets the same rules without being told them. Ours is about a hundred lines. The parts that do the most work are these:",{"type":16,"tag":86,"props":403,"children":407},{"code":404,"language":405,"meta":7,"className":406,"style":7},"## Operations\n\n- **ingest**: for each file in sources/ not yet listed in log/*.md, read it,\n  update or create the wiki pages it affects, add a line to index.md if a page\n  is new, and append one line per touched page to log/\u003Cyour-name>.md.\n  Never summarise a source into a single page. Spread it across the pages it\n  belongs to (a meeting usually touches decisions/, people/ and a workstream).\n- **query**: answer from wiki/ first. Every claim gets a citation in the form\n  (sources/meetings/2026-05-12-design-review/transcript.md). If the wiki does\n  not know, say so and name the source that would.\n- **lint**: find pages that contradict each other or a newer source, decisions\n  with no source, people pages not touched in 30 days, and open questions that\n  a later meeting answered. Write the findings to inbox/lint.md as checkboxes.\n\n## Rules\n\n- sources/ is read-only. Do not edit, rename or move anything in it.\n- A decision page must name the meeting it came from and who made the call.\n- Do not create a page for a person from a single mention. Two sources or a\n  human confirmation.\n- Anything you are not sure belongs in the wiki goes in inbox/, not in wiki/.\n","markdown","language-markdown shiki shiki-themes github-dark",[408],{"type":16,"tag":66,"props":409,"children":410},{"__ignoreMap":7},[411,420,427,448,456,475,483,491,508,516,524,541,549,557,564,572,579,591,603,615,623],{"type":16,"tag":176,"props":412,"children":413},{"class":178,"line":179},[414],{"type":16,"tag":176,"props":415,"children":417},{"style":416},"--shiki-default:#79B8FF;--shiki-default-font-weight:bold",[418],{"type":21,"value":419},"## Operations\n",{"type":16,"tag":176,"props":421,"children":422},{"class":178,"line":188},[423],{"type":16,"tag":176,"props":424,"children":425},{"emptyLinePlaceholder":201},[426],{"type":21,"value":204},{"type":16,"tag":176,"props":428,"children":429},{"class":178,"line":197},[430,436,442],{"type":16,"tag":176,"props":431,"children":433},{"style":432},"--shiki-default:#FFAB70",[434],{"type":21,"value":435},"-",{"type":16,"tag":176,"props":437,"children":439},{"style":438},"--shiki-default:#E1E4E8;--shiki-default-font-weight:bold",[440],{"type":21,"value":441}," **ingest**",{"type":16,"tag":176,"props":443,"children":445},{"style":444},"--shiki-default:#E1E4E8",[446],{"type":21,"value":447},": for each file in sources/ not yet listed in log/*.md, read it,\n",{"type":16,"tag":176,"props":449,"children":450},{"class":178,"line":207},[451],{"type":16,"tag":176,"props":452,"children":453},{"style":444},[454],{"type":21,"value":455},"  update or create the wiki pages it affects, add a line to index.md if a page\n",{"type":16,"tag":176,"props":457,"children":458},{"class":178,"line":216},[459,464,470],{"type":16,"tag":176,"props":460,"children":461},{"style":444},[462],{"type":21,"value":463},"  is new, and append one line per touched page to log/\u003C",{"type":16,"tag":176,"props":465,"children":467},{"style":466},"--shiki-default:#85E89D",[468],{"type":21,"value":469},"your-name",{"type":16,"tag":176,"props":471,"children":472},{"style":444},[473],{"type":21,"value":474},">.md.\n",{"type":16,"tag":176,"props":476,"children":477},{"class":178,"line":224},[478],{"type":16,"tag":176,"props":479,"children":480},{"style":444},[481],{"type":21,"value":482},"  Never summarise a source into a single page. Spread it across the pages it\n",{"type":16,"tag":176,"props":484,"children":485},{"class":178,"line":233},[486],{"type":16,"tag":176,"props":487,"children":488},{"style":444},[489],{"type":21,"value":490},"  belongs to (a meeting usually touches decisions/, people/ and a workstream).\n",{"type":16,"tag":176,"props":492,"children":493},{"class":178,"line":242},[494,498,503],{"type":16,"tag":176,"props":495,"children":496},{"style":432},[497],{"type":21,"value":435},{"type":16,"tag":176,"props":499,"children":500},{"style":438},[501],{"type":21,"value":502}," **query**",{"type":16,"tag":176,"props":504,"children":505},{"style":444},[506],{"type":21,"value":507},": answer from wiki/ first. Every claim gets a citation in the form\n",{"type":16,"tag":176,"props":509,"children":510},{"class":178,"line":251},[511],{"type":16,"tag":176,"props":512,"children":513},{"style":444},[514],{"type":21,"value":515},"  (sources/meetings/2026-05-12-design-review/transcript.md). If the wiki does\n",{"type":16,"tag":176,"props":517,"children":518},{"class":178,"line":260},[519],{"type":16,"tag":176,"props":520,"children":521},{"style":444},[522],{"type":21,"value":523},"  not know, say so and name the source that would.\n",{"type":16,"tag":176,"props":525,"children":526},{"class":178,"line":269},[527,531,536],{"type":16,"tag":176,"props":528,"children":529},{"style":432},[530],{"type":21,"value":435},{"type":16,"tag":176,"props":532,"children":533},{"style":438},[534],{"type":21,"value":535}," **lint**",{"type":16,"tag":176,"props":537,"children":538},{"style":444},[539],{"type":21,"value":540},": find pages that contradict each other or a newer source, decisions\n",{"type":16,"tag":176,"props":542,"children":543},{"class":178,"line":278},[544],{"type":16,"tag":176,"props":545,"children":546},{"style":444},[547],{"type":21,"value":548},"  with no source, people pages not touched in 30 days, and open questions that\n",{"type":16,"tag":176,"props":550,"children":551},{"class":178,"line":287},[552],{"type":16,"tag":176,"props":553,"children":554},{"style":444},[555],{"type":21,"value":556},"  a later meeting answered. Write the findings to inbox/lint.md as checkboxes.\n",{"type":16,"tag":176,"props":558,"children":559},{"class":178,"line":296},[560],{"type":16,"tag":176,"props":561,"children":562},{"emptyLinePlaceholder":201},[563],{"type":21,"value":204},{"type":16,"tag":176,"props":565,"children":566},{"class":178,"line":305},[567],{"type":16,"tag":176,"props":568,"children":569},{"style":416},[570],{"type":21,"value":571},"## Rules\n",{"type":16,"tag":176,"props":573,"children":574},{"class":178,"line":314},[575],{"type":16,"tag":176,"props":576,"children":577},{"emptyLinePlaceholder":201},[578],{"type":21,"value":204},{"type":16,"tag":176,"props":580,"children":581},{"class":178,"line":322},[582,586],{"type":16,"tag":176,"props":583,"children":584},{"style":432},[585],{"type":21,"value":435},{"type":16,"tag":176,"props":587,"children":588},{"style":444},[589],{"type":21,"value":590}," sources/ is read-only. Do not edit, rename or move anything in it.\n",{"type":16,"tag":176,"props":592,"children":593},{"class":178,"line":331},[594,598],{"type":16,"tag":176,"props":595,"children":596},{"style":432},[597],{"type":21,"value":435},{"type":16,"tag":176,"props":599,"children":600},{"style":444},[601],{"type":21,"value":602}," A decision page must name the meeting it came from and who made the call.\n",{"type":16,"tag":176,"props":604,"children":605},{"class":178,"line":340},[606,610],{"type":16,"tag":176,"props":607,"children":608},{"style":432},[609],{"type":21,"value":435},{"type":16,"tag":176,"props":611,"children":612},{"style":444},[613],{"type":21,"value":614}," Do not create a page for a person from a single mention. Two sources or a\n",{"type":16,"tag":176,"props":616,"children":617},{"class":178,"line":349},[618],{"type":16,"tag":176,"props":619,"children":620},{"style":444},[621],{"type":21,"value":622},"  human confirmation.\n",{"type":16,"tag":176,"props":624,"children":625},{"class":178,"line":358},[626,630],{"type":16,"tag":176,"props":627,"children":628},{"style":432},[629],{"type":21,"value":435},{"type":16,"tag":176,"props":631,"children":632},{"style":444},[633],{"type":21,"value":634}," Anything you are not sure belongs in the wiki goes in inbox/, not in wiki/.\n",{"type":16,"tag":17,"props":636,"children":637},{},[638],{"type":21,"value":639},"The two operations we use most are ingest and query. Whoever adds a source runs the ingest, in their own session, the same day. Everyone runs queries. A new joiner's first afternoon is now \"open a session in the folder and ask it why we chose event sourcing for the orders domain\", and the answer comes back with the date of the meeting and the name of the architect who pushed for it.",{"type":16,"tag":49,"props":641,"children":643},{"id":642},"what-onedrive-does-to-you",[644],{"type":21,"value":645},"What OneDrive does to you",{"type":16,"tag":17,"props":647,"children":648},{},[649],{"type":21,"value":650},"This is the part I wish someone had written down.",{"type":16,"tag":17,"props":652,"children":653},{},[654,660,662,667],{"type":16,"tag":655,"props":656,"children":657},"strong",{},[658],{"type":21,"value":659},"Files On-Demand.",{"type":21,"value":661}," By default OneDrive on macOS keeps cloud-only placeholders and downloads a file when something opens it. Claude Code's Read tool triggers a download, but a Grep across ",{"type":16,"tag":66,"props":663,"children":665},{"className":664},[],[666],{"type":21,"value":104},{"type":21,"value":668}," will silently search only what has been hydrated. Right-click the engagement folder, choose \"Always Keep on This Device\", and check Finder shows the solid tick on every file before you trust a search. We lost half a day to a transcript that \"did not exist\" because it had never been downloaded.",{"type":16,"tag":17,"props":670,"children":671},{},[672,677,679,685,687,693],{"type":16,"tag":655,"props":673,"children":674},{},[675],{"type":21,"value":676},"Conflicts.",{"type":21,"value":678}," OneDrive does not merge. If two people edit ",{"type":16,"tag":66,"props":680,"children":682},{"className":681},[],[683],{"type":21,"value":684},"index.md",{"type":21,"value":686}," within the same sync window, the second one gets a copy named ",{"type":16,"tag":66,"props":688,"children":690},{"className":689},[],[691],{"type":21,"value":692},"index-Marco's MacBook Pro.md",{"type":21,"value":694}," and no warning worth the name. Three rules made this a non-problem. Many small pages rather than a few big ones, so two people rarely touch the same file. One log file per person, never a shared one. And ingest is run by the person who added the source, so two people are never ingesting the same meeting at the same time.",{"type":16,"tag":17,"props":696,"children":697},{},[698,703],{"type":16,"tag":655,"props":699,"children":700},{},[701],{"type":21,"value":702},"Latency.",{"type":21,"value":704}," A change takes anywhere from five seconds to a couple of minutes to appear on a colleague's machine, depending on what the client's network is doing. The lint operation catches the case where someone queried a page that was about to change. It is not real-time and it does not need to be.",{"type":16,"tag":17,"props":706,"children":707},{},[708,713,715,721],{"type":16,"tag":655,"props":709,"children":710},{},[711],{"type":21,"value":712},"Paths with spaces.",{"type":21,"value":714}," The synced root will be something like ",{"type":16,"tag":66,"props":716,"children":718},{"className":717},[],[719],{"type":21,"value":720},"OneDrive-AcmeCorp/Engagement - Acme Data Platform/",{"type":21,"value":722},". Every script quotes its paths. Every script.",{"type":16,"tag":49,"props":724,"children":726},{"id":725},"what-the-model-is-not-allowed-to-see",[727],{"type":21,"value":728},"What the model is not allowed to see",{"type":16,"tag":17,"props":730,"children":731},{},[732],{"type":21,"value":733},"The client's data stays in the client's tenant, which is the whole point of using their SharePoint. But the folder is on your laptop, and the agent reads what is on your laptop, so the deny list still matters. Ours keeps commercial documents out of the model entirely:",{"type":16,"tag":86,"props":735,"children":739},{"code":736,"language":737,"meta":7,"className":738,"style":7},"{\n  \"permissions\": {\n    \"deny\": [\n      \"Read(./sources/contract/rates/**)\",\n      \"Read(./sources/client-docs/hr/**)\"\n    ]\n  },\n  \"sandbox\": {\n    \"enabled\": true,\n    \"filesystem\": {\n      \"denyRead\": [\n        \"/Users/marco/Library/CloudStorage/OneDrive-AcmeCorp/Engagement - Acme Data Platform/sources/contract/rates/\",\n        \"/Users/marco/Library/CloudStorage/OneDrive-AcmeCorp/Engagement - Acme Data Platform/sources/client-docs/hr/\"\n      ]\n    }\n  }\n}\n","json","language-json shiki shiki-themes github-dark",[740],{"type":16,"tag":66,"props":741,"children":742},{"__ignoreMap":7},[743,751,765,778,792,800,808,816,828,850,862,874,886,894,902,910,918],{"type":16,"tag":176,"props":744,"children":745},{"class":178,"line":179},[746],{"type":16,"tag":176,"props":747,"children":748},{"style":444},[749],{"type":21,"value":750},"{\n",{"type":16,"tag":176,"props":752,"children":753},{"class":178,"line":188},[754,760],{"type":16,"tag":176,"props":755,"children":757},{"style":756},"--shiki-default:#79B8FF",[758],{"type":21,"value":759},"  \"permissions\"",{"type":16,"tag":176,"props":761,"children":762},{"style":444},[763],{"type":21,"value":764},": {\n",{"type":16,"tag":176,"props":766,"children":767},{"class":178,"line":197},[768,773],{"type":16,"tag":176,"props":769,"children":770},{"style":756},[771],{"type":21,"value":772},"    \"deny\"",{"type":16,"tag":176,"props":774,"children":775},{"style":444},[776],{"type":21,"value":777},": [\n",{"type":16,"tag":176,"props":779,"children":780},{"class":178,"line":207},[781,787],{"type":16,"tag":176,"props":782,"children":784},{"style":783},"--shiki-default:#9ECBFF",[785],{"type":21,"value":786},"      \"Read(./sources/contract/rates/**)\"",{"type":16,"tag":176,"props":788,"children":789},{"style":444},[790],{"type":21,"value":791},",\n",{"type":16,"tag":176,"props":793,"children":794},{"class":178,"line":216},[795],{"type":16,"tag":176,"props":796,"children":797},{"style":783},[798],{"type":21,"value":799},"      \"Read(./sources/client-docs/hr/**)\"\n",{"type":16,"tag":176,"props":801,"children":802},{"class":178,"line":224},[803],{"type":16,"tag":176,"props":804,"children":805},{"style":444},[806],{"type":21,"value":807},"    ]\n",{"type":16,"tag":176,"props":809,"children":810},{"class":178,"line":233},[811],{"type":16,"tag":176,"props":812,"children":813},{"style":444},[814],{"type":21,"value":815},"  },\n",{"type":16,"tag":176,"props":817,"children":818},{"class":178,"line":242},[819,824],{"type":16,"tag":176,"props":820,"children":821},{"style":756},[822],{"type":21,"value":823},"  \"sandbox\"",{"type":16,"tag":176,"props":825,"children":826},{"style":444},[827],{"type":21,"value":764},{"type":16,"tag":176,"props":829,"children":830},{"class":178,"line":251},[831,836,841,846],{"type":16,"tag":176,"props":832,"children":833},{"style":756},[834],{"type":21,"value":835},"    \"enabled\"",{"type":16,"tag":176,"props":837,"children":838},{"style":444},[839],{"type":21,"value":840},": ",{"type":16,"tag":176,"props":842,"children":843},{"style":756},[844],{"type":21,"value":845},"true",{"type":16,"tag":176,"props":847,"children":848},{"style":444},[849],{"type":21,"value":791},{"type":16,"tag":176,"props":851,"children":852},{"class":178,"line":260},[853,858],{"type":16,"tag":176,"props":854,"children":855},{"style":756},[856],{"type":21,"value":857},"    \"filesystem\"",{"type":16,"tag":176,"props":859,"children":860},{"style":444},[861],{"type":21,"value":764},{"type":16,"tag":176,"props":863,"children":864},{"class":178,"line":269},[865,870],{"type":16,"tag":176,"props":866,"children":867},{"style":756},[868],{"type":21,"value":869},"      \"denyRead\"",{"type":16,"tag":176,"props":871,"children":872},{"style":444},[873],{"type":21,"value":777},{"type":16,"tag":176,"props":875,"children":876},{"class":178,"line":278},[877,882],{"type":16,"tag":176,"props":878,"children":879},{"style":783},[880],{"type":21,"value":881},"        \"/Users/marco/Library/CloudStorage/OneDrive-AcmeCorp/Engagement - Acme Data Platform/sources/contract/rates/\"",{"type":16,"tag":176,"props":883,"children":884},{"style":444},[885],{"type":21,"value":791},{"type":16,"tag":176,"props":887,"children":888},{"class":178,"line":287},[889],{"type":16,"tag":176,"props":890,"children":891},{"style":783},[892],{"type":21,"value":893},"        \"/Users/marco/Library/CloudStorage/OneDrive-AcmeCorp/Engagement - Acme Data Platform/sources/client-docs/hr/\"\n",{"type":16,"tag":176,"props":895,"children":896},{"class":178,"line":296},[897],{"type":16,"tag":176,"props":898,"children":899},{"style":444},[900],{"type":21,"value":901},"      ]\n",{"type":16,"tag":176,"props":903,"children":904},{"class":178,"line":305},[905],{"type":16,"tag":176,"props":906,"children":907},{"style":444},[908],{"type":21,"value":909},"    }\n",{"type":16,"tag":176,"props":911,"children":912},{"class":178,"line":314},[913],{"type":16,"tag":176,"props":914,"children":915},{"style":444},[916],{"type":21,"value":917},"  }\n",{"type":16,"tag":176,"props":919,"children":920},{"class":178,"line":322},[921],{"type":16,"tag":176,"props":922,"children":923},{"style":444},[924],{"type":21,"value":925},"}\n",{"type":16,"tag":17,"props":927,"children":928},{},[929,931,937,939,945,947,953,955,961,963,969],{"type":21,"value":930},"That ",{"type":16,"tag":66,"props":932,"children":934},{"className":933},[],[935],{"type":21,"value":936},".claude/settings.json",{"type":21,"value":938}," lives in the folder and syncs with it, so the deny list is the same on every laptop. The sandbox ",{"type":16,"tag":66,"props":940,"children":942},{"className":941},[],[943],{"type":21,"value":944},"denyRead",{"type":21,"value":946}," block is there because ",{"type":16,"tag":66,"props":948,"children":950},{"className":949},[],[951],{"type":21,"value":952},"permissions.deny",{"type":21,"value":954}," only stops the file tools, not a shell ",{"type":16,"tag":66,"props":956,"children":958},{"className":957},[],[959],{"type":21,"value":960},"cat",{"type":21,"value":962},". I have written about that distinction ",{"type":16,"tag":29,"props":964,"children":966},{"href":965},"/articles/how-i-let-ai-run-my-life",[967],{"type":21,"value":968},"before",{"type":21,"value":970}," and it applies twice as hard when the folder is not yours.",{"type":16,"tag":17,"props":972,"children":973},{},[974],{"type":21,"value":975},"Two things you should also check with the client before starting. Which model provider the tenant has approved, and whether the SharePoint library carries a sensitivity label that forbids exactly this. Both are five-minute conversations if you have them in week one and month-long ones if you have them in week ten.",{"type":16,"tag":49,"props":977,"children":979},{"id":978},"what-changed",[980],{"type":21,"value":981},"What changed",{"type":16,"tag":17,"props":983,"children":984},{},[985],{"type":21,"value":986},"The measurable thing is onboarding. The last joiner was productive on day two rather than in week two. The less measurable thing is that arguments about what was agreed now end in about a minute, because someone asks the wiki and it cites a transcript.",{"type":16,"tag":17,"props":988,"children":989},{},[990,992,998],{"type":21,"value":991},"The thing I did not expect is that the wiki caught the client contradicting themselves. The lint flagged that a data residency requirement stated in a March workshop was missing from a May architecture review, and it was right. That is a page in ",{"type":16,"tag":66,"props":993,"children":995},{"className":994},[],[996],{"type":21,"value":997},"decisions/",{"type":21,"value":999}," now, with both sources cited, and it saved a rework we would have found in testing.",{"type":16,"tag":17,"props":1001,"children":1002},{},[1003,1005,1010,1012,1017],{"type":21,"value":1004},"If you are going to build one, start with the meetings. Get the Power Automate flow running in the first week, before there is a backlog. Write ",{"type":16,"tag":66,"props":1006,"children":1008},{"className":1007},[],[1009],{"type":21,"value":399},{"type":21,"value":1011}," before the first ingest, and make the ",{"type":16,"tag":66,"props":1013,"children":1015},{"className":1014},[],[1016],{"type":21,"value":104},{"type":21,"value":1018}," rule the first line. Then open a session in the folder and let it do the filing.",{"type":16,"tag":1020,"props":1021,"children":1022},"style",{},[1023],{"type":21,"value":1024},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":188,"depth":188,"links":1026},[1027,1028,1029,1030,1031,1032,1033],{"id":51,"depth":188,"text":54},{"id":76,"depth":188,"text":79},{"id":109,"depth":188,"text":112},{"id":387,"depth":188,"text":390},{"id":642,"depth":188,"text":645},{"id":725,"depth":188,"text":728},{"id":978,"depth":188,"text":981},"content:articles:the-whole-engagement-in-one-folder.md","content","articles/the-whole-engagement-in-one-folder.md","articles/the-whole-engagement-in-one-folder","md",1789325964930]