← All build notes

November 6, 2026 · 9 min read

What I require before an agent posts as me

An agent runs AIWG's social accounts this month. The rule, grant and checks I put around it, what a run looks like, and how to build the same setup yourself...

By Joseph Magly

This month an agent runs AIWG's own social accounts. It prepares the posts, marks them approved and publishes them to AIWG's accounts on X, LinkedIn, Telegram and Discord, including the Friday pieces like this one that go out in my voice.

There are two decisions in letting an agent post for you, and I keep them apart. One is whether the writing is good enough. The other is whether the agent may speak as you at all: where, under what conditions, and with what way to stop it. The first one gets most of the discussion when people talk about agents writing for them. The second is the one that can cost you, because a published post is hard to take back and it carries your name whether or not you read it first.

So I treat it as a permission problem first and a voice problem second. Below is the setup I actually run, with the config and prompts, and then how you can build the same thing with what ships in AIWG.

Everything is prompts and files

AIWG is prompt-first. You ask your coding assistant for what you want, and AIWG supplies the rules, skills and config the assistant reads while it works. That is also why this setup can be inspected. The authority an agent has is written in files I can read, diff and revert, and the agent reads the same files before it acts.

My publishing setup is a project-local AIWG extension in my own workspace, and it does not ship in AIWG. It is four ordinary files working together: a rule the agent loads before any publishing work, a grant in config that names the exact destinations it may approve for itself, an agent definition that limits which tools the publisher can use, and a run prompt that the scheduler hands to the agent one post at a time.

The rule comes before the grant

My default rule for publishing is strict, and it is the first thing in the rule file. Paraphrased:

---
enforcement: high
---

# Social publication safety

0. Everything published requires Joe's explicit approval of the exact text
   before it goes out. Any image the agents generate requires approval of the
   exact image. Any edit after approval needs re-approval. Browser login,
   schedule and queue status are not publication authority.

   Standing authority: destinations listed under an authorized grant in
   config/standing-authority.json are approved at all times. Every other
   check in this file still applies.

The second paragraph is what connects the rule to the grant. Without it, an agent reading the rule would see only "everything needs Joe" and would have no way to learn there is an exception, or where it is written down.

The grant itself lives in config. It records my instruction in my own words, and then turns it into something a validator can check: which agent may use it, and which destinations it covers.

{
  "schema": "social-standing-authority/v1",
  "grants": [
    {
      "id": "standing-aiwg",
      "status": "authorized",
      "authorized_by": "Joseph Magly",
      "authorized_at": "2026-11-02T15:20:00-05:00",
      "expires_at": null,
      "agent": "AIWG conductor agent",
      "covers_generated_images": true,
      "destinations": [
        { "platform": "x", "account": "@AIWGio" },
        { "platform": "linkedin", "account": "AIWG.io" },
        { "platform": "telegram", "account": "AIWG group" },
        { "platform": "discord", "account": "AIWG #announcements" }
      ]
    }
  ]
}

I've trimmed this. The real file also records my instruction verbatim and where the decision was made, and for LinkedIn, Telegram and Discord it carries the operator account plus the stable page, chat and channel IDs where I've shown names here. Those three route by ID, because a title can be renamed and an ID cannot. X routes by handle.

The grant has no expiry. These are the project's own accounts and the arrangement runs for the month, so an expiry would only make me renew it. If the accounts or the agent changed, I would add one.

For those four destinations the agent may mark a post approved itself, and the record has to say which grant it relied on:

"approval": {
  "status": "approved-for-publish",
  "authority": "standing-aiwg",
  "approved_by": "standing:standing-aiwg",
  "approved_at": "2026-11-03T01:01:19.800Z"
}

A validator checks that claim against the grant before anything runs. An unknown grant, a withdrawn grant or a destination not on the list is rejected. Every other account still needs my approval of each post, including my personal feeds, other projects' accounts and Reddit.

A blanket "you can post" would have been shorter to write. It would also reach every account the browser happens to be signed into. Naming the destinations keeps the grant to the places I chose, and the rule stays the default everywhere else.

Checks that run under the grant

A grant says the agent may post. It does not say this post is safe to send right now, so every check that ran before the grant still runs.

The first is identity. Immediately before composing, the agent confirms the account visibly active in the browser is the one the record names. On a mismatch it stops, unless the record's handle is one I authorized for switching and the platform offers that already signed-in account, in which case it switches and checks the profile link again. It never adds an account or recovers a login. Being signed in is not evidence of being signed in as the right account.

Then it searches the destination for the post's link and distinctive copy, because the dangerous case is a retry. If the post is already there, it records the existing post instead of sending a second one. Each record carries a stable idempotency key, and a retry resumes at the first step that was not verified.

