diff --git a/Makefile b/Makefile
index 4f1db35b..15b4a7ac 100644
--- a/Makefile
+++ b/Makefile
@@ -32,8 +32,8 @@ COMPILED_LIBS = $(CHIBI_COMPILED_LIBS) $(CHIBI_IO_COMPILED_LIBS) \
 BASE_INCLUDES = include/chibi/sexp.h include/chibi/features.h include/chibi/install.h include/chibi/bignum.h
 INCLUDES = $(BASE_INCLUDES) include/chibi/eval.h
 
-MODULE_DOCS := ast disasm equiv filesystem generic heap-stats io loop \
-	match mime modules net pathname process repl scribble stty \
+MODULE_DOCS := ast config disasm equiv filesystem generic heap-stats io \
+	loop match mime modules net pathname process repl scribble stty \
 	system test time trace type-inference uri weak
 
 HTML_LIBS = $(MODULE_DOCS:%=doc/lib/chibi/%.html)
diff --git a/doc/chibi.scrbl b/doc/chibi.scrbl
index 984cee4b..469b7048 100755
--- a/doc/chibi.scrbl
+++ b/doc/chibi.scrbl
@@ -1061,9 +1061,10 @@ or the symbol null, indicating a NULL-terminated array.}}
 @section{Standard Modules}
 
 A number of SRFIs are provided in the default installation.  Note that
