diff --git a/projects/lilith/BUILD b/projects/lilith/BUILD
new file mode 100644
index 0000000..58e3348
--- /dev/null
+++ b/projects/lilith/BUILD
@@ -0,0 +1,9 @@
+py_project(
+    name = "lilith",
+    lib_deps = [
+
+    ],
+    test_deps = [
+        py_requirement("hypothesis"),
+    ]
+)
diff --git a/projects/lilith/README.md b/projects/lilith/README.md
new file mode 100644
index 0000000..01d4295
--- /dev/null
+++ b/projects/lilith/README.md
@@ -0,0 +1,81 @@
+# Lilith
+
+> The theme of the jam is "first-class comments".
+
+```c
+// foo bar baz
+```
+
+``` clojure
+; text that the parser throws away
+
+(def <name> <value>)
+(def ^{:doc "foo bar baz"} <name> <value>)
+```
+
+``` python
+"""modules have __doc__ as a property defined by the FIRST heredoc-string in the file"""
+
+def foo(a, b):
+    """Adds a and b.
+
+    >>> foo(1, 2)
+    3
+    """
+
+    return a + b
+
+```
+
+``` clojure
+(defn foo [a b]
+    "Adds a and b
+
+    >>> (foo 1 2)
+    => 3
+    "
+    (+ a b))
+```
+
+- Object code
+- .... Other media?
+
+``` clojure
+(defn foo [a b]
+    (!doc "")
+    (!doctest "")
+    (!test "")
+
+    (!impl
+       (+ a b)))
+```
+
+``` clojure
+(defn foo [a b]
+    "---
+    doc: |
+       Adds a and b
+
+    ---
+    doctest:
+      - in: (foo 1 2)
+        out: 3
+    "
+    )
+```
+
+``` org
+#+TITLE: foo
+* my bullet
+  #+BEGIN_SRC clojure
+    (defn foo [a b] (+ a b))
+  #+END_SRC
+```
+
+`org-babel` `org-babel-tagle`
+
+``` clojure
+(defn foo [] ..)
+```
+
+####################################################################################################
diff --git a/projects/lilith/setup.py b/projects/lilith/setup.py
new file mode 100644
index 0000000..7af5daa
--- /dev/null
+++ b/projects/lilith/setup.py
@@ -0,0 +1,20 @@
+from setuptools import setup
+
+setup(
+    name="arrdem.lilith",
+    # Package metadata
+    version="0.0.0",
+    license="MIT",
+    # Package setup
+    package_dir={"": "src/python"},
+    packages=[
+        "lilith",
+    ],
+    install_requires=[
+        "lark",
+    ],
+    test_requires=[
+        "pytest",
+        "hypothesis",
+    ],
+)
diff --git a/projects/lilith/src/lilith/designdoc.lil b/projects/lilith/src/lilith/designdoc.lil
new file mode 100644
index 0000000..e5d18fc
--- /dev/null
+++ b/projects/lilith/src/lilith/designdoc.lil
@@ -0,0 +1,47 @@
+!defscope[designdoc]
+!scope[designdoc]
+
+!def[pitch]
+!frag[lang: md]
+
+# The Lilith Pitch
+
+Code is more than .. just code for the compiler.
+We write lots of artifacts related to our software
+ - Design document
+ - Formatted API listing
+ - Formatted book which is MORE than just the API / source listing
+ - A spec? OpenAPI example
+ - Doctests? / inline test cases
+
+Theory - most of your "program" isn't code.
+idea: the parser is going to IGNORE stuff by default.
+
+Let's build an M-expression syntax with support for multiple co-equal languages and fragments/scopes with a meta-syntax allowing for references between them and consuming fragment-notation-defined blocks? (FIXME: better word?)
+
+!def[sytnax]
+!frag[lang: md]
+
+\![] bang-brackets is a meta-syntactic directive used both by the lil tangler and the lil object language.
+
+Programs consist of many fragments, which can reference each-other and from which executable framents are woven.
+
+Fragments define interpretations and scopes.
+
+Interpretations control sub-languages, the bang-bracket notation is more of a meta-language than a target language.
+Interpretation is defined by the `lang:` key of a bang-bracket.
+
+FIXME: how to define more langs?
+
+FIXME: how do scopes get defined?
+\!defscope[name: <>]
+
+FIXME: we need bare words, we need strings
+
+FIXME: We need the object language
+
+!def[main]
+!frag[lang: lil]
+; is importing a bang-operation?
+import[tagle]
+print[tangle[pitch, syntax]]
diff --git a/projects/lilith/src/python/lilith/parser.py b/projects/lilith/src/python/lilith/parser.py
new file mode 100644
index 0000000..f183c3d
--- /dev/null
+++ b/projects/lilith/src/python/lilith/parser.py
@@ -0,0 +1,84 @@
+"""
+Variously poor parsing for Lilith.
+"""
+
+import typing as t
+import re
+
+import lark
+
+class Block(t.NamedTuple):
+    tag: str
+    args: list
+    kwargs: list
+    body_lines: list
+
+    @property
+    def body(self):
+        return "\n".join(self.body_lines)
+
+
+class TreeToTuples(lark.Transformer):
+    def kwargs(self, args):
+        d = {}
+        key, val = args[0:2]
+        if len(args) == 3:
+            d.update(args[2])
+        d[key.value] = val.value
+        return d
+
+    def args(self, args):
+        _args = [args[0].value]
+        if len(args) == 2:
+            _args = _args + args[1]
+        return _args
+
+    def header(self, parse_args):
+        print("Header", parse_args)
+        tag = None
+        args = None
+        kwargs = None
+        body = []
+
+        iargs = iter(parse_args[1])
+        tag = parse_args[0]
+        v = next(iargs, None)
+        if isinstance(v, list):
+            args = v
+            v = next(iargs, None)
+        if isinstance(v, dict):
+            kwargs = v
+
+        return Block(tag, args, kwargs, body)
+
+
+block_grammar = lark.Lark("""
+%import common.WORD
+%import common.WS
+%ignore WS
+?start: header
+
+args: WORD ("," args)?
+kwargs: WORD ":" WORD ("," kwargs)?
+arguments: args "," kwargs | args | kwargs
+header: "!" WORD "[" arguments? "]"
+""",
+                          parser='lalr',
+                          transformer=TreeToTuples())
+
+
+
+def shotgun_parse(buff: str) -> t.List[object]:
+    def _parse():
+        block = None
+        for line in buff.splitlines():
+            if line.startswith("!"):
+                if block:
+                    yield block
+                block = [line]
+            else:
+                block.append(line)
+        if block:
+            yield block
+
+    return list(_parse())
diff --git a/projects/lilith/test/python/conftest.py b/projects/lilith/test/python/conftest.py
new file mode 100644
index 0000000..58a7168
--- /dev/null
+++ b/projects/lilith/test/python/conftest.py
@@ -0,0 +1,3 @@
+"""
+Pytest fixtures.
+"""
diff --git a/projects/lilith/test/python/test_parser.py b/projects/lilith/test/python/test_parser.py
new file mode 100644
index 0000000..f6bec6f
--- /dev/null
+++ b/projects/lilith/test/python/test_parser.py
@@ -0,0 +1,26 @@
+"""tests covering the Lilith parser."""
+
+from lilith.parser import block_grammar, Block
+
+import pytest
+
+@pytest.mark.parametrize('example, result', [
+    ('!def[syntax]',
+     Block('def', ['syntax'], None, [])),
+    ('!frag[lang: md]',
+     Block('frag', None, {'lang': 'md'}, [])),
+    ('!frag[foo, lang: md]',
+     Block('frag', ['foo'], {'lang': 'md'}, [])),
+])
+def test_parse_header(example, result):
+    assert block_grammar.parse(example) == result
+
+
+(
+    """!def[designdoc]
+!frag[lang: md]
+# Designdoc
+
+A design document""",
+     None
+)