The content itself does not move once approved. Images are tracked by checksum, the text in the composer is read back and compared exactly with the approved copy, and any change resets approval. A link counts as verified only when the platform's short link redirects to the approved address; a card preview with the right title is not enough. Each campaign also keeps a verified-claims file that traces every product claim to its source, and the copy is checked against it before approval.

Over all of it sits a stop. A message from me, a flag file on the machine, or a label on the month's tracking issue pauses every outward action: posts, tags, package publishes, pushes and tracker writes. The pause is checked before each outward action, not once at the start of a run, and a missing pause check counts as a stop rather than a pass.

What a run looks like

The scheduler starts one run per due post. It hands the agent the run prompt, which names the files to read and the checks to make, followed by a small job block. Trimmed:

Process exactly one record from the named dataset.
Validate the dataset, attachment checksum, public article URL, platform
identity, exact destination, copy, approval and idempotency key.
In publish mode, require approved-for-publish, search the exact destination
for a prior match, publish the exact record, and verify the public URL.
Return one JSON object matching the configured output schema.

<job>
mode: publish
dataset: aiwg-joe-agent-posts-as-me-2026-11
record_id: <record id>
issue_url: <tracking issue URL>
</job>

Here is a simulated response for a retried run. It is illustrative, written to show the shape, not copied from a log, and the JSON is trimmed to the fields that carry the story:

Checked: dataset valid; image checksum matches; article URL returns 200;
approval approved-for-publish under standing-aiwg, destination listed.
Identity: visible account is @AIWGio, matches the record.
Duplicate search: found a post on @AIWGio with this record's opening line,
published 4 minutes ago by the earlier attempt that timed out before
recording its result. Not posting again.

{"status": "already-published", "mode": "publish", "platform": "x",
 "account": "@AIWGio", "public_url": "<existing post URL>",
 "summary": "Duplicate search found the earlier post; none sent."}

A retry after a timeout is the case this design exists for. Without the duplicate search, that run would have posted twice under AIWG's name.

Building your own

You do not need my extension to get the same shape. AIWG ships the pieces, and you can ask your assistant for each one in plain language. I would build them in the same order I did, default first, because the exception only means something once the default is written down.

Start with the rule:

Scaffold a project-local extension called publish-rules with a rule starter. The rule: never publish, post or send anything without my approval of the exact final text.

That runs aiwg new-bundle publish-rules --type extension --starter rule. You edit the rule file, then deploy it with aiwg use publish-rules. A rule file is markdown with a short header. AIWG's rule schema expects an activation field, which my older rule above predates:

---
activation: summary
summary: Never publish without the exact final text approved in writing by the owner.
---

# publish-exact-text-approval

Before any publish, post or send, show the exact final text and wait for
explicit approval of that text. An edit after approval needs approval again.

A rule is prompt-level guidance. It shapes what the assistant does, and it does not by itself stop a browser from clicking Post. That is why the checks above run in scripts as well as in the rule.

Next, decide what routine work you are willing to have done without being asked. AIWG's approval profiles let you write narrow, expiring grants in your project config. This one allows docs edits and test runs in one repository, locally, for a day; adjust the dates and names before you use it:

{
  "version": "1",
  "enabled": true,
  "reviewedAt": "2026-11-06T12:00:00Z",
  "maxAgeSeconds": 86400,
  "expiresAt": "2026-11-07T12:00:00Z",
  "deny": [],
  "requireReview": [],
  "profiles": {
    "docs-and-tests": {
      "categories": ["docs.edit", "test.run"],
      "repos": ["example-org/example-repo"],
      "environments": ["local"],
      "resources": ["docs/**", "test/**"],
      "reversible": true,
      "maxImpact": "low",
      "expiresAt": "2026-11-07T12:00:00Z"
    }
  }
}

Enable it with aiwg config authorization enable --policy-file hitl-policy.json, then try an action against it with aiwg config authorization preview --action-file action.json; preview writes nothing. When an action matches, a workflow that consults the policy skips its own confirmation and writes a signed receipt. Your provider's own permission prompts still apply. The grantable categories are repository reads and edits, docs edits, test runs and testnet configuration. Secrets, destructive changes, production and policy overrides can never be approved in advance, and publishing is not a category at all. So a post still comes to you, or to a grant you wrote for it yourself, the way I did.

Then schedule the run. I run mine from a small script on a user timer. aiwg job is the shipped version of the same shape: a reviewed contract that lists the sites, accounts and attachment folders a run may use, and requires an approval label by default. Today it runs Codex in exec mode against work items in a Gitea repository, so check that fits before you build on it:

