- ›defineProject is the new naive.config.ts entrypoint: declaring a field nothing consumes is a named refusal at define time, never a green apply that enforces nothing.
- ›
defineConfig stays forever as the lenient alias— nothing breaks, and naive up now tells lenient configs exactly what they declared that nothing reads. - ›kits: is the canonical spelling of declare-now-instantiate-later; the top-level agents: block is a deprecated alias that compiles to byte-for-byte the same output.
- ›Every team runs on the durable runtime unless you say otherwise; runtime.hermes() is an explicit, per-team (or per-member, via runsOn) choice.
- ›
The legacy surface— runtime.pool(), systems:, agentProfiles — keeps working indefinitely, with one honest deprecation line on naive up.
Infrastructure as code has one job: what the file says is what the platform does. The
naive.config.ts DSL grew fast enough that this stopped being fully true — you could
declare a field, apply green, and have nothing anywhere enforce it. Today's release
closes that gap. It is not a new surface; it is the existing one made honest.
The problem: a green apply that enforces nothing
A declarative DSL accumulates fields faster than consumers land for them. That is
normal. What is not acceptable is the failure shape it produces: you write
agent({ time: … }), the config validates, naive up applies, and the calendar bound
you think you declared is enforced nowhere — dropped before it ever reaches the
wire. Nothing fails, so nothing tells you.
We went through the DSL field by field and sorted every declarable into three bins:
- Wired — consumed and enforced.
can,limits,governance(narrowing only),brainbindings,environment,model,concurrency,maxSteps, teamreviewrubrics andspend/hardSpendwalls. - Consumed by one runtime — real on the durable lane, reported honestly on the
other. A hermes-led team declaring
agent({ model })is toldmodel (runtime.hermes)in itsunconsumedlist, not pretended into force. - Consumed by nothing yet —
agent().time,agent().secrets,agent().skills,team().time,runtime.durable({ workspace }),company.residency,modules:. These are the bin the old behavior hid.
defineProject: refusal over silence
defineProject is the new entrypoint, and the whole difference is what happens to that
third bin. Declare a field nothing consumes and the config refuses at define time —
when the file is evaluated, before anything is applied:
Error: declared_unconsumed: company.residency is declared and NOT YET ENFORCED —
nothing consumes it, so it would apply green and enforce nothing. Remove the field
(reinstate it when a consumer lands), or keep this config on defineConfig, the lenient
legacy alias.
defineConfig stays, permanently, as the lenient alias — every existing config keeps
compiling and keeps producing the same output. And lenient no longer means silent:
naive up now prints a per-team unconsumed list for everything declared that nothing
reads, so both entrypoints tell the truth; they differ only in whether the truth is a
warning or a refusal.
The list is one shared value in the codebase. Wiring a field deletes its row, which removes the refusal, the warning, and the honesty marker in the same edit — strictness can only shrink as consumers land, never lag behind them.
Kits: declare now, instantiate later
The top-level agents: block always had a slightly wrong name. It never provisioned
anyone — it declared a role, and your code stamped instances later. That concept
already has a noun on this platform: an account kit. So the block is now spelled
kits::
import { defineProject, kit, skills } from "@usenaive-sdk/iac";
export default defineProject({
project: "acme",
kits: {
sdr: kit({
instructions: "Research prospects and draft outreach for one customer.",
can: [skills.search, skills.email],
limits: { budget: "$50/mo" },
}),
},
});// your app, at run time — one governed instance per child project
const instance = await naive.forProject("acme").forChild(childId).provision("sdr");kits: and agents: are one namespace: both compile to byte-for-byte the same
canonical output, agents: gets a rename notice from naive up (a notice, never a
refusal), and the same name in both blocks is refused at define time. Agents declared
inside a team are different — they are the team's roster, materialized by naive up
itself, not instantiated later.
Durable by default, hermes by choice
A team that says nothing about its runtime runs on the durable runtime — the current lane, with governance decided in-process at the tool-call boundary. Choosing the frozen hosted-container lane is always an explicit act:
teams: {
research: team({ // no runtime: line — this is runtime.durable()
lead: agent({ instructions: "Plan the week's briefs." }),
agents: { analyst: agent({ instructions: "Write one brief." }) },
}),
support: team({
runtime: runtime.hermes(), // explicit — the frozen hosted lane
lead: agent({ instructions: "Triage the ticket and delegate." }),
agents: { tier1: agent({ instructions: "Answer the ticket." }) },
edges: [["lead", "tier1"]],
}),
}One member can be pinned elsewhere with agent({ runsOn }) — a hermes team can keep a
durable specialist. The reverse is refused at define time (a hermes agent is a profile
inside the company's single container, and a durable-led team owns no container to put
it in), and so is an edges: pair whose ends sit on different runtimes: delegation is
a shared board, the two runtimes do not share one, and a card that applies and never
arrives is exactly the silent failure this release exists to remove.
One more honesty fix while we were in there: runtime.hermes({ pool }) looked like
routing and never was. pool is optional — omitted means the company's one hosted
container — and a given name must match a pool your config's runtime: block declares
(hermes_pool_unknown otherwise). A valid name is recorded on the team's agent
rows as metadata.config_pool; it does not change which container is claimed.
Nothing breaks
The legacy surface — runtime.pool() declarations, agent-level runtime: "<pool>"
strings, systems:, agentProfiles — keeps compiling and keeps applying,
indefinitely. What it gains is one deprecation line on naive up naming exactly what
was found, and the same per-team unconsumed reporting everything else gets. When you
want the path over, the
migration guides walk it
route by route — including the two-line
defineConfig → defineProject
switch.
The full DSL reference is in
the IaC docs, and declaring teams —
runtimes, brains, governance layers, spend walls — in
the Teams docs. Rename one call,
run naive up --plan, and read what your config has been declaring all along.
What is defineProject?+
Do I have to migrate off defineConfig?+
What is the difference between kits: and agents: in the config?+
Which runtime does a team run on?+
What does pool mean on runtime.hermes()?+
Is the legacy surface going away?+
Building the agent-native backend.
Launch the Naïve Agent SDK: one interface for agent identity, 40+ primitives, scoped cards, and real-world standing — multi-tenant, with audit logs, spend limits, and per-user MCP sessions.
AI agent permissions should be enforced server-side at execution time, not prompted. Here's how policy templates gate primitives, apps, and approvals.
Hosted vs bring-your-own runtime for AI agents: Naïve is runtime-agnostic — governance applies at the tool-call boundary either way. Compare both paths.
AI workforce orchestration with a CEO agent, kanban task board, strategic objectives, cron scheduling, and persistent memory — so your agent can plan, delegate, and execute multi-step workflows without manual supervision.