How We Connected Basecamp to BlitzBase, Our AI Knowledge Base

BlitzBase is the AI knowledge base Dylan Haugen runs for Local Service Spotlight and BlitzMetrics client work: one folder of markdown where every client’s reconciled record lives, readable by humans in Obsidian and by Claude agents directly off disk. The one thing it couldn’t see was Basecamp, where the client communication actually happens. In a single evening session, a Claude agent connected them: it registered the OAuth app, wrote a dependency-free sync tool, mapped every active client project, pulled every Access and Updates thread into the repo as markdown, captured end-of-day check-in reports with their replies, and put the whole thing on a daily schedule. The test that proved it: asking “what did Dennis tell me to do in reply to my EOD yesterday?” went from a six-step browser hunt to a two-second local file read.

The agent that built the connection wrote this article from its own session log, the same way the other builds in this series were documented. The bugs and decisions below are the real ones.

Where This Fits in the Methodology

This is an agent-infrastructure build in the same series as the Cam Hazzard build and the internal link building workflow: each one documents a task an agent executed end to end so the next person, or the next agent, can replicate it. The format follows the meta-article prompt template. There is no parent definitive article on agent knowledge bases yet; this build is the argument for writing one.

The Integration Task

The goal, in Dylan’s words at kickoff:

“If the agents have context, then they can just do things a lot faster, and we want to have the raw Basecamp logs in there.”

Dylan Haugen, session kickoff

In scope: a Basecamp OAuth app, a sync tool that pulls each client project’s message-board threads (Access, Updates and its continuations, Meetings, Strategy Assessment, Project Overview) plus recent Campfire lines into each client’s folder, capture of automatic check-in answers (the end-of-day reports) with every reply, a live search mode for “find anything Dennis said about X this week,” a daily refresh schedule, and documentation so the whole thing is repeatable for a public distribution later. Out of scope: posting anything back to Basecamp. The connection reads; humans write.

Source Material Ingestion

Before touching Basecamp, the agent read the knowledge base’s own rules: the folder layout (a knowledge-base file at each client root, compiled/ for the reconciled wiki, raw/ for source material), the source-of-truth hierarchy that makes local files outrank live apps, and the existing operating rules for agents working in the repo. That dictated the design target: Basecamp content is raw input, so it lands in raw/basecamp/ per client, never in compiled/. The agent also read each client’s knowledge-base file to confirm which Basecamp project maps to which client folder, instead of guessing from project names.

Initial Build: OAuth App and a Dependency-Free Sync Tool

Basecamp has no simple API keys; everything goes through an OAuth app registered on the account. The agent drove the registration itself through the operator’s logged-in browser session, stopping only at the login screen (agents don’t enter passwords). Redirect URI: https://localhost:7652/callback, because the one-time authorization runs on the operator’s machine, catches the code on a local port, and exchanges it for tokens that auto-refresh from then on.

The sync tool itself is one Python file with zero dependencies outside the standard library, a deliberate constraint so it runs on a stock Mac with no installs. Four subcommands: auth (one-time browser authorization), projects (list and cache every project on the account), sync (pull mapped projects into the repo), and search (query recent activity across all projects by text, person, and day range). Credentials, tokens, and the project-to-client mapping live in a gitignored config folder, so nothing sensitive can ever reach the repo’s history.

Each synced thread becomes one markdown file: thread URL, timestamps, then every message and comment in order with author and date, converted from Basecamp’s rich-text HTML to readable markdown by a small parser that handles links, bold, lists, and image attachments. Each project also gets an INDEX.md listing every thread with its last-updated date, which is what an agent reads first to orient.

The EOD Loop: Check-In Answers With Replies

The highest-value content wasn’t in any message board. End-of-day reports live in Basecamp’s Automatic Check-ins, a different content type with its own API shape: a recurring question, answers per person per day, and comment replies on each answer. The replies are the payoff, because that’s where the feedback and assignments land. The sync’s check-in module pulls one person’s answers, filtered by creator so teammates’ answers stay out, bundles each answer with all of its replies, and writes monthly files into the personal folder.

The proof came the same night. Dennis replied to the previous day’s EOD, the sync captured it, and the comment that triggered the next piece of work was sitting in a local file:

“VERY NICE! Got a meta article showing what you (and your agents) have built?”

Dennis Yu, replying to Dylan’s EOD in Basecamp, as captured by the sync

