Package System
AILANG's package system enables multi-package projects with deterministic dependency resolution, export enforcement, and effect ceilings. Packages are the primary unit of autonomous coordination — they define what an agent can see, change, what authority it has, and what guarantees it must preserve.
Quick Start
# Create a package
ailang init package --name myorg/mylib
ailang init package --name sunholo/myapp --module-prefix myapp # for existing apps
# Add a dependency
ailang add --path ../shared-utils
# Resolve dependencies and generate lock file
ailang lock
# Validate package (cross-module type checking)
ailang check --package .
# Run tests
ailang test --package .
# View dependency tree
ailang tree
# Publish to registry
ailang publish --dry-run
ailang publish
# Run (package resolution is automatic)
ailang run main.ail
Package Structure
A package is defined by an ailang.toml manifest:
my-package/
ailang.toml # Package manifest (human-written)
ailang.lock # Resolved dependencies (machine-generated)
src/
core.ail # Source modules
helpers.ail
tests/
core_test.ail
Manifest (ailang.toml)
[package]
name = "sunholo/docparse"
version = "0.3.0"
edition = "1"
ailang = ">=0.9.5"
description = "Parse documents into structured blocks"
license = "MIT"
[exports]
modules = [
"sunholo/docparse/parser",
"sunholo/docparse/types"
]
[dependencies]
"sunholo/json" = { path = "../json" }
[effects]
max = ["IO", "FS"]
[stability]
level = "experimental"
Required Fields
[package].name— Two-levelvendor/nameformat[package].version— Semantic version[package].edition— Language edition (currently"1")
Optional Fields
[package].ailang— Minimum AILANG version required (e.g.,">=0.9.5"). Auto-set byailang init package. Enforced onailang install— incompatible packages are rejected with a clear error message.[package].module_prefix— Maps existing module paths to the package namespace without renaming source files. See Module Prefix below.
Module Prefix
Existing applications can adopt the package system without renaming all module declarations. If your project uses module myapp/services/api but you want to publish as sunholo/myapp, set module_prefix:
[package]
name = "sunholo/myapp"
module_prefix = "myapp"
[exports]
modules = ["myapp/services/api", "myapp/handlers/parse"]
Source files keep their existing module myapp/... declarations. Consumers import via the canonical path:
import pkg/sunholo/myapp/services/api (startServer)
The loader maps sunholo/myapp/services/api → myapp/services/api automatically.
Rules:
module_prefixmust be a single segment (no slashes)- Exports can use either
module_prefix/...orvendor/name/...prefixes - CLI:
ailang init package --name sunholo/myapp --module-prefix myapp
Exports
Only modules listed in [exports].modules are accessible to dependents. Modules not in this list are package-private.
[exports]
modules = ["sunholo/docparse/parser"]
# sunholo/docparse/internal/helpers is NOT accessible from outside
Effect Ceiling
Packages declare their maximum allowed effects:
[effects]
max = ["IO"] # Only IO allowed; Net, FS, etc. would be a compile error
If a function in the package declares an effect not in max, compilation fails:
effect ceiling violation in package sunholo/mylib: effects [FS] not in max [IO]
Add missing effects to [effects].max in ailang.toml
An empty max = [] means the package is pure — no effects allowed.
Ghost effects (like Debug) are exempt from ceiling checks. Packages can use std/debug or sunholo/logging without listing Debug in [effects].max. Ghost effects are invisible to callers, auto-granted at runtime, and erased in --release mode.
Stability
[stability]
level = "experimental" # or "stable" | "frozen"
Lock File (ailang.lock)
Generated by ailang lock. Committed to version control. Contains:
- Content hash (SHA256 of all
.ailsource files) - Interface hash (SHA256 of exports + effects — stays same on internal refactor)
- Resolved dependency paths
The lock file is validated at build time — if a dependency's content has changed, you'll see:
Warning: dependency sunholo/json content changed (locked: sha256:a1b2c3... current: sha256:d4e5f6...)
Run 'ailang lock' to update
Importing from Packages
Use the pkg/ prefix for external package imports:
-- Stdlib (always available)
import std/io (println)
-- Local module (same project)
import myproject/parser (parse)
-- External package
import pkg/sunholo/json/parser (parseJson)
The pkg/ prefix tells the compiler to resolve against the lock file instead of the local filesystem.
Dependencies
Path Dependencies
Link to local packages:
[dependencies]
"shared/utils" = { path = "../utils" }
"shared/types" = { path = "../types" }
Git Dependencies
Pin to a specific tag or commit in a git repository:
[dependencies]
"sunholo/auth" = { git = "https://github.com/sunholo-data/ailang-packages", subdir = "packages/auth", tag = "main" }
Registry Dependencies
Install from the AILANG package registry:
[dependencies]
"sunholo/auth" = "0.1.0"
Resolution priority: path > git > registry.
CLI Commands
| Command | Description |
|---|---|
ailang init package --name vendor/name | Create ailang.toml |
ailang add --path ../dep | Add path dependency |
ailang add --git URL --subdir DIR --tag TAG | Add git dependency |
ailang install vendor/name | Install latest from registry |
ailang install vendor/name@version | Install exact version from registry |
ailang lock | Resolve deps, generate ailang.lock |
ailang tree | Display dependency tree |
ailang search "query" | Search registry packages |
ailang publish | Publish to registry |
ailang pkg-docs vendor/name | View package AGENT.md |
Registry
The AILANG package registry is hosted on GCP at https://storage.googleapis.com/ailang-registry.
Publishing
cd my-package/
ailang publish # Upload to registry (requires AILANG_REGISTRY_API_KEY)
ailang publish --dry-run # Preview without uploading
The validator service automatically:
- Compiles your package (
ailang check) - Verifies effect ceilings match
[effects].max - Runs contract verification (
ailang verify, best-effort) - Computes content + interface + tarball hashes
- Rejects duplicate versions (immutable once published)
Searching
ailang search "auth" # Keyword search on name, ai_summary, tags
ailang search --tag gcp # Filter by tag
ailang search # List all packages
Installing
ailang install sunholo/auth # Install latest version
ailang install sunholo/auth@latest # Same as above
ailang install sunholo/auth@0.1.0 # Install exact version
The resolved exact version is always written to ailang.toml. Semver ranges (^, ~, >=) are not supported — AILANG requires exact versions for deterministic builds.
Lock File Portability
ailang.lock is portable across machines and containers — it does not contain absolute paths. Cache paths for registry and git packages are resolved at runtime. This means you can COPY ailang.lock into Docker and run ailang install to populate the cache.
Version Conflict Detection
AILANG uses flat dependencies — one version per package name. If a transitive dependency requires a different version than your root manifest pins, ailang lock fails with a structured error:
version conflict: sunholo/firestore
root requires: 0.2.0
already resolved: 0.1.0
transitive requires: 0.1.0 (via sunholo/billing_store)
resolution aborted
suggestion:
- republish sunholo/billing_store against sunholo/firestore@0.2.0
- or change root dependency to sunholo/firestore@0.1.0 explicitly
Direct dependencies are authoritative — the resolver never silently downgrades them. To fix a conflict, either republish the transitive package or change your root pin to match.
Package Documentation
ailang pkg-docs sunholo/auth # Display AGENT.md usage guide
Each package can include an AGENT.md — a structured guide for AI agents with quick start, exported functions, and common patterns.
Environment Variables
| Variable | Purpose |
|---|---|
AILANG_REGISTRY | Registry URL (default: https://storage.googleapis.com/ailang-registry) |
AILANG_REGISTRY_VALIDATOR | Validator service URL (for ailang publish) |
AILANG_REGISTRY_API_KEY | API key for publishing |
AILANG_CLOUD_PROJECT | GCP project hosting the cascade Pub/Sub topic (e.g. ailang-multivac-dev). When unset, ailang publish skips the cascade-topic publish and only emits legacy inbox messages. |
AILANG_TOPIC_PREFIX | Prefix for cascade topic names (default: ailang). Cloud envs use the env-specific prefix (ailang-dev, ailang-test, ailang). The full topic name is <prefix>-cascade. |
Cascade env vars: Both AILANG_CLOUD_PROJECT and AILANG_TOPIC_PREFIX must match the cloud coordinator's deployment. For the public ailang-dev environment:
export AILANG_CLOUD_PROJECT=ailang-multivac-dev
export AILANG_TOPIC_PREFIX=ailang-dev
Without these, ailang publish still uploads to the registry and notifies dependent inboxes, but the cloud coordinator never wakes up to open repair PRs — the cascade is effectively local-only.
Admin: Rebuild Index
If the registry index becomes corrupted, an admin can rebuild it from all published metadata:
curl -X POST https://<validator-url>/rebuild-index \
-H "X-API-Key: $AILANG_REGISTRY_API_KEY"
This scans all metadata.json files in the bucket and reconstructs index.json. Same API key auth as publish.
Available Packages
| Package | Description | Effects |
|---|---|---|
sunholo/auth | API key validation, HMAC hashing, bearer tokens | Pure |
sunholo/gcp_auth | GCP ADC OAuth2 token exchange, project detection | FS, Net, Env |
sunholo/http_helpers | HTTP request builders, auth headers, JSON response parsing | Net |
sunholo/logging | Structured JSON logging for Cloud Run | IO |
sunholo/config | Config loading from env vars with validation | Env |
sunholo/testing_utils | Test assertion helpers | Pure |
sunholo/registry_validator | Package validator written in AILANG (dogfooding) | IO, FS |
Source: github.com/sunholo-data/ailang-packages
Dual Hash Model
Each package in the lock file has two hashes:
| Hash | Changes When | Purpose |
|---|---|---|
| Content hash | Any source file changes | Reproducibility |
| Interface hash | Exports or effects change | Coordination — internal refactors don't cascade |
Backward Compatibility
Projects without ailang.toml work exactly as before. The package system is opt-in — existing module imports (std/..., local modules) are unaffected.
Workspace Pattern
Multi-package repos use path dependencies without needing a registry:
workspace/
types/
ailang.toml # name = "myorg/types"
parser/
ailang.toml # name = "myorg/parser", depends on types via path
app/
ailang.toml # depends on types + parser via path
Each package is independently valid. Workspaces are emergent from path-linked packages, not a separate manifest type.
AI Agent Coordination
The registry is more than a package manager — it's the coordination layer through which AI agents discover, share, and compose capabilities.
Why This Matters for Multi-Agent Workflows
When multiple AI agents work on a project (or across projects), the registry provides the shared vocabulary:
-
Effect ceilings — An agent can inspect
[effects].maxto know exactly what authority a package has. A package withmax = ["IO"]cannot access the network, filesystem, or environment. This lets agents reason about trust boundaries without reading source code. -
Interface hashes — The interface hash changes only when exports or effects change. An internal refactor (same exports, same effects) produces a new content hash but the same interface hash. Downstream agents can skip re-verification when only content changes.
-
AGENT.md discovery — Each package can include an
AGENT.md— a structured guide written for AI consumption, not human prose. The registry indexes whether a package has one (has_agent_doc: true), andailang pkg-docs vendor/nameserves it directly. -
Immutability — Once published, a version can never be overwritten. This means agents working concurrently can safely depend on a version without worrying about it changing underneath them.
-
Machine-readable metadata —
ai_summary,tags,effects, andhas_agent_docare all in the registry index as structured JSON. Agents can programmatically search and filter without parsing documentation.
Typical Agent Workflow
# 1. Discover what's available
ailang search "auth" --tag gcp
# 2. Read the AI-specific documentation
ailang pkg-docs sunholo/gcp-auth
# 3. Install with hash verification
ailang install sunholo/gcp-auth@0.1.0
# 4. Use in code
# import pkg/sunholo/gcp-auth/token (getAccessToken)
# 5. Publish reusable work back to the registry
ailang publish --dry-run # Validate first
ailang publish # Share with other agents
Writing Good AGENT.md
Structure your AGENT.md for machine consumption:
- Quick Start — minimal import + usage example (copy-pasteable)
- Exported Functions — signatures with effect requirements
- Common Patterns — typical workflows with the package
- Effect Requirements — what
--capsflags the consumer needs
Package Coordination Messages
When packages are published or upgraded, the system emits structured coordination messages to notify affected workspaces. This enables multi-agent coordination at package scale.
Auto-Emitted Events
Running ailang publish automatically emits:
upgrade-available— new version with change class (A=internal, B=additive, C=contract)interface-change-notice— when exported API (interface hash) changedeffect-widening-warning— when effect ceiling expanded (e.g., addedNet)
Package-Scoped Inboxes
Messages route to typed inboxes: pkg:sunholo/auth, workspace:docparse, team:registry-admin.
# Notify downstream consumers
ailang pkg notify-upgrade sunholo/auth@0.2.0 --summary "Tightened validation"
# Find who depends on your package
ailang pkg affected-by sunholo/auth
# View coordination messages
ailang messages list --inbox pkg:sunholo/auth
For AI Agents
When upgrading a dependency, check for coordination messages:
ailang messages list --inbox workspace:<your-workspace> --unread- Review any
upgrade-availableorinterface-change-noticemessages - Run compatibility checks
- Send a
compatibility-reportback to the package inbox
See Agent Messaging Guide for full details.
Troubleshooting
Common Errors
| Error | Cause | Fix |
|---|---|---|
409 Conflict on publish | Version already exists | Bump version in ailang.toml |
401 Unauthorized | Missing or invalid API key | Set AILANG_REGISTRY_API_KEY |
hash mismatch on install | Corrupted download or tampered package | Retry; if persistent, report to registry admin |
effect ceiling violation | Package uses effects not in [effects].max | Add missing effects to ailang.toml (note: Debug is exempt as a ghost effect) |
package not found | Wrong name or not yet published | Check spelling with ailang search |
IMP010: symbol not exported | Type missing export keyword | Add export type Foo = ... |
LDR001: module not found | Missing dependency or wrong import | Add dep + ailang lock |
cannot unify type constructor X with TRecord | Type alias not exported | Add export type in defining module |
PAR_HYPHEN_IN_MODULE | Hyphen in module path | Use underscores: billing_store |
Common Pitfalls
1. Hyphens in module names: Directory names can use hyphens (billing-store/) but module paths must use underscores (module sunholo/billing_store/...). Hyphens parse as subtraction.
2. Missing export type: Any type used by another package must have export type, not just type. This includes record types AND ADTs.
3. Use ./ for intra-package siblings, pkg/ for external: Three-way import distinction:
import ./plan (Plan, lookupPlan) -- LOCAL: sibling in same package
import pkg/sunholo/firestore/client (getDoc) -- EXTERNAL: different package
import std/result (Ok, Err) -- STDLIB: bundled
./ resolves in module namespace: if current module is a/b/c, then ./d means a/b/d.
4. Ok/Err not in prelude: Every file that uses Result needs import std/result (Ok, Err).
5. Run ailang lock after changing dependencies: The lockfile must be regenerated whenever [dependencies] changes.