home.social
  1. 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


    If you chart my week before and after, the total didn't shrink much. What changed is where the time goes:

    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


    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


    1. Start with the glue** (Stage 6). Zero risk, instant payoff, builds your trust in the tooling.
    2. Then the high-typing code** (Stage 2) — but only where you've already made the decision. Write the spec first, by hand.
    3. Split your test agent from your code agent** (Stage 3). This is the single highest-leverage structural choice in the whole setup.
    4. Keep the judgment review on a human.** Use a reviewer agent as a first pass, never as the last word.
    5. 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

  2. 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_M

    Mistral 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.dev

    Then create ~/.cloudflared/config.yml:

    tunnel: ollama-prod
    ingress:
    - hostname: ollama.yourname.workers.dev
    service: http://localhost:11434
    - service: http_status:404

    Run cloudflared tunnel run ollama-prod and 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 with curl https://ollama.yourname.workers.dev/api/generate -d '{"model": "mistral:7b-instruct-q4_K_M", "prompt": "test"}'. ## Build an Agent Worker That Routes Tasks

    Cloudflare 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-agent

    Replace src/index.js with 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 with curl -X POST https://my-ai-agent.workers.dev -H 'Content-Type: application/json' -d '{"message": "What is 2+2?"}'. ## Add Memory with Durable Objects

    Cloudflare 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 serve

    With 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:

    1. 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 to https://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

  3. 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)
  4. 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 .pem file needed, AWS handles the identity check quietly in the background. To leave, just type exit or hit Ctrl+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 (ubuntu for Ubuntu boxes, ec2-user for Amazon Linux). Then under Advanced SSH Settings, tick "Use private key" and point it at your .pem file. Hit OK and you're in. Same deal to exit — just type exit.

    One thing that trips people up on Linux/Mac if you're SSHing with the .pem file 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-keygen is just the tool, -t rsa picks the key type, and -b 4096 sets 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), and id_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 .pem file (you still need that the first time), then open vim ~/.ssh/authorized_keys on the target and paste the key in on a new line. Press i to type, Esc, then :x to 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 -y

    or, if you'd rather go through pip:

    sudo apt install python3-pip -y
    pip install ansible

    Check 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=ubuntu

    That's just saying: there's a group called targets, with one server in it, log in as ubuntu. 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: yes

    tasks:
    - name: Update apt cache
    apt:
    update_cache: yes

    - name: Install nginx
    apt:
    name: nginx
    state: present

    - name: Start nginx service
    service:
    name: nginx
    state: started

    hosts: targets says which group from the inventory to run on. become: yes means run with sudo. Each task under tasks: 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 ping test 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

  5. `#!/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 git and oci sources - there is no

    Helm 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-manager

    Chart 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.0

    TOKEN="$(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 ~/.docker

    untouched 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
    fi

    Values 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 silently

    produced 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 <<YAML

    helm 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}
    YAML

    render cert-manager cert-manager "${CERT_MANAGER_VERSION}" \
    render/values-cert-manager.yaml manifests/addons/cert-manager/cert-manager.yaml

    render cert-manager-google-cas-issuer cert-manager-google-cas-issuer \
    "${CAS_ISSUER_VERSION}" render/values-cas-issuer.yaml \
    manifests/addons/cas-issuer/cas-issuer.yaml

    echo
    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

  6. 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.com addresses, 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

  7. 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
  8. 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
  9. 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_lines is 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.1 are 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.dumps settings on every side. The proposal leaves ensure_ascii at 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


    Read left to right. The first failed key wins. Do not average a near-miss into a partial allow.

    Property IDFixture hashWindowActionEqual to the cited IDEqual to this contractUnexpired, same runner class, failure class 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 --now is already closed. Matching keys with a failure class other than environment still reject. The checker returns reject:not_environment for that case. allow_cite is not a merge approval. The named assertion still has to run.

    Five steps


    1. Put the identifier in the test module. The ledger string must be searchable in that file. A pull-request comment is not an identifier.
    2. Write a contract file from inputs the assertion actually reads. Keep schema, field names, and the seed. Omit clocks and host identity.
    3. Classify the miss before the row is eligible. Use invariant when the named property fails on the hashed contract. Use environment for timeouts, connection resets, and missing optional services. Only environment may later return allow_cite.
    4. Compare the row at a timestamp you pass in. --now is an argument, not a clock read, and not a timestamp copied from a remote log.
    5. Keep the decision string with the diff. allow_cite means the existing row may stay referenced. Any reject:* 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, timezone

    def 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_hash is 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())
    PY

    python3 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_at does 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_class by 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, invariant or environment, plus one sentence of reason. Leave that text in the scratch note. decide never 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 example free-server, into cited_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 to allow_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_cite to retain a failing invariant. The environment class is a precondition. Do not source --now from 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 decide more 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 returns reject:*. 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 decide does 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

  10. 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

Share on Mastodon

Enter the server where you have an account.