Skip to content
andrew.dunn.dev

A Progressive Vendor Stance for the Agent-Native Future

In September 2024 I emailed Famly’s support asking whether they had a parent-side data export feature on their roadmap. The response was honest. They took the feature request and said they would track it. They could not commit to a timeline. In the meantime, they pointed me at their API: open the browser’s developer tools, watch the network tab while you use the web app, and construct equivalent queries against the GraphQL endpoint.

That response is actually fair. Famly is a small SaaS competing in a hard market. Like every commercial vendor, they have to prioritize features that drive revenue, and a parent-side photo export is a maintenance feature: it serves the existing user base but does not produce new contracts with nurseries. I would have made the same call in their position. The interesting question is not “why didn’t they ship it.” It is “what did they do instead.”

What they did, and what this post is really about, is put themselves in an architecturally accessible position. Their web client speaks to an inspectable GraphQL surface. Their GraphQL endpoint exposes introspection. Their support engaged with the use case rather than deflecting it. The path “if you really need this, here is how to build it yourself” was a real path, with shape, that someone could actually follow. Most vendors that decline to ship a feature also decline to leave the surface usable. Famly did the opposite.

I sat with that for twenty months. The reason I sat is that exploring a private GraphQL surface by hand is daunting. The web client issues hundreds of queries across screens. Mapping which queries my use case needs, deriving the type system for the response shapes, and writing a typed client against an undocumented schema is a non-trivial amount of careful, tedious work. I had a small budget for it, and the work kept losing the prioritization fight against everything else my family or my homelab needed.

What brought me back, and what bairn became, is the rest of this post.

This post is two arguments at once. The small one: building against a vendor that engages with the use case is a more honest posture than either pretending the practice does not exist or pretending it is fully legal. The bigger one: the stance Famly chose (decline gracefully, stay architecturally inspectable, let users solve it themselves with the help they have) is the right stance for a world where users will increasingly act through agents. The integration shipped because of that stance, not despite it.

bairn lives herehostileTOSforbidsSUPPORTdeflects, threatensOUTCOMEcease-and-desistEXAMPLESenterprise SaaS,market data feedssilentTOSforbidsSUPPORTdoes not engageOUTCOMEtolerated driftEXAMPLESmost consumer apps,aging open-source portsengagedTOSrestrictsSUPPORTacknowledges, guidesOUTCOMEuser-led integrationEXAMPLESour experience withFamly support

A vendor’s posture is set before any integration exists. Hostile and silent both end at a fence; engaged is the column where a user-built integration can ship and say so out loud. Famly sits in the third column, which is why bairn exists.

The problem

Famly is a childcare-management SaaS used by nurseries and preschools. Educators post photos of the day to a feed, tag them by child, and parents see the feed in the official Famly app. Years of nursery photographs accumulate on the parent’s account. There is no parent-side “download my child’s photos” feature. When the kid moves on, those photos remain on the vendor’s server until the parent extracts them.

What “extraction” looks like today, without a tool, is right-clicking each photo to save it, one by one. The downloaded JPEGs arrive without their EXIF metadata; the platform strips it on serve. The educator’s name, the kids tagged in the photo, the post body that gave the moment context, the timestamp the photo was taken, none of that survives onto the file. A parent who wants their kid’s nursery years archived gets a directory of stripped JPEGs and the task of remembering, by hand, what each one was about. Most parents do not extract at all, and the years quietly accumulate on the vendor’s server until the kid graduates.

Coming back

What brought me back to this in early 2026 was a conversation at the school with another parent. They had decided to do their own export by hand: manually tagging every photo of their kid in the Famly app, on the hope that some future feature might one day let them retrieve all photos under a tag in one shot. They were investing real labor into a workaround, indebting themselves to a feature that might never ship. I had not even known parents could tag their own kids in the platform, adding more metadata themselves. It had been almost two years since I emailed support for the feature, and my wife and I were still saving photos by hand. It was time to look at this again.

What was different in early 2026, beyond the conversation, was the AI harness. I was curious how far the leverage and techniques in the harness could get me into a problem I had previously set aside. Almost immediately the agent surfaced jacobbunk/famly-fetch, a Python port someone had quietly maintained for five years without enforcement action. That was a reference for what people in our shape had done before, and the comfort I needed before going further.

