               :when (not (#{"=constant" "=variable" "=function" "filter"} name))]
         (let [fields (get @clause-id->fields clause-id)
               var->key (atom {})]
-          (doseq [[_ field-type key val] fields]
+          (doseq [[_ field-type key val] fields
+                  :when (not (#{"aggregate-variable" "aggregate-function"} key))]
             (if (= field-type "constant")
               ;; rewrite (foo 1) to (foo x) (constant= x 1)
               (let [new-var (new-id)

(.get-or-create-index kn "know" "foo" #js ["x" "y"])

(.get-or-create-index kn "know" "bar" #js ["z"])
)

              output-names #js []
              output-fields #js []]

          (doseq [[_ clause-type clause-id clause-name] clauses
                  :when (not= clause-type "when")]
            (let [clause-fields (get @clause-id->fields clause-id)
                  fields (make-array num-vars)
                  aggregate-variable (first (for [[_ field-type key val] clause-fields
                                                  :when (= key "aggregate-variable")]
                                              val))

                  aggregate-function (first (for [[_ field-type key val] clause-fields
                                                  :when (= key "aggregate-function")]
                                              val))
                  output-name (if (nil? aggregate-variable)
                                clause-name
                                (str "aggregate-" rule-id))
                  output-type (if (nil? aggregate-variable)
                                clause-type
                                "know")]

              ;; add to rule outputs
              (doseq [[_ field-type key val] clause-fields
                      :when (= field-type "variable")]
                (aset fields (var->ix val) key))
              (apush output-kinds output-type)
              (apush output-names output-name)
              (apush output-fields fields)

              ;; ensure indexes exist
              (let [filtered-fields (into-array (filter #(not (or (nil? %) (= aggregate-variable %))) fields))
                    ;; make sure aggregate-variable is last if it exists
                    _ (when-not (nil? aggregate-variable)
                        (apush filtered-fields aggregate-variable))
                    ;; TODO get transient? from schema instead
                    transient? (identical? clause-type "know")]
                (assert (not= (not transient?) (get @name->transient? clause-name)) clause-name)
                (swap! name->transient? assoc clause-name transient?)
                (swap! kind->name->rules update-in [clause-type clause-name] #(or % #{}))
                (.ensure-index kn "know" clause-name filtered-fields)
                (when (false? transient?)
                  (.ensure-index kn "forget" clause-name filtered-fields)
                  (.ensure-index kn "remember" clause-name filtered-fields))

                ;; create aggregate flow if needed
                (when-not (nil? aggregate-variable)
                  (let [input-index (.get-or-create-index kn "know" output-name filtered-fields)
                        aggregate-function (aget js/aurora.aggregates aggregate-function)]
                    (swap! name->transient? assoc output-name true)
                    (swap! kind->name->rules update-in ["know" output-name] #(conj (or % #{}) output-name))
                    (swap! rule->flow assoc output-name (AggregateFlow. input-index clause-type clause-name filtered-fields aggregate-function)))))))

          (swap! rule->flow assoc rule-id (SolverFlow. solver output-kinds output-names output-fields)))))

    ;; TODO stratify
    (Flows. (clj->js (map first @rule->flow)) (clj->js @rule->flow) #js {} (clj->js @kind->name->rules) (clj->js @name->transient?))))
