diff --git a/IPython/core/alias.py b/IPython/core/alias.py
index 752e8f4..c05db21 100644
--- a/IPython/core/alias.py
+++ b/IPython/core/alias.py
@@ -104,7 +104,7 @@ class AliasManager(Configurable):
 
     default_aliases = List(default_aliases(), config=True)
     user_aliases = List(default_value=[], config=True)
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
 
     def __init__(self, shell, config=None):
         super(AliasManager, self).__init__(config=config)
diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py
index 2f26496..0924510 100755
--- a/IPython/core/builtin_trap.py
+++ b/IPython/core/builtin_trap.py
@@ -37,7 +37,7 @@ BuiltinUndefined = __BuiltinUndefined()
 
 class BuiltinTrap(Configurable):
 
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
 
     def __init__(self, shell):
         super(BuiltinTrap, self).__init__(None)
diff --git a/IPython/core/extensions.py b/IPython/core/extensions.py
index 03bb258..82f84cd 100644
--- a/IPython/core/extensions.py
+++ b/IPython/core/extensions.py
@@ -28,8 +28,32 @@ from IPython.utils.traitlets import Instance
 #-----------------------------------------------------------------------------
 
 class ExtensionManager(Configurable):
+    """A class to manage IPython extensions.
 
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    An IPython extension is an importable Python module that has
+    a function with the signature::
+
+        def load_ipython_extension(ipython):
+            # Do things with ipython
+
+    This function is called after your extension is imported and the 
+    currently active :class:`InteractiveShell` instance is passed as
+    the only argument.  You can do anything you want with IPython at
+    that point, including defining new magic and aliases, adding new
+    components, etc.
+
+    The :func:`load_ipython_extension` will be called again is you 
+    load or reload the extension again.  It is up to the extension
+    author to add code to manage that.
+
+    You can put your extension modules anywhere you want, as long as
+    they can be imported by Python's standard import mechanism.  However,
+    to make it easy to write extensions, you can also put your extensions
+    in ``os.path.join(self.ipython_dir, 'extensions')``.  This directory
+    is added to ``sys.path`` automatically.
+    """
+
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
 
     def __init__(self, shell, config=None):
         super(ExtensionManager, self).__init__(config=config)
@@ -54,28 +78,6 @@ class ExtensionManager(Configurable):
     def load_extension(self, module_str):
         """Load an IPython extension by its module name.
 
-        An IPython extension is an importable Python module that has
-        a function with the signature::
-
-            def load_ipython_extension(ipython):
-                # Do things with ipython
-
-        This function is called after your extension is imported and the 
-        currently active :class:`InteractiveShell` instance is passed as
-        the only argument.  You can do anything you want with IPython at
-        that point, including defining new magic and aliases, adding new
-        components, etc.
-
-        The :func:`load_ipython_extension` will be called again is you 
-        load or reload the extension again.  It is up to the extension
-        author to add code to manage that.
-
-        You can put your extension modules anywhere you want, as long as
-        they can be imported by Python's standard import mechanism.  However,
-        to make it easy to write extensions, you can also put your extensions
-        in ``os.path.join(self.ipython_dir, 'extensions')``.  This directory
-        is added to ``sys.path`` automatically.
-
         If :func:`load_ipython_extension` returns anything, this function
         will return that object.
         """
@@ -121,4 +123,4 @@ class ExtensionManager(Configurable):
 
     def _call_unload_ipython_extension(self, mod):
         if hasattr(mod, 'unload_ipython_extension'):
-            return mod.unload_ipython_extension(self.shell)
\ No newline at end of file
+            return mod.unload_ipython_extension(self.shell)
diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py
index b01d68f..af27eb9 100644
--- a/IPython/core/iplib.py
+++ b/IPython/core/iplib.py
@@ -18,6 +18,7 @@ from __future__ import with_statement
 from __future__ import absolute_import
 
 import __builtin__
+import abc
 import bdb
 import codeop
 import exceptions
@@ -43,6 +44,7 @@ from IPython.core.extensions import ExtensionManager
 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
 from IPython.core.logger import Logger
 from IPython.core.magic import Magic
+from IPython.core.plugin import PluginManager
 from IPython.core.prefilter import PrefilterManager
 from IPython.core.prompts import CachedOutput
 from IPython.core.usage import interactive_usage, default_banner