What we considered

Before bairn became code, we sat with whether to build it at all. The prior art was a reference, not a permission. We read Famly’s Terms of Use carefully. Sections 9.1(o) and 9.1(t) restrict non-official-client integration when read strictly. We tried to think about it from Famly’s side as much as our own.

Where would a third-party project plausibly step on commercial interests Famly has reason to defend? A multi-tenant SaaS that mirrored photos for many households would compete with their own API access add-on. A shared-credential service would create a redistribution channel for other people’s children’s photos. A project at scale would change the abuse-detection picture for them in ways they have legitimate reason not to want.

What we wanted to build was none of those. A single household using its own credentials, fetching at the rate a single human user generates, archiving photos to its own machine, not redistributing them. That sits within the spirit of “your own data on your own machine” and does not compete with Famly’s product. We felt we could build something that respected the platform, was not anti-commercial to their interests, and solved a real problem we had.

NARROWING TO A SHAPE WE COULD SHIPAny integration the surface allowsthe client is readable, anyone could build oneOne household, not a servicedrops the multi-tenant mirrorPAID API ADD-ONIts own credentialsdrops shared-login servicesOTHERS’ PHOTOSHuman rate, to its own diskdrops anything at scale · bairn ships hereABUSE SIGNAL

Each constraint bairn accepts removes a shape that would step on something Famly has reason to defend. What survives the narrowing is the household case, which was the only one we wanted to build.

The email exchange from two years earlier suggested Famly had already thought about parents in our shape. That made the next step feel like one we could take honestly.

The agent unlock

The gap between “the surface is right there” and “I have a typed client against the surface” is the work. For a private GraphQL API with hundreds of queries across screens, that work is not algorithmic; it is judgment. Which queries cover the use case. What the response types actually look like. Where the cursors live. Which mutations matter (and which to never call by accident). All of it requires reading and remembering a moving structure no one has documented for you.

The harness collapsed that work into a single night. The agent could:

  • Read a HAR file and emit a normalized list of unique (method, path, body-shape) tuples I had not yet mapped.
  • Take a single observed query, write a typed operation file against the introspected schema, and verify the response against the official client’s response.
  • Hold the typestate model in working memory while I described what each phase needed to know, and emit the Go types alongside the prose.
  • Generalize the discovery toolkit (shape probe, capture playbook, introspection) into a vendor-agnostic methodology rather than vendor-specific scripts.

The first-principles design for bairn (typed Go client, typestate asset lifecycle, in-file metadata that puts every piece of context back onto the JPEG, optional Immich upload, gitignored discovery artifacts) was one night of work. Twenty months of stalled effort, one night to ship the design. That is the agent-native multiplier. The methodology was always available. What changed is the integrator’s cost of using it.

This is why the vendor stance matters more every year. By keeping the surface inspectable and telling users how to inspect it, Famly let an integration ship that they were not in a position to prioritize themselves. That stance, paired with the agent-native multiplier, was enough.

The methodology

Once you accept that the methodology is “watch the official client and replicate,” it generalizes nicely. Any JSON-over-HTTP API a vendor exposes to its own client can be approached this way. I formalized three modes in discovery/PROTOCOL.md in the bairn repo. None of them are novel; they are just named.

Mode 1, shape probe. Given a manifest of known endpoints, hit each one and record a JSON-key-only signature. Diff signatures across runs to detect drift in the response shape. The output is type structure, never values. A /me-style endpoint becomes:

{
  "email": "str",
  "loginId": "str",
  "name": {"firstName": "str", "lastName": "str"},
  "roles": [{"id": "str", "permissions": ["str", "<n=12>"]}, "<n=3>"]
}

That signature can sit in a CI baseline. When the vendor changes a field, the diff fires. The probe never records actual data, so the baseline is safe to keep around even if the operator’s account has private content. Cheap, repeatable, runnable on a schedule.

Mode 2, traffic capture. This is the discovery mode. You drive the official web client through every screen a user can reach, capture the network traffic, and extract the unique tuples of (method, host, pathname). The new entries that are not in your manifest are candidates for inclusion. The output is HAR files captured from the browser, which contain auth headers and full payloads, so they live on the operator’s machine and never get committed.