-SRFIs 0, 6, 22, 23, 46 and 62 are built into the default environment so
-there's no need to import them.  This list includes popular SRFIs or
-SRFIs used in standard Chibi modules
+SRFIs 0, 6, 23, 46 and 62 are built into the default environment so
+there's no need to import them.  SRFI 22 is available with the "-r"
+command-line option.  This list includes popular SRFIs or SRFIs used
+in standard Chibi modules
 
 @itemlist[
 
@@ -1100,6 +1101,8 @@ namespace.
 
 @item{@hyperlink["lib/chibi/ast.html"]{(chibi ast) - Abstract Syntax Tree and other internal data types}}
 
+@item{@hyperlink["lib/chibi/config.html"]{(chibi config) - General configuration management}}
+
 @item{@hyperlink["lib/chibi/disasm.html"]{(chibi disasm) - Disassembler for the virtual machine}}
 
 @item{@hyperlink["lib/chibi/equiv.html"]{(chibi equiv) - A version of @scheme{equal?} which is guaranteed to terminate}}
diff --git a/lib/chibi/config.scm b/lib/chibi/config.scm
new file mode 100644
index 00000000..1a7ac693
--- /dev/null
+++ b/lib/chibi/config.scm
@@ -0,0 +1,460 @@
+;; config.scm -- general configuration management
+;; Copyright (c) 2012 Alex Shinn.  All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+;;> This is a library for unified configuration management.
+;;> Essentially it provides an abstract collection data type for
+;;> looking up named values, two or more of which can be chained
+;;> together.  Values from more recent collections can be preferred as
+;;> with an environment, or the values at multiple levels can be
+;;> flattened together.  Convenience routines are provided from
+;;> loading these collections from files while allowing extensions
+;;> such as configurations from command-line options.
+
+;;> @subsubsection{Background}
+;;>
+;;> As any application grows to sufficient complexity, it acquires
+;;> options and behaviors that one may want to modify at startup or
+;;> runtime.  The traditional approach is a combination of
+;;> command-line options, config files, environment variables, and/or
+;;> other specialized settings.  These all have various pros and cons:
+;;>
+;;> @table[(^ (border 1) (style border-collapse:collapse) (width "100%"))]{
+;;> @tr{@th{name} @th{pros} @th{cons}}
+;;> @tr{@td{environment variables}
+;;>     @td{implicit - no need to retype; can share between applications}
+;;>     @td{unclear when set; unexpected differences between users; limited size}}
+;;> @tr{@td{command-line options}
+;;>     @td{explicit - visible each time a command is run; }
+;;>     @td{verbose; limited size}}
+;;> @tr{@td{config files}
+;;>     @td{implicit; preserved - can be shared and version controlled}
+;;>     @td{requires a parser}}
+;;> }
+;;>
+;;> Environment variables are convenient for broad preferences, used
+;;> by many different applications, and unlikely to change per user.
+;;> Command-line options are best for settings that are likely to
+;;> change between invocations of a program.  Anything else is best
+;;> stored in a config file.  If there are settings that multiple
+;;> users of a group or whole system are likely to want to share, then
+;;> it makes sense to cascade multiple config files.
+
+;;> @subsubsection{Syntax}
+;;>
+;;> With any other language there is a question of config file syntax,
+;;> and a few popular choices exist such as .ini syntax.  With Scheme
+;;> the obvious choice is sexps, generally as an alist.  We use a
+;;> single alist for the whole file, with symbols for keys and
+;;> arbitrary sexps for values.  The alists are intended primarily for
+;;> editing by hand and need not be dotted, but the interface allows
+;;> dotted values.  Disambiguation is handled as with two separate
+;;> functions, @scheme{(conf-get config key)} and
+;;> @scheme{(conf-get-list config key)}, which both retrieve the value
+;;> associated with @var{key} from @var{config}, in the latter case
+;;> coercing to a list.  The result is determined according to the
+;;> structure of the alist cell as follows:
+;;>
+;;> @table[(^ (border 1) (style border-collapse:collapse) (width "100%"))]{
+;;> @tr{@th{Cell} @th{@scheme{conf-get} result} @th{@scheme{conf-get-list} result}}
+;;> @tr{@td{@scheme{(key)}} @td{@scheme{()}} @td{@scheme{()}}}
+;;> @tr{@td{@scheme{(key . non-list-value)}} @td{@scheme{non-list-value}} @td{@scheme{(non-list-value)}}}
+;;> @tr{@td{@scheme{(key non-list-value)}} @td{@scheme{non-list-value}} @td{@scheme{(non-list-value)}}}
+;;> @tr{@td{@scheme{(key (value1 value2 ...))}} @td{@scheme{(value1 value2 ...)}} @td{@scheme{(value1 value2 ...)}}}
+;;> @tr{@td{@scheme{(key value1 value2 ...)}} @td{@scheme{(value1 value2 ...)}} @td{@scheme{(value1 value2 ...)}}}
+;;> }
+;;>
+;;> Thus writing the non-dotted value will always do what you want.
+;;> Specifically, the only thing to be careful of is if you want a
+;;> single-element list value, even with @scheme{conf-get}, you should
+;;> write @scheme{(key (value))}.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;> @subsubsection{Interface}
+
+;;> Returns true iff @var{x} is a config object.
+
+(define-record-type Config
+  (make-conf alist parent source timestamp)
+  conf?
+  (alist conf-alist conf-alist-set!)
+  (parent conf-parent conf-parent-set!)
+  (source conf-source conf-source-set!)
+  (timestamp conf-timestamp conf-timestamp-set!))
+
+(define (assq-tail key alist)
+  (let lp ((ls alist))
+    (and (pair? ls)
+         (if (and (pair? (car ls)) (eq? key (caar ls)))
+             ls
+             (lp (cdr ls))))))
+
+(define (assq-chain key alist)
+  (let ((x (assq-tail (car key) alist)))
+    (and x
+         (if (null? (cdr key))
+             (car x)
+             (or (assq-chain (cdr key) (cdar x))
+                 (assq-chain key (cdr x)))))))
+
+(define (assq-split key alist)
+  (let lp ((ls alist) (rev '()))
+    (cond
+     ((null? ls) #f)
+     ((and (pair? (car ls)) (eq? key (caar ls))) (cons (reverse rev) ls))
+     (else (lp (cdr ls) (cons (car ls) rev))))))
+
+(define (read-from-file file . opt)
+  (guard (exn (else (and (pair? opt) (car opt))))
+    (call-with-input-file file read)))
+
+(define (alist? x)
+  (and (list? x) (every pair? x)))
+
+;;> Returns just the base of @var{config} without any parent.
+
+(define (conf-head config)
+  (make-conf
+   (conf-alist config) #f (conf-source config) (conf-timestamp config)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Loading from files.
+
+;;> @subsubsubsection{@rawcode{(conf-load file [conf])}}
+
+;;> Loads the config file @var{file}, prepending to @var{conf} if
+;;> provided.
+
+(define (conf-load file . o)
+  (make-conf
+   (read-from-file file '())
+   (if (pair? o) (car o) '())
+   file
+   (current-second)))
+
+;;> Search for and load any files named @var{file} in the
+;;> @var{config-path}, which should be a list of strings.
+
+(define (conf-load-in-path config-path file)
+  (cond
+   ((equal? file "")
+    (error "can't load from empty filename" file))
+   ((eqv? #\/ (string-ref file 0))
+    (conf-load file))
+   (else
+    (let lp ((ls (reverse config-path)) (res #f))
+      (if (null? ls)
+          (or res (make-conf '() #f #f (current-second)))
+          (let ((path (string-append (car ls) "/" file)))
+            (if (file-exists? path)
+                (lp (cdr ls) (conf-load path res))
+                (lp (cdr ls) res))))))))
+
+;;> @subsubsubsection{@rawcode{(conf-load-cascaded config-path file [include-keyword])}}
+
+;;> Similar to conf-load-in-path, but also recursively loads any
+;;> "include" config files, indicated by a top-level
+;;> @var{include-keyword} with either a string or symbol value.
+;;> Includes are loaded relative to the current file, and cycles
+;;> automatically ignored.
+
+(define (conf-load-cascaded config-path file . o)
+  (define (path-directory file)
+    (let lp ((i (string-length file)))
+      (cond ((zero? i) "./")
+            ((eqv? #\/ (string-ref file (- i 1))) (substring file 0 i))
+            (else (lp (- i 1))))))
+  (define (path-relative file from)
+    (if (eqv? #\/ (string-ref file 0))
+        file
+        (string-append (path-directory from) file)))
+  (let ((include-keyword (if (pair? o) (car o) 'include)))
+    (let load ((ls (list (cons file (and (pair? o) (pair? (cdr o)) (cadr o)))))
+               (seen '())
+               (res '()))
+      (cond
+       ((null? ls)
+        res)
+       (else
+        (let ((file (if (symbol? (caar ls))
+                        (symbol->string (caar ls))
+                        (caar ls)))
+              (depth (cdar ls)))
+          (cond
+           ((member file seen)
+            (load (cdr ls) seen res))
+           ((and (number? depth) (<= depth 0))
+            (load (cdr ls) seen res))
+           (else
+            (let* ((config (conf-load-in-path config-path file))
+                   (includes (conf-get-list config include-keyword)))
+              (load (append (cdr ls)
+                            (map (lambda (x)
+                                   (cons (path-relative x file)
+                                         (and (number? depth) (- depth 1))))
+                                 includes))
+                    (cons file seen)
+                    (append res config)))))))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (conf-get-cell config key)
+  (cond
+   ((pair? key)
+    (if (null? (cdr key))
+        (conf-get-cell config (car key))
+        (any (lambda (x) (assq-chain key x)) config)))
+   (else
+    (let search ((config config))
+      (and config
+           (or (assq key (conf-alist config))
+               (search (conf-parent config))))))))
+
+;;> @subsubsubsection{@rawcode{(conf-get config key [default])}}
+
+;;> Basic config lookup - retrieves the value from @var{config}
+;;> associated with @var{key}.  If not present, return @var{default}.
+;;> In @scheme{conf-get} and related accessors @var{key} can be either
+;;> a symbol, or a list of symbols.  In the latter case, each symbol
+;;> is used as a key in turn, with the value taken as an alist to
+;;> further lookup values in.
+
+(define (conf-get config key . opt)
+  (let ((cell (conf-get-cell config key)))
+    (if (not cell)
+        (and (pair? opt) (car opt))
+        (if (and (pair? (cdr cell)) (null? (cddr cell)))
+            (cadr cell)
+            (cdr cell)))))
+
+;;> @subsubsubsection{@rawcode{(conf-get-list config key [default])}}
+
+;;> Equivalent to @scheme{conf-get} but coerces its result to a list
+;;> as described in the syntax section.
+
+(define (conf-get-list config key . opt)
+  (let ((res (conf-get config key)))
+    (if res
+        (if (or (pair? res) (null? res)) res (list res))
+        (if (pair? opt) (car opt) '()))))
+
+;;> Equivalent to @scheme{conf-get-list} but returns a list of all
+
+(define (conf-get-multi config key)
+  (if (not config)
+      '()
+      (append (conf-get-list (conf-head config))
+              (conf-get-multi (conf-parent config) key))))
+
+;;> Extends the config with anadditional alist.
+
+(define (conf-extend config alist . o)
+  (if (pair? alist)
+      (make-conf alist config (and (pair? o) (car o)) (current-second))
+      config))
+
+;;> Joins two configs.
+
+(define (conf-append a b)
+  (let ((parent (if (conf-parent a) (conf-append (conf-parent a) b) b)))
+    (make-conf (conf-alist a) parent (conf-source a) (conf-timestamp a))))
+
+;;> Utility to create an alist cell representing the chained key
+;;> @var{key} mapped to @var{value}.
+
+(define (conf-unfold-key key value)
+  (if (null? (cdr key))
+      (cons (car key) value)
+      (list (car key) (conf-unfold-key (cdr key) value))))
+
+;;> Replace a new definition into the first config alist.
+
+(define (conf-set config key value)
+  (make-conf
+   (let lp ((key (if (not (list? key)) (list key) key))
+            (alist (conf-alist config)))
+     (cond
+      ((null? (cdr key))
+       (cons (cons (car key) value)
+             (remove (lambda (x) (and (pair? x) (eq? (car key) (car x))))
+                     alist)))
+      ((assq-split (car key) alist)
+       => (lambda (x)
+            (let ((left (car x))
+                  (right (cdr x)))
+              (append left
+                      (cons (cons (car key) (lp (cdr key) (cdar right)))
+                            (cdr right))))))
+      (else
+       (cons (conf-unfold-key key value) alist))))
+   (conf-parent config)
+   (conf-source config)
+   (conf-timestamp config)))
+
+;;> Lift specialized sections to the top-level of a config.
+
+(define (conf-specialize config key name)
+  (let lp ((ls config) (res '()))
+    (cond
+     ((null? ls) (reverse res))
+     ((assq key (car ls))
+      => (lambda (specialized)
+           (let ((named (assq name (cdr specialized))))
+             (if named
+                 (lp (cdr ls) (cons (car ls) (cons (cdr named) res)))
+                 (lp (cdr ls) (cons (car ls) res))))))
+     (else (lp (cdr ls) (cons (car ls) res))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;> @subsubsection{Config Verification}
+
+(define (conf-default-warn . args)
+  (for-each
+   (lambda (a) ((if (string? a) display write) a (current-error-port)))
+   args)
+  (newline (current-error-port))
+  #f)
+
+(define (conf-verify-symbol->predicate sym)
+  (case sym
+    ((integer) integer?)
+    ((number) number?)
+    ((list) list?)
+    ((alist) alist?)
+    ((boolean) boolean?)
+    ((char) char?)
+    ((string) string?)
+    ((symbol) symbol?)
+    ((pair) pair?)
+    ((filename dirname) string?)
+    (else (error "no known conf predicate for" sym))))
+
+;; non-short-circuit versions to report all warnings
+
+(define (and* . args)
+  (every (lambda (x) x) args))
+
+(define (every* pred ls)
+  (apply and* (map pred ls)))
+
+(define (conf-verify-match def cell warn)
+  (define (cell-value)
+    (if (and (pair? (cdr cell)) (null? (cddr cell))) (cadr cell) (cdr cell)))
+  (define (cell-list)
+    (if (and (pair? (cdr cell)) (null? (cddr cell)) (not (pair? (cadr cell))))
+        (list (cadr cell))
+        (cdr cell)))
+  (cond
+   ((procedure? def)
+    (or (def (cell-value))
+        (warn "bad conf value for " (car cell) ": " (cell-value))))
+   ((symbol? def)
+    (case def
+      ((existing-filename)
+       (cond
+        ((not (string? (cell-value)))
+         (warn "bad conf value for " (car cell)
+               ": expected a filename but got " (cell-value)))
+        ((not (file-exists? (cell-value)))
+         (warn "conf setting ~S references a non-existent file: ~S"
+               (car cell) (cell-value)))
+        (else
+         #t)))
+      ((existing-dirname)
+       (cond
+        ((not (string? (cell-value)))
+         (warn "bad conf value for " (car cell)
+               ": expected a dirname but got " (cell-value)))
+        ((not (file-directory? (cell-value)))
+         (cond
+          ((file-exists? (cell-value))
+           (warn "conf setting " (car cell)
+                 " expected a directory but found a file: " (cell-value)))
+          (else
+           (warn "conf setting " (car cell)
+                 " references a non-existent directory: " (cell-value)))))
+        (else
+         #t)))
+      ((integer number char string symbol filename dirname boolean pair)
+       (or ((conf-verify-symbol->predicate def) (cell-value))
+           (warn "bad conf value for " (car cell)
+                 ": expected " def " but got " (cell-value))))
+      ((list alist)
+       (or ((conf-verify-symbol->predicate def) (cell-list))
+           (warn "bad conf value for " (car cell)
+                 ": expected " def " but got " (cell-list))))
+      (else
+       (warn "bad conf spec list: " def))))
+   ((pair? def)
+    (case (car def)
+      ((cons)
+       (and*
+        (conf-verify-match
+         (cadr def) (cons `(car ,(car cell)) (car (cell-list))) warn)
+        (conf-verify-match
+         (car (cddr def)) (cons `(car ,(car cell)) (cdr (cell-list))) warn)))
+      ((list)
+       (and (list? (cell-list))
+            (every* (lambda (x)
+                      ;; (cons `(list ,(car cell)) x)
+                      (conf-verify-match (cadr def) x warn))
+                    (cell-list))))
+      ((alist)
+       (let ((key-def (cadr def))
+             (val-def (if (pair? (cddr def)) (car (cddr def)) (lambda (x) #t))))
+         (and (alist? (cell-list))
+              (every* (lambda (x)
+                        (and (pair? x)
+                             (conf-verify-match key-def (car x) warn)
+                             (conf-verify-match val-def (cell-value x) warn)))
+                      (cell-list)))))
+      ((conf)
+       (and (alist? (cell-list))
+            (conf-verify (cdr def) (list (cell-list)) warn)))
+      ((or)
+       (or (any (lambda (x) (conf-verify-match x cell (lambda (x) x)))
+                (cdr def))
+           (warn "bad spec value for " (car cell)
+                 ": expected " def " but got " (cell-value))))
+      ((member)
+       (or (member (cell-value) (cdr def))
+           (warn "bad spec value " (cell-value)
+                 " for " (car cell) ", expected one of " (cdr def))))
+      ((quote)
+       (or (equal? (cadr def) (cell-value))
+           (warn "bad conf value for " (car cell)
+                 ": expected '" (cadr def) " but got " (cell-value))))
+      (else
+       (warn "bad conf list spec name: " (car def)))))
+   (else
+    (or (equal? def (cell-value))
+        (warn "bad conf value for " (car cell)
+              ": expected " def " but got " (cell-value))))))
+
+(define (conf-verify-one spec cell warn)
+  (cond
+   ((not (pair? cell))
+    (warn "bad config entry: " cell))
+   ((not (symbol? (car cell)))
+    (warn "non-symbol config entry name: " (car cell)))
+   (else
+    (let ((def (assq (car cell) spec)))
+      (cond
+       ((not def)
+        (warn "unknown config entry: " (car cell)))
+       ((null? (cdr def)))
+       (else (conf-verify-match (cadr def) cell warn)))))))
+
+(define (conf-verify spec config . o)
+  (let ((warn (if (pair? o)
+                  (lambda args (apply (car o) args) #f)
+                  conf-default-warn)))
+    (let lp ((config config))
+      (cond
+       (config
+        (for-each
+         (lambda (cell) (conf-verify-one spec cell warn))
+         (conf-alist config))
+        (lp (conf-parent config)))))))
diff --git a/lib/chibi/config.sld b/lib/chibi/config.sld
new file mode 100644
index 00000000..89d44c7a
--- /dev/null
+++ b/lib/chibi/config.sld
@@ -0,0 +1,14 @@
+
+(define-library (chibi config)
+  (export make-conf conf? conf-load conf-load-in-path conf-load-cascaded
+          conf-verify conf-extend conf-append conf-set conf-unfold-key
+          conf-get conf-get-list conf-get-multi
+          conf-specialize read-from-file)
+  (import (scheme base) (scheme read) (scheme write) (scheme file)
+          (scheme time) (srfi 1))
+  ;; This is only used for config verification, it's acceptable to
+  ;; substitute file existence for the stronger directory check.
+  (cond-expand
+   (chibi (import (only (chibi filesystem) file-directory?)))
+   (else (begin (define file-directory? file-exists?))))
+  (include "config.scm"))