The QA Marathon: Real Bugs and the Fixes

The EODs that weren’t where threads live

Symptom: the first live test (“check what Dennis said in reply to my EOD”) found nothing in any synced thread file. Cause: EODs aren’t message-board threads at all; they’re Automatic Check-in answers, a content type the message-board sync never touches, parked in a team project that wasn’t mapped. The agent had to find them the slow way once, through six browser navigation hops. Fix: a dedicated check-in module in the sync tool, driven by config (project, question, person, destination), so the slow hunt never happens again. The general lesson: a “sync everything” claim is only as true as the content types you’ve enumerated.

The archived threads that looked like a sync bug

Symptom: one client’s folder had two more thread files than its INDEX listed. Cause: not a bug in the new sync. The extra files were hand-pasted archives saved weeks earlier, and Basecamp’s API only returns active threads, so the sync neither overwrote nor indexed them. Fix: verified by file timestamps, then documented: archived Basecamp threads aren’t returned by the API, and legacy hand-saved files coexist untouched. What looked like corruption was the old workflow and the new one shaking hands.

The attachment captions that printed twice

Symptom: image attachments in synced threads rendered as a caption marker followed by stray whitespace and the same caption again. Cause: Basecamp wraps attachments in a custom bc-attachment element whose inner figcaption duplicates the caption attribute the converter had already emitted. Fix: a skip flag in the HTML parser that swallows everything inside the attachment element after emitting one clean marker, verified with a unit test on a captured fragment before re-syncing.

The sandbox that can’t reach Basecamp

Symptom: every direct API call from the agent’s sandboxed environment failed instantly. Cause: agent sandboxes allowlist their network destinations, and Basecamp’s API isn’t on the list. Fix: a division of labor that became the architecture. The agent writes and edits the tool through the shared filesystem; the tool executes on the operator’s Mac, where the network and the OAuth callback both live. Each execution is one paste into Terminal, and the agent reads the results off disk the moment they land. The same constraint produced the documentation rule that tells future agents to read the synced files instead of attempting live API calls from a sandbox.

The 7am schedule that never fired

Symptom: the morning after setup, the log file didn’t exist and every synced file still carried the previous night’s timestamps. The scheduled 7am job had done nothing, silently. Cause: two independent macOS behaviors, either one fatal. cron skips any job whose time passes while the machine is asleep, with no catch-up on wake; and macOS privacy protection blocks cron-launched processes from reading the Documents folder, so even on an awake machine the job would have died before writing its own log. Silent on both paths. Fix: replace cron with a user LaunchAgent. launchd treats a missed calendar interval as due and fires it when the Mac wakes, runs in the user’s session where the Documents permission can be granted once, and logs stdout and stderr to a file so the next failure has nowhere to hide.

<key>RunAtLoad</key><true/> <key>StartInterval</key><integer>14400</integer> <key>StartCalendarInterval</key> <dict> <key>Hour</key><integer>7</integer> <key>Minute</key><integer>0</integer> </dict> <key>StandardOutPath</key> <string>/Users/you/Library/Logs/basecamp-sync.log</string> # install (one paste, replaces any cron line): crontab -l | grep -v basecamp-sync | crontab - cp com.yourname.basecamp-sync.plist ~/Library/LaunchAgents/ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.yourname.basecamp-sync.plist

If macOS asks whether the tool can access files in Documents on the first run, allow it once. That prompt is the same protection that was killing cron without ever asking.

A second reliability lesson (July 2026). The LaunchAgent above is a real improvement over cron, but it is not bulletproof. If the Mac is asleep at the single scheduled time, launchd does not reliably fire the missed run on wake the way you would hope, and a later macOS update can quietly revoke the Full Disk Access the job needs. Either way the failure is silent: the sync just stops, and every session keeps reading a snapshot that is no longer current. The durable fix is defense in depth. Run the job on load and every few hours, not once a day, so one missed window cannot strand it. And because any scheduled job can fail quietly, add a freshness check you actually run: if the newest synced file is more than a day old, treat the data as stale, read the live source, and repair the schedule. Present is not the same as current.

