Architecture
Overview
This section dives into the architectural components of Tansive, focusing on Tansive Server and Tangent.
Tansive Server hosts the control and management plane of Tansive - the Catalog server, Policy engine, and the coordination of Skill executions.
Tangent is the execution plane that actually executes the Skills and mediates Skill to Skill invocations, access the contextual and global Resources, etc.
The overall architecture of the platform is as shown below:
Fig. 1 - Architecture Overview
Tansive Server
Tansive Server hosts the Catalog and exposes a REST API interface for all CRUD operations on the Catalog including SkillSets, Resources, Namespaces, Variants, and Views. The Catalog is implemented in a PostgreSQL database that ensures ACID-compliant, transactional updates. The Resource and SkillSet definitions are stored as content-addressable blobs allowing for caching at the Tangent or other intermediate nodes as the deployment topology may demand.
The server also embeds the policy engine that evaluates View scopes and rules, and issues scoped JWT tokens at session start. It serves as source of truth for session status, outcome, and audit logs pertaining to each session.
Users and other systems such as CI/CD interact primarily with the Tansive Server. Users will be directed to the Tangent when running interactive Skills, but it will be implicit to the user.
In release 0.1.0-alpha.1, Tansive supports a single-user-mode enabling local deployments, demos, and initial integration. Multi-user system with user identity and authentication is targeted for the next point release. Session and View scoping, however, are fully featured and secure, laying the groundwork for transition to production use.
Tangent - Tansive Runtime Agent
Tangent is the execution node in the Tansive platform. When the Tangent starts up, it reads the server endpoint from a configuration file and registers with the Tangent Server along with its capabilities. As of this early release, capabilities includes just the runner type. This is left extensible by design.
When the Tansive Server creates a session to run a Skill, it issues a scoped JWT token bound to a specific View. The session is then handed off to Tangent.
Tangent uses the token to extract the applicable policy, retrieves the referenced SkillSet, and executes the requested Skill using the specified runner.
The Tangent also runs a SkillSetService where it exposes REST APIs over a Unix Domain Socket for Skills to invoke other Skills, access Context and Resources.
Runners - system.stdiorunner
Tangent has one or more runners capable of executing Skills. As of current release, Tangent supports only the system.stdiorunner
. This runner executes each invocation of a Skill by forking the script or executable implementing the Skill in a separate local process. It invokes the process passing input arguments as a JSON string. It then pipes stdout
and stderr
of the process back to the caller as the output of the Skill. This runner, while simple in its operation, can support a wide variety of tools written in any language and LLM-backed agents. The runner itself is a lightweight Goroutine. Each invocation of a Skill is handled by a distinct runner instance in its own Goroutine.
The system.stdiorunner
looks for scripts and executables in a local directory in the Tangent. Support for distribution of signed runnable objects (such as scripts and executable binaries) are in the near-term Tansive roadmap as well as support for a direct pull from a Git repository of reviewed and released code.
Currently, the system.stdiorunner
supports direct execution under default
security. Sandboxed mode employing a namespace jail will be supported in the next point release. Depending on usage and feedback, addition security modes will be supported.
While
system.stdiorunner
is quite capable by itself, support for runners that can launch a Skill in a serverless function or interface with a long-running service is in the roadmap.
Skill Invocations and Skill Inputs
When a session is initiated, a SkillSet and a launch Skill within the SkillSet needs to be provided. The launch Skill is what will start the session. In Agent driven workflows such as the Kubernetes demo, this is typically an LLM-backed Agent. In the demo, the k8s_troubleshooter
was launched via the command:
tansive session create /skillsets/kubernetes-demo/k8s_troubleshooter --view dev-view --input-args '{"prompt":"An order-placement issue is affecting our e-commerce system. Use the provided tools to identify the root cause and take any necessary steps to resolve it.","model":"gpt4o"}'
The command calls session create with the path to the Skill. /skillsets/kubernetes-demo
is the SkillSet and k8s_troubleshooter
is the Skill within the SkillSet that was invoked. We also passed the name of the View via the --view
flag. The input argument was a JSON string matching the input schema of the Skill.
Anatomy of a Skill
We will now dissect the implementation of the k8s_troubleshooter
which is a Python script. This script could have been written in Node, Go or any other language. We are only practically limited by the support for the OpenAI SDK since this is an LLM backed agent. The whole script is in skillset_scripts/run_llm.py
in the cloned repo for reference, while we'll only analyze snippets.
Initialization
We'll first analyze the script startup.
def main():
if len(sys.argv) < 2:
print("No args provided", file=sys.stderr)
sys.exit(1)
try:
args = json.loads(sys.argv[1])
except Exception as e:
print(f"Failed to parse input args: {e}", file=sys.stderr)
sys.exit(1)
session_id = args.get("sessionID")
invocation_id = args.get("invocationID")
socket_path = args.get("serviceEndpoint")
input_args = args.get("inputArgs", {})
model_name = input_args.get("model")
if not isinstance(model_name, str) or not model_name:
print("model not provided in input args", file=sys.stderr)
sys.exit(1)
client = SkillSetClient(
socket_path, dial_timeout=10.0, max_retries=3, retry_delay=0.1
)
...
The script obtains the input argument as a JSON string and obtains the following:
-
sessionID
: This is a unique UUID assigned to each session. The Skill must include it in all communication with the Tangent such as when invoking Skills or fetching a list of Skills to provide as tools to the LLM. -
invocationID
: This is a unique UUID issued to every invocation of a Skill within a session. TheinvocationID
is used to correlate lineage of chained Skill to Skill calls and to also prevent spurious or malicious Skill or Context invocations. Tangent internally maintains a DAG of Skill calls based oninvocationID
to prevent loops and limit call depth in chained calls. TheinvocationID
is required to be provided in all calls back to Tangent. -
serviceEndpoint
: The Tangent operates a SkillSet service that is bound to a Unix Domain Socket on the node. The path to the UDS is provided so the Skill can connect to it to request for list of Skills, obtain Context, or call other Skills. -
inputArgs
: This is the JSON object passed by the caller of the Skill. In our example, the JSON containing the prompt and the model name was provided tok8s_troubleshooter
. -
sessionVariables
: While not used in this Skill example,sessionVariables
is a JSON object that is provided at the time of session creation and that will be made available to all Skill invocations during that session. This can be used to set configuration values, boundary conditions, etc.
Using the serviceEndpoint
, the k8s_troubleshooter
creates a SkillSetClient object binding to the Unix Domain Socket of Tangent's SkillSetService.
The SkillSetService exposes a simple REST API for Skills to invoke other Skills, and access Context and Resources. Tansive provides an official Python SDK https://github.com/tansive/skillset-sdk-python that wraps the REST API. It can be installed with
pip install tansive-skillset-sdk
Retrieving Context
Let's dig further in to the k8s_troubleshooter
script:
try:
model_info = client.get_context(session_id, invocation_id, model_name)
except Exception as e:
print(f"failed to get model info: {e}", file=sys.stderr)
sys.exit(1)
The script now obtains the information about the model that was provided in the Context. For reference, we provided this information when we set up the skillset with the skillset_k8s.yaml
script:
context:
- name: claude
schema:
type: object
properties:
apiKey:
type: string
model:
type: string
required:
- apiKey
- model
value:
apiKey: { { .ENV.CLAUDE_API_KEY } }
model: claude-3-7-sonnet-latest
- name: gpt4o
# ... (other properties for gpt4o)
The call to get_context
must include both the sessionID
and the invocationID
.
Retrieve Skills to pass as Tool List to LLMS
The k8s_troubleshooter
implements a get_skills
method that is used to get a list of tools to provide to the LLM.
def get_skills(client: SkillSetClient, session_id: str) -> list[dict]:
try:
skills = client.get_skills(session_id)
except Exception as e:
print(f"failed to get skills: {e}", file=sys.stderr)
sys.exit(1)
result = []
for skill in skills:
try:
schema = skill["inputSchema"]
except KeyError as e:
print(f"missing required field in skill: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"failed to process input schema: {e}", file=sys.stderr)
sys.exit(1)
result.append(
{
"type": "function",
"function": {
"name": skill["name"],
"description": skill.get("description", ""),
"parameters": schema,
},
}
)
return result
This function calls the get_skills
method of the SkillServiceClient and morphs the returned Skill name, input schema, and the description (provided in the annotations.llm:description
field) in to a tool format expected by the OpenAI SDK. This pattern is used to get a list of all Skills that can be used as tools by an LLM backed agent.
Execute Tool Calls by invoking Skills
The Agent then parses tool call request from the LLM and invokes the corresponding Skill via the SkillSet service.
for tool_call in message.tool_calls:
try:
tool_args = json.loads(tool_call.function.arguments)
result = client.invoke_skill(
session_id, invocation_id, tool_call.function.name, tool_args
)
tool_response = json.dumps(result.output)
except Exception as e:
print(f"Tool call failed: {e}", file=sys.stderr)
sys.exit(1)
The invoke_skill
method is called with the SessionID
, InvocationID
, name of the Skill, and the input arguments.
At this point Tangent does the following:
- Evaluates the View policy that is bound to this session to determine if the Skill can be invoked or not. It performs this check by comparing if the action exported by the Skill matches any of the allowed actions in the View policy.
- Run the Skill if the policy permits the Skill to be called.
The two operations above are indicated by Tansive in the interactive output available to the user.
[00:00.003] [system.stdiorunner] ▶ running skill: k8s_troubleshooter
[00:03.018] k8s_troubleshooter ▶ 🤔 Thinking: I'll help you identify and resolve the order-placement issue in your e-commerce system. Let me first check the status of all pods in your Kubernetes cluster to see if there are any obvious issues.
[00:03.020] ▶ requested skill: list_pods
[00:03.021] 🛡️ allowed by Tansive policy: view 'dev-view' authorizes actions - [kubernetes.pods.list] - to use this skill
[00:03.021] [system.stdiorunner] ▶ running skill: list_pods
[00:03.047] list_pods ▶ NAME READY STATUS RESTARTS AGE
api-server-5f5b7f77b7-zx9qs 1/1 Running 0 2d
[00:03.047] list_pods ▶ web-frontend-6f6f9d7b7b-xv2mn 1/1 Running 1 5h
cache-worker-7d7d9d9b7b-pv9lk 1/1 Running 0 1d
orders-api-7ff9d44db7-abcde 0/1 CrashLoopBackOff 12 3h
# Filter applied: null
[00:03.047] [system.stdiorunner] ▶ skill completed successfully: list_pods
at [00:03:20], the k8s_troubleshooter
requested invocation of the skill list_pods
. In the next line, Tansive provides the name of the View that authorized this Skill invocation along with the action (kubernetes.pods.list
) that was authorized.
Similar information is captured in the Audit logs along with the full approving rule set in the View in a tamper-evident format, so it can be used for later investigation and compliance.
The tamper-evident log trail is a key part of how Tansive ensure observability of agent behavior.
The following diagram conceptually explains the chained Skill invocation flow:
Fig. 2 - Skill Invocation Chain
The Tangent, Policy Evaluator, and Runner are components within Tangent and are part of the same process. Each Skill invocation forks a new process to execute the script or binary. Runners are lightweight Goroutines - each Skill invocation is associated with a separate instance of the runner in its own Goroutine.
Session Types
There are two modes of Session initiation:
- Non-interactive session: Programmatically initiated by Applications and services that want to delegate a task to an agentic workflow. In this mode, the Tansive Server creates a session and directly hands off to a Tangent capable of executing the requested SkillSet.
- Interactive Session: This is initiated by a user by who explicitly requests a session from the Tansive Server. In this mode, the server uses the user as an intermediary to securely hand off the session to the selected Tangent.
Note: In the current Alpha release, only Interactive Sessions are supported. This enables demos and early enablement where visibility into how things work is paramount. Non-interactive sessions are comparatively simpler and are on the immediate-term roadmap.
Next Steps
Now that you understand the architecture, you can explore how to deploy and configure Tansive in your environment. The next sections will cover installation, configuration, and operational aspects of running Tansive.