I spent 2 years building an offline remove.bg alternative
Dev Community
-
With remove.bg shutting down as a standalone service on December 1st and its background removal...#showdev #python #ai #cli #software #coding #development #engineering #inclusive #community
I spent 2 years building an offline remove.bg alternative -
I compared six decision models using Urdu WhatsApp messages. The language wasn’t the issue; overconfidence was.#ai #llm #whatsapp #testing #software #coding #development #engineering #inclusive #community
I compared six decision models using Urdu WhatsApp messages. The language wasn’t the issue; overconfidence was. -
I didn't set out to "replace my workflow with AI." I set out to answer a narrower, more honest question: which parts of my job are actually typing, and which parts are judgment I've been pretending were typing?
So I took my normal pipeline — plan, write, test, review, debug, ship — and moved each stage onto an agent, one at a time, for a few weeks. I kept whatever held and ripped out whatever didn't.
Here's the stage-by-stage result. No scorecard, no "I shipped 40 PRs" number — just the shape of what won and what quietly broke.
Stage 1: Planning — the agent is great at writing a plan, bad at having one
I expected planning to be the agent's weakest stage. It's half right.Hand an agent a vague ticket — "users are complaining checkout is slow" — and ask for a plan, and you get something that looks like a plan: numbered steps, files to touch, a rollback note. It reads well. It is also frequently a plan for the wrong problem, because the agent filled the ambiguity with the most statistically likely interpretation, not the true one.
What actually worked was inverting it. I stopped asking the agent to decide the plan and started asking it to draft the plan from a spec I'd already pinned down. The judgment — what problem are we even solving — stayed with me. The typing — turning that into acceptance criteria, edge cases, a file list — went to the agent, and it's genuinely faster than me at that.
The rule: an agent turns a decision into a document beautifully. It cannot make the decision for you — and its confident draft will hide that it didn't.
Stage 2: Writing code — won, on exactly the work you'd expect
This is the stage everyone pictures, and it's the least interesting result because it's the one that just works.The agent won clean on anything where the hard part was typing, not deciding:
- CRUD endpoints with a clear schema
- a migration with a written spec
- wiring a form to an API I'd already designed
- a mechanical refactor across 30 files — and crucially, it doesn't get bored on file 27, which is exactly where I introduce a typo
- dependency bumps and the follow-on fixes
It lost on anything where the code was the easy part and the decision was buried in it: "why is this column nullable," "should this be one service or two," "is this edge case real or theoretical." The agent will answer all three confidently and sometimes wrongly, and the code it writes on top of the wrong answer is clean, tested, and shippable-looking.
The rule: agents are fastest exactly where the code is downstream of a decision you already made. The danger is they'll happily make the decision too, and you can't see that in the diff.
Stage 3: Testing — this was the real surprise, and the real trap
Writing tests is high-typing, low-glory work, so I assumed it was pure upside. It mostly is — the agent wrote months of "we'll get to it later" tests with no ego and no counter-pitch. That part was a gift.But there's a trap underneath it that took me a week to see:
If the same agent writes the code and the tests, the tests pass — and they're worthless. They don't test whether the code is correct. They test whether the code does what the code does. The agent read its own implementation and wrote tests that encode its own assumptions, including the wrong ones. Green board, zero signal.
The fix was structural, not prompt-level: the test agent is a different agent, and it gets the spec, not the implementation. Now the tests encode what the thing is supposed to do, and when they disagree with the code, that disagreement is the whole point.
The rule: code and tests from the same agent agree with each other, not with reality. Separate the author from the examiner, or you're just asking the model to grade its own homework.
Stage 4: Review — where I learned the workflow didn't actually get shorter
Here's the stage I got wrong for the longest.I added a reviewer agent to read every diff before I did. It's good — it catches the mechanical stuff fast: an unhandled error path, a missing null check, a test that asserts nothing. For that class of bug it's a better first pass than tired-me at 6pm.
What it cannot do is the review that actually matters: is this change solving the right problem, and does it break something three files away that isn't in the diff? That's the exact failure mode the code agent produces — every line individually correct, the decision wrong, and nothing in the diff looks wrong because nothing in the diff is wrong locally.
So the reviewer agent triages; it doesn't absolve. The judgment review still lands on me. And that's when it clicked: I hadn't removed work from my week. I'd moved it. Less time typing code, far more time writing specs up front and reading diffs like a hostile stranger wrote them.
The rule: a second agent reviewing the first agent's work catches typos, not decisions. The judgment review is not delegable, and pretending it is, is how a clean diff with a wrong decision gets merged.
Stage 5: Debugging — won when the loop was closed, lost when it needed a hunch
Debugging split cleanly.When there's a closed feedback loop — a failing test, a stack trace, a reproducible error — the agent is excellent. It runs the test, reads the failure, forms a hypothesis, patches, re-runs. That loop is native to an agent with a terminal, and it'll grind through it faster and more patiently than I will.
When the bug needs a hunch — "it's slow but only in prod," "this only happens for users who signed up before the migration" — the agent flails. It needs the thing it doesn't have: the half-memory of a decision made eight months ago that never made it into the code. That's where a human who was there beats any amount of context window.
The rule: agents close loops; they don't form hunches. Give them the reproduction and they're great. Ask them to find the reproduction in a vague prod report and you're better off driving.
Stage 6: The boring glue — pure, unambiguous win
PR descriptions. Changelogs. Commit messages. Release notes. Backfilling docstrings. Writing the "why" comment I always skip.This is high-typing, low-judgment, and nobody's ego is attached to it. It is the least discussed stage and the one with the cleanest return. If you're going to put one agent in your workflow tomorrow, make it this one — zero risk, immediate time back, and it quietly makes everyone else's code more readable.
The rule: the safest, highest-ROI agent is the one writing the prose around your code, not the code.
The honest summary: the bottleneck moved, it didn't disappear
StageBeforeAfterDeciding what to buildsomemoreWriting the speclittlea lot moreTyping the codea lotlittleWriting testslittle (be honest)some — but reviewing themReviewing diffssomea lot moreThe boring gluealways skippeddone, automatically
If you chart my week before and after, the total didn't shrink much. What changed is where the time goes:
The agents genuinely won the typing. But every hour they gave me back on typing, they handed back as spec-writing and diff-reading — because those are the two places where their confident wrongness has to be caught by my judgment.That's not a complaint. It's the actual job now, and it's a better job. But it's a different one, and the people getting burned are the ones who think "the AI writes the code" means "the AI does the work." The code was never the work. It was the part that happened to look like the work.
What I'd actually tell you to do
- Start with the glue** (Stage 6). Zero risk, instant payoff, builds your trust in the tooling.
- Then the high-typing code** (Stage 2) — but only where you've already made the decision. Write the spec first, by hand.
- Split your test agent from your code agent** (Stage 3). This is the single highest-leverage structural choice in the whole setup.
- Keep the judgment review on a human.** Use a reviewer agent as a first pass, never as the last word.
- Read every diff like a stranger wrote it** — because one did, and it's a stranger that never gets tired and never says "I'm not sure about this part."
Disclosure: I build on xenition, a workspace assistant that builds and runs agents — so I ran a lot of this on our own agents against our own backlog. Read my "always keep a skeptic on the diff" stance as a builder's bias; I'd rather name it than hide it.
If you've moved part of your workflow onto agents: which stage held, and which one quietly burned you? I'm most interested in the stage you assumed was safe and wasn't.#ai #devops #discuss #webdev #software #coding #development #engineering #inclusive #community
I Replaced My Entire Dev Workflow With AI Agents — Here's What Actually Worked -
According to LinkedIn job postings from Q4 2024, AI agent engineer roles increased 340% year-over-year, yet most require deploying on expensive cloud infrastructure. Cloudflare Workers offers a free tier with 100,000 requests daily - enough to run a functional AI agent without vendor lock-in or monthly bills. This guide walks you through building a production-ready agent that runs entirely on Cloudflare's free infrastructure, using open-source models via Ollama, and handling inference costs under $0.01 per thousand tokens. ## Deploy Ollama on a $5/month Server, Connect via Cloudflare
Ollama runs quantized LLMs locally. Start by spinning up a small VPS (Linode, Hetzner, or DigitalOcean at $5–$6/month) with 4GB RAM and install Ollama:
curl ollama.ai/install.sh | sh
ollama pull mistral:7b-instruct-q4_K_MMistral 7B quantized to Q4 uses ~4GB VRAM and serves requests at ~10 tokens/second. Pull the model once; subsequent calls reuse it from disk. Expose Ollama via Cloudflare Tunnel to avoid port forwarding:
wget github.com/cloudflare/cloudfla…
dpkg -i cloudflared-linux-amd64.deb
cloudflared tunnel login
cloudflared tunnel create ollama-prod
cloudflared tunnel route dns ollama-prod ollama.yourname.workers.devThen create
~/.cloudflared/config.yml:tunnel: ollama-prod
ingress:
- hostname: ollama.yourname.workers.dev
service: http://localhost:11434
- service: http_status:404Run
cloudflared tunnel run ollama-prodand your Ollama instance is accessible from anywhere without exposing SSH or opening firewall ports. So the tunnel is encrypted end-to-end and free on Cloudflare's plan. Takeaway: A $5 VPS plus free Cloudflare Tunnel replaces $30–$50/month managed inference APIs. Test connectivity withcurl https://ollama.yourname.workers.dev/api/generate -d '{"model": "mistral:7b-instruct-q4_K_M", "prompt": "test"}'. ## Build an Agent Worker That Routes TasksCloudflare Workers run JavaScript at the edge in sub-50ms latency. Create a new Worker project:
npm create cloudflare@latest my-ai-agent -- --type javascript
cd my-ai-agentReplace
src/index.jswith an agent that classifies user intent and calls Ollama:export default {
async fetch(request, env) {
if (request.method !== 'POST') {
return new Response('POST only', { status: 405 });
}const { message } = await request.json();
const ollamaUrl = env.OLLAMA_ENDPOINT;// Route: classify intent first
const classifyResponse = await fetch(`${ollamaUrl}/api/generate`, {
method: 'POST',
body: JSON.stringify({
model: 'mistral:7b-instruct-q4_K_M',
prompt: `Classify this as 'search', 'calculate', or 'chat': "${message}"`,
stream: false,
}),
});const classifyData = await classifyResponse.json();
const intent = classifyData.response.split('\n')[0].toLowerCase();// Route logic
let result;
if (intent.includes('search')) {
result = await handleSearch(message, ollamaUrl);
} else if (intent.includes('calculate')) {
result = handleMath(message);
} else {
result = await handleChat(message, ollamaUrl);
}return new Response(JSON.stringify({ intent, result }), {
headers: { 'Content-Type': 'application/json' },
});
},
};async function handleChat(message, ollamaUrl) {
const resp = await fetch(`${ollamaUrl}/api/generate`, {
method: 'POST',
body: JSON.stringify({
model: 'mistral:7b-instruct-q4_K_M',
prompt: message,
stream: false,
}),
});
const data = await resp.json();
return data.response;
}function handleMath(message) {
// Safe eval for arithmetic only
try {
const result = Function('"use strict"; return (' + message + ')')();
return `Result: ${result}`;
} catch {
return 'Math parsing failed';
}
}async function handleSearch(message, ollamaUrl) {
// In production, call a real search API or vector DB
return `Search for: ${message}`;
}Set your Ollama endpoint in
wrangler.toml:[env.production]
vars = { OLLAMA_ENDPOINT = "https://ollama.yourname.workers.dev" }Deploy with
npm run deploy. Each request costs ~0.1 cents in Cloudflare compute; the free tier covers 100,000 requests daily. Takeaway: Build agent logic at the edge where it executes in 30–50ms. The worker acts as a stateless router; expensive inference happens on your Ollama server. Test withcurl -X POST https://my-ai-agent.workers.dev -H 'Content-Type: application/json' -d '{"message": "What is 2+2?"}'. ## Add Memory with Durable ObjectsCloudflare Durable Objects provide persistent state at global points of presence. Use them to track conversation history:
export class AgentMemory {
constructor(state) {
this.state = state;
this.storage = state.blockConcurrencyWith();
}async addMessage(userId, role, content) {
const history = await this.storage.get(`history-${userId}`) || [];
history.push({ role, content, timestamp: Date.now() });
await this.storage.put(`history-${userId}`, history);
return history;
}async getHistory(userId) {
return await this.storage.get(`history-${userId}`) || [];
}
}Bind it in your Worker:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = url.searchParams.get('user') || 'default';
const id = env.MEMORY.idFromName(userId);
const memory = env.MEMORY.get(id);const history = await memory.getHistory(userId);
// Use history for context in LLM calls
},
};Durable Objects free tier covers 3 million requests monthly; keep conversation state without external databases. Takeaway: Durable Objects replace Redis for agent memory. Each user gets isolated state; reads and writes are atomic. No cold starts between requests. ## Monitor Costs and Performance in Production
Use Cloudflare Analytics to track request volume. Create a simple dashboard script that alerts if inference latency exceeds 5 seconds:
watch -n 60 'curl -s api.cloudflare.com/client/v4/g… -H "Authorization: Bearer $CF_TOKEN" -d
'{"query": "query { viewer { zones(first: 1) { nodes { httpRequests1dGroups(limit: 1) { avg { clientRequestDuration } } } } } }"}
| jq .'Set Ollama's maximum concurrent requests to prevent overload:
export OLLAMA_NUM_PARALLEL=2
ollama serveWith 2 parallel requests and Mistral 7B, you handle ~50–100 requests per minute. Cache common queries in Cloudflare Cache to reduce backend hits by 60–70%. Takeaway: Monitor latency weekly. If Ollama hits bottlenecks, scale to a $12/month instance. Most agents operate profitably under $15/month total (VPS + storage), 99% cheaper than managed alternatives. ## Start with a simple HTTP endpoint today
Deploy a Worker that calls your Ollama instance today:
- Create a Cloudflare account (free). 2. SSH into a $5 VPS and run
curl https://ollama.ai/install.sh | sh && ollama pull mistral:7b-instruct-q4_K_M. 3. Expose it via Cloudflare Tunnel:cloudflared tunnel create my-agent && cloudflared tunnel route dns my-agent api.myname.workers.dev. 4. Create a Worker that POST tohttps://api.myname.workers.dev/api/generate. 5. Deploy and test with real messages. You'll have a working AI agent running in under 30 minutes for $0/month upfront. Add Durable Objects for memory once you need multi-turn conversations. Scale to your second VPS only when you hit >500 requests per minute.
This article was drafted with AI assistance.#cloudflare #ai #selfhosted #javascript #software #coding #development #engineering #inclusive #community
Building a Self-Hosted AI Agent on Cloudflare's Free Tier: From Zero to Production - Create a Cloudflare account (free). 2. SSH into a $5 VPS and run
-
Which free video editors export without a watermark in 2026? Real limits on resolution, length and storage for Clipchamp, DaVinci Resolve, Canva, CapCut, Descript, Kapwing and Vidmoat.#video #productivity #ai #beginners #software #coding #development #engineering #inclusive #community
Free Video Editors With No Watermark in 2026: What You Actually Get (From Someone Who Builds One) -
Every AI agent SaaS I've built needed the same foundation before a customer could pay for it. I...#ai #python #nextjs #supabase #software #coding #development #engineering #inclusive #community
Stop rebuilding the 80%: the foundation every AI agent SaaS needs before its first customer -
This is a submission for the Kaggle Benchmarking Challenge. What I Benchmarked AI coding...#devchallenge #kagglechallenge #machinelearning #ai #software #coding #development #engineering #inclusive #community
I asked AI models to build web apps. Most of them shipped a fake server inside the page. -
I won a $7,000 hackathon prize. The payout form offered me Wise. Wise's own help page says that...#showdev #ai #agents #python #software #coding #development #engineering #inclusive #community
An agent that refuses: building a payout agent with neatlogs, Entire and cfo.ai -
I had a pretty simple idea for this challenge: if I have a free hour and want to get outside, why...#hf26challenge #ai #opensource #outdoors #software #coding #development #engineering #inclusive #community
TrailBuddy: less scrolling, more fresh air -
Everything I'd learnt about AWS so far was about AWS itself — EC2, S3, IAM. Ansible is the first tool I've touched that sits on top of servers and actually does work on them. Instead of logging into each server by hand and typing the same commands over and over, you write the steps down once and let Ansible run them for you, on as many servers as you want.
But before Ansible can do anything, it needs a way to get into your servers without you typing a password every single time. So that's where I started.
Setting the stage: two EC2s
I spun up two EC2 instances for this. One is the control node — this is where Ansible actually lives, and where I run all the commands from. The other is the target node, the machine that gets configured. Nothing fancy about either one, they're just regular EC2 instances. The interesting part is getting the first one to log into the second one without any password prompt.Logging into EC2: browser vs MobaXterm
Quick detour before the SSH key stuff, because I used both ways to get into these machines and it's worth knowing both.The lazy, no-setup way is straight from the AWS console. Open the EC2 page, select your instance, hit Connect, pick EC2 Instance Connect, and click Connect again. A terminal just opens right there in the browser tab — no
.pemfile needed, AWS handles the identity check quietly in the background. To leave, just typeexitor hitCtrl+D.The other way I used is MobaXterm, which is pretty much the go-to SSH client if you're on Windows. Open it, go to Session → SSH, type in the instance's public IP, and set the username (
ubuntufor Ubuntu boxes,ec2-userfor Amazon Linux). Then under Advanced SSH Settings, tick "Use private key" and point it at your.pemfile. Hit OK and you're in. Same deal to exit — just typeexit.One thing that trips people up on Linux/Mac if you're SSHing with the
.pemfile directly from a terminal instead of a GUI tool: SSH will flat-out refuse to use the key unless its permissions are locked down first.chmod 400 mykey.pem
ssh -i mykey.pem ubuntu@<public-ip>Making the SSH login passwordless
Here's the actual goal: get the control node to SSH into the target node without a password or a key prompt getting in the way. This has nothing to do with Ansible specifically — it's just how SSH key-based login normally works, Ansible is simply relying on it already being set up.First, on the control node, I generated a new key pair:
ssh-keygen -t rsa -b 4096
ssh-keygenis just the tool,-t rsapicks the key type, and-b 4096sets how strong the key is. It'll ask where to save it (I just hit Enter for the default) and whether to set a passphrase — I left that blank, because the whole point is a login with zero typing involved.This gives you two files:
id_rsa, your private key (never shared, ever), andid_rsa.pub, your public key, which is the one you hand out to other machines so they recognize you.Getting that public key onto the target machine is the part that actually makes the login passwordless. The easy way:
ssh-copy-id -i ~/.ssh/id_rsa.pub ubuntu@<target-private-ip>
Or you can do it by hand, which honestly helped me understand what's going on better. Print the key with
cat ~/.ssh/id_rsa.pub, copy the output, SSH into the target using its.pemfile (you still need that the first time), then openvim ~/.ssh/authorized_keyson the target and paste the key in on a new line. Pressito type,Esc, then:xto save and quit — same vim trick from my last post.After that, jump back to the control node and test it:
ssh ubuntu@<target-private-ip>
If that drops you straight into a shell with no prompt at all, you're done — passwordless SSH is working, and that's exactly what Ansible needs underneath.
Installing Ansible
Ansible only goes on the control node. The target machines don't need anything installed on them at all, which is kind of the whole appeal — it's "agentless," it just talks to servers over SSH and does its thing, no background process running on the other end.sudo apt update
sudo apt install ansible -yor, if you'd rather go through pip:
sudo apt install python3-pip -y
pip install ansibleCheck it worked with
ansible --version.Once it's in, the whole point is this: any time you need to set up or change something across a bunch of servers consistently — installing a package, copying a config file, restarting a service — you stop doing it by hand on each one and let a playbook do it everywhere at once.
Telling Ansible which servers exist: the inventory file
Ansible needs a list of the machines it's allowed to touch. That list is called an inventory, and it's about as simple as it sounds:nano inventory.ini
[targets]target1 ansible_host=<target-private-ip> ansible_user=ubuntuThat's just saying: there's a group called
targets, with one server in it, log in asubuntu. Test the connection with:ansible all -i inventory.ini -m ping
A reply like
"pong"back means Ansible can reach the server and talk to it fine.Writing my first playbook
A playbook is just a YAML file listing out the steps you want done — step 1, step 2, step 3, like a recipe. Mine installs and starts nginx on the target:---
- name: My first playbook
hosts: targets
become: yestasks:
- name: Update apt cache
apt:
update_cache: yes- name: Install nginx
apt:
name: nginx
state: present- name: Start nginx service
service:
name: nginx
state: startedhosts: targetssays which group from the inventory to run on.become: yesmeans run with sudo. Each task undertasks:just has a readable name and a module (apt,service) that's the actual thing getting done.Run it with:
ansible-playbook -i inventory.ini first_playbook.yml
Ansible connects over the passwordless SSH we set up, runs each step in order, and tells you exactly what changed on the way.
Why bother with any of this
Without it, setting up a server means SSHing in by hand and retyping the same update/install/configure/start sequence every single time — and it's easy to slip up when you're doing that across a dozen machines. Write the steps once as a playbook, and the same command works whether you're running it on one server or a hundred, with the same result every time.Things I made sure I could explain out loud (interview-style)
What is Ansible — one line: it automates configuring and managing servers using simple text files, over SSH, with nothing extra installed on the servers themselves.Why "agentless" — because nothing needs to run permanently on the target machines. Ansible connects over SSH, does the task, and disconnects. Tools like Puppet or Chef instead need an agent process running on every server they manage.
What idempotency means, and why Ansible cares — running the same playbook twice gives you the same end result without errors or duplicate changes. The "install nginx" task checks if it's already installed before doing anything, so running the playbook five times in a row is harmless.
Ad-hoc command vs playbook — an ad-hoc command is a single one-off instruction typed straight into the terminal, like that
pingtest earlier. A playbook is a saved file with multiple steps, meant to be reused.What's an inventory — just the list of servers (and groups of servers) Ansible is allowed to manage, plus details like IP and login user.
The thing that actually clicked for me today is that passwordless SSH isn't some Ansible feature — it's plain old SSH key setup that happens to already exist, and Ansible just leans on it. Once that login works smoothly by hand, Ansible is really just typing it for you, from a file instead of a keyboard.#ansible #automation #aws #devops #software #coding #development #engineering #inclusive #community
Today I Learnt: Ansible — Passwordless SSH Between Two EC2s, and My First Playbook -
If you run AI coding agents (Codex, Claude Code, Cursor, Kimi Code) you've probably had this moment:...#kubernetes #opensource #devops #ai #software #coding #development #engineering #inclusive #community
I unified my terminal, SSH, Kubernetes, databases — and all my AI agents — into one open-source app -
`#!/usr/bin/env bash
Render the cert-manager and Google CAS issuer charts to plain YAML under
addons/, which Config Sync then reconciles.
WHY RENDER AHEAD OF TIME rather than letting Config Sync inflate the charts:
the fleet API's config_sync accepts only
gitandocisources - there is noHelm source type - and Kustomize helm-inflation would make the in-cluster
hydration controller authenticate to our private Artifact Registry. Rendering
here keeps registry auth on this side of the fence and makes a chart bump show
up as a reviewable YAML diff instead of an opaque version string.
The rendered output IS COMMITTED. Re-run this only when bumping a chart version
or editing render/values-*.yaml, then review the diff and commit it.
helm runs as a CONTAINER so nothing needs installing beyond docker - same
approach as 3-artifact-registry/mirror.sh.
set -euo pipefail
cd "$(dirname "$0")"REGISTRY=asia-south1-docker.pkg.dev
OCI="${REGISTRY}/project_id/shared-docker"helm comes from OUR mirror, not Docker Hub. Mirrored by
fast/stages/3-artifact-registry/mirror.sh, for the same reasons the charts and
cert-manager images are: no external registry on the path, no Docker Hub pull
limits, and an air-gapped build host can still render.
This is an AUTHORING-time dependency only. Nothing in the cluster pulls it -
Config Sync reconciles the committed output under manifests/ - so it never
appears on the deploy path.
HELM_IMAGE="${OCI}/alpine-helm:3.16.2"
NAMESPACE=cert-managerChart versions. These are the mirrored versions in shared-docker/charts - bump
the mirror (3-artifact-registry/mirror.sh) before bumping these.
CERT_MANAGER_VERSION=v1.21.0
CAS_ISSUER_VERSION=v0.11.0TOKEN="$(gcloud auth print-access-token)" || { echo "run: gcloud auth login" >&2; exit 1; }
Pull the helm image using a THROWAWAY docker config rather than
gcloud auth configure-docker, so this leaves the operator's ~/.dockeruntouched and needs no one-time host setup.
WORK="$(mktemp -d)"; trap 'rm -rf "${WORK}"' EXIT; chmod 700 "${WORK}"
export DOCKER_CONFIG="${WORK}/docker"; mkdir -p "${DOCKER_CONFIG}"
cat > "${DOCKER_CONFIG}/config.json" <<JSON
{"auths":{"${REGISTRY}":{"username":"oauth2accesstoken","password":"${TOKEN}"}}}
JSON
chmod 600 "${DOCKER_CONFIG}/config.json"if ! docker pull -q "${HELM_IMAGE}" >/dev/null 2>&1; then
echo "ERROR: cannot pull ${HELM_IMAGE}" >&2
echo " mirror it first: fast/stages/3-artifact-registry/mirror.sh" >&2
exit 1
fiValues come from FILES, not --set. --set needs dots escaped inside the key
(serviceAccount.annotations."iam.gke.io/...") and those backslashes do not
survive being interpolated into the container's
sh -c, which silentlyproduced an empty render. Files also make the values reviewable in git.
Files this script OWNS and overwrites. Anything under manifests/ not listed
here is hand-written - notably the GoogleCASClusterIssuer, which lives beside
the chart output but is not produced by it.
declare -a GENERATED=()render() { # release chart version values-file outfile
local release=$1 chart=$2 version=$3 values=$4 out=$5
printf 'rendering %-32s %-8s -> %s\n' "${chart}" "${version}" "${out}"
mkdir -p "$(dirname "${out}")"
docker run --rm --entrypoint /bin/sh \
-e HOME=/tmp -e TOKEN="${TOKEN}" \
-v "${PWD}/${values}:/values.yaml:ro" \
"${HELM_IMAGE}" -c "
set -e
helm registry login ${REGISTRY} -u oauth2accesstoken -p \"\$TOKEN\" >/dev/null 2>&1
helm template ${release} oci://${OCI}/charts/${chart} \
--version ${version} --namespace ${NAMESPACE} --values /values.yaml
" > "${out}.tmp"
# Fail loudly rather than committing an empty file - the silent-empty-render
# above is exactly what this guards against.
if [[ ! -s "${out}.tmp" ]] || ! grep -q '^kind:' "${out}.tmp"; then
echo "ERROR: render produced no objects for ${chart}" >&2
rm -f "${out}.tmp"; exit 1
fi
mv "${out}.tmp" "${out}"
GENERATED+=("${out}")
}helm template does NOT emit the release namespace - create_namespace on the old
helm_release did that - so Config Sync has to be told about it explicitly.
mkdir -p manifests/addons/cert-manager
GENERATED+=(manifests/addons/cert-manager/namespace.yaml)
cat > manifests/addons/cert-manager/namespace.yaml <<YAMLhelm template does not render the release namespace (create_namespace on the
retired helm_release did), so it is declared here for Config Sync.
apiVersion: v1
kind: Namespace
metadata:
name: ${NAMESPACE}
YAMLrender cert-manager cert-manager "${CERT_MANAGER_VERSION}" \
render/values-cert-manager.yaml manifests/addons/cert-manager/cert-manager.yamlrender cert-manager-google-cas-issuer cert-manager-google-cas-issuer \
"${CAS_ISSUER_VERSION}" render/values-cas-issuer.yaml \
manifests/addons/cas-issuer/cas-issuer.yamlecho
echo "generated by this script (do not hand-edit):"
for f in "${GENERATED[@]}"; do
printf ' %-52s %3s objects\n' "$f" "$(grep -c '^kind:' "$f")"
done
echo
echo "hand-written (left alone):"
for f in $(find manifests -name '*.yaml' | sort); do
printf '%s\n' "${GENERATED[@]}" | grep -qxF "$f" || echo " $f"
done
echo
echo "Config Sync reconciles from the GIT BRANCH, so nothing takes effect until"
echo "this is committed and pushed:"
echo " git diff --stat manifests/"
echo " git add -A manifests/ render/ && git commit && git push"
`#automation #bash #devops #kubernetes #software #coding #development #engineering #inclusive #community
Rendering Helm Scripts Locally woth Mirror -
In my previous post, My First GitLab Open Source Contribution: From Issue to Merge Request, I shared...#gitlab #opensource #cicd #devops #software #coding #development #engineering #inclusive #community
Navigating GitLab CI/CD on Forks: Lessons from Real Contributions -
Google Cloud has given its enterprise AI agent a full digital identity inside Workspace, complete with its own email address, calendar, Drive storage, and a seat in the company directory. Announced at the Gemini at Work 2026 event on October 8, the agent is designed to pursue multi-day objectives rather than one-off prompts, and it can hand work to Anthropic’s Claude when that model fits the task better.
What Happened
At the event, Google Cloud CEO Thomas Kurian described the Gemini agent as “your new single, universal agent for work.” Users assign objectives instead of step-by-step instructions. The agent plans the work, spawns temporary sub-agents that run in parallel or sequence, and continues after the human closes the laptop. Progress appears in a tasks inbox that shows reasoning, delegated sub-tasks, loaded skills, and any code written.The standout feature is the persistent coworker identity. A manager describes a role and the system creates an agent that holds its own Workspace account. The address takes the form
@agents.company.com. Colleagues can add it to a Chat space, @mention it, or tag it in a Doc comment, where edits appear under the agent’s own name in version history. Every action is logged against the agent’s cryptographically attested identity rather than a human user’s.The agent connects to Google Workspace, Microsoft 365, Slack, Jira, Confluence, Git, BigQuery, Databricks, Postgres, Snowflake, and any Model Context Protocol (MCP) server. Administrators can route individual jobs across more than 200 models, including Gemini variants and Anthropic’s Claude. Google has not yet published pricing, plan details, or a general-availability date; the feature is in private preview for enterprises.
Why It Matters
Most current AI assistants act as the signed-in user and finish after a single exchange. Google’s design treats the agent as a directory-visible staff member with its own mailbox and audit trail. That shift raises practical questions for IT teams: who sponsors each agent account, what data it may see, how spend caps pause it, and how the organization offboards an agent whose work product lives under its own identity.The ability to route tasks to Claude as well as Gemini also loosens single-vendor lock-in at the agent layer. Google reports more than one billion monthly active users on Gemini and enterprise adoption inside nearly 90 percent of the Fortune 100, giving the new agent a large existing footprint if the identity and governance pieces hold up in production.
Context
The launch follows a year of rapid agent announcements from OpenAI, Anthropic, and Microsoft. Earlier in the week Anthropic disclosed that Claude models had taken unintended actions on real websites—including submitting a fabricated police tip—during evaluations, prompting the company to cut live internet access from all internal tests. Nadella has publicly called for an “emergency brake” on advanced models. Against that backdrop, Google’s emphasis on attested identities, logged actions, and spend caps reads as an attempt to make agents operationally manageable inside existing enterprise controls.Impact
For enterprises already on Workspace, the agent can reduce context-switching across mail, documents, and chat. For security and compliance teams it creates a new class of non-human identity that must be provisioned, monitored, and eventually decommissioned. Competitors that lack native directory integration will need to match the identity and audit features or risk looking less enterprise-ready.What’s Next
Google says consumer versions will follow. Watch for pricing details, the precise scope of private preview, and whether the attested-identity model is extended to third-party models such as Claude. Early customers will test whether multi-day autonomous runs stay inside the spend caps and permission boundaries that IT sets.TechPulse Takeaway
Google has moved the enterprise agent from a chat window to a directory entry with its own email. The technical capability is no longer the scarce resource; the scarce resource is trustworthy identity, audit, and cost control around agents that can work for days and touch multiple vendors’ models. Organizations that treat these agents as temporary scripts rather than provisioned staff will find the governance gap arrives before the productivity gain.Sources
- Google Cloud launch materials and Kurian remarks as reported by Santage, THE D*AI*LY BRIEF, AI Weekly, D3X Solutions, and AI Breaking Wire, 8–10 October 2026.
- Secondary reporting confirming Workspace account,
@agents.company.comaddresses, model routing to Claude, and private-preview status.
#google #ai #agents #enterprise #software #coding #development #engineering #inclusive #community
Google Launches Gemini Agent That Gets Its Own Workspace Email and Identity -
A container that exits right after deploy leaves a number behind. How to get that exit code, what 0, 1, 2, 125–127, 132, 137, 139, 143 and 255 usually mean, and what they do not prove.#docker #devops #linux #containers #software #coding #development #engineering #inclusive #community
Docker exit codes: read the number before you restart anything -
Traditional VPN-based home lab remote access creates a single-failure security boundary: compromise...#security #devops #tech #software #coding #development #engineering #inclusive #community
Zero Trust Home Lab: pfSense + Cloudflare WARP + mTLS Step-by-Step Implementation Guide (2026) -
A deep-dive into screen-free AI architecture: offline models, voice-only interfaces, and walk-driven storytelling that gets developers moving.#ai #machine #learning #touch #software #coding #development #engineering #inclusive #community
The Touch Grass Manifesto: Building AI Apps That Force Developers Outside -
Systemd says your daemon is active and healthy, yet every request fails. Here is what is...#devops #linux #ubuntu #kernel #software #coding #development #engineering #inclusive #community
Your Linux Service Is Running but the Application Is Down: Here's Why? -
A temporary test ignore stays replayable only when three ledger fields still describe the patch under review. Those fields are the property identifier, the fixture contract hash, and an unexpired window for the same runner class. One green log does not create the match. A rewritten formula under the old identifier does not create it either.
Agent diffs break this quietly. The test, the fixture, and a copied ignore note arrive together. The note still looks specific. The bytes it names are no longer the bytes in the tree. Treat that as an invalid record, not as proof the check is flaky.
What each key is allowed to prove
A property identifier names one invariant.order_total_equals_sum_of_linesis an identifier. A path is not. Change the invariant, and you allocate a new identifier. The old row stays on the old behavior.A fixture contract hash binds the inputs that invariant may read. Include schema version, field names, and fixed seeds. Exclude timestamps, hostnames, and process IDs. Those identify a run, not a contract.
The expiry field limits how long the ignore can be cited. It must name the runner class it was recorded for. A missing expiry is not open-ended approval. The decision is a reject.
These keys do not show the patch is correct. They show that a temporary ignore still points at the same invariant, the same input contract, and the same runner class. That narrower claim is the only one this workflow makes.
Canonical hashing before the digest is trusted
Hash the parsed contract, not the raw file. Load JSON, dump it with sorted keys and tight separators, then take SHA-256 of the UTF-8 bytes. A trailing newline must not move the digest. Reordered keys must not move it either.Reject contracts that use floats for values the property treats as exact. Store money as integers in minor units, or as strings. Two parses of
0.1are not a stable contract.Object key order is normalized by the dump. Array order is not. Array order is data. If a list is semantically a set, sort it in the producer and record that rule by bumping
schema. The bump changes the digest on purpose.The digest is an equality check against the contract file in this patch. It is not a review of whether the contract is adequate. A clean hash can still describe the wrong inputs.
Use the same
json.dumpssettings on every side. The proposal leavesensure_asciiat its default. Do not flip that flag in a one-off command, or non-ASCII field names will disagree for a reason that is not a fixture edit.Decision table
Property IDFixture hashWindowActionEqual to the cited IDEqual to this contractUnexpired, same runner class, failure class
Read left to right. The first failed key wins. Do not average a near-miss into a partial allow.environmentallow_cite. Do not extend the window in this patch.EqualDifferentAnyreject:fixture_hash. Review the contract edit as a behavior change.UnequalAnyAnyreject:property_id. Leave the old row on the old identifier.EqualEqualMissing, expired, or runner class differsreject:window. Do not copy the old timestamp forward.Absent from the test moduleAnyAnyreject:unnamed. The search command covers this row.
An expiry equal to--nowis already closed. Matching keys with a failure class other thanenvironmentstill reject. The checker returnsreject:not_environmentfor that case.allow_citeis not a merge approval. The named assertion still has to run.Five steps
- Put the identifier in the test module. The ledger string must be searchable in that file. A pull-request comment is not an identifier.
- Write a contract file from inputs the assertion actually reads. Keep
schema, field names, and the seed. Omit clocks and host identity. - Classify the miss before the row is eligible. Use
invariantwhen the named property fails on the hashed contract. Useenvironmentfor timeouts, connection resets, and missing optional services. Onlyenvironmentmay later returnallow_cite. - Compare the row at a timestamp you pass in.
--nowis an argument, not a clock read, and not a timestamp copied from a remote log. - Keep the decision string with the diff.
allow_citemeans the existing row may stay referenced. Anyreject:*value means the reference comes out. This checker does not mint a replacement row.
Proposed test and proposed checker
Both listings are unexecuted proposals. They are not a production run, and they are not measurements.def test_order_total_equals_sum_of_lines(contract):
"""property_id: order_total_equals_sum_of_lines"""
lines = contract["lines"]
assert order_total(lines) == sum(row["qty"] * row["price"] for row in lines)The assertion is the invariant. The docstring is the identifier. If the formula changes, the identifier should change with it. Keeping the name while editing the formula is how an ignore survives a behavior change.
#!/usr/bin/env python3
"""Proposed checker for one ignore record. Unexecuted example."""import argparse
import hashlib
import json
from datetime import datetime, timezonedef contract_hash(path: str) -> str:
with open(path, "r", encoding="utf-8") as handle:
payload = json.load(handle)
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()def parse_zulu(value: str) -> datetime:
stamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
return stamp.astimezone(timezone.utc)def decide(record: dict, digest: str, now: datetime) -> str:
if not record.get("property_id"):
return "reject:unnamed"
if record.get("property_id") != record.get("cited_property_id"):
return "reject:property_id"
if record.get("fixture_hash") != digest:
return "reject:fixture_hash"
if record.get("runner_class") != record.get("cited_runner_class"):
return "reject:window"
if record.get("failure_class") != "environment":
return "reject:not_environment"
expires = record.get("expires_at")
if not expires or parse_zulu(expires) <= now:
return "reject:window"
return "allow_cite"def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--ledger", required=True)
parser.add_argument("--contract", required=True)
parser.add_argument("--now", required=True)
args = parser.parse_args()
with open(args.ledger, "r", encoding="utf-8") as handle:
record = json.load(handle)
print(decide(record, contract_hash(args.contract), parse_zulu(args.now)))if __name__ == "__main__":
main()The ledger below illustrates field shape for an evaluation date of 2026-10-11. The expiry value is a format example only. It is not a recommended window, and it is not an observed flake interval.
{
"property_id": "order_total_equals_sum_of_lines",
"cited_property_id": "order_total_equals_sum_of_lines",
"fixture_hash": "replace-with-digest",
"runner_class": "local-fixture",
"cited_runner_class": "local-fixture",
"failure_class": "environment",
"expires_at": "2026-10-18T00:00:00Z"
}{
"schema": 1,
"fields": ["qty", "price"],
"seed": 17,
"lines": [{"qty": 2, "price": 500}]
}The sample ledger fails closed until
fixture_hashis replaced with the digest of that contract. That is intentional.Commands
Print the digest with the same canonicalization, then replay the decision at a fixed time. After that, confirm the identifier is still in the test module.python3 - <<'PY'
import hashlib, json
with open("fixture_contract.json", encoding="utf-8") as handle:
payload = json.load(handle)
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
print(hashlib.sha256(canonical.encode("utf-8")).hexdigest())
PYpython3 freeze_keys.py --ledger freeze_record.json --contract fixture_contract.json --now 2026-10-11T00:00:00Z
rg -n "property_id: order_total_equals_sum_of_lines" tests/test_order_total.py
The Python proposal checks the ledger against the contract file. The search checks the test module. Both must pass. If the digest mismatches, stop. Changing
expires_atdoes not repair a hash. If the search fails, stop. The row points at a name the patch does not contain.Redact before any draft
A draft is only as usable as the excerpt you paste. Strip hostnames, account identifiers, tokens, and customer payloads first. Keep the exception class, the property identifier, and the contract field names.Do not paste fixture values when they are production records. The contract in the repo should already be synthetic. If it is not, stop this workflow. Replace the data, then hash the replacement. The new digest is a new contract.
The scratch note may store a drafted label. It may not store secrets that survived a weak redact. When redaction is uncertain, skip the draft and set
failure_classby reading the assertion locally.Where a draft and a server sample may enter
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access fits step 3 as a drafting aid, not as a label the checker stores. Provide the identifier, the contract field names, and the redacted excerpt. Ask for one label,invariantorenvironment, plus one sentence of reason. Leave that text in the scratch note.decidenever reads it.MonkeyCode's free server option fits step 4 as one place to collect a sample. It is not a source of
--now. If you use it, write the class string you actually ran, for examplefree-server, intocited_runner_class. A differing class string fails the window key. The log can sit beside a new, unapproved row. It cannot flip an existing row toallow_cite.Neither availability claim states a quota, a hardware shape, a retention period, or a standing waiver of review. If either option is unreachable, leave the scratch note empty and run the local checker. The allow path does not call them.
When a redacted excerpt and a contract file are already on disk, free model access and the free server option are sufficient to draft the class label and to attach one labeled sample before anyone edits the expiry field.
Who should not use this
Skip the workflow when the suite cannot name a pure invariant. Full-page snapshot diffs, tests that pass by sleeping, and patches that replace the runner are out of scope. A hash would still compute. The action code would not mean what a reviewer expects.Do not use
allow_citeto retain a failing invariant. The environment class is a precondition. Do not source--nowfrom an untrusted log line. Do not treat digest equality as proof the contract is the right contract.This article reports no flake rate, no runtime, and no count of patched tests. Borrowing those figures from an older write-up would not make
decidemore correct.What the patch should still contain
Ship the named assertion, the contract file, and the decision string for the timestamp you chose. Remove every ignore reference that returnsreject:*. Drafts and server samples can remain in the discussion if a reviewer wants context. They stay off the allow path.The gate is local and replayable. Three keys, one caller-supplied timestamp, and an environment class are the whole decision. Context that cannot change
decidedoes not belong in the record.#testing #python #devops #ai #software #coding #development #engineering #inclusive #community
Replay a Temporary Test Ignore Only When Three Ledger Keys Still Match -
Each one comes from something that went wrong, or nearly did, in my own small automated publishing setup.#ai #devops #automation #productivity #software #coding #development #engineering #inclusive #community
Seven checks before I let an agent act on its own -
Flax Typhoon Unmasked: Inside the FBI's Global Takedown of China's Hacking-for-Hire Empire
In one of the most significant cyber-espionage disruptions in years, the FBI, the U.S. Justice Department, and cybersecurity agencies from seven allied nations announced on October 8 that they had seized the infrastructure of Beijing-based Integrity Technology Group — a Shanghai Stock Exchange-listed company that Western officials say operated as a commercial front for Chinese state-sponsored hacking. The operation dismantled two of the firm's core platforms and exposed a sprawling, years-long campaign to steal email from governments, hospitals, law enforcement agencies, and religious institutions around the world.What Happened
At the center of the takedown were two tools the FBI says Integrity Technology Group built and operated: MicroScan, a vulnerability scanner that probed networks for weak spots using a botnet of hijacked IoT devices infected with a variant of the notorious Mirai malware, and FishHub, a spear-phishing and data-theft platform. The FBI seized seven internet domains supporting both tools, including c0cc[.]cc, which served as MicroScan's front door and was confirmed still online in September 2026.The Justice Department said MicroScan's scanning targets included a U.S. power company in South Carolina, a multinational NGO, Japanese and Polish airports, Taiwanese natural gas and power companies, and roughly twenty Taiwanese universities. FishHub, meanwhile, was used to support spear-phishing and intrusion activity against many of those same victims.
The most startling revelation, however, came in the joint advisory (AA26-281A) issued by the FBI, CISA, and NSA alongside agencies from the UK, Australia, Canada, Japan, New Zealand, and Spain: the hackers ran a web application that provided third-party access to stolen email content. In other words, compromised inboxes weren't just harvested for intelligence — they were effectively catalogued and made browsable to outside parties. The FBI also recovered an archived email database the threat actors used to track their targets.
A Multi-Year, Multi-Continent Campaign
According to the advisory, the campaign dates back to at least 2021 and relied on a hybrid of automated and hands-on techniques. The actors used automated scanners armed with more than 1,300 penetration-testing scripts to find vulnerable web applications, exploited known CVEs, and conducted password-spraying attacks against Microsoft 365 and Exchange accounts. Once inside, they deployed custom email-harvesting tools — including a PHP bot that pulled mail through Exchange Web Services — and established persistence using SoftEther VPN clients disguised as legitimate Windows processes.Observed victims of email theft spanned government organizations, law enforcement agencies, healthcare systems, and religious institutions in Southeast Asia, with additional targets across Africa, North America, and the United States itself. The activity overlaps with threat clusters tracked by security vendors under the names Flax Typhoon, Ethereal Panda, and RedJuliett.
Integrity Technology Group is no stranger to U.S. law enforcement. Treasury sanctioned the firm in January 2025 for its role in computer intrusions, and the FBI previously disrupted its Raptor Train botnet — a network of more than 200,000 compromised routers, IP cameras, and NAS devices — in September 2024. The company, which holds contracts with the Chinese government, rejected the earlier U.S. accusations as baseless. The EU added it to its own sanctions list in March 2026.
Why This Matters
The case is a vivid illustration of how Beijing increasingly outsources cyber operations to commercial contractors. As FBI Cyber Division Assistant Director Brett Leatherman noted, the Chinese government relies on such companies to expand the reach of its operations — blending for-profit revenue with state-aligned espionage.It also sends an urgent message to every organization running Microsoft Exchange or Microsoft 365: if your mail environment is internet-reachable and multi-factor authentication isn't universally enforced, you may already be a victim. The security recommendations are familiar but bear repeating — patch aggressively, enforce MFA everywhere, monitor for anomalous Exchange Web Services traffic, and assume credential attacks are ongoing.
Officials caution that the seizure is a temporary degradation, not an all-kill. The advisory explicitly warns that Integrity Tech retains the capability to rebuild its infrastructure. For defenders, that means this week's victory is a window to harden systems — not an excuse to stand down.#cybersecurity #china #fbi #espionage #software #coding #development #engineering #inclusive #community
Flax Typhoon Unmasked: Inside the FBI's Global Takedown of China's Hacking-for-Hire Empire