The root cause, found later: the log path itself. The defense-in-depth advice above is right, but it did not explain why an apparently healthy LaunchAgent still produced nothing. The answer was the plist. If StandardOutPath points inside a privacy-protected folder (Documents, Desktop, Downloads), launchd cannot open that file to wire up the job’s output, so it aborts the entire job with EX_CONFIG (exit 78) before the script ever runs. And because the log could not be opened, it stays empty, so there is no error anywhere to find. Stale files plus exit 78 plus an empty log is the signature. Diagnose it with launchctl print gui/$(id -u)/<label> | grep -i ’last exit’, then move the log somewhere never protected, such as ~/Library/Logs/, which is what the snippet above now shows. Order matters between the two privacy traps: fixing the log path is what lets the job start and finally capture errors, and only then does a missing Full Disk Access grant show up as a readable “Operation not permitted.” You need both fixes, and the log path first.

And what the schedule actually delivers, once both fixes are in. Verified in production: the morning job fired on its own, exited clean, and wrote to the log outside the protected folder, which is the whole chain working rather than a theory about it. The honest expectation to set, though, is roughly one refresh a day, not one every few hours. A laptop that sleeps overnight and between sessions misses most short intervals, and launchd does not reliably replay every missed window on wake. The morning calendar anchor is the run you can count on; run-at-load covers logins and reloads. Data that is a few hours to a day old is fine as context. The failure mode to avoid is assuming an interval caught something when you need same-day information, so if the timing matters, run the sync by hand and check the log. Present is not the same as current, and neither is scheduled.

Critical Decisions With Rationale

Standard library only, no installs. The sync tool refuses every dependency: OAuth, HTTP, pagination, rate-limit handling, and HTML conversion are all stdlib Python. The repo is meant to be distributable later as a public starter kit, and every dependency is a support ticket waiting to happen on someone else’s machine. One file, one paste, it runs.

Raw means raw. Synced files land in each client’s raw/ layer and are rewritten wholesale on every run. No merging, no summarizing, no agent commentary in the files. The knowledge base’s compiled layer is where reconciliation happens, and keeping the sync dumb means it can never corrupt the part humans curate.

Filter check-ins by person, keep every reply. A team check-in contains everyone’s answers, but the operator’s archive only needs their own, with all replies attached. Filtering by creator at sync time keeps teammates’ reports out of a personal folder while preserving the one thing that matters most: what the boss said back.

A local schedule, but launchd, not cron. The refresh runs at 7am from the operator’s own machine: no cloud scheduler, no background service, no third subscription. The first implementation was a single crontab line, and it failed its very first morning; the full story is in the QA Marathon below. The shipped version is a macOS LaunchAgent, which writes a log the operator can check. A missed run costs nothing on its own, because every run rewrites everything anyway. Do not assume launchd reliably catches up a single missed daily run after sleep, though; it does not, which is why the shipped schedule also runs on load and on a few-hour interval, and why the log lives outside the protected folder. Both corrections are in the QA Marathon below.

Read-only by design. The integration never posts to Basecamp. Drafts that agents produce (weekly client reports, EOD drafts) stay drafts until a human pastes them. That boundary is what makes a fully synced communication archive safe to hand to agents.

Effort and Cost Comparison

Task Agent Time Human Time Agent Cost Human Cost ($50/hr blended)
OAuth app registration + auth flow 20 min 3 hrs $0.60 $150
Sync tool (threads, comments, Campfire) 1 hr 8 hrs $1.50 $400
Check-in (EOD) module with replies 30 min 4 hrs $0.80 $200
Project mapping + folder scaffolds 15 min 2 hrs $0.40 $100
Testing across four client projects 30 min 4 hrs $0.70 $200
Docs: README, agent rules, schedule 25 min 3 hrs $0.50 $150
TOTAL 3 hrs 24 hrs $4.50 $1,200

Pricing basis: the session consumed about 1.1 million tokens (1M input, 100K output), priced at Claude Sonnet public rates of $3 per million input and $15 per million output for comparability with the other builds in this series. Human cost uses a blended $50/hr. The ratio is roughly 265x. The human estimate covers a developer producing the same tool with tests and docs; the agent figure excludes the operator’s own minutes, which were a login, three Terminal pastes, and answers to a handful of decision questions.

What the Agent Handled vs What Needed a Human

Agent handled autonomously: the OAuth app registration through the browser, the entire sync tool, the HTML-to-markdown converter and its attachment fix, the project-to-client mapping verified against the knowledge base instead of guessed, the client folder scaffolds, the check-in module, the live search mode, the README, the agent-facing operating rules so every future session knows the Basecamp layer exists, and verification passes on every synced file.

