Meet SALLY
Author: Andrew Porter
Published: 10/11/2026
-
An agentic AI pipeline that reads your existing brand materials and drafts cross-site social media campaigns for a human to publish.
The Issue
Most businesses are quietly sitting on a pile of raw material - a camera roll full of photos, a handful of blog posts and product pages that explain what you actually do. And when the day-to-day of running a business gets in the way of dedicating staff to brand promotion, wouldn’t it be nice if there was a fresh campaign draft waiting for you every Monday morning?
Our Take
We all know the drill when there’s a hole in the content calendar - you open a blank doc, scroll the camera roll, find a great shot of the product, and think “I’ll write something around this later.” Confidence turns into a whole week and finally, on Friday afternoon, you do the modern thing: drag the photo, a blog post, and a product page into a chatbot with the prompt “make this go viral.” Thirty seconds later you have an entire social media campaign ready to hit the “Easy” button on. It’s enthusiastic. It’s proud. It… has a hashtag for a feature you never built, and a glowing quote from a corporate customer who also doesn’t exist. The cheerful voice of a company that isn’t yours, advertising a product you didn’t create - and now you have a new job: fact-checking every line of a draft that was supposed to save you time.
The raw campaign material was never the problem, and neither was the model. The gap is trust. You’d happily take a head start from a drafting partner that stays inside what you’ve actually told it, shows its work, and leaves the final call to a person - but that’s not what a chat box gives you. We wanted that Partner for our own day-to-day business development, so… we built one! Today we’re announcing SALLY (as in SociALLY). It’s Open Source, it’s on our GitHub, and it usually just does one non-deterministic job: take the photos and product material you already have and generate it into cross-site social media campaigns for a human to review and post.
What is SALLY?
SALLY is a bare-metal agentic AI Python harness that reads a set of brand photos and a list of product materials - blog posts, listings, even a plain list of URLs - and then drafts multi-track social media campaigns for a human on your team to review and post. Each generated campaign comes with a rationale, visual direction, a handful of post ideas that point at your actual photos, a specified cadence, and a plan for measuring awareness. Then it stops. SALLY never posts anything: there’s no publishing API, no scheduler, and no social account credentials anywhere in the project. The last step is, deliberately, a person. It’s also company-agnostic: everything about your brand lives in a few context files in agent/, so the harness never changes when the brand does.
Let’s also be honest about the word “agent.” The “agents” here are stage-specific prompts - each one gets a fixed set of instructions and its own slice of data. Don’t worry though, these are not autonomous crab-shaped actors wandering off to make decisions on your behalf - it’s a very slim harness, and it absolutely cannot rm -rf your files.
Unlike some of our other projects such as TIBIA or KARPAL, where the goal was “two environment variables and five minutes,” SALLY is a building block you’re expected to get your hands dirty with this time. Clone it, add your context, and then make it yours: your voice, your sources, your prompts. We call it a “transparent” harness because it’s all right there for you to see - we haven’t invented anything and there’s no magic behind the curtain. When you’re ready to fire up a new campaign draft it’s just one command, and you’re off to the races with whatever your brand has already been creating.
Why We Built It
Here’s a confession from an engineering team: we will happily spend a Saturday debugging a certificate rotation, but ask us to deliver a social media campaign and suddenly everybody is very busy with a very important set of code changes for the website’s loading animation. Nobody here has said the words “brand awareness” out loud without wincing and there’s a self-titled launch post that has been “almost ready” since spring of 2013 - we even have a random photo of a prototype that is objectively great… but has lived in a folder called final_FINAL_use_this_jpgs_social_media ever since.
So this is the “hard delivery” angle, with a new direction: it turns out the hardest delivery of all is hitting Publish on something with our name on it. And if we were going to keep dodging the work, the obvious engineering response was to build the thing that does most of it. Fortunately for us what we actually needed was a short list:
- Something that doesn’t invent features we never built. Our products do not, in fact, “revolutionize digital ecosystem synergy”
- Something that doesn’t have to start from scratch every time (see below)
- Something that survives an API having a bad day, without us babysitting it
- Something that is very clearly not allowed to post on our behalf - we’ve all seen what happens
We didn’t want to hand our marketing direction to a generic chatbot that was going to invent the same three campaigns for the same fake products every time we ran it. We wanted the robot to do the blank-page part - read the photos, read the sources, draft the angles - so a human only has to say yes, no, or “fewer exclamation marks.” It’s the same instinct behind CHaaS: agents are at their best when they do the work and then get out of your way.
One Stage at a Time
The easiest way to understand SALLY is as a few small, non-deterministic model calls buried inside a lot of deterministic plumbing - very much like most agentic workflows. Finding context, batching research, caching results, trimming context, retrying failed model calls, and writing to disk behave the same way across the entire harness. The model calls are the only parts that don’t - and the format of what passes between them is a deliberate choice about who (or what) reads it next.
Here’s the whole run, one stage at a time:
1. Catalog the photos. The image research agent reads photos from images/ and groups them into batches, skipping anything it has already analyzed. The agent model - a vision-capable model - then describes each photo: mood, composition, what’s actually in the frame. The result is image_inventory.json, and it’s JSON on purpose: the later stages treat it as structured data, with real filenames that external tooling can point to. That precision has a price, though - generally speaking JSON breaks pretty easily when using generative AI models. The Nemotron model in use is particularly good at this, which was one of the selection criteria for that model (beyond cost and availability).
2. Research the actual product. The product research agent gathers every .md, .txt, and .html file in sources/, plus any pages listed in sources/urls.txt, which it fetches with Playwright. The research model - a writing model - then takes notes on each source, limited to what that source explicitly states. The output is Markdown, with one set of notes per source: readable by a human, easy to spot-check, and very forgiving if something gets trimmed or needs to be condensed to fit in another context window.
3. Brief, then campaign. This is the stage with a secret. This is a self-feeding compound stage - before it drafts a single post, the campaign stage first condenses all of the research notes into one, single product brief. Why bother? Because a single bounded document makes a better source of truth than a pile of notes competing for room in the prompt - and it gives every claim in every campaign exactly one place to trace back to. Then the drafting begins, with all three kinds of context in one prompt: the agent’s persona as Markdown, the photo inventory as JSON, and the brief as Markdown. That mix is intentional - traceable metadata for the photos and formatted prose for everything a person might need to read. The prompt’s rules are blunt: the condensed product brief is the only source of facts, no invented metrics, no claims beyond what the photos show and the product offers. The generated campaign lands in output/campaigns_raw.md - with each campaign track ready to go, under its own document section.
4. A human decides. The only fully deterministic stage in the pipeline, because it isn’t automated at all. Someone reads the draft, keeps what’s good, rewrites what isn’t, and then presses the “Easy” button by hand.
Since we’re dedicated to Open Source we thought our social media harness should be as transparent as possible, too. That’s why we built it in a way where all of the prompts are visible, the model calls go through a thin resilience framework, and it’s wired by default to a cloud provider so you don’t need an expensive GPU to try it out. The payoff of building it this way is that everything between the model calls is cached and logged, re-runs only rebuild what changed, and when a draft looks off you can instantly tell whether the context or the harness or the model is to blame.
The Model Is the Easy Part
Ask anyone who builds agent workflows for a living what the hard part is and you’ll eventually hear the same answer, and it definitely isn’t the prompt - it’s resilience. A demo only has to work once - a workflow has to work on a Tuesday at 4 p.m. when the free tier is having a moment, across dozens of slow, rate-limited, occasionally flaky network calls to a model you don’t control - and one bad call in the middle of a long run shouldn’t cost you the whole workflow, or worse, quietly poison it. That’s the real engineering challenge, and it’s where SALLY really shines.
A few decisions you’re welcome to steal:
- One retry policy, in one place. The client library’s built-in retries are switched off so a single loop owns the behavior: up to
MAX_RETRIESattempts (4 by default), exponential backoff with a little random jitter, capped at 30 seconds, with every attempt logged. Retries scattered across layers multiply - retries inside retries turn a blip into a stampede - and jitter keeps a pipeline from hammering a provider that’s already struggling. A politeSLEEP_BETWEEN_CALLSpause keeps it from getting there in the first place. - A 200 is not a success. An error tucked inside a 200 response, an empty
choiceslist, and empty text all count as failures and get retried. With model APIs, “the request went through” and “we got something usable” are two different questions. - Cache so failure is cheap. Batches and per-source notes are cached as they finish, so a crash at photo batch 7 doesn’t mean redoing batches 1 through 6, and a rerun only touches new or changed files. Resumability is what turns an outage from a disaster into an inconvenience.
- Log the raw conversation. Every request and response lands in
logs/as JSON, so when output looks wrong you can tell whether the model, the prompt, or the plumbing is at fault. - Fail loudly, not quietly. The scariest failures are the ones that look like success - a truncated input that still produces a confident, wrong answer (hold that thought, for the next section). Resilience includes making the silent cases noisy.
And because we’re not in the business of pretending: it isn’t perfect - but are agentic pipelines… ever?
Your Context Window Is a Budget
Every model call has a finite amount of room, so SALLY treats context like a budget and gives each block a hard cap - in characters, not tokens, to keep the math boring:
- Persona: 30,000 characters (everything in
agent/, sent with every request) - Photo inventory: 42,000 characters
- A single source document: 24,000 characters
- Merged research notes: 60,000 characters, split evenly across your sources
The important word there is hard. When a block goes over, it gets cut - and where it gets cut matters more than you’d think. Chop the end off a persona file and whichever rule happened to live down there is simply gone; the model won’t miss what it never saw, and neither will anything else in the pipeline. Chop through the inventory JSON and the photos at the end of the list quietly vanish from the campaigns, along with the structure that made the data parseable in the first place. Nothing crashes. The drafts just get a little worse, and nobody can say why.
The fix isn’t a smarter cut, it’s a shorter input. Trim persona files down to the rules that matter, keep sources focused, and prune the photo folder to the shots you’d actually use - then read the logs when the harness tells you it had to clamp something, because the raw prompts show exactly what the model did and didn’t see. The lesson travels well beyond this project: if you don’t decide what goes in the window, the window will decide for you.
Honest Limitations
We’re dedicated to talking about the soft underbelly of our creations, and a pipeline that repeatedly ships your photos and brand copy to a third party has a few worth knowing up front:
- Images and source text are sent to the model provider (through OpenRouter), and the whole
agent/folder goes out with every single persona-bound request. Keep secrets and private customer data out ofUSER.md, the photos, and the sources - and read your provider’s data policy before you feed it anything confidential. - SALLY drafts, and a person verifies. The prompt tells the model to stay inside the product brief, but nothing downstream checks that every claim in a campaign actually traces back to it - the output is written to disk as-is, with no parsing or validation. That’s what the human review step is for; skip it at your own risk.
- Every error gets the same retry treatment, including permanent ones like a bad key or a retired model, so a doomed request burns through all of its attempts and backoff before you hear about it.
Retry-Afterheaders from the provider are ignored. Keep an eye on SALLY while it’s working and don’t be afraid to get involved if things aren’t looking good. - Context limits are hard cuts, not summaries. If an input is too long, the tail is simply lost - shorten it at the source.
- Some providers reject large image batches, so you may need to lower
IMAGE_BATCH_SIZEto get a clean run. - The campaign prompt is written for Instagram and Reddit. Other platforms mean editing the prompt - which, to be clear, is the intended workflow on this project!
- Doing it - free-tier models are rate-limited, and they change, get renamed, or disappear without much notice. Models and providers will keep moving whether or not your project does, so expect to revisit your model settings semi-frequently. SALLY makes the churn cheap to diagnose with its caches and raw logs, but it is not zero effort. Plan on owning your whole project space as soon as you clone the repo.
Get It / What’s Next
SALLY is available, now, on our GitHub. You’ll need Python, an OpenRouter API key (free-tier models work), and a few minutes with the questionnaire in agent/USER.md - that file is where your brand lives, so it’s worth doing properly:
git clone https://github.com/315Concepts/sally-agent-pipeline
cd sally-agent-pipeline
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium # only needed for pages in sources/urls.txt
cp .env.template .env # then add your OpenRouter key
# fill in agent/USER.md, and customize the other persona files
# add photos to images/, and sources to sources/
python run_agent.py
The README covers the full list of tuning options, the context limits, and how the caches and logs work. As with the rest of the family, the code is out in the open so you can use it, break it, and improve it. If you hit a rough edge, find a gap in the prompts, or want SALLY to do something it doesn’t yet - open an Issue or send a Pull Request. The best fixes tend to come from someone else’s “oh, that’s why nobody does it that way” moment and we’re always happy to learn the easy way from someone else.
And don’t worry - SALLY has never once hit Publish, which - given how the rest of us feel about brand awareness - is exactly why we trust it so much.