This mode is the one Famly’s support pointed at. It is also the mode you cannot really automate without a human deciding which screens are worth visiting. The browser is the integration tool, Playwright or its equivalent makes it scriptable, and a small jq filter against the HAR makes the diff trivial. Most of the work is judgment about coverage.

Mode 3, schema introspection. When the vendor’s API is GraphQL with introspection enabled, you can dump the entire schema in a single POST. Most production GraphQL servers turn introspection off in production, so this mode is hit-or-miss. When it works, it collapses the manifest-maintenance work into a codegen pipeline. You generate types and a typed client from the schema, write your operation files against the schema, and the vendor’s own type system becomes your spec.

In bairn’s case, mode 3 worked. Famly’s parent-side GraphQL endpoint exposes introspection. I dumped the schema, generated a client with genqlient, and wrote operation files for the queries the official client also issues. The mode-2 walk became a verification step rather than the only path to the surface.

POST __schema → JSONdrive client → HARhit → record signatureVENDORvendor web clientthe source of truth for the API surfaceMODE 3schema introspectioncollapses 1+2 if availableMODE 2traffic capturediscovers new endpointsMODE 1shape proberuns on a scheduleOPERATOR-ONLYschema dumpcodegen inputOPERATOR-ONLYendpoint manifestdrift seedsCOMMITTABLECI baselinedrift detectorWHAT YOU SHIPyour typed clientcodegen + verification walk + drift baseline

The three modes read one source, the vendor’s own web client, and each leaves its own artifact: mode 2 finds what mode 1 then watches, and mode 3 collapses both where introspection is left on. Famly left it on. The three artifacts together are what the typed client is generated from and kept honest by, and only the signature baseline is safe to commit.

What that codegen path looks like in practice. A query the official client issues becomes the inspiration for an operation file. The schema dump types the fields. genqlient reads both and emits a typed Go function. The vendor’s own type system is the boundary the resulting code is compiled against.

