diff --git a/docs/almanac.md b/docs/almanac.md
index 3ceafad..d0c3826 100644
--- a/docs/almanac.md
+++ b/docs/almanac.md
@@ -3,6 +3,7 @@
 * [Overview](#overview)
 * [Methods](#methods)
     * [almanac.factValue(Fact fact, Object params, String path) -> Promise](#almanacfactvaluefact-fact-object-params-string-path---promise)
+    * [almanac.addFact(String id, Function [definitionFunc], Object [options])](#almanacaddfactstring-id-function-definitionfunc-object-options)
     * [almanac.addRuntimeFact(String factId, Mixed value)](#almanacaddruntimefactstring-factid-mixed-value)
     * [almanac.getEvents(String outcome) -> Events[]](#almanacgeteventsstring-outcome---events)
     * [almanac.getResults() -> RuleResults[]](#almanacgetresults---ruleresults)
@@ -33,8 +34,28 @@ almanac
   .then( value => console.log(value))
 ```
 
+### almanac.addFact(String id, Function [definitionFunc], Object [options])
+
+Sets a fact in the almanac. Used in conjunction with rule and engine event emissions.
+
+```js
+// constant facts:
+engine.addFact('speed-of-light', 299792458)
+
+// facts computed via function
+engine.addFact('account-type', function getAccountType(params, almanac) {
+  // ...
+})
+
+// facts with options:
+engine.addFact('account-type', function getAccountType(params, almanac) {
+  // ...
+}, { cache: false, priority: 500 })
+```
+
 ### almanac.addRuntimeFact(String factId, Mixed value)
 
+**Deprecated** Use `almanac.addFact` instead
 Sets a constant fact mid-run.  Often used in conjunction with rule and engine event emissions.
 
 ```js
diff --git a/docs/engine.md b/docs/engine.md
index 9712b5e..8546656 100644
--- a/docs/engine.md
+++ b/docs/engine.md
@@ -269,6 +269,16 @@ const {
 ```
 Link to the [Almanac documentation](./almanac.md)
 
+Optionally, you may specify a specific almanac instance via the almanac property.
+
+```js
+// create a custom Almanac
+const myCustomAlmanac = new CustomAlmanac();
+
+// run the engine with the custom almanac
+await engine.run({}, { almanac: myCustomAlmanac })
+```
+
 ### engine.stop() -> Engine
 
 Stops the rules engine from running the next priority set of Rules.  All remaining rules will be resolved as undefined,
diff --git a/examples/07-rule-chaining.js b/examples/07-rule-chaining.js
index eb32df2..e086cbc 100644
--- a/examples/07-rule-chaining.js
+++ b/examples/07-rule-chaining.js
@@ -39,16 +39,16 @@ async function start () {
     event: { type: 'drinks-screwdrivers' },
     priority: 10, // IMPORTANT!  Set a higher priority for the drinkRule, so it runs first
     onSuccess: async function (event, almanac) {
-      almanac.addRuntimeFact('screwdriverAficionado', true)
+      almanac.addFact('screwdriverAficionado', true)
 
       // asychronous operations can be performed within callbacks
       // engine execution will not proceed until the returned promises is resolved
       const accountId = await almanac.factValue('accountId')
       const accountInfo = await getAccountInformation(accountId)
-      almanac.addRuntimeFact('accountInfo', accountInfo)
+      almanac.addFact('accountInfo', accountInfo)
     },
     onFailure: function (event, almanac) {
-      almanac.addRuntimeFact('screwdriverAficionado', false)
+      almanac.addFact('screwdriverAficionado', false)
     }
   }
   engine.addRule(drinkRule)
diff --git a/examples/12-using-custom-almanac.js b/examples/12-using-custom-almanac.js
new file mode 100644
index 0000000..c94c398
--- /dev/null
+++ b/examples/12-using-custom-almanac.js
@@ -0,0 +1,94 @@
+'use strict'
+
+require('colors')
+const { Almanac, Engine } = require('json-rules-engine')
+
+/**
+ * Almanac that support piping values through named functions
+ */
+class PipedAlmanac extends Almanac {
+  constructor (options) {
+    super(options)
+    this.pipes = new Map()
+  }
+
+  addPipe (name, pipe) {
+    this.pipes.set(name, pipe)
+  }
+
+  factValue (factId, params, path) {
+    let pipes = []
+    if (params && 'pipes' in params && Array.isArray(params.pipes)) {
+      pipes = params.pipes
+      delete params.pipes
+    }
+    return super.factValue(factId, params, path).then(value => {
+      return pipes.reduce((value, pipeName) => {
+        const pipe = this.pipes.get(pipeName)
+        if (pipe) {
+          return pipe(value)
+        }
+        return value
+      }, value)
+    })
+  }
+}
+
+async function start () {
+  const engine = new Engine()
+    .addRule({
+      conditions: {
+        all: [
+          {
+            fact: 'age',
+            params: {
+              // the addOne pipe adds one to the value
+              pipes: ['addOne']
+            },
+            operator: 'greaterThanInclusive',
+            value: 21
+          }
+        ]
+      },
+      event: {
+        type: 'Over 21(ish)'
+      }
+    })
+
+  engine.on('success', async (event, almanac) => {
+    const name = await almanac.factValue('name')
+    const age = await almanac.factValue('age')
+    console.log(`${name} is ${age} years old and ${'is'.green} ${event.type}`)
+  })
+
+  engine.on('failure', async (event, almanac) => {
+    const name = await almanac.factValue('name')
+    const age = await almanac.factValue('age')
+    console.log(`${name} is ${age} years old and ${'is not'.red} ${event.type}`)
+  })
+
+  const createAlmanacWithPipes = () => {
+    const almanac = new PipedAlmanac()
+    almanac.addPipe('addOne', (v) => v + 1)
+    return almanac
+  }
+
+  // first run Bob who is less than 20
+  await engine.run({ name: 'Bob', age: 19 }, { almanac: createAlmanacWithPipes() })
+
+  // second run Alice who is 21
+  await engine.run({ name: 'Alice', age: 21 }, { almanac: createAlmanacWithPipes() })
+
+  // third run Chad who is 20
+  await engine.run({ name: 'Chad', age: 20 }, { almanac: createAlmanacWithPipes() })
+}
+
+start()
+
+/*
+ * OUTPUT:
+ *
+ * Bob is 19 years old and is not Over 21(ish)
+ * Alice is 21 years old and is Over 21(ish)
+ * Chad is 20 years old and is Over 21(ish)
+ */
diff --git a/src/almanac.js b/src/almanac.js
index cc8fdda..58cc243 100644
--- a/src/almanac.js
+++ b/src/almanac.js
@@ -17,25 +17,13 @@ function defaultPathResolver (value, path) {
  * A new almanac is used for every engine run()
  */
 export default class Almanac {
-  constructor (factMap, runtimeFacts = {}, options = {}) {
-    this.factMap = new Map(factMap)
+  constructor (options = {}) {
+    this.factMap = new Map()
     this.factResultsCache = new Map() // { cacheKey:  Promise<factValu> }
     this.allowUndefinedFacts = Boolean(options.allowUndefinedFacts)
     this.pathResolver = options.pathResolver || defaultPathResolver
     this.events = { success: [], failure: [] }
     this.ruleResults = []
-
-    for (const factId in runtimeFacts) {
-      let fact
-      if (runtimeFacts[factId] instanceof Fact) {
-        fact = runtimeFacts[factId]
-      } else {
-        fact = new Fact(factId, runtimeFacts[factId])
-      }
-
-      this._addConstantFact(fact)
-      debug(`almanac::constructor initialized runtime fact:${fact.id} with ${fact.value}<${typeof fact.value}>`)
-    }
   }
 
   /**
@@ -103,8 +91,32 @@ export default class Almanac {
     return factValue
   }
 
+  /**
+   * Add a fact definition to the engine.  Facts are called by rules as they are evaluated.
+   * @param {object|Fact} id - fact identifier or instance of Fact
+   * @param {function} definitionFunc - function to be called when computing the fact value for a given rule
+   * @param {Object} options - options to initialize the fact with. used when "id" is not a Fact instance
+   */
+  addFact (id, valueOrMethod, options) {
+    let factId = id
+    let fact
+    if (id instanceof Fact) {
+      factId = id.id
+      fact = id
+    } else {
+      fact = new Fact(id, valueOrMethod, options)
+    }
+    debug(`almanac::addFact id:${factId}`)
+    this.factMap.set(factId, fact)
+    if (fact.isConstant()) {
+      this._setFactValue(fact, {}, fact.value)
+    }
+    return this
+  }
+
   /**
    * Adds a constant fact during runtime.  Can be used mid-run() to add additional information
+   * @deprecated use addFact
    * @param {String} fact - fact identifier
    * @param {Mixed} value - constant value of the fact
    */
diff --git a/src/engine.js b/src/engine.js
index 70929c9..c67f3a2 100644
--- a/src/engine.js
+++ b/src/engine.js
@@ -261,14 +261,29 @@ class Engine extends EventEmitter {
    * @param  {Object} runOptions - run options
    * @return {Promise} resolves when the engine has completed running
    */
-  run (runtimeFacts = {}) {
+  run (runtimeFacts = {}, runOptions = {}) {
     debug('engine::run started')
     this.status = RUNNING
-    const almanacOptions = {
+
+    const almanac = runOptions.almanac || new Almanac({
       allowUndefinedFacts: this.allowUndefinedFacts,
       pathResolver: this.pathResolver
+    })
+
+    this.facts.forEach(fact => {
+      almanac.addFact(fact)
+    })
+    for (const factId in runtimeFacts) {
+      let fact
+      if (runtimeFacts[factId] instanceof Fact) {
+        fact = runtimeFacts[factId]
+      } else {
+        fact = new Fact(factId, runtimeFacts[factId])
+      }
+
+      almanac.addFact(fact)
+      debug(`engine::run initialized runtime fact:${fact.id} with ${fact.value}<${typeof fact.value}>`)
     }
-    const almanac = new Almanac(this.facts, runtimeFacts, almanacOptions)
     const orderedSets = this.prioritizeRules()
     let cursor = Promise.resolve()
     // for each rule set, evaluate in parallel,
diff --git a/src/json-rules-engine.js b/src/json-rules-engine.js
index 339c3c0..6f3b149 100644
--- a/src/json-rules-engine.js
+++ b/src/json-rules-engine.js
@@ -2,8 +2,9 @@ import Engine from './engine'
 import Fact from './fact'
 import Rule from './rule'
 import Operator from './operator'
+import Almanac from './almanac'
 
-export { Fact, Rule, Operator, Engine }
+export { Fact, Rule, Operator, Engine, Almanac }
 export default function (rules, options) {
   return new Engine(rules, options)
 }
diff --git a/test/almanac.test.js b/test/almanac.test.js
index d538101..b970183 100644
--- a/test/almanac.test.js
+++ b/test/almanac.test.js
@@ -23,20 +23,33 @@ describe('Almanac', () => {
     })
 
     it('adds runtime facts', () => {
-      almanac = new Almanac(new Map(), { modelId: 'XYZ' })
+      almanac = new Almanac()
+      almanac.addFact('modelId', 'XYZ')
       expect(almanac.factMap.get('modelId').value).to.equal('XYZ')
     })
   })
 
-  describe('constructor', () => {
+  describe('addFact', () => {
     it('supports runtime facts as key => values', () => {
-      almanac = new Almanac(new Map(), { fact1: 3 })
+      almanac = new Almanac()
+      almanac.addFact('fact1', 3)
       return expect(almanac.factValue('fact1')).to.eventually.equal(3)
     })
 
+    it('supporrts runtime facts as dynamic callbacks', async () => {
+      almanac = new Almanac()
+      almanac.addFact('fact1', () => {
+        factSpy()
+        return Promise.resolve(3)
+      })
+      await expect(almanac.factValue('fact1')).to.eventually.equal(3)
+      await expect(factSpy).to.have.been.calledOnce()
+    })
+
     it('supports runtime fact instances', () => {
       const fact = new Fact('fact1', 3)
-      almanac = new Almanac(new Map(), { fact1: fact })
+      almanac = new Almanac()
+      almanac.addFact(fact)
       return expect(almanac.factValue('fact1')).to.eventually.equal(fact.value)
     })
   })
@@ -69,9 +82,8 @@ describe('Almanac', () => {
         if (params.userId) return params.userId
         return 'unknown'
       })
-      const factMap = new Map()
-      factMap.set(fact.id, fact)
-      almanac = new Almanac(factMap)
+      almanac = new Almanac()
+      almanac.addFact(fact)
     })
 
     it('allows parameters to be passed to the fact', async () => {
@@ -106,10 +118,9 @@ describe('Almanac', () => {
 
   describe('_getFact', _ => {
     it('retrieves the fact object', () => {
-      const facts = new Map()
       const fact = new Fact('id', 1)
-      facts.set(fact.id, fact)
-      almanac = new Almanac(facts)
+      almanac = new Almanac()
+      almanac.addFact(fact)
       expect(almanac._getFact('id')).to.equal(fact)
     })
   })
@@ -124,9 +135,8 @@ describe('Almanac', () => {
 
     function setup (f = new Fact('id', 1)) {
       fact = f
-      const facts = new Map()
-      facts.set(fact.id, fact)
-      almanac = new Almanac(facts)
+      almanac = new Almanac()
+      almanac.addFact(fact)
     }
     let fact
     const FACT_VALUE = 2
@@ -154,9 +164,8 @@ describe('Almanac', () => {
           name: 'Thomas'
         }]
       })
-      const factMap = new Map()
-      factMap.set(fact.id, fact)
-      almanac = new Almanac(factMap)
+      almanac = new Almanac()
+      almanac.addFact(fact)
       const result = await almanac.factValue('foo', null, '$..name')
       expect(result).to.deep.equal(['George', 'Thomas'])
     })
@@ -167,9 +176,8 @@ describe('Almanac', () => {
           factSpy()
           return 'unknown'
         }, factOptions)
-        const factMap = new Map()
-        factMap.set(fact.id, fact)
-        almanac = new Almanac(factMap)
+        almanac = new Almanac()
+        almanac.addFact(fact)
       }
 
       it('evaluates the fact every time when fact caching is off', () => {
diff --git a/test/condition.test.js b/test/condition.test.js
index d4ab046..cd1d8f5 100644
--- a/test/condition.test.js
+++ b/test/condition.test.js
@@ -91,7 +91,8 @@ describe('Condition', () => {
       const properties = Object.assign({}, conditionBase, options)
       condition = new Condition(properties)
       const fact = new Fact(conditionBase.fact, factValue)
-      almanac = new Almanac(new Map([[fact.id, fact]]))
+      almanac = new Almanac()
+      almanac.addFact(fact)
     }
 
     context('validations', () => {
@@ -118,12 +119,14 @@ describe('Condition', () => {
     it('evaluates "equal" to check for undefined', async () => {
       condition = new Condition({ fact: 'age', operator: 'equal', value: undefined })
       let fact = new Fact('age', undefined)
-      almanac = new Almanac(new Map([[fact.id, fact]]))
+      almanac = new Almanac()
+      almanac.addFact(fact)
 
       expect((await condition.evaluate(almanac, operators)).result).to.equal(true)
 
       fact = new Fact('age', 1)
-      almanac = new Almanac(new Map([[fact.id, fact]]))
+      almanac = new Almanac()
+      almanac.addFact(fact)
       expect((await condition.evaluate(almanac, operators)).result).to.equal(false)
     })
 
@@ -235,8 +238,8 @@ describe('Condition', () => {
       it('extracts the object property values using its "path" property', async () => {
         const condition = new Condition({ operator: 'equal', path: '$.[0].id', fact: 'age', value: 50 })
         const ageFact = new Fact('age', [{ id: 50 }, { id: 60 }])
-        const facts = new Map([[ageFact.id, ageFact]])
-        const almanac = new Almanac(facts)
+        const almanac = new Almanac()
+        almanac.addFact(ageFact)
         expect((await condition.evaluate(almanac, operators)).result).to.equal(true)
 
         condition.value = 100 // negative case
@@ -245,8 +248,8 @@ describe('Condition', () => {
 
       it('ignores "path" when non-objects are returned by the fact', async () => {
         const ageFact = new Fact('age', 50)
-        const facts = new Map([[ageFact.id, ageFact]])
-        const almanac = new Almanac(facts)
+        const almanac = new Almanac()
+        almanac.addFact(ageFact)
 
         const condition = new Condition({ operator: 'equal', path: '$.[0].id', fact: 'age', value: 50 })
         expect((await condition.evaluate(almanac, operators, 50)).result).to.equal(true)
@@ -273,8 +276,8 @@ describe('Condition', () => {
         }
 
         const usersFact = new Fact('users', userData)
-        const facts = new Map([[usersFact.id, usersFact]])
-        const almanac = new Almanac(facts)
+        const almanac = new Almanac()
+        almanac.addFact(usersFact)
         expect((await condition.evaluate(almanac, operators)).result).to.equal(true)
 
         condition.value = 'work' // negative case
diff --git a/test/engine-run.test.js b/test/engine-run.test.js
index 18b8155..a96d950 100644
--- a/test/engine-run.test.js
+++ b/test/engine-run.test.js
@@ -113,4 +113,24 @@ describe('Engine: run', () => {
       })
     })
   })
+
+  describe('custom alamanc', () => {
+    class CapitalAlmanac extends Almanac {
+      factValue (factId, params, path) {
+        return super.factValue(factId, params, path).then(value => {
+          if (typeof value === 'string') {
+            return value.toUpperCase()
+          }
+          return value
+        })
+      }
+    }
+
+    it('returns the capitalized value when using the CapitalAlamanc', () => {
+      return engine.run({ greeting: 'hello', age: 30 }, { almanac: new CapitalAlmanac() }).then((results) => {
+        const fact = results.almanac.factValue('greeting')
+        return expect(fact).to.eventually.equal('HELLO')
+      })
+    })
+  })
 })
diff --git a/types/index.d.ts b/types/index.d.ts
index 35b380a..c6ace7f 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1,8 +1,15 @@
-export interface EngineOptions {
+export interface AlmanacOptions {
   allowUndefinedFacts?: boolean;
+  pathResolver?: PathResolver;
+}
+
+export interface EngineOptions extends AlmanacOptions {
   allowUndefinedConditions?: boolean;
   replaceFactsInEventParams?: boolean;
-  pathResolver?: PathResolver;
+}
+
+export interface RunOptions {
+  almanac?: Almanac;
 }
 
 export interface EngineResult {
@@ -48,7 +55,7 @@ export class Engine {
   on(eventName: "failure", handler: EventHandler): this;
   on(eventName: string, handler: EventHandler): this;
 
-  run(facts?: Record<string, any>): Promise<EngineResult>;
+  run(facts?: Record<string, any>, runOptions?: RunOptions): Promise<EngineResult>;
   stop(): this;
 }
 
@@ -66,11 +73,18 @@ export class Operator<A = unknown, B = unknown> {
 }
 
 export class Almanac {
+  constructor(options?: AlmanacOptions);
   factValue<T>(
     factId: string,
     params?: Record<string, any>,
     path?: string
   ): Promise<T>;
+  addFact<T>(fact: Fact<T>): this;
+  addFact<T>(
+    id: string,
+    valueCallback: DynamicFactCallback<T> | T,
+    options?: FactOptions
+  ): this;
   addRuntimeFact(factId: string, value: any): void;
 }