Required human input: the Basecamp login (agents don’t touch passwords), three Terminal pastes (authorize, first sync, schedule), confirmation of which person was the fourth client, and the corrections that kept the work honest. The pattern from every build in this series held: agents execute, humans decide.

Information Ingestion Inventory

  • 1 OAuth app registered; 1 authorization flow caught on a local port
  • 120+ projects enumerated on the account; 4 active client projects mapped and synced
  • 24 thread files and 4 project indexes written on the first sync, every message and comment preserved verbatim
  • 3 months of end-of-day answers backfilled (26 answers) with every reply attached
  • 1 LaunchAgent keeping all of it fresh, installed after the cron attempt failed silently, later hardened twice for schedule and log-path failures (see the QA Marathon)
  • 495 lines of dependency-free Python; ~1.1 million tokens consumed
  • 5 named bugs caught and documented; 5 named decisions locked with rationale

Guidelines Compliance Scorecard

BlitzMetrics Guideline Status Notes
Hook names person/result/timeframe PASS System, builder, result, one evening
Answer in first paragraph PASS
Short paragraphs (3-5 lines) PASS
Active voice PASS
No AI fluff phrases PASS Checked against the banned list
Title under 65 characters PASS 57
H2/H3 structure PASS H3s only in the QA Marathon
Internal links to BlitzMetrics PASS 5 internal links, each URL once
Cross-link to sibling meta-articles PASS Cam Hazzard, internal link building
Parent definitive article linked FAIL None exists on agent knowledge bases; recommend writing one
Entity links follow decision tree PASS
Source video embedded N/A No video source
Featured image NEEDS HUMAN Suggest the Terminal sync output or an Obsidian graph screenshot
Real screenshots, no stock images NEEDS HUMAN Same as above
RankMath configured PASS Set in the draft payload
Author byline PASS Dylan Haugen
Categories and tags set PASS
Anchor text 3-6 words PASS
Evergreen framing PASS No dates in body
Specific CTA PASS Closing section

Why This Creates Specific Value for the Team

Every agent session that touches a client now starts already briefed: the full Access and Updates history is sitting next to the compiled wiki it feeds, current as of 7am. Questions that used to mean logging into Basecamp and scrolling (“what did the client say about access,” “what did Dennis assign me yesterday”) are now local file reads, which makes them available to every agent, every session, at zero marginal effort. The check-in capture quietly builds something else too: a permanent, searchable archive of daily work and the feedback on it, which is the raw material for weekly reports, reviews, and exactly the kind of article you’re reading.

Why This Creates Value for BlitzMetrics

The $4.50-versus-$1,200 ratio matters less than what the pattern proves: the moat for agent work isn’t the model, it’s the context layer. A team that pipes its communication exhaust into a local, agent-readable knowledge base gets compounding returns from every future session, and the integration that does it costs less than lunch. This build is also the template for the planned public version of the knowledge base starter kit, where “connect your Basecamp” becomes a documented setup step anyone can run with their own OAuth app.

The Build Pattern

The pattern this build instantiates: give agents the same source of truth the humans use, keep it local, keep the pipe read-only, and let a schedule do the remembering. The agent registered the access, built the pipe, tested it on real client history, and documented the rules for the next agent, all in one session, with the human supplying logins and judgment. Compare it against the Cam Hazzard build linked above to see the same execute-and-decide split applied to a website instead of an integration. If you want your client communication flowing into an agent-readable knowledge base like this, reach out through the Marketing Mechanic framework page and ask about agent infrastructure.

Want this for your own system? The Basecamp connection now ships inside BlitzBase, our distributable AI knowledge base — the Basecamp integration guide covers what your agent walks you through: app registration, project mapping, and a daily sync that survives sleep.

THE DELIVERABLE
See the system this article documents

BlitzBase, our AI knowledge base, now wired to Basecamp.

Explore BlitzBase →

Dylan Haugen
Dylan Haugen
Dylan Haugen is a professional dunker, content creator, and editor at the Content Factory, where he transforms podcasts and interviews into strategic brand assets. He collaborates with Dennis Yu to support young entrepreneurs and business owners in building their personal brands through education, transparency, and effective content marketing. As the host of the Dunk Talk podcast and a dedicated advocate for establishing dunking as a recognized sport, Dylan combines athletic expertise, storytelling, and digital strategy to help elevate the next generation of creators.