inspirestypesgenqlientobserved queryPOST /graphqlquery getFeed($cursor) {feed(after: $cursor) {items {…}schema dumptype FeedItem {id: ID!body: String…operation fileapi/famly/operations.graphqlquery GetFeed($cursor: String){feed(after: $cursor) {items { id body … }}generated Goapi/famly/gen.gofunc GetFeed(ctx,client, cursor)(*GetFeedResp,error)

Only the operation file is written by hand. The observed query says which fields to ask for, the introspected schema says what they are, and genqlient emits the Go. The vendor’s own type system becomes the boundary the code is compiled against, so a breaking upstream change fails the build instead of production.

Why this matters

The piece I keep returning to is how the calculation shifts as more of a vendor’s user base reaches them through agents. A user clicking buttons in the official client and a user who has delegated those clicks to an agent issue the same traffic against the same surface. What changes is whether the vendor has structured itself to engage with that population or to ignore it.

Famly chose to engage. Not by promising every feature (a small SaaS chasing the revenue features that keep the company alive cannot honestly do that), but by staying in an architecturally accessible position: a web client that speaks to an inspectable surface, an introspection-on GraphQL endpoint, a support team that responds to the use case rather than deflecting. None of those decisions costs the vendor much. All of them retain users who would otherwise be stuck.

I am curious how generalizable this is. Is Famly an outlier, or is “engaged” becoming the median posture for vendors whose user base will increasingly reach them through agents? Do those users actually reward vendors who structure for them, or is the reward too quiet to register on a roadmap call? I do not know. What I notice is that the vendors I would still want to be a customer of in five years all share something like this stance.

The shape on our side

The methodology is half the story. The integration’s posture on the integrator’s side is the other half.

A tasteful Famly client is not a multi-tenant SaaS. It is not a public API. It is not a CLI you point at someone else’s account. It is a binary you run on your own machine, with your own credentials, against your own household’s data, at the rate a single human user would naturally generate. It saves to your disk. It optionally uploads to your own self-hosted Immich. The state file lives under XDG_STATE_HOME and never travels. There is a file lock to prevent concurrent runs from corrupting state. There is a polite retry primitive that honors Retry-After and treats 4xx auth errors as permanent failures. There is no telemetry. There are no automatic updates that hit the vendor on a schedule the operator did not ask for.

OPERATOR’S MACHINEhuman-rate fetchvendor platformGraphQL endpointmedia CDN (signed URLs)bairnpaginate, fetch, embedsingle static binarydiskcanonical archive (EXIF + XMP)Immichoptional, self-hostedstate file$XDG_STATE_HOME/bairncredentialsenv or interactive promptCONTRACTsingle household • single account • single host • human-rate • operator’s own credentials • no redistribution

Everything the integration holds sits on the operator’s own machine: one binary, one account’s credentials, one disk, and an Immich upload only if the operator runs one. The single line leaving that boundary is a human-rate fetch, and staying that shape is the whole contract.

The constraints are not moral; they are operational. They keep the integration consistent with what the platform is designed to handle from a single user: one login, paginated reads at the cadence a parent’s session would naturally produce, well inside the rate envelope the platform was built around. The point is not to hide. The point is to stay shaped like the use case the vendor’s surface was built for, which is the shape we are.

This is not new. Email clients work this way against IMAP servers they do not own. RSS readers work this way against websites that do not advertise their feeds. Backup tools work this way against drives they did not format. The category is “small-scale personal automation against a service the user already has a contract with.” It exists. It always has. What is new is the vendor’s willingness to acknowledge that it exists and tell users how to do it cleanly.

What didn’t work

A few honest limits, both on the methodology and on bairn specifically.

Mode 3 is hit-or-miss. Most production GraphQL servers disable introspection. Famly leaves it on, which collapsed the manifest-maintenance work into a codegen pipeline. If your vendor disables it, the trump card is gone and you live in Mode 2.

Mode 2 is not fully automatable. A scripted Playwright walk can capture HARs, but the judgment call (“which screens cover what we need”) is human work. Coverage is the part of the methodology that still requires sitting down with the official client and clicking through it.

The contract is reputational, not legal. Famly’s TOS forbids non-official-client integration. Support guidance is not a contract amendment. The reason this works is mutual: vendor tolerance, integrator restraint. If either side moves, the other follows. An operator who treats the contract as a guideline rather than a fence creates a pattern the abuse-detection system was built for.

The toolkit is generic; the manifests are not. discovery/probe/*.py works against any JSON-over-HTTP surface. The endpoint manifests, schema dumps, and operation files are vendor-specific and operator-side. Adopting the methodology against a different vendor is a copy of discovery/probe/manifest.example.toml and a few hours of mode-2 walking.

bairn v0.1.0 is small on purpose. Disk-first save is the canonical path. Immich upload is alpha. Native Go drift detection is stubbed; the LLM-augmented CI fires on output that does not yet exist. The integrations that need bedding-in get bedded-in over a v0.2.0 cycle, against the same surface, with the same posture.

The agent-native multiplier is unevenly distributed. Operators without an AI harness still face the long stall I faced in 2024. The methodology document, the discovery toolkit, and bairn itself help; they do not erase the gap. The closer in time we get to “everyone has a working harness,” the more the multiplier matters. Today it still privileges the operators who have figured the harness piece out.

Generalizing

I don’t think Famly is uniquely friendly to integrators. The posture could change with a leadership turnover, a priority shift, a contract review. What I take from the experience is narrower. When a vendor stays architecturally accessible, the integrations that ship around them get to be honest about what they are. The work can be public, the methodology committed, the contract written down. None of that requires the vendor to do anything heroic. It requires them to not actively close off the surface.

A note about Famly itself. The transition our kids’ school made to the platform genuinely improved how educators engage with parents in our experience, and it lifted a real chunk of regulatory and compliance burden off the classrooms we know. Anything that takes load off educators and lets them concentrate on the kids in front of them is, in our view, awesome work. We like the product, and we hope this writeup does no harm. If anyone at Famly finds it, take it as the opposite: by staying in an architecturally accessible position, you implicitly allow integrations like bairn to exist alongside what you ship. That, we think, is the right call. The integration we built exists because of decisions you made.

The question I’d ask before building something like bairn against another vendor is whether support, in practice, engages with the use case or deflects it. I haven’t tested this anywhere else yet. I am curious which posture is more common, and how much the answer depends on whether the vendor’s user base already includes people building agents.


bairn is at gitlab.com/dunn.dev/bairn. The discovery methodology is in discovery/PROTOCOL.md. The posture is in NOTICE.md. The prior art that proved this kind of project survives is jacobbunk/famly-fetch.