A New AI Agent Oriented Programming Language Grenat, a new compiled programming language for building AI agent systems, treats prompts, tools, supervised actor agents, budgets and durable workflows as first-class language constructs, with an effect system that turns prompt injection into a compile-time error (E0412). The language's specification is published in SPEC.md alongside an llms.txt reference for LLMs writing Grenat and example programs including support_desk.grn (multi-agent with human approval) and triage.grn (tests with mocks, evals with an LLM judge). Grenat marks model output as untrusted (~T), requiring it to be checked, human-approved or explicitly trusted before reaching the network, a file, a command, an email or a page, and journals each workflow step so runs resume after a crash without billing a model call twice. Ruby's syntax, Rust's speed, agents as first-class citizens. Grenat is a compiled programming language for building AI agent systems: typed prompts, tools, supervised actor agents, budgets, durable workflows, and an effect system that turns prompt injection into a compile-time error . php prompt summarize article: String - ~Summary using :fast user "Summarize: {article}" end agent Researcher model :smart tools search web, read url budget usd: 2.00, time: 10.min on Research topic: String - ~Report run "Investigate {topic}" end end - Specification: SPEC.md https://github.com/itsmedit/grenat/blob/main/SPEC.md - A compact reference for LLMs writing Grenat: llms.txt https://github.com/itsmedit/grenat/blob/main/llms.txt - Examples: basics.grn https://github.com/itsmedit/grenat/blob/main/examples/basics.grn , reviews.grn https://github.com/itsmedit/grenat/blob/main/examples/reviews.grn native statistics + validated LLM analysis , explorer.grn https://github.com/itsmedit/grenat/blob/main/examples/explorer.grn a real agent , support desk.grn https://github.com/itsmedit/grenat/blob/main/examples/support desk.grn multi-agent, human approval , triage.grn https://github.com/itsmedit/grenat/blob/main/examples/triage.grn tests with mocks, evals with an LLM judge , macros.grn https://github.com/itsmedit/grenat/blob/main/examples/macros.grn compile-time code generation , usecases/ https://github.com/itsmedit/grenat/blob/main/examples/usecases twelve agent use cases: support, code review, research, data, documents, a weekly digest, operations, a chat with memory, a team of agents, MCP tools, a knowledge base searched by meaning, meeting minutes from a recording Every snippet below passes grenat check , grenat test and grenat fmt --check . A prompt is a function a model implements. Its return type becomes a JSON schema, and the comments describe the fields to the model. A model's answer is untrusted ~T : it must be checked, approved by a human, or explicitly trusted before it reaches the network, a file, a command, an email or a page — otherwise grenat check fails E0412 . struct Summary title: String 8 words at most bullets: Array String 3 to 5 key points end Summarizes an article. prompt summarize article: String - ~Summary using :fast user "Summarize:\n {article}" end def headline article: String - String uses llm summarize article .check { |s| s.bullets.size.between? 3, 5 }?.title end A tool is a function a model may call; an agent is an actor whose run loop calls the model and its tools until it produces the handler's return type, within a budget. Effects uses … are capabilities checked by the compiler, then again at run time. php Reads a page of the handbook. tool read page name: String - String uses fs.read "./handbook" File.read "./handbook/ {name}" end Opens a ticket. A human approves it first. tool open ticket title: String - Int uses net "tracker.acme.io" , human, env approve "Open “ {title}”?" token = Credentials.fetch :tracker, :token a Secret: never printed, never sent to a model Http.post "https://tracker.acme.io/tickets", json: {title:}, headers: {"Authorization" = "Bearer {token}"}, .status end agent Support model :smart tools read page, open ticket budget usd: 0.50, time: 2.min max turns 12 instructions "Answer from the handbook only. Open a ticket for bugs." on Ask question: String - ~String run "Customer question: {question}" end end def answer question: String - ~String uses llm, fs.read "./handbook" , net "tracker.acme.io" , human, env spawn Support .ask Ask question: end Each step of a workflow is journaled: after a crash, or a human answering days later, the run resumes where it stopped and no model call is billed twice. php def recent releases repo: String - Array String uses net "api.github.com" , env, time token = Credentials.fetch :github, :token res = Http.get "https://api.github.com/repos/ {repo}/releases", headers: {"Authorization" = "Bearer {token}"}, week ago = Time.now - 7.days res.json.trust .select { |r| Time.parse r "published at" week ago }.map { |r| r "tag name" } end workflow weekly digest monday: String uses llm, net "api.github.com" , net "smtp.acme.io" , env, human, time tags = step :fetch { recent releases "rust-lang/rust" } digest = step :summarize { summarize tags.join ", " .trust } step :review { approve "Send “ {digest.title}”?" } step :email do Mail.connect Credentials.fetch :smtp, :url .send from: "bot@acme.io", to: "team@acme.io" , subject: digest.title, body: digest.bullets.join "\n" , end end every cron: "0 8 MON" do UTC, run by grenat serve weekly digest Time.today end Answers a customer, three sentences at most. prompt reply question: String - ~String using :fast user question end get "/chat" do |req| stream do |out| Server-Sent Events, as the model writes reply req.params "q" { |chunk| out << chunk } end end grenat serve reads a mailbox — Gmail, Microsoft 365, any IMAP server — and hands each new email to its handler, then marks it seen or moves it; every field of it is untrusted. on email Credentials.fetch :support, :imap url , every: 1.minute, move to: "Done" do |email| answer = reply email.text .check { |a| a.size < 2000 }? puts " {email.attachments.size} attachments, answer ready: {answer.size} characters" end struct Passage table :passages id: Int? text: String embedding: Vector 1024 end migration "001 create passages" do |db| db.migrate "CREATE TABLE passages id {db.primary key}, text TEXT NOT NULL, embedding {db.vector 1024 } NOT NULL " end def index parts: Array String uses llm, db vectors = embed :docs, parts one request for many texts parts.each with index { |text, i| Passage.create text:, embedding: vectors i } end def search question: String - Array Passage uses llm, db.read Passage.nearest :embedding, embed :docs, question , limit: 3 pgvector, or brute force on SQLite end php def restart server: SshSession - Bool uses ssh "api.acme.com" server.run "systemctl", "restart", "shop" .ok? an argument vector: no shell injection end def main uses ssh "api.acme.com" , env server = Ssh.connect "deploy@api.acme.com", key: Credentials.fetch :deploy, :ssh key puts restart server end test "only this week's releases" do freeze time "2026-10-05T08:00:00Z" do mock http "GET https://api.github.com/repos/rust-lang/rust/releases", json: {tag name: "1.95.0", published at: "2026-10-01T10:00:00Z"}, {tag name: "1.94.0", published at: "2026-08-20T10:00:00Z"}, assert equal "1.95.0" , recent releases "rust-lang/rust" assert equal "Bearer test-github-token", Http.requests.last "headers" "Authorization" end end test "the agent reads the handbook, then answers" do File.write "./handbook/refunds.md", "Refunds: within 30 days." mock :smart, replies: call :read page, name: "refunds.md" , "Refunds are possible within 30 days.", assert equal "Refunds are possible within 30 days.", answer "Can I get a refund?" .trust end test "a model answer out of bounds is refused" do mock :fast, replies: {title: "Rust 2.0", bullets: "only one" } assert raises CheckError do headline "…" end end test "the chat streams its answer" do mock :fast, replies: "Hello, Ada" assert equal "Hello, Ada", request :get, "/chat?q=hi" "events" .first "data" end test "the passage about refunds is found" do mock embed :docs vectors made from the texts' words index "A refund is asked for within 30 days.", "Invoices export to CSV." assert equal "A refund is asked for within 30 days.", search "How do I get a refund?" .first&.text end Other doubles: mock shell , mock ssh , mock mcp , mock transcribe , mock env , mock mail raise: "SMTP down" , mock credentials , cassette real calls recorded once, then replayed , with human approve all | deny all , deliver webhook , Jobs.perform , Mail.deliveries . config/models.yml — the first model is the default one fast: provider: anthropic anthropic, openai, gemini, mistral, xai, openrouter, groq, deepseek, together, ollama name: claude-haiku-4-5 smart: provider: openai name: gpt-5 docs: provider: voyage embeddings: voyage, openai, gemini, mistral, ollama name: voyage-3.5 kind: embedding dimensions: 1024 grenat credentials edit config/credentials.yml.enc, AES-256-GCM, key in config/master.key grenat credentials edit --env production one per environment, chosen by GRENAT ENV Keys are read from the credentials openai: {api key: …} , else from the provider's variable OPENAI API KEY … . Anthropic agents use prompt caching by default cache: true extends it to prompts and conversations . grenat new