GRAIL is an experimental model for goal-directed autonomous systems.
Instead of giving an agent a predefined workflow, GRAIL describes an environment of capabilities, conditions, inputs, and effects. The agent starts with a goal and discovers a path through that environment as it attempts to achieve the goal.
Define the environment, not the path.
The basic GRAIL algorithm is small.
An agent pursues a goal by:
- Attempting the capability associated with the goal.
- Identifying unmet conditions when the capability is blocked.
- Finding capabilities that can establish those conditions.
- Pursuing those capabilities.
- Retrying the original capability as conditions are satisfied.
Conceptually:
goal
|
v
attempt capability
|
+---- SUCCESS ----> done
|
+---- BLOCKED
|
v
unmet condition
|
v
find a capability
that can establish it
|
v
pursue
|
v
retry
The agent does not need a predefined sequence of steps. The path emerges from the goal, the current state of the world, and the capabilities available in the environment.
If you are new to GRAIL, start with:
grail-demo-01
This is the smallest working demonstration of the GRAIL goal-resolution model.
The simplicity is intentional. It exposes the basic algorithm without the additional features explored in later demos.
The original example demonstrates a goal that cannot initially be completed because required conditions are missing. GRAIL recursively pursues capabilities that can establish those conditions and retries the goal as the world changes.
Later demos build on this same basic model.
A conventional workflow describes a route:
do A
then B
then C
GRAIL describes the environment in which a goal can be achieved.
A capability declares things such as:
capability
|
+-- preconditions
+-- inputs
+-- effects
The environment may contain many capabilities and many possible ways to establish the conditions required by a goal.
GRAIL traverses that capability space at runtime.
This shifts an important design question from:
What steps should the agent follow?
to:
What must be true for this capability to succeed?
When a required condition is identified, another question follows:
What capability can make that condition true?
The environment grows outward from the goals it is expected to support.
A GRAIL environment does not necessarily contain a single path to a goal.
Multiple capabilities may be able to establish the same condition. GRAIL can choose among those alternatives as it traverses the environment.
Different runs may therefore follow different paths.
The declared goal remains stable.
This is the idea behind:
Stochastic Traversal with Deterministic Results
The traversal may vary while successful traversals converge on the declared goal state.
A capability represents behavior available in the environment.
For example:
setCustomerEmail
precondition: customerProfileCollected
input: email
effect: customerEmailSet
Capabilities can also establish multiple effects:
setCustomerContactDetails
|
+-- customerEmailSet
+-- customerPhoneNumberSet
+-- customerAddressSet
Fine-grained and coarse-grained capabilities can coexist in the same environment.
GRAIL does not need to know whether a capability is a shortcut, a bundle of operations, or a simple action. It needs to know the conditions under which the capability can execute and the effects that successful execution establishes.
A capability's effects define its success boundary.
If a capability declares several effects, SUCCESS means that all of those effects have been established.
The original GRAIL demos execute capabilities locally.
More recent experiments separate the behavioral definition of a capability from its implementation.
A capability may include a binding:
capability
|
| behavior
|
binding
|
| execution
v
external service
The first implemented external binding uses HTTP. More recent experiments also support dynamically loaded Node.js modules.
This allows GRAIL to retain the behavioral model while capability work is performed either by an independent service or by a local module.
Capabilities define behavior. Bindings define execution.
The capability implementation does not need to understand the GRAIL traversal algorithm.
Affordances and bindings may optionally declare an enabled property.
An affordance with enabled: false is unavailable to discovery. A binding with enabled: false is not executed; the affordance follows the normal unbound behavior and succeeds once its preconditions are satisfied, applying its declared effects.
When enabled is omitted, both affordances and bindings are enabled. This preserves compatibility with existing registry documents.
The current HTTP experiments support external capability execution using bindings such as:
{
"protocol": "http",
"method": "POST",
"url": "http://localhost:3001/execute"
}
Capability inputs may be mapped to four HTTP request locations:
path
query
header
body
Bindings declare these mappings using parameters:
{
"protocol": "http",
"method": "POST",
"url": "http://localhost:3001/customers/{customerId}",
"parameters": {
"customerId": {
"in": "path"
},
"region": {
"in": "query",
"name": "lang"
},
"termsVersion": {
"in": "header",
"name": "X-Terms-Version"
},
"email": {
"in": "body",
"name": "emailAddress"
}
}
}
The parameter key identifies the GRAIL input. The optional name
identifies its HTTP representation. This allows the vocabulary used by the
GRAIL environment to remain independent of the vocabulary used by an
external service.
Body values may be represented using:
application/json
or:
application/x-www-form-urlencoded
JSON is the default when no content type is specified.
Bindings without an explicit parameters declaration retain the original
behavior and send declared capability inputs in the request body.
Node bindings allow GRAIL to execute ordinary JavaScript modules directly.
A binding identifies the module and exported function:
{
"protocol": "node",
"module": "./capabilities/customer.js",
"function": "lookupCustomer",
"outputs": {
"accountId": {
"from": "result",
"path": "account.id"
}
}
}
The capability receives the resolved GRAIL inputs as an object and returns a result. The capability itself does not need to know anything about GRAIL.
Node outputs may be extracted from the returned result using simple
dot-separated paths.
Output mappings also provide an anti-corruption layer between capability implementations and the GRAIL environment. A capability may use its own result structure and vocabulary while the binding maps those values to stable output names used by the GRAIL scenario.
HTTP and Node execution are routed through a common binding layer:
GRAIL mechanics
|
v
binding
/ \
HTTP Node
This keeps protocol-specific execution outside the generic GRAIL mechanics.
Demo 14 introduces a breaking change to capability input declarations.
Earlier demos declare inputs as an array:
"inputs": [
"customerId"
]
The current model maps each local input name to a source expression:
"inputs": {
"customerId": "$inputs.customerId"
}
This separates the name used by the capability from the source of the value.
$inputs refers to values supplied at the beginning of a run.
Values learned during execution may be resolved from observations using
$outputs:
"inputs": {
"accountId": "$outputs.lookupCustomer.latest.accountId"
}
Two forms are currently supported:
$outputs.<affordance>.latest.<output>
$outputs.latest.<output>
The affordance-scoped form resolves the latest matching output produced by a specific affordance. The scenario-scoped form searches observations from newest to oldest and resolves the latest matching output regardless of which affordance produced it.
Scenario-scoped resolution is useful when multiple affordances can produce equivalent information. It allows a consumer to depend on the GRAIL output vocabulary without being coupled to a particular producer. Use the affordance-scoped form when the identity of the producer is semantically important.
This is an intentional breaking change. The current runtime does not support both the older input-array form and the newer source-aware form.
The current runtime can record completed capability interactions as observations.
An observation records:
invocation
response
outputs
result
Conceptually:
invocation what GRAIL attempted
response what the binding returned
outputs what GRAIL extracted
result what GRAIL concluded
Bindings may declare values to extract from their execution results.
For HTTP:
"outputs": {
"accountId": {
"from": "body",
"path": "account.id"
}
}
Node bindings use from: "result" to extract values from the value returned
by a module.
Extracted values remain part of the observation that produced them. $outputs
is a resolver over observations, not a separate mutable output store.
The current observation vocabulary still uses response, which is natural for
HTTP but less natural for Node execution. Demo 17 records this as an observed
design pressure rather than introducing a new generic observation model.
This gives the current model four distinct kinds of information:
$inputs what was known when the run began
worldstate what is currently true
observations what actually happened
$outputs what can be learned from what happened
For the current demos, observations are persisted in observations.json.
That file is a runtime artifact rather than part of the environment
configuration.
The current runtime distinguishes three results:
BLOCKED
SUCCESS
FAIL
BLOCKED is determined before capability execution. It means the capability could not currently be executed because an environmental requirement was not satisfied.
SUCCESS and FAIL are determined after capability execution.
For an unbound capability, SUCCESS remains the default once its environmental requirements have been satisfied.
For the current HTTP binding, HTTP 2xx responses result in SUCCESS and other HTTP responses result in FAIL.
A failed interaction may still produce an observation and extracted information.
The repository includes a standalone environment validator:
grail-validate.js
Use it before running an environment:
node grail-validate.js config
It validates:
registry.json
inputs.json
worldstate.json
goal.json
against the schemas in the repository's schemas directory.
observations.json is intentionally not required because it is generated
during execution.
A successful validation exits with status 0. Missing files, invalid JSON,
or schema failures result in status 1.
The normal GRAIL runtime continues to perform its own configuration validation. The standalone validator provides an independent static check before execution.
The repository records the evolution of GRAIL through a series of working experiments.
The early demos establish the basic goal-resolution model and explore different application scenarios.
Later demos introduce increasingly dynamic environments, including multiple goals and randomized traversal.
Recent experiments focus on several areas:
grail-demo-09-random-conditions
Explores traversal when the engine can select among unmet conditions rather than following a fixed ordering.
grail-demo-10-multi-effects
Explores capabilities that establish multiple effects and environments where more than one capability can satisfy a condition.
grail-demo-11-initial-http-bindings
Moves capability execution outside the GRAIL process through HTTP while retaining GRAIL's declared preconditions and effects.
grail-demo-12-http-bindings-inputs
Adds transmission of capability inputs to external HTTP services using JSON and FORM representations.
grail-demo-13-http-binding-locations
Extends HTTP bindings by allowing capability inputs to be mapped to path, query, header, or body locations. Bindings may also assign HTTP-side names to inputs, allowing the GRAIL environment and external service to use different vocabularies.
grail-demo-14-http-response-observations
Introduces source-aware capability inputs, records HTTP interactions as
observations, extracts declared values from responses, and allows later
capabilities to consume learned values through $outputs.
This demo introduces a breaking change from input arrays to mappings between local input names and source expressions.
grail-demo-15-http-bindings-location-bc
Ports the Demo 13 HTTP binding-location environment to the Demo 14 declaration model and runs it using the Demo 14 runtime.
This verifies that path, query, header, body, and HTTP-side naming behavior survive the migration without adding a backward-compatibility layer to the runtime.
grail-demo-16-http-output-sources
Extends HTTP output extraction beyond response bodies. Declared outputs may be captured from the response body, a response header, or the HTTP status code.
Body outputs use from: "body" and a dot-separated path. Header outputs use
from: "header" and a case-insensitive header name. Status outputs use
from: "status".
This demo also reinforces the separation between capture and consumption: GRAIL may observe and preserve information even when no later capability currently consumes it.
grail-demo-17-node-module-bindings
Adds dynamically loaded Node.js modules as a second capability realization mechanism alongside HTTP.
A Node binding declares a module and exported function. Resolved GRAIL inputs
are passed to that function, and declared outputs may be extracted from the
returned result using from: "result".
Demo 17 introduces a common binding layer so the generic GRAIL mechanics do not need to know whether a capability is realized through HTTP or a local Node module.
The experiment demonstrates that the same precondition, input-resolution, observation, output, effect, and goal-pursuit mechanics work across both binding types.
grail-demo-18-node-customer-onboarding
Extends the Node binding model to a complete customer-onboarding application composed of multiple independent capabilities sharing application state.
The demo begins with the single goal onboardCustomer. GRAIL discovers the
capabilities needed to satisfy its unmet conditions at runtime rather than
following a predefined onboarding workflow.
Initial values such as customer name, email, phone, address, and terms version
are supplied through $inputs. Values learned during execution, including
onboardingId, customerId, and verification identifiers, are captured from
Node capability results and consumed by later capabilities through $outputs.
The experiment demonstrates multi-step information flow across Node bindings, capabilities that establish effects without producing output values, multiple effects from a single affordance, scenario-scoped output resolution, and repeated goal re-evaluation as the environment changes.
The scenario includes both fine-grained setters and a coarse-grained
setCustomerDetails affordance. Either can produce the verification identifiers
needed by later verification affordances. Those consumers use
$outputs.latest.<output> so they depend on the learned value rather than on a
specific producer.
The customer-onboarding application maintains its own application state independently of GRAIL's world state. The experiment also exposed an important contract boundary: a capability that reports SUCCESS must actually establish the effects associated with that success in the GRAIL environment.
Demo 18 demonstrates that the richer customer-onboarding scenario requires no changes to the generic GRAIL traversal mechanics.
During development of Demo 18, optional enabled properties were added for affordances and bindings. These allow an affordance to be removed from discovery or a binding to be bypassed without changing existing registry behavior when the property is omitted.
Each experiment builds on the same small GRAIL goal-resolution model.
The current experiments can be viewed as three cooperating elements:
GRAIL environment
goal
|
v
capability
/ | \
/ | \
pre- inputs effects
conditions
|
binding
|
v
implementation
The environment describes what capabilities mean and how they relate to world state.
Bindings describe how capability implementations are reached. The current runtime supports HTTP services and dynamically loaded Node.js modules behind a common binding layer.
Implementations perform the work.
This separation allows a GRAIL environment to compose capabilities without requiring all implementations to share the same internal architecture.
GRAIL is still experimental.
The current HTTP binding treats HTTP 2xx responses as SUCCESS and other HTTP responses as FAIL. Node module execution succeeds when the selected function completes normally and fails when module loading or execution fails.
HTTP output extraction currently supports response body, header, and status
sources. Node output extraction supports returned results using simple
dot-separated paths. $outputs currently supports only the latest
observation selector.
The current observation structure was originally shaped around HTTP request/response interactions. Node bindings expose the need for more binding-neutral observation vocabulary, but that model has not yet been redesigned.
The current resolver supports both producer-specific
$outputs.<affordance>.latest.<output> references and scenario-scoped
$outputs.latest.<output> references. Both use the latest selector over
observations. Scenario-scoped resolution assumes that output names have stable
semantics within the GRAIL environment; producer-specific resolution remains
available when provenance matters or output names would otherwise be ambiguous.
Other areas for future experiments include:
- richer observation selection and extraction;
- richer FAIL semantics and the use of information learned from failed calls;
- generalized selection among capabilities that can satisfy a missing requirement;
- observation timing, retention, and redaction policies;
- additional binding protocols as concrete needs emerge.
These are intentionally being introduced through small experiments rather than designed into GRAIL in advance.
Each demo is self-contained. See the README and notes within an individual demo for its requirements and instructions.
For the simplest introduction to the algorithm, begin with:
grail-demo-01
For the most recent binding experiments, see:
grail-demo-10-multi-effects
grail-demo-11-initial-http-bindings
grail-demo-12-http-bindings-inputs
grail-demo-13-http-binding-locations
grail-demo-14-http-response-observations
grail-demo-15-http-bindings-location-bc
grail-demo-16-http-output-sources
grail-demo-17-node-module-bindings
grail-demo-18-node-customer-onboarding
The experiments have produced a small set of principles that describe the direction of GRAIL:
Define the environment, not the path.
Stochastic Traversal with Deterministic Results.
A capability's effects define its success boundary.
Capabilities define behavior. Bindings define execution.
An affordance describes what the environment makes possible. A binding describes how that possibility is realized.
Keep the physics fixed. Make judgment configurable.
GRAIL remains intentionally small. The complexity belongs primarily in the environment: the capabilities available, the conditions they require, and the effects they can establish.
The agent's job is to traverse that environment in pursuit of a goal.