There is a tempting version of band automation where an AI agent gets Gmail, Calendar, social media and a vague objective such as “get us more shows”.

I built almost the opposite.

The current CrowdRelay Autopilot is deliberately deterministic. It does not need a personality, a browser session or permission to improvise. It needs good facts, explicit authority and a receipt after every side effect.

Seven programs, one operations plane

Internally the code is split into bounded contexts, but the team does not need to think in crate names. The useful product view is seven programs: release, fan growth, press, opportunities, commerce, patronage/endorsement and funding.

Each program reads facts already owned by CrowdRelay and produces a domain decision. The decision then passes through a shared authority gate.

A context can observe, recommend, require approval or execute inside bounded policy. Confidence thresholds and daily quotas are explicit. A global kill switch can stop autonomous execution without deleting the facts that produced the decision.

Bounded auto is not “the bot felt confident”

The phrase “autonomous agent” often hides the most important question: autonomous to do what?

ViryaOS makes that question concrete.

A reversible calendar upsert is different from signing a contract. A free, high-confidence and non-exclusive application is different from paying an entry fee. A first-party fan reminder is different from cold outreach to an unverified address.

The domain that owns the action also owns the limits. The shared autonomy vocabulary does not know ticket economics, outreach cooldowns or funding rules; it only applies the authority and confidence gate after the domain has made its decision.

That keeps reuse small and useful instead of building a universal policy engine that knows everything badly.

n8n became replaceable

Provider execution is intentionally outside the decision core.

CrowdRelay may ask for calendar.upsert, outreach.send or funding.package. The executor binds that capability to a real provider and reports accepted, executing, succeeded or failed.

If Gmail is replaced one day, the outreach domain should not care. If Discord disappears, “notify the operator” should not become a new business rule.

The executor also advertises which capabilities it actually has. A release manifest and heartbeat prevent the backend from assuming a workflow exists simply because it existed last week.

Closed loop means provider-confirmed facts

A queued job is not an email sent. An HTTP 200 from an internal bridge is not a provider outcome. A draft is not outreach. A checkout redirect is not payment.

ViryaOS keeps those distinctions all the way through.

decision→ authority→ action→ execution claim→ provider→ receipt→ measurement→ next policy decision

For provider operations without native idempotency, a durable pre-send claim prevents a retry from accidentally duplicating the side effect. Provider references such as Gmail thread IDs come back into the execution ledger so inbound events can correlate to the original action without n8n owning a second map.

The UI should show exceptions, not machinery

A strong automation system can still fail as a product if the team has to stare at its internals all day.

Virya Signal therefore treats ViryaOS as a Chief of Staff. It surfaces the things that need a person: approvals, operational failures, measured regressions, show readiness and deadlines.

The Deadline Radar is a good example. Approval expiry and opportunity deadlines already existed in authoritative tables. Instead of creating a new tasks service, Signal gets one read model that turns those dates into an ordered attention list.

No duplicate state. No polling loop just to feel “real time”. No second place where a deadline can disagree with the first.

More automation can mean fewer systems

The surprising part of this project is that making the autopilot do more did not require making the architecture more agentic.

It required deleting ambiguity about ownership.

CrowdRelay owns durable state and decisions. PostgreSQL owns persistence. n8n and providers execute. Signal presents exceptions. AI may enrich language, but it does not get authority. OpenAPI and signed event contracts are the compatibility boundary; internal Rust modules remain implementation details.

That is the version of an autopilot I trust around a real band.

Not because it can do everything.

Because it knows exactly what it is allowed to do, remembers what actually happened and bothers a human only when the next decision is genuinely human.

The control plane now includes delivery health

Push notifications are no longer a hidden transport detail. CrowdRelay exposes the same operational queue shape for push as it does for the outbox and provider deliveries: pending, in-flight, failed, delivered in the last 24 hours and age of the oldest pending item. Those numbers also exist as Prometheus metrics.

The Virya web control plane and Virya Signal operator view consume that state instead of inventing a second health model. The public push configuration separately reports whether Android FCM and Web Push are actually configured, while push_delivery_enabled remains an explicit kill switch.

That sounds like UI plumbing, but it closes an important loop: a communication capability is not “working” merely because an endpoint accepted a subscription. Operators can now see whether delivery is moving.

Refactoring without moving the boundaries

The codebase also got less monolithic without making the architecture more abstract. Large Rust orchestration modules were split into narrow implementation chunks, while the dependency direction stayed unchanged: pure domain policy works on typed objects, application code builds decisions and coordinates ports, infrastructure owns SQL and provider adapters, and API/worker layers expose or execute the use cases.

A source-size ratchet now keeps production Rust modules below the agreed ceiling instead of relying on discipline after the next feature lands. The point is not small files for their own sake. It is making it difficult for persistence, transport and policy to quietly grow back into one controller.