@@ -286,6 +288,7 @@ class InteractiveShell(Configurable, Magic):
     builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
     display_trap = Instance('IPython.core.display_trap.DisplayTrap')
     extension_manager = Instance('IPython.core.extensions.ExtensionManager')
+    plugin_manager = Instance('IPython.core.plugin.PluginManager')
 
     def __init__(self, config=None, ipython_dir=None, usage=None,
                  user_ns=None, user_global_ns=None,
@@ -341,6 +344,7 @@ class InteractiveShell(Configurable, Magic):
         self.init_magics()
         self.init_pdb()
         self.init_extension_manager()
+        self.init_plugin_manager()
         self.hooks.late_startup_hook()
 
     @classmethod
@@ -1759,12 +1763,15 @@ class InteractiveShell(Configurable, Magic):
         self.ns_table['alias'] = self.alias_manager.alias_table,
 
     #-------------------------------------------------------------------------
-    # Things related to extensions
+    # Things related to extensions and plugins
     #-------------------------------------------------------------------------
 
     def init_extension_manager(self):
         self.extension_manager = ExtensionManager(self, config=self.config)
 
+    def init_plugin_manager(self):
+        self.plugin_manager = PluginManager(config=self.config)
+
     #-------------------------------------------------------------------------
     # Things related to the running of code
     #-------------------------------------------------------------------------
@@ -2509,3 +2516,8 @@ class InteractiveShell(Configurable, Magic):
         self.restore_sys_module_state()
 
 
+class InteractiveShellABC(object):
+    """An abstract base class for InteractiveShell."""
+    __metaclass__ = abc.ABCMeta
+
+InteractiveShellABC.register(InteractiveShell)
diff --git a/IPython/core/plugin.py b/IPython/core/plugin.py
new file mode 100644
index 0000000..ee12abe
--- /dev/null
+++ b/IPython/core/plugin.py
@@ -0,0 +1,51 @@
+# encoding: utf-8
+"""IPython plugins.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+#  Copyright (C) 2010  The IPython Development Team
+#
+#  Distributed under the terms of the BSD License.  The full license is in
+#  the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from IPython.config.configurable import Configurable
+from IPython.utils.traitlets import Dict
+
+#-----------------------------------------------------------------------------
+# Main class
+#-----------------------------------------------------------------------------
+
+class PluginManager(Configurable):
+    """A manager for IPython plugins."""
+
+    plugins = Dict({})
+
+    def __init__(self, config=None):
+        super(PluginManager, self).__init__(config=config)
+
+    def register_plugin(self, name, plugin):
+        if not isinstance(plugin, Plugin):
+            raise TypeError('Expected Plugin, got: %r' % plugin)
+        if self.plugins.has_key(name):
+            raise KeyError('Plugin with name already exists: %r' % name)
+        self.plugins[name] = plugin
+
+    def unregister_plugin(self, name):
+        del self.plugins[name]
+
+    def get_plugin(self, name, default=None):
+        return self.plugins.get(name, default)
+
+
+class Plugin(Configurable):
+    """Base class for IPython plugins."""
+    pass
diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py
index 3fe16e3..10e366b 100755
--- a/IPython/core/prefilter.py
+++ b/IPython/core/prefilter.py
@@ -210,7 +210,7 @@ class PrefilterManager(Configurable):
     """
 
     multi_line_specials = CBool(True, config=True)
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
 
     def __init__(self, shell, config=None):
         super(PrefilterManager, self).__init__(config=config)
@@ -447,7 +447,7 @@ class PrefilterTransformer(Configurable):
     priority = Int(100, config=True)
     # Transformers don't currently use shell or prefilter_manager, but as we
     # move away from checkers and handlers, they will need them.
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
     prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
     enabled = Bool(True, config=True)
 
@@ -555,7 +555,7 @@ class PrefilterChecker(Configurable):
     """Inspect an input line and return a handler for that line."""
 
     priority = Int(100, config=True)
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
     prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
     enabled = Bool(True, config=True)
 
@@ -748,7 +748,7 @@ class PrefilterHandler(Configurable):
 
     handler_name = Str('normal')
     esc_strings = List([])
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
     prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
 
     def __init__(self, shell, prefilter_manager, config=None):
diff --git a/IPython/core/tests/test_plugin.py b/IPython/core/tests/test_plugin.py
new file mode 100644
index 0000000..92ff22b
--- /dev/null
+++ b/IPython/core/tests/test_plugin.py
@@ -0,0 +1,46 @@
+"""Tests for plugin.py"""
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from unittest import TestCase
+
+from IPython.core.plugin import Plugin, PluginManager
+
+#-----------------------------------------------------------------------------
+# Tests
+#-----------------------------------------------------------------------------
+
+class FooPlugin(Plugin):
+    pass
+
+
+class BarPlugin(Plugin):
+    pass
+
+
+class BadPlugin(object):
+    pass
+
+
+class PluginTest(TestCase):
+
+    def setUp(self):
+        self.manager = PluginManager()
+
+    def test_register_get(self):
+        self.assertEquals(None, self.manager.get_plugin('foo'))
+        foo = FooPlugin()
+        self.manager.register_plugin('foo', foo)
+        self.assertEquals(foo, self.manager.get_plugin('foo'))
+        bar = BarPlugin()
+        self.assertRaises(KeyError, self.manager.register_plugin, 'foo', bar)
+        bad = BadPlugin()
+        self.assertRaises(TypeError, self.manager.register_plugin, 'bad')
+
+    def test_unregister(self):
+        foo = FooPlugin()
+        self.manager.register_plugin('foo', foo)
+        self.manager.unregister_plugin('foo')
+        self.assertEquals(None, self.manager.get_plugin('foo'))
diff --git a/IPython/extensions/parallelmagic.py b/IPython/extensions/parallelmagic.py
index 127e212..e61d9a7 100755
--- a/IPython/extensions/parallelmagic.py
+++ b/IPython/extensions/parallelmagic.py
@@ -16,7 +16,7 @@
 
 import new
 
-from IPython.config.configurable import Configurable
+from IPython.core.plugin import Plugin
 from IPython.utils.traitlets import Bool, Any, Instance
 from IPython.utils.autoattr import auto_attr
 from IPython.testing import decorators as testdec
@@ -31,15 +31,15 @@ Use activate() on a MultiEngineClient object to activate it for magics.
 """
 
 
-class ParalleMagicComponent(Configurable):
+class ParalleMagic(Plugin):
     """A component to manage the %result, %px and %autopx magics."""
 
     active_multiengine_client = Any()
     verbose = Bool(False, config=True)
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
 
     def __init__(self, shell, config=None):
-        super(ParalleMagicComponent, self).__init__(config=config)
+        super(ParalleMagic, self).__init__(config=config)
         self.shell = shell
         self._define_magics()
         # A flag showing if autopx is activated or not
@@ -196,6 +196,7 @@ def load_ipython_extension(ip):
     """Load the extension in IPython."""
     global _loaded
     if not _loaded:
-        prd = ParalleMagicComponent(ip, config=ip.config)
+        plugin = ParalleMagic(ip, config=ip.config)
+        ip.plugin_manager.register_plugin('parallel_magic', plugin)
         _loaded = True
 
diff --git a/IPython/extensions/pretty.py b/IPython/extensions/pretty.py
index 52501d1..5e94db0 100644
--- a/IPython/extensions/pretty.py
+++ b/IPython/extensions/pretty.py
@@ -37,7 +37,7 @@ by doing::
 
 from IPython.core.error import TryNext
 from IPython.external import pretty
-from IPython.config.configurable import Configurable
+from IPython.core.plugin import Plugin
 from IPython.utils.traitlets import Bool, List, Instance
 from IPython.utils.io import Term
 from IPython.utils.autoattr import auto_attr
@@ -51,11 +51,11 @@ from IPython.utils.importstring import import_item
 _loaded = False
 
 
-class PrettyResultDisplay(Configurable):
+class PrettyResultDisplay(Plugin):
     """A component for pretty printing on steroids."""
 
     verbose = Bool(False, config=True)
-    shell = Instance('IPython.core.iplib.InteractiveShell')
+    shell = Instance('IPython.core.iplib.InteractiveShellABC')
 
     # A list of (type, func_name), like
     # [(dict, 'my_dict_printer')]
@@ -124,10 +124,10 @@ def load_ipython_extension(ip):
     """Load the extension in IPython as a hook."""
     global _loaded
     if not _loaded:
-        prd = PrettyResultDisplay(ip, config=ip.config)
-        ip.set_hook('result_display', prd, priority=99)
+        plugin = PrettyResultDisplay(ip, config=ip.config)
+        ip.set_hook('result_display', plugin, priority=99)
         _loaded = True
-        return prd
+        ip.plugin_manager.register_plugin('pretty_result_display', plugin)
 
 def unload_ipython_extension(ip):
     """Unload the extension."""
diff --git a/IPython/extensions/tests/test_pretty.py b/IPython/extensions/tests/test_pretty.py
index bef6b12..29e9752 100644
--- a/IPython/extensions/tests/test_pretty.py
+++ b/IPython/extensions/tests/test_pretty.py
@@ -17,8 +17,8 @@ Simple tests for :mod:`IPython.extensions.pretty`.
 
 from unittest import TestCase
 
-from IPython.core.component import Component, masquerade_as
-from IPython.core.iplib import InteractiveShell
+from IPython.config.configurable import Configurable
+from IPython.core.iplib import InteractiveShellABC
 from IPython.extensions import pretty as pretty_ext
 from IPython.external import pretty
 from IPython.testing import decorators as dec
@@ -29,9 +29,11 @@ from IPython.utils.traitlets import Bool
 # Tests
 #-----------------------------------------------------------------------------
 
-class InteractiveShellStub(Component):
+class InteractiveShellStub(Configurable):
     pprint = Bool(True)
 
+InteractiveShellABC.register(InteractiveShellStub)
+
 class A(object):
     pass
 
@@ -41,12 +43,8 @@ def a_pprinter(o, p, c):
 class TestPrettyResultDisplay(TestCase):
 
     def setUp(self):
-        self.ip = InteractiveShellStub(None)
-        # This allows our stub to be retrieved instead of the real
-        # InteractiveShell
-        masquerade_as(self.ip, InteractiveShell)
-        self.prd = pretty_ext.PrettyResultDisplay(self.ip,
-                                                  name='pretty_result_display')
+        self.ip = InteractiveShellStub()
+        self.prd = pretty_ext.PrettyResultDisplay(self.ip, config=None)
 
     def test_for_type(self):
         self.prd.for_type(A, a_pprinter)
@@ -77,7 +75,8 @@ a
 b
 
 ip = get_ipython()
-prd = ip.extension_manager.load_extension('pretty')
+ip.extension_manager.load_extension('pretty')
+prd = ip.plugin_manager.get_plugin('pretty_result_display')
 prd.for_type(A, a_pretty_printer)
 prd.for_type_by_name(B.__module__, B.__name__, b_pretty_printer)
 
diff --git a/IPython/testing/globalipapp.py b/IPython/testing/globalipapp.py
index 714554e..56dd52a 100644
--- a/IPython/testing/globalipapp.py
+++ b/IPython/testing/globalipapp.py
@@ -137,7 +137,7 @@ def start_ipython():
 
     # Create and initialize our test-friendly IPython instance.
     shell = iplib.InteractiveShell(
-        parent=None, config=config, 
+        config=config, 
         user_ns=ipnsdict(), user_global_ns={}
     )
 
diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py
index 67af9c2..eb28b3b 100644
--- a/IPython/utils/traitlets.py
+++ b/IPython/utils/traitlets.py
@@ -1023,3 +1023,25 @@ class List(Instance):
 
         super(List,self).__init__(klass=list, args=args, 
                                   allow_none=allow_none, **metadata)
+
+
+class Dict(Instance):
+    """An instance of a Python dict."""
+
+    def __init__(self, default_value=None, allow_none=True, **metadata):
+        """Create a dict trait type from a dict.
+
+        The default value is created by doing ``dict(default_value)``, 
+        which creates a copy of the ``default_value``.
+        """
+        if default_value is None:
+            args = ((),)
+        elif isinstance(default_value, dict):
+            args = (default_value,)
+        elif isinstance(default_value, SequenceTypes):
+            args = (default_value,)
+        else:
+            raise TypeError('default value of Dict was %s' % default_value)
+
+        super(Dict,self).__init__(klass=dict, args=args, 
+                                  allow_none=allow_none, **metadata)