OP seems to be mostly interested in Mathematica's built-in capabilities for grammar definition, parsing, and interpretation, but I think some of the questions asked can be seen and answered within a more general development perspective.
General
Building on the natural language interface used with Mathematica would
be a great project. Any advice if this is possible?
This can be done using functional parsers. See these blog posts with detailed examples:
"Natural language processing with functional parsers",
"Simple time series conversational engine".
Here is a general answer / discussion on creating Domain Specific Languages (DSLs):
Extending and maintaining grammars
The idea is to be able to add to the grammar rules of the main natural
language parser so that the natural language interface can be extended
with new facts, phrases and functionality. Can this be done, or do you
have to make a custom parser from scratch to handle the whole new
language using the (ample) framework of language datatypes provided by
the build in interpreters?
The package "FunctionalParsers.m" can produce parsers from Extended Backus-Naur Form (EBNF) of the grammars. With that package designing grammars and adding new rules to grammars becomes a much easier task.
(Three years ago I worked with the Nuance Speech Recognition System and I was feeding that system with grammars derived from text corpuses and data bases using Mathematica.)
On the concrete example
Similar to the concrete example in the question:
"set mylist to {l}"
"the head of the first element of mylist"
the time series conversational engine can work with the following sequence of commands:
"load data file '~/example.csv'"
"least squares fit with x+Sin[x]"
"find bottom outliers"
(See this movie also linked in the mentioned blog post.)
Code
Using the concrete example in the question I programmed the following parsing and interpretation. I did not try to be as complete as possible, just to provide a good enough example.
Load the package:
Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/FunctionalParsers.m"]
EBNF
Here is a grammar that reflects the concrete example:
ebnfCode = "
<command> = <assignment> | <position-query> ;
<assignment> = ( 'set' &> <var-name> , ( 'to' | 'as' ) &> <value> ) | ( 'assign' &> <value> , 'to' &> <var-name> ) <@ Assign ;
<var-name> = '_WordString' <@ Var ;
<value> = '_String' <@ Value ;
<position-query> = ( 'element' &> <pos-int> | [ 'the' ] &> <pos-word> <& 'element' | <pos-head> ) , 'of' &> ( <position-query> | <var-name> ) <@ PositionQuery ;
<pos-int> = 'Range[1,100]' <@ PosInt ;
<pos-word> = 'first' | 'second' | 'third' | 'tenth' <@ PosWord ;
<pos-head> = [ 'the' ] &> 'head' <@ PosInt@0& ;
";
Note the recursive definition of the <position-query>
rule.
Parser generation
Generate the parsers from EBNF code.
GenerateParsersFromEBNF[ParseToEBNFTokens[ebnfCode]];
As mentioned above with this function we can easily extend existing grammars.
Verification
statements = { "assign {3,4,{4,a},5,k} to mylist",
"element 4 of mylist", "third element of mylist",
"head of mylist" , "second element of element 3 of mylist"};
ParsingTestTable[pCOMMAND, statements, "Layout" -> "Vertical"]

Interpretation
At this point we write interpreters of the parsing output.
Block[{Assign, PositionQuery, PosWord, wordToIntRules},
wordToIntRules =
Thread[{"first", "second", "third", "tenth"} -> {1, 2, 3, 10}];
Assign[parsed_] :=
Block[{varName, value},
varName = First[Cases[parsed, Var[v_] :> v, Infinity]];
ClearAll[Evaluate@varName];
value =
ToExpression@First@Cases[parsed, Value[v_] :> v, Infinity];
With[{sn = ToExpression@varName, v = value},
OwnValues[sn] = {HoldPattern[sn] :> v}]
];
PositionQuery[parsedArg_] :=
Block[{parsed = parsedArg},
parsed = parsed /. PosWord[pw_] :> PosInt[pw /. wordToIntRules];
If[Length[Cases[parsed, Var[v_] :> v, Infinity]] > 0,
Part[
ToExpression@First@Cases[parsed, Var[v_] :> v, Infinity],
ToExpression@First@Cases[parsed, PosInt[p_] :> p, Infinity]],
Part[
First@DeleteCases[parsed, _PosInt],
ToExpression@First@Cases[parsed, PosInt[p_] :> p, Infinity]
]
]
];
statements = { "assign {3,4,{4,a},5,k} to mylist",
"element 4 of mylist", "third element of mylist",
"head of mylist" , "second element of element 3 of mylist"};
ParsingTestTable[pCOMMAND, statements, "Layout" -> "Vertical"]
]

Symbol
, not1
. While I think that what you ask for is impossible, these confusing instructions could make it doubly so! $\endgroup$