Affordance
Affordance is a browser for models. It is an open-source engine, written in Rust, that renders an app written in TSX into the exact token ids a language model reads, and runs the app's handlers when the model calls one of the app's tools. It replaces Jinja chat templates and hand-written agent harnesses with one renderer that works in tokens. Version 0.1 is a beta under Apache-2.0, by Approximate Labs.
Site: https://affordance.rs/
Source: https://github.com/approximatelabs/affordance
All documentation as one text file: llms-full.txt
A note on who wrote this
This file was written by an AI model that worked on Affordance, for the models and agents that will read it. It is longer than a usual llms.txt on purpose. You can take in the whole picture at once, so the whole picture is here: the idea, the analogy that makes it easy to hold, the guide with its code and real output, the measurements, and links to every page.
The idea
Affordance is a browser for models. A web browser takes HTML and renders it into pixels for a person, and runs the page's code when the person clicks. Affordance takes an app written in TSX and renders it into the exact tokens a model reads, and runs the app's code when the model calls a tool.
The aim is to open up a new way of building for models. The web let many developers build apps for their own domain, because the browser gave them one engine and a simple way to describe a page. Affordance does that for the thing a model works inside. A developer, or a model, can build a harness that fits the problem at hand: what the model sees, what it can do, what it remembers, and what gets dropped when space runs out. Those are short files you can read and change. They are no longer fixed inside someone else's agent.
That includes changing them while running. An Affordance app can load new TSX at runtime, so a model can write a component, install it, and see it in its own next prompt. A model can change what it sees.
The browser analogy, part by part
Most of Affordance can be understood by asking what the web's answer is.
- HTML and JSX describe a page. An Affordance app is TSX that describes what the model reads.
- A browser renders to pixels. Affordance renders to token ids, the numbers a model actually reads, for that model's own tokenizer.
- A page has buttons and links a person can click. An app has tools a model can call. A tool exists only where it is rendered, as a button exists only where it is on the page. If the app does not render a tool on some turn, the model cannot call it on that turn.
- CSS sizes things in pixels, because the browser lays the page out. Token style sheets (
.tss files) size things in tokens, because Affordance lays the prompt out: max-tokens, what to do on overflow, and how to drop old history.
- A page reaches the world through
window: fetch, timers, messages, storage. An app reaches the world through harness: model calls, machines to run commands on, messages to other agents, storage.
- The browser has an event loop, and a page decides when to fetch. The runtime has an event loop per agent, and the app decides when to call the model.
- A browser runs many different web apps on one engine. Affordance runs chat templates, single agents and fleets of agents on one engine.
- React made pages into functions of state, with hooks. Affordance apps are functions of state with the same hooks (
useState, useEffect, useRef), so the model you already have of React mostly carries over.
What you get
The harness is an app. An agent's harness is the program that decides what the model reads next and which tools it has. Coding agents, memory systems and research prototypes each write their own in host code. In Affordance the loop, the tools, the history rule and the summarizing are a few short TSX files and a style sheet. Changing how an agent compacts its history, or which tools it offers when, is an edit to a small file.
Exact tokens. A render is the list of token ids the model will read, with the model's chat format applied. 57 models' formats are included, and each was compared with the model's official chat template and tokenizer on 963 conversations: 40,977 conversations gave the same tokens and 13,914 were rejected by both. None differed.
Layout in tokens, with a proof. A style rule such as max-tokens: 1500tok; overflow: clip-middle gives a part of the prompt a hard limit. That a limited box never emits more tokens than its limit is proved with Verus for every input, on the same Rust file the engine runs.
One engine on both sides. The same renderer runs inside an agent, as the part that builds each prompt, and inside a model server, in place of the Jinja chat template and the tokenizer. We run it in a fork of the pi coding agent, and in forks of SGLang and vLLM.
Speed where it shows. Inside SGLang, with Qwen3.6-27B generating, the first token of a reply to a prompt over 120,000 tokens arrived in 968 ms with Affordance and 1,121 ms without. One style rule, drop-step: 25%, took a long conversation's requests from 4,605 ms to 492 ms by keeping the start of the prompt stable for the server's cache.
Many agents in one process. Each agent is an event loop with its own state, timers, messages and model calls. A single Affordance app has been tested running over 2,000 simultaneous agents in Multiplet (https://multiplet.ai), balancing messaging, dispatching commands and managing agent sessions, with minimal overhead across the whole system.
Runs that can be replayed. Everything an agent hears from outside (a model's reply, a tool's result, a message) is written to a journal. A stopped run continues from its journal without asking the model anything twice, and a finished run can be stepped through turn by turn and gives the same tokens each time.
Apps that change at runtime. loadModule(source) compiles TSX that arrived as data, for example in a tool call from the model, into a component the app can mount. The example views/demo/live_components.tsx lets a model install, replace and undo its own components.
Any app can be a chat endpoint. affordance serve puts an app behind HTTP, and a small library hook makes it answer as an OpenAI-compatible /v1/chat/completions endpoint. So a model plus an app.tsx becomes something any chat client can talk to, and the client does not have to know what is behind it. The app can keep each conversation itself, summarize older exchanges so the memory outlasts the context window, run its own tools on the server side, call the model several times for one reply, or hand the work to other agents. The example views/demo/memory_server is about sixty lines: a client sends only its newest message, and the model still sees summaries of everything earlier plus the last six exchanges. The app is handed the whole request, including any tools the client listed, and decides what to do with them.
A familiar way to write. Components, props, hooks, JSX and a CSS-like style sheet. The language is a documented subset of TypeScript and JSX, interpreted in Rust. It reads like React and runs without React, Node or npm.
Prior art
Building prompts from components is a known idea, and many people have worked on it. Jinja chat templates are how nearly every model's prompt format is written today. Priompt, from the makers of Cursor, and Microsoft's prompt-tsx write prompts as JSX with priorities, so the less important parts drop out when the context window is full. Guidance mixes templates with generation and constrains what the model may write. Affordance takes from the best of these: components from JSX, budgets from Priompt and prompt-tsx, exact model formats from the Jinja templates. What it tries to add is one general answer. A single engine renders to exact token ids, treats tools as part of what is rendered, and runs the agent around it, so the same approach covers a chat template, one agent and a fleet.
The language, and extending it
Apps are written in a documented subset of TypeScript and JSX. When code steps outside the subset, Affordance says so: a render fails with an error that names the problem and the file and line of each component it was in, and affordance lint checks files ahead of time, including idioms that would behave differently from JavaScript. The differences from JavaScript and the unsupported features are listed in docs/language.html (sections 7.3 and 7.4).
The subset is meant to grow. If you hit its edge with code that has a clear use, open an issue that shows what you wanted to write and why, and we are happy to extend the language. docs/contributing.html says how to contribute.
Serving an app as a chat endpoint
affordance serve views/demo/memory_server --set model.url=http://127.0.0.1:8080/v1
curl -s localhost:8000/v1/chat/completions -H 'content-type: application/json' \
-d '{"model": "m", "messages": [{"role": "user", "content": "My name is Ada."}]}'
Each HTTP request reaches the app as an event, and the app answers it, as a Service Worker answers fetch. Requests are written to the journal like every other input, so a served app can be stopped and resumed with its conversations intact. The header x-conversation-id names a conversation. Streaming works: a model's reply can be piped to the client as it is generated. There is no TLS or authentication built in, so put a proxy in front.
The guide: build an app.tsx
This is the guide at guide.html written out. Each output is what the command printed.
cargo install affordance
1. An app is a function from state to tokens
The default export is a function. It takes the state as its props and returns what the model should read. affordance render prints the token ids, the text they decode to, and any spans.
hello.tsx:
export default function Hello({ name }) {
return <>{`Hello, ${name}!\n`}</>;
}
echo '{"name":"world"}' | affordance render hello.tsx --state -
ids [15496, 11, 995, 0, 198]
text Hello, world!
With no --tokenizer, render uses GPT-2's. Pass a model's tokenizer.json and the ids are that model's. An unknown lowercase tag is an error.
2. State comes in as JSON
Anything in the state can be mapped into text. Strings render as text, arrays render each item in order, and null and false render nothing, as in React.
inbox.tsx:
export default function Inbox({ mail }) {
return <>
{`Inbox: ${mail.length} unread\n`}
{mail.map(m => `${m.from}: ${m.subject}\n`)}
</>;
}
inbox.json:
{"mail":[{"from":"Ada","subject":"The build is red"},{"from":"Grace","subject":"Lunch on Friday?"},{"from":"Linus","subject":"Re: patch v3"}]}
affordance render inbox.tsx --state inbox.json
ids [818, 3524, 25, 513, 555, 961, 198, 2782, 64, 25, 383, 1382, 318, 2266, 198, 8642, 558, 25, 40514, 319, 3217, 30, 198, 14993, 385, 25, 797, 25, 8529, 410, 18, 198]
text Inbox: 3 unread
Ada: The build is red
Grace: Lunch on Friday?
Linus: Re: patch v3
3. A tool is a function the model can call
A web page has buttons. An app has tools. <Tool fn={archive}> offers the function to the model and marks the tokens it is drawn with. The model is told the function's name, its comment and its argument type.
inbox.tsx:
import { Tool } from "@affordance/lib/tool.tsx";
export default function Inbox({ mail }) {
const [unread, setUnread] = useState(mail);
/** Archive every message from one sender. */
function archive({ from }: { from: string }) {
setUnread(unread.filter(m => m.from !== from));
return `Archived the mail from ${from}.`;
}
return <>
{`Inbox: ${unread.length} unread\n`}
{unread.map(m => `${m.from}: ${m.subject}\n`)}
<Tool fn={archive}>[archive]</Tool>
</>;
}
affordance render inbox.tsx --state inbox.json
ids [818, 3524, 25, 513, 555, 961, 198, 2782, 64, 25, 383, 1382, 318, 2266, 198, 8642, 558, 25, 40514, 319, 3217, 30, 198, 14993, 385, 25, 797, 25, 8529, 410, 18, 198, 58, 17474, 60]
text Inbox: 3 unread
Ada: The build is red
Grace: Lunch on Friday?
Linus: Re: patch v3
[archive]
span 32..35 affordance
span 32..35 action run(from)
What the model is told about the tool (from --json):
{
"name": "archive",
"description": "Archive every message from one sender.",
"parameters": {
"type": "object",
"properties": {
"from": {
"type": "string"
}
},
"required": [
"from"
]
}
}
useState keeps a value between renders, as in React. When the model calls archive with from: "Ada", the handler runs and sets the state. Its return value is the result the model reads. Then the app renders again:
ids [818, 3524, 25, 362, 555, 961, 198, 8642, 558, 25, 40514, 319, 3217, 30, 198, 14993, 385, 25, 797, 25, 8529, 410, 18, 198, 58, 17474, 60]
text Inbox: 2 unread
Grace: Lunch on Friday?
Linus: Re: patch v3
[archive]
span 24..27 affordance
span 24..27 action run(from)
A tool exists only where it is rendered. Underneath, <Tool> is the element <aff>, an action attached to tokens.
4. A style sheet sets budgets in tokens
CSS can size a box in pixels because the browser lays the page out. Affordance lays the prompt out in tokens, so a token style sheet can size a box in tokens. Selectors name components and elements, as in CSS.
shell.tss:
Output {
max-tokens: 90tok;
overflow: clip-middle;
more: "\n[... {hidden} tokens hidden ...]\n";
}
shell.tsx:
import "./shell.tss";
const Output = ({ children }) => children;
export default function Shell({ command, output }) {
return <>
{`$ ${command}\n`}
<Output>{output}</Output>
</>;
}
Given the output of a test run that is 1,440 tokens long, the model reads 98 tokens:
$ cargo test --release --lib
running 66 tests
test emit::drop_cut_tests::graded_results_grow_in_steps ... ok
test grammar::tests::byte_level_map_is_a_bijection ... ok
test
[... 1355 tokens hidden ...]
ok
test result: ok. 66 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.21s
The two units are tok, a count of tokens, and win, a percentage of the model's context window. The rule most agents want is for the history:
history {
max-tokens: 75win; /* at most 75% of the window */
overflow: drop-start; /* drop whole turns, oldest first */
drop-step: 25%; /* a quarter at a time, so the server's cache survives */
}
5. A model format and a loop make it an agent
So far the app renders plain text. A model wants its own chat format: its turn markers, its tool definitions, its reasoning tags. ModelContext wraps the app in the format of the model you name, and useLoop calls the model and runs the tools it picks. affordance init writes this file.
app.tsx:
// A first agent: it reads its task, may run shell commands, and stops when it answers.
// The page is the task, the tools and the log of what the model saw and did; ModelContext
// renders it in the bound model's format (a hosted chat API, or a native token-id server).
import { ModelContext } from "@affordance/lib/model.tsx";
import { useLog } from "@affordance/lib/log.tsx";
import { useLoop } from "@affordance/lib/loop.tsx";
import { Tool } from "@affordance/lib/tool.tsx";
import { machineTools } from "@affordance/tools/machine.tsx";
export default function Main({ task }) {
const log = useLog();
useLoop(log); // ask the model, run its calls, repeat
const { shell } = machineTools(harness.machine.get("home"));
/** Give your final answer. This ends the task. */
function answer({ text }: { text: string }) {
harness.stop({ answer: text });
return "done";
}
return <ModelContext strategy="unrolled" log={[{ kind: "input", content: task }, ...log.records]}>
<Tool fn={shell}>{"[shell] "}</Tool>
<Tool fn={answer}>{"[answer]\n"}</Tool>
</ModelContext>;
}
useLog() is the history: the task, each reply from the model and each tool result. It is app state.
useLoop(log) is a library hook of 18 lines. It asks the model, runs the calls in its reply, and asks again. You can copy it and change it.
answer is a tool, as in step 3. This one stops the agent.
ModelContext renders the task, the tools and the log in the model's format.
affordance.yaml says where the app runs: which model server, which machines its tools may use, and the task.
app:
view: app.tsx
model:
kind: chat
url: http://127.0.0.1:8080/v1
model: local
context: 8192 # the model's context length
machines:
home:
kind: local # this machine's shell
agents:
- id: first
max_turns: 8
props:
task: How many lines are in ./app.tsx? Count them with the shell, then answer.
affordance run
A recorded run of this agent, with the task "How many lines are in ./app.tsx? Count them with the shell, then answer.":
- The model read 384 tokens and called
shell: wc -l ./app.tsx
Result: 23 ./app.tsx
- The model read 451 tokens and called
shell: cd . && tail -c 1 ./app.tsx | xxd | tail -1; awk 'END{print NR" lines by awk"}' ./app.tsx
Result: 00000000: 0a . / 23 lines by awk
- The model read 555 tokens and called
answer: ./app.tsx has 23 lines. wc -l reports 23, and since the file ends with a newline, awk's record count also confirms 23 lines.
6. harness is to an app what window is to a page
A page reaches the world through window. An app reaches the world through harness and a few globals.
| On a web page |
In an app |
What it does |
fetch(url) |
fetch(url) |
An HTTP request, through an origin the deployment allows. |
| a request to a server |
model(), dispatch(output) |
model() sends this app's render to its model. dispatch runs the tool calls in the reply. |
addEventListener |
useEvent(name, fn) |
Runs a listener when an event arrives, such as a user's message. |
setTimeout |
harness.wait({ ms, events }) |
Parks the task until a time or an event. |
postMessage |
harness.send(to, payload) |
Sends an event to another agent. |
localStorage |
harness.store.put / get / search |
Storage that belongs to this agent. |
ResizeObserver |
useEvent("layout", fn) |
Reports how many tokens each box took and hid, after each prompt is laid out. |
Date.now(), Math.random() |
harness.now(), harness.random() |
Recorded in the journal, so a replay sees the same values. |
|
harness.machine.call({ cmd }) |
Runs a command on a machine the deployment names: this host, a container, or a host over SSH. |
window.close() |
harness.stop(value) |
Stops the agent with a result. |
A render is pure: it reads state and returns tokens. Calls to the world happen in handlers and effects, and each result is written to the agent's journal. The model is never called for you. When to call it is the app's decision.
Install and first run
cargo install affordance # one Rust binary, Linux and macOS, Rust 1.89 or later
affordance init my-agent && cd my-agent
affordance run # needs a model server; see below
affordance run --resume # continue a stopped run from its journal
A run needs a model: any OpenAI-compatible server (llama.cpp, Ollama, vLLM, SGLang) or a hosted API. Set it in affordance.yaml under model: (kind, url, model, context, key_env). Tools run on a machine named under machines: (local, ssh, docker, bwrap, command, http).
Commands
affordance run [PATH]: run the deployment in affordance.yaml. Options: --set KEY=VALUE, -f FILE, --resume, --fresh, --keep-running.
affordance serve [PATH]: run it behind HTTP.
affordance host [PATH]: run it for a frontend over stdio (JSON lines).
affordance init [DIR]: write a first agent.
affordance render APP.tsx --state FILE|- [--tokenizer FILE] [--json]: render an app to token ids and spans.
affordance test APP.tsx SCENARIO.json: run an app against a scripted model and check what it did.
affordance lint [PATH]: check TSX.
affordance config [PATH]: print the resolved deployment.
When it fits
- You are writing an agent harness and want the loop, tools, history policy and compaction to be short files you can read and change.
- You study memory systems, long-context policies or multi-agent setups and want to compare harness designs on the same engine with exact, repeatable prompts.
- You run a model server with long agent conversations and want the chat template rendered straight to token ids, with earlier turns kept between requests.
- You need exact control of the tokens in a prompt: budgets per section, stable prefixes for the server's cache, and a record of what was hidden.
Measured results, with their conditions
- 57 models' chat templates were rewritten as TSX apps. On 54,891 conversations (963 per model) each app matched the official Jinja template and tokenizer: 40,977 gave the same token ids and 13,914 were refused by both. 0 differed. showcase/chat-templates.html
- Inside SGLang's Rust server (a 140-line change), with Qwen3.6-27B on one RTX PRO 6000 and a conversation growing to 164,000 tokens, the median time to first token on prompts over 120,000 tokens was 962 to 974 ms with Affordance and 1,107 to 1,135 ms without. Two runs each, one machine. showcase/first-token.html
- Inside vLLM's Rust frontend (188 lines), time to first token did not change: 1,161 ms with Affordance and 1,158 ms without. vLLM's tokenizer is already fast, so there was little time to save. Measured without a model running, Affordance prepares a growing conversation 2.8 to 13.9 times faster than vLLM's Rust frontend. The one case where it is slower is a conversation it has not seen before: preparing a 185,000-token prompt takes about 20 to 45 ms, against about 11 ms in vLLM.
- With
drop-step: 25% on a 24,000-token history, 89% of prompt tokens were cached by SGLang and a request took 492 ms. Without it, 0% and 4,605 ms. One run of 150 turns. showcase/prefix-cache.html
- A box with a token limit never emits more tokens than the limit. This is proved with Verus on
src/fit.rs (20 verified, 0 errors). The proof bounds each box and does not bound the whole prompt. showcase/proved-limit.html
Limits
- v0.1 is a beta. The app API will change before 1.0.
- Linux and macOS only.
- The measurements above are from one machine and one or two runs each.
- In the model-server integrations, images and other media still go to the server's own renderer.
- Verus is not part of an ordinary build or of the pull-request gate.
Showcase
- We built OptMem, a long-term memory for AI agents, as a small Affordance app: Built with Affordance. A chat model forgets everything when a conversation ends. OptMem, a design by Victor Taelin, has the model keep one-line notes and read them back next time. Our version is 116 lines, and it answered 8 of 8 questions about facts from earlier conversations.
- We built a Recursive Language Model as one Affordance file: Built with Affordance. Models get worse at answering as their input gets longer. A Recursive Language Model keeps the long input out of the prompt and lets the model work on it with code. Ours got 5 of 6 questions right on a 408,893-character log. Reading the log directly, the same model finished none.
- SGLang started replying 153 ms sooner on long prompts with Affordance preparing them: Benchmark. Before a model can start, its server turns the conversation into tokens. We replaced that step in SGLang with Affordance. On prompts over 120,000 tokens, the wait for the first word fell from 1,121 ms to 968 ms, and the model read exactly the same tokens.
- 57 models' prompt formats rebuilt in Affordance, each matching the official one token for token: Compatibility test. Every chat model expects its conversations in one exact format. We rebuilt the format of 57 models as Affordance apps and ran 963 test conversations through each one and through the model's official tools. The tokens matched every time.
- One line in a style sheet cut a long conversation's requests from 4.6 seconds to 0.5: Benchmark. A model server saves work when a prompt starts the same way as the last one. An agent with a full history breaks that on every turn by trimming a little off the front. We changed how it trims, with one line, and requests got nine times faster.
- Running the pi coding agent on Affordance: Built with Affordance. pi is an open-source coding agent for the terminal. In our fork, the part that talks to the model is an Affordance app. Changing when pi summarizes a long conversation is now an edit to two numbers in a 41-line file.
- Affordance vs. Pi Durable: Comparison. Both can run durable agents. Affordance also renders the model's interface, down to the template and token IDs inside an inference server.
- Showing a model the useful 98 tokens of a 1,440-token test log: Example. When an agent runs your tests, the output can be thousands of tokens, and the model mostly needs the last line. This three-line rule keeps the start and the end of a command's output and says how much it left out.
- A first agent in 23 lines, and a recording of it at work: Example. An agent is a model that uses tools in a loop until a task is done. This is the smallest one Affordance writes for you: it can run shell commands and give an answer. The page walks through the file and shows one real run.
- One app, shown to three different models: Example. Qwen, GLM and Llama each expect a prompt in their own format, and each has its own way of listing tools. This app is written once and names no model. The page shows what each of the three models reads.
- A proof that a part of the prompt never goes over its token limit: Formal proof. If you give a part of the prompt a limit of 1,500 tokens, Affordance must never put 1,501 there, whatever the content is. We did not only test that. A proof checker confirms it for every possible input.
Documentation
Start here
- Getting started (source:
docs/GETTING_STARTED.md): You need Rust 1.89 or later. It runs on Linux and macOS; see platform evidence. Python, Node and model weights are not needed.
- A first agent (source:
examples/first_agent/README.md): An agent that reads its task, may run shell commands on this machine, and stops when it answers. app.tsx is the whole app; affordance.yaml binds it to a model server and a machine. affordance init ...
- Examples (source:
examples/README.md): An Affordance app is a folder with an app.tsx and an affordance.yaml, run with affordance run in that folder. You do not write Rust to build one.
- Why Affordance is shaped like a browser (source:
docs/DESIGN.md): This document explains where the line between the engine and an app is, and why. It is for anyone extending Affordance, person or model, who wants to predict the answer to "where does this feature ...
Writing apps
- The view language (source:
spec/LANGUAGE.md): Views are TypeScript/JSX files (.tsx) implementing the JavaScript subset specified here. Intentional differences are in §7.3; unsupported features are in §7.4. Everything here is implemented in src/ ...
- App, turn strategy, template app (source:
docs/MIDDLE_LAYER.md): Contract authority: public contracts, language and harness.
- Native apps in model chat formats (source:
docs/ADAPTERS.md): Contract authority: public contracts, language and harness.
- Reading model replies (source:
docs/REPLY_PARSING.md): Contract authority: public contracts, language and harness.
Running agents
- The agent runtime (source:
docs/RUNTIME.md): Contract authority: public contracts, language and harness.
- Configuring model bindings (source:
docs/MODEL_BINDINGS.md): The configuration contract is in HARNESS.md §6. A binding combines a tokenizer and one stock client. model names the default binding; models adds names selected by agents[].model or . ...
- Fetch, HTTP bindings and MCP (source:
docs/HTTP_AND_MCP.md): Contract authority: public contracts, language and harness.
- The harness contract (source:
spec/HARNESS.md): Affordance has two parts. The language (spec/LANGUAGE.md) turns state into exact token ids for a model, plus the actions it offers. The harness (this document) runs apps the way a browser runs pages: ...
Example apps
- Context policies example (source:
views/demo/research/README.md): A small fleet on the instance runtime. Each agent answers a question by searching a document corpus through bash, on its own machine, under a context policy:
- Permanent memory (after OptMem) (source:
views/demo/memory/README.md): An agent whose memory outlives its sessions, after VictorTaelin's OptMem, built as an instance tree.
- A memory longer than the context, for any OpenAI client (source:
views/demo/memory_server/README.md): A chat model behind an OpenAI-compatible endpoint (affordance serve) that keeps each conversation itself, in its journal. A client may send its whole history or only its newest message; either way the ...
- Recursive Language Model (source:
views/demo/rlm/README.md): A Recursive Language Model, after Prime Intellect's RLM, built as an instance tree. The root model never reads the input. The input sits in a persistent Python REPL on the agent's machine, as context, ...
Embedding and internals
- Harness guide: driving an affordance app from Rust (source:
docs/HARNESS_GUIDE.md): Contract authority: public contracts, language and harness.
- Architecture (source:
docs/ARCHITECTURE.md): Affordance has one engine, implemented in Rust. Applications and reusable application policies are written in the TSX subset described in LANGUAGE.md. CONTRACTS.md maps the public boundaries and their ...
- Public contracts and ownership (source:
spec/CONTRACTS.md): This is the map of Affordance's shipped contracts. LANGUAGE.md is normative for source evaluation and document layout; HARNESS.md is normative for instance execution and recovery. The supported ...
- Verification (draft) (source:
docs/VERIFICATION.md): Some properties of the engine are proved, not only tested. This page lists each one: the claim in a sentence or two, the exact statement the tool checked, what the proof assumes, and what it does not ...
- Dependency security (source:
docs/SECURITY.md): The release gate runs python3 tools/check-security.py, using cargo-audit and the RustSec database. Install the auditor with cargo install cargo-audit --version 0.22.2 --locked. New advisories fail the ...
Project
- Developing Affordance (source:
docs/DEVELOPING.md): Contract authority: public contracts, language and harness.
- Contributing (source:
CONTRIBUTING.md): Start with your first app, then read the public boundaries. Affordance is currently an experimental Rust framework with a TSX application language. Python reference tools belong to development; ...
Source