Modules and imports
Harn supports splitting code across files using import and top-level fn declarations.
Importing files
import "lib/helpers.harn"
The extension is optional — these are equivalent:
import "lib/helpers.harn"
import "lib/helpers"
Import paths are resolved relative to the current file’s directory.
If main.harn imports "lib/helpers", it looks for lib/helpers.harn
next to main.harn.
Writing a library file
Library files contain top-level fn declarations:
// lib/math.harn
fn double(x) {
return x * 2
}
fn clamp(value, low, high) {
if value < low { return low }
if value > high { return high }
return value
}
When imported, these functions become available in the importing file’s scope.
Using imported functions
import "lib/math"
pipeline default(task) {
log(double(21)) // 42
log(clamp(150, 0, 100)) // 100
}
Importing pipelines
Imported files can also contain pipelines, which are registered globally by name:
// lib/analysis.harn
pipeline analyze(task) {
log("Analyzing: ${task}")
}
import "lib/analysis"
pipeline default(task) {
// the "analyze" pipeline is now registered and available
}
Standard library modules
Harn includes built-in modules that are compiled into the interpreter.
Import them with the std/ prefix:
import "std/text"
import "std/collections"
import "std/math"
import "std/path"
import "std/json"
std/text
Text processing utilities for LLM output and code analysis:
| Function | Description |
|---|---|
extract_paths(text) | Extract file paths from text, filtering comments and validating extensions |
parse_cells(response) | Parse fenced code blocks from LLM output. Returns [{type, lang, code}] |
filter_test_cells(cells, target_file?) | Filter cells to keep code blocks and write_file calls |
truncate_head_tail(text, n) | Keep first/last n lines with omission marker |
detect_compile_error(output) | Check for compile error patterns (SyntaxError, etc.) |
has_got_want(output) | Check for got/want test failure patterns |
format_test_errors(output) | Extract error-relevant lines (max 20) |
std/collections
Collection utilities and store helpers:
| Function | Description |
|---|---|
filter_nil(dict) | Remove entries where value is nil, empty string, or “null” |
store_stale(key, max_age_seconds) | Check if a store key’s timestamp is stale |
store_refresh(key) | Update a store key’s timestamp to now |
std/math
Extended math utilities:
| Function | Description |
|---|---|
clamp(value, lo, hi) | Clamp a value between min and max |
lerp(a, b, t) | Linear interpolation between a and b by t (0..1) |
map_range(value, in_lo, in_hi, out_lo, out_hi) | Map a value from one range to another |
deg_to_rad(degrees) | Convert degrees to radians |
rad_to_deg(radians) | Convert radians to degrees |
sum(items) | Sum a list of numbers |
avg(items) | Average of a list of numbers (returns 0 for empty lists) |
import "std/math"
log(clamp(150, 0, 100)) // 100
log(lerp(0, 10, 0.5)) // 5
log(map_range(50, 0, 100, 0, 1)) // 0.5
log(sum([1, 2, 3, 4])) // 10
log(avg([10, 20, 30])) // 20
std/path
Path manipulation utilities:
| Function | Description |
|---|---|
ext(path) | Get the file extension without the dot |
stem(path) | Get the filename without extension |
normalize(path) | Normalize path separators (backslash to forward slash) |
is_absolute(path) | Check if a path is absolute |
list_files(dir) | List files in a directory (one level) |
list_dirs(dir) | List subdirectories in a directory |
import "std/path"
log(ext("main.harn")) // "harn"
log(stem("/src/main.harn")) // "main"
log(is_absolute("/usr/bin")) // true
let files = list_files("src")
let dirs = list_dirs(".")
std/json
JSON utility patterns:
| Function | Description |
|---|---|
pretty(value) | Pretty-print a value as indented JSON |
safe_parse(text) | Safely parse JSON, returning nil on failure instead of throwing |
merge(a, b) | Shallow-merge two dicts (keys in b override keys in a) |
pick(data, keys) | Pick specific keys from a dict |
omit(data, keys) | Omit specific keys from a dict |
import "std/json"
let data = safe_parse("{\"x\": 1}") // {x: 1}, or nil on bad input
let merged = merge({a: 1}, {b: 2}) // {a: 1, b: 2}
let subset = pick({a: 1, b: 2, c: 3}, ["a", "c"]) // {a: 1, c: 3}
let rest = omit({a: 1, b: 2, c: 3}, ["b"]) // {a: 1, c: 3}
Selective imports
Import specific functions from any module:
import { extract_paths, parse_cells } from "std/text"
Import behavior
- The imported file is parsed and executed
- Pipelines in the imported file are registered by name
- Non-pipeline top-level statements (fn declarations, let bindings) are executed, making their values available
- Circular imports are detected and skipped (each file is imported at most once)
- The working directory is temporarily changed to the imported file’s directory, so nested imports resolve correctly
Pipeline inheritance
Pipelines can extend other pipelines:
pipeline base(task) {
log("Step 1: setup")
log("Step 2: execute")
log("Step 3: cleanup")
}
pipeline custom(task) extends base {
override fn setup() {
log("Custom setup")
}
}
If the child pipeline has override declarations, the parent’s body runs
with the overrides applied. If the child has no overrides, the child’s body
replaces the parent’s entirely.
Organizing a project
A typical project structure:
my-project/
main.harn
lib/
context.harn # shared context-gathering functions
agent.harn # shared agent utility functions
helpers.harn # general-purpose utilities
// main.harn
import "lib/context"
import "lib/agent"
import "lib/helpers"
pipeline default(task, project) {
let ctx = gather_context(task, project)
let result = run_agent(ctx)
finalize(result)
}