apiVersion: jobs.aiwg.io/v1
kind: ExternalJob
metadata:
  name: publish-approved-item
  revision: "1"
spec:
  trigger: { type: external }
  executor:
    provider: codex
    mode: exec
    workspace: /srv/aiwg/publisher
    prompt: prompts/publish.md
    resultSchema: schemas/job-result.json
    binary: /usr/local/bin/codex
  workItem:
    provider: gitea
    baseUrl: https://git.example.test
    repository: team/publication-queue
    tokenFile: /run/credentials/aiwg-gitea-token
    eligibleLabels: [publication-job]
  approval: { required: true, label: approved-for-publish }
  security:
    allowedOrigins: [https://social.example.test]
    allowedAccounts: [brand-main]
    approvedAttachmentRoots: [/srv/aiwg/publisher/approved-assets]
  completion:
    require: [external-result-url, issue-comment, idempotency-key, verification]

prompts/publish.md is where a run prompt like mine goes. Check the contract with aiwg job validate jobs/publish.yaml, render a timer for your own scheduler with aiwg job render-cron jobs/publish.yaml --format systemd (or cron, or gitea-actions), and run it once with aiwg job run jobs/publish.yaml --once. AIWG has no resident scheduler; your system owns the clock. Each run gets a stable idempotency key from the job name, revision and work item, and a result is accepted only when its URL, account and verification evidence match the contract. After an uncertain failure the same revision does not run again on its own, and you bump revision when a new attempt is safe. AIWG checks those declared boundaries. Your browser and the platform still enforce their own account permissions.

Giving the agent your voice

Voice is the second decision, and I keep it in different files from authority on purpose. A voice file says how something should sound. It never says the thing may be published, and if the two lived in one file it would be too easy for an edit to one to change the other.

AIWG describes an identity in a SOUL.md at the root of your project: who the persona is, what it believes, its specific opinions, its standard for done, its vocabulary and what it refuses to do. You can build one from your own writing:

Build a soul from these blog posts: ./samples/*.md

The soul-create skill reads the samples, pulls out recurring beliefs, opinions, vocabulary and contradictions, and writes the file. A short excerpt of the kind of thing it produces:

## Worldview
- Systems that ship and stay maintainable beat clever ones; cleverness that
  adds operating cost is a tax.

## Standards
**Bar for completion**: done means verified, not "should work".
**Phrases I avoid**: "good enough", "we can iterate"

## Boundaries
- I do not claim more than the evidence supports.

Ask your assistant to "check the soul" and it scores the file and points at sections that are too vague to predict anything from. Enable it with soul-enable so the assistant loads it into context, and from then on you can ask for work in that identity:

Draft Friday's post from these notes using SOUL.md. Don't change any facts, and keep my first person and my qualifiers.

The soul governs voice and identity, never facts. When staying in character and being accurate conflict, accuracy wins, which is the property that makes it reasonable to hand to an agent at all.

For tone without a full identity, a voice profile is a small YAML file in .aiwg/voices/:

name: internal-engineering-docs
version: 1.0.0
description: Casual but technically precise engineering documentation.
tone:
  formality: 0.5
  confidence: 0.8
  warmth: 0.4
  energy: 0.4
  complexity: 0.7

Then ask for "rewrite this in the internal-engineering-docs voice". You can turn a voice profile into a soul, or a soul into a voice profile, with voice-to-soul and soul-to-voice.

I don't use a SOUL for the pieces in my name. I use a reviewed writer profile built from my own writing and a short voice contract next to it, which is the same idea with a stricter edit policy. The contract is the part I would copy. Written generically, its shape is:

# Voice contract

May change: spelling, punctuation, local grammar, register for the channel.
Must keep: facts and conditions, the order of my reasoning, first person,
  qualifiers like "usually" and "in my experience", code and identifiers.
Remove: sentences that announce what the next sentence is for, the same
  labelled slots repeated under every item, claims that something is
  important instead of showing the situation where it changes what you do.

I wrote mine because avoiding familiar AI phrases was not enough on its own. An early round of edits did that and still read like a model to me. The round I accepted kept my clause order, connectives and paragraph progression, and only then made local fixes.

Reading what it did

Spending stays outside all of this. Every spend still needs my yes, including moving work to a more expensive model, and escalating something to me does not block the rest of the work.

Then there is the report. Every Monday I get a digest with releases, issues opened and closed, posts published and their early engagement, the spend ledger, risks and open escalations. The grant, the checks and the stop decide what the agent may do. The digest is how I find out what it actually did, and whether the grant still covers what I meant it to.