Imported from robbieplata/agentic-c-slopository (
AGENTS.md). Install upstream withnpx skills add robbieplata/agentic-c-slopository. Copyright stays with the author.
AGENTS.md: Agentic-C language reference
Every .c and .h file in this repository uses Agentic-C: standard C99/C11
plus the vocabulary in ac.h. There is no transpiler; run
./unfold.sh path/to/file.c to inspect the preprocessor expansion.
This is the single source of truth. Modern coding agents (Cursor, Codex, Claude Code, Cline, Aider, Continue, Windsurf, …) auto-discover this file. Tools that look for their own filename can be wired up with a one-line symlink (see the root README.md). There are no other agent-config files in this repo; do not duplicate vocabulary anywhere else.
Read this file end-to-end before emitting code.
1. What Agentic-C is, and why
Agentic-C reduces LLM token usage while keeping standard C semantics. Macro
names target single-token encoding under o200k_base (GPT-4o / GPT-5). Source
layout matters too: C does not require newline characters (semicolons and
braces suffice), and each \n is often its own token. Emit compact,
newline-free source unless a tool explicitly requires otherwise.
Legacy names (prln, clamp, defer_free, …) remain as compat aliases unless
AC_NO_COMPAT is set.
Examples:
int main(void)→entry0() { ... }int main(int argc, char **argv)→entry() { ... }typedef struct Foo Foo; struct Foo { ... };→st(Foo) { ... };for (size_t i = 0; i < n; ++i)→r(i, n)for (const char *p = s; *p; ++p)→ch(p, s)(read-onlycstr)(strcmp(a, b) == 0)→eq(a, b)strstr(s, sub) != NULL→has(s, sub)bs[b/32] |= (1u << (b%32))→set(bs[b/32], bit(b%32))- hand-rolled string copy →
dup(s) - 12 lines of file-read boilerplate →
slurp(path)
Legacy UPPERCASE names (MAIN, ST, R, …) remain as aliases unless
AC_NO_LEGACY is defined before #include "ac.h".
2. The six structural rules
- Function-like macros are preferred. They only expand when followed by
(. - Lowercase identifiers are function-like only, with two exceptions:
the sanctioned type-alias typedefs (
u8…cstr), and the cleanup-attribute macros (df,dc, and their compat aliasesdefer_free,defer_close) which are object-like by necessity: they sit after a variable name in a declaration. - No UPPERCASE macro names in new code (legacy aliases only).
- No single-character keyword aliases (
f=int,r=return, …). - Every macro is killable via
#define AC_NO_<name>before include, or#define AC_MINfor types +entry()only. - Macro names target o200k_base. Primary symbols should encode as one BPE
token on o200k. Type aliases (
u32, …) may be two tokens but still beatuint32_t. Runmake tokensbefore adding a macro.
Do not name a macro main(): its expansion contains the token main( and
re-triggers on rescan. Use entry() / entry0() instead.
3. Complete macro vocabulary
3.1 Type aliases
| Macro | Expands to |
|---|---|
u8 u16 u32 u64 |
uint8_t … uint64_t |
i8 i16 i32 i64 |
int8_t … int64_t |
f32 f64 |
float / double |
b8 b32 |
uint8_t / uint32_t (boolean storage) |
usz isz |
size_t / ptrdiff_t |
cstr |
const char * |
3.2 Declarations
| Macro | Expands to | Example |
|---|---|---|
entry() |
int main(int argc, char **argv) |
entry() { ... } |
entry0() |
int main(void) |
entry0() { ... } |
st(Name) |
typedef struct Name Name; struct Name |
st(Point) { i32 x, y; }; |
en(Name) |
typedef enum Name Name; enum Name |
en(Color) { RED, GREEN }; |
un(Name) |
typedef union Name Name; union Name |
un(Word) { u32 u; f32 f; }; |
fn(name, R, ...) |
typedef R (*name)(...) |
fn(cmp, i32, cstr, cstr); |
fwd(Name) |
typedef struct Name Name |
fwd(Node); |
inl() |
static inline |
inl() u32 add(u32 a, u32 b) { ... } |
Prefer entry0() when argc/argv are unused.
3.3 Universal helpers
| Macro | Example |
|---|---|
min(a,b) max(a,b) clam(x,lo,hi) |
i32 m = min(a, b); |
len(a) |
r(i, len(xs)) ... |
abv(x) |
absolute value (not abs; stdlib collision) |
sign(x) |
-1, 0, or 1 |
3.4 Control flow
| Macro | Example |
|---|---|
r(i,n) |
r(i, 10) sum += xs[i]; |
rs(i,a,b) |
rs(i, 1, 31) ... |
rr(i,n) |
reverse range |
loop() |
loop() { if (done) break; } |
each(p,arr) |
iterate stack array by pointer |
ll(p,head) |
linked list with next field |
lln(p,head,fld) |
linked list with custom link field |
ch(p,s) |
iterate cstr by pointer (read-only) |
unless(c) |
if (!(c)) |
swap(a,b) |
typeof-safe swap |
chk(p,code) |
early return code on falsy p |
For mutable string iteration use for (char *p = s; *p; ++p); ch binds cstr.
3.5 I/O
| Macro | Example |
|---|---|
pr(...) pn(...) say(s) |
formatted output (printf, printf + newline, puts) |
ep(...) die(...) |
stderr + optional exit |
slurp(path) |
read whole file (malloc'd, NUL-terminated) |
spit(path,buf,n) |
write whole file |
3.6 Memory
| Macro | Example |
|---|---|
new(T) arr(T,n) |
calloc wrappers |
del(p) |
free(p); p = NULL; |
must(p) |
abort if falsy |
zero(p) |
memset one object |
grow(p,n) |
realloc to n elements |
3.7 Resource cleanup (GCC/Clang)
| Macro | Example |
|---|---|
df |
char *buf df = slurp(path); |
dc |
FILE *f dc = fopen(path, "rb"); |
3.8 Strings
| Macro | Example |
|---|---|
eq(a,b) neq(a,b) |
string compare |
has(s,sub) |
(strstr(s, sub) != NULL) |
dup(s) |
malloc'd copy of NUL-string |
3.9 Bit manipulation
| Macro | Example |
|---|---|
bit(n) |
(1u << (n)) |
btst(x,b) |
test bit(s) in word |
set(x,b) clr(x,b) tgl(x,b) |
modify bits |
3.10 Hashing
| Macro | Example |
|---|---|
fnv(p,n) fnvs(s) |
FNV-1a 64-bit hash |
3.11 C11
| Macro | Example |
|---|---|
sas(c,msg) |
_Static_assert(c, msg) |
4. Style guide
Always
#include "ac.h"at the top of every Agentic-C file.- Use
entry0()orentry(), never hand-writeint main(...). - Use type aliases (
u32,cstr,usz, …). - Use
st/en/unfor every struct/enum/union. - Use
r/rs/rr/each/ch/llinstead of equivalentforloops. - Use
pn/say/ep/diefor output. - Use
new/arr/del/mustfor owning pointers. - Use
eq/neq/has/dupfor string idioms. - Use
bit/btst/set/clr/tglfor bit ops. - Use
slurp/spitfor whole-file I/O. - Use
df/dcunder GCC/Clang. - Use
chkfor early-return-on-error;mustfor invariants. - Emit newline-free source: one logical line per declaration or block when
possible; rely on
;and{}instead of line breaks.
Never
- Never insert
\ncharacters in emitted C source: no blank lines, no statement-per-line formatting. (Markdown fences in this file may use newlines for human reading; agent output should not.) - Never write a
forloop equivalent tor,rs,rr,each,ch, orll. - Never write
strcmp(...) == 0; useeq. - Never write the fopen/fseek/ftell/fread chain; use
slurp. - Never use UPPERCASE macros (
MAIN,ST,R, …) in new code. - Never
#undefan Agentic-C macro without#pragma push_macro/pop_macro. - Never name a function
set,bit,eq,has,dup,r, etc. - Never add a lowercase object-like macro (except type aliases).
- Never add single-char keyword aliases.
5. Anti-patterns
5.1 Naming a function after a macro
void set(i32 x); // wrong: set( triggers macro expansion
i32 set = 0; // ok: not followed by (
5.2 Side-effecting macro arguments
A handful of macros evaluate an argument more than once. Passing an expression
with side effects (i++, a function call, etc.) to one of these is wrong.
Side-effect unsafe (read each arg into a local first):
swap(a, b):aandbeach evaluated twice.each(p, arr):arrre-evaluated on every iteration.grow(p, n):pevaluated twice.del(p):pevaluated twice (free(p); p = NULL).r(i, n),rs(i, a, b): loop bound re-evaluated each iteration.
Everything else (min, max, clam, abv, sign, bit, btst, set,
clr, tgl, eq, neq, has, dup, len, rr, ll, lln, ch,
unless, chk, must, zero, new, arr, pr/pn/say/ep/die,
slurp/spit, fnv/fnvs, sas) evaluates each argument exactly once.
5.3 ch on mutable buffers
char buf[64];
ch(p, buf) *p = 'x'; // wrong: cstr is const
for (char *p = buf; *p; ++p) *p = 'x'; // ok
5.4 en(Name) under strict ISO
typedef enum X X; is a GCC/Clang extension. Use -DAC_NO_en on strict compilers.
5.5 chk outside a function
chk(p, code) expands to return code; use must(p) elsewhere.
6. Language coverage
Use plain C when no shorter Agentic-C idiom exists.
| Vanilla C | Agentic-C |
|---|---|
int main(void) |
entry0() { } |
int main(int, char**) |
entry() { } |
| struct + typedef boilerplate | st(X) { }; |
| enum + typedef boilerplate | en(X) { }; |
| union + typedef boilerplate | un(X) { }; |
for (size_t i=0; i<n; ++i) |
r(i, n) |
| linked-list for-loop | ll(p, head) |
| string char loop (const) | ch(p, s) |
strcmp(a,b)==0 |
eq(a, b) |
strstr(s,sub)!=NULL |
has(s, sub) |
| hand-rolled strdup | dup(s) |
| fopen/fseek/ftell/fread chain | slurp(path) |
switch / case / break |
plain C (1 token each on o200k) |
pointers, casts, sizeof |
plain C |
| function definitions | plain C |
| complex loop conditions | plain C |
7. Kill-switches
#define AC_NO_r // disable single macro
#define AC_NO_TYPES // disable all type aliases (u8 … cstr)
#define AC_NO_LEGACY // disable UPPERCASE aliases
#define AC_NO_COMPAT // disable pre-o200k aliases (prln, clamp, …)
#define AC_MIN // types + entry() only
#include "ac.h"
User definitions win: every macro is guarded with !defined(<name>).
8. Collision discipline
- Lowercase macros are function-like;
int set = 0;ands.setare safe. - Type aliases are typedefs, not
#define. - Every macro has
AC_NO_<name>and!defined(<name>)guards. make collisionsprependsac.hto the files intests/collisions/(locals named after macros, struct fields named after macros) and compiles.
9. Runtime performance
Agentic-C is zero-overhead vs equivalent vanilla C:
- Pure macros expand to identical statements; optimizer sees the same code.
- Paved-path helpers (
slurp,spit,dup,fnv,fnvs) areAC_INL(always_inlineon GCC/Clang). Onlydf/dccallbacks stay outline (required by the cleanup attribute). make perffails CI if Agentic-C is more than 5% slower than vanilla at-O2.
10. Unfold escape hatch
./unfold.sh path/to/file.c
Runs cc -E, filters to the user's lines, optionally pretty-prints with
clang-format. Verifies any program collapses back to plain C.
11. Dev gates
All CI lives under tests/. Local commands:
make ... |
Purpose |
|---|---|
test |
Full suite (rules, tokens, coverage, bench, perf, collisions) |
rules |
AGENTS.md lists every public macro in ac.h (no drift) |
tokens |
o200k single-token macro name check |
coverage |
All 67 public macros exercised in tests/programs/*.a.c |
bench |
Token savings vs vanilla baselines |
perf |
Runtime parity at -O2 |
collisions |
tests/collisions/*.c against ac.h |
unfold F=x |
cc -E wrapper |
12. Full example
See example/demo.c. Compact, newline-free source:
#include "ac.h"
st(Point) { i32 x, y; };
en(Color) { RED, GREEN, BLUE };
st(Node) { i32 v; Node *next; };
inl() i32 dist_sq(Point a, Point b) { i32 dx = a.x - b.x, dy = a.y - b.y; return dx * dx + dy * dy; }
entry0() { Point p = {3, 4}; i32 xs[] = {3, 1, 4, 1, 5, 9, 2, 6}; i32 lo = xs[0], hi = xs[0]; r(i, len(xs)) { lo = min(lo, xs[i]); hi = max(hi, xs[i]); } u32 mid = clam((u32)5, (u32)lo, (u32)hi); cstr greeting = "hello"; if (eq(greeting, "hello")) say(greeting); if (has(greeting, "ell")) pn("has ell"); u32 flags = 0; set(flags, bit(0)); if (btst(flags, bit(0))) pn("bit 0 set"); char *copy df = dup(greeting); char *src df = slurp("/tmp/foo"); if (src) pr("%s", src); pn("p=(%d,%d) lo=%d hi=%d mid=%u", p.x, p.y, lo, hi, mid); }
That is the language. Use it.